Compare commits
131 Commits
19e51a2b12
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
babb22bf88
|
|||
|
468127996c
|
|||
|
|
b6b6b20fe9 | ||
|
|
f75b512837 | ||
|
|
7412aca81c | ||
|
|
02cf349075 | ||
|
|
67b988e6d2 | ||
|
|
d2c13c2a4c | ||
|
|
498a38cf74 | ||
|
|
13a1c93ea6 | ||
|
|
38a0a7ed27 | ||
|
|
70c3eabdb1 | ||
|
|
4cd3b68697 | ||
|
|
1140fc65ba | ||
|
|
5753548b44 | ||
|
|
806b386cdb | ||
|
|
d266059abe | ||
|
|
b1dd26fbc4 | ||
|
|
c8c27af7c3 | ||
|
|
ba01492790 | ||
|
|
94368d5021 | ||
|
|
ec793a9fdb | ||
|
|
37f622f226 | ||
|
|
87f74eaeda | ||
|
|
8406979e71 | ||
|
|
6e80f57739 | ||
|
|
1532fb360b | ||
|
|
180af8c844 | ||
|
|
311e2bc18d | ||
|
|
bfd15e1a81 | ||
|
|
d2d333a958 | ||
|
|
60a22ff330 | ||
|
|
946e5c47d4 | ||
|
|
5d450ab3c7 | ||
|
|
a509a878e9 | ||
|
|
49e2988e37 | ||
|
|
db8d16fd6e | ||
|
|
d47848cb93 | ||
| 873fca763d | |||
|
|
e99407132f | ||
| 67d63faeca | |||
| 871b053561 | |||
|
|
81deb1fff8 | ||
|
|
59560ebad1 | ||
|
|
67a6600c05 | ||
|
|
f3e30558e4 | ||
|
|
e46ab6ad7d | ||
|
|
b6fd48f282 | ||
| c2edc26d8e | |||
| 76417b13d4 | |||
| 47f36e3ff9 | |||
|
|
10e75b6d55 | ||
|
|
fe69214e77 | ||
|
|
020c7fe6f5 | ||
|
|
44bc15db40 | ||
|
|
549b8d624e | ||
|
|
669fda298c | ||
| 75bb48d00e | |||
|
|
21ecd344ba | ||
|
|
8c8162dbf6 | ||
|
|
5e20ec19ff | ||
|
|
5eb83760cd | ||
|
|
8fea43d6ba | ||
|
|
678c7aa3a4 | ||
|
|
e13c8c340f | ||
|
|
dc659538d3 | ||
|
|
eb099d1220 | ||
|
|
1ad1f3b215 | ||
|
|
063065b21a | ||
|
|
00016ba4e1 | ||
| 11834de220 | |||
|
|
b411a0fa09 | ||
|
|
1561ba2e38 | ||
| 91a61643bd | |||
| b6e05cb0b9 | |||
| 1507b91463 | |||
|
|
bd2bb6325e | ||
|
|
3f0a85d794 | ||
|
|
74d05211d6 | ||
|
|
7ad06614fe | ||
|
|
163a85a468 | ||
|
|
a51711e4bb | ||
|
|
ae312339a3 | ||
|
|
a429bb53ce | ||
|
|
a549880df1 | ||
|
|
9c1be72db0 | ||
|
|
747f70d80a | ||
|
|
b482443e79 | ||
|
|
676ec9cb21 | ||
|
|
aae97dce3d | ||
|
|
c72a3eefde | ||
|
|
86b88cf4b7 | ||
|
|
7ffa43f0e2 | ||
|
|
b3e218bd7d | ||
|
|
b06af3718c | ||
|
|
9324a52ce0 | ||
|
|
cc93f5eea4 | ||
|
|
c3d10bd410 | ||
|
|
d2cd79185b | ||
|
|
4df640268f | ||
|
|
d9f1016bd5 | ||
|
|
c9aa1cca09 | ||
|
|
f980037bfd | ||
|
|
8f3dea5427 | ||
|
|
b8fe3577c9 | ||
|
|
d4ea40a4a2 | ||
|
|
0afe061224 | ||
|
|
58816c804a | ||
|
|
e69213f987 | ||
|
|
f986eb51ec | ||
|
|
9d1332d3a3 | ||
|
|
83c1749eed | ||
|
|
a26d5581c4 | ||
|
|
1652d44f5f | ||
|
|
6815808d32 | ||
|
|
91bdcebb1a | ||
|
|
0e84b57e53 | ||
|
|
876b9c3601 | ||
|
|
6995f415d3 | ||
|
|
371e686076 | ||
|
|
a20a896582 | ||
|
|
e1a3421212 | ||
|
|
7392cd6574 | ||
|
|
71feb11b2e | ||
|
|
c0fdf20224 | ||
|
|
00b68859ff | ||
|
|
1db8c8c6ab | ||
|
|
af97ffffa5 | ||
|
|
39191957ea | ||
|
|
d77e508e9c | ||
|
|
4aa9ee7735 |
13
.github/workflows/gradle.yml
vendored
13
.github/workflows/gradle.yml
vendored
@@ -10,13 +10,14 @@ jobs:
|
||||
steps:
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@v4
|
||||
- name: Validate Gradle Wrapper
|
||||
uses: gradle/actions/wrapper-validation@v3
|
||||
- name: Set up JDK 17
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Set up Gradle
|
||||
uses: gradle/actions/setup-gradle@v4
|
||||
- name: Set up JDK 21
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
java-version: 17
|
||||
distribution: 'temurin'
|
||||
cache: 'gradle'
|
||||
java-version: 21
|
||||
distribution: 'zulu'
|
||||
- name: Build with Gradle
|
||||
run: ./gradlew build
|
||||
|
||||
@@ -61,6 +61,7 @@ tasks {
|
||||
o.encoding = "UTF-8"
|
||||
o.source = "17"
|
||||
|
||||
o.use()
|
||||
o.links(
|
||||
"https://www.slf4j.org/apidocs/",
|
||||
"https://guava.dev/releases/${libs.guava.get().version}/api/docs/",
|
||||
@@ -69,7 +70,7 @@ tasks {
|
||||
"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}/",
|
||||
"https://www.javadocs.dev/com.github.ben-manes.caffeine/caffeine/${libs.caffeine.get().version}/",
|
||||
)
|
||||
|
||||
o.tags(
|
||||
|
||||
@@ -14,7 +14,6 @@ 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;
|
||||
@@ -68,8 +67,8 @@ public class PluginAnnotationProcessor extends AbstractProcessor {
|
||||
|
||||
Name qualifiedName = ((TypeElement) element).getQualifiedName();
|
||||
|
||||
if (Objects.equals(pluginClassFound, qualifiedName.toString())) {
|
||||
if (!warnedAboutMultiplePlugins) {
|
||||
if (pluginClassFound != null) {
|
||||
if (!pluginClassFound.equals(qualifiedName.toString()) && !warnedAboutMultiplePlugins) {
|
||||
environment.getMessager()
|
||||
.printMessage(Diagnostic.Kind.WARNING, "Velocity does not yet currently support "
|
||||
+ "multiple plugins. We are using " + pluginClassFound
|
||||
|
||||
@@ -7,8 +7,10 @@
|
||||
|
||||
package com.velocitypowered.api.command;
|
||||
|
||||
import com.mojang.brigadier.suggestion.Suggestions;
|
||||
import com.velocitypowered.api.event.command.CommandExecuteEvent;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.function.Predicate;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
@@ -116,6 +118,27 @@ public interface CommandManager {
|
||||
*/
|
||||
CompletableFuture<Boolean> executeImmediatelyAsync(CommandSource source, String cmdLine);
|
||||
|
||||
/**
|
||||
* Asynchronously collects suggestions to fill in the given command {@code cmdLine}.
|
||||
* Returns only the raw completion suggestions without tooltips.
|
||||
*
|
||||
* @param source the source to execute the command for
|
||||
* @param cmdLine the partially completed command
|
||||
* @return a {@link CompletableFuture} eventually completed with a {@link List}, possibly empty
|
||||
*/
|
||||
CompletableFuture<List<String>> offerSuggestions(CommandSource source, String cmdLine);
|
||||
|
||||
/**
|
||||
* Asynchronously collects suggestions to fill in the given command {@code cmdLine}.
|
||||
* Returns the brigadier {@link Suggestions} with tooltips for each result.
|
||||
*
|
||||
* @param source the source to execute the command for
|
||||
* @param cmdLine the partially completed command
|
||||
* @return a {@link CompletableFuture} eventually completed with {@link Suggestions}, possibly
|
||||
* empty
|
||||
*/
|
||||
CompletableFuture<Suggestions> offerBrigadierSuggestions(CommandSource source, String cmdLine);
|
||||
|
||||
/**
|
||||
* Returns an immutable collection of the case-insensitive aliases registered
|
||||
* on this manager.
|
||||
|
||||
@@ -27,7 +27,7 @@ public interface CommandSource extends Audience, PermissionSubject {
|
||||
* for more information on the format.
|
||||
**/
|
||||
default void sendRichMessage(final @NotNull String message) {
|
||||
this.sendMessage(MiniMessage.miniMessage().deserialize(message));
|
||||
this.sendMessage(MiniMessage.miniMessage().deserialize(message, this));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -43,7 +43,7 @@ public interface CommandSource extends Audience, PermissionSubject {
|
||||
final @NotNull String message,
|
||||
final @NotNull TagResolver @NotNull... resolvers
|
||||
) {
|
||||
this.sendMessage(MiniMessage.miniMessage().deserialize(message, resolvers));
|
||||
this.sendMessage(MiniMessage.miniMessage().deserialize(message, this, resolvers));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -60,7 +60,8 @@ 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
|
||||
* @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 handler the handler to register
|
||||
* @param <E> the event type to handle
|
||||
*/
|
||||
|
||||
@@ -12,6 +12,14 @@ package com.velocitypowered.api.event;
|
||||
*/
|
||||
public enum PostOrder {
|
||||
|
||||
FIRST, EARLY, NORMAL, LATE, LAST, CUSTOM
|
||||
FIRST, EARLY, NORMAL, LATE, LAST,
|
||||
|
||||
/**
|
||||
* Previously used to specify that {@link Subscribe#priority()} should be used.
|
||||
*
|
||||
* @deprecated No longer required, you only need to specify {@link Subscribe#priority()}.
|
||||
*/
|
||||
@Deprecated
|
||||
CUSTOM
|
||||
|
||||
}
|
||||
|
||||
@@ -32,12 +32,9 @@ public @interface Subscribe {
|
||||
* The priority of this event handler. Priorities are used to determine the order in which event
|
||||
* handlers are called. The higher the priority, the earlier the event handler will be called.
|
||||
*
|
||||
* <p>Note that due to compatibility constraints, you must specify {@link PostOrder#CUSTOM}
|
||||
* in order to use this field.</p>
|
||||
*
|
||||
* @return the priority
|
||||
*/
|
||||
short priority() default Short.MIN_VALUE;
|
||||
short priority() default 0;
|
||||
|
||||
/**
|
||||
* Whether the handler must be called asynchronously. By default, all event handlers are called
|
||||
|
||||
@@ -9,7 +9,6 @@ package com.velocitypowered.api.event.command;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
|
||||
import com.google.common.annotations.Beta;
|
||||
import com.mojang.brigadier.tree.RootCommandNode;
|
||||
import com.velocitypowered.api.event.annotation.AwaitingEvent;
|
||||
import com.velocitypowered.api.proxy.Player;
|
||||
@@ -21,7 +20,6 @@ import com.velocitypowered.api.proxy.Player;
|
||||
* client.
|
||||
*/
|
||||
@AwaitingEvent
|
||||
@Beta
|
||||
public class PlayerAvailableCommandsEvent {
|
||||
|
||||
private final Player player;
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
|
||||
package com.velocitypowered.api.event.player;
|
||||
|
||||
import com.google.common.annotations.Beta;
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.velocitypowered.api.proxy.Player;
|
||||
import com.velocitypowered.api.proxy.server.RegisteredServer;
|
||||
@@ -18,7 +17,6 @@ import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
* available in {@link Player#getCurrentServer()}. Velocity will not wait on this event to finish
|
||||
* firing.
|
||||
*/
|
||||
@Beta
|
||||
public class ServerPostConnectEvent {
|
||||
private final Player player;
|
||||
private final RegisteredServer previousServer;
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* 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";
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,6 @@
|
||||
|
||||
package com.velocitypowered.api.event.proxy.server;
|
||||
|
||||
import com.google.common.annotations.Beta;
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.velocitypowered.api.proxy.server.RegisteredServer;
|
||||
import com.velocitypowered.api.proxy.server.ServerInfo;
|
||||
@@ -23,7 +22,6 @@ import org.jetbrains.annotations.NotNull;
|
||||
* @param registeredServer A {@link RegisteredServer} that has been registered.
|
||||
* @since 3.3.0
|
||||
*/
|
||||
@Beta
|
||||
public record ServerRegisteredEvent(@NotNull RegisteredServer registeredServer) {
|
||||
public ServerRegisteredEvent {
|
||||
Preconditions.checkNotNull(registeredServer, "registeredServer");
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
|
||||
package com.velocitypowered.api.event.proxy.server;
|
||||
|
||||
import com.google.common.annotations.Beta;
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.velocitypowered.api.proxy.server.RegisteredServer;
|
||||
import com.velocitypowered.api.proxy.server.ServerInfo;
|
||||
@@ -23,7 +22,6 @@ import org.jetbrains.annotations.NotNull;
|
||||
* @param unregisteredServer A {@link RegisteredServer} that has been unregistered.
|
||||
* @since 3.3.0
|
||||
*/
|
||||
@Beta
|
||||
public record ServerUnregisteredEvent(@NotNull RegisteredServer unregisteredServer) {
|
||||
public ServerUnregisteredEvent {
|
||||
Preconditions.checkNotNull(unregisteredServer, "unregisteredServer");
|
||||
|
||||
@@ -89,7 +89,11 @@ public enum ProtocolVersion implements Ordered<ProtocolVersion> {
|
||||
MINECRAFT_1_20_5(766, "1.20.5", "1.20.6"),
|
||||
MINECRAFT_1_21(767, "1.21", "1.21.1"),
|
||||
MINECRAFT_1_21_2(768, "1.21.2", "1.21.3"),
|
||||
MINECRAFT_1_21_4(769, "1.21.4");
|
||||
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");
|
||||
|
||||
private static final int SNAPSHOT_BIT = 30;
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
|
||||
package com.velocitypowered.api.proxy;
|
||||
|
||||
import com.velocitypowered.api.network.HandshakeIntent;
|
||||
import com.velocitypowered.api.network.ProtocolState;
|
||||
import com.velocitypowered.api.network.ProtocolVersion;
|
||||
import java.net.InetSocketAddress;
|
||||
@@ -60,4 +61,11 @@ public interface InboundConnection {
|
||||
* @return the protocol state of the connection
|
||||
*/
|
||||
ProtocolState getProtocolState();
|
||||
|
||||
/**
|
||||
* Returns the initial intent for the connection.
|
||||
*
|
||||
* @return the intent of the connection
|
||||
*/
|
||||
HandshakeIntent getHandshakeIntent();
|
||||
}
|
||||
|
||||
@@ -29,6 +29,7 @@ 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;
|
||||
@@ -48,7 +49,7 @@ public interface Player extends
|
||||
/* Fundamental Velocity interfaces */
|
||||
CommandSource, InboundConnection, ChannelMessageSource, ChannelMessageSink,
|
||||
/* Adventure-specific interfaces */
|
||||
Identified, HoverEventSource<HoverEvent.ShowEntity>, Keyed, KeyIdentifiable {
|
||||
Identified, HoverEventSource<HoverEvent.ShowEntity>, Keyed, KeyIdentifiable, Sound.Emitter {
|
||||
|
||||
/**
|
||||
* Returns the player's current username.
|
||||
@@ -383,8 +384,12 @@ public interface Player extends
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* <b>This method is not currently implemented in Velocity
|
||||
* and will not perform any actions.</b>
|
||||
*
|
||||
* @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>
|
||||
*/
|
||||
@Override
|
||||
default void playSound(@NotNull Sound sound) {
|
||||
@@ -393,8 +398,11 @@ public interface Player extends
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* <b>This method is not currently implemented in Velocity
|
||||
* and will not perform any actions.</b>
|
||||
* @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>
|
||||
*/
|
||||
@Override
|
||||
default void playSound(@NotNull Sound sound, double x, double y, double z) {
|
||||
@@ -403,18 +411,28 @@ public interface Player extends
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* <b>This method is not currently implemented in Velocity
|
||||
* and will not perform any actions.</b>
|
||||
* <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.
|
||||
*/
|
||||
@Override
|
||||
default void playSound(@NotNull Sound sound, Sound.Emitter emitter) {
|
||||
default void playSound(@NotNull Sound sound, @NotNull Sound.Emitter emitter) {
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* <b>This method is not currently implemented in Velocity
|
||||
* and will not perform any actions.</b>
|
||||
* @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+.
|
||||
*/
|
||||
@Override
|
||||
default void stopSound(@NotNull SoundStop stop) {
|
||||
@@ -425,11 +443,40 @@ 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.
|
||||
*
|
||||
|
||||
@@ -41,6 +41,13 @@ public interface ProxyServer extends Audience {
|
||||
*/
|
||||
void shutdown();
|
||||
|
||||
/**
|
||||
* Returns whether the proxy is currently shutting down.
|
||||
*
|
||||
* @return {@code true} if the proxy is shutting down, {@code false} otherwise
|
||||
*/
|
||||
boolean isShuttingDown();
|
||||
|
||||
/**
|
||||
* Closes all listening endpoints for this server.
|
||||
* This includes the main minecraft listener and query channel.
|
||||
|
||||
@@ -148,4 +148,59 @@ public interface ProxyConfig {
|
||||
* @return read timeout (in milliseconds)
|
||||
*/
|
||||
int getReadTimeout();
|
||||
|
||||
/**
|
||||
* Get the rate limit for how fast a player can execute commands.
|
||||
*
|
||||
* @return the command rate limit (in milliseconds)
|
||||
*/
|
||||
int getCommandRatelimit();
|
||||
|
||||
/**
|
||||
* Get whether we should forward commands to the backend if the player is rate limited.
|
||||
*
|
||||
* @return whether to forward commands if rate limited
|
||||
*/
|
||||
boolean isForwardCommandsIfRateLimited();
|
||||
|
||||
/**
|
||||
* Get the kick limit for commands that are rate limited.
|
||||
* If this limit is 0 or less, the player will be not be kicked.
|
||||
*
|
||||
* @return the rate limited command rate limit
|
||||
*/
|
||||
int getKickAfterRateLimitedCommands();
|
||||
|
||||
/**
|
||||
* Get whether the proxy should kick players who are command rate limited.
|
||||
*
|
||||
* @return whether to kick players who are rate limited
|
||||
*/
|
||||
default boolean isKickOnCommandRateLimit() {
|
||||
return getKickAfterRateLimitedCommands() > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the rate limit for how fast a player can tab complete.
|
||||
*
|
||||
* @return the tab complete rate limit (in milliseconds)
|
||||
*/
|
||||
int getTabCompleteRatelimit();
|
||||
|
||||
/**
|
||||
* Get the kick limit for tab completes that are rate limited.
|
||||
* If this limit is 0 or less, the player will be not be kicked.
|
||||
*
|
||||
* @return the rate limited command rate limit
|
||||
*/
|
||||
int getKickAfterRateLimitedTabCompletes();
|
||||
|
||||
/**
|
||||
* Get whether the proxy should kick players who are tab complete rate limited.
|
||||
*
|
||||
* @return whether to kick players who are rate limited
|
||||
*/
|
||||
default boolean isKickOnTabCompleteRateLimit() {
|
||||
return getKickAfterRateLimitedTabCompletes() > 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,6 @@ import static com.google.common.base.Preconditions.checkArgument;
|
||||
|
||||
import com.google.common.base.Strings;
|
||||
import java.util.Objects;
|
||||
import java.util.regex.Pattern;
|
||||
import net.kyori.adventure.key.Key;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
|
||||
@@ -21,8 +20,6 @@ import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
*/
|
||||
public final class MinecraftChannelIdentifier implements ChannelIdentifier {
|
||||
|
||||
private static final Pattern VALID_IDENTIFIER_REGEX = Pattern.compile("[a-z0-9/\\-_]*");
|
||||
|
||||
private final String namespace;
|
||||
private final String name;
|
||||
|
||||
@@ -39,7 +36,7 @@ public final class MinecraftChannelIdentifier implements ChannelIdentifier {
|
||||
* @return a new channel identifier
|
||||
*/
|
||||
public static MinecraftChannelIdentifier forDefaultNamespace(String name) {
|
||||
return new MinecraftChannelIdentifier("minecraft", name);
|
||||
return new MinecraftChannelIdentifier(Key.MINECRAFT_NAMESPACE, name);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -52,14 +49,10 @@ public final class MinecraftChannelIdentifier implements ChannelIdentifier {
|
||||
public static MinecraftChannelIdentifier create(String namespace, String name) {
|
||||
checkArgument(!Strings.isNullOrEmpty(namespace), "namespace is null or empty");
|
||||
checkArgument(name != null, "namespace is null or empty");
|
||||
checkArgument(VALID_IDENTIFIER_REGEX.matcher(namespace).matches(),
|
||||
"namespace is not valid, must match: %s got %s",
|
||||
VALID_IDENTIFIER_REGEX.toString(),
|
||||
namespace);
|
||||
checkArgument(VALID_IDENTIFIER_REGEX.matcher(name).matches(),
|
||||
"name is not valid, must match: %s got %s",
|
||||
VALID_IDENTIFIER_REGEX.toString(),
|
||||
name);
|
||||
checkArgument(Key.parseableNamespace(namespace),
|
||||
"namespace is not valid, must match: [a-z0-9_.-] got %s", namespace);
|
||||
checkArgument(Key.parseableValue(name),
|
||||
"name is not valid, must match: [a-z0-9/._-] got %s", name);
|
||||
return new MinecraftChannelIdentifier(namespace, name);
|
||||
}
|
||||
|
||||
@@ -72,10 +65,9 @@ public final class MinecraftChannelIdentifier implements ChannelIdentifier {
|
||||
public static MinecraftChannelIdentifier from(String identifier) {
|
||||
int colonPos = identifier.indexOf(':');
|
||||
if (colonPos == -1) {
|
||||
throw new IllegalArgumentException("Identifier does not contain a colon.");
|
||||
}
|
||||
if (colonPos + 1 == identifier.length()) {
|
||||
throw new IllegalArgumentException("Identifier is empty.");
|
||||
return create(Key.MINECRAFT_NAMESPACE, identifier);
|
||||
} else if (colonPos == 0) {
|
||||
return create(Key.MINECRAFT_NAMESPACE, identifier.substring(1));
|
||||
}
|
||||
String namespace = identifier.substring(0, colonPos);
|
||||
String name = identifier.substring(colonPos + 1);
|
||||
|
||||
@@ -40,6 +40,7 @@ public interface TabList {
|
||||
* Adds a {@link TabListEntry} to the {@link Player}'s tab list.
|
||||
*
|
||||
* @param entry to add to the tab list
|
||||
* @throws IllegalArgumentException on versions below 1.19.3, if an entry with the same UUID already exists
|
||||
*/
|
||||
void addEntry(TabListEntry entry);
|
||||
|
||||
@@ -47,6 +48,7 @@ public interface TabList {
|
||||
* Adds a {@link Iterable} of {@link TabListEntry}'s to the {@link Player}'s tab list.
|
||||
*
|
||||
* @param entries to add to the tab list
|
||||
* @throws IllegalArgumentException on versions below 1.19.3, if an entry with the same UUID already exists
|
||||
*/
|
||||
default void addEntries(Iterable<TabListEntry> entries) {
|
||||
for (TabListEntry entry : entries) {
|
||||
@@ -58,6 +60,7 @@ public interface TabList {
|
||||
* Adds an array of {@link TabListEntry}'s to the {@link Player}'s tab list.
|
||||
*
|
||||
* @param entries to add to the tab list
|
||||
* @throws IllegalArgumentException on versions below 1.19.3, if an entry with the same UUID already exists
|
||||
*/
|
||||
default void addEntries(TabListEntry... entries) {
|
||||
for (TabListEntry entry : entries) {
|
||||
@@ -187,6 +190,26 @@ public interface TabList {
|
||||
* @deprecated Internal usage. Use {@link TabListEntry.Builder} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
default TabListEntry buildEntry(GameProfile profile, @Nullable Component displayName, int latency,
|
||||
int gameMode, @Nullable ChatSession chatSession, boolean listed, int listOrder) {
|
||||
return buildEntry(profile, displayName, latency, gameMode, chatSession, listed, listOrder, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents an entry in a {@link Player}'s tab list.
|
||||
*
|
||||
* @param profile the profile
|
||||
* @param displayName the display name
|
||||
* @param latency the latency
|
||||
* @param gameMode the game mode
|
||||
* @param chatSession the chat session
|
||||
* @param listed the visible status of entry
|
||||
* @param listOrder the order/priority of entry in the tab list
|
||||
* @param showHat the visibility of this entry's hat layer
|
||||
* @return the entry
|
||||
* @deprecated Internal usage. Use {@link TabListEntry.Builder} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
TabListEntry buildEntry(GameProfile profile, @Nullable Component displayName, int latency,
|
||||
int gameMode, @Nullable ChatSession chatSession, boolean listed, int listOrder);
|
||||
int gameMode, @Nullable ChatSession chatSession, boolean listed, int listOrder, boolean showHat);
|
||||
}
|
||||
|
||||
@@ -80,7 +80,7 @@ public interface TabListEntry extends KeyIdentifiable {
|
||||
* <li>150-300 will display 4 bars</li>
|
||||
* <li>300-600 will display 3 bars</li>
|
||||
* <li>600-1000 will display 2 bars</li>
|
||||
* <li>A latency move than 1 second will display 1 bar</li>
|
||||
* <li>A latency greater than 1 second will display 1 bar</li>
|
||||
* </ul>
|
||||
*
|
||||
* @return latency set for {@code this} entry
|
||||
@@ -160,6 +160,27 @@ public interface TabListEntry extends KeyIdentifiable {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether this entry's hat layer is shown in the tab list.
|
||||
*
|
||||
* @return whether to show this entry's hat layer
|
||||
* @sinceMinecraft 1.21.4
|
||||
*/
|
||||
default boolean isShowHat() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether to show this entry's hat layer in the tab list.
|
||||
*
|
||||
* @param showHat whether to show this entry's hat layer
|
||||
* @return {@code this}, for chaining
|
||||
* @sinceMinecraft 1.21.4
|
||||
*/
|
||||
default TabListEntry setShowHat(boolean showHat) {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a {@link Builder} to create a {@link TabListEntry}.
|
||||
*
|
||||
@@ -183,6 +204,7 @@ public interface TabListEntry extends KeyIdentifiable {
|
||||
private int gameMode = 0;
|
||||
private boolean listed = true;
|
||||
private int listOrder = 0;
|
||||
private boolean showHat;
|
||||
|
||||
private @Nullable ChatSession chatSession;
|
||||
|
||||
@@ -268,7 +290,7 @@ public interface TabListEntry extends KeyIdentifiable {
|
||||
* Sets whether this entry should be visible.
|
||||
*
|
||||
* @param listed to set
|
||||
* @return ${code this}, for chaining
|
||||
* @return {@code this}, for chaining
|
||||
* @see TabListEntry#isListed()
|
||||
*/
|
||||
public Builder listed(boolean listed) {
|
||||
@@ -280,7 +302,7 @@ public interface TabListEntry extends KeyIdentifiable {
|
||||
* Sets the order/priority of this entry in the tab list.
|
||||
*
|
||||
* @param order to set
|
||||
* @return ${code this}, for chaining
|
||||
* @return {@code this}, for chaining
|
||||
* @sinceMinecraft 1.21.2
|
||||
* @see TabListEntry#getListOrder()
|
||||
*/
|
||||
@@ -289,6 +311,18 @@ public interface TabListEntry extends KeyIdentifiable {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether this entry's hat layer should be shown in the tab list.
|
||||
*
|
||||
* @param showHat to set
|
||||
* @return {@code this}, for chaining
|
||||
* @see TabListEntry#isShowHat()
|
||||
*/
|
||||
public Builder showHat(boolean showHat) {
|
||||
this.showHat = showHat;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs the {@link TabListEntry} specified by {@code this} {@link Builder}.
|
||||
*
|
||||
@@ -301,7 +335,7 @@ public interface TabListEntry extends KeyIdentifiable {
|
||||
if (profile == null) {
|
||||
throw new IllegalStateException("The GameProfile must be set when building a TabListEntry");
|
||||
}
|
||||
return tabList.buildEntry(profile, displayName, latency, gameMode, chatSession, listed, listOrder);
|
||||
return tabList.buildEntry(profile, displayName, latency, gameMode, chatSession, listed, listOrder, showHat);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ import java.util.Objects;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import net.kyori.adventure.builder.AbstractBuilder;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* Contains the parameters used to ping a {@link RegisteredServer}.
|
||||
@@ -30,10 +31,12 @@ public final class PingOptions {
|
||||
public static final PingOptions DEFAULT = PingOptions.builder().build();
|
||||
private final ProtocolVersion protocolVersion;
|
||||
private final long timeout;
|
||||
private final String virtualHost;
|
||||
|
||||
private PingOptions(final Builder builder) {
|
||||
this.protocolVersion = builder.protocolVersion;
|
||||
this.timeout = builder.timeout;
|
||||
this.virtualHost = builder.virtualHost;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -54,6 +57,16 @@ public final class PingOptions {
|
||||
return this.timeout;
|
||||
}
|
||||
|
||||
/**
|
||||
* The virtual host to pass to the server for the ping.
|
||||
*
|
||||
* @return the virtual hostname to pass to the server for the ping
|
||||
* @since 3.4.0
|
||||
*/
|
||||
public @Nullable String getVirtualHost() {
|
||||
return this.virtualHost;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new builder to assign values to a new PingOptions.
|
||||
*
|
||||
@@ -68,10 +81,9 @@ public final class PingOptions {
|
||||
if (o == null) {
|
||||
return false;
|
||||
}
|
||||
if (!(o instanceof PingOptions)) {
|
||||
if (!(o instanceof final PingOptions other)) {
|
||||
return false;
|
||||
}
|
||||
final PingOptions other = (PingOptions) o;
|
||||
return Objects.equals(this.protocolVersion, other.protocolVersion)
|
||||
&& Objects.equals(this.timeout, other.timeout);
|
||||
}
|
||||
@@ -97,6 +109,7 @@ public final class PingOptions {
|
||||
public static final class Builder implements AbstractBuilder<PingOptions> {
|
||||
private ProtocolVersion protocolVersion = ProtocolVersion.UNKNOWN;
|
||||
private long timeout = 0;
|
||||
private String virtualHost = null;
|
||||
|
||||
private Builder() {
|
||||
}
|
||||
@@ -146,6 +159,18 @@ public final class PingOptions {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the virtual host to pass to the server for the ping.
|
||||
*
|
||||
* @param virtualHost the virtual hostname to pass to the server for the ping
|
||||
* @return this builder
|
||||
* @since 3.4.0
|
||||
*/
|
||||
public Builder virtualHost(final @Nullable String virtualHost) {
|
||||
this.virtualHost = virtualHost;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link PingOptions} with the values of this Builder.
|
||||
*
|
||||
|
||||
@@ -14,11 +14,14 @@ import com.velocitypowered.api.util.Favicon;
|
||||
import com.velocitypowered.api.util.ModInfo;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
|
||||
/**
|
||||
* Represents a 1.7 and above server list ping response. This class is immutable.
|
||||
@@ -27,7 +30,7 @@ public final class ServerPing {
|
||||
|
||||
private final Version version;
|
||||
private final @Nullable Players players;
|
||||
private final net.kyori.adventure.text.Component description;
|
||||
private final @Nullable Component description;
|
||||
private final @Nullable Favicon favicon;
|
||||
private final @Nullable ModInfo modinfo;
|
||||
private final boolean preventsChatReports = true;
|
||||
@@ -47,8 +50,8 @@ public final class ServerPing {
|
||||
* @param modinfo the mods this server runs
|
||||
*/
|
||||
public ServerPing(Version version, @Nullable Players players,
|
||||
net.kyori.adventure.text.Component description, @Nullable Favicon favicon,
|
||||
@Nullable ModInfo modinfo) {
|
||||
Component description, @Nullable Favicon favicon,
|
||||
@Nullable ModInfo modinfo) {
|
||||
this.version = Preconditions.checkNotNull(version, "version");
|
||||
this.players = players;
|
||||
this.description = Preconditions.checkNotNull(description, "description");
|
||||
@@ -64,7 +67,8 @@ public final class ServerPing {
|
||||
return Optional.ofNullable(players);
|
||||
}
|
||||
|
||||
public net.kyori.adventure.text.Component getDescriptionComponent() {
|
||||
@Nullable
|
||||
public Component getDescriptionComponent() {
|
||||
return description;
|
||||
}
|
||||
|
||||
@@ -151,7 +155,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 net.kyori.adventure.text.Component description;
|
||||
private Component description;
|
||||
private @Nullable Favicon favicon;
|
||||
private boolean nullOutPlayers;
|
||||
private boolean nullOutModinfo;
|
||||
@@ -160,31 +164,79 @@ public final class ServerPing {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Uses the modified {@code version} info in the response.
|
||||
*
|
||||
* @param version version info to set
|
||||
* @return this builder, for chaining
|
||||
*/
|
||||
public Builder version(Version version) {
|
||||
this.version = Preconditions.checkNotNull(version, "version");
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Uses the modified {@code onlinePlayers} number in the response.
|
||||
*
|
||||
* @param onlinePlayers number for online players to set
|
||||
* @return this builder, for chaining
|
||||
*/
|
||||
public Builder onlinePlayers(int onlinePlayers) {
|
||||
this.onlinePlayers = onlinePlayers;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Uses the modified {@code maximumPlayers} number in the response.
|
||||
* <b>This will not modify the actual maximum players that can join the server.</b>
|
||||
*
|
||||
* @param maximumPlayers number for maximum players to set
|
||||
* @return this builder, for chaining
|
||||
*/
|
||||
public Builder maximumPlayers(int maximumPlayers) {
|
||||
this.maximumPlayers = maximumPlayers;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Uses the modified {@code players} array in the response.
|
||||
*
|
||||
* @param players array of SamplePlayers to add
|
||||
* @return this builder, for chaining
|
||||
*/
|
||||
public Builder samplePlayers(SamplePlayer... players) {
|
||||
this.samplePlayers.addAll(Arrays.asList(players));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Uses the modified {@code players} collection in the response.
|
||||
*
|
||||
* @param players collection of SamplePlayers to add
|
||||
* @return this builder, for chaining
|
||||
*/
|
||||
public Builder samplePlayers(Collection<SamplePlayer> players) {
|
||||
this.samplePlayers.addAll(players);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Uses the modified {@code modType} in the response.
|
||||
*
|
||||
* @param modType the mod type to set
|
||||
* @return this builder, for chaining
|
||||
*/
|
||||
public Builder modType(String modType) {
|
||||
this.modType = Preconditions.checkNotNull(modType, "modType");
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Uses the modified {@code mods} array in the response.
|
||||
*
|
||||
* @param mods array of mods to use
|
||||
* @return this builder, for chaining
|
||||
*/
|
||||
public Builder mods(ModInfo.Mod... mods) {
|
||||
this.mods.addAll(Arrays.asList(mods));
|
||||
return this;
|
||||
@@ -194,7 +246,7 @@ public final class ServerPing {
|
||||
* Uses the modified {@code mods} list in the response.
|
||||
*
|
||||
* @param mods the mods list to use
|
||||
* @return this build, for chaining
|
||||
* @return this builder, for chaining
|
||||
*/
|
||||
public Builder mods(ModInfo mods) {
|
||||
Preconditions.checkNotNull(mods, "mods");
|
||||
@@ -204,36 +256,74 @@ public final class ServerPing {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the current list of mods to use in the response.
|
||||
*
|
||||
* @return this builder, for chaining
|
||||
*/
|
||||
public Builder clearMods() {
|
||||
this.mods.clear();
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the current list of PlayerSamples to use in the response.
|
||||
*
|
||||
* @return this builder, for chaining
|
||||
*/
|
||||
public Builder clearSamplePlayers() {
|
||||
this.samplePlayers.clear();
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines the server as mod incompatible in the response.
|
||||
*
|
||||
* @return this builder, for chaining
|
||||
*/
|
||||
public Builder notModCompatible() {
|
||||
this.nullOutModinfo = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables nulling Players in the response.
|
||||
* This will display the player count as {@code ???}.
|
||||
*
|
||||
* @return this builder, for chaining
|
||||
*/
|
||||
public Builder nullPlayers() {
|
||||
this.nullOutPlayers = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder description(net.kyori.adventure.text.Component description) {
|
||||
/**
|
||||
* Uses the {@code description} Component in the response.
|
||||
*
|
||||
* @param description Component to use as the description.
|
||||
* @return this builder, for chaining
|
||||
*/
|
||||
public Builder description(Component description) {
|
||||
this.description = Preconditions.checkNotNull(description, "description");
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Uses the {@code favicon} in the response.
|
||||
*
|
||||
* @param favicon Favicon instance to use.
|
||||
* @return this builder, for chaining
|
||||
*/
|
||||
public Builder favicon(Favicon favicon) {
|
||||
this.favicon = Preconditions.checkNotNull(favicon, "favicon");
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the current favicon used in the response.
|
||||
*
|
||||
* @return this builder, for chaining
|
||||
*/
|
||||
public Builder clearFavicon() {
|
||||
this.favicon = null;
|
||||
return this;
|
||||
@@ -273,7 +363,7 @@ public final class ServerPing {
|
||||
return samplePlayers;
|
||||
}
|
||||
|
||||
public Optional<net.kyori.adventure.text.Component> getDescriptionComponent() {
|
||||
public Optional<Component> getDescriptionComponent() {
|
||||
return Optional.ofNullable(description);
|
||||
}
|
||||
|
||||
@@ -430,6 +520,10 @@ public final class ServerPing {
|
||||
*/
|
||||
public static final class SamplePlayer {
|
||||
|
||||
public static final SamplePlayer ANONYMOUS = new SamplePlayer(
|
||||
"Anonymous Player",
|
||||
new UUID(0L, 0L)
|
||||
);
|
||||
private final String name;
|
||||
private final UUID id;
|
||||
|
||||
|
||||
@@ -76,9 +76,17 @@ public final class ModInfo {
|
||||
private final String id;
|
||||
private final String version;
|
||||
|
||||
/**
|
||||
* Creates a new mod info.
|
||||
*
|
||||
* @param id the mod identifier
|
||||
* @param version the mod version
|
||||
*/
|
||||
public Mod(String id, String version) {
|
||||
this.id = Preconditions.checkNotNull(id, "id");
|
||||
this.version = Preconditions.checkNotNull(version, "version");
|
||||
Preconditions.checkArgument(id.length() < 128, "mod id is too long");
|
||||
Preconditions.checkArgument(version.length() < 128, "mod version is too long");
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
|
||||
@@ -47,17 +47,25 @@ class MinecraftChannelIdentifierTest {
|
||||
create("velocity", "test/test2");
|
||||
}
|
||||
|
||||
@Test
|
||||
void fromIdentifierDefaultNamespace() {
|
||||
assertEquals("minecraft", from("test").getNamespace());
|
||||
assertEquals("minecraft", from(":test").getNamespace());
|
||||
}
|
||||
|
||||
@Test
|
||||
void fromIdentifierAllowsEmptyName() {
|
||||
from("minecraft:");
|
||||
from(":");
|
||||
from("");
|
||||
}
|
||||
|
||||
@Test
|
||||
void fromIdentifierThrowsOnBadValues() {
|
||||
assertAll(
|
||||
() -> assertThrows(IllegalArgumentException.class, () -> from("")),
|
||||
() -> assertThrows(IllegalArgumentException.class, () -> from(":")),
|
||||
() -> assertThrows(IllegalArgumentException.class, () -> from(":a")),
|
||||
() -> assertThrows(IllegalArgumentException.class, () -> from("a:")),
|
||||
() -> assertThrows(IllegalArgumentException.class, () -> from("hello:$$$$$$")),
|
||||
() -> assertThrows(IllegalArgumentException.class, () -> from("he/llo:wor/ld")),
|
||||
() -> assertThrows(IllegalArgumentException.class, () -> from("hello::"))
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -2,8 +2,15 @@ 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 {
|
||||
exec {
|
||||
val execOps = objects.newInstance<Injected>().execOps
|
||||
execOps.exec {
|
||||
executable = "git"
|
||||
args = listOf("rev-parse", "HEAD")
|
||||
standardOutput = it
|
||||
|
||||
@@ -8,10 +8,10 @@ extensions.configure<PublishingExtension> {
|
||||
maven {
|
||||
credentials(PasswordCredentials::class.java)
|
||||
|
||||
name = "paper"
|
||||
val base = "https://repo.papermc.io/repository/maven"
|
||||
val releasesRepoUrl = "$base-releases/"
|
||||
val snapshotsRepoUrl = "$base-snapshots/"
|
||||
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/"
|
||||
setUrl(if (version.toString().endsWith("SNAPSHOT")) snapshotsRepoUrl else releasesRepoUrl)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,11 +20,11 @@ subprojects {
|
||||
testImplementation(rootProject.libs.junit)
|
||||
}
|
||||
|
||||
tasks {
|
||||
test {
|
||||
useJUnitPlatform()
|
||||
reports {
|
||||
junitXml.required.set(true)
|
||||
testing.suites.named<JvmTestSuite>("test") {
|
||||
useJUnitJupiter()
|
||||
targets.all {
|
||||
testTask.configure {
|
||||
reports.junitXml.required = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,23 +2,24 @@
|
||||
configurate3 = "3.7.3"
|
||||
configurate4 = "4.1.2"
|
||||
flare = "2.0.1"
|
||||
log4j = "2.24.1"
|
||||
netty = "4.1.114.Final"
|
||||
log4j = "2.24.3"
|
||||
netty = "4.2.7.Final"
|
||||
|
||||
[plugins]
|
||||
fill = "io.papermc.fill.gradle:1.0.3"
|
||||
indra-publishing = "net.kyori.indra.publishing:2.0.6"
|
||||
shadow = "io.github.goooler.shadow:8.1.5"
|
||||
shadow = "com.gradleup.shadow:8.3.6"
|
||||
spotless = "com.diffplug.spotless:6.25.0"
|
||||
|
||||
[libraries]
|
||||
adventure-bom = "net.kyori:adventure-bom:4.17.0"
|
||||
adventure-text-serializer-json-legacy-impl = "net.kyori:adventure-text-serializer-json-legacy-impl:4.17.0"
|
||||
adventure-bom = "net.kyori:adventure-bom:4.25.0"
|
||||
adventure-text-serializer-json-legacy-impl = "net.kyori:adventure-text-serializer-json-legacy-impl:4.25.0"
|
||||
adventure-facet = "net.kyori:adventure-platform-facet:4.3.4"
|
||||
asm = "org.ow2.asm:asm:9.6"
|
||||
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.0.2"
|
||||
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"
|
||||
@@ -33,11 +34,11 @@ disruptor = "com.lmax:disruptor:4.0.0"
|
||||
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.27.1"
|
||||
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:5.10.2"
|
||||
jspecify = "org.jspecify:jspecify:0.3.0"
|
||||
kyori-ansi = "net.kyori:ansi:1.1.0"
|
||||
kyori-ansi = "net.kyori:ansi:1.1.1"
|
||||
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"
|
||||
@@ -53,10 +54,10 @@ netty-codec-haproxy = { module = "io.netty:netty-codec-haproxy", version.ref = "
|
||||
netty-codec-http = { module = "io.netty:netty-codec-http", version.ref = "netty" }
|
||||
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-iouring = { module = "io.netty.incubator:netty-incubator-transport-native-io_uring", version = "0.0.25.Final" }
|
||||
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.6.7"
|
||||
slf4j = "org.slf4j:slf4j-api:2.0.12"
|
||||
slf4j = "org.slf4j:slf4j-api:2.0.17"
|
||||
snakeyaml = "org.yaml:snakeyaml:1.33"
|
||||
spotbugs-annotations = "com.github.spotbugs:spotbugs-annotations:4.7.3"
|
||||
terminalconsoleappender = "net.minecrell:terminalconsoleappender:1.3.0"
|
||||
|
||||
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Binary file not shown.
2
gradle/wrapper/gradle-wrapper.properties
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -1,6 +1,6 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.2-bin.zip
|
||||
networkTimeout=10000
|
||||
validateDistributionUrl=true
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
|
||||
9
gradlew
vendored
9
gradlew
vendored
@@ -15,6 +15,8 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
|
||||
##############################################################################
|
||||
#
|
||||
@@ -55,7 +57,7 @@
|
||||
# Darwin, MinGW, and NonStop.
|
||||
#
|
||||
# (3) This script is generated from the Groovy template
|
||||
# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||
# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||
# within the Gradle project.
|
||||
#
|
||||
# You can find Gradle at https://github.com/gradle/gradle/.
|
||||
@@ -84,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 "${APP_HOME:-./}" > /dev/null && pwd -P ) || 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
|
||||
@@ -200,7 +203,7 @@ 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, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
|
||||
|
||||
4
gradlew.bat
vendored
4
gradlew.bat
vendored
@@ -13,6 +13,8 @@
|
||||
@rem See the License for the specific language governing permissions and
|
||||
@rem limitations under the License.
|
||||
@rem
|
||||
@rem SPDX-License-Identifier: Apache-2.0
|
||||
@rem
|
||||
|
||||
@if "%DEBUG%"=="" @echo off
|
||||
@rem ##########################################################################
|
||||
@@ -34,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
|
||||
|
||||
@@ -26,6 +26,15 @@ Java_com_velocitypowered_natives_encryption_OpenSslCipherImpl_init(JNIEnv *env,
|
||||
return 0;
|
||||
}
|
||||
|
||||
// But, you're saying, *why* are we using the key as the IV? After all, reusing the key as
|
||||
// the IV defeats the entire point - we might as well just initialize it to all zeroes.
|
||||
//
|
||||
// You can blame Mojang. For the record, we also don't consider the Minecraft protocol
|
||||
// encryption scheme to be secure, and it has reached the point where any serious cryptographic
|
||||
// protocol needs a refresh. There are multiple obvious weaknesses, and this is far from the
|
||||
// most serious.
|
||||
//
|
||||
// If you are using Minecraft in a security-sensitive application, *I don't know what to say.*
|
||||
CCCryptorRef cryptor = NULL;
|
||||
CCCryptorStatus result = CCCryptorCreateWithMode(encrypt ? kCCEncrypt : kCCDecrypt,
|
||||
kCCModeCFB8,
|
||||
|
||||
@@ -32,6 +32,15 @@ Java_com_velocitypowered_natives_encryption_OpenSslCipherImpl_init(JNIEnv *env,
|
||||
return 0;
|
||||
}
|
||||
|
||||
// But, you're saying, *why* are we using the key as the IV? After all, reusing the key as
|
||||
// the IV defeats the entire point - we might as well just initialize it to all zeroes.
|
||||
//
|
||||
// You can blame Mojang. For the record, we also don't consider the Minecraft protocol
|
||||
// encryption scheme to be secure, and it has reached the point where any serious cryptographic
|
||||
// protocol needs a refresh. There are multiple obvious weaknesses, and this is far from the
|
||||
// most serious.
|
||||
//
|
||||
// If you are using Minecraft in a security-sensitive application, *I don't know what to say.*
|
||||
int result = EVP_CipherInit(ctx, EVP_aes_128_cfb8(), (byte*) keyBytes, (byte*) keyBytes,
|
||||
encrypt);
|
||||
if (result != 1) {
|
||||
|
||||
@@ -57,7 +57,8 @@ public class JavaVelocityCompressor implements VelocityCompressor {
|
||||
inflater.setInput(source.nioBuffer());
|
||||
|
||||
try {
|
||||
while (!inflater.finished() && inflater.getBytesWritten() < uncompressedSize) {
|
||||
final int readable = source.readableBytes();
|
||||
while (!inflater.finished() && inflater.getBytesRead() < readable) {
|
||||
if (!destination.isWritable()) {
|
||||
destination.ensureWritable(ZLIB_BUFFER_SIZE);
|
||||
}
|
||||
|
||||
@@ -48,6 +48,15 @@ public class JavaVelocityCipher implements VelocityCipher {
|
||||
|
||||
private JavaVelocityCipher(boolean encrypt, SecretKey key) throws GeneralSecurityException {
|
||||
this.cipher = Cipher.getInstance("AES/CFB8/NoPadding");
|
||||
// But, you're saying, *why* are we using the key as the IV? After all, reusing the key as
|
||||
// the IV defeats the entire point - we might as well just initialize it to all zeroes.
|
||||
//
|
||||
// You can blame Mojang. For the record, we also don't consider the Minecraft protocol
|
||||
// encryption scheme to be secure, and it has reached the point where any serious cryptographic
|
||||
// protocol needs a refresh. There are multiple obvious weaknesses, and this is far from the
|
||||
// most serious.
|
||||
//
|
||||
// If you are using Minecraft in a security-sensitive application, *I don't know what to say.*
|
||||
this.cipher.init(encrypt ? Cipher.ENCRYPT_MODE : Cipher.DECRYPT_MODE, key,
|
||||
new IvParameterSpec(key.getEncoded()));
|
||||
}
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
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 {
|
||||
@@ -51,7 +53,11 @@ tasks {
|
||||
exclude("it/unimi/dsi/fastutil/ints/*Int2Short*")
|
||||
exclude("it/unimi/dsi/fastutil/ints/*Int2Reference*")
|
||||
exclude("it/unimi/dsi/fastutil/ints/IntAVL*")
|
||||
exclude("it/unimi/dsi/fastutil/ints/IntArray*")
|
||||
exclude("it/unimi/dsi/fastutil/ints/IntArrayF*")
|
||||
exclude("it/unimi/dsi/fastutil/ints/IntArrayI*")
|
||||
exclude("it/unimi/dsi/fastutil/ints/IntArrayL*")
|
||||
exclude("it/unimi/dsi/fastutil/ints/IntArrayP*")
|
||||
exclude("it/unimi/dsi/fastutil/ints/IntArraySet*")
|
||||
exclude("it/unimi/dsi/fastutil/ints/*IntBi*")
|
||||
exclude("it/unimi/dsi/fastutil/ints/Int*Pair")
|
||||
exclude("it/unimi/dsi/fastutil/ints/IntLinked*")
|
||||
@@ -96,6 +102,7 @@ tasks {
|
||||
runShadow {
|
||||
workingDir = file("run").also(File::mkdirs)
|
||||
standardInput = System.`in`
|
||||
jvmArgs("-Dvelocity.packet-decode-logging=true")
|
||||
}
|
||||
named<JavaExec>("run") {
|
||||
workingDir = file("run").also(File::mkdirs)
|
||||
@@ -103,6 +110,24 @@ 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"))
|
||||
|
||||
@@ -65,7 +65,8 @@ public class Metrics {
|
||||
logger::info,
|
||||
config.isLogErrorsEnabled(),
|
||||
config.isLogSentDataEnabled(),
|
||||
config.isLogResponseStatusTextEnabled()
|
||||
config.isLogResponseStatusTextEnabled(),
|
||||
false
|
||||
);
|
||||
|
||||
if (!config.didExistBefore()) {
|
||||
|
||||
@@ -24,6 +24,7 @@ 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;
|
||||
@@ -75,11 +76,13 @@ import io.netty.channel.ChannelInitializer;
|
||||
import io.netty.channel.EventLoopGroup;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.InetAddress;
|
||||
import java.net.InetSocketAddress;
|
||||
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;
|
||||
@@ -103,7 +106,7 @@ import net.kyori.adventure.audience.ForwardingAudience;
|
||||
import net.kyori.adventure.key.Key;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.translation.GlobalTranslator;
|
||||
import net.kyori.adventure.translation.TranslationRegistry;
|
||||
import net.kyori.adventure.translation.TranslationStore;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.bstats.MetricsBase;
|
||||
@@ -117,7 +120,7 @@ import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
*/
|
||||
public class VelocityServer implements ProxyServer, ForwardingAudience {
|
||||
|
||||
public static final String VELOCITY_URL = "https://velocitypowered.com";
|
||||
public static final String VELOCITY_URL = "https://papermc.io/software/velocity";
|
||||
|
||||
private static final Logger logger = LogManager.getLogger(VelocityServer.class);
|
||||
public static final Gson GENERAL_GSON = new GsonBuilder()
|
||||
@@ -148,6 +151,8 @@ 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;
|
||||
@@ -162,7 +167,9 @@ public class VelocityServer implements ProxyServer, ForwardingAudience {
|
||||
private final Map<UUID, ConnectedPlayer> connectionsByUuid = new ConcurrentHashMap<>();
|
||||
private final Map<String, ConnectedPlayer> connectionsByName = new ConcurrentHashMap<>();
|
||||
private final VelocityConsole console;
|
||||
private @MonotonicNonNull Ratelimiter ipAttemptLimiter;
|
||||
private @MonotonicNonNull Ratelimiter<InetAddress> ipAttemptLimiter;
|
||||
private @MonotonicNonNull Ratelimiter<UUID> commandRateLimiter;
|
||||
private @MonotonicNonNull Ratelimiter<UUID> tabCompleteRateLimiter;
|
||||
private final VelocityEventManager eventManager;
|
||||
private final VelocityScheduler scheduler;
|
||||
private final VelocityChannelRegistrar channelRegistrar = new VelocityChannelRegistrar();
|
||||
@@ -212,7 +219,8 @@ public class VelocityServer implements ProxyServer, ForwardingAudience {
|
||||
ProxyVersion version = getVersion();
|
||||
PluginDescription description = new VelocityPluginDescription(
|
||||
"velocity", version.getName(), version.getVersion(), "The Velocity proxy",
|
||||
VELOCITY_URL, ImmutableList.of(version.getVendor()), Collections.emptyList(), null);
|
||||
version.getName().equals("Velocity") ? VELOCITY_URL : null,
|
||||
ImmutableList.of(version.getVendor()), Collections.emptyList(), null);
|
||||
VelocityPluginContainer container = new VelocityPluginContainer(description);
|
||||
container.setInstance(VelocityVirtualPlugin.INSTANCE);
|
||||
return container;
|
||||
@@ -236,6 +244,15 @@ public class VelocityServer implements ProxyServer, ForwardingAudience {
|
||||
|
||||
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...
|
||||
//
|
||||
// You can blame Mojang. For the record, we also don't consider the Minecraft protocol
|
||||
// encryption scheme to be secure, and it has reached the point where any serious cryptographic
|
||||
// protocol needs a refresh. There are multiple obvious weaknesses, and this is far from the
|
||||
// most serious.
|
||||
//
|
||||
// If you are using Minecraft in a security-sensitive application, *I don't know what to say.*
|
||||
serverKeyPair = EncryptionUtils.createRsaKeyPair(1024);
|
||||
|
||||
cm.logChannelInformation();
|
||||
@@ -286,6 +303,8 @@ public class VelocityServer implements ProxyServer, ForwardingAudience {
|
||||
}
|
||||
|
||||
ipAttemptLimiter = Ratelimiters.createWithMilliseconds(configuration.getLoginRatelimit());
|
||||
commandRateLimiter = Ratelimiters.createWithMilliseconds(configuration.getCommandRatelimit());
|
||||
tabCompleteRateLimiter = Ratelimiters.createWithMilliseconds(configuration.getTabCompleteRatelimit());
|
||||
loadPlugins();
|
||||
|
||||
// Go ahead and fire the proxy initialization event. We block since plugins should have a chance
|
||||
@@ -323,8 +342,8 @@ public class VelocityServer implements ProxyServer, ForwardingAudience {
|
||||
}
|
||||
|
||||
private void registerTranslations() {
|
||||
final TranslationRegistry translationRegistry = TranslationRegistry
|
||||
.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 -> {
|
||||
@@ -563,6 +582,20 @@ 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);
|
||||
@@ -645,10 +678,18 @@ public class VelocityServer implements ProxyServer, ForwardingAudience {
|
||||
return cm.createHttpClient();
|
||||
}
|
||||
|
||||
public Ratelimiter getIpAttemptLimiter() {
|
||||
public @MonotonicNonNull Ratelimiter<InetAddress> getIpAttemptLimiter() {
|
||||
return ipAttemptLimiter;
|
||||
}
|
||||
|
||||
public @MonotonicNonNull Ratelimiter<UUID> getCommandRateLimiter() {
|
||||
return commandRateLimiter;
|
||||
}
|
||||
|
||||
public @MonotonicNonNull Ratelimiter<UUID> getTabCompleteRateLimiter() {
|
||||
return tabCompleteRateLimiter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the {@code connection} can be registered with the proxy.
|
||||
*
|
||||
@@ -795,6 +836,11 @@ public class VelocityServer implements ProxyServer, ForwardingAudience {
|
||||
return channelRegistrar;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isShuttingDown() {
|
||||
return shutdownInProgress.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public InetSocketAddress getBoundAddress() {
|
||||
if (configuration == null) {
|
||||
|
||||
@@ -53,15 +53,23 @@ public final class VelocityBossBarImplementation implements BossBar.Listener,
|
||||
viewer.getProtocolVersion(),
|
||||
viewer.translateMessage(this.bar.name())
|
||||
);
|
||||
viewer.getConnection().write(BossBarPacket.createAddPacket(this.id, this.bar, name));
|
||||
viewer.getBossBarManager().writeUpdate(this, 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.getConnection().write(BossBarPacket.createRemovePacket(this.id, this.bar));
|
||||
viewer.getBossBarManager().remove(this, BossBarPacket.createRemovePacket(this.id, this.bar));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
@@ -84,7 +92,7 @@ public final class VelocityBossBarImplementation implements BossBar.Listener,
|
||||
this.bar,
|
||||
new ComponentHolder(viewer.getProtocolVersion(), translated)
|
||||
);
|
||||
viewer.getConnection().write(packet);
|
||||
viewer.getBossBarManager().writeUpdate(this, packet);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -96,7 +104,7 @@ public final class VelocityBossBarImplementation implements BossBar.Listener,
|
||||
) {
|
||||
final BossBarPacket packet = BossBarPacket.createUpdateProgressPacket(this.id, this.bar);
|
||||
for (final ConnectedPlayer viewer : this.viewers) {
|
||||
viewer.getConnection().write(packet);
|
||||
viewer.getBossBarManager().writeUpdate(this, packet);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -108,7 +116,7 @@ public final class VelocityBossBarImplementation implements BossBar.Listener,
|
||||
) {
|
||||
final BossBarPacket packet = BossBarPacket.createUpdateStylePacket(this.id, this.bar);
|
||||
for (final ConnectedPlayer viewer : this.viewers) {
|
||||
viewer.getConnection().write(packet);
|
||||
viewer.getBossBarManager().writeUpdate(this, packet);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -120,7 +128,7 @@ public final class VelocityBossBarImplementation implements BossBar.Listener,
|
||||
) {
|
||||
final BossBarPacket packet = BossBarPacket.createUpdateStylePacket(this.id, this.bar);
|
||||
for (final ConnectedPlayer viewer : this.viewers) {
|
||||
viewer.getConnection().write(packet);
|
||||
viewer.getBossBarManager().writeUpdate(this, packet);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -132,7 +140,7 @@ public final class VelocityBossBarImplementation implements BossBar.Listener,
|
||||
) {
|
||||
final BossBarPacket packet = BossBarPacket.createUpdatePropertiesPacket(this.id, this.bar);
|
||||
for (final ConnectedPlayer viewer : this.viewers) {
|
||||
viewer.getConnection().write(packet);
|
||||
viewer.getBossBarManager().writeUpdate(this, packet);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,7 +35,6 @@ 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;
|
||||
@@ -59,6 +58,7 @@ 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;
|
||||
@@ -242,8 +242,8 @@ public class VelocityCommandManager implements CommandManager {
|
||||
CommandSyntaxException.BUILT_IN_EXCEPTIONS.dispatcherUnknownCommand());
|
||||
if (isSyntaxError) {
|
||||
final Message message = e.getRawMessage();
|
||||
if (message instanceof VelocityBrigadierMessage velocityMessage) {
|
||||
source.sendMessage(velocityMessage.asComponent().applyFallbackStyle(NamedTextColor.RED));
|
||||
if (message instanceof ComponentLike componentLike) {
|
||||
source.sendMessage(componentLike.asComponent().applyFallbackStyle(NamedTextColor.RED));
|
||||
} else {
|
||||
source.sendMessage(Component.text(e.getMessage(), NamedTextColor.RED));
|
||||
}
|
||||
@@ -300,27 +300,14 @@ public class VelocityCommandManager implements CommandManager {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns suggestions to fill in the given command.
|
||||
*
|
||||
* @param source the source to execute the command for
|
||||
* @param cmdLine the partially completed command
|
||||
* @return a {@link CompletableFuture} eventually completed with a {@link List}, possibly empty
|
||||
*/
|
||||
@Override
|
||||
public CompletableFuture<List<String>> offerSuggestions(final CommandSource source,
|
||||
final String cmdLine) {
|
||||
return offerBrigadierSuggestions(source, cmdLine)
|
||||
.thenApply(suggestions -> Lists.transform(suggestions.getList(), Suggestion::getText));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns suggestions to fill in the given command.
|
||||
*
|
||||
* @param source the source to execute the command for
|
||||
* @param cmdLine the partially completed command
|
||||
* @return a {@link CompletableFuture} eventually completed with {@link Suggestions}, possibly
|
||||
* empty
|
||||
*/
|
||||
@Override
|
||||
public CompletableFuture<Suggestions> offerBrigadierSuggestions(
|
||||
final CommandSource source, final String cmdLine) {
|
||||
Preconditions.checkNotNull(source, "source");
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
|
||||
package com.velocitypowered.proxy.command.builtin;
|
||||
|
||||
import com.google.gson.JsonSyntaxException;
|
||||
import com.mojang.brigadier.Command;
|
||||
import com.mojang.brigadier.arguments.StringArgumentType;
|
||||
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
|
||||
@@ -25,8 +26,9 @@ import com.velocitypowered.api.command.BrigadierCommand;
|
||||
import com.velocitypowered.api.command.CommandSource;
|
||||
import com.velocitypowered.api.proxy.ConsoleCommandSource;
|
||||
import com.velocitypowered.proxy.VelocityServer;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.minimessage.MiniMessage;
|
||||
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
|
||||
import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer;
|
||||
|
||||
/**
|
||||
* Shuts down the proxy.
|
||||
@@ -53,11 +55,22 @@ public final class ShutdownCommand {
|
||||
StringArgumentType.greedyString())
|
||||
.executes(context -> {
|
||||
String reason = context.getArgument("reason", String.class);
|
||||
server.shutdown(true, MiniMessage.miniMessage().deserialize(
|
||||
MiniMessage.miniMessage().serialize(
|
||||
LegacyComponentSerializer.legacy('&').deserialize(reason)
|
||||
)
|
||||
));
|
||||
Component reasonComponent = null;
|
||||
|
||||
if (reason.startsWith("{") || reason.startsWith("[") || reason.startsWith("\"")) {
|
||||
try {
|
||||
reasonComponent = GsonComponentSerializer.gson()
|
||||
.deserializeOrNull(reason);
|
||||
} catch (JsonSyntaxException expected) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
if (reasonComponent == null) {
|
||||
reasonComponent = MiniMessage.miniMessage().deserialize(reason);
|
||||
}
|
||||
|
||||
server.shutdown(true, reasonComponent);
|
||||
return Command.SINGLE_SUCCESS;
|
||||
})
|
||||
).build());
|
||||
|
||||
@@ -82,7 +82,7 @@ public final class VelocityCommand {
|
||||
.executes(new Heap())
|
||||
.build();
|
||||
final LiteralCommandNode<CommandSource> info = BrigadierCommand.literalArgumentBuilder("info")
|
||||
.requires(source -> source.getPermissionValue("velocity.command.info") != Tristate.FALSE)
|
||||
.requires(source -> source.getPermissionValue("velocity.command.info") == Tristate.TRUE)
|
||||
.executes(new Info(server))
|
||||
.build();
|
||||
final LiteralCommandNode<CommandSource> plugins = BrigadierCommand
|
||||
@@ -176,8 +176,7 @@ public final class VelocityCommand {
|
||||
.append(Component.text()
|
||||
.content("PaperMC")
|
||||
.color(NamedTextColor.GREEN)
|
||||
.clickEvent(
|
||||
ClickEvent.openUrl("https://papermc.io/software/velocity"))
|
||||
.clickEvent(ClickEvent.openUrl(VelocityServer.VELOCITY_URL))
|
||||
.build())
|
||||
.append(Component.text(" - "))
|
||||
.append(Component.text()
|
||||
|
||||
@@ -103,13 +103,17 @@ 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");
|
||||
builder.suggest(value);
|
||||
offsetBuilder.suggest(value);
|
||||
}
|
||||
return builder.build();
|
||||
return offsetBuilder.build();
|
||||
});
|
||||
})
|
||||
.build();
|
||||
|
||||
@@ -78,6 +78,8 @@ public class VelocityConfiguration implements ProxyConfig {
|
||||
private boolean onlineModeKickExistingPlayers = false;
|
||||
@Expose
|
||||
private PingPassthroughMode pingPassthrough = PingPassthroughMode.DISABLED;
|
||||
@Expose
|
||||
private boolean samplePlayersInPing = false;
|
||||
private final Servers servers;
|
||||
private final ForcedHosts forcedHosts;
|
||||
@Expose
|
||||
@@ -105,8 +107,9 @@ public class VelocityConfiguration implements ProxyConfig {
|
||||
boolean preventClientProxyConnections, boolean announceForge,
|
||||
PlayerInfoForwarding playerInfoForwardingMode, byte[] forwardingSecret,
|
||||
boolean onlineModeKickExistingPlayers, PingPassthroughMode pingPassthrough,
|
||||
boolean enablePlayerAddressLogging, Servers servers, ForcedHosts forcedHosts,
|
||||
Advanced advanced, Query query, Metrics metrics, boolean forceKeyAuthentication) {
|
||||
boolean samplePlayersInPing, boolean enablePlayerAddressLogging, Servers servers,
|
||||
ForcedHosts forcedHosts, Advanced advanced, Query query, Metrics metrics,
|
||||
boolean forceKeyAuthentication) {
|
||||
this.bind = bind;
|
||||
this.motd = motd;
|
||||
this.showMaxPlayers = showMaxPlayers;
|
||||
@@ -117,6 +120,7 @@ public class VelocityConfiguration implements ProxyConfig {
|
||||
this.forwardingSecret = forwardingSecret;
|
||||
this.onlineModeKickExistingPlayers = onlineModeKickExistingPlayers;
|
||||
this.pingPassthrough = pingPassthrough;
|
||||
this.samplePlayersInPing = samplePlayersInPing;
|
||||
this.enablePlayerAddressLogging = enablePlayerAddressLogging;
|
||||
this.servers = servers;
|
||||
this.forcedHosts = forcedHosts;
|
||||
@@ -230,6 +234,11 @@ public class VelocityConfiguration implements ProxyConfig {
|
||||
valid = false;
|
||||
}
|
||||
|
||||
if (advanced.commandRateLimit < 0) {
|
||||
logger.error("Invalid command rate limit {}", advanced.commandRateLimit);
|
||||
valid = false;
|
||||
}
|
||||
|
||||
loadFavicon();
|
||||
|
||||
return valid;
|
||||
@@ -351,6 +360,31 @@ public class VelocityConfiguration implements ProxyConfig {
|
||||
return advanced.getReadTimeout();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCommandRatelimit() {
|
||||
return advanced.getCommandRateLimit();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getTabCompleteRatelimit() {
|
||||
return advanced.getTabCompleteRateLimit();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getKickAfterRateLimitedTabCompletes() {
|
||||
return advanced.getKickAfterRateLimitedTabCompletes();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isForwardCommandsIfRateLimited() {
|
||||
return advanced.isForwardCommandsIfRateLimited();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getKickAfterRateLimitedCommands() {
|
||||
return advanced.getKickAfterRateLimitedCommands();
|
||||
}
|
||||
|
||||
public boolean isProxyProtocol() {
|
||||
return advanced.isProxyProtocol();
|
||||
}
|
||||
@@ -371,6 +405,10 @@ public class VelocityConfiguration implements ProxyConfig {
|
||||
return pingPassthrough;
|
||||
}
|
||||
|
||||
public boolean getSamplePlayersInPing() {
|
||||
return samplePlayersInPing;
|
||||
}
|
||||
|
||||
public boolean isPlayerAddressLoggingEnabled() {
|
||||
return enablePlayerAddressLogging;
|
||||
}
|
||||
@@ -407,6 +445,10 @@ public class VelocityConfiguration implements ProxyConfig {
|
||||
return forceKeyAuthentication;
|
||||
}
|
||||
|
||||
public boolean isEnableReusePort() {
|
||||
return advanced.isEnableReusePort();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return MoreObjects.toStringHelper(this)
|
||||
@@ -473,7 +515,7 @@ public class VelocityConfiguration implements ProxyConfig {
|
||||
|
||||
String forwardingSecretString = System.getenv().getOrDefault(
|
||||
"VELOCITY_FORWARDING_SECRET", "");
|
||||
if (forwardingSecretString.isEmpty()) {
|
||||
if (forwardingSecretString.isBlank()) {
|
||||
final String forwardSecretFile = config.get("forwarding-secret-file");
|
||||
final Path secretPath = forwardSecretFile == null
|
||||
? defaultForwardingSecretPath
|
||||
@@ -486,7 +528,11 @@ public class VelocityConfiguration implements ProxyConfig {
|
||||
"The file " + forwardSecretFile + " is not a valid file or it is a directory.");
|
||||
}
|
||||
} else {
|
||||
throw new RuntimeException("The forwarding-secret-file does not exist.");
|
||||
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);
|
||||
}
|
||||
}
|
||||
final byte[] forwardingSecret = forwardingSecretString.getBytes(StandardCharsets.UTF_8);
|
||||
@@ -503,6 +549,8 @@ public class VelocityConfiguration implements ProxyConfig {
|
||||
final PingPassthroughMode pingPassthroughMode = config.getEnumOrElse("ping-passthrough",
|
||||
PingPassthroughMode.DISABLED);
|
||||
|
||||
final boolean samplePlayersInPing = config.getOrElse("sample-players-in-ping", false);
|
||||
|
||||
final String bind = config.getOrElse("bind", "0.0.0.0:25565");
|
||||
final int maxPlayers = config.getIntOrElse("show-max-players", 500);
|
||||
final boolean onlineMode = config.getOrElse("online-mode", true);
|
||||
@@ -533,6 +581,7 @@ public class VelocityConfiguration implements ProxyConfig {
|
||||
forwardingSecret,
|
||||
kickExisting,
|
||||
pingPassthroughMode,
|
||||
samplePlayersInPing,
|
||||
enablePlayerAddressLogging,
|
||||
new Servers(serversConfig),
|
||||
new ForcedHosts(forcedHostsConfig),
|
||||
@@ -716,6 +765,18 @@ public class VelocityConfiguration implements ProxyConfig {
|
||||
private boolean logPlayerConnections = true;
|
||||
@Expose
|
||||
private boolean acceptTransfers = false;
|
||||
@Expose
|
||||
private boolean enableReusePort = false;
|
||||
@Expose
|
||||
private int commandRateLimit = 50;
|
||||
@Expose
|
||||
private boolean forwardCommandsIfRateLimited = true;
|
||||
@Expose
|
||||
private int kickAfterRateLimitedCommands = 5;
|
||||
@Expose
|
||||
private int tabCompleteRateLimit = 50;
|
||||
@Expose
|
||||
private int kickAfterRateLimitedTabCompletes = 10;
|
||||
|
||||
private Advanced() {
|
||||
}
|
||||
@@ -741,6 +802,12 @@ public class VelocityConfiguration implements ProxyConfig {
|
||||
this.logCommandExecutions = config.getOrElse("log-command-executions", false);
|
||||
this.logPlayerConnections = config.getOrElse("log-player-connections", true);
|
||||
this.acceptTransfers = config.getOrElse("accepts-transfers", false);
|
||||
this.enableReusePort = config.getOrElse("enable-reuse-port", false);
|
||||
this.commandRateLimit = config.getIntOrElse("command-rate-limit", 25);
|
||||
this.forwardCommandsIfRateLimited = config.getOrElse("forward-commands-if-rate-limited", true);
|
||||
this.kickAfterRateLimitedCommands = config.getIntOrElse("kick-after-rate-limited-commands", 0);
|
||||
this.tabCompleteRateLimit = config.getIntOrElse("tab-complete-rate-limit", 10); // very lenient
|
||||
this.kickAfterRateLimitedTabCompletes = config.getIntOrElse("kick-after-rate-limited-tab-completes", 0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -804,6 +871,30 @@ public class VelocityConfiguration implements ProxyConfig {
|
||||
return this.acceptTransfers;
|
||||
}
|
||||
|
||||
public boolean isEnableReusePort() {
|
||||
return enableReusePort;
|
||||
}
|
||||
|
||||
public int getCommandRateLimit() {
|
||||
return commandRateLimit;
|
||||
}
|
||||
|
||||
public boolean isForwardCommandsIfRateLimited() {
|
||||
return forwardCommandsIfRateLimited;
|
||||
}
|
||||
|
||||
public int getKickAfterRateLimitedCommands() {
|
||||
return kickAfterRateLimitedCommands;
|
||||
}
|
||||
|
||||
public int getTabCompleteRateLimit() {
|
||||
return tabCompleteRateLimit;
|
||||
}
|
||||
|
||||
public int getKickAfterRateLimitedTabCompletes() {
|
||||
return kickAfterRateLimitedTabCompletes;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Advanced{"
|
||||
@@ -821,6 +912,7 @@ public class VelocityConfiguration implements ProxyConfig {
|
||||
+ ", logCommandExecutions=" + logCommandExecutions
|
||||
+ ", logPlayerConnections=" + logPlayerConnections
|
||||
+ ", acceptTransfers=" + acceptTransfers
|
||||
+ ", enableReusePort=" + enableReusePort
|
||||
+ '}';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,6 +46,7 @@ import com.velocitypowered.proxy.protocol.netty.MinecraftCompressDecoder;
|
||||
import com.velocitypowered.proxy.protocol.netty.MinecraftCompressorAndLengthEncoder;
|
||||
import com.velocitypowered.proxy.protocol.netty.MinecraftDecoder;
|
||||
import com.velocitypowered.proxy.protocol.netty.MinecraftEncoder;
|
||||
import com.velocitypowered.proxy.protocol.netty.MinecraftVarintFrameDecoder;
|
||||
import com.velocitypowered.proxy.protocol.netty.MinecraftVarintLengthEncoder;
|
||||
import com.velocitypowered.proxy.protocol.netty.PlayPacketQueueInboundHandler;
|
||||
import com.velocitypowered.proxy.protocol.netty.PlayPacketQueueOutboundHandler;
|
||||
@@ -84,6 +85,7 @@ public class MinecraftConnection extends ChannelInboundHandlerAdapter {
|
||||
private static final Logger logger = LogManager.getLogger(MinecraftConnection.class);
|
||||
|
||||
private final Channel channel;
|
||||
public boolean pendingConfigurationSwitch = false;
|
||||
private SocketAddress remoteAddress;
|
||||
private StateRegistry state;
|
||||
private Map<StateRegistry, MinecraftSessionHandler> sessionHandlers;
|
||||
@@ -367,6 +369,11 @@ public class MinecraftConnection extends ChannelInboundHandlerAdapter {
|
||||
ensureInEventLoop();
|
||||
|
||||
this.state = state;
|
||||
final MinecraftVarintFrameDecoder frameDecoder = this.channel.pipeline()
|
||||
.get(MinecraftVarintFrameDecoder.class);
|
||||
if (frameDecoder != null) {
|
||||
frameDecoder.setState(state);
|
||||
}
|
||||
// If the connection is LEGACY (<1.6), the decoder and encoder are not set.
|
||||
final MinecraftEncoder minecraftEncoder = this.channel.pipeline()
|
||||
.get(MinecraftEncoder.class);
|
||||
|
||||
@@ -23,7 +23,11 @@ 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;
|
||||
@@ -48,6 +52,7 @@ 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;
|
||||
@@ -67,6 +72,8 @@ 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;
|
||||
@@ -364,4 +371,32 @@ 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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,6 +49,7 @@ import com.velocitypowered.proxy.connection.util.ConnectionMessages;
|
||||
import com.velocitypowered.proxy.protocol.MinecraftPacket;
|
||||
import com.velocitypowered.proxy.protocol.StateRegistry;
|
||||
import com.velocitypowered.proxy.protocol.netty.MinecraftDecoder;
|
||||
import com.velocitypowered.proxy.protocol.netty.MinecraftVarintFrameDecoder;
|
||||
import com.velocitypowered.proxy.protocol.packet.AvailableCommandsPacket;
|
||||
import com.velocitypowered.proxy.protocol.packet.BossBarPacket;
|
||||
import com.velocitypowered.proxy.protocol.packet.BundleDelimiterPacket;
|
||||
@@ -151,6 +152,7 @@ public class BackendPlaySessionHandler implements MinecraftSessionHandler {
|
||||
MinecraftConnection smc = serverConn.ensureConnected();
|
||||
smc.setAutoReading(false);
|
||||
// Even when not auto reading messages are still decoded. Decode them with the correct state
|
||||
smc.getChannel().pipeline().get(MinecraftVarintFrameDecoder.class).setState(StateRegistry.CONFIG);
|
||||
smc.getChannel().pipeline().get(MinecraftDecoder.class).setState(StateRegistry.CONFIG);
|
||||
serverConn.getPlayer().switchToConfigState();
|
||||
return true;
|
||||
@@ -177,10 +179,12 @@ public class BackendPlaySessionHandler implements MinecraftSessionHandler {
|
||||
|
||||
@Override
|
||||
public boolean handle(BossBarPacket packet) {
|
||||
if (packet.getAction() == BossBarPacket.ADD) {
|
||||
playerSessionHandler.getServerBossBars().add(packet.getUuid());
|
||||
} else if (packet.getAction() == BossBarPacket.REMOVE) {
|
||||
playerSessionHandler.getServerBossBars().remove(packet.getUuid());
|
||||
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());
|
||||
}
|
||||
}
|
||||
return false; // forward
|
||||
}
|
||||
@@ -342,7 +346,12 @@ public class BackendPlaySessionHandler implements MinecraftSessionHandler {
|
||||
// Inject commands from the proxy.
|
||||
final CommandGraphInjector<CommandSource> injector = server.getCommandManager().getInjector();
|
||||
injector.inject(rootNode, serverConn.getPlayer());
|
||||
rootNode.removeChildByName("velocity:callback");
|
||||
|
||||
// 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");
|
||||
}
|
||||
}
|
||||
|
||||
server.getEventManager().fire(
|
||||
|
||||
@@ -20,6 +20,7 @@ package com.velocitypowered.proxy.connection.backend;
|
||||
import com.velocitypowered.api.network.ProtocolVersion;
|
||||
import com.velocitypowered.api.proxy.Player;
|
||||
import com.velocitypowered.api.proxy.ServerConnection;
|
||||
import com.velocitypowered.api.proxy.messages.ChannelIdentifier;
|
||||
import com.velocitypowered.api.proxy.messages.LegacyChannelIdentifier;
|
||||
import com.velocitypowered.api.proxy.messages.MinecraftChannelIdentifier;
|
||||
import com.velocitypowered.api.proxy.server.RegisteredServer;
|
||||
@@ -301,9 +302,24 @@ public class BungeeCordMessageResponder {
|
||||
}
|
||||
}
|
||||
|
||||
static String getBungeeCordChannel(ProtocolVersion version) {
|
||||
return version.noLessThan(ProtocolVersion.MINECRAFT_1_13) ? MODERN_CHANNEL.getId()
|
||||
: LEGACY_CHANNEL.getId();
|
||||
private void processGetPlayerServer(ByteBufDataInput in) {
|
||||
proxy.getPlayer(in.readUTF()).ifPresent(player -> {
|
||||
player.getCurrentServer().ifPresent(server -> {
|
||||
ByteBuf buf = Unpooled.buffer();
|
||||
ByteBufDataOutput out = new ByteBufDataOutput(buf);
|
||||
|
||||
out.writeUTF("GetPlayerServer");
|
||||
out.writeUTF(player.getUsername());
|
||||
out.writeUTF(server.getServerInfo().getName());
|
||||
|
||||
sendResponseOnConnection(buf);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
static ChannelIdentifier getBungeeCordChannel(ProtocolVersion version) {
|
||||
return version.noLessThan(ProtocolVersion.MINECRAFT_1_13) ? MODERN_CHANNEL
|
||||
: LEGACY_CHANNEL;
|
||||
}
|
||||
|
||||
// Note: this method will always release the buffer!
|
||||
@@ -314,8 +330,8 @@ public class BungeeCordMessageResponder {
|
||||
// Note: this method will always release the buffer!
|
||||
private static void sendServerResponse(ConnectedPlayer player, ByteBuf buf) {
|
||||
MinecraftConnection serverConnection = player.ensureAndGetCurrentServer().ensureConnected();
|
||||
String chan = getBungeeCordChannel(serverConnection.getProtocolVersion());
|
||||
PluginMessagePacket msg = new PluginMessagePacket(chan, buf);
|
||||
ChannelIdentifier chan = getBungeeCordChannel(serverConnection.getProtocolVersion());
|
||||
PluginMessagePacket msg = new PluginMessagePacket(chan.getId(), buf);
|
||||
serverConnection.write(msg);
|
||||
}
|
||||
|
||||
@@ -331,6 +347,9 @@ public class BungeeCordMessageResponder {
|
||||
ByteBufDataInput in = new ByteBufDataInput(message.content());
|
||||
String subChannel = in.readUTF();
|
||||
switch (subChannel) {
|
||||
case "GetPlayerServer":
|
||||
this.processGetPlayerServer(in);
|
||||
break;
|
||||
case "ForwardToPlayer":
|
||||
this.processForwardToPlayer(in);
|
||||
break;
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
|
||||
package com.velocitypowered.proxy.connection.backend;
|
||||
|
||||
import com.velocitypowered.api.event.connection.PluginMessageEvent;
|
||||
import com.velocitypowered.api.event.connection.PreTransferEvent;
|
||||
import com.velocitypowered.api.event.player.CookieRequestEvent;
|
||||
import com.velocitypowered.api.event.player.CookieStoreEvent;
|
||||
@@ -24,6 +25,7 @@ import com.velocitypowered.api.event.player.PlayerResourcePackStatusEvent;
|
||||
import com.velocitypowered.api.event.player.ServerResourcePackRemoveEvent;
|
||||
import com.velocitypowered.api.event.player.ServerResourcePackSendEvent;
|
||||
import com.velocitypowered.api.network.ProtocolVersion;
|
||||
import com.velocitypowered.api.proxy.messages.ChannelIdentifier;
|
||||
import com.velocitypowered.api.proxy.player.ResourcePackInfo;
|
||||
import com.velocitypowered.proxy.VelocityServer;
|
||||
import com.velocitypowered.proxy.connection.MinecraftConnection;
|
||||
@@ -38,6 +40,7 @@ import com.velocitypowered.proxy.connection.util.ConnectionRequestResults.Impl;
|
||||
import com.velocitypowered.proxy.protocol.MinecraftPacket;
|
||||
import com.velocitypowered.proxy.protocol.StateRegistry;
|
||||
import com.velocitypowered.proxy.protocol.netty.MinecraftDecoder;
|
||||
import com.velocitypowered.proxy.protocol.netty.MinecraftVarintFrameDecoder;
|
||||
import com.velocitypowered.proxy.protocol.packet.ClientboundCookieRequestPacket;
|
||||
import com.velocitypowered.proxy.protocol.packet.ClientboundStoreCookiePacket;
|
||||
import com.velocitypowered.proxy.protocol.packet.DisconnectPacket;
|
||||
@@ -49,11 +52,14 @@ 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;
|
||||
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 java.io.IOException;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
@@ -228,6 +234,7 @@ public class ConfigSessionHandler implements MinecraftSessionHandler {
|
||||
final ConnectedPlayer player = serverConn.getPlayer();
|
||||
final ClientConfigSessionHandler configHandler = (ClientConfigSessionHandler) player.getConnection().getActiveSessionHandler();
|
||||
|
||||
smc.getChannel().pipeline().get(MinecraftVarintFrameDecoder.class).setState(StateRegistry.PLAY);
|
||||
smc.getChannel().pipeline().get(MinecraftDecoder.class).setState(StateRegistry.PLAY);
|
||||
//noinspection DataFlowIssue
|
||||
configHandler.handleBackendFinishUpdate(serverConn).thenRunAsync(() -> {
|
||||
@@ -250,7 +257,13 @@ public class ConfigSessionHandler implements MinecraftSessionHandler {
|
||||
@Override
|
||||
public boolean handle(DisconnectPacket packet) {
|
||||
serverConn.disconnect();
|
||||
resultFuture.complete(ConnectionRequestResults.forDisconnect(packet, serverConn.getServer()));
|
||||
// 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);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -261,7 +274,29 @@ public class ConfigSessionHandler implements MinecraftSessionHandler {
|
||||
PluginMessageUtil.rewriteMinecraftBrand(packet, server.getVersion(),
|
||||
serverConn.getPlayer().getProtocolVersion()));
|
||||
} else {
|
||||
serverConn.getPlayer().getConnection().write(packet.retain());
|
||||
byte[] bytes = ByteBufUtil.getBytes(packet.content());
|
||||
ChannelIdentifier id = this.server.getChannelRegistrar().getFromId(packet.getChannel());
|
||||
|
||||
if (id == null) {
|
||||
serverConn.getPlayer().getConnection().write(packet.retain());
|
||||
return true;
|
||||
}
|
||||
|
||||
// Handling this stuff async means that we should probably pause
|
||||
// the connection while we toss this off into another pool
|
||||
this.serverConn.getConnection().setAutoReading(false);
|
||||
this.server.getEventManager()
|
||||
.fire(new PluginMessageEvent(serverConn, serverConn.getPlayer(), id, bytes))
|
||||
.thenAcceptAsync(pme -> {
|
||||
if (pme.getResult().isAllowed() && !serverConn.getPlayer().getConnection().isClosed()) {
|
||||
serverConn.getPlayer().getConnection().write(new PluginMessagePacket(
|
||||
pme.getIdentifier().getId(), Unpooled.wrappedBuffer(bytes)));
|
||||
}
|
||||
this.serverConn.getConnection().setAutoReading(true);
|
||||
}, serverConn.ensureConnected().eventLoop()).exceptionally((ex) -> {
|
||||
logger.error("Exception while handling plugin message {}", packet, ex);
|
||||
return null;
|
||||
});
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -330,6 +365,12 @@ 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.completeExceptionally(
|
||||
|
||||
@@ -165,7 +165,7 @@ public class LoginSessionHandler implements MinecraftSessionHandler {
|
||||
}
|
||||
if (player.getConnection().getActiveSessionHandler() instanceof ClientPlaySessionHandler clientPlaySessionHandler) {
|
||||
smc.setAutoReading(false);
|
||||
clientPlaySessionHandler.doSwitch().thenAcceptAsync((unused) -> smc.setAutoReading(true), smc.eventLoop());
|
||||
clientPlaySessionHandler.doSwitch().thenRunAsync(() -> smc.setAutoReading(true), smc.eventLoop());
|
||||
} else {
|
||||
// Initial login - the player is already in configuration state.
|
||||
server.getEventManager().fireAndForget(new PlayerEnteredConfigurationEvent(player, serverConn));
|
||||
|
||||
@@ -53,6 +53,7 @@ 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;
|
||||
|
||||
@@ -70,6 +71,7 @@ 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.
|
||||
@@ -324,6 +326,14 @@ 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.
|
||||
|
||||
@@ -97,7 +97,7 @@ public class AuthSessionHandler implements MinecraftSessionHandler {
|
||||
// Initiate a regular connection and move over to it.
|
||||
ConnectedPlayer player = new ConnectedPlayer(server, profileEvent.getGameProfile(),
|
||||
mcConnection, inbound.getVirtualHost().orElse(null), inbound.getRawVirtualHost().orElse(null), onlineMode,
|
||||
inbound.getIdentifiedKey());
|
||||
inbound.getHandshakeIntent(), inbound.getIdentifiedKey());
|
||||
this.connectedPlayer = player;
|
||||
if (!server.canRegisterConnection(player)) {
|
||||
player.disconnect0(
|
||||
@@ -106,7 +106,9 @@ public class AuthSessionHandler implements MinecraftSessionHandler {
|
||||
return CompletableFuture.completedFuture(null);
|
||||
}
|
||||
|
||||
logger.info("{} has connected", player);
|
||||
if (server.getConfiguration().isLogPlayerConnections()) {
|
||||
logger.info("{} has connected", player);
|
||||
}
|
||||
|
||||
return server.getEventManager()
|
||||
.fire(new PermissionsSetupEvent(player, ConnectedPlayer.DEFAULT_PERMISSIONS))
|
||||
|
||||
@@ -17,14 +17,17 @@
|
||||
|
||||
package com.velocitypowered.proxy.connection.client;
|
||||
|
||||
import com.velocitypowered.api.event.connection.PluginMessageEvent;
|
||||
import com.velocitypowered.api.event.player.CookieReceiveEvent;
|
||||
import com.velocitypowered.api.event.player.PlayerClientBrandEvent;
|
||||
import com.velocitypowered.api.event.player.configuration.PlayerConfigurationEvent;
|
||||
import com.velocitypowered.api.event.player.configuration.PlayerFinishConfigurationEvent;
|
||||
import com.velocitypowered.api.event.player.configuration.PlayerFinishedConfigurationEvent;
|
||||
import com.velocitypowered.api.proxy.messages.ChannelIdentifier;
|
||||
import com.velocitypowered.proxy.VelocityServer;
|
||||
import com.velocitypowered.proxy.connection.MinecraftConnection;
|
||||
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
|
||||
import com.velocitypowered.proxy.connection.backend.BungeeCordMessageResponder;
|
||||
import com.velocitypowered.proxy.connection.backend.VelocityServerConnection;
|
||||
import com.velocitypowered.proxy.connection.player.resourcepack.ResourcePackResponseBundle;
|
||||
import com.velocitypowered.proxy.protocol.MinecraftPacket;
|
||||
@@ -37,10 +40,13 @@ 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;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.ByteBufUtil;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
@@ -123,8 +129,32 @@ public class ClientConfigSessionHandler implements MinecraftSessionHandler {
|
||||
brandChannel = packet.getChannel();
|
||||
// Client sends `minecraft:brand` packet immediately after Login,
|
||||
// but at this time the backend server may not be ready
|
||||
} else if (BungeeCordMessageResponder.isBungeeCordMessage(packet)) {
|
||||
return true;
|
||||
} else if (serverConn != null) {
|
||||
serverConn.ensureConnected().write(packet.retain());
|
||||
byte[] bytes = ByteBufUtil.getBytes(packet.content());
|
||||
ChannelIdentifier id = this.server.getChannelRegistrar().getFromId(packet.getChannel());
|
||||
|
||||
if (id == null) {
|
||||
serverConn.ensureConnected().write(packet.retain());
|
||||
return true;
|
||||
}
|
||||
|
||||
// Handling this stuff async means that we should probably pause
|
||||
// the connection while we toss this off into another pool
|
||||
serverConn.getPlayer().getConnection().setAutoReading(false);
|
||||
this.server.getEventManager()
|
||||
.fire(new PluginMessageEvent(serverConn.getPlayer(), serverConn, id, bytes))
|
||||
.thenAcceptAsync(pme -> {
|
||||
if (pme.getResult().isAllowed() && serverConn.getConnection() != null) {
|
||||
serverConn.ensureConnected().write(new PluginMessagePacket(
|
||||
pme.getIdentifier().getId(), Unpooled.wrappedBuffer(bytes)));
|
||||
}
|
||||
serverConn.getPlayer().getConnection().setAutoReading(true);
|
||||
}, player.getConnection().eventLoop()).exceptionally((ex) -> {
|
||||
logger.error("Exception while handling plugin message packet for {}", player, ex);
|
||||
return null;
|
||||
});
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -142,7 +172,11 @@ public class ClientConfigSessionHandler implements MinecraftSessionHandler {
|
||||
@Override
|
||||
public boolean handle(KnownPacksPacket packet) {
|
||||
callConfigurationEvent().thenRun(() -> {
|
||||
player.getConnectionInFlightOrConnectedServer().ensureConnected().write(packet);
|
||||
VelocityServerConnection targetServer =
|
||||
player.getConnectionInFlightOrConnectedServer();
|
||||
if (targetServer != null) {
|
||||
targetServer.ensureConnected().write(packet);
|
||||
}
|
||||
}).exceptionally(ex -> {
|
||||
logger.error("Error forwarding known packs response to backend:", ex);
|
||||
return null;
|
||||
@@ -173,6 +207,26 @@ 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();
|
||||
|
||||
@@ -21,7 +21,6 @@ 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;
|
||||
@@ -74,6 +73,7 @@ import com.velocitypowered.proxy.protocol.packet.config.FinishedUpdatePacket;
|
||||
import com.velocitypowered.proxy.protocol.packet.title.GenericTitlePacket;
|
||||
import com.velocitypowered.proxy.protocol.util.PluginMessageUtil;
|
||||
import com.velocitypowered.proxy.util.CharacterUtil;
|
||||
import com.velocitypowered.proxy.util.except.QuietRuntimeException;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.ByteBufUtil;
|
||||
import io.netty.buffer.Unpooled;
|
||||
@@ -88,6 +88,7 @@ import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
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;
|
||||
@@ -113,6 +114,8 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
|
||||
|
||||
private CompletableFuture<Void> configSwitchFuture;
|
||||
|
||||
private int failedTabCompleteAttempts;
|
||||
|
||||
/**
|
||||
* Constructs a client play session handler.
|
||||
*
|
||||
@@ -160,7 +163,7 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
|
||||
@Override
|
||||
public void activated() {
|
||||
configSwitchFuture = new CompletableFuture<>();
|
||||
Collection<String> channels =
|
||||
Collection<ChannelIdentifier> channels =
|
||||
server.getChannelRegistrar().getChannelsForProtocol(player.getProtocolVersion());
|
||||
if (!channels.isEmpty()) {
|
||||
PluginMessagePacket register = constructChannelsPacket(player.getProtocolVersion(), channels);
|
||||
@@ -170,6 +173,7 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
|
||||
|
||||
@Override
|
||||
public void deactivated() {
|
||||
player.discardChatQueue();
|
||||
for (PluginMessagePacket message : loginPluginMessages) {
|
||||
ReferenceCountUtil.release(message);
|
||||
}
|
||||
@@ -307,20 +311,17 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
|
||||
logger.warn("A plugin message was received while the backend server was not "
|
||||
+ "ready. Channel: {}. Packet discarded.", packet.getChannel());
|
||||
} else if (PluginMessageUtil.isRegister(packet)) {
|
||||
List<String> channels = PluginMessageUtil.getChannels(packet);
|
||||
List<ChannelIdentifier> channelIdentifiers = new ArrayList<>();
|
||||
for (String channel : channels) {
|
||||
try {
|
||||
channelIdentifiers.add(MinecraftChannelIdentifier.from(channel));
|
||||
} catch (IllegalArgumentException e) {
|
||||
channelIdentifiers.add(new LegacyChannelIdentifier(channel));
|
||||
}
|
||||
}
|
||||
List<ChannelIdentifier> channels =
|
||||
PluginMessageUtil.getChannels(this.player.getClientsideChannels().size(), packet,
|
||||
this.player.getProtocolVersion());
|
||||
player.getClientsideChannels().addAll(channels);
|
||||
server.getEventManager()
|
||||
.fireAndForget(
|
||||
new PlayerChannelRegisterEvent(player, ImmutableList.copyOf(channelIdentifiers)));
|
||||
new PlayerChannelRegisterEvent(player, ImmutableList.copyOf(channels)));
|
||||
backendConn.write(packet.retain());
|
||||
} else if (PluginMessageUtil.isUnregister(packet)) {
|
||||
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());
|
||||
@@ -376,10 +377,14 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
|
||||
|
||||
@Override
|
||||
public boolean handle(FinishedUpdatePacket packet) {
|
||||
if (!player.getConnection().pendingConfigurationSwitch) {
|
||||
throw new QuietRuntimeException("Not expecting reconfiguration");
|
||||
}
|
||||
// Complete client switch
|
||||
player.getConnection().setActiveSessionHandler(StateRegistry.CONFIG);
|
||||
VelocityServerConnection serverConnection = player.getConnectedServer();
|
||||
server.getEventManager().fireAndForget(new PlayerEnteredConfigurationEvent(player, serverConnection));
|
||||
server.getEventManager()
|
||||
.fireAndForget(new PlayerEnteredConfigurationEvent(player, serverConnection));
|
||||
if (serverConnection != null) {
|
||||
MinecraftConnection smc = serverConnection.ensureConnected();
|
||||
CompletableFuture.runAsync(() -> {
|
||||
@@ -426,6 +431,13 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handle(JoinGamePacket packet) {
|
||||
// Forward the packet as normal, but discard any chat state we have queued - the client will do this too
|
||||
player.discardChatQueue();
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleGeneric(MinecraftPacket packet) {
|
||||
VelocityServerConnection serverConnection = player.getConnectedServer();
|
||||
@@ -507,9 +519,13 @@ 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();
|
||||
@@ -547,23 +563,32 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
|
||||
}
|
||||
}
|
||||
|
||||
// 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);
|
||||
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();
|
||||
}
|
||||
serverBossBars.clear();
|
||||
|
||||
// Tell the server about the proxy's plugin message channels.
|
||||
ProtocolVersion serverVersion = serverMc.getProtocolVersion();
|
||||
final Collection<String> channels = server.getChannelRegistrar()
|
||||
final Collection<ChannelIdentifier> channels = server.getChannelRegistrar()
|
||||
.getChannelsForProtocol(serverMc.getProtocolVersion());
|
||||
if (!channels.isEmpty()) {
|
||||
serverMc.delayedWrite(constructChannelsPacket(serverVersion, channels));
|
||||
}
|
||||
// Tell the server about this client's plugin message channels.
|
||||
if (!player.getClientsideChannels().isEmpty()) {
|
||||
serverMc.delayedWrite(constructChannelsPacket(serverVersion, player.getClientsideChannels()));
|
||||
}
|
||||
|
||||
// If we had plugin messages queued during login/FML handshake, send them now.
|
||||
PluginMessagePacket pm;
|
||||
@@ -645,29 +670,52 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!server.getTabCompleteRateLimiter().attempt(player.getUniqueId())) {
|
||||
if (server.getConfiguration().isKickOnTabCompleteRateLimit()
|
||||
&& failedTabCompleteAttempts++ >= server.getConfiguration().getKickAfterRateLimitedTabCompletes()) {
|
||||
player.disconnect(Component.translatable("velocity.kick.tab-complete-rate-limit"));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
failedTabCompleteAttempts = 0;
|
||||
|
||||
server.getCommandManager().offerBrigadierSuggestions(player, command)
|
||||
.thenAcceptAsync(suggestions -> {
|
||||
if (suggestions.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
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());
|
||||
int startPos = -1;
|
||||
for (var suggestion : suggestions.getList()) {
|
||||
if (startPos == -1 || startPos > suggestion.getRange().getStart()) {
|
||||
startPos = suggestion.getRange().getStart();
|
||||
}
|
||||
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);
|
||||
resp.setLength(packet.getCommand().length() - startPos);
|
||||
resp.setStart(startPos + 1);
|
||||
resp.setLength(packet.getCommand().length() - startPos - 1);
|
||||
resp.getOffers().addAll(offers);
|
||||
player.getConnection().write(resp);
|
||||
}
|
||||
@@ -722,10 +770,10 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
|
||||
offer = offer.substring(command.length());
|
||||
}
|
||||
ComponentHolder tooltip = null;
|
||||
if (suggestion.getTooltip() != null
|
||||
&& suggestion.getTooltip() instanceof VelocityBrigadierMessage) {
|
||||
tooltip = new ComponentHolder(player.getProtocolVersion(),
|
||||
((VelocityBrigadierMessage) suggestion.getTooltip()).asComponent());
|
||||
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()));
|
||||
}
|
||||
response.getOffers().add(new Offer(offer, tooltip));
|
||||
}
|
||||
|
||||
@@ -38,6 +38,7 @@ import com.velocitypowered.api.event.player.PlayerModInfoEvent;
|
||||
import com.velocitypowered.api.event.player.PlayerSettingsChangedEvent;
|
||||
import com.velocitypowered.api.event.player.ServerPreConnectEvent;
|
||||
import com.velocitypowered.api.event.player.configuration.PlayerEnterConfigurationEvent;
|
||||
import com.velocitypowered.api.network.HandshakeIntent;
|
||||
import com.velocitypowered.api.network.ProtocolState;
|
||||
import com.velocitypowered.api.network.ProtocolVersion;
|
||||
import com.velocitypowered.api.permission.PermissionFunction;
|
||||
@@ -61,6 +62,7 @@ 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;
|
||||
@@ -72,6 +74,8 @@ 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;
|
||||
@@ -98,6 +102,7 @@ import com.velocitypowered.proxy.tablist.VelocityTabListLegacy;
|
||||
import com.velocitypowered.proxy.util.ClosestLocaleMatcher;
|
||||
import com.velocitypowered.proxy.util.DurationUtils;
|
||||
import com.velocitypowered.proxy.util.TranslatableMapper;
|
||||
import com.velocitypowered.proxy.util.collect.CappedSet;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import java.net.InetSocketAddress;
|
||||
@@ -121,9 +126,12 @@ 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;
|
||||
@@ -143,19 +151,30 @@ import org.jetbrains.annotations.NotNull;
|
||||
public class ConnectedPlayer implements MinecraftConnectionAssociation, Player, KeyIdentifiable,
|
||||
VelocityInboundConnection {
|
||||
|
||||
public static final int MAX_CLIENTSIDE_PLUGIN_CHANNELS = Integer.getInteger("velocity.max-clientside-plugin-channels", 1024);
|
||||
private static final PlainTextComponentSerializer PASS_THRU_TRANSLATE =
|
||||
PlainTextComponentSerializer.builder().flattener(TranslatableMapper.FLATTENER).build();
|
||||
static final PermissionProvider DEFAULT_PERMISSIONS = s -> PermissionFunction.ALWAYS_UNDEFINED;
|
||||
|
||||
private static final ComponentLogger logger = ComponentLogger.logger(ConnectedPlayer.class);
|
||||
|
||||
private final Identity identity = new IdentityImpl();
|
||||
private static final @NotNull PointersSupplier<ConnectedPlayer> POINTERS_SUPPLIER =
|
||||
PointersSupplier.<ConnectedPlayer>builder()
|
||||
.resolving(Identity.UUID, Player::getUniqueId)
|
||||
.resolving(Identity.NAME, Player::getUsername)
|
||||
.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();
|
||||
|
||||
/**
|
||||
* The actual Minecraft connection. This is actually a wrapper object around the Netty channel.
|
||||
*/
|
||||
private final MinecraftConnection connection;
|
||||
private final @Nullable InetSocketAddress virtualHost;
|
||||
private final @Nullable String rawVirtualHost;
|
||||
private final HandshakeIntent handshakeIntent;
|
||||
private GameProfile profile;
|
||||
private PermissionFunction permissionFunction;
|
||||
private int tryIndex = 0;
|
||||
@@ -171,37 +190,33 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player,
|
||||
private final InternalTabList tabList;
|
||||
private final VelocityServer server;
|
||||
private ClientConnectionPhase connectionPhase;
|
||||
private final Collection<ChannelIdentifier> clientsideChannels;
|
||||
private final CompletableFuture<Void> teardownFuture = new CompletableFuture<>();
|
||||
private @MonotonicNonNull List<String> serversToTry = null;
|
||||
private final ResourcePackHandler resourcePackHandler;
|
||||
private final BundleDelimiterHandler bundleHandler = new BundleDelimiterHandler(this);
|
||||
|
||||
private final @NotNull Pointers pointers =
|
||||
Player.super.pointers().toBuilder()
|
||||
.withDynamic(Identity.UUID, this::getUniqueId)
|
||||
.withDynamic(Identity.NAME, this::getUsername)
|
||||
.withDynamic(Identity.DISPLAY_NAME, () -> Component.text(this.getUsername()))
|
||||
.withDynamic(Identity.LOCALE, this::getEffectiveLocale)
|
||||
.withStatic(PermissionChecker.POINTER, getPermissionChecker())
|
||||
.withStatic(FacetPointers.TYPE, Type.PLAYER).build();
|
||||
private @Nullable String clientBrand;
|
||||
private @Nullable Locale effectiveLocale;
|
||||
private final @Nullable IdentifiedKey playerKey;
|
||||
private @Nullable ClientSettingsPacket clientSettingsPacket;
|
||||
private final ChatQueue chatQueue;
|
||||
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,
|
||||
@Nullable IdentifiedKey playerKey) {
|
||||
HandshakeIntent handshakeIntent, @Nullable IdentifiedKey playerKey) {
|
||||
this.server = server;
|
||||
this.profile = profile;
|
||||
this.connection = connection;
|
||||
this.virtualHost = virtualHost;
|
||||
this.rawVirtualHost = rawVirtualHost;
|
||||
this.handshakeIntent = handshakeIntent;
|
||||
this.permissionFunction = PermissionFunction.ALWAYS_UNDEFINED;
|
||||
this.connectionPhase = connection.getType().getInitialClientPhase();
|
||||
this.onlineMode = onlineMode;
|
||||
this.clientsideChannels = CappedSet.create(MAX_CLIENTSIDE_PLUGIN_CHANNELS);
|
||||
|
||||
if (connection.getProtocolVersion().noLessThan(ProtocolVersion.MINECRAFT_1_19_3)) {
|
||||
this.tabList = new VelocityTabList(this);
|
||||
@@ -214,6 +229,7 @@ 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);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -233,13 +249,24 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player,
|
||||
return chatQueue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Discards any messages still being processed by the {@link ChatQueue}, and creates a fresh state for future packets.
|
||||
* This should be used on server switches, or whenever the client resets its own 'last seen' state.
|
||||
*/
|
||||
public void discardChatQueue() {
|
||||
// No need for atomic swap, should only be called from event loop
|
||||
final ChatQueue oldChatQueue = chatQueue;
|
||||
chatQueue = new ChatQueue(this);
|
||||
oldChatQueue.close();
|
||||
}
|
||||
|
||||
public BundleDelimiterHandler getBundleHandler() {
|
||||
return this.bundleHandler;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull Identity identity() {
|
||||
return this.identity;
|
||||
return Identity.identity(this.getUniqueId());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -345,7 +372,7 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player,
|
||||
|
||||
@Override
|
||||
public @NotNull Pointers pointers() {
|
||||
return this.pointers;
|
||||
return POINTERS_SUPPLIER.view(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -378,14 +405,20 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player,
|
||||
}
|
||||
|
||||
/**
|
||||
* Translates the message in the user's locale.
|
||||
* Translates the message in the user's locale, falling back to the default locale if not set.
|
||||
*
|
||||
* @param message the message to translate
|
||||
* @return the translated message
|
||||
*/
|
||||
public Component translateMessage(Component message) {
|
||||
Locale locale = ClosestLocaleMatcher.INSTANCE
|
||||
.lookupClosest(getEffectiveLocale() == null ? Locale.getDefault() : getEffectiveLocale());
|
||||
Locale locale = this.getEffectiveLocale();
|
||||
if (locale == null && settings != null) {
|
||||
locale = settings.getLocale();
|
||||
}
|
||||
if (locale == null) {
|
||||
locale = Locale.getDefault();
|
||||
}
|
||||
locale = ClosestLocaleMatcher.INSTANCE.lookupClosest(locale);
|
||||
return GlobalTranslator.render(message, locale);
|
||||
}
|
||||
|
||||
@@ -707,15 +740,19 @@ 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())) {
|
||||
logger.info("{}: kicked from server {}: {}", this, server.getServerInfo().getName(),
|
||||
plainTextReason);
|
||||
if (this.server.getConfiguration().isLogPlayerConnections()) {
|
||||
logger.info("{}: kicked from server {}: {}", this, server.getServerInfo().getName(),
|
||||
plainTextReason);
|
||||
}
|
||||
handleConnectionException(server, disconnectReason,
|
||||
Component.translatable("velocity.error.moved-to-new-server", NamedTextColor.RED,
|
||||
Component.text(server.getServerInfo().getName()),
|
||||
disconnectReason), safe);
|
||||
} else {
|
||||
logger.error("{}: disconnected while connecting to {}: {}", this,
|
||||
server.getServerInfo().getName(), plainTextReason);
|
||||
if (this.server.getConfiguration().isLogPlayerConnections()) {
|
||||
logger.error("{}: disconnected while connecting to {}: {}", this,
|
||||
server.getServerInfo().getName(), plainTextReason);
|
||||
}
|
||||
handleConnectionException(server, disconnectReason,
|
||||
Component.translatable("velocity.error.cant-connect", NamedTextColor.RED,
|
||||
Component.text(server.getServerInfo().getName()),
|
||||
@@ -780,9 +817,7 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player,
|
||||
createConnectionRequest(res.getServer(), previousConnection).connect()
|
||||
.whenCompleteAsync((status, throwable) -> {
|
||||
if (throwable != null) {
|
||||
handleConnectionException(
|
||||
status != null ? status.getAttemptedConnection() : res.getServer(), throwable,
|
||||
true);
|
||||
handleConnectionException(res.getServer(), throwable, true);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1012,6 +1047,50 @@ 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);
|
||||
@@ -1087,8 +1166,14 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player,
|
||||
throw new IllegalStateException("Can only send server links in CONFIGURATION or PLAY protocol");
|
||||
}
|
||||
|
||||
connection.write(new ClientboundServerLinksPacket(List.copyOf(links).stream()
|
||||
.map(l -> new ClientboundServerLinksPacket.ServerLink(l, getProtocolVersion())).toList()));
|
||||
connection.write(new ClientboundServerLinksPacket(links.stream()
|
||||
.map(l -> new ClientboundServerLinksPacket.ServerLink(
|
||||
l.getBuiltInType().map(Enum::ordinal).orElse(-1),
|
||||
l.getCustomLabel()
|
||||
.map(c -> new ComponentHolder(getProtocolVersion(), translateMessage(c)))
|
||||
.orElse(null),
|
||||
l.getUrl().toString()))
|
||||
.toList()));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -1284,11 +1369,17 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player,
|
||||
public void switchToConfigState() {
|
||||
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()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (bundleHandler.isInBundleSession()) {
|
||||
bundleHandler.toggleBundleSession();
|
||||
connection.write(BundleDelimiterPacket.INSTANCE);
|
||||
}
|
||||
connection.write(StartUpdatePacket.INSTANCE);
|
||||
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.addPlayPacketQueueHandler();
|
||||
@@ -1317,24 +1408,34 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player,
|
||||
this.connectionPhase = connectionPhase;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return all the plugin message channels that registered by client.
|
||||
*
|
||||
* @return the channels
|
||||
*/
|
||||
public Collection<ChannelIdentifier> getClientsideChannels() {
|
||||
return clientsideChannels;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable IdentifiedKey getIdentifiedKey() {
|
||||
return playerKey;
|
||||
}
|
||||
|
||||
private class IdentityImpl implements Identity {
|
||||
|
||||
@Override
|
||||
public @NonNull UUID uuid() {
|
||||
return ConnectedPlayer.this.getUniqueId();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ProtocolState getProtocolState() {
|
||||
return connection.getState().toProtocolState();
|
||||
}
|
||||
|
||||
@Override
|
||||
public HandshakeIntent getHandshakeIntent() {
|
||||
return handshakeIntent;
|
||||
}
|
||||
|
||||
public BossBarManager getBossBarManager() {
|
||||
return bossBarManager;
|
||||
}
|
||||
|
||||
private final class ConnectionRequestBuilderImpl implements ConnectionRequestBuilder {
|
||||
|
||||
private final RegisteredServer toConnect;
|
||||
@@ -1394,7 +1495,16 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player,
|
||||
VelocityServerConnection con =
|
||||
new VelocityServerConnection(vrs, previousServer, ConnectedPlayer.this, server);
|
||||
connectionInFlight = con;
|
||||
return con.connect().whenCompleteAsync((result, exception) -> this.resetIfInFlightIs(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);
|
||||
},
|
||||
connection.eventLoop());
|
||||
}, connection.eventLoop());
|
||||
});
|
||||
@@ -1408,22 +1518,14 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player,
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Result> connect() {
|
||||
return this.internalConnect().whenCompleteAsync((status, throwable) -> {
|
||||
if (status != null && !status.isSuccessful()) {
|
||||
if (!status.isSafe()) {
|
||||
handleConnectionException(status.getAttemptedConnection(), throwable, false);
|
||||
}
|
||||
}
|
||||
}, connection.eventLoop()).thenApply(x -> x);
|
||||
return this.internalConnect().thenApply(x -> x);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Boolean> connectWithIndication() {
|
||||
return internalConnect().whenCompleteAsync((status, throwable) -> {
|
||||
if (throwable != null) {
|
||||
// TODO: The exception handling from this is not very good. Find a better way.
|
||||
handleConnectionException(status != null ? status.getAttemptedConnection() : toConnect,
|
||||
throwable, true);
|
||||
handleConnectionException(toConnect, throwable, true);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -277,5 +277,10 @@ public class HandshakeSessionHandler implements MinecraftSessionHandler {
|
||||
public ProtocolState getProtocolState() {
|
||||
return connection.getState().toProtocolState();
|
||||
}
|
||||
|
||||
@Override
|
||||
public HandshakeIntent getHandshakeIntent() {
|
||||
return HandshakeIntent.STATUS;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
|
||||
package com.velocitypowered.proxy.connection.client;
|
||||
|
||||
import com.velocitypowered.api.network.HandshakeIntent;
|
||||
import com.velocitypowered.api.network.ProtocolState;
|
||||
import com.velocitypowered.api.network.ProtocolVersion;
|
||||
import com.velocitypowered.api.proxy.InboundConnection;
|
||||
@@ -98,6 +99,11 @@ public final class InitialInboundConnection implements VelocityInboundConnection
|
||||
return connection.getState().toProtocolState();
|
||||
}
|
||||
|
||||
@Override
|
||||
public HandshakeIntent getHandshakeIntent() {
|
||||
return handshake.getIntent();
|
||||
}
|
||||
|
||||
/**
|
||||
* Disconnects the connection from the server.
|
||||
*
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
|
||||
package com.velocitypowered.proxy.connection.client;
|
||||
|
||||
import com.velocitypowered.api.network.HandshakeIntent;
|
||||
import com.velocitypowered.api.network.ProtocolState;
|
||||
import com.velocitypowered.api.network.ProtocolVersion;
|
||||
import com.velocitypowered.api.proxy.LoginPhaseConnection;
|
||||
@@ -177,4 +178,9 @@ public class LoginInboundConnection implements LoginPhaseConnection, KeyIdentifi
|
||||
public ProtocolState getProtocolState() {
|
||||
return delegate.getProtocolState();
|
||||
}
|
||||
|
||||
@Override
|
||||
public HandshakeIntent getHandshakeIntent() {
|
||||
return delegate.getHandshakeIntent();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,6 +64,7 @@ class LegacyForgeUtil {
|
||||
if (discriminator == MOD_LIST_DISCRIMINATOR) {
|
||||
ImmutableList.Builder<ModInfo.Mod> mods = ImmutableList.builder();
|
||||
int modCount = ProtocolUtils.readVarInt(contents);
|
||||
Preconditions.checkArgument(modCount < 1024, "Oversized mods list");
|
||||
|
||||
for (int index = 0; index < modCount; index++) {
|
||||
String id = ProtocolUtils.readString(contents);
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
@@ -111,7 +111,7 @@ public abstract sealed class ResourcePackHandler
|
||||
}
|
||||
request.setRequired(queued.getShouldForce());
|
||||
request.setPrompt(queued.getPrompt() == null ? null :
|
||||
new ComponentHolder(player.getProtocolVersion(), queued.getPrompt()));
|
||||
new ComponentHolder(player.getProtocolVersion(), player.translateMessage(queued.getPrompt())));
|
||||
|
||||
player.getConnection().write(request);
|
||||
}
|
||||
|
||||
@@ -30,10 +30,13 @@ import com.velocitypowered.proxy.config.VelocityConfiguration;
|
||||
import com.velocitypowered.proxy.server.VelocityRegisteredServer;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
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.
|
||||
@@ -51,11 +54,27 @@ public class ServerListPingHandler {
|
||||
version = ProtocolVersion.MAXIMUM_VERSION;
|
||||
}
|
||||
VelocityConfiguration configuration = server.getConfiguration();
|
||||
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());
|
||||
Collections.shuffle(unshuffledPlayers);
|
||||
samplePlayers = unshuffledPlayers.subList(0, Math.min(12, server.getPlayerCount()));
|
||||
} else {
|
||||
samplePlayers = ImmutableList.of();
|
||||
}
|
||||
return new ServerPing(
|
||||
new ServerPing.Version(version.getProtocol(),
|
||||
"Velocity " + ProtocolVersion.SUPPORTED_VERSION_STRING),
|
||||
new ServerPing.Players(server.getPlayerCount(), configuration.getShowMaxPlayers(),
|
||||
ImmutableList.of()),
|
||||
samplePlayers),
|
||||
configuration.getMotd(),
|
||||
configuration.getFavicon().orElse(null),
|
||||
configuration.isAnnounceForge() ? ModInfo.DEFAULT : null
|
||||
@@ -63,7 +82,7 @@ public class ServerListPingHandler {
|
||||
}
|
||||
|
||||
private CompletableFuture<ServerPing> attemptPingPassthrough(VelocityInboundConnection connection,
|
||||
PingPassthroughMode mode, List<String> servers, ProtocolVersion responseProtocolVersion) {
|
||||
PingPassthroughMode mode, List<String> servers, ProtocolVersion responseProtocolVersion, String virtualHostStr) {
|
||||
ServerPing fallback = constructLocalPing(connection.getProtocolVersion());
|
||||
List<CompletableFuture<ServerPing>> pings = new ArrayList<>();
|
||||
for (String s : servers) {
|
||||
@@ -73,7 +92,7 @@ public class ServerListPingHandler {
|
||||
}
|
||||
VelocityRegisteredServer vrs = (VelocityRegisteredServer) rs.get();
|
||||
pings.add(vrs.ping(connection.getConnection().eventLoop(), PingOptions.builder()
|
||||
.version(responseProtocolVersion).build()));
|
||||
.version(responseProtocolVersion).virtualHost(virtualHostStr).build()));
|
||||
}
|
||||
if (pings.isEmpty()) {
|
||||
return CompletableFuture.completedFuture(fallback);
|
||||
@@ -89,6 +108,13 @@ public class ServerListPingHandler {
|
||||
if (response == fallback) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (response.getDescriptionComponent() == null) {
|
||||
return response.asBuilder()
|
||||
.description(Component.empty())
|
||||
.build();
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
return fallback;
|
||||
@@ -155,7 +181,7 @@ public class ServerListPingHandler {
|
||||
.orElse("");
|
||||
List<String> serversToTry = server.getConfiguration().getForcedHosts().getOrDefault(
|
||||
virtualHostStr, server.getConfiguration().getAttemptConnectionOrder());
|
||||
return attemptPingPassthrough(connection, passthroughMode, serversToTry, shownVersion);
|
||||
return attemptPingPassthrough(connection, passthroughMode, serversToTry, shownVersion, virtualHostStr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -136,6 +136,10 @@ 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);
|
||||
|
||||
@@ -23,8 +23,6 @@ import com.velocitypowered.proxy.util.except.QuietDecoderException;
|
||||
import it.unimi.dsi.fastutil.Pair;
|
||||
import java.io.IOException;
|
||||
import java.math.BigInteger;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.Key;
|
||||
@@ -111,42 +109,6 @@ public enum EncryptionUtils {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a signature for input data.
|
||||
*
|
||||
* @param algorithm the signature algorithm
|
||||
* @param base the private key to sign with
|
||||
* @param toSign the byte array(s) of data to sign
|
||||
* @return the generated signature
|
||||
*/
|
||||
public static byte[] generateSignature(String algorithm, PrivateKey base, byte[]... toSign) {
|
||||
Preconditions.checkArgument(toSign.length > 0);
|
||||
try {
|
||||
Signature construct = Signature.getInstance(algorithm);
|
||||
construct.initSign(base);
|
||||
for (byte[] bytes : toSign) {
|
||||
construct.update(bytes);
|
||||
}
|
||||
return construct.sign();
|
||||
} catch (GeneralSecurityException e) {
|
||||
throw new IllegalArgumentException("Invalid signature parameters");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Encodes a long array as Big-endian byte array.
|
||||
*
|
||||
* @param bits the long (array) of numbers to encode
|
||||
* @return the encoded bytes
|
||||
*/
|
||||
public static byte[] longToBigEndianByteArray(long... bits) {
|
||||
ByteBuffer ret = ByteBuffer.allocate(8 * bits.length).order(ByteOrder.BIG_ENDIAN);
|
||||
for (long put : bits) {
|
||||
ret.putLong(put);
|
||||
}
|
||||
return ret.array();
|
||||
}
|
||||
|
||||
public static String encodeUrlEncoded(byte[] data) {
|
||||
return MIME_SPECIAL_ENCODER.encodeToString(data);
|
||||
}
|
||||
@@ -155,22 +117,6 @@ public enum EncryptionUtils {
|
||||
return Base64.getMimeDecoder().decode(toParse);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a cer-encoded RSA key into its key bytes.
|
||||
*
|
||||
* @param toParse the cer-encoded key String
|
||||
* @param descriptors the type of key
|
||||
* @return the parsed key bytes
|
||||
*/
|
||||
public static byte[] parsePemEncoded(String toParse, Pair<String, String> descriptors) {
|
||||
int startIdx = toParse.indexOf(descriptors.first());
|
||||
Preconditions.checkArgument(startIdx >= 0);
|
||||
int firstLen = descriptors.first().length();
|
||||
int endIdx = toParse.indexOf(descriptors.second(), firstLen + startIdx) + 1;
|
||||
Preconditions.checkArgument(endIdx > 0);
|
||||
return decodeUrlEncoded(toParse.substring(startIdx + firstLen, endIdx));
|
||||
}
|
||||
|
||||
/**
|
||||
* Encodes an RSA key as String cer format.
|
||||
*
|
||||
|
||||
@@ -350,8 +350,9 @@ public class VelocityEventManager implements EventManager {
|
||||
asyncType = AsyncType.ALWAYS;
|
||||
}
|
||||
|
||||
// The default value of 0 will fall back to PostOrder, the default PostOrder (NORMAL) is also 0
|
||||
final short order;
|
||||
if (subscribe.order() == PostOrder.CUSTOM) {
|
||||
if (subscribe.priority() != 0) {
|
||||
order = subscribe.priority();
|
||||
} else {
|
||||
order = (short) POST_ORDER_MAP.get(subscribe.order());
|
||||
|
||||
@@ -51,7 +51,7 @@ public class BackendChannelInitializer extends ChannelInitializer<Channel> {
|
||||
@Override
|
||||
protected void initChannel(Channel ch) {
|
||||
ch.pipeline()
|
||||
.addLast(FRAME_DECODER, new MinecraftVarintFrameDecoder())
|
||||
.addLast(FRAME_DECODER, new MinecraftVarintFrameDecoder(ProtocolUtils.Direction.CLIENTBOUND))
|
||||
.addLast(READ_TIMEOUT,
|
||||
new ReadTimeoutHandler(server.getConfiguration().getReadTimeout(),
|
||||
TimeUnit.MILLISECONDS))
|
||||
|
||||
@@ -18,6 +18,8 @@
|
||||
package com.velocitypowered.proxy.network;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.google.common.collect.HashMultimap;
|
||||
import com.google.common.collect.Multimap;
|
||||
import com.velocitypowered.api.event.proxy.ListenerBoundEvent;
|
||||
import com.velocitypowered.api.event.proxy.ListenerCloseEvent;
|
||||
import com.velocitypowered.api.network.ListenerType;
|
||||
@@ -28,14 +30,17 @@ import com.velocitypowered.proxy.protocol.netty.GameSpyQueryHandler;
|
||||
import io.netty.bootstrap.Bootstrap;
|
||||
import io.netty.bootstrap.ServerBootstrap;
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelFuture;
|
||||
import io.netty.channel.ChannelFutureListener;
|
||||
import io.netty.channel.ChannelOption;
|
||||
import io.netty.channel.EventLoopGroup;
|
||||
import io.netty.channel.WriteBufferWaterMark;
|
||||
import io.netty.channel.unix.UnixChannelOption;
|
||||
import io.netty.util.concurrent.GlobalEventExecutor;
|
||||
import io.netty.util.concurrent.MultithreadEventExecutorGroup;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.http.HttpClient;
|
||||
import java.util.HashMap;
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
@@ -49,7 +54,7 @@ public final class ConnectionManager {
|
||||
private static final WriteBufferWaterMark SERVER_WRITE_MARK = new WriteBufferWaterMark(1 << 20,
|
||||
1 << 21);
|
||||
private static final Logger LOGGER = LogManager.getLogger(ConnectionManager.class);
|
||||
private final Map<InetSocketAddress, Endpoint> endpoints = new HashMap<>();
|
||||
private final Multimap<InetSocketAddress, Endpoint> endpoints = HashMultimap.create();
|
||||
private final TransportType transportType;
|
||||
private final EventLoopGroup bossGroup;
|
||||
private final EventLoopGroup workerGroup;
|
||||
@@ -93,37 +98,60 @@ public final class ConnectionManager {
|
||||
public void bind(final InetSocketAddress address) {
|
||||
final ServerBootstrap bootstrap = new ServerBootstrap()
|
||||
.channelFactory(this.transportType.serverSocketChannelFactory)
|
||||
.group(this.bossGroup, this.workerGroup)
|
||||
.childOption(ChannelOption.WRITE_BUFFER_WATER_MARK, SERVER_WRITE_MARK)
|
||||
.childHandler(this.serverChannelInitializer.get())
|
||||
.childOption(ChannelOption.TCP_NODELAY, true)
|
||||
.childOption(ChannelOption.IP_TOS, 0x18)
|
||||
.localAddress(address);
|
||||
|
||||
if (transportType.supportsTcpFastOpenServer() && server.getConfiguration().useTcpFastOpen()) {
|
||||
if (server.getConfiguration().useTcpFastOpen()) {
|
||||
bootstrap.option(ChannelOption.TCP_FASTOPEN, 3);
|
||||
}
|
||||
|
||||
bootstrap.bind()
|
||||
.addListener((ChannelFutureListener) future -> {
|
||||
final Channel channel = future.channel();
|
||||
if (future.isSuccess()) {
|
||||
this.endpoints.put(address, new Endpoint(channel, ListenerType.MINECRAFT));
|
||||
if (server.getConfiguration().isEnableReusePort()) {
|
||||
// We don't need a boss group, since each worker will bind to the socket
|
||||
bootstrap.option(UnixChannelOption.SO_REUSEPORT, true)
|
||||
.group(this.workerGroup);
|
||||
} else {
|
||||
bootstrap.group(this.bossGroup, this.workerGroup);
|
||||
}
|
||||
|
||||
// Warn people with console access that HAProxy is in use, see PR: #1436
|
||||
if (this.server.getConfiguration().isProxyProtocol()) {
|
||||
LOGGER.warn("Using HAProxy and listening on {}, please ensure this listener is adequately firewalled.", channel.localAddress());
|
||||
final int binds = server.getConfiguration().isEnableReusePort()
|
||||
? ((MultithreadEventExecutorGroup) this.workerGroup).executorCount() : 1;
|
||||
|
||||
for (int bind = 0; bind < binds; bind++) {
|
||||
// Wait for each bind to open. If we encounter any errors, don't try to bind again.
|
||||
int finalBind = bind;
|
||||
ChannelFuture f = bootstrap.bind()
|
||||
.addListener((ChannelFutureListener) future -> {
|
||||
final Channel channel = future.channel();
|
||||
if (future.isSuccess()) {
|
||||
this.endpoints.put(address, new Endpoint(channel, ListenerType.MINECRAFT));
|
||||
|
||||
LOGGER.info("Listening on {}", channel.localAddress());
|
||||
|
||||
if (finalBind == 0) {
|
||||
// Warn people with console access that HAProxy is in use, see PR: #1436
|
||||
if (this.server.getConfiguration().isProxyProtocol()) {
|
||||
LOGGER.warn(
|
||||
"Using HAProxy and listening on {}, please ensure this listener is adequately firewalled.",
|
||||
channel.localAddress());
|
||||
}
|
||||
|
||||
// Fire the proxy bound event after the socket is bound
|
||||
server.getEventManager().fireAndForget(
|
||||
new ListenerBoundEvent(address, ListenerType.MINECRAFT));
|
||||
}
|
||||
} else {
|
||||
LOGGER.error("Can't bind to {}", address, future.cause());
|
||||
}
|
||||
});
|
||||
f.syncUninterruptibly();
|
||||
|
||||
LOGGER.info("Listening on {}", channel.localAddress());
|
||||
|
||||
// Fire the proxy bound event after the socket is bound
|
||||
server.getEventManager().fireAndForget(
|
||||
new ListenerBoundEvent(address, ListenerType.MINECRAFT));
|
||||
} else {
|
||||
LOGGER.error("Can't bind to {}", address, future.cause());
|
||||
}
|
||||
});
|
||||
if (!f.isSuccess()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -169,7 +197,7 @@ public final class ConnectionManager {
|
||||
this.server.getConfiguration().getConnectTimeout())
|
||||
.group(group == null ? this.workerGroup : group)
|
||||
.resolver(this.resolver.asGroup());
|
||||
if (transportType.supportsTcpFastOpenClient() && server.getConfiguration().useTcpFastOpen()) {
|
||||
if (server.getConfiguration().useTcpFastOpen()) {
|
||||
bootstrap.option(ChannelOption.TCP_FASTOPEN_CONNECT, true);
|
||||
}
|
||||
return bootstrap;
|
||||
@@ -181,17 +209,20 @@ public final class ConnectionManager {
|
||||
* @param oldBind the endpoint to close
|
||||
*/
|
||||
public void close(InetSocketAddress oldBind) {
|
||||
Endpoint endpoint = endpoints.remove(oldBind);
|
||||
Collection<Endpoint> endpoints = this.endpoints.removeAll(oldBind);
|
||||
Preconditions.checkState(!endpoints.isEmpty(), "Endpoint was not registered");
|
||||
|
||||
ListenerType type = endpoints.iterator().next().getType();
|
||||
|
||||
// Fire proxy close event to notify plugins of socket close. We block since plugins
|
||||
// should have a chance to be notified before the server stops accepting connections.
|
||||
server.getEventManager().fire(new ListenerCloseEvent(oldBind, endpoint.getType())).join();
|
||||
server.getEventManager().fire(new ListenerCloseEvent(oldBind, type)).join();
|
||||
|
||||
Channel serverChannel = endpoint.getChannel();
|
||||
|
||||
Preconditions.checkState(serverChannel != null, "Endpoint %s not registered", oldBind);
|
||||
LOGGER.info("Closing endpoint {}", serverChannel.localAddress());
|
||||
serverChannel.close().syncUninterruptibly();
|
||||
for (Endpoint endpoint : endpoints) {
|
||||
Channel serverChannel = endpoint.getChannel();
|
||||
LOGGER.info("Closing endpoint {}", serverChannel.localAddress());
|
||||
serverChannel.close().syncUninterruptibly();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -200,24 +231,28 @@ public final class ConnectionManager {
|
||||
* @param interrupt should closing forward interruptions
|
||||
*/
|
||||
public void closeEndpoints(boolean interrupt) {
|
||||
for (final Map.Entry<InetSocketAddress, Endpoint> entry : this.endpoints.entrySet()) {
|
||||
for (final Map.Entry<InetSocketAddress, Collection<Endpoint>> entry : this.endpoints.asMap()
|
||||
.entrySet()) {
|
||||
final InetSocketAddress address = entry.getKey();
|
||||
final Endpoint endpoint = entry.getValue();
|
||||
final Collection<Endpoint> endpoints = entry.getValue();
|
||||
ListenerType type = endpoints.iterator().next().getType();
|
||||
|
||||
// Fire proxy close event to notify plugins of socket close. We block since plugins
|
||||
// should have a chance to be notified before the server stops accepting connections.
|
||||
server.getEventManager().fire(new ListenerCloseEvent(address, endpoint.getType())).join();
|
||||
server.getEventManager().fire(new ListenerCloseEvent(address, type)).join();
|
||||
|
||||
LOGGER.info("Closing endpoint {}", address);
|
||||
if (interrupt) {
|
||||
try {
|
||||
endpoint.getChannel().close().sync();
|
||||
} catch (final InterruptedException e) {
|
||||
LOGGER.info("Interrupted whilst closing endpoint", e);
|
||||
Thread.currentThread().interrupt();
|
||||
for (Endpoint endpoint : endpoints) {
|
||||
LOGGER.info("Closing endpoint {}", address);
|
||||
if (interrupt) {
|
||||
try {
|
||||
endpoint.getChannel().close().sync();
|
||||
} catch (final InterruptedException e) {
|
||||
LOGGER.info("Interrupted whilst closing endpoint", e);
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
} else {
|
||||
endpoint.getChannel().close().syncUninterruptibly();
|
||||
}
|
||||
} else {
|
||||
endpoint.getChannel().close().syncUninterruptibly();
|
||||
}
|
||||
}
|
||||
this.endpoints.clear();
|
||||
|
||||
@@ -58,7 +58,7 @@ public class ServerChannelInitializer extends ChannelInitializer<Channel> {
|
||||
protected void initChannel(final Channel ch) {
|
||||
ch.pipeline()
|
||||
.addLast(LEGACY_PING_DECODER, new LegacyPingDecoder())
|
||||
.addLast(FRAME_DECODER, new MinecraftVarintFrameDecoder())
|
||||
.addLast(FRAME_DECODER, new MinecraftVarintFrameDecoder(ProtocolUtils.Direction.SERVERBOUND))
|
||||
.addLast(READ_TIMEOUT,
|
||||
new ReadTimeoutHandler(this.server.getConfiguration().getReadTimeout(),
|
||||
TimeUnit.MILLISECONDS))
|
||||
|
||||
@@ -20,27 +20,32 @@ package com.velocitypowered.proxy.network;
|
||||
import com.velocitypowered.proxy.util.concurrent.VelocityNettyThreadFactory;
|
||||
import io.netty.channel.ChannelFactory;
|
||||
import io.netty.channel.EventLoopGroup;
|
||||
import io.netty.channel.IoHandlerFactory;
|
||||
import io.netty.channel.MultiThreadIoEventLoopGroup;
|
||||
import io.netty.channel.epoll.Epoll;
|
||||
import io.netty.channel.epoll.EpollDatagramChannel;
|
||||
import io.netty.channel.epoll.EpollEventLoopGroup;
|
||||
import io.netty.channel.epoll.EpollIoHandler;
|
||||
import io.netty.channel.epoll.EpollServerSocketChannel;
|
||||
import io.netty.channel.epoll.EpollSocketChannel;
|
||||
import io.netty.channel.kqueue.KQueue;
|
||||
import io.netty.channel.kqueue.KQueueDatagramChannel;
|
||||
import io.netty.channel.kqueue.KQueueEventLoopGroup;
|
||||
import io.netty.channel.kqueue.KQueueIoHandler;
|
||||
import io.netty.channel.kqueue.KQueueServerSocketChannel;
|
||||
import io.netty.channel.kqueue.KQueueSocketChannel;
|
||||
import io.netty.channel.nio.NioEventLoopGroup;
|
||||
import io.netty.channel.nio.NioIoHandler;
|
||||
import io.netty.channel.socket.DatagramChannel;
|
||||
import io.netty.channel.socket.ServerSocketChannel;
|
||||
import io.netty.channel.socket.SocketChannel;
|
||||
import io.netty.channel.socket.nio.NioDatagramChannel;
|
||||
import io.netty.channel.socket.nio.NioServerSocketChannel;
|
||||
import io.netty.channel.socket.nio.NioSocketChannel;
|
||||
import io.netty.incubator.channel.uring.*;
|
||||
|
||||
import io.netty.channel.uring.IoUring;
|
||||
import io.netty.channel.uring.IoUringDatagramChannel;
|
||||
import io.netty.channel.uring.IoUringIoHandler;
|
||||
import io.netty.channel.uring.IoUringServerSocketChannel;
|
||||
import io.netty.channel.uring.IoUringSocketChannel;
|
||||
import java.util.concurrent.ThreadFactory;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
* Enumerates the supported transports for Velocity.
|
||||
@@ -49,50 +54,36 @@ public enum TransportType {
|
||||
NIO("NIO", NioServerSocketChannel::new,
|
||||
NioSocketChannel::new,
|
||||
NioDatagramChannel::new,
|
||||
(name, type) -> new NioEventLoopGroup(0, createThreadFactory(name, type)),
|
||||
false,
|
||||
false),
|
||||
NioIoHandler::newFactory),
|
||||
EPOLL("epoll", EpollServerSocketChannel::new,
|
||||
EpollSocketChannel::new,
|
||||
EpollDatagramChannel::new,
|
||||
(name, type) -> new EpollEventLoopGroup(0, createThreadFactory(name, type)),
|
||||
Epoll.isTcpFastOpenServerSideAvailable(),
|
||||
Epoll.isTcpFastOpenClientSideAvailable()),
|
||||
IO_URING("io_uring", IOUringServerSocketChannel::new,
|
||||
IOUringSocketChannel::new,
|
||||
IOUringDatagramChannel::new,
|
||||
(name, type) -> new IOUringEventLoopGroup(0, createThreadFactory(name, type)),
|
||||
IOUring.isTcpFastOpenServerSideAvailable(),
|
||||
IOUring.isTcpFastOpenClientSideAvailable()),
|
||||
EpollIoHandler::newFactory),
|
||||
KQUEUE("kqueue", KQueueServerSocketChannel::new,
|
||||
KQueueSocketChannel::new,
|
||||
KQueueDatagramChannel::new,
|
||||
(name, type) -> new KQueueEventLoopGroup(0, createThreadFactory(name, type)),
|
||||
KQueue.isTcpFastOpenServerSideAvailable(),
|
||||
KQueue.isTcpFastOpenClientSideAvailable());
|
||||
KQueueIoHandler::newFactory),
|
||||
IO_URING("io_uring", IoUringServerSocketChannel::new,
|
||||
IoUringSocketChannel::new,
|
||||
IoUringDatagramChannel::new,
|
||||
IoUringIoHandler::newFactory);
|
||||
|
||||
final String name;
|
||||
final ChannelFactory<? extends ServerSocketChannel> serverSocketChannelFactory;
|
||||
final ChannelFactory<? extends SocketChannel> socketChannelFactory;
|
||||
final ChannelFactory<? extends DatagramChannel> datagramChannelFactory;
|
||||
final BiFunction<String, Type, EventLoopGroup> eventLoopGroupFactory;
|
||||
final boolean supportsTcpFastOpenServer;
|
||||
final boolean supportsTcpFastOpenClient;
|
||||
final Supplier<IoHandlerFactory> ioHandlerFactorySupplier;
|
||||
|
||||
TransportType(final String name,
|
||||
final ChannelFactory<? extends ServerSocketChannel> serverSocketChannelFactory,
|
||||
final ChannelFactory<? extends SocketChannel> socketChannelFactory,
|
||||
final ChannelFactory<? extends DatagramChannel> datagramChannelFactory,
|
||||
final BiFunction<String, Type, EventLoopGroup> eventLoopGroupFactory,
|
||||
final boolean supportsTcpFastOpenServer,
|
||||
final boolean supportsTcpFastOpenClient) {
|
||||
final Supplier<IoHandlerFactory> ioHandlerFactorySupplier) {
|
||||
this.name = name;
|
||||
this.serverSocketChannelFactory = serverSocketChannelFactory;
|
||||
this.socketChannelFactory = socketChannelFactory;
|
||||
this.datagramChannelFactory = datagramChannelFactory;
|
||||
this.eventLoopGroupFactory = eventLoopGroupFactory;
|
||||
this.supportsTcpFastOpenServer = supportsTcpFastOpenServer;
|
||||
this.supportsTcpFastOpenClient = supportsTcpFastOpenClient;
|
||||
this.ioHandlerFactorySupplier = ioHandlerFactorySupplier;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -100,16 +91,15 @@ public enum TransportType {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new event loop group for the given type.
|
||||
*
|
||||
* @param type the type of event loop group to create
|
||||
* @return the event loop group
|
||||
*/
|
||||
public EventLoopGroup createEventLoopGroup(final Type type) {
|
||||
return this.eventLoopGroupFactory.apply(this.name, type);
|
||||
}
|
||||
|
||||
public boolean supportsTcpFastOpenServer() {
|
||||
return supportsTcpFastOpenServer;
|
||||
}
|
||||
|
||||
public boolean supportsTcpFastOpenClient() {
|
||||
return supportsTcpFastOpenClient;
|
||||
return new MultiThreadIoEventLoopGroup(
|
||||
0, createThreadFactory(this.name, type), this.ioHandlerFactorySupplier.get());
|
||||
}
|
||||
|
||||
private static ThreadFactory createThreadFactory(final String name, final Type type) {
|
||||
@@ -126,7 +116,7 @@ public enum TransportType {
|
||||
return NIO;
|
||||
}
|
||||
|
||||
if(IOUring.isAvailable()) {
|
||||
if (IoUring.isAvailable() && Boolean.getBoolean("velocity.enable-iouring-transport")) {
|
||||
return IO_URING;
|
||||
}
|
||||
|
||||
|
||||
@@ -32,13 +32,18 @@ public interface MinecraftPacket {
|
||||
|
||||
boolean handle(MinecraftSessionHandler handler);
|
||||
|
||||
default int expectedMaxLength(ByteBuf buf, ProtocolUtils.Direction direction,
|
||||
default int decodeExpectedMaxLength(ByteBuf buf, ProtocolUtils.Direction direction,
|
||||
ProtocolVersion version) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
default int expectedMinLength(ByteBuf buf, ProtocolUtils.Direction direction,
|
||||
default int decodeExpectedMinLength(ByteBuf buf, ProtocolUtils.Direction direction,
|
||||
ProtocolVersion version) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
default int encodeSizeHint(ProtocolUtils.Direction direction,
|
||||
ProtocolVersion version) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,10 +44,11 @@ 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;
|
||||
import net.kyori.option.OptionState;
|
||||
import net.kyori.option.OptionSchema;
|
||||
|
||||
/**
|
||||
* Utilities for writing and reading data in the Minecraft protocol.
|
||||
@@ -60,14 +61,19 @@ public enum ProtocolUtils {
|
||||
.downsampleColors()
|
||||
.legacyHoverEventSerializer(NBTLegacyHoverEventSerializer.get())
|
||||
.options(
|
||||
OptionState.optionState()
|
||||
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.LEGACY_ONLY)
|
||||
.value(JSONOptions.EMIT_HOVER_EVENT_TYPE, JSONOptions.HoverEventValueMode.VALUE_FIELD)
|
||||
.value(JSONOptions.EMIT_CLICK_EVENT_TYPE, JSONOptions.ClickEventValueMode.CAMEL_CASE)
|
||||
// before 1.20.3
|
||||
.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();
|
||||
@@ -75,14 +81,41 @@ public enum ProtocolUtils {
|
||||
GsonComponentSerializer.builder()
|
||||
.legacyHoverEventSerializer(NBTLegacyHoverEventSerializer.get())
|
||||
.options(
|
||||
OptionState.optionState()
|
||||
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.MODERN_ONLY)
|
||||
.value(JSONOptions.EMIT_HOVER_EVENT_TYPE, JSONOptions.HoverEventValueMode.CAMEL_CASE)
|
||||
.value(JSONOptions.EMIT_CLICK_EVENT_TYPE, JSONOptions.ClickEventValueMode.CAMEL_CASE)
|
||||
.value(JSONOptions.EMIT_HOVER_SHOW_ENTITY_KEY_AS_TYPE_AND_UUID_AS_ID, true)
|
||||
// before 1.20.3
|
||||
.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();
|
||||
private static final GsonComponentSerializer PRE_1_21_5_SERIALIZER =
|
||||
GsonComponentSerializer.builder()
|
||||
.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)
|
||||
.value(JSONOptions.EMIT_CLICK_EVENT_TYPE, JSONOptions.ClickEventValueMode.CAMEL_CASE)
|
||||
.value(JSONOptions.EMIT_HOVER_SHOW_ENTITY_KEY_AS_TYPE_AND_UUID_AS_ID, true)
|
||||
// after 1.20.3
|
||||
.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();
|
||||
@@ -90,14 +123,20 @@ public enum ProtocolUtils {
|
||||
GsonComponentSerializer.builder()
|
||||
.legacyHoverEventSerializer(NBTLegacyHoverEventSerializer.get())
|
||||
.options(
|
||||
OptionState.optionState()
|
||||
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.MODERN_ONLY)
|
||||
.value(JSONOptions.EMIT_HOVER_EVENT_TYPE, JSONOptions.HoverEventValueMode.SNAKE_CASE)
|
||||
.value(JSONOptions.EMIT_CLICK_EVENT_TYPE, JSONOptions.ClickEventValueMode.SNAKE_CASE)
|
||||
// after 1.20.3
|
||||
.value(JSONOptions.EMIT_COMPACT_TEXT_COMPONENT, Boolean.TRUE)
|
||||
.value(JSONOptions.EMIT_HOVER_SHOW_ENTITY_ID_AS_INT_ARRAY, Boolean.TRUE)
|
||||
// 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();
|
||||
@@ -111,7 +150,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[65];
|
||||
private static final int[] VAR_INT_LENGTHS = new int[33];
|
||||
|
||||
static {
|
||||
for (int i = 0; i <= 32; ++i) {
|
||||
@@ -211,16 +250,15 @@ public enum ProtocolUtils {
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes the specified {@code value} as a 21-bit Minecraft VarInt to the specified {@code buf}.
|
||||
* Directly encodes a 21-bit Minecraft VarInt, ready to be written with {@link ByteBuf#writeMedium(int)}.
|
||||
* The upper 11 bits will be discarded.
|
||||
*
|
||||
* @param buf the buffer to read from
|
||||
* @param value the integer to write
|
||||
* @param value the value to encode
|
||||
* @return the encoded value
|
||||
*/
|
||||
public static void write21BitVarInt(ByteBuf buf, int value) {
|
||||
public static int encode21BitVarInt(int value) {
|
||||
// See https://steinborn.me/posts/performance/how-fast-can-you-write-a-varint/
|
||||
int w = (value & 0x7F | 0x80) << 16 | ((value >>> 7) & 0x7F | 0x80) << 8 | (value >>> 14);
|
||||
buf.writeMedium(w);
|
||||
return (value & 0x7F | 0x80) << 16 | ((value >>> 7) & 0x7F | 0x80) << 8 | (value >>> 14);
|
||||
}
|
||||
|
||||
public static String readString(ByteBuf buf) {
|
||||
@@ -249,12 +287,22 @@ 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.toString(buf.readerIndex(), length, StandardCharsets.UTF_8);
|
||||
buf.skipBytes(length);
|
||||
String str = buf.readString(length, StandardCharsets.UTF_8);
|
||||
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.
|
||||
*
|
||||
@@ -287,6 +335,16 @@ 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.
|
||||
*
|
||||
@@ -713,9 +771,12 @@ public enum ProtocolUtils {
|
||||
* @return the appropriate {@link GsonComponentSerializer}
|
||||
*/
|
||||
public static GsonComponentSerializer getJsonChatSerializer(ProtocolVersion version) {
|
||||
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_20_3)) {
|
||||
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_21_5)) {
|
||||
return MODERN_SERIALIZER;
|
||||
}
|
||||
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_20_3)) {
|
||||
return PRE_1_21_5_SERIALIZER;
|
||||
}
|
||||
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_16)) {
|
||||
return PRE_1_20_3_SERIALIZER;
|
||||
}
|
||||
@@ -749,6 +810,40 @@ 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());
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents the direction in which a packet flows.
|
||||
*/
|
||||
|
||||
@@ -39,6 +39,9 @@ import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_20_5;
|
||||
import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_21;
|
||||
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;
|
||||
@@ -56,7 +59,11 @@ 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;
|
||||
@@ -79,6 +86,7 @@ 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;
|
||||
@@ -100,6 +108,8 @@ 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;
|
||||
@@ -182,6 +192,12 @@ 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,
|
||||
@@ -236,6 +252,12 @@ 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 {
|
||||
@@ -256,7 +278,8 @@ public enum StateRegistry {
|
||||
map(0x09, MINECRAFT_1_19_4, false),
|
||||
map(0x0A, MINECRAFT_1_20_2, false),
|
||||
map(0x0B, MINECRAFT_1_20_5, false),
|
||||
map(0x0D, MINECRAFT_1_21_2, false));
|
||||
map(0x0D, MINECRAFT_1_21_2, false),
|
||||
map(0x0E, MINECRAFT_1_21_6, false));
|
||||
serverbound.register(
|
||||
LegacyChatPacket.class,
|
||||
LegacyChatPacket::new,
|
||||
@@ -269,7 +292,8 @@ public enum StateRegistry {
|
||||
ChatAcknowledgementPacket.class,
|
||||
ChatAcknowledgementPacket::new,
|
||||
map(0x03, MINECRAFT_1_19_3, false),
|
||||
map(0x04, MINECRAFT_1_21_2, false));
|
||||
map(0x04, MINECRAFT_1_21_2, 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));
|
||||
@@ -279,16 +303,19 @@ public enum StateRegistry {
|
||||
serverbound.register(SessionPlayerCommandPacket.class, SessionPlayerCommandPacket::new,
|
||||
map(0x04, MINECRAFT_1_19_3, false),
|
||||
map(0x05, MINECRAFT_1_20_5, false),
|
||||
map(0x06, MINECRAFT_1_21_2, false));
|
||||
map(0x06, MINECRAFT_1_21_2, 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(0x05, MINECRAFT_1_21_2, 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(0x07, MINECRAFT_1_21_2, false),
|
||||
map(0x08, MINECRAFT_1_21_6, false));
|
||||
serverbound.register(
|
||||
ClientSettingsPacket.class,
|
||||
ClientSettingsPacket::new,
|
||||
@@ -303,11 +330,13 @@ public enum StateRegistry {
|
||||
map(0x08, MINECRAFT_1_19_4, false),
|
||||
map(0x09, MINECRAFT_1_20_2, false),
|
||||
map(0x0A, MINECRAFT_1_20_5, false),
|
||||
map(0x0C, MINECRAFT_1_21_2, false));
|
||||
map(0x0C, MINECRAFT_1_21_2, 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(0x13, MINECRAFT_1_21_2, false),
|
||||
map(0x14, MINECRAFT_1_21_6, false));
|
||||
serverbound.register(
|
||||
PluginMessagePacket.class,
|
||||
PluginMessagePacket::new,
|
||||
@@ -325,7 +354,8 @@ public enum StateRegistry {
|
||||
map(0x0F, MINECRAFT_1_20_2, false),
|
||||
map(0x10, MINECRAFT_1_20_3, false),
|
||||
map(0x12, MINECRAFT_1_20_5, false),
|
||||
map(0x14, MINECRAFT_1_21_2, false));
|
||||
map(0x14, MINECRAFT_1_21_2, false),
|
||||
map(0x15, MINECRAFT_1_21_6, false));
|
||||
serverbound.register(
|
||||
KeepAlivePacket.class,
|
||||
KeepAlivePacket::new,
|
||||
@@ -344,7 +374,8 @@ public enum StateRegistry {
|
||||
map(0x14, MINECRAFT_1_20_2, false),
|
||||
map(0x15, MINECRAFT_1_20_3, false),
|
||||
map(0x18, MINECRAFT_1_20_5, false),
|
||||
map(0x1A, MINECRAFT_1_21_2, false));
|
||||
map(0x1A, MINECRAFT_1_21_2, false),
|
||||
map(0x1B, MINECRAFT_1_21_6, false));
|
||||
serverbound.register(
|
||||
ResourcePackResponsePacket.class,
|
||||
ResourcePackResponsePacket::new,
|
||||
@@ -361,12 +392,14 @@ public enum StateRegistry {
|
||||
map(0x28, MINECRAFT_1_20_3, false),
|
||||
map(0x2B, MINECRAFT_1_20_5, false),
|
||||
map(0x2D, MINECRAFT_1_21_2, false),
|
||||
map(0x2F, MINECRAFT_1_21_4, false));
|
||||
map(0x2F, MINECRAFT_1_21_4, 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(0x0E, MINECRAFT_1_21_2, false),
|
||||
map(0x0F, MINECRAFT_1_21_6, false));
|
||||
|
||||
clientbound.register(
|
||||
BossBarPacket.class,
|
||||
@@ -377,7 +410,8 @@ public enum StateRegistry {
|
||||
map(0x0D, MINECRAFT_1_17, false),
|
||||
map(0x0A, MINECRAFT_1_19, false),
|
||||
map(0x0B, MINECRAFT_1_19_4, false),
|
||||
map(0x0A, MINECRAFT_1_20_2, false));
|
||||
map(0x0A, MINECRAFT_1_20_2, false),
|
||||
map(0x09, MINECRAFT_1_21_5, false));
|
||||
clientbound.register(
|
||||
LegacyChatPacket.class,
|
||||
LegacyChatPacket::new,
|
||||
@@ -398,7 +432,8 @@ public enum StateRegistry {
|
||||
map(0x0E, MINECRAFT_1_19, false),
|
||||
map(0x0D, MINECRAFT_1_19_3, false),
|
||||
map(0x0F, MINECRAFT_1_19_4, false),
|
||||
map(0x10, MINECRAFT_1_20_2, false));
|
||||
map(0x10, MINECRAFT_1_20_2, false),
|
||||
map(0x0F, MINECRAFT_1_21_5, false));
|
||||
clientbound.register(
|
||||
AvailableCommandsPacket.class,
|
||||
AvailableCommandsPacket::new,
|
||||
@@ -410,10 +445,32 @@ public enum StateRegistry {
|
||||
map(0x0F, MINECRAFT_1_19, false),
|
||||
map(0x0E, MINECRAFT_1_19_3, false),
|
||||
map(0x10, MINECRAFT_1_19_4, false),
|
||||
map(0x11, MINECRAFT_1_20_2, false));
|
||||
map(0x11, MINECRAFT_1_20_2, false),
|
||||
map(0x10, MINECRAFT_1_21_5, false));
|
||||
clientbound.register(
|
||||
ClientboundCookieRequestPacket.class, ClientboundCookieRequestPacket::new,
|
||||
map(0x16, MINECRAFT_1_20_5, false));
|
||||
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));
|
||||
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));
|
||||
clientbound.register(
|
||||
PluginMessagePacket.class,
|
||||
PluginMessagePacket::new,
|
||||
@@ -430,7 +487,8 @@ public enum StateRegistry {
|
||||
map(0x15, MINECRAFT_1_19_3, false),
|
||||
map(0x17, MINECRAFT_1_19_4, false),
|
||||
map(0x18, MINECRAFT_1_20_2, false),
|
||||
map(0x19, MINECRAFT_1_20_5, false));
|
||||
map(0x19, MINECRAFT_1_20_5, false),
|
||||
map(0x18, MINECRAFT_1_21_5, false));
|
||||
clientbound.register(
|
||||
DisconnectPacket.class,
|
||||
() -> new DisconnectPacket(this),
|
||||
@@ -447,7 +505,9 @@ public enum StateRegistry {
|
||||
map(0x17, MINECRAFT_1_19_3, false),
|
||||
map(0x1A, MINECRAFT_1_19_4, false),
|
||||
map(0x1B, MINECRAFT_1_20_2, false),
|
||||
map(0x1D, MINECRAFT_1_20_5, false));
|
||||
map(0x1D, MINECRAFT_1_20_5, false),
|
||||
map(0x1C, MINECRAFT_1_21_5, false),
|
||||
map(0x20, MINECRAFT_1_21_9, false));
|
||||
clientbound.register(
|
||||
KeepAlivePacket.class,
|
||||
KeepAlivePacket::new,
|
||||
@@ -465,7 +525,9 @@ public enum StateRegistry {
|
||||
map(0x23, MINECRAFT_1_19_4, false),
|
||||
map(0x24, MINECRAFT_1_20_2, false),
|
||||
map(0x26, MINECRAFT_1_20_5, false),
|
||||
map(0x27, MINECRAFT_1_21_2, false));
|
||||
map(0x27, MINECRAFT_1_21_2, false),
|
||||
map(0x26, MINECRAFT_1_21_5, false),
|
||||
map(0x2B, MINECRAFT_1_21_9, false));
|
||||
clientbound.register(
|
||||
JoinGamePacket.class,
|
||||
JoinGamePacket::new,
|
||||
@@ -483,7 +545,9 @@ public enum StateRegistry {
|
||||
map(0x28, MINECRAFT_1_19_4, false),
|
||||
map(0x29, MINECRAFT_1_20_2, false),
|
||||
map(0x2B, MINECRAFT_1_20_5, false),
|
||||
map(0x2C, MINECRAFT_1_21_2, false));
|
||||
map(0x2C, MINECRAFT_1_21_2, false),
|
||||
map(0x2B, MINECRAFT_1_21_5, false),
|
||||
map(0x30, MINECRAFT_1_21_9, false));
|
||||
clientbound.register(
|
||||
RespawnPacket.class,
|
||||
RespawnPacket::new,
|
||||
@@ -504,13 +568,17 @@ public enum StateRegistry {
|
||||
map(0x43, MINECRAFT_1_20_2, true),
|
||||
map(0x45, MINECRAFT_1_20_3, true),
|
||||
map(0x47, MINECRAFT_1_20_5, true),
|
||||
map(0x4C, MINECRAFT_1_21_2, true));
|
||||
map(0x4C, MINECRAFT_1_21_2, true),
|
||||
map(0x4B, MINECRAFT_1_21_5, true),
|
||||
map(0x50, MINECRAFT_1_21_9, 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(0x4A, MINECRAFT_1_21_2, false),
|
||||
map(0x49, MINECRAFT_1_21_5, false),
|
||||
map(0x4E, MINECRAFT_1_21_9, false));
|
||||
clientbound.register(
|
||||
ResourcePackRequestPacket.class,
|
||||
ResourcePackRequestPacket::new,
|
||||
@@ -531,7 +599,9 @@ public enum StateRegistry {
|
||||
map(0x42, MINECRAFT_1_20_2, false),
|
||||
map(0x44, MINECRAFT_1_20_3, false),
|
||||
map(0x46, MINECRAFT_1_20_5, false),
|
||||
map(0x4B, MINECRAFT_1_21_2, false));
|
||||
map(0x4B, MINECRAFT_1_21_2, false),
|
||||
map(0x4A, MINECRAFT_1_21_5, false),
|
||||
map(0x4F, MINECRAFT_1_21_9, false));
|
||||
clientbound.register(
|
||||
HeaderAndFooterPacket.class,
|
||||
HeaderAndFooterPacket::new,
|
||||
@@ -553,7 +623,9 @@ public enum StateRegistry {
|
||||
map(0x68, MINECRAFT_1_20_2, true),
|
||||
map(0x6A, MINECRAFT_1_20_3, true),
|
||||
map(0x6D, MINECRAFT_1_20_5, true),
|
||||
map(0x74, MINECRAFT_1_21_2, true));
|
||||
map(0x74, MINECRAFT_1_21_2, true),
|
||||
map(0x73, MINECRAFT_1_21_5, true),
|
||||
map(0x78, MINECRAFT_1_21_9, true));
|
||||
clientbound.register(
|
||||
LegacyTitlePacket.class,
|
||||
LegacyTitlePacket::new,
|
||||
@@ -574,7 +646,9 @@ public enum StateRegistry {
|
||||
map(0x5F, MINECRAFT_1_20_2, true),
|
||||
map(0x61, MINECRAFT_1_20_3, true),
|
||||
map(0x63, MINECRAFT_1_20_5, true),
|
||||
map(0x6A, MINECRAFT_1_21_2, true));
|
||||
map(0x6A, MINECRAFT_1_21_2, true),
|
||||
map(0x69, MINECRAFT_1_21_5, true),
|
||||
map(0x6E, MINECRAFT_1_21_9, true));
|
||||
clientbound.register(
|
||||
TitleTextPacket.class,
|
||||
TitleTextPacket::new,
|
||||
@@ -586,7 +660,9 @@ public enum StateRegistry {
|
||||
map(0x61, MINECRAFT_1_20_2, true),
|
||||
map(0x63, MINECRAFT_1_20_3, true),
|
||||
map(0x65, MINECRAFT_1_20_5, true),
|
||||
map(0x6C, MINECRAFT_1_21_2, true));
|
||||
map(0x6C, MINECRAFT_1_21_2, true),
|
||||
map(0x6B, MINECRAFT_1_21_5, true),
|
||||
map(0x70, MINECRAFT_1_21_9, true));
|
||||
clientbound.register(
|
||||
TitleActionbarPacket.class,
|
||||
TitleActionbarPacket::new,
|
||||
@@ -598,7 +674,9 @@ public enum StateRegistry {
|
||||
map(0x48, MINECRAFT_1_20_2, true),
|
||||
map(0x4A, MINECRAFT_1_20_3, true),
|
||||
map(0x4C, MINECRAFT_1_20_5, true),
|
||||
map(0x51, MINECRAFT_1_21_2, true));
|
||||
map(0x51, MINECRAFT_1_21_2, true),
|
||||
map(0x50, MINECRAFT_1_21_5, true),
|
||||
map(0x55, MINECRAFT_1_21_9, true));
|
||||
clientbound.register(
|
||||
TitleTimesPacket.class,
|
||||
TitleTimesPacket::new,
|
||||
@@ -610,7 +688,9 @@ public enum StateRegistry {
|
||||
map(0x62, MINECRAFT_1_20_2, true),
|
||||
map(0x64, MINECRAFT_1_20_3, true),
|
||||
map(0x66, MINECRAFT_1_20_5, true),
|
||||
map(0x6D, MINECRAFT_1_21_2, true));
|
||||
map(0x6D, MINECRAFT_1_21_2, true),
|
||||
map(0x6C, MINECRAFT_1_21_5, true),
|
||||
map(0x71, MINECRAFT_1_21_9, true));
|
||||
clientbound.register(
|
||||
TitleClearPacket.class,
|
||||
TitleClearPacket::new,
|
||||
@@ -618,7 +698,8 @@ public enum StateRegistry {
|
||||
map(0x0D, MINECRAFT_1_19, true),
|
||||
map(0x0C, MINECRAFT_1_19_3, true),
|
||||
map(0x0E, MINECRAFT_1_19_4, true),
|
||||
map(0x0F, MINECRAFT_1_20_2, true));
|
||||
map(0x0F, MINECRAFT_1_20_2, true),
|
||||
map(0x0E, MINECRAFT_1_21_5, true));
|
||||
clientbound.register(
|
||||
LegacyPlayerListItemPacket.class,
|
||||
LegacyPlayerListItemPacket::new,
|
||||
@@ -638,7 +719,9 @@ public enum StateRegistry {
|
||||
map(0x39, MINECRAFT_1_19_4, false),
|
||||
map(0x3B, MINECRAFT_1_20_2, false),
|
||||
map(0x3D, MINECRAFT_1_20_5, false),
|
||||
map(0x3F, MINECRAFT_1_21_2, false));
|
||||
map(0x3F, MINECRAFT_1_21_2, false),
|
||||
map(0x3E, MINECRAFT_1_21_5, false),
|
||||
map(0x43, MINECRAFT_1_21_9, false));
|
||||
clientbound.register(
|
||||
UpsertPlayerInfoPacket.class,
|
||||
UpsertPlayerInfoPacket::new,
|
||||
@@ -646,11 +729,15 @@ public enum StateRegistry {
|
||||
map(0x3A, MINECRAFT_1_19_4, false),
|
||||
map(0x3C, MINECRAFT_1_20_2, false),
|
||||
map(0x3E, MINECRAFT_1_20_5, false),
|
||||
map(0x40, MINECRAFT_1_21_2, false));
|
||||
map(0x40, MINECRAFT_1_21_2, false),
|
||||
map(0x3F, MINECRAFT_1_21_5, false),
|
||||
map(0x44, MINECRAFT_1_21_9, false));
|
||||
clientbound.register(
|
||||
ClientboundStoreCookiePacket.class, ClientboundStoreCookiePacket::new,
|
||||
map(0x6B, MINECRAFT_1_20_5, false),
|
||||
map(0x72, MINECRAFT_1_21_2, false));
|
||||
map(0x72, MINECRAFT_1_21_2, false),
|
||||
map(0x71, MINECRAFT_1_21_5, false),
|
||||
map(0x76, MINECRAFT_1_21_9, false));
|
||||
clientbound.register(
|
||||
SystemChatPacket.class,
|
||||
SystemChatPacket::new,
|
||||
@@ -661,7 +748,9 @@ public enum StateRegistry {
|
||||
map(0x67, MINECRAFT_1_20_2, true),
|
||||
map(0x69, MINECRAFT_1_20_3, true),
|
||||
map(0x6C, MINECRAFT_1_20_5, true),
|
||||
map(0x73, MINECRAFT_1_21_2, true));
|
||||
map(0x73, MINECRAFT_1_21_2, true),
|
||||
map(0x72, MINECRAFT_1_21_5, true),
|
||||
map(0x77, MINECRAFT_1_21_9, true));
|
||||
clientbound.register(
|
||||
PlayerChatCompletionPacket.class,
|
||||
PlayerChatCompletionPacket::new,
|
||||
@@ -669,7 +758,8 @@ public enum StateRegistry {
|
||||
map(0x14, MINECRAFT_1_19_3, true),
|
||||
map(0x16, MINECRAFT_1_19_4, true),
|
||||
map(0x17, MINECRAFT_1_20_2, true),
|
||||
map(0x18, MINECRAFT_1_20_5, true));
|
||||
map(0x18, MINECRAFT_1_20_5, true),
|
||||
map(0x17, MINECRAFT_1_21_5, true));
|
||||
clientbound.register(
|
||||
ServerDataPacket.class,
|
||||
ServerDataPacket::new,
|
||||
@@ -680,14 +770,18 @@ public enum StateRegistry {
|
||||
map(0x47, MINECRAFT_1_20_2, false),
|
||||
map(0x49, MINECRAFT_1_20_3, false),
|
||||
map(0x4B, MINECRAFT_1_20_5, false),
|
||||
map(0x50, MINECRAFT_1_21_2, false));
|
||||
map(0x50, MINECRAFT_1_21_2, false),
|
||||
map(0x4F, MINECRAFT_1_21_5, false),
|
||||
map(0x54, MINECRAFT_1_21_9, false));
|
||||
clientbound.register(
|
||||
StartUpdatePacket.class,
|
||||
() -> StartUpdatePacket.INSTANCE,
|
||||
map(0x65, MINECRAFT_1_20_2, false),
|
||||
map(0x67, MINECRAFT_1_20_3, false),
|
||||
map(0x69, MINECRAFT_1_20_5, false),
|
||||
map(0x70, MINECRAFT_1_21_2, false));
|
||||
map(0x70, MINECRAFT_1_21_2, false),
|
||||
map(0x6F, MINECRAFT_1_21_5, false),
|
||||
map(0x74, MINECRAFT_1_21_9, false));
|
||||
clientbound.register(
|
||||
BundleDelimiterPacket.class,
|
||||
() -> BundleDelimiterPacket.INSTANCE,
|
||||
@@ -696,17 +790,20 @@ public enum StateRegistry {
|
||||
TransferPacket.class,
|
||||
TransferPacket::new,
|
||||
map(0x73, MINECRAFT_1_20_5, false),
|
||||
map(0x7A, MINECRAFT_1_21_2, false));
|
||||
map(0x7A, MINECRAFT_1_21_2, false),
|
||||
map(0x7F, MINECRAFT_1_21_9, false));
|
||||
clientbound.register(
|
||||
ClientboundCustomReportDetailsPacket.class,
|
||||
ClientboundCustomReportDetailsPacket::new,
|
||||
map(0x7A, MINECRAFT_1_21, false),
|
||||
map(0x81, MINECRAFT_1_21_2, false));
|
||||
map(0x81, MINECRAFT_1_21_2, false),
|
||||
map(0x86, MINECRAFT_1_21_9, false));
|
||||
clientbound.register(
|
||||
ClientboundServerLinksPacket.class,
|
||||
ClientboundServerLinksPacket::new,
|
||||
map(0x7B, MINECRAFT_1_21, false),
|
||||
map(0x82, MINECRAFT_1_21_2, false));
|
||||
map(0x82, MINECRAFT_1_21_2, false),
|
||||
map(0x87, MINECRAFT_1_21_9, false));
|
||||
clientbound.register(UpdateTeamsPacket.class, UpdateTeamsPacket::new,
|
||||
map(0x41, ProtocolVersion.MINECRAFT_1_9, true),
|
||||
map(0x43, ProtocolVersion.MINECRAFT_1_12, true),
|
||||
|
||||
@@ -39,6 +39,7 @@ public class MinecraftCompressDecoder extends MessageToMessageDecoder<ByteBuf> {
|
||||
private static final int UNCOMPRESSED_CAP =
|
||||
Boolean.getBoolean("velocity.increased-compression-cap")
|
||||
? HARD_MAXIMUM_UNCOMPRESSED_SIZE : VANILLA_MAXIMUM_UNCOMPRESSED_SIZE;
|
||||
private static final boolean SKIP_COMPRESSION_VALIDATION = Boolean.getBoolean("velocity.skip-uncompressed-packet-size-validation");
|
||||
|
||||
private int threshold;
|
||||
private final VelocityCompressor compressor;
|
||||
@@ -52,6 +53,11 @@ public class MinecraftCompressDecoder extends MessageToMessageDecoder<ByteBuf> {
|
||||
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
|
||||
int claimedUncompressedSize = ProtocolUtils.readVarInt(in);
|
||||
if (claimedUncompressedSize == 0) {
|
||||
if (!SKIP_COMPRESSION_VALIDATION) {
|
||||
int actualUncompressedSize = in.readableBytes();
|
||||
checkFrame(actualUncompressedSize < threshold, "Actual uncompressed size %s is greater than"
|
||||
+ " threshold %s", actualUncompressedSize, threshold);
|
||||
}
|
||||
// This message is not compressed.
|
||||
out.add(in.retain());
|
||||
return;
|
||||
|
||||
@@ -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);
|
||||
ProtocolUtils.writeVarInt(out, 0);
|
||||
out.writeByte(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();
|
||||
|
||||
ProtocolUtils.write21BitVarInt(out, 0); // Dummy packet length
|
||||
out.writeMedium(0); // Reserve the packet length
|
||||
ProtocolUtils.writeVarInt(out, uncompressed);
|
||||
ByteBuf compatibleIn = MoreByteBufUtils.ensureCompatible(ctx.alloc(), compressor, msg);
|
||||
|
||||
@@ -72,11 +72,8 @@ 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.writerIndex(0);
|
||||
ProtocolUtils.write21BitVarInt(out, packetLength); // Rewrite packet length
|
||||
out.writerIndex(writerIndex);
|
||||
out.setMedium(0, ProtocolUtils.encode21BitVarInt(packetLength)); // Rewrite packet length
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -96,8 +96,8 @@ public class MinecraftDecoder extends ChannelInboundHandlerAdapter {
|
||||
}
|
||||
|
||||
private void doLengthSanityChecks(ByteBuf buf, MinecraftPacket packet) throws Exception {
|
||||
int expectedMinLen = packet.expectedMinLength(buf, direction, registry.version);
|
||||
int expectedMaxLen = packet.expectedMaxLength(buf, direction, registry.version);
|
||||
int expectedMinLen = packet.decodeExpectedMinLength(buf, direction, registry.version);
|
||||
int expectedMaxLen = packet.decodeExpectedMaxLength(buf, direction, registry.version);
|
||||
if (expectedMaxLen != -1 && buf.readableBytes() > expectedMaxLen) {
|
||||
throw handleOverflow(packet, expectedMaxLen, buf.readableBytes());
|
||||
}
|
||||
@@ -135,7 +135,7 @@ public class MinecraftDecoder extends ChannelInboundHandlerAdapter {
|
||||
|
||||
private String getExtraConnectionDetail(int packetId) {
|
||||
return "Direction " + direction + " Protocol " + registry.version + " State " + state
|
||||
+ " ID " + Integer.toHexString(packetId);
|
||||
+ " ID 0x" + Integer.toHexString(packetId);
|
||||
}
|
||||
|
||||
public void setProtocolVersion(ProtocolVersion protocolVersion) {
|
||||
|
||||
@@ -54,6 +54,19 @@ 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);
|
||||
}
|
||||
|
||||
@@ -19,21 +19,51 @@ 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.protocol.MinecraftPacket;
|
||||
import com.velocitypowered.proxy.protocol.ProtocolUtils;
|
||||
import com.velocitypowered.proxy.protocol.StateRegistry;
|
||||
import com.velocitypowered.proxy.util.except.QuietDecoderException;
|
||||
import com.velocitypowered.proxy.util.except.QuietRuntimeException;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.handler.codec.ByteToMessageDecoder;
|
||||
import io.netty.handler.codec.CorruptedFrameException;
|
||||
import java.util.List;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
/**
|
||||
* Frames Minecraft server packets which are prefixed by a 21-bit VarInt encoding.
|
||||
*/
|
||||
public class MinecraftVarintFrameDecoder extends ByteToMessageDecoder {
|
||||
|
||||
private static final Logger LOGGER = LogManager.getLogger(MinecraftVarintFrameDecoder.class);
|
||||
private static final QuietRuntimeException FRAME_DECODER_FAILED =
|
||||
new QuietRuntimeException("A packet frame decoder failed. For more information, launch "
|
||||
+ "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 VARINT_TOO_BIG =
|
||||
new QuietDecoderException("VarInt too big");
|
||||
private static final QuietDecoderException UNKNOWN_PACKET =
|
||||
new QuietDecoderException("Unknown packet");
|
||||
|
||||
private final ProtocolUtils.Direction direction;
|
||||
private final StateRegistry.PacketRegistry.ProtocolRegistry registry;
|
||||
private StateRegistry state;
|
||||
|
||||
/**
|
||||
* Creates a new {@code MinecraftVarintFrameDecoder} decoding packets from the specified {@code Direction}.
|
||||
*
|
||||
* @param direction the direction from which we decode from
|
||||
*/
|
||||
public MinecraftVarintFrameDecoder(ProtocolUtils.Direction direction) {
|
||||
this.direction = direction;
|
||||
this.registry = StateRegistry.HANDSHAKE.getProtocolRegistry(
|
||||
direction, ProtocolVersion.MINIMUM_VERSION);
|
||||
this.state = StateRegistry.HANDSHAKE;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out)
|
||||
@@ -53,15 +83,22 @@ public class MinecraftVarintFrameDecoder extends ByteToMessageDecoder {
|
||||
|
||||
// try to read the length of the packet
|
||||
in.markReaderIndex();
|
||||
int preIndex = in.readerIndex();
|
||||
int length = readRawVarInt21(in);
|
||||
if (preIndex == in.readerIndex()) {
|
||||
if (packetStart == 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)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// note that zero-length packets are ignored
|
||||
if (length > 0) {
|
||||
if (in.readableBytes() < length) {
|
||||
@@ -72,6 +109,51 @@ public class MinecraftVarintFrameDecoder extends ByteToMessageDecoder {
|
||||
}
|
||||
}
|
||||
|
||||
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()) {
|
||||
in.resetReaderIndex();
|
||||
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, in.readableBytes());
|
||||
}
|
||||
if (payloadLength < expectedMinLen) {
|
||||
throw handleUnderflow(packet, expectedMaxLen, in.readableBytes());
|
||||
}
|
||||
|
||||
in.readerIndex(index);
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
|
||||
if (MinecraftDecoder.DEBUG) {
|
||||
LOGGER.atWarn()
|
||||
.withThrowable(cause)
|
||||
.log("Exception caught while decoding frame for {}", ctx.channel().remoteAddress());
|
||||
}
|
||||
super.exceptionCaught(ctx, cause);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads a VarInt from the buffer of up to 21 bits in size.
|
||||
*
|
||||
@@ -141,4 +223,26 @@ public class MinecraftVarintFrameDecoder extends ByteToMessageDecoder {
|
||||
}
|
||||
return result | (tmp & 0x7F) << 14;
|
||||
}
|
||||
|
||||
private Exception handleOverflow(MinecraftPacket packet, int expected, int actual) {
|
||||
if (MinecraftDecoder.DEBUG) {
|
||||
return new CorruptedFrameException("Packet sent for " + packet.getClass() + " was too "
|
||||
+ "big (expected " + expected + " bytes, got " + actual + " bytes)");
|
||||
} else {
|
||||
return FRAME_DECODER_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
private Exception handleUnderflow(MinecraftPacket packet, int expected, int actual) {
|
||||
if (MinecraftDecoder.DEBUG) {
|
||||
return new CorruptedFrameException("Packet sent for " + packet.getClass() + " was too "
|
||||
+ "small (expected " + expected + " bytes, got " + actual + " bytes)");
|
||||
} else {
|
||||
return FRAME_DECODER_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
public void setState(StateRegistry stateRegistry) {
|
||||
this.state = stateRegistry;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,12 +50,14 @@ import java.util.Deque;
|
||||
import java.util.Iterator;
|
||||
import java.util.Queue;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.function.Predicate;
|
||||
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
|
||||
public class AvailableCommandsPacket implements MinecraftPacket {
|
||||
|
||||
private static final Command<CommandSource> PLACEHOLDER_COMMAND = source -> 0;
|
||||
private static final Predicate<CommandSource> PLACEHOLDER_REQUIREMENT = source -> true;
|
||||
|
||||
private static final byte NODE_TYPE_ROOT = 0x00;
|
||||
private static final byte NODE_TYPE_LITERAL = 0x01;
|
||||
@@ -65,6 +67,7 @@ public class AvailableCommandsPacket implements MinecraftPacket {
|
||||
private static final byte FLAG_EXECUTABLE = 0x04;
|
||||
private static final byte FLAG_IS_REDIRECT = 0x08;
|
||||
private static final byte FLAG_HAS_SUGGESTIONS = 0x10;
|
||||
private static final byte FLAG_IS_RESTRICTED = 0x20;
|
||||
|
||||
private @MonotonicNonNull RootCommandNode<CommandSource> rootNode;
|
||||
|
||||
@@ -146,6 +149,9 @@ public class AvailableCommandsPacket implements MinecraftPacket {
|
||||
if (node.getCommand() != null) {
|
||||
flags |= FLAG_EXECUTABLE;
|
||||
}
|
||||
if (node.getRequirement() == PLACEHOLDER_REQUIREMENT) {
|
||||
flags |= FLAG_IS_RESTRICTED;
|
||||
}
|
||||
|
||||
if (node instanceof LiteralCommandNode<?>) {
|
||||
flags |= NODE_TYPE_LITERAL;
|
||||
@@ -289,6 +295,11 @@ public class AvailableCommandsPacket implements MinecraftPacket {
|
||||
args.executes(PLACEHOLDER_COMMAND);
|
||||
}
|
||||
|
||||
// If restricted, add empty requirement
|
||||
if ((flags & FLAG_IS_RESTRICTED) != 0) {
|
||||
args.requires(PLACEHOLDER_REQUIREMENT);
|
||||
}
|
||||
|
||||
this.built = args.build();
|
||||
}
|
||||
}
|
||||
@@ -351,4 +362,12 @@ public class AvailableCommandsPacket implements MinecraftPacket {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* 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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
* 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 expectedMaxLength(ByteBuf buf, Direction direction, ProtocolVersion version) {
|
||||
public int decodeExpectedMaxLength(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 expectedMinLength(ByteBuf buf, Direction direction, ProtocolVersion version) {
|
||||
int base = expectedMaxLength(buf, direction, version);
|
||||
public int decodeExpectedMinLength(ByteBuf buf, Direction direction, ProtocolVersion version) {
|
||||
int base = decodeExpectedMaxLength(buf, direction, version);
|
||||
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_19)) {
|
||||
// These are "optional"
|
||||
base -= 128 + 8;
|
||||
|
||||
@@ -24,6 +24,7 @@ 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 {
|
||||
@@ -106,4 +107,23 @@ public class HandshakePacket implements MinecraftPacket {
|
||||
public boolean handle(MinecraftSessionHandler handler) {
|
||||
return handler.handle(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int decodeExpectedMinLength(ByteBuf buf, ProtocolUtils.Direction direction,
|
||||
ProtocolVersion version) {
|
||||
return 7;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int decodeExpectedMaxLength(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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ public class LoginAcknowledgedPacket implements MinecraftPacket {
|
||||
}
|
||||
|
||||
@Override
|
||||
public int expectedMaxLength(ByteBuf buf, ProtocolUtils.Direction direction,
|
||||
public int decodeExpectedMaxLength(ByteBuf buf, ProtocolUtils.Direction direction,
|
||||
ProtocolVersion version) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ 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;
|
||||
@@ -86,4 +87,9 @@ 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,6 +21,7 @@ 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;
|
||||
@@ -88,4 +89,9 @@ 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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ 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 org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
@@ -143,4 +144,9 @@ public class PluginMessagePacket extends DeferredByteBufHolder implements Minecr
|
||||
public PluginMessagePacket touch(Object hint) {
|
||||
return (PluginMessagePacket) super.touch(hint);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int encodeSizeHint(Direction direction, ProtocolVersion version) {
|
||||
return content().readableBytes();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ import com.velocitypowered.api.util.Favicon;
|
||||
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.packet.chat.ComponentHolder;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
@@ -121,4 +122,9 @@ public class ServerDataPacket implements MinecraftPacket {
|
||||
public void setSecureChatEnforced(boolean secureChatEnforced) {
|
||||
this.secureChatEnforced = secureChatEnforced;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int encodeSizeHint(Direction direction, ProtocolVersion version) {
|
||||
return 8 * 1024;
|
||||
}
|
||||
}
|
||||
@@ -150,7 +150,7 @@ public class ServerLoginPacket implements MinecraftPacket {
|
||||
}
|
||||
|
||||
@Override
|
||||
public int expectedMaxLength(ByteBuf buf, Direction direction, ProtocolVersion version) {
|
||||
public int decodeExpectedMaxLength(ByteBuf buf, Direction direction, ProtocolVersion version) {
|
||||
// Accommodate the rare (but likely malicious) use of UTF-8 usernames, since it is technically
|
||||
// legal on the protocol level.
|
||||
int base = 1 + (16 * 3);
|
||||
|
||||
@@ -23,6 +23,7 @@ import com.velocitypowered.api.util.UuidUtils;
|
||||
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
|
||||
import com.velocitypowered.proxy.protocol.MinecraftPacket;
|
||||
import com.velocitypowered.proxy.protocol.ProtocolUtils;
|
||||
import com.velocitypowered.proxy.protocol.ProtocolUtils.Direction;
|
||||
import com.velocitypowered.proxy.util.VelocityProperties;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import java.util.List;
|
||||
@@ -132,4 +133,11 @@ public class ServerLoginSuccessPacket implements MinecraftPacket {
|
||||
public boolean handle(MinecraftSessionHandler handler) {
|
||||
return handler.handle(this);
|
||||
}
|
||||
|
||||
@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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* 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.util.DeferredByteBufHolder;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
|
||||
public class ServerboundCustomClickActionPacket extends DeferredByteBufHolder implements MinecraftPacket {
|
||||
|
||||
public ServerboundCustomClickActionPacket() {
|
||||
super(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) {
|
||||
replace(buf.readRetainedSlice(buf.readableBytes()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) {
|
||||
buf.writeBytes(content());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handle(MinecraftSessionHandler handler) {
|
||||
return handler.handle(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int encodeSizeHint(Direction direction, ProtocolVersion version) {
|
||||
return content().readableBytes();
|
||||
}
|
||||
}
|
||||
@@ -44,12 +44,12 @@ public class StatusPingPacket implements MinecraftPacket {
|
||||
}
|
||||
|
||||
@Override
|
||||
public int expectedMaxLength(ByteBuf buf, Direction direction, ProtocolVersion version) {
|
||||
public int decodeExpectedMaxLength(ByteBuf buf, Direction direction, ProtocolVersion version) {
|
||||
return 8;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int expectedMinLength(ByteBuf buf, Direction direction, ProtocolVersion version) {
|
||||
public int decodeExpectedMinLength(ByteBuf buf, Direction direction, ProtocolVersion version) {
|
||||
return 8;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,7 +53,7 @@ public class StatusRequestPacket implements MinecraftPacket {
|
||||
}
|
||||
|
||||
@Override
|
||||
public int expectedMaxLength(ByteBuf buf, Direction direction, ProtocolVersion version) {
|
||||
public int decodeExpectedMaxLength(ByteBuf buf, Direction direction, ProtocolVersion version) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ import com.velocitypowered.api.network.ProtocolVersion;
|
||||
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
|
||||
import com.velocitypowered.proxy.protocol.MinecraftPacket;
|
||||
import com.velocitypowered.proxy.protocol.ProtocolUtils;
|
||||
import com.velocitypowered.proxy.protocol.ProtocolUtils.Direction;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
|
||||
@@ -66,4 +67,9 @@ public class StatusResponsePacket implements MinecraftPacket {
|
||||
public boolean handle(MinecraftSessionHandler handler) {
|
||||
return handler.handle(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int encodeSizeHint(Direction direction, ProtocolVersion version) {
|
||||
return ProtocolUtils.stringSizeHint(this.status);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,6 +36,8 @@ import org.jetbrains.annotations.Nullable;
|
||||
|
||||
public class UpsertPlayerInfoPacket implements MinecraftPacket {
|
||||
|
||||
private static final Action[] ALL_ACTIONS = Action.class.getEnumConstants();
|
||||
|
||||
private final EnumSet<Action> actions;
|
||||
private final List<Entry> entries;
|
||||
|
||||
@@ -85,14 +87,13 @@ public class UpsertPlayerInfoPacket implements MinecraftPacket {
|
||||
@Override
|
||||
public void decode(ByteBuf buf, ProtocolUtils.Direction direction,
|
||||
ProtocolVersion protocolVersion) {
|
||||
Action[] actions = Action.class.getEnumConstants();
|
||||
byte[] bytes = new byte[-Math.floorDiv(-actions.length, 8)];
|
||||
byte[] bytes = new byte[-Math.floorDiv(-ALL_ACTIONS.length, 8)];
|
||||
buf.readBytes(bytes);
|
||||
BitSet actionSet = BitSet.valueOf(bytes);
|
||||
|
||||
for (int idx = 0; idx < actions.length; idx++) {
|
||||
for (int idx = 0; idx < ALL_ACTIONS.length; idx++) {
|
||||
if (actionSet.get(idx)) {
|
||||
addAction(actions[idx]);
|
||||
addAction(ALL_ACTIONS[idx]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -109,14 +110,13 @@ public class UpsertPlayerInfoPacket implements MinecraftPacket {
|
||||
@Override
|
||||
public void encode(ByteBuf buf, ProtocolUtils.Direction direction,
|
||||
ProtocolVersion protocolVersion) {
|
||||
Action[] actions = Action.class.getEnumConstants();
|
||||
BitSet set = new BitSet(actions.length);
|
||||
for (int idx = 0; idx < actions.length; idx++) {
|
||||
set.set(idx, this.actions.contains(actions[idx]));
|
||||
BitSet set = new BitSet(ALL_ACTIONS.length);
|
||||
for (int idx = 0; idx < ALL_ACTIONS.length; idx++) {
|
||||
set.set(idx, this.actions.contains(ALL_ACTIONS[idx]));
|
||||
}
|
||||
|
||||
byte[] bytes = set.toByteArray();
|
||||
buf.writeBytes(Arrays.copyOf(bytes, -Math.floorDiv(-actions.length, 8)));
|
||||
buf.writeBytes(Arrays.copyOf(bytes, -Math.floorDiv(-ALL_ACTIONS.length, 8)));
|
||||
|
||||
ProtocolUtils.writeVarInt(buf, this.entries.size());
|
||||
for (Entry entry : this.entries) {
|
||||
@@ -133,12 +133,6 @@ public class UpsertPlayerInfoPacket implements MinecraftPacket {
|
||||
return handler.handle(this);
|
||||
}
|
||||
|
||||
public BitSet readFixedBitSet(ByteBuf buf, int param0) {
|
||||
byte[] var0 = new byte[-Math.floorDiv(-param0, 8)];
|
||||
buf.readBytes(var0);
|
||||
return BitSet.valueOf(var0);
|
||||
}
|
||||
|
||||
public enum Action {
|
||||
ADD_PLAYER((ignored, buf, info) -> { // read
|
||||
info.profile = new GameProfile(
|
||||
|
||||
@@ -58,6 +58,13 @@ public class ArgumentIdentifier {
|
||||
this.versionById = ImmutableMap.copyOf(temp);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ArgumentIdentifier{" +
|
||||
"identifier='" + identifier + '\'' +
|
||||
'}';
|
||||
}
|
||||
|
||||
public String getIdentifier() {
|
||||
return identifier;
|
||||
}
|
||||
|
||||
@@ -22,6 +22,8 @@ import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_19_3;
|
||||
import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_19_4;
|
||||
import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_20_3;
|
||||
import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_20_5;
|
||||
import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_21_5;
|
||||
import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_21_6;
|
||||
import static com.velocitypowered.proxy.protocol.packet.brigadier.ArgumentIdentifier.id;
|
||||
import static com.velocitypowered.proxy.protocol.packet.brigadier.ArgumentIdentifier.mapSet;
|
||||
import static com.velocitypowered.proxy.protocol.packet.brigadier.DoubleArgumentPropertySerializer.DOUBLE;
|
||||
@@ -43,6 +45,8 @@ import com.mojang.brigadier.arguments.StringArgumentType;
|
||||
import com.velocitypowered.api.network.ProtocolVersion;
|
||||
import com.velocitypowered.proxy.protocol.ProtocolUtils;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
@@ -85,9 +89,6 @@ public class ArgumentPropertyRegistry {
|
||||
ArgumentIdentifier identifier = readIdentifier(buf, protocolVersion);
|
||||
|
||||
ArgumentPropertySerializer<?> serializer = byIdentifier.get(identifier);
|
||||
if (serializer == null) {
|
||||
throw new IllegalArgumentException("Argument type identifier " + identifier + " unknown.");
|
||||
}
|
||||
Object result = serializer.deserialize(buf, protocolVersion);
|
||||
|
||||
if (result instanceof ArgumentType) {
|
||||
@@ -154,7 +155,7 @@ public class ArgumentPropertyRegistry {
|
||||
* @param protocolVersion the protocol version to use
|
||||
* @return the identifier read from the buffer
|
||||
*/
|
||||
public static ArgumentIdentifier readIdentifier(ByteBuf buf, ProtocolVersion protocolVersion) {
|
||||
public static @NotNull ArgumentIdentifier readIdentifier(ByteBuf buf, ProtocolVersion protocolVersion) {
|
||||
if (protocolVersion.noLessThan(MINECRAFT_1_19)) {
|
||||
int id = ProtocolUtils.readVarInt(buf);
|
||||
for (ArgumentIdentifier i : byIdentifier.keySet()) {
|
||||
@@ -163,6 +164,7 @@ public class ArgumentPropertyRegistry {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
throw new IllegalArgumentException("Argument type identifier " + id + " unknown.");
|
||||
} else {
|
||||
String identifier = ProtocolUtils.readString(buf);
|
||||
for (ArgumentIdentifier i : byIdentifier.keySet()) {
|
||||
@@ -170,8 +172,8 @@ public class ArgumentPropertyRegistry {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
throw new IllegalArgumentException("Argument type identifier " + identifier + " unknown.");
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static {
|
||||
@@ -206,65 +208,79 @@ public class ArgumentPropertyRegistry {
|
||||
empty(id("minecraft:item_stack", mapSet(MINECRAFT_1_19, 14)));
|
||||
empty(id("minecraft:item_predicate", mapSet(MINECRAFT_1_19, 15)));
|
||||
empty(id("minecraft:color", mapSet(MINECRAFT_1_19, 16)));
|
||||
empty(id("minecraft:component", mapSet(MINECRAFT_1_19, 17)));
|
||||
empty(id("minecraft:style", mapSet(MINECRAFT_1_20_3, 18))); // added 1.20.3
|
||||
empty(id("minecraft:message", mapSet(MINECRAFT_1_20_3, 19), mapSet(MINECRAFT_1_19, 18)));
|
||||
empty(id("minecraft:nbt_compound_tag", mapSet(MINECRAFT_1_20_3, 20), mapSet(MINECRAFT_1_19, 19))); // added in 1.14
|
||||
empty(id("minecraft:nbt_tag", mapSet(MINECRAFT_1_20_3, 21), mapSet(MINECRAFT_1_19, 20))); // added in 1.14
|
||||
empty(id("minecraft:nbt_path", mapSet(MINECRAFT_1_20_3, 22), mapSet(MINECRAFT_1_19, 21)));
|
||||
empty(id("minecraft:objective", mapSet(MINECRAFT_1_20_3, 23), mapSet(MINECRAFT_1_19, 22)));
|
||||
empty(id("minecraft:objective_criteria", mapSet(MINECRAFT_1_20_3, 24), mapSet(MINECRAFT_1_19, 23)));
|
||||
empty(id("minecraft:operation", mapSet(MINECRAFT_1_20_3, 25), mapSet(MINECRAFT_1_19, 24)));
|
||||
empty(id("minecraft:particle", mapSet(MINECRAFT_1_20_3, 26), mapSet(MINECRAFT_1_19, 25)));
|
||||
empty(id("minecraft:angle", mapSet(MINECRAFT_1_20_3, 27), mapSet(MINECRAFT_1_19, 26))); // added in 1.16.2
|
||||
empty(id("minecraft:rotation", mapSet(MINECRAFT_1_20_3, 28), mapSet(MINECRAFT_1_19, 27)));
|
||||
empty(id("minecraft:scoreboard_slot", mapSet(MINECRAFT_1_20_3, 29), mapSet(MINECRAFT_1_19, 28)));
|
||||
empty(id("minecraft:score_holder", mapSet(MINECRAFT_1_20_3, 30), mapSet(MINECRAFT_1_19, 29)), ByteArgumentPropertySerializer.BYTE);
|
||||
empty(id("minecraft:swizzle", mapSet(MINECRAFT_1_20_3, 31), mapSet(MINECRAFT_1_19, 30)));
|
||||
empty(id("minecraft:team", mapSet(MINECRAFT_1_20_3, 32), mapSet(MINECRAFT_1_19, 31)));
|
||||
empty(id("minecraft:item_slot", mapSet(MINECRAFT_1_20_3, 33), mapSet(MINECRAFT_1_19, 32)));
|
||||
empty(id("minecraft:item_slots", mapSet(MINECRAFT_1_20_5, 34))); // added 1.20.5
|
||||
empty(id("minecraft:resource_location", mapSet(MINECRAFT_1_20_5, 35), mapSet(MINECRAFT_1_20_3, 34), mapSet(MINECRAFT_1_19, 33)));
|
||||
empty(id("minecraft:component", mapSet(MINECRAFT_1_21_6, 18), mapSet(MINECRAFT_1_19, 17)));
|
||||
empty(id("minecraft:style", mapSet(MINECRAFT_1_21_6, 19), mapSet(MINECRAFT_1_20_3, 18))); // added 1.20.3
|
||||
empty(id("minecraft:message", mapSet(MINECRAFT_1_21_6, 20), mapSet(MINECRAFT_1_20_3, 19), mapSet(MINECRAFT_1_19, 18)));
|
||||
empty(id("minecraft:nbt_compound_tag", mapSet(MINECRAFT_1_21_6, 21), mapSet(MINECRAFT_1_20_3, 20), mapSet(MINECRAFT_1_19, 19))); // added in 1.14
|
||||
empty(id("minecraft:nbt_tag", mapSet(MINECRAFT_1_21_6, 22), mapSet(MINECRAFT_1_20_3, 21), mapSet(MINECRAFT_1_19, 20))); // added in 1.14
|
||||
empty(id("minecraft:nbt_path", mapSet(MINECRAFT_1_21_6, 23), mapSet(MINECRAFT_1_20_3, 22), mapSet(MINECRAFT_1_19, 21)));
|
||||
empty(id("minecraft:objective", mapSet(MINECRAFT_1_21_6, 24), mapSet(MINECRAFT_1_20_3, 23), mapSet(MINECRAFT_1_19, 22)));
|
||||
empty(id("minecraft:objective_criteria", mapSet(MINECRAFT_1_21_6, 25), mapSet(MINECRAFT_1_20_3, 24), mapSet(MINECRAFT_1_19, 23)));
|
||||
empty(id("minecraft:operation", mapSet(MINECRAFT_1_21_6, 26), mapSet(MINECRAFT_1_20_3, 25), mapSet(MINECRAFT_1_19, 24)));
|
||||
empty(id("minecraft:particle", mapSet(MINECRAFT_1_21_6, 27), mapSet(MINECRAFT_1_20_3, 26), mapSet(MINECRAFT_1_19, 25)));
|
||||
empty(id("minecraft:angle", mapSet(MINECRAFT_1_21_6, 28), mapSet(MINECRAFT_1_20_3, 27), mapSet(MINECRAFT_1_19, 26))); // added in 1.16.2
|
||||
empty(id("minecraft:rotation", mapSet(MINECRAFT_1_21_6, 29), mapSet(MINECRAFT_1_20_3, 28), mapSet(MINECRAFT_1_19, 27)));
|
||||
empty(id("minecraft:scoreboard_slot", mapSet(MINECRAFT_1_21_6, 30), mapSet(MINECRAFT_1_20_3, 29), mapSet(MINECRAFT_1_19, 28)));
|
||||
empty(id("minecraft:score_holder", mapSet(MINECRAFT_1_21_6, 31), mapSet(MINECRAFT_1_20_3, 30), mapSet(MINECRAFT_1_19, 29)),
|
||||
ByteArgumentPropertySerializer.BYTE);
|
||||
empty(id("minecraft:swizzle", mapSet(MINECRAFT_1_21_6, 32), mapSet(MINECRAFT_1_20_3, 31), mapSet(MINECRAFT_1_19, 30)));
|
||||
empty(id("minecraft:team", mapSet(MINECRAFT_1_21_6, 33), mapSet(MINECRAFT_1_20_3, 32), mapSet(MINECRAFT_1_19, 31)));
|
||||
empty(id("minecraft:item_slot", mapSet(MINECRAFT_1_21_6, 34), mapSet(MINECRAFT_1_20_3, 33), mapSet(MINECRAFT_1_19, 32)));
|
||||
empty(id("minecraft:item_slots", mapSet(MINECRAFT_1_21_6, 35), mapSet(MINECRAFT_1_20_5, 34))); // added 1.20.5
|
||||
empty(id("minecraft:resource_location", mapSet(MINECRAFT_1_21_6, 36), mapSet(MINECRAFT_1_20_5, 35), mapSet(MINECRAFT_1_20_3, 34),
|
||||
mapSet(MINECRAFT_1_19, 33)));
|
||||
empty(id("minecraft:mob_effect", mapSet(MINECRAFT_1_19_3, -1), mapSet(MINECRAFT_1_19, 34)));
|
||||
empty(id("minecraft:function", mapSet(MINECRAFT_1_20_5, 36), mapSet(MINECRAFT_1_20_3, 35), mapSet(MINECRAFT_1_19_3, 34),
|
||||
mapSet(MINECRAFT_1_19, 35)));
|
||||
empty(id("minecraft:entity_anchor", mapSet(MINECRAFT_1_20_5, 37), mapSet(MINECRAFT_1_20_3, 36), mapSet(MINECRAFT_1_19_3, 35),
|
||||
mapSet(MINECRAFT_1_19, 36)));
|
||||
empty(id("minecraft:int_range", mapSet(MINECRAFT_1_20_5, 38), mapSet(MINECRAFT_1_20_3, 37), mapSet(MINECRAFT_1_19_3, 36),
|
||||
mapSet(MINECRAFT_1_19, 37)));
|
||||
empty(id("minecraft:float_range", mapSet(MINECRAFT_1_20_5, 39), mapSet(MINECRAFT_1_20_3, 38), mapSet(MINECRAFT_1_19_3, 37),
|
||||
mapSet(MINECRAFT_1_19, 38)));
|
||||
empty(id("minecraft:function", mapSet(MINECRAFT_1_21_6, 37), mapSet(MINECRAFT_1_20_5, 36), mapSet(MINECRAFT_1_20_3, 35),
|
||||
mapSet(MINECRAFT_1_19_3, 34), mapSet(MINECRAFT_1_19, 35)));
|
||||
empty(id("minecraft:entity_anchor", mapSet(MINECRAFT_1_21_6, 38), mapSet(MINECRAFT_1_20_5, 37), mapSet(MINECRAFT_1_20_3, 36),
|
||||
mapSet(MINECRAFT_1_19_3, 35), mapSet(MINECRAFT_1_19, 36)));
|
||||
empty(id("minecraft:int_range", mapSet(MINECRAFT_1_21_6, 39), mapSet(MINECRAFT_1_20_5, 38), mapSet(MINECRAFT_1_20_3, 37),
|
||||
mapSet(MINECRAFT_1_19_3, 36), mapSet(MINECRAFT_1_19, 37)));
|
||||
empty(id("minecraft:float_range", mapSet(MINECRAFT_1_21_6, 40), mapSet(MINECRAFT_1_20_5, 39), mapSet(MINECRAFT_1_20_3, 38),
|
||||
mapSet(MINECRAFT_1_19_3, 37), mapSet(MINECRAFT_1_19, 38)));
|
||||
empty(id("minecraft:item_enchantment", mapSet(MINECRAFT_1_19_3, -1), mapSet(MINECRAFT_1_19, 39)));
|
||||
empty(id("minecraft:entity_summon", mapSet(MINECRAFT_1_19_3, -1), mapSet(MINECRAFT_1_19, 40)));
|
||||
empty(id("minecraft:dimension", mapSet(MINECRAFT_1_20_5, 40), mapSet(MINECRAFT_1_20_3, 39), mapSet(MINECRAFT_1_19_3, 38),
|
||||
mapSet(MINECRAFT_1_19, 41)));
|
||||
empty(id("minecraft:gamemode", mapSet(MINECRAFT_1_20_5, 41), mapSet(MINECRAFT_1_20_3, 40), mapSet(MINECRAFT_1_19_3, 39))); // 1.19.3
|
||||
empty(id("minecraft:dimension", mapSet(MINECRAFT_1_21_6, 41), mapSet(MINECRAFT_1_20_5, 40), mapSet(MINECRAFT_1_20_3, 39),
|
||||
mapSet(MINECRAFT_1_19_3, 38), mapSet(MINECRAFT_1_19, 41)));
|
||||
empty(id("minecraft:gamemode", mapSet(MINECRAFT_1_21_6, 42), mapSet(MINECRAFT_1_20_5, 41), mapSet(MINECRAFT_1_20_3, 40),
|
||||
mapSet(MINECRAFT_1_19_3, 39))); // 1.19.3
|
||||
|
||||
empty(id("minecraft:time", mapSet(MINECRAFT_1_20_5, 42), mapSet(MINECRAFT_1_20_3, 41), mapSet(MINECRAFT_1_19_3, 40),
|
||||
mapSet(MINECRAFT_1_19, 42)), TimeArgumentSerializer.TIME); // added in 1.14
|
||||
empty(id("minecraft:time", mapSet(MINECRAFT_1_21_6, 43), mapSet(MINECRAFT_1_20_5, 42), mapSet(MINECRAFT_1_20_3, 41),
|
||||
mapSet(MINECRAFT_1_19_3, 40), mapSet(MINECRAFT_1_19, 42)), TimeArgumentSerializer.TIME); // added in 1.14
|
||||
|
||||
register(id("minecraft:resource_or_tag", mapSet(MINECRAFT_1_20_5, 43), mapSet(MINECRAFT_1_20_3, 42), mapSet(MINECRAFT_1_19_3, 41),
|
||||
mapSet(MINECRAFT_1_19, 43)), RegistryKeyArgument.class, RegistryKeyArgumentSerializer.REGISTRY);
|
||||
register(id("minecraft:resource_or_tag_key", mapSet(MINECRAFT_1_20_5, 44), mapSet(MINECRAFT_1_20_3, 43), mapSet(MINECRAFT_1_19_3, 42)),
|
||||
register(id("minecraft:resource_or_tag", mapSet(MINECRAFT_1_21_6, 44), mapSet(MINECRAFT_1_20_5, 43), mapSet(MINECRAFT_1_20_3, 42),
|
||||
mapSet(MINECRAFT_1_19_3, 41), mapSet(MINECRAFT_1_19, 43)), RegistryKeyArgument.class, RegistryKeyArgumentSerializer.REGISTRY);
|
||||
register(id("minecraft:resource_or_tag_key", mapSet(MINECRAFT_1_21_6, 45), mapSet(MINECRAFT_1_20_5, 44), mapSet(MINECRAFT_1_20_3, 43),
|
||||
mapSet(MINECRAFT_1_19_3, 42)),
|
||||
RegistryKeyArgumentList.ResourceOrTagKey.class,
|
||||
RegistryKeyArgumentList.ResourceOrTagKey.Serializer.REGISTRY);
|
||||
register(id("minecraft:resource", mapSet(MINECRAFT_1_20_5, 45), mapSet(MINECRAFT_1_20_3, 44), mapSet(MINECRAFT_1_19_3, 43),
|
||||
mapSet(MINECRAFT_1_19, 44)),
|
||||
register(id("minecraft:resource", mapSet(MINECRAFT_1_21_6, 46), mapSet(MINECRAFT_1_20_5, 45), mapSet(MINECRAFT_1_20_3, 44),
|
||||
mapSet(MINECRAFT_1_19_3, 43), mapSet(MINECRAFT_1_19, 44)),
|
||||
RegistryKeyArgument.class, RegistryKeyArgumentSerializer.REGISTRY);
|
||||
register(id("minecraft:resource_key", mapSet(MINECRAFT_1_20_5, 46), mapSet(MINECRAFT_1_20_3, 45), mapSet(MINECRAFT_1_19_3, 44)),
|
||||
register(id("minecraft:resource_key", mapSet(MINECRAFT_1_21_6, 47), mapSet(MINECRAFT_1_20_5, 46), mapSet(MINECRAFT_1_20_3, 45),
|
||||
mapSet(MINECRAFT_1_19_3, 44)),
|
||||
RegistryKeyArgumentList.ResourceKey.class,
|
||||
RegistryKeyArgumentList.ResourceKey.Serializer.REGISTRY);
|
||||
register(id("minecraft:resource_selector", mapSet(MINECRAFT_1_21_6, 48), mapSet(MINECRAFT_1_21_5, 47)),
|
||||
RegistryKeyArgumentList.ResourceSelector.class,
|
||||
RegistryKeyArgumentList.ResourceSelector.Serializer.REGISTRY);
|
||||
|
||||
empty(id("minecraft:template_mirror", mapSet(MINECRAFT_1_20_5, 47), mapSet(MINECRAFT_1_20_3, 46), mapSet(MINECRAFT_1_19, 45))); // 1.19
|
||||
empty(id("minecraft:template_rotation", mapSet(MINECRAFT_1_20_5, 48), mapSet(MINECRAFT_1_20_3, 47), mapSet(MINECRAFT_1_19, 46))); // 1.19
|
||||
empty(id("minecraft:heightmap", mapSet(MINECRAFT_1_20_3, 49), mapSet(MINECRAFT_1_19_4, 47))); // 1.19.4
|
||||
empty(id("minecraft:template_mirror", mapSet(MINECRAFT_1_21_6, 49), mapSet(MINECRAFT_1_21_5, 48), mapSet(MINECRAFT_1_20_5, 47),
|
||||
mapSet(MINECRAFT_1_20_3, 46), mapSet(MINECRAFT_1_19, 45))); // 1.19
|
||||
empty(id("minecraft:template_rotation", mapSet(MINECRAFT_1_21_6, 50), mapSet(MINECRAFT_1_21_5, 49), mapSet(MINECRAFT_1_20_5, 48),
|
||||
mapSet(MINECRAFT_1_20_3, 47), mapSet(MINECRAFT_1_19, 46))); // 1.19
|
||||
empty(id("minecraft:heightmap", mapSet(MINECRAFT_1_21_6, 51), mapSet(MINECRAFT_1_21_5, 50), mapSet(MINECRAFT_1_20_3, 49),
|
||||
mapSet(MINECRAFT_1_19_4, 47))); // 1.19.4
|
||||
|
||||
empty(id("minecraft:uuid", mapSet(MINECRAFT_1_20_5, 53), mapSet(MINECRAFT_1_20_3, 48), mapSet(MINECRAFT_1_19_4, 48),
|
||||
mapSet(MINECRAFT_1_19, 47))); // added in 1.16
|
||||
empty(id("minecraft:uuid", mapSet(MINECRAFT_1_21_6, 56), mapSet(MINECRAFT_1_21_5, 54),mapSet(MINECRAFT_1_20_5, 53), mapSet(MINECRAFT_1_20_3, 48),
|
||||
mapSet(MINECRAFT_1_19_4, 48), mapSet(MINECRAFT_1_19, 47))); // added in 1.16
|
||||
|
||||
empty(id("minecraft:loot_table", mapSet(MINECRAFT_1_20_5, 50)));
|
||||
empty(id("minecraft:loot_predicate", mapSet(MINECRAFT_1_20_5, 51)));
|
||||
empty(id("minecraft:loot_modifier", mapSet(MINECRAFT_1_20_5, 52)));
|
||||
empty(id("minecraft:loot_table", mapSet(MINECRAFT_1_21_6, 52), mapSet(MINECRAFT_1_21_5, 51), mapSet(MINECRAFT_1_20_5, 50)));
|
||||
empty(id("minecraft:loot_predicate", mapSet(MINECRAFT_1_21_6, 53), mapSet(MINECRAFT_1_21_5, 52), mapSet(MINECRAFT_1_20_5, 51)));
|
||||
empty(id("minecraft:loot_modifier", mapSet(MINECRAFT_1_21_6, 54), mapSet(MINECRAFT_1_21_5, 53), mapSet(MINECRAFT_1_20_5, 52)));
|
||||
|
||||
empty(id("minecraft:hex_color", mapSet(MINECRAFT_1_21_6, 17))); // added in 1.21.6
|
||||
empty(id("minecraft:dialog", mapSet(MINECRAFT_1_21_6, 55))); // added in 1.21.6
|
||||
|
||||
// Crossstitch support
|
||||
register(id("crossstitch:mod_argument", mapSet(MINECRAFT_1_19, -256)), ModArgumentProperty.class, MOD);
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user