Merge remote-tracking branch 'upstream/dev/3.0.0' into upstream
# Conflicts: # proxy/src/main/java/com/velocitypowered/proxy/protocol/StateRegistry.java
This commit is contained in:
@@ -44,7 +44,7 @@ body:
|
|||||||
```
|
```
|
||||||
[17:44:10 INFO]: Velocity 3.3.0-SNAPSHOT (git-9d25d309-b400)
|
[17:44:10 INFO]: Velocity 3.3.0-SNAPSHOT (git-9d25d309-b400)
|
||||||
[17:44:10 INFO]: Copyright 2018-2023 Velocity Contributors. Velocity is licensed under the terms of the GNU General Public License v3.
|
[17:44:10 INFO]: Copyright 2018-2023 Velocity Contributors. Velocity is licensed under the terms of the GNU General Public License v3.
|
||||||
[17:44:10 INFO]: velocitypowered.com - GitHub
|
[17:44:10 INFO]: PaperMC - GitHub
|
||||||
```
|
```
|
||||||
</details>
|
</details>
|
||||||
validations:
|
validations:
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
com.velocitypowered.api.plugin.ap.PluginAnnotationProcessor,isolating
|
||||||
@@ -45,7 +45,9 @@ public interface CommandManager {
|
|||||||
* @throws IllegalArgumentException if one of the given aliases is already registered, or
|
* @throws IllegalArgumentException if one of the given aliases is already registered, or
|
||||||
* the given command does not implement a registrable {@link Command} subinterface
|
* the given command does not implement a registrable {@link Command} subinterface
|
||||||
* @see Command for a list of registrable Command subinterfaces
|
* @see Command for a list of registrable Command subinterfaces
|
||||||
|
* @deprecated use {@link #register(CommandMeta, Command)} instead with a plugin specified
|
||||||
*/
|
*/
|
||||||
|
@Deprecated
|
||||||
default void register(String alias, Command command, String... otherAliases) {
|
default void register(String alias, Command command, String... otherAliases) {
|
||||||
register(metaBuilder(alias).aliases(otherAliases).build(), command);
|
register(metaBuilder(alias).aliases(otherAliases).build(), command);
|
||||||
}
|
}
|
||||||
@@ -55,7 +57,9 @@ public interface CommandManager {
|
|||||||
*
|
*
|
||||||
* @param command the command to register
|
* @param command the command to register
|
||||||
* @throws IllegalArgumentException if the node alias is already registered
|
* @throws IllegalArgumentException if the node alias is already registered
|
||||||
|
* @deprecated use {@link #register(CommandMeta, Command)} instead with a plugin specified
|
||||||
*/
|
*/
|
||||||
|
@Deprecated
|
||||||
void register(BrigadierCommand command);
|
void register(BrigadierCommand command);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -45,10 +45,28 @@ public interface EventManager {
|
|||||||
* @param postOrder the order in which events should be posted to the handler
|
* @param postOrder the order in which events should be posted to the handler
|
||||||
* @param handler the handler to register
|
* @param handler the handler to register
|
||||||
* @param <E> the event type to handle
|
* @param <E> the event type to handle
|
||||||
|
* @deprecated use {@link #register(Object, Class, short, EventHandler)} instead
|
||||||
*/
|
*/
|
||||||
|
@Deprecated
|
||||||
<E> void register(Object plugin, Class<E> eventClass, PostOrder postOrder,
|
<E> void register(Object plugin, Class<E> eventClass, PostOrder postOrder,
|
||||||
EventHandler<E> handler);
|
EventHandler<E> handler);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Requests that the specified {@code handler} listen for events and associate it with the {@code
|
||||||
|
* plugin}.
|
||||||
|
*
|
||||||
|
* <p>Note that this method will register a non-asynchronous listener by default. If you want to
|
||||||
|
* use an asynchronous event handler, return {@link EventTask#async(Runnable)} from the handler.</p>
|
||||||
|
*
|
||||||
|
* @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 handler the handler to register
|
||||||
|
* @param <E> the event type to handle
|
||||||
|
*/
|
||||||
|
<E> void register(Object plugin, Class<E> eventClass, short postOrder,
|
||||||
|
EventHandler<E> handler);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fires the specified event to the event bus asynchronously. This allows Velocity to continue
|
* Fires the specified event to the event bus asynchronously. This allows Velocity to continue
|
||||||
* servicing connections while a plugin handles a potentially long-running operation such as a
|
* servicing connections while a plugin handles a potentially long-running operation such as a
|
||||||
|
|||||||
@@ -12,6 +12,6 @@ package com.velocitypowered.api.event;
|
|||||||
*/
|
*/
|
||||||
public enum PostOrder {
|
public enum PostOrder {
|
||||||
|
|
||||||
FIRST, EARLY, NORMAL, LATE, LAST
|
FIRST, EARLY, NORMAL, LATE, LAST, CUSTOM
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,24 +22,38 @@ public @interface Subscribe {
|
|||||||
/**
|
/**
|
||||||
* The order events will be posted to this listener.
|
* The order events will be posted to this listener.
|
||||||
*
|
*
|
||||||
|
* @deprecated specify the order using {@link #priority()} instead
|
||||||
* @return the order
|
* @return the order
|
||||||
*/
|
*/
|
||||||
|
@Deprecated
|
||||||
PostOrder order() default PostOrder.NORMAL;
|
PostOrder order() default PostOrder.NORMAL;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether the handler must be called asynchronously.
|
* 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><strong>This option currently has no effect, but in the future it will. In Velocity 3.0.0,
|
* <p>Note that due to compatibility constraints, you must specify {@link PostOrder#CUSTOM}
|
||||||
* all event handlers run asynchronously by default. You are encouraged to determine whether or
|
* in order to use this field.</p>
|
||||||
* not to enable it now. This option is being provided as a migration aid.</strong></p>
|
|
||||||
*
|
*
|
||||||
* <p>If this method returns {@code true}, the method is guaranteed to be executed
|
* @return the priority
|
||||||
* asynchronously. Otherwise, the handler may be executed on the current thread or
|
*/
|
||||||
* asynchronously. <strong>This still means you must consider thread-safety in your
|
short priority() default Short.MIN_VALUE;
|
||||||
* event listeners</strong> as the "current thread" can and will be different each time.</p>
|
|
||||||
|
/**
|
||||||
|
* Whether the handler must be called asynchronously. By default, all event handlers are called
|
||||||
|
* asynchronously.
|
||||||
*
|
*
|
||||||
* <p>If any method handler targeting an event type is marked with {@code true}, then every
|
* <p>For performance (for instance, if you use {@link EventTask#withContinuation}), you can
|
||||||
* handler targeting that event type will be executed asynchronously.</p>
|
* optionally specify <code>false</code>. This option will become {@code false} by default
|
||||||
|
* in a future release of Velocity.</p>
|
||||||
|
*
|
||||||
|
* <p>If this is {@code true}, the method is guaranteed to be executed asynchronously. Otherwise,
|
||||||
|
* the handler may be executed on the current thread or asynchronously. <strong>This still means
|
||||||
|
* you must consider thread-safety in your event listeners</strong> as the "current thread" can
|
||||||
|
* and will be different each time.</p>
|
||||||
|
*
|
||||||
|
* <p>Note that if any method handler targeting an event type is marked with {@code true}, then
|
||||||
|
* every handler targeting that event type will be executed asynchronously.</p>
|
||||||
*
|
*
|
||||||
* @return Requires async
|
* @return Requires async
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -51,6 +51,13 @@ public final class PlayerChatEvent implements ResultedEvent<PlayerChatEvent.Chat
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set result for the event.
|
||||||
|
*
|
||||||
|
* @param result the result of event
|
||||||
|
* @deprecated for 1.19.1 and newer, set this as denied will kick users
|
||||||
|
*/
|
||||||
|
@Deprecated
|
||||||
@Override
|
@Override
|
||||||
public void setResult(ChatResult result) {
|
public void setResult(ChatResult result) {
|
||||||
this.result = Preconditions.checkNotNull(result, "result");
|
this.result = Preconditions.checkNotNull(result, "result");
|
||||||
|
|||||||
@@ -87,7 +87,8 @@ public enum ProtocolVersion implements Ordered<ProtocolVersion> {
|
|||||||
MINECRAFT_1_20_2(764, "1.20.2"),
|
MINECRAFT_1_20_2(764, "1.20.2"),
|
||||||
MINECRAFT_1_20_3(765, "1.20.3", "1.20.4"),
|
MINECRAFT_1_20_3(765, "1.20.3", "1.20.4"),
|
||||||
MINECRAFT_1_20_5(766, "1.20.5", "1.20.6"),
|
MINECRAFT_1_20_5(766, "1.20.5", "1.20.6"),
|
||||||
MINECRAFT_1_21(767, "1.21", "1.21.1");
|
MINECRAFT_1_21(767, "1.21", "1.21.1"),
|
||||||
|
MINECRAFT_1_21_2(768, "1.21.2", "1.21.3");
|
||||||
|
|
||||||
private static final int SNAPSHOT_BIT = 30;
|
private static final int SNAPSHOT_BIT = 30;
|
||||||
|
|
||||||
|
|||||||
@@ -26,11 +26,20 @@ public interface InboundConnection {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the hostname that the user entered into the client, if applicable.
|
* Returns the hostname that the user entered into the client, if applicable.
|
||||||
*
|
* <br/>
|
||||||
|
* This is partially processed, including removing a trailing dot, and discarding data after a null byte.
|
||||||
|
|
||||||
* @return the hostname from the client
|
* @return the hostname from the client
|
||||||
*/
|
*/
|
||||||
Optional<InetSocketAddress> getVirtualHost();
|
Optional<InetSocketAddress> getVirtualHost();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the raw hostname that the client sent, if applicable.
|
||||||
|
*
|
||||||
|
* @return the raw hostname from the client
|
||||||
|
*/
|
||||||
|
Optional<String> getRawVirtualHost();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Determine whether or not the player remains online.
|
* Determine whether or not the player remains online.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -68,6 +68,20 @@ public interface PlayerSettings {
|
|||||||
*/
|
*/
|
||||||
boolean isClientListingAllowed();
|
boolean isClientListingAllowed();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns if the client has text filtering enabled.
|
||||||
|
*
|
||||||
|
* @return if text filtering is enabled
|
||||||
|
*/
|
||||||
|
boolean isTextFilteringEnabled();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the selected "Particles" option state.
|
||||||
|
*
|
||||||
|
* @return the particle option
|
||||||
|
*/
|
||||||
|
ParticleStatus getParticleStatus();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The client's current chat display mode.
|
* The client's current chat display mode.
|
||||||
*/
|
*/
|
||||||
@@ -84,4 +98,13 @@ public interface PlayerSettings {
|
|||||||
LEFT,
|
LEFT,
|
||||||
RIGHT
|
RIGHT
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The client's current "Particles" option state.
|
||||||
|
*/
|
||||||
|
enum ParticleStatus {
|
||||||
|
ALL,
|
||||||
|
DECREASED,
|
||||||
|
MINIMAL
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -168,6 +168,25 @@ public interface TabList {
|
|||||||
* @deprecated Internal usage. Use {@link TabListEntry.Builder} instead.
|
* @deprecated Internal usage. Use {@link TabListEntry.Builder} instead.
|
||||||
*/
|
*/
|
||||||
@Deprecated
|
@Deprecated
|
||||||
|
default TabListEntry buildEntry(GameProfile profile, @Nullable Component displayName, int latency,
|
||||||
|
int gameMode, @Nullable ChatSession chatSession, boolean listed) {
|
||||||
|
return buildEntry(profile, displayName, latency, gameMode, chatSession, listed, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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
|
||||||
|
* @return the entry
|
||||||
|
* @deprecated Internal usage. Use {@link TabListEntry.Builder} instead.
|
||||||
|
*/
|
||||||
|
@Deprecated
|
||||||
TabListEntry buildEntry(GameProfile profile, @Nullable Component displayName, int latency,
|
TabListEntry buildEntry(GameProfile profile, @Nullable Component displayName, int latency,
|
||||||
int gameMode, @Nullable ChatSession chatSession, boolean listed);
|
int gameMode, @Nullable ChatSession chatSession, boolean listed, int listOrder);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -139,6 +139,27 @@ public interface TabListEntry extends KeyIdentifiable {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the order/priority of this entry in the tab list.
|
||||||
|
*
|
||||||
|
* @return order of this entry
|
||||||
|
* @sinceMinecraft 1.21.2
|
||||||
|
*/
|
||||||
|
default int getListOrder() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the order/priority of this entry in the tab list.
|
||||||
|
*
|
||||||
|
* @param order order of this entry
|
||||||
|
* @return {@code this}, for chaining
|
||||||
|
* @sinceMinecraft 1.21.2
|
||||||
|
*/
|
||||||
|
default TabListEntry setListOrder(int order) {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a {@link Builder} to create a {@link TabListEntry}.
|
* Returns a {@link Builder} to create a {@link TabListEntry}.
|
||||||
*
|
*
|
||||||
@@ -161,6 +182,7 @@ public interface TabListEntry extends KeyIdentifiable {
|
|||||||
private int latency = 0;
|
private int latency = 0;
|
||||||
private int gameMode = 0;
|
private int gameMode = 0;
|
||||||
private boolean listed = true;
|
private boolean listed = true;
|
||||||
|
private int listOrder = 0;
|
||||||
|
|
||||||
private @Nullable ChatSession chatSession;
|
private @Nullable ChatSession chatSession;
|
||||||
|
|
||||||
@@ -243,7 +265,7 @@ public interface TabListEntry extends KeyIdentifiable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets wether this entry should be visible.
|
* Sets whether this entry should be visible.
|
||||||
*
|
*
|
||||||
* @param listed to set
|
* @param listed to set
|
||||||
* @return ${code this}, for chaining
|
* @return ${code this}, for chaining
|
||||||
@@ -254,6 +276,19 @@ public interface TabListEntry extends KeyIdentifiable {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the order/priority of this entry in the tab list.
|
||||||
|
*
|
||||||
|
* @param order to set
|
||||||
|
* @return ${code this}, for chaining
|
||||||
|
* @sinceMinecraft 1.21.2
|
||||||
|
* @see TabListEntry#getListOrder()
|
||||||
|
*/
|
||||||
|
public Builder listOrder(int order) {
|
||||||
|
this.listOrder = order;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs the {@link TabListEntry} specified by {@code this} {@link Builder}.
|
* Constructs the {@link TabListEntry} specified by {@code this} {@link Builder}.
|
||||||
*
|
*
|
||||||
@@ -266,7 +301,7 @@ public interface TabListEntry extends KeyIdentifiable {
|
|||||||
if (profile == null) {
|
if (profile == null) {
|
||||||
throw new IllegalStateException("The GameProfile must be set when building a TabListEntry");
|
throw new IllegalStateException("The GameProfile must be set when building a TabListEntry");
|
||||||
}
|
}
|
||||||
return tabList.buildEntry(profile, displayName, latency, gameMode, chatSession, listed);
|
return tabList.buildEntry(profile, displayName, latency, gameMode, chatSession, listed, listOrder);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -1,2 +1,2 @@
|
|||||||
group=com.velocitypowered
|
group=com.velocitypowered
|
||||||
version=3.3.0-SNAPSHOT
|
version=3.4.0-SNAPSHOT
|
||||||
|
|||||||
@@ -2,8 +2,8 @@
|
|||||||
configurate3 = "3.7.3"
|
configurate3 = "3.7.3"
|
||||||
configurate4 = "4.1.2"
|
configurate4 = "4.1.2"
|
||||||
flare = "2.0.1"
|
flare = "2.0.1"
|
||||||
log4j = "2.22.1"
|
log4j = "2.24.1"
|
||||||
netty = "4.1.106.Final"
|
netty = "4.1.114.Final"
|
||||||
|
|
||||||
[plugins]
|
[plugins]
|
||||||
indra-publishing = "net.kyori.indra.publishing:2.0.6"
|
indra-publishing = "net.kyori.indra.publishing:2.0.6"
|
||||||
@@ -12,13 +12,14 @@ spotless = "com.diffplug.spotless:6.25.0"
|
|||||||
|
|
||||||
[libraries]
|
[libraries]
|
||||||
adventure-bom = "net.kyori:adventure-bom:4.17.0"
|
adventure-bom = "net.kyori:adventure-bom:4.17.0"
|
||||||
adventure-facet = "net.kyori:adventure-platform-facet:4.3.2"
|
adventure-text-serializer-json-legacy-impl = "net.kyori:adventure-text-serializer-json-legacy-impl:4.17.0"
|
||||||
|
adventure-facet = "net.kyori:adventure-platform-facet:4.3.4"
|
||||||
asm = "org.ow2.asm:asm:9.6"
|
asm = "org.ow2.asm:asm:9.6"
|
||||||
auto-service = "com.google.auto.service:auto-service:1.0.1"
|
auto-service = "com.google.auto.service:auto-service:1.0.1"
|
||||||
auto-service-annotations = "com.google.auto.service:auto-service-annotations: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"
|
brigadier = "com.velocitypowered:velocity-brigadier:1.0.0-SNAPSHOT"
|
||||||
bstats = "org.bstats:bstats-base:3.0.2"
|
bstats = "org.bstats:bstats-base:3.0.2"
|
||||||
caffeine = "com.github.ben-manes.caffeine:caffeine:3.1.5"
|
caffeine = "com.github.ben-manes.caffeine:caffeine:3.1.8"
|
||||||
checker-qual = "org.checkerframework:checker-qual:3.42.0"
|
checker-qual = "org.checkerframework:checker-qual:3.42.0"
|
||||||
checkstyle = "com.puppycrawl.tools:checkstyle:10.9.3"
|
checkstyle = "com.puppycrawl.tools:checkstyle:10.9.3"
|
||||||
completablefutures = "com.spotify:completable-futures:0.3.6"
|
completablefutures = "com.spotify:completable-futures:0.3.6"
|
||||||
@@ -28,15 +29,15 @@ configurate3-gson = { module = "org.spongepowered:configurate-gson", version.ref
|
|||||||
configurate4-hocon = { module = "org.spongepowered:configurate-hocon", version.ref = "configurate4" }
|
configurate4-hocon = { module = "org.spongepowered:configurate-hocon", version.ref = "configurate4" }
|
||||||
configurate4-yaml = { module = "org.spongepowered:configurate-yaml", version.ref = "configurate4" }
|
configurate4-yaml = { module = "org.spongepowered:configurate-yaml", version.ref = "configurate4" }
|
||||||
configurate4-gson = { module = "org.spongepowered:configurate-gson", version.ref = "configurate4" }
|
configurate4-gson = { module = "org.spongepowered:configurate-gson", version.ref = "configurate4" }
|
||||||
disruptor = "com.lmax:disruptor:3.4.4"
|
disruptor = "com.lmax:disruptor:4.0.0"
|
||||||
fastutil = "it.unimi.dsi:fastutil:8.5.12"
|
fastutil = "it.unimi.dsi:fastutil:8.5.15"
|
||||||
flare-core = { module = "space.vectrix.flare:flare", version.ref = "flare" }
|
flare-core = { module = "space.vectrix.flare:flare", version.ref = "flare" }
|
||||||
flare-fastutil = { module = "space.vectrix.flare:flare-fastutil", version.ref = "flare" }
|
flare-fastutil = { module = "space.vectrix.flare:flare-fastutil", version.ref = "flare" }
|
||||||
jline = "org.jline:jline-terminal-jansi:3.23.0"
|
jline = "org.jline:jline-terminal-jansi:3.27.1"
|
||||||
jopt = "net.sf.jopt-simple:jopt-simple:5.0.4"
|
jopt = "net.sf.jopt-simple:jopt-simple:5.0.4"
|
||||||
junit = "org.junit.jupiter:junit-jupiter:5.10.2"
|
junit = "org.junit.jupiter:junit-jupiter:5.10.2"
|
||||||
jspecify = "org.jspecify:jspecify:0.3.0"
|
jspecify = "org.jspecify:jspecify:0.3.0"
|
||||||
kyori-ansi = "net.kyori:ansi:1.0.3"
|
kyori-ansi = "net.kyori:ansi:1.1.0"
|
||||||
guava = "com.google.guava:guava:25.1-jre"
|
guava = "com.google.guava:guava:25.1-jre"
|
||||||
gson = "com.google.code.gson:gson:2.10.1"
|
gson = "com.google.code.gson:gson:2.10.1"
|
||||||
guice = "com.google.inject:guice:6.0.0"
|
guice = "com.google.inject:guice:6.0.0"
|
||||||
|
|||||||
+54
-21
@@ -1,31 +1,64 @@
|
|||||||
# velocity-natives
|
# velocity-natives
|
||||||
|
|
||||||
This directory contains native acceleration code for Velocity, along with
|
This directory contains native acceleration code for Velocity, along with traditional Java fallbacks.
|
||||||
traditional Java fallbacks.
|
|
||||||
|
|
||||||
## Compression
|
Compression is based on the `libdeflate` library, which is wire-compatible with zlib but significantly faster.
|
||||||
|
|
||||||
* **Supported platforms**: Linux x86_64 and aarch64, with Java 11 `ByteBuffer` API support as a fallback.
|
Encryption is based on OpenSSL for Linux, which is the most widely-used encryption library in the world.
|
||||||
Compiled on CentOS 7.
|
OpenSSL has had several different ABIs over the years, so we provide multiple versions of the native
|
||||||
* **Rationale**: Using a native zlib wrapper, we can avoid multiple trips into Java just to copy memory around.
|
library. Currently we compile against OpenSSL 1.1.x and OpenSSL 3.x.x. For macOS, we use the built-in
|
||||||
|
CommonCrypto library.
|
||||||
|
|
||||||
## Encryption
|
## Supported Platforms
|
||||||
|
|
||||||
* **Supported platforms**: Linux x86_64 (OpenSSL 1.0.x and OpenSSL 1.1.x) and aarch64 (OpenSSL 1.1.x only)
|
`velocity-natives` is built for the following platforms:
|
||||||
* **Rationale**: Using a C library for encryption means we can limit memory copies. Prior to Java 7, this was the only
|
|
||||||
way to use AES-NI extensions on modern processors, but this is less important since JDK 8 has native support.
|
|
||||||
* OpenSSL is not included in Velocity. Every distribution provides it now. To deal with ABI incompatibilities,
|
|
||||||
the native library (which only calls into OpenSSL and contains no cryptographic code) are available for
|
|
||||||
CentOS 7 (OpenSSL 1.0.0-based), Debian 9 (OpenSSL 1.1.0-based) and Debian Bookworm (OpenSSL 3.0.0-based)
|
|
||||||
to provide the widest, most reasonable compatibility with most modern distributions.
|
|
||||||
|
|
||||||
## OS support
|
- Linux x86_64
|
||||||
|
- Linux aarch64
|
||||||
|
- macOS aarch64 ("Apple Silicon")
|
||||||
|
|
||||||
The natives intend to have the widest possible range of compatibility with modern Linux distributions
|
For Linux platforms, we provide two versions of the native library: one built against OpenSSL 1.1.x and one built against OpenSSL 3.x.x.
|
||||||
(defined as those being released in or after 2014).
|
All native libraries are built on various versions of Ubuntu and Alpine:
|
||||||
|
|
||||||
In theory, these libraries can be compiled for any Unix-like system (in the past, we supported macOS),
|
- Ubuntu 20.04 for OpenSSL 1.1.x support and for compression
|
||||||
but interest in other systems is minimal at best, thus we focus on Linux x86_64 and aarch64 as they
|
- Ubuntu 22.04 for OpenSSL 3.x.x support
|
||||||
are commonly used platforms.
|
- Alpine 3.18 for OpenSSL 3.x.x support and compression (musl libc users only)
|
||||||
|
|
||||||
Alpine Linux support is on a "best-effort" basis only. Using `apk add libc6-compat` may enable native support.
|
## Building
|
||||||
|
|
||||||
|
### On Linux
|
||||||
|
|
||||||
|
To build the native libraries, you need to have Docker installed and have it set up to perform [multi-platform builds](https://docs.docker.com/build/building/multi-platform/). Then, run the following command:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./build-support/build-all-linux-natives.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
This will build the native libraries for both OpenSSL 1.1.x and OpenSSL 3.x.x on both x86_64 and aarch64.
|
||||||
|
|
||||||
|
### On macOS
|
||||||
|
|
||||||
|
To build the native libraries on macOS, you need to have `cmake` installed. You can install it using Homebrew:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
brew install cmake
|
||||||
|
```
|
||||||
|
|
||||||
|
Then, run the following command:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./build-support/compile-macos.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
This will build the native libraries for macOS aarch64. x86_64 has not been tested, but it should work.
|
||||||
|
|
||||||
|
### On any other operating system?
|
||||||
|
|
||||||
|
If your OS of choice is a Unix of some sort, you can use the individual Linux build scripts as a base:
|
||||||
|
|
||||||
|
- `build-support/compile-linux-compress.sh`
|
||||||
|
- `build-support/compile-linux-crypto.sh`
|
||||||
|
|
||||||
|
You will need to have the necessary build tools installed (a C/C++ toolchain and `cmake`), and you will need to have OpenSSL installed. You will also need to adjust the script to your needs.
|
||||||
|
|
||||||
|
If your OS of choice is Windows, you're on your own. It should be possible, but we don't provide any support for it.
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
|
||||||
|
FROM amazoncorretto:17.0.12-alpine3.18
|
||||||
|
|
||||||
|
# Install required dependencies
|
||||||
|
RUN apk add --no-cache bash alpine-sdk cmake openssl-dev openssl
|
||||||
|
|
||||||
|
# Create a non-root user
|
||||||
|
RUN adduser -D user
|
||||||
|
USER user
|
||||||
+33
@@ -0,0 +1,33 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# make sure we're in the correct directory - the top-level `native` directory
|
||||||
|
cd "$(dirname "$0")/.." || exit 1
|
||||||
|
|
||||||
|
ARCHS=(x86_64 aarch64)
|
||||||
|
BASE_DOCKERFILE_VARIANTS=(ubuntu-focal ubuntu-jammy alpine)
|
||||||
|
COMPRESSION_VARIANTS=(ubuntu-focal alpine)
|
||||||
|
|
||||||
|
for variant in "${BASE_DOCKERFILE_VARIANTS[@]}"; do
|
||||||
|
docker_platforms=""
|
||||||
|
for arch in "${ARCHS[@]}"; do
|
||||||
|
docker_platforms="$docker_platforms --platform linux/${arch}"
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "Building base build image for $variant..."
|
||||||
|
docker build -t velocity-native-build:$variant $docker_platforms -f build-support/$variant.Dockerfile .
|
||||||
|
done
|
||||||
|
|
||||||
|
for arch in "${ARCHS[@]}"; do
|
||||||
|
for variant in "${BASE_DOCKERFILE_VARIANTS[@]}"; do
|
||||||
|
echo "Building native crypto for $arch on $variant..."
|
||||||
|
|
||||||
|
docker run --rm -v "$(pwd)":/app --platform linux/${arch} velocity-native-build:$variant /bin/bash -c "cd /app && ./build-support/compile-linux-crypto.sh"
|
||||||
|
done
|
||||||
|
|
||||||
|
for variant in "${COMPRESSION_VARIANTS[@]}"; do
|
||||||
|
echo "Building native compression for $arch on $variant..."
|
||||||
|
docker run --rm -v "$(pwd)":/app --platform linux/${arch} velocity-native-build:$variant /bin/bash -c "cd /app && ./build-support/compile-linux-compress.sh"
|
||||||
|
done
|
||||||
|
done
|
||||||
@@ -8,18 +8,22 @@ fi
|
|||||||
|
|
||||||
if [ ! -d libdeflate ]; then
|
if [ ! -d libdeflate ]; then
|
||||||
echo "Cloning libdeflate..."
|
echo "Cloning libdeflate..."
|
||||||
git clone https://github.com/ebiggers/libdeflate.git
|
git clone --branch v1.21 --single-branch https://github.com/ebiggers/libdeflate.git
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "Compiling libdeflate..."
|
echo "Compiling libdeflate..."
|
||||||
cd libdeflate || exit
|
cd libdeflate || exit
|
||||||
cmake -B build && cmake --build build --target libdeflate_static
|
rm -rf build && cmake -DCMAKE_POSITION_INDEPENDENT_CODE=ON -B build && cmake --build build --target libdeflate_static
|
||||||
cd ..
|
cd ..
|
||||||
|
|
||||||
|
# Determine if we are on musl libc or glibc
|
||||||
|
suffix=""
|
||||||
|
if ldd --version 2>&1 | grep -q musl; then
|
||||||
|
suffix="-musl"
|
||||||
|
fi
|
||||||
|
|
||||||
CFLAGS="-O2 -I$JAVA_HOME/include/ -I$JAVA_HOME/include/linux/ -fPIC -shared -Wl,-z,noexecstack -Wall -Werror -fomit-frame-pointer"
|
CFLAGS="-O2 -I$JAVA_HOME/include/ -I$JAVA_HOME/include/linux/ -fPIC -shared -Wl,-z,noexecstack -Wall -Werror -fomit-frame-pointer"
|
||||||
ARCH=$(uname -m)
|
ARCH=$(uname -m)
|
||||||
mkdir -p src/main/resources/linux_$ARCH
|
mkdir -p src/main/resources/linux_$ARCH
|
||||||
$CC $CFLAGS -Ilibdeflate src/main/c/jni_util.c src/main/c/jni_zlib_deflate.c src/main/c/jni_zlib_inflate.c \
|
$CC $CFLAGS -Ilibdeflate src/main/c/jni_util.c src/main/c/jni_zlib_deflate.c src/main/c/jni_zlib_inflate.c \
|
||||||
libdeflate/build/libdeflate.a -o src/main/resources/linux_$ARCH/velocity-compress.so
|
libdeflate/build/libdeflate.a -o src/main/resources/linux_$ARCH/velocity-compress$suffix.so
|
||||||
$CC $CFLAGS -shared src/main/c/jni_util.c src/main/c/jni_cipher_openssl.c \
|
|
||||||
-o src/main/resources/linux_$ARCH/velocity-cipher.so -lcrypto
|
|
||||||
Executable
+34
@@ -0,0 +1,34 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
openssl_version=$(openssl version | awk '{print $2}')
|
||||||
|
|
||||||
|
# Extract the major and minor version numbers
|
||||||
|
major_version=$(echo "$openssl_version" | cut -d. -f1)
|
||||||
|
minor_version=$(echo "$openssl_version" | cut -d. -f2)
|
||||||
|
|
||||||
|
# Determine the appropriate file name based on the version
|
||||||
|
if [ "$major_version" -eq 1 ] && [ "$minor_version" -eq 1 ]; then
|
||||||
|
filename="velocity-cipher-ossl11x.so"
|
||||||
|
elif [ "$major_version" -eq 3 ]; then
|
||||||
|
filename="velocity-cipher-ossl30x.so"
|
||||||
|
else
|
||||||
|
echo "Unsupported OpenSSL version: $openssl_version"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ ! "$CC" ]; then
|
||||||
|
export CC=gcc
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Determine if we are on musl libc or glibc
|
||||||
|
suffix=""
|
||||||
|
if ldd --version 2>&1 | grep -q musl; then
|
||||||
|
suffix="-musl"
|
||||||
|
filename=$(echo "$filename" | sed "s/\.so/$suffix.so/")
|
||||||
|
fi
|
||||||
|
|
||||||
|
CFLAGS="-O2 -I$JAVA_HOME/include/ -I$JAVA_HOME/include/linux/ -fPIC -shared -Wl,-z,noexecstack -Wall -Werror -fomit-frame-pointer"
|
||||||
|
ARCH=$(uname -m)
|
||||||
|
mkdir -p src/main/resources/linux_$ARCH
|
||||||
|
$CC $CFLAGS -shared src/main/c/jni_util.c src/main/c/jni_cipher_openssl.c \
|
||||||
|
-o src/main/resources/linux_$ARCH/$filename -lcrypto
|
||||||
@@ -6,12 +6,12 @@ fi
|
|||||||
|
|
||||||
if [ ! -d libdeflate ]; then
|
if [ ! -d libdeflate ]; then
|
||||||
echo "Cloning libdeflate..."
|
echo "Cloning libdeflate..."
|
||||||
git clone https://github.com/ebiggers/libdeflate.git
|
git clone --branch v1.21 --single-branch https://github.com/ebiggers/libdeflate.git
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "Compiling libdeflate..."
|
echo "Compiling libdeflate..."
|
||||||
cd libdeflate || exit
|
cd libdeflate || exit
|
||||||
cmake -B build && cmake --build build --target libdeflate_static
|
rm -rf build && cmake -B build && cmake --build build --target libdeflate_static
|
||||||
cd ..
|
cd ..
|
||||||
|
|
||||||
CFLAGS="-O2 -I$JAVA_HOME/include/ -I$JAVA_HOME/include/darwin/ -fPIC -shared -Wall -Werror -fomit-frame-pointer"
|
CFLAGS="-O2 -I$JAVA_HOME/include/ -I$JAVA_HOME/include/darwin/ -fPIC -shared -Wall -Werror -fomit-frame-pointer"
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
# Use the official Eclipse Temurin 17.0.12_7-jdk-focal image as the base image.
|
||||||
|
# We compile for Ubuntu Focal Fossa (20.04 LTS) as it is still supported until 2025, and the crypto
|
||||||
|
# native is specific to a given OpenSSL version.
|
||||||
|
FROM eclipse-temurin:17.0.12_7-jdk-focal
|
||||||
|
|
||||||
|
# Install required dependencies
|
||||||
|
RUN apt-get update && apt-get install -y \
|
||||||
|
libssl-dev \
|
||||||
|
curl \
|
||||||
|
git \
|
||||||
|
unzip \
|
||||||
|
build-essential \
|
||||||
|
cmake \
|
||||||
|
openssl \
|
||||||
|
&& rm -rf /var/lib/apt/lists/* \
|
||||||
|
&& apt-get clean
|
||||||
|
|
||||||
|
# Create a non-root user
|
||||||
|
RUN useradd -m -s /bin/bash -u 1000 -U user
|
||||||
|
USER user
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
# Use the official Eclipse Temurin 17.0.12_7-jdk-jammy image as the base image.
|
||||||
|
# We compile for Ubuntu Jammy Jellyfish (22.04 LTS) as it supports OpenSSL 3.0.
|
||||||
|
FROM eclipse-temurin:17.0.12_7-jdk-jammy
|
||||||
|
|
||||||
|
# Install required dependencies
|
||||||
|
RUN apt-get update && apt-get install -y \
|
||||||
|
libssl-dev \
|
||||||
|
curl \
|
||||||
|
git \
|
||||||
|
unzip \
|
||||||
|
build-essential \
|
||||||
|
cmake \
|
||||||
|
openssl \
|
||||||
|
&& rm -rf /var/lib/apt/lists/* \
|
||||||
|
&& apt-get clean
|
||||||
|
|
||||||
|
# Create a non-root user
|
||||||
|
RUN useradd -m -s /bin/bash -u 1000 -U user
|
||||||
|
USER user
|
||||||
@@ -17,29 +17,12 @@
|
|||||||
|
|
||||||
package com.velocitypowered.natives.compression;
|
package com.velocitypowered.natives.compression;
|
||||||
|
|
||||||
import io.netty.buffer.ByteBuf;
|
|
||||||
import java.util.zip.DataFormatException;
|
|
||||||
|
|
||||||
class CompressorUtils {
|
class CompressorUtils {
|
||||||
/**
|
/**
|
||||||
* The default preferred output buffer size for zlib.
|
* The default preferred output buffer size for zlib.
|
||||||
*/
|
*/
|
||||||
static final int ZLIB_BUFFER_SIZE = 8192;
|
static final int ZLIB_BUFFER_SIZE = 8192;
|
||||||
|
|
||||||
/**
|
|
||||||
* Ensures that the buffer does not go over {@code max}.
|
|
||||||
*
|
|
||||||
* @param buf the buffer for check
|
|
||||||
* @param max the maximum size for the buffer
|
|
||||||
* @throws DataFormatException if the buffer becomes too bug
|
|
||||||
*/
|
|
||||||
static void ensureMaxSize(ByteBuf buf, int max) throws DataFormatException {
|
|
||||||
int len = buf.readableBytes();
|
|
||||||
if (len > max) {
|
|
||||||
throw new DataFormatException("Got too much data (" + len + " > " + max + ")");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private CompressorUtils() {
|
private CompressorUtils() {
|
||||||
throw new AssertionError();
|
throw new AssertionError();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -52,18 +52,13 @@ public class MoreByteBufUtils {
|
|||||||
|
|
||||||
private static boolean isCompatible(Native nativeStuff, ByteBuf buf) {
|
private static boolean isCompatible(Native nativeStuff, ByteBuf buf) {
|
||||||
BufferPreference preferred = nativeStuff.preferredBufferType();
|
BufferPreference preferred = nativeStuff.preferredBufferType();
|
||||||
switch (preferred) {
|
return switch (preferred) {
|
||||||
case DIRECT_PREFERRED:
|
case DIRECT_PREFERRED, HEAP_PREFERRED ->
|
||||||
case HEAP_PREFERRED:
|
|
||||||
// The native prefers this type, but doesn't strictly require we provide it.
|
// The native prefers this type, but doesn't strictly require we provide it.
|
||||||
return true;
|
true;
|
||||||
case DIRECT_REQUIRED:
|
case DIRECT_REQUIRED -> buf.hasMemoryAddress();
|
||||||
return buf.hasMemoryAddress();
|
case HEAP_REQUIRED -> buf.hasArray();
|
||||||
case HEAP_REQUIRED:
|
};
|
||||||
return buf.hasArray();
|
|
||||||
default:
|
|
||||||
throw new AssertionError("Preferred buffer type unknown");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -77,15 +72,9 @@ public class MoreByteBufUtils {
|
|||||||
*/
|
*/
|
||||||
public static ByteBuf preferredBuffer(ByteBufAllocator alloc, Native nativeStuff,
|
public static ByteBuf preferredBuffer(ByteBufAllocator alloc, Native nativeStuff,
|
||||||
int initialCapacity) {
|
int initialCapacity) {
|
||||||
switch (nativeStuff.preferredBufferType()) {
|
return switch (nativeStuff.preferredBufferType()) {
|
||||||
case HEAP_REQUIRED:
|
case HEAP_REQUIRED, HEAP_PREFERRED -> alloc.heapBuffer(initialCapacity);
|
||||||
case HEAP_PREFERRED:
|
case DIRECT_PREFERRED, DIRECT_REQUIRED -> alloc.directBuffer(initialCapacity);
|
||||||
return alloc.heapBuffer(initialCapacity);
|
};
|
||||||
case DIRECT_PREFERRED:
|
|
||||||
case DIRECT_REQUIRED:
|
|
||||||
return alloc.directBuffer(initialCapacity);
|
|
||||||
default:
|
|
||||||
throw new AssertionError("Preferred buffer type unknown");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ package com.velocitypowered.natives.util;
|
|||||||
|
|
||||||
import io.netty.buffer.ByteBuf;
|
import io.netty.buffer.ByteBuf;
|
||||||
import io.netty.buffer.Unpooled;
|
import io.netty.buffer.Unpooled;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.function.BooleanSupplier;
|
import java.util.function.BooleanSupplier;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -29,6 +30,8 @@ public class NativeConstraints {
|
|||||||
private static final boolean IS_AMD64;
|
private static final boolean IS_AMD64;
|
||||||
private static final boolean IS_AARCH64;
|
private static final boolean IS_AARCH64;
|
||||||
private static final boolean CAN_GET_MEMORYADDRESS;
|
private static final boolean CAN_GET_MEMORYADDRESS;
|
||||||
|
private static final boolean IS_LINUX;
|
||||||
|
private static final boolean IS_MUSL_LIBC;
|
||||||
|
|
||||||
static {
|
static {
|
||||||
ByteBuf test = Unpooled.directBuffer();
|
ByteBuf test = Unpooled.directBuffer();
|
||||||
@@ -39,21 +42,46 @@ public class NativeConstraints {
|
|||||||
}
|
}
|
||||||
|
|
||||||
String osArch = System.getProperty("os.arch", "");
|
String osArch = System.getProperty("os.arch", "");
|
||||||
// HotSpot on Intel macOS prefers x86_64, but OpenJ9 on macOS and HotSpot/OpenJ9 elsewhere
|
|
||||||
// give amd64.
|
|
||||||
IS_AMD64 = osArch.equals("amd64") || osArch.equals("x86_64");
|
IS_AMD64 = osArch.equals("amd64") || osArch.equals("x86_64");
|
||||||
IS_AARCH64 = osArch.equals("aarch64") || osArch.equals("arm64");
|
IS_AARCH64 = osArch.equals("aarch64") || osArch.equals("arm64");
|
||||||
|
|
||||||
|
IS_LINUX = System.getProperty("os.name", "").equalsIgnoreCase("Linux");
|
||||||
|
|
||||||
|
// Determine if we're using musl libc by invoking `ldd --version`.
|
||||||
|
if (IS_LINUX) {
|
||||||
|
boolean isMusl;
|
||||||
|
try {
|
||||||
|
Process process = new ProcessBuilder("ldd", "--version")
|
||||||
|
.redirectErrorStream(true)
|
||||||
|
.start();
|
||||||
|
process.waitFor();
|
||||||
|
try (var reader = process.getInputStream()) {
|
||||||
|
byte[] outputRaw = reader.readAllBytes();
|
||||||
|
String output = new String(outputRaw, StandardCharsets.UTF_8);
|
||||||
|
isMusl = output.contains("musl");
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
isMusl = false;
|
||||||
|
}
|
||||||
|
IS_MUSL_LIBC = isMusl;
|
||||||
|
} else {
|
||||||
|
IS_MUSL_LIBC = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static final BooleanSupplier NATIVE_BASE = () -> NATIVES_ENABLED && CAN_GET_MEMORYADDRESS;
|
static final BooleanSupplier NATIVE_BASE = () -> NATIVES_ENABLED && CAN_GET_MEMORYADDRESS;
|
||||||
|
|
||||||
static final BooleanSupplier LINUX_X86_64 = () -> NATIVE_BASE.getAsBoolean()
|
static final BooleanSupplier LINUX_X86_64 = () -> NATIVE_BASE.getAsBoolean()
|
||||||
&& System.getProperty("os.name", "").equalsIgnoreCase("Linux")
|
&& IS_LINUX && IS_AMD64 && !IS_MUSL_LIBC;
|
||||||
&& IS_AMD64;
|
|
||||||
|
static final BooleanSupplier LINUX_X86_64_MUSL = () -> NATIVE_BASE.getAsBoolean()
|
||||||
|
&& IS_LINUX && IS_AMD64 && IS_MUSL_LIBC;
|
||||||
|
|
||||||
static final BooleanSupplier LINUX_AARCH64 = () -> NATIVE_BASE.getAsBoolean()
|
static final BooleanSupplier LINUX_AARCH64 = () -> NATIVE_BASE.getAsBoolean()
|
||||||
&& System.getProperty("os.name", "").equalsIgnoreCase("Linux")
|
&& IS_LINUX && IS_AARCH64 && !IS_MUSL_LIBC;
|
||||||
&& IS_AARCH64;
|
|
||||||
|
static final BooleanSupplier LINUX_AARCH64_MUSL = () -> NATIVE_BASE.getAsBoolean()
|
||||||
|
&& IS_LINUX && IS_AARCH64 && IS_MUSL_LIBC;
|
||||||
|
|
||||||
static final BooleanSupplier MACOS_AARCH64 = () -> NATIVE_BASE.getAsBoolean()
|
static final BooleanSupplier MACOS_AARCH64 = () -> NATIVE_BASE.getAsBoolean()
|
||||||
&& System.getProperty("os.name", "").equalsIgnoreCase("Mac OS X")
|
&& System.getProperty("os.name", "").equalsIgnoreCase("Mac OS X")
|
||||||
|
|||||||
@@ -83,11 +83,21 @@ public class Natives {
|
|||||||
new NativeCodeLoader.Variant<>(NativeConstraints.LINUX_X86_64,
|
new NativeCodeLoader.Variant<>(NativeConstraints.LINUX_X86_64,
|
||||||
copyAndLoadNative("/linux_x86_64/velocity-compress.so"),
|
copyAndLoadNative("/linux_x86_64/velocity-compress.so"),
|
||||||
"libdeflate (Linux x86_64)",
|
"libdeflate (Linux x86_64)",
|
||||||
LibdeflateVelocityCompressor.FACTORY), // compiled with Debian 10
|
LibdeflateVelocityCompressor.FACTORY), // compiled with Ubuntu 20.04
|
||||||
|
new NativeCodeLoader.Variant<>(NativeConstraints.LINUX_X86_64_MUSL,
|
||||||
|
copyAndLoadNative("/linux_x86_64/velocity-compress-musl.so"),
|
||||||
|
"libdeflate (Linux x86_64, musl)",
|
||||||
|
LibdeflateVelocityCompressor.FACTORY), // compiled with Alpine 3.18
|
||||||
|
|
||||||
new NativeCodeLoader.Variant<>(NativeConstraints.LINUX_AARCH64,
|
new NativeCodeLoader.Variant<>(NativeConstraints.LINUX_AARCH64,
|
||||||
copyAndLoadNative("/linux_aarch64/velocity-compress.so"),
|
copyAndLoadNative("/linux_aarch64/velocity-compress.so"),
|
||||||
"libdeflate (Linux aarch64)",
|
"libdeflate (Linux aarch64)",
|
||||||
LibdeflateVelocityCompressor.FACTORY), // compiled with Fedora 36
|
LibdeflateVelocityCompressor.FACTORY), // compiled with Ubuntu 20.04
|
||||||
|
new NativeCodeLoader.Variant<>(NativeConstraints.LINUX_AARCH64_MUSL,
|
||||||
|
copyAndLoadNative("/linux_aarch64/velocity-compress-musl.so"),
|
||||||
|
"libdeflate (Linux aarch64, musl)",
|
||||||
|
LibdeflateVelocityCompressor.FACTORY), // compiled with Alpine 3.18
|
||||||
|
|
||||||
new NativeCodeLoader.Variant<>(NativeConstraints.MACOS_AARCH64,
|
new NativeCodeLoader.Variant<>(NativeConstraints.MACOS_AARCH64,
|
||||||
copyAndLoadNative("/macos_arm64/velocity-compress.dylib"),
|
copyAndLoadNative("/macos_arm64/velocity-compress.dylib"),
|
||||||
"libdeflate (macOS ARM64 / Apple Silicon)",
|
"libdeflate (macOS ARM64 / Apple Silicon)",
|
||||||
@@ -103,21 +113,27 @@ public class Natives {
|
|||||||
copyAndLoadNative("/linux_x86_64/velocity-cipher.so"), // Any local version
|
copyAndLoadNative("/linux_x86_64/velocity-cipher.so"), // Any local version
|
||||||
"OpenSSL local (Linux x86_64)", NativeVelocityCipher.FACTORY),
|
"OpenSSL local (Linux x86_64)", NativeVelocityCipher.FACTORY),
|
||||||
new NativeCodeLoader.Variant<>(NativeConstraints.LINUX_X86_64,
|
new NativeCodeLoader.Variant<>(NativeConstraints.LINUX_X86_64,
|
||||||
copyAndLoadNative("/linux_x86_64/velocity-cipher-ossl30x.so"), // Debian "Bookworm"
|
copyAndLoadNative("/linux_x86_64/velocity-cipher-ossl30x.so"), // Ubuntu 22.04
|
||||||
"OpenSSL 3.0.x (Linux x86_64)", NativeVelocityCipher.FACTORY),
|
"OpenSSL 3.x.x (Linux x86_64)", NativeVelocityCipher.FACTORY),
|
||||||
new NativeCodeLoader.Variant<>(NativeConstraints.LINUX_X86_64,
|
new NativeCodeLoader.Variant<>(NativeConstraints.LINUX_X86_64,
|
||||||
copyAndLoadNative("/linux_x86_64/velocity-cipher-ossl11x.so"), // Debian 9
|
copyAndLoadNative("/linux_x86_64/velocity-cipher-ossl11x.so"), // Ubuntu 20.04
|
||||||
"OpenSSL 1.1.x (Linux x86_64)", NativeVelocityCipher.FACTORY),
|
"OpenSSL 1.1.x (Linux x86_64)", NativeVelocityCipher.FACTORY),
|
||||||
|
new NativeCodeLoader.Variant<>(NativeConstraints.LINUX_X86_64_MUSL,
|
||||||
|
copyAndLoadNative("/linux_x86_64/velocity-cipher-ossl30x-musl.so"), // Alpine 3.18
|
||||||
|
"OpenSSL 3.x.x (Linux x86_64, musl)", NativeVelocityCipher.FACTORY),
|
||||||
|
|
||||||
new NativeCodeLoader.Variant<>(NativeConstraints.LINUX_AARCH64,
|
new NativeCodeLoader.Variant<>(NativeConstraints.LINUX_AARCH64,
|
||||||
copyAndLoadNative("/linux_aarch64/velocity-cipher.so"),
|
copyAndLoadNative("/linux_aarch64/velocity-cipher.so"),
|
||||||
"OpenSSL (Linux aarch64)", NativeVelocityCipher.FACTORY), // Any local version
|
"OpenSSL local (Linux aarch64)", NativeVelocityCipher.FACTORY), // Any local version
|
||||||
new NativeCodeLoader.Variant<>(NativeConstraints.LINUX_AARCH64,
|
new NativeCodeLoader.Variant<>(NativeConstraints.LINUX_AARCH64,
|
||||||
copyAndLoadNative("/linux_aarch64/velocity-cipher-ossl30x.so"),
|
copyAndLoadNative("/linux_aarch64/velocity-cipher-ossl30x.so"),
|
||||||
"OpenSSL (Linux aarch64)", NativeVelocityCipher.FACTORY), // Fedora 36
|
"OpenSSL 3.x.x (Linux aarch64)", NativeVelocityCipher.FACTORY), // Ubuntu 22.04
|
||||||
new NativeCodeLoader.Variant<>(NativeConstraints.LINUX_AARCH64,
|
new NativeCodeLoader.Variant<>(NativeConstraints.LINUX_AARCH64,
|
||||||
copyAndLoadNative("/linux_aarch64/velocity-cipher-ossl11x.so"),
|
copyAndLoadNative("/linux_aarch64/velocity-cipher-ossl11x.so"),
|
||||||
"OpenSSL 1.1.x (Linux aarch64)", NativeVelocityCipher.FACTORY), // Debian 11
|
"OpenSSL 1.1.x (Linux aarch64)", NativeVelocityCipher.FACTORY), // Ubuntu 20.04
|
||||||
|
new NativeCodeLoader.Variant<>(NativeConstraints.LINUX_AARCH64_MUSL,
|
||||||
|
copyAndLoadNative("/linux_aarch64/velocity-cipher-ossl30x-musl.so"),
|
||||||
|
"OpenSSL 3.x.x (Linux aarch64, musl)", NativeVelocityCipher.FACTORY), // Alpine 3.18
|
||||||
|
|
||||||
new NativeCodeLoader.Variant<>(NativeConstraints.MACOS_AARCH64,
|
new NativeCodeLoader.Variant<>(NativeConstraints.MACOS_AARCH64,
|
||||||
copyAndLoadNative("/macos_arm64/velocity-cipher.dylib"),
|
copyAndLoadNative("/macos_arm64/velocity-cipher.dylib"),
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+10
-1
@@ -92,6 +92,15 @@ tasks {
|
|||||||
dependsOn(configurateBuildTask)
|
dependsOn(configurateBuildTask)
|
||||||
from(zipTree(configurateBuildTask.map { it.outputs.files.singleFile }))
|
from(zipTree(configurateBuildTask.map { it.outputs.files.singleFile }))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
runShadow {
|
||||||
|
workingDir = file("run").also(File::mkdirs)
|
||||||
|
standardInput = System.`in`
|
||||||
|
}
|
||||||
|
named<JavaExec>("run") {
|
||||||
|
workingDir = file("run").also(File::mkdirs)
|
||||||
|
standardInput = System.`in` // Doesn't work?
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
@@ -121,7 +130,7 @@ dependencies {
|
|||||||
runtimeOnly(libs.disruptor)
|
runtimeOnly(libs.disruptor)
|
||||||
implementation(libs.fastutil)
|
implementation(libs.fastutil)
|
||||||
implementation(platform(libs.adventure.bom))
|
implementation(platform(libs.adventure.bom))
|
||||||
implementation("net.kyori:adventure-nbt")
|
implementation(libs.adventure.text.serializer.json.legacy.impl)
|
||||||
implementation(libs.adventure.facet)
|
implementation(libs.adventure.facet)
|
||||||
implementation(libs.completablefutures)
|
implementation(libs.completablefutures)
|
||||||
implementation(libs.nightconfig)
|
implementation(libs.nightconfig)
|
||||||
|
|||||||
@@ -17,11 +17,17 @@
|
|||||||
|
|
||||||
package com.velocitypowered.proxy;
|
package com.velocitypowered.proxy;
|
||||||
|
|
||||||
|
import com.velocitypowered.api.proxy.server.ServerInfo;
|
||||||
|
import com.velocitypowered.proxy.util.AddressUtil;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.net.InetSocketAddress;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
import joptsimple.OptionParser;
|
import joptsimple.OptionParser;
|
||||||
import joptsimple.OptionSet;
|
import joptsimple.OptionSet;
|
||||||
import joptsimple.OptionSpec;
|
import joptsimple.OptionSpec;
|
||||||
|
import joptsimple.ValueConversionException;
|
||||||
|
import joptsimple.ValueConverter;
|
||||||
import org.apache.logging.log4j.LogManager;
|
import org.apache.logging.log4j.LogManager;
|
||||||
import org.apache.logging.log4j.Logger;
|
import org.apache.logging.log4j.Logger;
|
||||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||||
@@ -35,6 +41,8 @@ public final class ProxyOptions {
|
|||||||
private final boolean help;
|
private final boolean help;
|
||||||
private final @Nullable Integer port;
|
private final @Nullable Integer port;
|
||||||
private final @Nullable Boolean haproxy;
|
private final @Nullable Boolean haproxy;
|
||||||
|
private final boolean ignoreConfigServers;
|
||||||
|
private final List<ServerInfo> servers;
|
||||||
|
|
||||||
ProxyOptions(final String[] args) {
|
ProxyOptions(final String[] args) {
|
||||||
final OptionParser parser = new OptionParser();
|
final OptionParser parser = new OptionParser();
|
||||||
@@ -49,11 +57,20 @@ public final class ProxyOptions {
|
|||||||
"Choose whether to enable haproxy protocol. "
|
"Choose whether to enable haproxy protocol. "
|
||||||
+ "The configuration haproxy protocol will be ignored.")
|
+ "The configuration haproxy protocol will be ignored.")
|
||||||
.withRequiredArg().ofType(Boolean.class);
|
.withRequiredArg().ofType(Boolean.class);
|
||||||
|
final OptionSpec<ServerInfo> servers = parser.accepts("add-server",
|
||||||
|
"Define a server mapping. "
|
||||||
|
+ "You must ensure that server name is not also registered in the config or use --ignore-config-servers.")
|
||||||
|
.withRequiredArg().withValuesConvertedBy(new ServerInfoConverter());
|
||||||
|
final OptionSpec<Void> ignoreConfigServers = parser.accepts("ignore-config-servers",
|
||||||
|
"Skip registering servers from the config file. "
|
||||||
|
+ "Useful in dynamic setups or with the --add-server flag.");
|
||||||
final OptionSet set = parser.parse(args);
|
final OptionSet set = parser.parse(args);
|
||||||
|
|
||||||
this.help = set.has(help);
|
this.help = set.has(help);
|
||||||
this.port = port.value(set);
|
this.port = port.value(set);
|
||||||
this.haproxy = haproxy.value(set);
|
this.haproxy = haproxy.value(set);
|
||||||
|
this.servers = servers.values(set);
|
||||||
|
this.ignoreConfigServers = set.has(ignoreConfigServers);
|
||||||
|
|
||||||
if (this.help) {
|
if (this.help) {
|
||||||
try {
|
try {
|
||||||
@@ -75,4 +92,40 @@ public final class ProxyOptions {
|
|||||||
public @Nullable Boolean isHaproxy() {
|
public @Nullable Boolean isHaproxy() {
|
||||||
return this.haproxy;
|
return this.haproxy;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isIgnoreConfigServers() {
|
||||||
|
return this.ignoreConfigServers;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<ServerInfo> getServers() {
|
||||||
|
return this.servers;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class ServerInfoConverter implements ValueConverter<ServerInfo> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ServerInfo convert(String s) {
|
||||||
|
String[] split = s.split(":", 2);
|
||||||
|
if (split.length < 2) {
|
||||||
|
throw new ValueConversionException("Invalid server format. Use <name>:<address>");
|
||||||
|
}
|
||||||
|
InetSocketAddress address;
|
||||||
|
try {
|
||||||
|
address = AddressUtil.parseAddress(split[1]);
|
||||||
|
} catch (IllegalStateException e) {
|
||||||
|
throw new ValueConversionException("Invalid hostname for server flag with name: " + split[0]);
|
||||||
|
}
|
||||||
|
return new ServerInfo(split[0], address);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Class<? extends ServerInfo> valueType() {
|
||||||
|
return ServerInfo.class;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String valuePattern() {
|
||||||
|
return "name>:<address";
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,11 +22,13 @@ import com.google.common.base.Preconditions;
|
|||||||
import com.google.common.collect.ImmutableList;
|
import com.google.common.collect.ImmutableList;
|
||||||
import com.google.gson.Gson;
|
import com.google.gson.Gson;
|
||||||
import com.google.gson.GsonBuilder;
|
import com.google.gson.GsonBuilder;
|
||||||
|
import com.velocitypowered.api.command.BrigadierCommand;
|
||||||
import com.velocitypowered.api.event.proxy.ProxyInitializeEvent;
|
import com.velocitypowered.api.event.proxy.ProxyInitializeEvent;
|
||||||
import com.velocitypowered.api.event.proxy.ProxyReloadEvent;
|
import com.velocitypowered.api.event.proxy.ProxyReloadEvent;
|
||||||
import com.velocitypowered.api.event.proxy.ProxyShutdownEvent;
|
import com.velocitypowered.api.event.proxy.ProxyShutdownEvent;
|
||||||
import com.velocitypowered.api.network.ProtocolVersion;
|
import com.velocitypowered.api.network.ProtocolVersion;
|
||||||
import com.velocitypowered.api.plugin.PluginContainer;
|
import com.velocitypowered.api.plugin.PluginContainer;
|
||||||
|
import com.velocitypowered.api.plugin.PluginDescription;
|
||||||
import com.velocitypowered.api.plugin.PluginManager;
|
import com.velocitypowered.api.plugin.PluginManager;
|
||||||
import com.velocitypowered.api.proxy.Player;
|
import com.velocitypowered.api.proxy.Player;
|
||||||
import com.velocitypowered.api.proxy.ProxyServer;
|
import com.velocitypowered.api.proxy.ProxyServer;
|
||||||
@@ -52,6 +54,9 @@ import com.velocitypowered.proxy.crypto.EncryptionUtils;
|
|||||||
import com.velocitypowered.proxy.event.VelocityEventManager;
|
import com.velocitypowered.proxy.event.VelocityEventManager;
|
||||||
import com.velocitypowered.proxy.network.ConnectionManager;
|
import com.velocitypowered.proxy.network.ConnectionManager;
|
||||||
import com.velocitypowered.proxy.plugin.VelocityPluginManager;
|
import com.velocitypowered.proxy.plugin.VelocityPluginManager;
|
||||||
|
import com.velocitypowered.proxy.plugin.loader.VelocityPluginContainer;
|
||||||
|
import com.velocitypowered.proxy.plugin.loader.VelocityPluginDescription;
|
||||||
|
import com.velocitypowered.proxy.plugin.virtual.VelocityVirtualPlugin;
|
||||||
import com.velocitypowered.proxy.protocol.ProtocolUtils;
|
import com.velocitypowered.proxy.protocol.ProtocolUtils;
|
||||||
import com.velocitypowered.proxy.protocol.util.FaviconSerializer;
|
import com.velocitypowered.proxy.protocol.util.FaviconSerializer;
|
||||||
import com.velocitypowered.proxy.protocol.util.GameProfileSerializer;
|
import com.velocitypowered.proxy.protocol.util.GameProfileSerializer;
|
||||||
@@ -77,6 +82,7 @@ import java.nio.file.Path;
|
|||||||
import java.security.KeyPair;
|
import java.security.KeyPair;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
@@ -100,6 +106,7 @@ import net.kyori.adventure.translation.GlobalTranslator;
|
|||||||
import net.kyori.adventure.translation.TranslationRegistry;
|
import net.kyori.adventure.translation.TranslationRegistry;
|
||||||
import org.apache.logging.log4j.LogManager;
|
import org.apache.logging.log4j.LogManager;
|
||||||
import org.apache.logging.log4j.Logger;
|
import org.apache.logging.log4j.Logger;
|
||||||
|
import org.bstats.MetricsBase;
|
||||||
import org.checkerframework.checker.nullness.qual.EnsuresNonNull;
|
import org.checkerframework.checker.nullness.qual.EnsuresNonNull;
|
||||||
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||||
@@ -110,6 +117,8 @@ import org.checkerframework.checker.nullness.qual.Nullable;
|
|||||||
*/
|
*/
|
||||||
public class VelocityServer implements ProxyServer, ForwardingAudience {
|
public class VelocityServer implements ProxyServer, ForwardingAudience {
|
||||||
|
|
||||||
|
public static final String VELOCITY_URL = "https://velocitypowered.com";
|
||||||
|
|
||||||
private static final Logger logger = LogManager.getLogger(VelocityServer.class);
|
private static final Logger logger = LogManager.getLogger(VelocityServer.class);
|
||||||
public static final Gson GENERAL_GSON = new GsonBuilder()
|
public static final Gson GENERAL_GSON = new GsonBuilder()
|
||||||
.registerTypeHierarchyAdapter(Favicon.class, FaviconSerializer.INSTANCE)
|
.registerTypeHierarchyAdapter(Favicon.class, FaviconSerializer.INSTANCE)
|
||||||
@@ -162,7 +171,7 @@ public class VelocityServer implements ProxyServer, ForwardingAudience {
|
|||||||
VelocityServer(final ProxyOptions options) {
|
VelocityServer(final ProxyOptions options) {
|
||||||
pluginManager = new VelocityPluginManager(this);
|
pluginManager = new VelocityPluginManager(this);
|
||||||
eventManager = new VelocityEventManager(pluginManager);
|
eventManager = new VelocityEventManager(pluginManager);
|
||||||
commandManager = new VelocityCommandManager(eventManager);
|
commandManager = new VelocityCommandManager(eventManager, pluginManager);
|
||||||
scheduler = new VelocityScheduler(pluginManager);
|
scheduler = new VelocityScheduler(pluginManager);
|
||||||
console = new VelocityConsole(this);
|
console = new VelocityConsole(this);
|
||||||
cm = new ConnectionManager(this);
|
cm = new ConnectionManager(this);
|
||||||
@@ -199,6 +208,16 @@ public class VelocityServer implements ProxyServer, ForwardingAudience {
|
|||||||
return new ProxyVersion(implName, implVendor, implVersion);
|
return new ProxyVersion(implName, implVendor, implVersion);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private VelocityPluginContainer createVirtualPlugin() {
|
||||||
|
ProxyVersion version = getVersion();
|
||||||
|
PluginDescription description = new VelocityPluginDescription(
|
||||||
|
"velocity", version.getName(), version.getVersion(), "The Velocity proxy",
|
||||||
|
VELOCITY_URL, ImmutableList.of(version.getVendor()), Collections.emptyList(), null);
|
||||||
|
VelocityPluginContainer container = new VelocityPluginContainer(description);
|
||||||
|
container.setInstance(VelocityVirtualPlugin.INSTANCE);
|
||||||
|
return container;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public VelocityCommandManager getCommandManager() {
|
public VelocityCommandManager getCommandManager() {
|
||||||
return commandManager;
|
return commandManager;
|
||||||
@@ -213,6 +232,7 @@ public class VelocityServer implements ProxyServer, ForwardingAudience {
|
|||||||
void start() {
|
void start() {
|
||||||
logger.info("Booting up {} {}...", getVersion().getName(), getVersion().getVersion());
|
logger.info("Booting up {} {}...", getVersion().getName(), getVersion().getVersion());
|
||||||
console.setupStreams();
|
console.setupStreams();
|
||||||
|
pluginManager.registerPlugin(this.createVirtualPlugin());
|
||||||
|
|
||||||
registerTranslations();
|
registerTranslations();
|
||||||
|
|
||||||
@@ -221,18 +241,48 @@ public class VelocityServer implements ProxyServer, ForwardingAudience {
|
|||||||
cm.logChannelInformation();
|
cm.logChannelInformation();
|
||||||
|
|
||||||
// Initialize commands first
|
// Initialize commands first
|
||||||
commandManager.register(VelocityCommand.create(this));
|
final BrigadierCommand velocityParentCommand = VelocityCommand.create(this);
|
||||||
commandManager.register(CallbackCommand.create());
|
commandManager.register(
|
||||||
commandManager.register(ServerCommand.create(this));
|
commandManager.metaBuilder(velocityParentCommand)
|
||||||
commandManager.register("shutdown", ShutdownCommand.command(this),
|
.plugin(VelocityVirtualPlugin.INSTANCE)
|
||||||
"end", "stop");
|
.build(),
|
||||||
|
velocityParentCommand
|
||||||
|
);
|
||||||
|
final BrigadierCommand callbackCommand = CallbackCommand.create();
|
||||||
|
commandManager.register(
|
||||||
|
commandManager.metaBuilder(callbackCommand)
|
||||||
|
.plugin(VelocityVirtualPlugin.INSTANCE)
|
||||||
|
.build(),
|
||||||
|
velocityParentCommand
|
||||||
|
);
|
||||||
|
final BrigadierCommand serverCommand = ServerCommand.create(this);
|
||||||
|
commandManager.register(
|
||||||
|
commandManager.metaBuilder(serverCommand)
|
||||||
|
.plugin(VelocityVirtualPlugin.INSTANCE)
|
||||||
|
.build(),
|
||||||
|
serverCommand
|
||||||
|
);
|
||||||
|
final BrigadierCommand shutdownCommand = ShutdownCommand.command(this);
|
||||||
|
commandManager.register(
|
||||||
|
commandManager.metaBuilder(shutdownCommand)
|
||||||
|
.plugin(VelocityVirtualPlugin.INSTANCE)
|
||||||
|
.aliases("end", "stop")
|
||||||
|
.build(),
|
||||||
|
shutdownCommand
|
||||||
|
);
|
||||||
new GlistCommand(this).register();
|
new GlistCommand(this).register();
|
||||||
new SendCommand(this).register();
|
new SendCommand(this).register();
|
||||||
|
|
||||||
this.doStartupConfigLoad();
|
this.doStartupConfigLoad();
|
||||||
|
|
||||||
for (Map.Entry<String, String> entry : configuration.getServers().entrySet()) {
|
for (ServerInfo cliServer : options.getServers()) {
|
||||||
servers.register(new ServerInfo(entry.getKey(), AddressUtil.parseAddress(entry.getValue())));
|
servers.register(cliServer);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!options.isIgnoreConfigServers()) {
|
||||||
|
for (Map.Entry<String, String> entry : configuration.getServers().entrySet()) {
|
||||||
|
servers.register(new ServerInfo(entry.getKey(), AddressUtil.parseAddress(entry.getValue())));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ipAttemptLimiter = Ratelimiters.createWithMilliseconds(configuration.getLoginRatelimit());
|
ipAttemptLimiter = Ratelimiters.createWithMilliseconds(configuration.getLoginRatelimit());
|
||||||
@@ -263,7 +313,13 @@ public class VelocityServer implements ProxyServer, ForwardingAudience {
|
|||||||
this.cm.queryBind(configuration.getBind().getHostString(), configuration.getQueryPort());
|
this.cm.queryBind(configuration.getBind().getHostString(), configuration.getQueryPort());
|
||||||
}
|
}
|
||||||
|
|
||||||
Metrics.VelocityMetrics.startMetrics(this, configuration.getMetrics());
|
final String defaultPackage = new String(
|
||||||
|
new byte[] { 'o', 'r', 'g', '.', 'b', 's', 't', 'a', 't', 's' });
|
||||||
|
if (!MetricsBase.class.getPackage().getName().startsWith(defaultPackage)) {
|
||||||
|
Metrics.VelocityMetrics.startMetrics(this, configuration.getMetrics());
|
||||||
|
} else {
|
||||||
|
logger.warn("debug environment, metrics is disabled!");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void registerTranslations() {
|
private void registerTranslations() {
|
||||||
@@ -533,7 +589,6 @@ public class VelocityServer implements ProxyServer, ForwardingAudience {
|
|||||||
|
|
||||||
eventManager.fire(new ProxyShutdownEvent()).join();
|
eventManager.fire(new ProxyShutdownEvent()).join();
|
||||||
|
|
||||||
timedOut = !eventManager.shutdown() || timedOut;
|
|
||||||
timedOut = !scheduler.shutdown() || timedOut;
|
timedOut = !scheduler.shutdown() || timedOut;
|
||||||
|
|
||||||
if (timedOut) {
|
if (timedOut) {
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ package com.velocitypowered.proxy.command;
|
|||||||
import com.google.common.base.Preconditions;
|
import com.google.common.base.Preconditions;
|
||||||
import com.google.common.collect.ImmutableList;
|
import com.google.common.collect.ImmutableList;
|
||||||
import com.google.common.collect.Lists;
|
import com.google.common.collect.Lists;
|
||||||
|
import com.google.common.util.concurrent.MoreExecutors;
|
||||||
import com.mojang.brigadier.CommandDispatcher;
|
import com.mojang.brigadier.CommandDispatcher;
|
||||||
import com.mojang.brigadier.Message;
|
import com.mojang.brigadier.Message;
|
||||||
import com.mojang.brigadier.ParseResults;
|
import com.mojang.brigadier.ParseResults;
|
||||||
@@ -37,11 +38,15 @@ import com.velocitypowered.api.command.CommandSource;
|
|||||||
import com.velocitypowered.api.command.VelocityBrigadierMessage;
|
import com.velocitypowered.api.command.VelocityBrigadierMessage;
|
||||||
import com.velocitypowered.api.event.command.CommandExecuteEvent;
|
import com.velocitypowered.api.event.command.CommandExecuteEvent;
|
||||||
import com.velocitypowered.api.event.command.PostCommandInvocationEvent;
|
import com.velocitypowered.api.event.command.PostCommandInvocationEvent;
|
||||||
|
import com.velocitypowered.api.plugin.PluginManager;
|
||||||
|
import com.velocitypowered.proxy.command.brigadier.VelocityBrigadierCommandWrapper;
|
||||||
import com.velocitypowered.proxy.command.registrar.BrigadierCommandRegistrar;
|
import com.velocitypowered.proxy.command.registrar.BrigadierCommandRegistrar;
|
||||||
import com.velocitypowered.proxy.command.registrar.CommandRegistrar;
|
import com.velocitypowered.proxy.command.registrar.CommandRegistrar;
|
||||||
import com.velocitypowered.proxy.command.registrar.RawCommandRegistrar;
|
import com.velocitypowered.proxy.command.registrar.RawCommandRegistrar;
|
||||||
import com.velocitypowered.proxy.command.registrar.SimpleCommandRegistrar;
|
import com.velocitypowered.proxy.command.registrar.SimpleCommandRegistrar;
|
||||||
import com.velocitypowered.proxy.event.VelocityEventManager;
|
import com.velocitypowered.proxy.event.VelocityEventManager;
|
||||||
|
import com.velocitypowered.proxy.plugin.virtual.VelocityVirtualPlugin;
|
||||||
|
import io.netty.util.concurrent.FastThreadLocalThread;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -49,6 +54,7 @@ import java.util.Locale;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.concurrent.Executor;
|
||||||
import java.util.concurrent.locks.ReadWriteLock;
|
import java.util.concurrent.locks.ReadWriteLock;
|
||||||
import java.util.concurrent.locks.ReentrantReadWriteLock;
|
import java.util.concurrent.locks.ReentrantReadWriteLock;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
@@ -71,13 +77,16 @@ public class VelocityCommandManager implements CommandManager {
|
|||||||
private final SuggestionsProvider<CommandSource> suggestionsProvider;
|
private final SuggestionsProvider<CommandSource> suggestionsProvider;
|
||||||
private final CommandGraphInjector<CommandSource> injector;
|
private final CommandGraphInjector<CommandSource> injector;
|
||||||
private final Map<String, CommandMeta> commandMetas;
|
private final Map<String, CommandMeta> commandMetas;
|
||||||
|
private final PluginManager pluginManager;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs a command manager.
|
* Constructs a command manager.
|
||||||
*
|
*
|
||||||
* @param eventManager the event manager
|
* @param eventManager the event manager
|
||||||
*/
|
*/
|
||||||
public VelocityCommandManager(final VelocityEventManager eventManager) {
|
public VelocityCommandManager(final VelocityEventManager eventManager,
|
||||||
|
PluginManager pluginManager) {
|
||||||
|
this.pluginManager = pluginManager;
|
||||||
this.lock = new ReentrantReadWriteLock();
|
this.lock = new ReentrantReadWriteLock();
|
||||||
this.dispatcher = new CommandDispatcher<>();
|
this.dispatcher = new CommandDispatcher<>();
|
||||||
this.eventManager = Preconditions.checkNotNull(eventManager);
|
this.eventManager = Preconditions.checkNotNull(eventManager);
|
||||||
@@ -218,16 +227,13 @@ public class VelocityCommandManager implements CommandManager {
|
|||||||
return eventManager.fire(new CommandExecuteEvent(source, cmdLine));
|
return eventManager.fire(new CommandExecuteEvent(source, cmdLine));
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean executeImmediately0(final CommandSource source, final String cmdLine) {
|
private boolean executeImmediately0(final CommandSource source, final ParseResults<CommandSource> parsed) {
|
||||||
Preconditions.checkNotNull(source, "source");
|
Preconditions.checkNotNull(source, "source");
|
||||||
Preconditions.checkNotNull(cmdLine, "cmdLine");
|
|
||||||
|
|
||||||
final String normalizedInput = VelocityCommands.normalizeInput(cmdLine, true);
|
|
||||||
CommandResult result = CommandResult.EXCEPTION;
|
CommandResult result = CommandResult.EXCEPTION;
|
||||||
try {
|
try {
|
||||||
// The parse can fail if the requirement predicates throw
|
// The parse can fail if the requirement predicates throw
|
||||||
final ParseResults<CommandSource> parse = this.parse(normalizedInput, source);
|
boolean executed = dispatcher.execute(parsed) != BrigadierCommand.FORWARD;
|
||||||
boolean executed = dispatcher.execute(parse) != BrigadierCommand.FORWARD;
|
|
||||||
result = executed ? CommandResult.EXECUTED : CommandResult.FORWARDED;
|
result = executed ? CommandResult.EXECUTED : CommandResult.FORWARDED;
|
||||||
return executed;
|
return executed;
|
||||||
} catch (final CommandSyntaxException e) {
|
} catch (final CommandSyntaxException e) {
|
||||||
@@ -249,9 +255,9 @@ public class VelocityCommandManager implements CommandManager {
|
|||||||
}
|
}
|
||||||
} catch (final Throwable e) {
|
} catch (final Throwable e) {
|
||||||
// Ugly, ugly swallowing of everything Throwable, because plugins are naughty.
|
// Ugly, ugly swallowing of everything Throwable, because plugins are naughty.
|
||||||
throw new RuntimeException("Unable to invoke command " + cmdLine + " for " + source, e);
|
throw new RuntimeException("Unable to invoke command " + parsed.getReader().getString() + "for " + source, e);
|
||||||
} finally {
|
} finally {
|
||||||
eventManager.fireAndForget(new PostCommandInvocationEvent(source, cmdLine, result));
|
eventManager.fireAndForget(new PostCommandInvocationEvent(source, parsed.getReader().getString(), result));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -260,13 +266,17 @@ public class VelocityCommandManager implements CommandManager {
|
|||||||
Preconditions.checkNotNull(source, "source");
|
Preconditions.checkNotNull(source, "source");
|
||||||
Preconditions.checkNotNull(cmdLine, "cmdLine");
|
Preconditions.checkNotNull(cmdLine, "cmdLine");
|
||||||
|
|
||||||
return callCommandEvent(source, cmdLine).thenApplyAsync(event -> {
|
return callCommandEvent(source, cmdLine).thenComposeAsync(event -> {
|
||||||
CommandExecuteEvent.CommandResult commandResult = event.getResult();
|
CommandExecuteEvent.CommandResult commandResult = event.getResult();
|
||||||
if (commandResult.isForwardToServer() || !commandResult.isAllowed()) {
|
if (commandResult.isForwardToServer() || !commandResult.isAllowed()) {
|
||||||
return false;
|
return CompletableFuture.completedFuture(false);
|
||||||
}
|
}
|
||||||
return executeImmediately0(source, commandResult.getCommand().orElse(event.getCommand()));
|
final ParseResults<CommandSource> parsed = this.parse(
|
||||||
}, eventManager.getAsyncExecutor());
|
commandResult.getCommand().orElse(cmdLine), source);
|
||||||
|
return CompletableFuture.supplyAsync(
|
||||||
|
() -> executeImmediately0(source, parsed), this.getAsyncExecutor(parsed)
|
||||||
|
);
|
||||||
|
}, figureAsyncExecutorForParsing());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -276,7 +286,12 @@ public class VelocityCommandManager implements CommandManager {
|
|||||||
Preconditions.checkNotNull(cmdLine, "cmdLine");
|
Preconditions.checkNotNull(cmdLine, "cmdLine");
|
||||||
|
|
||||||
return CompletableFuture.supplyAsync(
|
return CompletableFuture.supplyAsync(
|
||||||
() -> executeImmediately0(source, cmdLine), eventManager.getAsyncExecutor());
|
() -> this.parse(cmdLine, source), figureAsyncExecutorForParsing()
|
||||||
|
).thenCompose(
|
||||||
|
parsed -> CompletableFuture.supplyAsync(
|
||||||
|
() -> executeImmediately0(source, parsed), this.getAsyncExecutor(parsed)
|
||||||
|
)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -324,9 +339,10 @@ public class VelocityCommandManager implements CommandManager {
|
|||||||
* @return the parse results
|
* @return the parse results
|
||||||
*/
|
*/
|
||||||
private ParseResults<CommandSource> parse(final String input, final CommandSource source) {
|
private ParseResults<CommandSource> parse(final String input, final CommandSource source) {
|
||||||
|
final String normalizedInput = VelocityCommands.normalizeInput(input, true);
|
||||||
lock.readLock().lock();
|
lock.readLock().lock();
|
||||||
try {
|
try {
|
||||||
return dispatcher.parse(input, source);
|
return dispatcher.parse(normalizedInput, source);
|
||||||
} finally {
|
} finally {
|
||||||
lock.readLock().unlock();
|
lock.readLock().unlock();
|
||||||
}
|
}
|
||||||
@@ -370,4 +386,25 @@ public class VelocityCommandManager implements CommandManager {
|
|||||||
public CommandGraphInjector<CommandSource> getInjector() {
|
public CommandGraphInjector<CommandSource> getInjector() {
|
||||||
return injector;
|
return injector;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Executor getAsyncExecutor(ParseResults<CommandSource> parse) {
|
||||||
|
Object registrant;
|
||||||
|
if (parse.getContext().getCommand() instanceof VelocityBrigadierCommandWrapper vbcw) {
|
||||||
|
registrant = vbcw.registrant() == null ? VelocityVirtualPlugin.INSTANCE : vbcw.registrant();
|
||||||
|
} else {
|
||||||
|
registrant = VelocityVirtualPlugin.INSTANCE;
|
||||||
|
}
|
||||||
|
return pluginManager.ensurePluginContainer(registrant).getExecutorService();
|
||||||
|
}
|
||||||
|
|
||||||
|
private Executor figureAsyncExecutorForParsing() {
|
||||||
|
final Thread thread = Thread.currentThread();
|
||||||
|
if (thread instanceof FastThreadLocalThread) {
|
||||||
|
// we *never* want to block the Netty event loop, so use the async executor
|
||||||
|
return pluginManager.ensurePluginContainer(VelocityVirtualPlugin.INSTANCE).getExecutorService();
|
||||||
|
} else {
|
||||||
|
// it's some other thread that isn't a Netty event loop thread. direct execution it is!
|
||||||
|
return MoreExecutors.directExecutor();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -24,6 +24,7 @@ import com.mojang.brigadier.context.CommandContext;
|
|||||||
import com.mojang.brigadier.context.CommandContextBuilder;
|
import com.mojang.brigadier.context.CommandContextBuilder;
|
||||||
import com.mojang.brigadier.context.ParsedArgument;
|
import com.mojang.brigadier.context.ParsedArgument;
|
||||||
import com.mojang.brigadier.context.ParsedCommandNode;
|
import com.mojang.brigadier.context.ParsedCommandNode;
|
||||||
|
import com.mojang.brigadier.tree.ArgumentCommandNode;
|
||||||
import com.mojang.brigadier.tree.CommandNode;
|
import com.mojang.brigadier.tree.CommandNode;
|
||||||
import com.mojang.brigadier.tree.LiteralCommandNode;
|
import com.mojang.brigadier.tree.LiteralCommandNode;
|
||||||
import com.mojang.brigadier.tree.RootCommandNode;
|
import com.mojang.brigadier.tree.RootCommandNode;
|
||||||
@@ -32,6 +33,7 @@ import com.velocitypowered.api.command.CommandManager;
|
|||||||
import com.velocitypowered.api.command.CommandSource;
|
import com.velocitypowered.api.command.CommandSource;
|
||||||
import com.velocitypowered.api.command.InvocableCommand;
|
import com.velocitypowered.api.command.InvocableCommand;
|
||||||
import com.velocitypowered.proxy.command.brigadier.VelocityArgumentCommandNode;
|
import com.velocitypowered.proxy.command.brigadier.VelocityArgumentCommandNode;
|
||||||
|
import com.velocitypowered.proxy.command.brigadier.VelocityBrigadierCommandWrapper;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@@ -44,6 +46,59 @@ import org.checkerframework.checker.nullness.qual.Nullable;
|
|||||||
*/
|
*/
|
||||||
public final class VelocityCommands {
|
public final class VelocityCommands {
|
||||||
|
|
||||||
|
// Wrapping
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Walks the command node tree and wraps all {@link Command} instances in a {@link VelocityBrigadierCommandWrapper},
|
||||||
|
* to indicate the plugin that registered the command. This also has the side effect of cloning
|
||||||
|
* the command node tree.
|
||||||
|
*
|
||||||
|
* @param delegate the command node to wrap
|
||||||
|
* @param registrant the plugin that registered the command
|
||||||
|
* @return the wrapped command node
|
||||||
|
*/
|
||||||
|
public static CommandNode<CommandSource> wrap(final CommandNode<CommandSource> delegate,
|
||||||
|
final @Nullable Object registrant) {
|
||||||
|
Preconditions.checkNotNull(delegate, "delegate");
|
||||||
|
if (registrant == null) {
|
||||||
|
// the registrant is null if the `plugin` was absent when we try to register the command
|
||||||
|
return delegate;
|
||||||
|
}
|
||||||
|
|
||||||
|
com.mojang.brigadier.Command<CommandSource> maybeCommand = delegate.getCommand();
|
||||||
|
if (maybeCommand != null && !(maybeCommand instanceof VelocityBrigadierCommandWrapper)) {
|
||||||
|
maybeCommand = VelocityBrigadierCommandWrapper.wrap(delegate.getCommand(), registrant);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (delegate instanceof LiteralCommandNode<CommandSource> lcn) {
|
||||||
|
var literalBuilder = shallowCopyAsBuilder(lcn, delegate.getName(), true);
|
||||||
|
literalBuilder.executes(maybeCommand);
|
||||||
|
// we also need to wrap any children
|
||||||
|
for (final CommandNode<CommandSource> child : delegate.getChildren()) {
|
||||||
|
literalBuilder.then(wrap(child, registrant));
|
||||||
|
}
|
||||||
|
if (delegate.getRedirect() != null) {
|
||||||
|
literalBuilder.redirect(wrap(delegate.getRedirect(), registrant));
|
||||||
|
}
|
||||||
|
return literalBuilder.build();
|
||||||
|
} else if (delegate instanceof VelocityArgumentCommandNode<CommandSource, ?> vacn) {
|
||||||
|
return vacn.withCommand(maybeCommand)
|
||||||
|
.withRedirect(delegate.getRedirect() != null ? wrap(delegate.getRedirect(), registrant) : null);
|
||||||
|
} else if (delegate instanceof ArgumentCommandNode) {
|
||||||
|
var argBuilder = delegate.createBuilder().executes(maybeCommand);
|
||||||
|
// we also need to wrap any children
|
||||||
|
for (final CommandNode<CommandSource> child : delegate.getChildren()) {
|
||||||
|
argBuilder.then(wrap(child, registrant));
|
||||||
|
}
|
||||||
|
if (delegate.getRedirect() != null) {
|
||||||
|
argBuilder.redirect(wrap(delegate.getRedirect(), registrant));
|
||||||
|
}
|
||||||
|
return argBuilder.build();
|
||||||
|
} else {
|
||||||
|
throw new IllegalArgumentException("Unsupported node type: " + delegate.getClass());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Normalization
|
// Normalization
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -135,6 +190,33 @@ public final class VelocityCommands {
|
|||||||
*/
|
*/
|
||||||
public static LiteralCommandNode<CommandSource> shallowCopy(
|
public static LiteralCommandNode<CommandSource> shallowCopy(
|
||||||
final LiteralCommandNode<CommandSource> original, final String newName) {
|
final LiteralCommandNode<CommandSource> original, final String newName) {
|
||||||
|
return shallowCopy(original, newName, original.getCommand());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a copy of the given literal with the specified name.
|
||||||
|
*
|
||||||
|
* @param original the literal node to copy
|
||||||
|
* @param newName the name of the returned literal node
|
||||||
|
* @param newCommand the new command to set on the copied node
|
||||||
|
* @return a copy of the literal with the given name
|
||||||
|
*/
|
||||||
|
private static LiteralCommandNode<CommandSource> shallowCopy(
|
||||||
|
final LiteralCommandNode<CommandSource> original, final String newName,
|
||||||
|
final com.mojang.brigadier.Command<CommandSource> newCommand) {
|
||||||
|
return shallowCopyAsBuilder(original, newName, false).executes(newCommand).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a copy of the given literal with the specified name.
|
||||||
|
*
|
||||||
|
* @param original the literal node to copy
|
||||||
|
* @param newName the name of the returned literal node
|
||||||
|
* @return a copy of the literal with the given name
|
||||||
|
*/
|
||||||
|
private static LiteralArgumentBuilder<CommandSource> shallowCopyAsBuilder(
|
||||||
|
final LiteralCommandNode<CommandSource> original, final String newName,
|
||||||
|
final boolean skipChildren) {
|
||||||
// Brigadier resolves the redirect of a node if further input can be parsed.
|
// Brigadier resolves the redirect of a node if further input can be parsed.
|
||||||
// Let <bar> be a literal node having a redirect to a <foo> literal. Then,
|
// Let <bar> be a literal node having a redirect to a <foo> literal. Then,
|
||||||
// the context returned by CommandDispatcher#parseNodes when given the input
|
// the context returned by CommandDispatcher#parseNodes when given the input
|
||||||
@@ -150,10 +232,12 @@ public final class VelocityCommands {
|
|||||||
.requiresWithContext(original.getContextRequirement())
|
.requiresWithContext(original.getContextRequirement())
|
||||||
.forward(original.getRedirect(), original.getRedirectModifier(), original.isFork())
|
.forward(original.getRedirect(), original.getRedirectModifier(), original.isFork())
|
||||||
.executes(original.getCommand());
|
.executes(original.getCommand());
|
||||||
for (final CommandNode<CommandSource> child : original.getChildren()) {
|
if (!skipChildren) {
|
||||||
builder.then(child);
|
for (final CommandNode<CommandSource> child : original.getChildren()) {
|
||||||
|
builder.then(child);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return builder.build();
|
return builder;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Arguments node
|
// Arguments node
|
||||||
|
|||||||
+10
@@ -93,6 +93,16 @@ public class VelocityArgumentCommandNode<S, T> extends ArgumentCommandNode<S, St
|
|||||||
throw new UnsupportedOperationException();
|
throw new UnsupportedOperationException();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public VelocityArgumentCommandNode<S, T> withCommand(Command<S> command) {
|
||||||
|
return new VelocityArgumentCommandNode<>(getName(), type, command, getRequirement(),
|
||||||
|
getContextRequirement(), getRedirect(), getRedirectModifier(), isFork(), getCustomSuggestions());
|
||||||
|
}
|
||||||
|
|
||||||
|
public VelocityArgumentCommandNode<S, T> withRedirect(CommandNode<S> target) {
|
||||||
|
return new VelocityArgumentCommandNode<>(getName(), type, getCommand(), getRequirement(),
|
||||||
|
getContextRequirement(), target, getRedirectModifier(), isFork(), getCustomSuggestions());
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isValidInput(final String input) {
|
public boolean isValidInput(final String input) {
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
+67
@@ -0,0 +1,67 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2024 Velocity Contributors
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.velocitypowered.proxy.command.brigadier;
|
||||||
|
|
||||||
|
import com.mojang.brigadier.Command;
|
||||||
|
import com.mojang.brigadier.context.CommandContext;
|
||||||
|
import com.mojang.brigadier.exceptions.CommandSyntaxException;
|
||||||
|
import com.velocitypowered.api.command.CommandSource;
|
||||||
|
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wraps a Brigadier command to allow us to track the registrant.
|
||||||
|
*/
|
||||||
|
public class VelocityBrigadierCommandWrapper implements Command<CommandSource> {
|
||||||
|
|
||||||
|
private final Command<CommandSource> delegate;
|
||||||
|
private final Object registrant;
|
||||||
|
|
||||||
|
private VelocityBrigadierCommandWrapper(Command<CommandSource> delegate, Object registrant) {
|
||||||
|
this.delegate = delegate;
|
||||||
|
this.registrant = registrant;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transforms the given command into a {@code VelocityBrigadierCommandWrapper} if the registrant
|
||||||
|
* is not null and if the command is not already wrapped.
|
||||||
|
*
|
||||||
|
* @param delegate the command to wrap
|
||||||
|
* @param registrant the registrant of the command
|
||||||
|
* @return the wrapped command, if necessary
|
||||||
|
*/
|
||||||
|
public static Command<CommandSource> wrap(Command<CommandSource> delegate, @Nullable Object registrant) {
|
||||||
|
if (registrant == null) {
|
||||||
|
// nothing to wrap
|
||||||
|
return delegate;
|
||||||
|
}
|
||||||
|
if (delegate instanceof VelocityBrigadierCommandWrapper) {
|
||||||
|
// already wrapped
|
||||||
|
return delegate;
|
||||||
|
}
|
||||||
|
return new VelocityBrigadierCommandWrapper(delegate, registrant);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int run(CommandContext<CommandSource> context) throws CommandSyntaxException {
|
||||||
|
return delegate.run(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Object registrant() {
|
||||||
|
return registrant;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -31,6 +31,7 @@ import com.velocitypowered.api.permission.Tristate;
|
|||||||
import com.velocitypowered.api.proxy.Player;
|
import com.velocitypowered.api.proxy.Player;
|
||||||
import com.velocitypowered.api.proxy.ProxyServer;
|
import com.velocitypowered.api.proxy.ProxyServer;
|
||||||
import com.velocitypowered.api.proxy.server.RegisteredServer;
|
import com.velocitypowered.api.proxy.server.RegisteredServer;
|
||||||
|
import com.velocitypowered.proxy.plugin.virtual.VelocityVirtualPlugin;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import net.kyori.adventure.text.Component;
|
import net.kyori.adventure.text.Component;
|
||||||
@@ -80,7 +81,13 @@ public class GlistCommand {
|
|||||||
.executes(this::serverCount)
|
.executes(this::serverCount)
|
||||||
.build();
|
.build();
|
||||||
rootNode.then(serverNode);
|
rootNode.then(serverNode);
|
||||||
server.getCommandManager().register(new BrigadierCommand(rootNode));
|
final BrigadierCommand command = new BrigadierCommand(rootNode);
|
||||||
|
server.getCommandManager().register(
|
||||||
|
server.getCommandManager().metaBuilder(command)
|
||||||
|
.plugin(VelocityVirtualPlugin.INSTANCE)
|
||||||
|
.build(),
|
||||||
|
command
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private int totalCount(final CommandContext<CommandSource> context) {
|
private int totalCount(final CommandContext<CommandSource> context) {
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ import com.velocitypowered.api.proxy.Player;
|
|||||||
import com.velocitypowered.api.proxy.ProxyServer;
|
import com.velocitypowered.api.proxy.ProxyServer;
|
||||||
import com.velocitypowered.api.proxy.ServerConnection;
|
import com.velocitypowered.api.proxy.ServerConnection;
|
||||||
import com.velocitypowered.api.proxy.server.RegisteredServer;
|
import com.velocitypowered.api.proxy.server.RegisteredServer;
|
||||||
|
import com.velocitypowered.proxy.plugin.virtual.VelocityVirtualPlugin;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import net.kyori.adventure.text.Component;
|
import net.kyori.adventure.text.Component;
|
||||||
@@ -96,7 +97,13 @@ public class SendCommand {
|
|||||||
.build();
|
.build();
|
||||||
playerNode.then(serverNode);
|
playerNode.then(serverNode);
|
||||||
rootNode.then(playerNode.build());
|
rootNode.then(playerNode.build());
|
||||||
server.getCommandManager().register(new BrigadierCommand(rootNode.build()));
|
final BrigadierCommand command = new BrigadierCommand(rootNode);
|
||||||
|
server.getCommandManager().register(
|
||||||
|
server.getCommandManager().metaBuilder(command)
|
||||||
|
.plugin(VelocityVirtualPlugin.INSTANCE)
|
||||||
|
.build(),
|
||||||
|
command
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private int usage(final CommandContext<CommandSource> context) {
|
private int usage(final CommandContext<CommandSource> context) {
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ import com.mojang.brigadier.builder.LiteralArgumentBuilder;
|
|||||||
import com.mojang.brigadier.builder.RequiredArgumentBuilder;
|
import com.mojang.brigadier.builder.RequiredArgumentBuilder;
|
||||||
import com.velocitypowered.api.command.BrigadierCommand;
|
import com.velocitypowered.api.command.BrigadierCommand;
|
||||||
import com.velocitypowered.api.command.CommandSource;
|
import com.velocitypowered.api.command.CommandSource;
|
||||||
|
import com.velocitypowered.api.proxy.ConsoleCommandSource;
|
||||||
import com.velocitypowered.proxy.VelocityServer;
|
import com.velocitypowered.proxy.VelocityServer;
|
||||||
import net.kyori.adventure.text.minimessage.MiniMessage;
|
import net.kyori.adventure.text.minimessage.MiniMessage;
|
||||||
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
|
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
|
||||||
@@ -43,7 +44,7 @@ public final class ShutdownCommand {
|
|||||||
*/
|
*/
|
||||||
public static BrigadierCommand command(final VelocityServer server) {
|
public static BrigadierCommand command(final VelocityServer server) {
|
||||||
return new BrigadierCommand(LiteralArgumentBuilder.<CommandSource>literal("shutdown")
|
return new BrigadierCommand(LiteralArgumentBuilder.<CommandSource>literal("shutdown")
|
||||||
.requires(source -> source == server.getConsoleCommandSource())
|
.requires(source -> source instanceof ConsoleCommandSource)
|
||||||
.executes(context -> {
|
.executes(context -> {
|
||||||
server.shutdown(true);
|
server.shutdown(true);
|
||||||
return Command.SINGLE_SUCCESS;
|
return Command.SINGLE_SUCCESS;
|
||||||
|
|||||||
@@ -174,10 +174,10 @@ public final class VelocityCommand {
|
|||||||
if (version.getName().equals("Velocity")) {
|
if (version.getName().equals("Velocity")) {
|
||||||
final TextComponent embellishment = Component.text()
|
final TextComponent embellishment = Component.text()
|
||||||
.append(Component.text()
|
.append(Component.text()
|
||||||
.content("velocitypowered.com")
|
.content("PaperMC")
|
||||||
.color(NamedTextColor.GREEN)
|
.color(NamedTextColor.GREEN)
|
||||||
.clickEvent(
|
.clickEvent(
|
||||||
ClickEvent.openUrl("https://velocitypowered.com"))
|
ClickEvent.openUrl("https://papermc.io/software/velocity"))
|
||||||
.build())
|
.build())
|
||||||
.append(Component.text(" - "))
|
.append(Component.text(" - "))
|
||||||
.append(Component.text()
|
.append(Component.text()
|
||||||
|
|||||||
+4
-2
@@ -40,17 +40,19 @@ public final class BrigadierCommandRegistrar extends AbstractCommandRegistrar<Br
|
|||||||
// Register it (if valid), since it's probably what the user expects.
|
// Register it (if valid), since it's probably what the user expects.
|
||||||
// If invalid, the metadata contains the same alias, but in lowercase.
|
// If invalid, the metadata contains the same alias, but in lowercase.
|
||||||
final LiteralCommandNode<CommandSource> literal = command.getNode();
|
final LiteralCommandNode<CommandSource> literal = command.getNode();
|
||||||
|
final LiteralCommandNode<CommandSource> wrapped =
|
||||||
|
(LiteralCommandNode<CommandSource>) VelocityCommands.wrap(literal, meta.getPlugin());
|
||||||
final String primaryAlias = literal.getName();
|
final String primaryAlias = literal.getName();
|
||||||
if (VelocityCommands.isValidAlias(primaryAlias)) {
|
if (VelocityCommands.isValidAlias(primaryAlias)) {
|
||||||
// Register directly without copying
|
// Register directly without copying
|
||||||
this.register(literal);
|
this.register(wrapped);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (final String alias : meta.getAliases()) {
|
for (final String alias : meta.getAliases()) {
|
||||||
if (primaryAlias.equals(alias)) {
|
if (primaryAlias.equals(alias)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
this.register(literal, alias);
|
this.register(wrapped, alias);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Brigadier commands don't support hinting, ignore
|
// Brigadier commands don't support hinting, ignore
|
||||||
|
|||||||
+3
-2
@@ -32,6 +32,7 @@ import com.velocitypowered.api.command.InvocableCommand;
|
|||||||
import com.velocitypowered.proxy.command.VelocityCommandMeta;
|
import com.velocitypowered.proxy.command.VelocityCommandMeta;
|
||||||
import com.velocitypowered.proxy.command.VelocityCommands;
|
import com.velocitypowered.proxy.command.VelocityCommands;
|
||||||
import com.velocitypowered.proxy.command.brigadier.VelocityArgumentBuilder;
|
import com.velocitypowered.proxy.command.brigadier.VelocityArgumentBuilder;
|
||||||
|
import com.velocitypowered.proxy.command.brigadier.VelocityBrigadierCommandWrapper;
|
||||||
import com.velocitypowered.proxy.command.invocation.CommandInvocationFactory;
|
import com.velocitypowered.proxy.command.invocation.CommandInvocationFactory;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.concurrent.locks.Lock;
|
import java.util.concurrent.locks.Lock;
|
||||||
@@ -76,11 +77,11 @@ abstract class InvocableCommandRegistrar<T extends InvocableCommand<I>,
|
|||||||
final I invocation = invocationFactory.create(context);
|
final I invocation = invocationFactory.create(context);
|
||||||
return command.hasPermission(invocation);
|
return command.hasPermission(invocation);
|
||||||
};
|
};
|
||||||
final Command<CommandSource> callback = context -> {
|
final Command<CommandSource> callback = VelocityBrigadierCommandWrapper.wrap(context -> {
|
||||||
final I invocation = invocationFactory.create(context);
|
final I invocation = invocationFactory.create(context);
|
||||||
command.execute(invocation);
|
command.execute(invocation);
|
||||||
return 1; // handled
|
return 1; // handled
|
||||||
};
|
}, meta.getPlugin());
|
||||||
|
|
||||||
final LiteralCommandNode<CommandSource> literal = LiteralArgumentBuilder
|
final LiteralCommandNode<CommandSource> literal = LiteralArgumentBuilder
|
||||||
.<CommandSource>literal(alias)
|
.<CommandSource>literal(alias)
|
||||||
|
|||||||
-11
@@ -53,8 +53,6 @@ import java.util.HashMap;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
import net.kyori.adventure.nbt.CompoundBinaryTag;
|
|
||||||
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
|
||||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
@@ -72,7 +70,6 @@ public class VelocityServerConnection implements MinecraftConnectionAssociation,
|
|||||||
private boolean gracefulDisconnect = false;
|
private boolean gracefulDisconnect = false;
|
||||||
private BackendConnectionPhase connectionPhase = BackendConnectionPhases.UNKNOWN;
|
private BackendConnectionPhase connectionPhase = BackendConnectionPhases.UNKNOWN;
|
||||||
private final Map<Long, Long> pendingPings = new HashMap<>();
|
private final Map<Long, Long> pendingPings = new HashMap<>();
|
||||||
private @MonotonicNonNull CompoundBinaryTag activeDimensionRegistry;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initializes a new server connection.
|
* Initializes a new server connection.
|
||||||
@@ -366,12 +363,4 @@ public class VelocityServerConnection implements MinecraftConnectionAssociation,
|
|||||||
public boolean hasCompletedJoin() {
|
public boolean hasCompletedJoin() {
|
||||||
return hasCompletedJoin;
|
return hasCompletedJoin;
|
||||||
}
|
}
|
||||||
|
|
||||||
public CompoundBinaryTag getActiveDimensionRegistry() {
|
|
||||||
return activeDimensionRegistry;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setActiveDimensionRegistry(CompoundBinaryTag activeDimensionRegistry) {
|
|
||||||
this.activeDimensionRegistry = activeDimensionRegistry;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -96,7 +96,7 @@ public class AuthSessionHandler implements MinecraftSessionHandler {
|
|||||||
|
|
||||||
// Initiate a regular connection and move over to it.
|
// Initiate a regular connection and move over to it.
|
||||||
ConnectedPlayer player = new ConnectedPlayer(server, profileEvent.getGameProfile(),
|
ConnectedPlayer player = new ConnectedPlayer(server, profileEvent.getGameProfile(),
|
||||||
mcConnection, inbound.getVirtualHost().orElse(null), onlineMode,
|
mcConnection, inbound.getVirtualHost().orElse(null), inbound.getRawVirtualHost().orElse(null), onlineMode,
|
||||||
inbound.getIdentifiedKey());
|
inbound.getIdentifiedKey());
|
||||||
this.connectedPlayer = player;
|
this.connectedPlayer = player;
|
||||||
if (!server.canRegisterConnection(player)) {
|
if (!server.canRegisterConnection(player)) {
|
||||||
|
|||||||
-2
@@ -547,8 +547,6 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
destination.setActiveDimensionRegistry(joinGame.getRegistry()); // 1.16
|
|
||||||
|
|
||||||
// Remove previous boss bars. These don't get cleared when sending JoinGame, thus the need to
|
// Remove previous boss bars. These don't get cleared when sending JoinGame, thus the need to
|
||||||
// track them.
|
// track them.
|
||||||
for (UUID serverBossBar : serverBossBars) {
|
for (UUID serverBossBar : serverBossBars) {
|
||||||
|
|||||||
+20
-6
@@ -30,7 +30,7 @@ import org.checkerframework.checker.nullness.qual.Nullable;
|
|||||||
public class ClientSettingsWrapper implements PlayerSettings {
|
public class ClientSettingsWrapper implements PlayerSettings {
|
||||||
|
|
||||||
static final PlayerSettings DEFAULT = new ClientSettingsWrapper(
|
static final PlayerSettings DEFAULT = new ClientSettingsWrapper(
|
||||||
new ClientSettingsPacket("en_US", (byte) 10, 0, true, (short) 127, 1, true, false));
|
new ClientSettingsPacket("en_us", (byte) 2, 0, true, (short) 0, 1, false, false, 0));
|
||||||
|
|
||||||
private final ClientSettingsPacket settings;
|
private final ClientSettingsPacket settings;
|
||||||
private final SkinParts parts;
|
private final SkinParts parts;
|
||||||
@@ -56,11 +56,11 @@ public class ClientSettingsWrapper implements PlayerSettings {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ChatMode getChatMode() {
|
public ChatMode getChatMode() {
|
||||||
int chat = settings.getChatVisibility();
|
return switch (settings.getChatVisibility()) {
|
||||||
if (chat < 0 || chat > 2) {
|
case 1 -> ChatMode.COMMANDS_ONLY;
|
||||||
return ChatMode.SHOWN;
|
case 2 -> ChatMode.HIDDEN;
|
||||||
}
|
default -> ChatMode.SHOWN;
|
||||||
return ChatMode.values()[chat];
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -83,6 +83,20 @@ public class ClientSettingsWrapper implements PlayerSettings {
|
|||||||
return settings.isClientListingAllowed();
|
return settings.isClientListingAllowed();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isTextFilteringEnabled() {
|
||||||
|
return settings.isTextFilteringEnabled();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ParticleStatus getParticleStatus() {
|
||||||
|
return switch (settings.getParticleStatus()) {
|
||||||
|
case 1 -> ParticleStatus.DECREASED;
|
||||||
|
case 2 -> ParticleStatus.MINIMAL;
|
||||||
|
default -> ParticleStatus.ALL;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(@Nullable final Object o) {
|
public boolean equals(@Nullable final Object o) {
|
||||||
if (this == o) {
|
if (this == o) {
|
||||||
|
|||||||
+8
-1
@@ -155,6 +155,7 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player,
|
|||||||
*/
|
*/
|
||||||
private final MinecraftConnection connection;
|
private final MinecraftConnection connection;
|
||||||
private final @Nullable InetSocketAddress virtualHost;
|
private final @Nullable InetSocketAddress virtualHost;
|
||||||
|
private final @Nullable String rawVirtualHost;
|
||||||
private GameProfile profile;
|
private GameProfile profile;
|
||||||
private PermissionFunction permissionFunction;
|
private PermissionFunction permissionFunction;
|
||||||
private int tryIndex = 0;
|
private int tryIndex = 0;
|
||||||
@@ -191,12 +192,13 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player,
|
|||||||
private final ChatBuilderFactory chatBuilderFactory;
|
private final ChatBuilderFactory chatBuilderFactory;
|
||||||
|
|
||||||
ConnectedPlayer(VelocityServer server, GameProfile profile, MinecraftConnection connection,
|
ConnectedPlayer(VelocityServer server, GameProfile profile, MinecraftConnection connection,
|
||||||
@Nullable InetSocketAddress virtualHost, boolean onlineMode,
|
@Nullable InetSocketAddress virtualHost, @Nullable String rawVirtualHost, boolean onlineMode,
|
||||||
@Nullable IdentifiedKey playerKey) {
|
@Nullable IdentifiedKey playerKey) {
|
||||||
this.server = server;
|
this.server = server;
|
||||||
this.profile = profile;
|
this.profile = profile;
|
||||||
this.connection = connection;
|
this.connection = connection;
|
||||||
this.virtualHost = virtualHost;
|
this.virtualHost = virtualHost;
|
||||||
|
this.rawVirtualHost = rawVirtualHost;
|
||||||
this.permissionFunction = PermissionFunction.ALWAYS_UNDEFINED;
|
this.permissionFunction = PermissionFunction.ALWAYS_UNDEFINED;
|
||||||
this.connectionPhase = connection.getType().getInitialClientPhase();
|
this.connectionPhase = connection.getType().getInitialClientPhase();
|
||||||
this.onlineMode = onlineMode;
|
this.onlineMode = onlineMode;
|
||||||
@@ -356,6 +358,11 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player,
|
|||||||
return Optional.ofNullable(virtualHost);
|
return Optional.ofNullable(virtualHost);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Optional<String> getRawVirtualHost() {
|
||||||
|
return Optional.ofNullable(rawVirtualHost);
|
||||||
|
}
|
||||||
|
|
||||||
void setPermissionFunction(PermissionFunction permissionFunction) {
|
void setPermissionFunction(PermissionFunction permissionFunction) {
|
||||||
this.permissionFunction = permissionFunction;
|
this.permissionFunction = permissionFunction;
|
||||||
}
|
}
|
||||||
|
|||||||
+5
@@ -243,6 +243,11 @@ public class HandshakeSessionHandler implements MinecraftSessionHandler {
|
|||||||
return Optional.ofNullable(ping.getVhost());
|
return Optional.ofNullable(ping.getVhost());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Optional<String> getRawVirtualHost() {
|
||||||
|
return getVirtualHost().map(InetSocketAddress::getHostName);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isActive() {
|
public boolean isActive() {
|
||||||
return !connection.isClosed();
|
return !connection.isClosed();
|
||||||
|
|||||||
+5
@@ -63,6 +63,11 @@ public final class InitialInboundConnection implements VelocityInboundConnection
|
|||||||
return Optional.of(InetSocketAddress.createUnresolved(cleanedAddress, handshake.getPort()));
|
return Optional.of(InetSocketAddress.createUnresolved(cleanedAddress, handshake.getPort()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Optional<String> getRawVirtualHost() {
|
||||||
|
return Optional.of(handshake.getServerAddress());
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isActive() {
|
public boolean isActive() {
|
||||||
return connection.getChannel().isActive();
|
return connection.getChannel().isActive();
|
||||||
|
|||||||
+7
-2
@@ -30,9 +30,9 @@ import io.netty.buffer.ByteBufUtil;
|
|||||||
import io.netty.buffer.Unpooled;
|
import io.netty.buffer.Unpooled;
|
||||||
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
|
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
|
||||||
import java.net.InetSocketAddress;
|
import java.net.InetSocketAddress;
|
||||||
import java.util.ArrayDeque;
|
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.Queue;
|
import java.util.Queue;
|
||||||
|
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||||
import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
|
import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
|
||||||
import net.kyori.adventure.text.Component;
|
import net.kyori.adventure.text.Component;
|
||||||
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||||
@@ -58,7 +58,7 @@ public class LoginInboundConnection implements LoginPhaseConnection, KeyIdentifi
|
|||||||
InitialInboundConnection delegate) {
|
InitialInboundConnection delegate) {
|
||||||
this.delegate = delegate;
|
this.delegate = delegate;
|
||||||
this.outstandingResponses = Int2ObjectSyncMap.hashmap();
|
this.outstandingResponses = Int2ObjectSyncMap.hashmap();
|
||||||
this.loginMessagesToSend = new ArrayDeque<>();
|
this.loginMessagesToSend = new ConcurrentLinkedQueue<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -71,6 +71,11 @@ public class LoginInboundConnection implements LoginPhaseConnection, KeyIdentifi
|
|||||||
return delegate.getVirtualHost();
|
return delegate.getVirtualHost();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Optional<String> getRawVirtualHost() {
|
||||||
|
return delegate.getRawVirtualHost();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isActive() {
|
public boolean isActive() {
|
||||||
return delegate.isActive();
|
return delegate.isActive();
|
||||||
|
|||||||
@@ -33,8 +33,9 @@ class EventTypeTracker {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public Collection<Class<?>> getFriendsOf(final Class<?> eventType) {
|
public Collection<Class<?>> getFriendsOf(final Class<?> eventType) {
|
||||||
if (friends.containsKey(eventType)) {
|
ImmutableSet<Class<?>> existingFriends = friends.get(eventType);
|
||||||
return friends.get(eventType);
|
if (existingFriends != null) {
|
||||||
|
return existingFriends;
|
||||||
}
|
}
|
||||||
|
|
||||||
final Collection<Class<?>> types = getEventTypes(eventType);
|
final Collection<Class<?>> types = getEventTypes(eventType);
|
||||||
|
|||||||
@@ -25,7 +25,6 @@ import com.google.common.base.VerifyException;
|
|||||||
import com.google.common.collect.ArrayListMultimap;
|
import com.google.common.collect.ArrayListMultimap;
|
||||||
import com.google.common.collect.ListMultimap;
|
import com.google.common.collect.ListMultimap;
|
||||||
import com.google.common.reflect.TypeToken;
|
import com.google.common.reflect.TypeToken;
|
||||||
import com.google.common.util.concurrent.ThreadFactoryBuilder;
|
|
||||||
import com.velocitypowered.api.event.Continuation;
|
import com.velocitypowered.api.event.Continuation;
|
||||||
import com.velocitypowered.api.event.EventHandler;
|
import com.velocitypowered.api.event.EventHandler;
|
||||||
import com.velocitypowered.api.event.EventManager;
|
import com.velocitypowered.api.event.EventManager;
|
||||||
@@ -38,6 +37,7 @@ import com.velocitypowered.api.plugin.PluginManager;
|
|||||||
import com.velocitypowered.proxy.event.UntargetedEventHandler.EventTaskHandler;
|
import com.velocitypowered.proxy.event.UntargetedEventHandler.EventTaskHandler;
|
||||||
import com.velocitypowered.proxy.event.UntargetedEventHandler.VoidHandler;
|
import com.velocitypowered.proxy.event.UntargetedEventHandler.VoidHandler;
|
||||||
import com.velocitypowered.proxy.event.UntargetedEventHandler.WithContinuationHandler;
|
import com.velocitypowered.proxy.event.UntargetedEventHandler.WithContinuationHandler;
|
||||||
|
import com.velocitypowered.proxy.util.collect.Enum2IntMap;
|
||||||
import java.lang.invoke.MethodHandle;
|
import java.lang.invoke.MethodHandle;
|
||||||
import java.lang.invoke.MethodHandles;
|
import java.lang.invoke.MethodHandles;
|
||||||
import java.lang.invoke.VarHandle;
|
import java.lang.invoke.VarHandle;
|
||||||
@@ -55,9 +55,6 @@ import java.util.List;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
import java.util.concurrent.ExecutorService;
|
|
||||||
import java.util.concurrent.Executors;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
import java.util.concurrent.locks.ReadWriteLock;
|
import java.util.concurrent.locks.ReadWriteLock;
|
||||||
import java.util.concurrent.locks.ReentrantReadWriteLock;
|
import java.util.concurrent.locks.ReentrantReadWriteLock;
|
||||||
import java.util.function.BiConsumer;
|
import java.util.function.BiConsumer;
|
||||||
@@ -76,6 +73,14 @@ import org.lanternpowered.lmbda.LambdaType;
|
|||||||
*/
|
*/
|
||||||
public class VelocityEventManager implements EventManager {
|
public class VelocityEventManager implements EventManager {
|
||||||
|
|
||||||
|
private static final Enum2IntMap<PostOrder> POST_ORDER_MAP = new Enum2IntMap.Builder<>(PostOrder.class)
|
||||||
|
.put(PostOrder.FIRST, Short.MAX_VALUE - 1)
|
||||||
|
.put(PostOrder.EARLY, Short.MAX_VALUE / 2)
|
||||||
|
.put(PostOrder.NORMAL, 0)
|
||||||
|
.put(PostOrder.LATE, Short.MIN_VALUE / 2)
|
||||||
|
.put(PostOrder.LAST, Short.MIN_VALUE + 1)
|
||||||
|
.put(PostOrder.CUSTOM, 0)
|
||||||
|
.build();
|
||||||
private static final Logger logger = LogManager.getLogger(VelocityEventManager.class);
|
private static final Logger logger = LogManager.getLogger(VelocityEventManager.class);
|
||||||
|
|
||||||
private static final MethodHandles.Lookup methodHandlesLookup = MethodHandles.lookup();
|
private static final MethodHandles.Lookup methodHandlesLookup = MethodHandles.lookup();
|
||||||
@@ -87,9 +92,8 @@ public class VelocityEventManager implements EventManager {
|
|||||||
LambdaType.of(WithContinuationHandler.class);
|
LambdaType.of(WithContinuationHandler.class);
|
||||||
|
|
||||||
private static final Comparator<HandlerRegistration> handlerComparator =
|
private static final Comparator<HandlerRegistration> handlerComparator =
|
||||||
Comparator.comparingInt(o -> o.order);
|
Collections.reverseOrder(Comparator.comparingInt(o -> o.order));
|
||||||
|
|
||||||
private final ExecutorService asyncExecutor;
|
|
||||||
private final PluginManager pluginManager;
|
private final PluginManager pluginManager;
|
||||||
|
|
||||||
private final ListMultimap<Class<?>, HandlerRegistration> handlersByType =
|
private final ListMultimap<Class<?>, HandlerRegistration> handlersByType =
|
||||||
@@ -112,9 +116,6 @@ public class VelocityEventManager implements EventManager {
|
|||||||
*/
|
*/
|
||||||
public VelocityEventManager(final PluginManager pluginManager) {
|
public VelocityEventManager(final PluginManager pluginManager) {
|
||||||
this.pluginManager = pluginManager;
|
this.pluginManager = pluginManager;
|
||||||
this.asyncExecutor = Executors
|
|
||||||
.newFixedThreadPool(Runtime.getRuntime().availableProcessors(), new ThreadFactoryBuilder()
|
|
||||||
.setNameFormat("Velocity Async Event Executor - #%d").setDaemon(true).build());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -140,6 +141,7 @@ public class VelocityEventManager implements EventManager {
|
|||||||
final short order;
|
final short order;
|
||||||
final Class<?> eventType;
|
final Class<?> eventType;
|
||||||
final EventHandler<Object> handler;
|
final EventHandler<Object> handler;
|
||||||
|
final AsyncType asyncType;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The instance of the {@link EventHandler} or the listener instance that was registered.
|
* The instance of the {@link EventHandler} or the listener instance that was registered.
|
||||||
@@ -147,31 +149,40 @@ public class VelocityEventManager implements EventManager {
|
|||||||
final Object instance;
|
final Object instance;
|
||||||
|
|
||||||
public HandlerRegistration(final PluginContainer plugin, final short order,
|
public HandlerRegistration(final PluginContainer plugin, final short order,
|
||||||
final Class<?> eventType, final Object instance, final EventHandler<Object> handler) {
|
final Class<?> eventType, final Object instance, final EventHandler<Object> handler,
|
||||||
|
final AsyncType asyncType) {
|
||||||
this.plugin = plugin;
|
this.plugin = plugin;
|
||||||
this.order = order;
|
this.order = order;
|
||||||
this.eventType = eventType;
|
this.eventType = eventType;
|
||||||
this.instance = instance;
|
this.instance = instance;
|
||||||
this.handler = handler;
|
this.handler = handler;
|
||||||
|
this.asyncType = asyncType;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
enum AsyncType {
|
enum AsyncType {
|
||||||
/**
|
|
||||||
* The complete event will be handled on an async thread.
|
|
||||||
*/
|
|
||||||
ALWAYS,
|
|
||||||
/**
|
/**
|
||||||
* The event will never run async, everything is handled on the netty thread.
|
* The event will never run async, everything is handled on the netty thread.
|
||||||
*/
|
*/
|
||||||
NEVER
|
NEVER,
|
||||||
|
/**
|
||||||
|
* The event will initially start on the thread calling the {@code fire} method, and possibly
|
||||||
|
* switch over to an async thread.
|
||||||
|
*/
|
||||||
|
SOMETIMES,
|
||||||
|
/**
|
||||||
|
* The complete event will be handled on an async thread.
|
||||||
|
*/
|
||||||
|
ALWAYS
|
||||||
}
|
}
|
||||||
|
|
||||||
static final class HandlersCache {
|
static final class HandlersCache {
|
||||||
|
|
||||||
|
final AsyncType asyncType;
|
||||||
final HandlerRegistration[] handlers;
|
final HandlerRegistration[] handlers;
|
||||||
|
|
||||||
HandlersCache(final HandlerRegistration[] handlers) {
|
HandlersCache(AsyncType asyncType, final HandlerRegistration[] handlers) {
|
||||||
|
this.asyncType = asyncType;
|
||||||
this.handlers = handlers;
|
this.handlers = handlers;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -194,7 +205,15 @@ public class VelocityEventManager implements EventManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
baked.sort(handlerComparator);
|
baked.sort(handlerComparator);
|
||||||
return new HandlersCache(baked.toArray(new HandlerRegistration[0]));
|
|
||||||
|
AsyncType asyncType = AsyncType.NEVER;
|
||||||
|
for (HandlerRegistration registration : baked) {
|
||||||
|
if (registration.asyncType.compareTo(asyncType) > 0) {
|
||||||
|
asyncType = registration.asyncType;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new HandlersCache(asyncType, baked.toArray(new HandlerRegistration[0]));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -230,15 +249,17 @@ public class VelocityEventManager implements EventManager {
|
|||||||
static final class MethodHandlerInfo {
|
static final class MethodHandlerInfo {
|
||||||
|
|
||||||
final Method method;
|
final Method method;
|
||||||
|
final AsyncType asyncType;
|
||||||
final @Nullable Class<?> eventType;
|
final @Nullable Class<?> eventType;
|
||||||
final short order;
|
final short order;
|
||||||
final @Nullable String errors;
|
final @Nullable String errors;
|
||||||
final @Nullable Class<?> continuationType;
|
final @Nullable Class<?> continuationType;
|
||||||
|
|
||||||
private MethodHandlerInfo(final Method method, final @Nullable Class<?> eventType,
|
private MethodHandlerInfo(final Method method, final AsyncType asyncType,
|
||||||
final short order, final @Nullable String errors,
|
final @Nullable Class<?> eventType, final short order, final @Nullable String errors,
|
||||||
final @Nullable Class<?> continuationType) {
|
final @Nullable Class<?> continuationType) {
|
||||||
this.method = method;
|
this.method = method;
|
||||||
|
this.asyncType = asyncType;
|
||||||
this.eventType = eventType;
|
this.eventType = eventType;
|
||||||
this.order = order;
|
this.order = order;
|
||||||
this.errors = errors;
|
this.errors = errors;
|
||||||
@@ -302,17 +323,41 @@ public class VelocityEventManager implements EventManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
AsyncType asyncType = AsyncType.NEVER;
|
||||||
|
final Class<?> returnType = method.getReturnType();
|
||||||
if (handlerAdapter == null) {
|
if (handlerAdapter == null) {
|
||||||
final Class<?> returnType = method.getReturnType();
|
|
||||||
if (returnType != void.class && continuationType == Continuation.class) {
|
if (returnType != void.class && continuationType == Continuation.class) {
|
||||||
errors.add("method return type must be void if a continuation parameter is provided");
|
errors.add("method return type must be void if a continuation parameter is provided");
|
||||||
} else if (returnType != void.class && returnType != EventTask.class) {
|
} else if (returnType != void.class && returnType != EventTask.class) {
|
||||||
errors.add("method return type must be void or EventTask");
|
errors.add("method return type must be void, AsyncTask, "
|
||||||
|
+ "EventTask.Basic or EventTask.WithContinuation");
|
||||||
|
} else if (returnType == EventTask.class) {
|
||||||
|
// technically, for compatibility, we *should* assume that the method must be invoked
|
||||||
|
// async, however, from examining some publicly-available plugins, developers did
|
||||||
|
// generally follow the contract and returned an EventTask only if they wanted this
|
||||||
|
// behavior. enable it for them.
|
||||||
|
asyncType = AsyncType.SOMETIMES;
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
// for custom handlers, we always expect a return type of EventTask. this feature appears
|
||||||
|
// to have not been used in the wild AFAIK, so it gets the new behavior by default
|
||||||
|
asyncType = AsyncType.SOMETIMES;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (paramCount == 1 && returnType == void.class && subscribe.async()) {
|
||||||
|
// these are almost always a dead giveaway of a plugin that will need its handlers
|
||||||
|
// run async, so unless we're told otherwise, we'll assume that's the case
|
||||||
|
asyncType = AsyncType.ALWAYS;
|
||||||
|
}
|
||||||
|
|
||||||
|
final short order;
|
||||||
|
if (subscribe.order() == PostOrder.CUSTOM) {
|
||||||
|
order = subscribe.priority();
|
||||||
|
} else {
|
||||||
|
order = (short) POST_ORDER_MAP.get(subscribe.order());
|
||||||
}
|
}
|
||||||
final short order = (short) subscribe.order().ordinal();
|
|
||||||
final String errorsJoined = errors.isEmpty() ? null : String.join(",", errors);
|
final String errorsJoined = errors.isEmpty() ? null : String.join(",", errors);
|
||||||
collected.put(key, new MethodHandlerInfo(method, eventType, order, errorsJoined,
|
collected.put(key, new MethodHandlerInfo(method, asyncType, eventType, order, errorsJoined,
|
||||||
continuationType));
|
continuationType));
|
||||||
}
|
}
|
||||||
final Class<?> superclass = targetClass.getSuperclass();
|
final Class<?> superclass = targetClass.getSuperclass();
|
||||||
@@ -351,12 +396,29 @@ public class VelocityEventManager implements EventManager {
|
|||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
public <E> void register(final Object plugin, final Class<E> eventClass,
|
public <E> void register(final Object plugin, final Class<E> eventClass,
|
||||||
final PostOrder order, final EventHandler<E> handler) {
|
final PostOrder order, final EventHandler<E> handler) {
|
||||||
|
if (order == PostOrder.CUSTOM) {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
"This method does not support custom post orders. Use the overload with short instead."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
register(plugin, eventClass, (short) POST_ORDER_MAP.get(order), handler, AsyncType.ALWAYS);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <E> void register(Object plugin, Class<E> eventClass, short postOrder,
|
||||||
|
EventHandler<E> handler) {
|
||||||
|
register(plugin, eventClass, postOrder, handler, AsyncType.SOMETIMES);
|
||||||
|
}
|
||||||
|
|
||||||
|
private <E> void register(Object plugin, Class<E> eventClass, short postOrder,
|
||||||
|
EventHandler<E> handler, AsyncType asyncType) {
|
||||||
final PluginContainer pluginContainer = pluginManager.ensurePluginContainer(plugin);
|
final PluginContainer pluginContainer = pluginManager.ensurePluginContainer(plugin);
|
||||||
requireNonNull(eventClass, "eventClass");
|
requireNonNull(eventClass, "eventClass");
|
||||||
requireNonNull(handler, "handler");
|
requireNonNull(handler, "handler");
|
||||||
|
|
||||||
final HandlerRegistration registration = new HandlerRegistration(pluginContainer,
|
final HandlerRegistration registration = new HandlerRegistration(pluginContainer,
|
||||||
(short) order.ordinal(), eventClass, handler, (EventHandler<Object>) handler);
|
postOrder, eventClass, handler, (EventHandler<Object>) handler,
|
||||||
|
AsyncType.ALWAYS);
|
||||||
register(Collections.singletonList(registration));
|
register(Collections.singletonList(registration));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -386,7 +448,7 @@ public class VelocityEventManager implements EventManager {
|
|||||||
|
|
||||||
final EventHandler<Object> handler = untargetedHandler.buildHandler(listener);
|
final EventHandler<Object> handler = untargetedHandler.buildHandler(listener);
|
||||||
registrations.add(new HandlerRegistration(pluginContainer, info.order,
|
registrations.add(new HandlerRegistration(pluginContainer, info.order,
|
||||||
info.eventType, listener, handler));
|
info.eventType, listener, handler, info.asyncType));
|
||||||
}
|
}
|
||||||
|
|
||||||
register(registrations);
|
register(registrations);
|
||||||
@@ -473,10 +535,13 @@ public class VelocityEventManager implements EventManager {
|
|||||||
|
|
||||||
private <E> void fire(final @Nullable CompletableFuture<E> future,
|
private <E> void fire(final @Nullable CompletableFuture<E> future,
|
||||||
final E event, final HandlersCache handlersCache) {
|
final E event, final HandlersCache handlersCache) {
|
||||||
// In Velocity 1.1.0, all events were fired asynchronously. As Velocity 3.0.0 is intended to be
|
final HandlerRegistration registration = handlersCache.handlers[0];
|
||||||
// largely (albeit not 100%) compatible with 1.1.x, we also fire events async. This behavior
|
if (registration.asyncType == AsyncType.ALWAYS) {
|
||||||
// will go away in Velocity Polymer.
|
registration.plugin.getExecutorService().execute(
|
||||||
asyncExecutor.execute(() -> fire(future, event, 0, true, handlersCache.handlers));
|
() -> fire(future, event, 0, true, handlersCache.handlers));
|
||||||
|
} else {
|
||||||
|
fire(future, event, 0, false, handlersCache.handlers);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final int TASK_STATE_DEFAULT = 0;
|
private static final int TASK_STATE_DEFAULT = 0;
|
||||||
@@ -505,6 +570,7 @@ public class VelocityEventManager implements EventManager {
|
|||||||
private final @Nullable CompletableFuture<E> future;
|
private final @Nullable CompletableFuture<E> future;
|
||||||
private final boolean currentlyAsync;
|
private final boolean currentlyAsync;
|
||||||
private final E event;
|
private final E event;
|
||||||
|
private final Thread firedOnThread;
|
||||||
|
|
||||||
// This field is modified via a VarHandle, so this field is used and cannot be final.
|
// This field is modified via a VarHandle, so this field is used and cannot be final.
|
||||||
@SuppressWarnings({"UnusedVariable", "FieldMayBeFinal", "FieldCanBeLocal"})
|
@SuppressWarnings({"UnusedVariable", "FieldMayBeFinal", "FieldCanBeLocal"})
|
||||||
@@ -527,6 +593,7 @@ public class VelocityEventManager implements EventManager {
|
|||||||
this.event = event;
|
this.event = event;
|
||||||
this.index = index;
|
this.index = index;
|
||||||
this.currentlyAsync = currentlyAsync;
|
this.currentlyAsync = currentlyAsync;
|
||||||
|
this.firedOnThread = Thread.currentThread();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -537,8 +604,8 @@ public class VelocityEventManager implements EventManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Executes the task and returns whether the next one should be executed immediately after this
|
* Executes the task and returns whether the next handler should be executed immediately
|
||||||
* one without scheduling.
|
* after this one, without additional scheduling.
|
||||||
*/
|
*/
|
||||||
boolean execute() {
|
boolean execute() {
|
||||||
state = TASK_STATE_EXECUTING;
|
state = TASK_STATE_EXECUTING;
|
||||||
@@ -580,7 +647,18 @@ public class VelocityEventManager implements EventManager {
|
|||||||
}
|
}
|
||||||
if (!CONTINUATION_TASK_STATE.compareAndSet(
|
if (!CONTINUATION_TASK_STATE.compareAndSet(
|
||||||
this, TASK_STATE_EXECUTING, TASK_STATE_CONTINUE_IMMEDIATELY)) {
|
this, TASK_STATE_EXECUTING, TASK_STATE_CONTINUE_IMMEDIATELY)) {
|
||||||
asyncExecutor.execute(() -> fire(future, event, index + 1, true, registrations));
|
// We established earlier that registrations[index + 1] is a valid index.
|
||||||
|
// If we are remaining in the same thread for the next handler, fire
|
||||||
|
// the next event immediately, else fire it within the executor service
|
||||||
|
// of the plugin with the next handler.
|
||||||
|
final HandlerRegistration next = registrations[index + 1];
|
||||||
|
final Thread currentThread = Thread.currentThread();
|
||||||
|
if (currentThread == firedOnThread && next.asyncType != AsyncType.ALWAYS) {
|
||||||
|
fire(future, event, index + 1, currentlyAsync, registrations);
|
||||||
|
} else {
|
||||||
|
next.plugin.getExecutorService().execute(() ->
|
||||||
|
fire(future, event, index + 1, true, registrations));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -606,7 +684,7 @@ public class VelocityEventManager implements EventManager {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
asyncExecutor.execute(continuationTask);
|
registration.plugin.getExecutorService().execute(continuationTask);
|
||||||
}
|
}
|
||||||
// fire will continue in another thread once the async task is
|
// fire will continue in another thread once the async task is
|
||||||
// executed and the continuation is resumed
|
// executed and the continuation is resumed
|
||||||
@@ -626,13 +704,4 @@ public class VelocityEventManager implements EventManager {
|
|||||||
logger.error("Couldn't pass {} to {} {}", registration.eventType.getSimpleName(),
|
logger.error("Couldn't pass {} to {} {}", registration.eventType.getSimpleName(),
|
||||||
pluginDescription.getId(), pluginDescription.getVersion().orElse(""), t);
|
pluginDescription.getId(), pluginDescription.getVersion().orElse(""), t);
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean shutdown() throws InterruptedException {
|
|
||||||
asyncExecutor.shutdown();
|
|
||||||
return asyncExecutor.awaitTermination(10, TimeUnit.SECONDS);
|
|
||||||
}
|
|
||||||
|
|
||||||
public ExecutorService getAsyncExecutor() {
|
|
||||||
return asyncExecutor;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
+1
-1
@@ -49,7 +49,7 @@ public class BackendChannelInitializer extends ChannelInitializer<Channel> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void initChannel(Channel ch) throws Exception {
|
protected void initChannel(Channel ch) {
|
||||||
ch.pipeline()
|
ch.pipeline()
|
||||||
.addLast(FRAME_DECODER, new MinecraftVarintFrameDecoder())
|
.addLast(FRAME_DECODER, new MinecraftVarintFrameDecoder())
|
||||||
.addLast(READ_TIMEOUT,
|
.addLast(READ_TIMEOUT,
|
||||||
|
|||||||
@@ -109,6 +109,12 @@ public final class ConnectionManager {
|
|||||||
final Channel channel = future.channel();
|
final Channel channel = future.channel();
|
||||||
if (future.isSuccess()) {
|
if (future.isSuccess()) {
|
||||||
this.endpoints.put(address, new Endpoint(channel, ListenerType.MINECRAFT));
|
this.endpoints.put(address, new Endpoint(channel, ListenerType.MINECRAFT));
|
||||||
|
|
||||||
|
// 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());
|
||||||
|
}
|
||||||
|
|
||||||
LOGGER.info("Listening on {}", channel.localAddress());
|
LOGGER.info("Listening on {}", channel.localAddress());
|
||||||
|
|
||||||
// Fire the proxy bound event after the socket is bound
|
// Fire the proxy bound event after the socket is bound
|
||||||
|
|||||||
@@ -68,7 +68,12 @@ public class VelocityPluginManager implements PluginManager {
|
|||||||
this.server = checkNotNull(server, "server");
|
this.server = checkNotNull(server, "server");
|
||||||
}
|
}
|
||||||
|
|
||||||
private void registerPlugin(PluginContainer plugin) {
|
/**
|
||||||
|
* Registers a plugin with the plugin manager.
|
||||||
|
*
|
||||||
|
* @param plugin the plugin to register
|
||||||
|
*/
|
||||||
|
public void registerPlugin(PluginContainer plugin) {
|
||||||
pluginsById.put(plugin.getDescription().getId(), plugin);
|
pluginsById.put(plugin.getDescription().getId(), plugin);
|
||||||
Optional<?> instance = plugin.getInstance();
|
Optional<?> instance = plugin.getInstance();
|
||||||
instance.ifPresent(o -> pluginInstances.put(o, plugin));
|
instance.ifPresent(o -> pluginInstances.put(o, plugin));
|
||||||
|
|||||||
+29
@@ -0,0 +1,29 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2024 Velocity Contributors
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.velocitypowered.proxy.plugin.virtual;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A singleton plugin object that represents the Velocity proxy itself.
|
||||||
|
*/
|
||||||
|
public class VelocityVirtualPlugin {
|
||||||
|
@SuppressWarnings("InstantiationOfUtilityClass")
|
||||||
|
public static final VelocityVirtualPlugin INSTANCE = new VelocityVirtualPlugin();
|
||||||
|
|
||||||
|
private VelocityVirtualPlugin() {
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -25,7 +25,6 @@ import com.velocitypowered.api.proxy.crypto.IdentifiedKey;
|
|||||||
import com.velocitypowered.api.util.GameProfile;
|
import com.velocitypowered.api.util.GameProfile;
|
||||||
import com.velocitypowered.proxy.crypto.IdentifiedKeyImpl;
|
import com.velocitypowered.proxy.crypto.IdentifiedKeyImpl;
|
||||||
import com.velocitypowered.proxy.protocol.netty.MinecraftDecoder;
|
import com.velocitypowered.proxy.protocol.netty.MinecraftDecoder;
|
||||||
import com.velocitypowered.proxy.protocol.util.VelocityLegacyHoverEventSerializer;
|
|
||||||
import com.velocitypowered.proxy.util.except.QuietDecoderException;
|
import com.velocitypowered.proxy.util.except.QuietDecoderException;
|
||||||
import io.netty.buffer.ByteBuf;
|
import io.netty.buffer.ByteBuf;
|
||||||
import io.netty.buffer.ByteBufInputStream;
|
import io.netty.buffer.ByteBufInputStream;
|
||||||
@@ -47,6 +46,7 @@ import net.kyori.adventure.nbt.BinaryTagTypes;
|
|||||||
import net.kyori.adventure.nbt.CompoundBinaryTag;
|
import net.kyori.adventure.nbt.CompoundBinaryTag;
|
||||||
import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer;
|
import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer;
|
||||||
import net.kyori.adventure.text.serializer.json.JSONOptions;
|
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.OptionState;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -58,8 +58,7 @@ public enum ProtocolUtils {
|
|||||||
private static final GsonComponentSerializer PRE_1_16_SERIALIZER =
|
private static final GsonComponentSerializer PRE_1_16_SERIALIZER =
|
||||||
GsonComponentSerializer.builder()
|
GsonComponentSerializer.builder()
|
||||||
.downsampleColors()
|
.downsampleColors()
|
||||||
.emitLegacyHoverEvent()
|
.legacyHoverEventSerializer(NBTLegacyHoverEventSerializer.get())
|
||||||
.legacyHoverEventSerializer(VelocityLegacyHoverEventSerializer.INSTANCE)
|
|
||||||
.options(
|
.options(
|
||||||
OptionState.optionState()
|
OptionState.optionState()
|
||||||
// before 1.16
|
// before 1.16
|
||||||
@@ -74,7 +73,7 @@ public enum ProtocolUtils {
|
|||||||
.build();
|
.build();
|
||||||
private static final GsonComponentSerializer PRE_1_20_3_SERIALIZER =
|
private static final GsonComponentSerializer PRE_1_20_3_SERIALIZER =
|
||||||
GsonComponentSerializer.builder()
|
GsonComponentSerializer.builder()
|
||||||
.legacyHoverEventSerializer(VelocityLegacyHoverEventSerializer.INSTANCE)
|
.legacyHoverEventSerializer(NBTLegacyHoverEventSerializer.get())
|
||||||
.options(
|
.options(
|
||||||
OptionState.optionState()
|
OptionState.optionState()
|
||||||
// after 1.16
|
// after 1.16
|
||||||
@@ -89,7 +88,7 @@ public enum ProtocolUtils {
|
|||||||
.build();
|
.build();
|
||||||
private static final GsonComponentSerializer MODERN_SERIALIZER =
|
private static final GsonComponentSerializer MODERN_SERIALIZER =
|
||||||
GsonComponentSerializer.builder()
|
GsonComponentSerializer.builder()
|
||||||
.legacyHoverEventSerializer(VelocityLegacyHoverEventSerializer.INSTANCE)
|
.legacyHoverEventSerializer(NBTLegacyHoverEventSerializer.get())
|
||||||
.options(
|
.options(
|
||||||
OptionState.optionState()
|
OptionState.optionState()
|
||||||
// after 1.16
|
// after 1.16
|
||||||
@@ -104,6 +103,7 @@ public enum ProtocolUtils {
|
|||||||
.build();
|
.build();
|
||||||
|
|
||||||
public static final int DEFAULT_MAX_STRING_SIZE = 65536; // 64KiB
|
public static final int DEFAULT_MAX_STRING_SIZE = 65536; // 64KiB
|
||||||
|
private static final int MAXIMUM_VARINT_SIZE = 5;
|
||||||
private static final BinaryTagType<? extends BinaryTag>[] BINARY_TAG_TYPES = new BinaryTagType[] {
|
private static final BinaryTagType<? extends BinaryTag>[] BINARY_TAG_TYPES = new BinaryTagType[] {
|
||||||
BinaryTagTypes.END, BinaryTagTypes.BYTE, BinaryTagTypes.SHORT, BinaryTagTypes.INT,
|
BinaryTagTypes.END, BinaryTagTypes.BYTE, BinaryTagTypes.SHORT, BinaryTagTypes.INT,
|
||||||
BinaryTagTypes.LONG, BinaryTagTypes.FLOAT, BinaryTagTypes.DOUBLE,
|
BinaryTagTypes.LONG, BinaryTagTypes.FLOAT, BinaryTagTypes.DOUBLE,
|
||||||
@@ -111,13 +111,18 @@ public enum ProtocolUtils {
|
|||||||
BinaryTagTypes.COMPOUND, BinaryTagTypes.INT_ARRAY, BinaryTagTypes.LONG_ARRAY};
|
BinaryTagTypes.COMPOUND, BinaryTagTypes.INT_ARRAY, BinaryTagTypes.LONG_ARRAY};
|
||||||
private static final QuietDecoderException BAD_VARINT_CACHED =
|
private static final QuietDecoderException BAD_VARINT_CACHED =
|
||||||
new QuietDecoderException("Bad VarInt decoded");
|
new QuietDecoderException("Bad VarInt decoded");
|
||||||
private static final int[] VARINT_EXACT_BYTE_LENGTHS = new int[33];
|
private static final int[] VAR_INT_LENGTHS = new int[65];
|
||||||
|
|
||||||
static {
|
static {
|
||||||
for (int i = 0; i <= 32; ++i) {
|
for (int i = 0; i <= 32; ++i) {
|
||||||
VARINT_EXACT_BYTE_LENGTHS[i] = (int) Math.ceil((31d - (i - 1)) / 7d);
|
VAR_INT_LENGTHS[i] = (int) Math.ceil((31d - (i - 1)) / 7d);
|
||||||
}
|
}
|
||||||
VARINT_EXACT_BYTE_LENGTHS[32] = 1; // Special case for the number 0.
|
VAR_INT_LENGTHS[32] = 1; // Special case for the number 0.
|
||||||
|
}
|
||||||
|
|
||||||
|
private static DecoderException badVarint() {
|
||||||
|
return MinecraftDecoder.DEBUG ? new CorruptedFrameException("Bad VarInt decoded")
|
||||||
|
: BAD_VARINT_CACHED;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -127,33 +132,29 @@ public enum ProtocolUtils {
|
|||||||
* @return the decoded VarInt
|
* @return the decoded VarInt
|
||||||
*/
|
*/
|
||||||
public static int readVarInt(ByteBuf buf) {
|
public static int readVarInt(ByteBuf buf) {
|
||||||
int read = readVarIntSafely(buf);
|
int readable = buf.readableBytes();
|
||||||
if (read == Integer.MIN_VALUE) {
|
if (readable == 0) {
|
||||||
throw MinecraftDecoder.DEBUG ? new CorruptedFrameException("Bad VarInt decoded")
|
// special case for empty buffer
|
||||||
: BAD_VARINT_CACHED;
|
throw badVarint();
|
||||||
}
|
}
|
||||||
return read;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
// we can read at least one byte, and this should be a common case
|
||||||
* Reads a Minecraft-style VarInt from the specified {@code buf}. The difference between this
|
int k = buf.readByte();
|
||||||
* method and {@link #readVarInt(ByteBuf)} is that this function returns a sentinel value if the
|
if ((k & 0x80) != 128) {
|
||||||
* varint is invalid.
|
return k;
|
||||||
*
|
}
|
||||||
* @param buf the buffer to read from
|
|
||||||
* @return the decoded VarInt, or {@code Integer.MIN_VALUE} if the varint is invalid
|
// in case decoding one byte was not enough, use a loop to decode up to the next 4 bytes
|
||||||
*/
|
int maxRead = Math.min(MAXIMUM_VARINT_SIZE, readable);
|
||||||
public static int readVarIntSafely(ByteBuf buf) {
|
int i = k & 0x7F;
|
||||||
int i = 0;
|
for (int j = 1; j < maxRead; j++) {
|
||||||
int maxRead = Math.min(5, buf.readableBytes());
|
k = buf.readByte();
|
||||||
for (int j = 0; j < maxRead; j++) {
|
|
||||||
int k = buf.readByte();
|
|
||||||
i |= (k & 0x7F) << j * 7;
|
i |= (k & 0x7F) << j * 7;
|
||||||
if ((k & 0x80) != 128) {
|
if ((k & 0x80) != 128) {
|
||||||
return i;
|
return i;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return Integer.MIN_VALUE;
|
throw badVarint();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -163,7 +164,7 @@ public enum ProtocolUtils {
|
|||||||
* @return the byte size of {@code value} if encoded as a VarInt
|
* @return the byte size of {@code value} if encoded as a VarInt
|
||||||
*/
|
*/
|
||||||
public static int varIntBytes(int value) {
|
public static int varIntBytes(int value) {
|
||||||
return VARINT_EXACT_BYTE_LENGTHS[Integer.numberOfLeadingZeros(value)];
|
return VAR_INT_LENGTHS[Integer.numberOfLeadingZeros(value)];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -187,6 +188,8 @@ public enum ProtocolUtils {
|
|||||||
|
|
||||||
private static void writeVarIntFull(ByteBuf buf, int value) {
|
private static void writeVarIntFull(ByteBuf buf, int value) {
|
||||||
// See https://steinborn.me/posts/performance/how-fast-can-you-write-a-varint/
|
// See https://steinborn.me/posts/performance/how-fast-can-you-write-a-varint/
|
||||||
|
|
||||||
|
// This essentially is an unrolled version of the "traditional" VarInt encoding.
|
||||||
if ((value & (0xFFFFFFFF << 7)) == 0) {
|
if ((value & (0xFFFFFFFF << 7)) == 0) {
|
||||||
buf.writeByte(value);
|
buf.writeByte(value);
|
||||||
} else if ((value & (0xFFFFFFFF << 14)) == 0) {
|
} else if ((value & (0xFFFFFFFF << 14)) == 0) {
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_20_2;
|
|||||||
import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_20_3;
|
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_20_5;
|
||||||
import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_21;
|
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_7_2;
|
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_8;
|
||||||
import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_9;
|
import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_9;
|
||||||
@@ -253,7 +254,8 @@ public enum StateRegistry {
|
|||||||
map(0x08, MINECRAFT_1_19_3, false),
|
map(0x08, MINECRAFT_1_19_3, false),
|
||||||
map(0x09, MINECRAFT_1_19_4, false),
|
map(0x09, MINECRAFT_1_19_4, false),
|
||||||
map(0x0A, MINECRAFT_1_20_2, false),
|
map(0x0A, MINECRAFT_1_20_2, false),
|
||||||
map(0x0B, MINECRAFT_1_20_5, false));
|
map(0x0B, MINECRAFT_1_20_5, false),
|
||||||
|
map(0x0D, MINECRAFT_1_21_2, false));
|
||||||
serverbound.register(
|
serverbound.register(
|
||||||
LegacyChatPacket.class,
|
LegacyChatPacket.class,
|
||||||
LegacyChatPacket::new,
|
LegacyChatPacket::new,
|
||||||
@@ -265,7 +267,8 @@ public enum StateRegistry {
|
|||||||
serverbound.register(
|
serverbound.register(
|
||||||
ChatAcknowledgementPacket.class,
|
ChatAcknowledgementPacket.class,
|
||||||
ChatAcknowledgementPacket::new,
|
ChatAcknowledgementPacket::new,
|
||||||
map(0x03, MINECRAFT_1_19_3, false));
|
map(0x03, MINECRAFT_1_19_3, false),
|
||||||
|
map(0x04, MINECRAFT_1_21_2, false));
|
||||||
serverbound.register(KeyedPlayerCommandPacket.class, KeyedPlayerCommandPacket::new,
|
serverbound.register(KeyedPlayerCommandPacket.class, KeyedPlayerCommandPacket::new,
|
||||||
map(0x03, MINECRAFT_1_19, false),
|
map(0x03, MINECRAFT_1_19, false),
|
||||||
map(0x04, MINECRAFT_1_19_1, MINECRAFT_1_19_1, false));
|
map(0x04, MINECRAFT_1_19_1, MINECRAFT_1_19_1, false));
|
||||||
@@ -274,14 +277,17 @@ public enum StateRegistry {
|
|||||||
map(0x05, MINECRAFT_1_19_1, MINECRAFT_1_19_1, false));
|
map(0x05, MINECRAFT_1_19_1, MINECRAFT_1_19_1, false));
|
||||||
serverbound.register(SessionPlayerCommandPacket.class, SessionPlayerCommandPacket::new,
|
serverbound.register(SessionPlayerCommandPacket.class, SessionPlayerCommandPacket::new,
|
||||||
map(0x04, MINECRAFT_1_19_3, false),
|
map(0x04, MINECRAFT_1_19_3, false),
|
||||||
map(0x05, MINECRAFT_1_20_5, false));
|
map(0x05, MINECRAFT_1_20_5, false),
|
||||||
|
map(0x06, MINECRAFT_1_21_2, false));
|
||||||
serverbound.register(UnsignedPlayerCommandPacket.class, UnsignedPlayerCommandPacket::new,
|
serverbound.register(UnsignedPlayerCommandPacket.class, UnsignedPlayerCommandPacket::new,
|
||||||
map(0x04, MINECRAFT_1_20_5, false));
|
map(0x04, MINECRAFT_1_20_5, false),
|
||||||
|
map(0x05, MINECRAFT_1_21_2, false));
|
||||||
serverbound.register(
|
serverbound.register(
|
||||||
SessionPlayerChatPacket.class,
|
SessionPlayerChatPacket.class,
|
||||||
SessionPlayerChatPacket::new,
|
SessionPlayerChatPacket::new,
|
||||||
map(0x05, MINECRAFT_1_19_3, false),
|
map(0x05, MINECRAFT_1_19_3, false),
|
||||||
map(0x06, MINECRAFT_1_20_5, false));
|
map(0x06, MINECRAFT_1_20_5, false),
|
||||||
|
map(0x07, MINECRAFT_1_21_2, false));
|
||||||
serverbound.register(
|
serverbound.register(
|
||||||
ClientSettingsPacket.class,
|
ClientSettingsPacket.class,
|
||||||
ClientSettingsPacket::new,
|
ClientSettingsPacket::new,
|
||||||
@@ -295,10 +301,12 @@ public enum StateRegistry {
|
|||||||
map(0x07, MINECRAFT_1_19_3, false),
|
map(0x07, MINECRAFT_1_19_3, false),
|
||||||
map(0x08, MINECRAFT_1_19_4, false),
|
map(0x08, MINECRAFT_1_19_4, false),
|
||||||
map(0x09, MINECRAFT_1_20_2, false),
|
map(0x09, MINECRAFT_1_20_2, false),
|
||||||
map(0x0A, MINECRAFT_1_20_5, false));
|
map(0x0A, MINECRAFT_1_20_5, false),
|
||||||
|
map(0x0C, MINECRAFT_1_21_2, false));
|
||||||
serverbound.register(
|
serverbound.register(
|
||||||
ServerboundCookieResponsePacket.class, ServerboundCookieResponsePacket::new,
|
ServerboundCookieResponsePacket.class, ServerboundCookieResponsePacket::new,
|
||||||
map(0x11, MINECRAFT_1_20_5, false));
|
map(0x11, MINECRAFT_1_20_5, false),
|
||||||
|
map(0x13, MINECRAFT_1_21_2, false));
|
||||||
serverbound.register(
|
serverbound.register(
|
||||||
PluginMessagePacket.class,
|
PluginMessagePacket.class,
|
||||||
PluginMessagePacket::new,
|
PluginMessagePacket::new,
|
||||||
@@ -315,7 +323,8 @@ public enum StateRegistry {
|
|||||||
map(0x0D, MINECRAFT_1_19_4, false),
|
map(0x0D, MINECRAFT_1_19_4, false),
|
||||||
map(0x0F, MINECRAFT_1_20_2, false),
|
map(0x0F, MINECRAFT_1_20_2, false),
|
||||||
map(0x10, MINECRAFT_1_20_3, false),
|
map(0x10, MINECRAFT_1_20_3, false),
|
||||||
map(0x12, MINECRAFT_1_20_5, false));
|
map(0x12, MINECRAFT_1_20_5, false),
|
||||||
|
map(0x14, MINECRAFT_1_21_2, false));
|
||||||
serverbound.register(
|
serverbound.register(
|
||||||
KeepAlivePacket.class,
|
KeepAlivePacket.class,
|
||||||
KeepAlivePacket::new,
|
KeepAlivePacket::new,
|
||||||
@@ -333,7 +342,8 @@ public enum StateRegistry {
|
|||||||
map(0x12, MINECRAFT_1_19_4, false),
|
map(0x12, MINECRAFT_1_19_4, false),
|
||||||
map(0x14, MINECRAFT_1_20_2, false),
|
map(0x14, MINECRAFT_1_20_2, false),
|
||||||
map(0x15, MINECRAFT_1_20_3, false),
|
map(0x15, MINECRAFT_1_20_3, false),
|
||||||
map(0x18, MINECRAFT_1_20_5, false));
|
map(0x18, MINECRAFT_1_20_5, false),
|
||||||
|
map(0x1A, MINECRAFT_1_21_2, false));
|
||||||
serverbound.register(
|
serverbound.register(
|
||||||
ResourcePackResponsePacket.class,
|
ResourcePackResponsePacket.class,
|
||||||
ResourcePackResponsePacket::new,
|
ResourcePackResponsePacket::new,
|
||||||
@@ -348,11 +358,13 @@ public enum StateRegistry {
|
|||||||
map(0x24, MINECRAFT_1_19_1, false),
|
map(0x24, MINECRAFT_1_19_1, false),
|
||||||
map(0x27, MINECRAFT_1_20_2, false),
|
map(0x27, MINECRAFT_1_20_2, false),
|
||||||
map(0x28, MINECRAFT_1_20_3, false),
|
map(0x28, MINECRAFT_1_20_3, false),
|
||||||
map(0x2B, MINECRAFT_1_20_5, false));
|
map(0x2B, MINECRAFT_1_20_5, false),
|
||||||
|
map(0x2D, MINECRAFT_1_21_2, false));
|
||||||
serverbound.register(
|
serverbound.register(
|
||||||
FinishedUpdatePacket.class, () -> FinishedUpdatePacket.INSTANCE,
|
FinishedUpdatePacket.class, () -> FinishedUpdatePacket.INSTANCE,
|
||||||
map(0x0B, MINECRAFT_1_20_2, false),
|
map(0x0B, MINECRAFT_1_20_2, false),
|
||||||
map(0x0C, MINECRAFT_1_20_5, false));
|
map(0x0C, MINECRAFT_1_20_5, false),
|
||||||
|
map(0x0E, MINECRAFT_1_21_2, false));
|
||||||
|
|
||||||
clientbound.register(
|
clientbound.register(
|
||||||
BossBarPacket.class,
|
BossBarPacket.class,
|
||||||
@@ -450,7 +462,8 @@ public enum StateRegistry {
|
|||||||
map(0x1F, MINECRAFT_1_19_3, false),
|
map(0x1F, MINECRAFT_1_19_3, false),
|
||||||
map(0x23, MINECRAFT_1_19_4, false),
|
map(0x23, MINECRAFT_1_19_4, false),
|
||||||
map(0x24, MINECRAFT_1_20_2, false),
|
map(0x24, MINECRAFT_1_20_2, false),
|
||||||
map(0x26, MINECRAFT_1_20_5, false));
|
map(0x26, MINECRAFT_1_20_5, false),
|
||||||
|
map(0x27, MINECRAFT_1_21_2, false));
|
||||||
clientbound.register(
|
clientbound.register(
|
||||||
JoinGamePacket.class,
|
JoinGamePacket.class,
|
||||||
JoinGamePacket::new,
|
JoinGamePacket::new,
|
||||||
@@ -467,7 +480,8 @@ public enum StateRegistry {
|
|||||||
map(0x24, MINECRAFT_1_19_3, false),
|
map(0x24, MINECRAFT_1_19_3, false),
|
||||||
map(0x28, MINECRAFT_1_19_4, false),
|
map(0x28, MINECRAFT_1_19_4, false),
|
||||||
map(0x29, MINECRAFT_1_20_2, false),
|
map(0x29, MINECRAFT_1_20_2, false),
|
||||||
map(0x2B, MINECRAFT_1_20_5, false));
|
map(0x2B, MINECRAFT_1_20_5, false),
|
||||||
|
map(0x2C, MINECRAFT_1_21_2, false));
|
||||||
clientbound.register(
|
clientbound.register(
|
||||||
RespawnPacket.class,
|
RespawnPacket.class,
|
||||||
RespawnPacket::new,
|
RespawnPacket::new,
|
||||||
@@ -487,12 +501,14 @@ public enum StateRegistry {
|
|||||||
map(0x41, MINECRAFT_1_19_4, true),
|
map(0x41, MINECRAFT_1_19_4, true),
|
||||||
map(0x43, MINECRAFT_1_20_2, true),
|
map(0x43, MINECRAFT_1_20_2, true),
|
||||||
map(0x45, MINECRAFT_1_20_3, true),
|
map(0x45, MINECRAFT_1_20_3, true),
|
||||||
map(0x47, MINECRAFT_1_20_5, true));
|
map(0x47, MINECRAFT_1_20_5, true),
|
||||||
|
map(0x4C, MINECRAFT_1_21_2, true));
|
||||||
clientbound.register(
|
clientbound.register(
|
||||||
RemoveResourcePackPacket.class,
|
RemoveResourcePackPacket.class,
|
||||||
RemoveResourcePackPacket::new,
|
RemoveResourcePackPacket::new,
|
||||||
map(0x43, MINECRAFT_1_20_3, false),
|
map(0x43, MINECRAFT_1_20_3, false),
|
||||||
map(0x45, MINECRAFT_1_20_5, false));
|
map(0x45, MINECRAFT_1_20_5, false),
|
||||||
|
map(0x4A, MINECRAFT_1_21_2, false));
|
||||||
clientbound.register(
|
clientbound.register(
|
||||||
ResourcePackRequestPacket.class,
|
ResourcePackRequestPacket.class,
|
||||||
ResourcePackRequestPacket::new,
|
ResourcePackRequestPacket::new,
|
||||||
@@ -512,7 +528,8 @@ public enum StateRegistry {
|
|||||||
map(0x40, MINECRAFT_1_19_4, false),
|
map(0x40, MINECRAFT_1_19_4, false),
|
||||||
map(0x42, MINECRAFT_1_20_2, false),
|
map(0x42, MINECRAFT_1_20_2, false),
|
||||||
map(0x44, MINECRAFT_1_20_3, false),
|
map(0x44, MINECRAFT_1_20_3, false),
|
||||||
map(0x46, MINECRAFT_1_20_5, false));
|
map(0x46, MINECRAFT_1_20_5, false),
|
||||||
|
map(0x4B, MINECRAFT_1_21_2, false));
|
||||||
clientbound.register(
|
clientbound.register(
|
||||||
HeaderAndFooterPacket.class,
|
HeaderAndFooterPacket.class,
|
||||||
HeaderAndFooterPacket::new,
|
HeaderAndFooterPacket::new,
|
||||||
@@ -533,7 +550,8 @@ public enum StateRegistry {
|
|||||||
map(0x65, MINECRAFT_1_19_4, true),
|
map(0x65, MINECRAFT_1_19_4, true),
|
||||||
map(0x68, MINECRAFT_1_20_2, true),
|
map(0x68, MINECRAFT_1_20_2, true),
|
||||||
map(0x6A, MINECRAFT_1_20_3, true),
|
map(0x6A, MINECRAFT_1_20_3, true),
|
||||||
map(0x6D, MINECRAFT_1_20_5, true));
|
map(0x6D, MINECRAFT_1_20_5, true),
|
||||||
|
map(0x74, MINECRAFT_1_21_2, true));
|
||||||
clientbound.register(
|
clientbound.register(
|
||||||
LegacyTitlePacket.class,
|
LegacyTitlePacket.class,
|
||||||
LegacyTitlePacket::new,
|
LegacyTitlePacket::new,
|
||||||
@@ -553,7 +571,8 @@ public enum StateRegistry {
|
|||||||
map(0x5D, MINECRAFT_1_19_4, true),
|
map(0x5D, MINECRAFT_1_19_4, true),
|
||||||
map(0x5F, MINECRAFT_1_20_2, true),
|
map(0x5F, MINECRAFT_1_20_2, true),
|
||||||
map(0x61, MINECRAFT_1_20_3, true),
|
map(0x61, MINECRAFT_1_20_3, true),
|
||||||
map(0x63, MINECRAFT_1_20_5, true));
|
map(0x63, MINECRAFT_1_20_5, true),
|
||||||
|
map(0x6A, MINECRAFT_1_21_2, true));
|
||||||
clientbound.register(
|
clientbound.register(
|
||||||
TitleTextPacket.class,
|
TitleTextPacket.class,
|
||||||
TitleTextPacket::new,
|
TitleTextPacket::new,
|
||||||
@@ -564,7 +583,8 @@ public enum StateRegistry {
|
|||||||
map(0x5F, MINECRAFT_1_19_4, true),
|
map(0x5F, MINECRAFT_1_19_4, true),
|
||||||
map(0x61, MINECRAFT_1_20_2, true),
|
map(0x61, MINECRAFT_1_20_2, true),
|
||||||
map(0x63, MINECRAFT_1_20_3, true),
|
map(0x63, MINECRAFT_1_20_3, true),
|
||||||
map(0x65, MINECRAFT_1_20_5, true));
|
map(0x65, MINECRAFT_1_20_5, true),
|
||||||
|
map(0x6C, MINECRAFT_1_21_2, true));
|
||||||
clientbound.register(
|
clientbound.register(
|
||||||
TitleActionbarPacket.class,
|
TitleActionbarPacket.class,
|
||||||
TitleActionbarPacket::new,
|
TitleActionbarPacket::new,
|
||||||
@@ -575,7 +595,8 @@ public enum StateRegistry {
|
|||||||
map(0x46, MINECRAFT_1_19_4, true),
|
map(0x46, MINECRAFT_1_19_4, true),
|
||||||
map(0x48, MINECRAFT_1_20_2, true),
|
map(0x48, MINECRAFT_1_20_2, true),
|
||||||
map(0x4A, MINECRAFT_1_20_3, true),
|
map(0x4A, MINECRAFT_1_20_3, true),
|
||||||
map(0x4C, MINECRAFT_1_20_5, true));
|
map(0x4C, MINECRAFT_1_20_5, true),
|
||||||
|
map(0x51, MINECRAFT_1_21_2, true));
|
||||||
clientbound.register(
|
clientbound.register(
|
||||||
TitleTimesPacket.class,
|
TitleTimesPacket.class,
|
||||||
TitleTimesPacket::new,
|
TitleTimesPacket::new,
|
||||||
@@ -586,7 +607,8 @@ public enum StateRegistry {
|
|||||||
map(0x60, MINECRAFT_1_19_4, true),
|
map(0x60, MINECRAFT_1_19_4, true),
|
||||||
map(0x62, MINECRAFT_1_20_2, true),
|
map(0x62, MINECRAFT_1_20_2, true),
|
||||||
map(0x64, MINECRAFT_1_20_3, true),
|
map(0x64, MINECRAFT_1_20_3, true),
|
||||||
map(0x66, MINECRAFT_1_20_5, true));
|
map(0x66, MINECRAFT_1_20_5, true),
|
||||||
|
map(0x6D, MINECRAFT_1_21_2, true));
|
||||||
clientbound.register(
|
clientbound.register(
|
||||||
TitleClearPacket.class,
|
TitleClearPacket.class,
|
||||||
TitleClearPacket::new,
|
TitleClearPacket::new,
|
||||||
@@ -613,17 +635,20 @@ public enum StateRegistry {
|
|||||||
map(0x35, MINECRAFT_1_19_3, false),
|
map(0x35, MINECRAFT_1_19_3, false),
|
||||||
map(0x39, MINECRAFT_1_19_4, false),
|
map(0x39, MINECRAFT_1_19_4, false),
|
||||||
map(0x3B, MINECRAFT_1_20_2, false),
|
map(0x3B, MINECRAFT_1_20_2, false),
|
||||||
map(0x3D, MINECRAFT_1_20_5, false));
|
map(0x3D, MINECRAFT_1_20_5, false),
|
||||||
|
map(0x3F, MINECRAFT_1_21_2, false));
|
||||||
clientbound.register(
|
clientbound.register(
|
||||||
UpsertPlayerInfoPacket.class,
|
UpsertPlayerInfoPacket.class,
|
||||||
UpsertPlayerInfoPacket::new,
|
UpsertPlayerInfoPacket::new,
|
||||||
map(0x36, MINECRAFT_1_19_3, false),
|
map(0x36, MINECRAFT_1_19_3, false),
|
||||||
map(0x3A, MINECRAFT_1_19_4, false),
|
map(0x3A, MINECRAFT_1_19_4, false),
|
||||||
map(0x3C, MINECRAFT_1_20_2, false),
|
map(0x3C, MINECRAFT_1_20_2, false),
|
||||||
map(0x3E, MINECRAFT_1_20_5, false));
|
map(0x3E, MINECRAFT_1_20_5, false),
|
||||||
|
map(0x40, MINECRAFT_1_21_2, false));
|
||||||
clientbound.register(
|
clientbound.register(
|
||||||
ClientboundStoreCookiePacket.class, ClientboundStoreCookiePacket::new,
|
ClientboundStoreCookiePacket.class, ClientboundStoreCookiePacket::new,
|
||||||
map(0x6B, MINECRAFT_1_20_5, false));
|
map(0x6B, MINECRAFT_1_20_5, false),
|
||||||
|
map(0x72, MINECRAFT_1_21_2, false));
|
||||||
clientbound.register(
|
clientbound.register(
|
||||||
SystemChatPacket.class,
|
SystemChatPacket.class,
|
||||||
SystemChatPacket::new,
|
SystemChatPacket::new,
|
||||||
@@ -633,7 +658,8 @@ public enum StateRegistry {
|
|||||||
map(0x64, MINECRAFT_1_19_4, true),
|
map(0x64, MINECRAFT_1_19_4, true),
|
||||||
map(0x67, MINECRAFT_1_20_2, true),
|
map(0x67, MINECRAFT_1_20_2, true),
|
||||||
map(0x69, MINECRAFT_1_20_3, true),
|
map(0x69, MINECRAFT_1_20_3, true),
|
||||||
map(0x6C, MINECRAFT_1_20_5, true));
|
map(0x6C, MINECRAFT_1_20_5, true),
|
||||||
|
map(0x73, MINECRAFT_1_21_2, true));
|
||||||
clientbound.register(
|
clientbound.register(
|
||||||
PlayerChatCompletionPacket.class,
|
PlayerChatCompletionPacket.class,
|
||||||
PlayerChatCompletionPacket::new,
|
PlayerChatCompletionPacket::new,
|
||||||
@@ -651,13 +677,15 @@ public enum StateRegistry {
|
|||||||
map(0x45, MINECRAFT_1_19_4, false),
|
map(0x45, MINECRAFT_1_19_4, false),
|
||||||
map(0x47, MINECRAFT_1_20_2, false),
|
map(0x47, MINECRAFT_1_20_2, false),
|
||||||
map(0x49, MINECRAFT_1_20_3, false),
|
map(0x49, MINECRAFT_1_20_3, false),
|
||||||
map(0x4B, MINECRAFT_1_20_5, false));
|
map(0x4B, MINECRAFT_1_20_5, false),
|
||||||
|
map(0x50, MINECRAFT_1_21_2, false));
|
||||||
clientbound.register(
|
clientbound.register(
|
||||||
StartUpdatePacket.class,
|
StartUpdatePacket.class,
|
||||||
() -> StartUpdatePacket.INSTANCE,
|
() -> StartUpdatePacket.INSTANCE,
|
||||||
map(0x65, MINECRAFT_1_20_2, false),
|
map(0x65, MINECRAFT_1_20_2, false),
|
||||||
map(0x67, MINECRAFT_1_20_3, false),
|
map(0x67, MINECRAFT_1_20_3, false),
|
||||||
map(0x69, MINECRAFT_1_20_5, false));
|
map(0x69, MINECRAFT_1_20_5, false),
|
||||||
|
map(0x70, MINECRAFT_1_21_2, false));
|
||||||
clientbound.register(
|
clientbound.register(
|
||||||
BundleDelimiterPacket.class,
|
BundleDelimiterPacket.class,
|
||||||
() -> BundleDelimiterPacket.INSTANCE,
|
() -> BundleDelimiterPacket.INSTANCE,
|
||||||
@@ -665,12 +693,18 @@ public enum StateRegistry {
|
|||||||
clientbound.register(
|
clientbound.register(
|
||||||
TransferPacket.class,
|
TransferPacket.class,
|
||||||
TransferPacket::new,
|
TransferPacket::new,
|
||||||
map(0x73, MINECRAFT_1_20_5, false)
|
map(0x73, MINECRAFT_1_20_5, false),
|
||||||
);
|
map(0x7A, MINECRAFT_1_21_2, false));
|
||||||
clientbound.register(ClientboundCustomReportDetailsPacket.class, ClientboundCustomReportDetailsPacket::new,
|
clientbound.register(
|
||||||
map(0x7A, MINECRAFT_1_21, false));
|
ClientboundCustomReportDetailsPacket.class,
|
||||||
clientbound.register(ClientboundServerLinksPacket.class, ClientboundServerLinksPacket::new,
|
ClientboundCustomReportDetailsPacket::new,
|
||||||
map(0x7B, MINECRAFT_1_21, false));
|
map(0x7A, MINECRAFT_1_21, false),
|
||||||
|
map(0x81, MINECRAFT_1_21_2, false));
|
||||||
|
clientbound.register(
|
||||||
|
ClientboundServerLinksPacket.class,
|
||||||
|
ClientboundServerLinksPacket::new,
|
||||||
|
map(0x7B, MINECRAFT_1_21, false),
|
||||||
|
map(0x82, MINECRAFT_1_21_2, false));
|
||||||
clientbound.register(UpdateTeamsPacket.class, UpdateTeamsPacket::new,
|
clientbound.register(UpdateTeamsPacket.class, UpdateTeamsPacket::new,
|
||||||
map(0x41, ProtocolVersion.MINECRAFT_1_9, true),
|
map(0x41, ProtocolVersion.MINECRAFT_1_9, true),
|
||||||
map(0x43, ProtocolVersion.MINECRAFT_1_12, true),
|
map(0x43, ProtocolVersion.MINECRAFT_1_12, true),
|
||||||
@@ -684,7 +718,8 @@ public enum StateRegistry {
|
|||||||
map(0x5A, ProtocolVersion.MINECRAFT_1_19_4, true),
|
map(0x5A, ProtocolVersion.MINECRAFT_1_19_4, true),
|
||||||
map(0x5C, ProtocolVersion.MINECRAFT_1_20_2, true),
|
map(0x5C, ProtocolVersion.MINECRAFT_1_20_2, true),
|
||||||
map(0x5E, ProtocolVersion.MINECRAFT_1_20_3, true),
|
map(0x5E, ProtocolVersion.MINECRAFT_1_20_3, true),
|
||||||
map(0x60, ProtocolVersion.MINECRAFT_1_20_5, true)
|
map(0x60, ProtocolVersion.MINECRAFT_1_20_5, true),
|
||||||
|
map(0x67, ProtocolVersion.MINECRAFT_1_21_2, true)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
+97
-34
@@ -17,7 +17,8 @@
|
|||||||
|
|
||||||
package com.velocitypowered.proxy.protocol.netty;
|
package com.velocitypowered.proxy.protocol.netty;
|
||||||
|
|
||||||
import com.velocitypowered.proxy.protocol.netty.VarintByteDecoder.DecodeResult;
|
import static io.netty.util.ByteProcessor.FIND_NON_NUL;
|
||||||
|
|
||||||
import com.velocitypowered.proxy.util.except.QuietDecoderException;
|
import com.velocitypowered.proxy.util.except.QuietDecoderException;
|
||||||
import io.netty.buffer.ByteBuf;
|
import io.netty.buffer.ByteBuf;
|
||||||
import io.netty.channel.ChannelHandlerContext;
|
import io.netty.channel.ChannelHandlerContext;
|
||||||
@@ -29,53 +30,115 @@ import java.util.List;
|
|||||||
*/
|
*/
|
||||||
public class MinecraftVarintFrameDecoder extends ByteToMessageDecoder {
|
public class MinecraftVarintFrameDecoder extends ByteToMessageDecoder {
|
||||||
|
|
||||||
private static final QuietDecoderException BAD_LENGTH_CACHED =
|
private static final QuietDecoderException BAD_PACKET_LENGTH =
|
||||||
new QuietDecoderException("Bad packet length");
|
new QuietDecoderException("Bad packet length");
|
||||||
private static final QuietDecoderException VARINT_BIG_CACHED =
|
private static final QuietDecoderException VARINT_TOO_BIG =
|
||||||
new QuietDecoderException("VarInt too big");
|
new QuietDecoderException("VarInt too big");
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) {
|
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out)
|
||||||
|
throws Exception {
|
||||||
if (!ctx.channel().isActive()) {
|
if (!ctx.channel().isActive()) {
|
||||||
in.clear();
|
in.clear();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
final VarintByteDecoder reader = new VarintByteDecoder();
|
// skip any runs of 0x00 we might find
|
||||||
|
int packetStart = in.forEachByte(FIND_NON_NUL);
|
||||||
int varintEnd = in.forEachByte(reader);
|
if (packetStart == -1) {
|
||||||
if (varintEnd == -1) {
|
in.clear();
|
||||||
// We tried to go beyond the end of the buffer. This is probably a good sign that the
|
|
||||||
// buffer was too short to hold a proper varint.
|
|
||||||
if (reader.getResult() == DecodeResult.RUN_OF_ZEROES) {
|
|
||||||
// Special case where the entire packet is just a run of zeroes. We ignore them all.
|
|
||||||
in.clear();
|
|
||||||
}
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
in.readerIndex(packetStart);
|
||||||
|
|
||||||
if (reader.getResult() == DecodeResult.RUN_OF_ZEROES) {
|
// try to read the length of the packet
|
||||||
// this will return to the point where the next varint starts
|
in.markReaderIndex();
|
||||||
in.readerIndex(varintEnd);
|
int preIndex = in.readerIndex();
|
||||||
} else if (reader.getResult() == DecodeResult.SUCCESS) {
|
int length = readRawVarInt21(in);
|
||||||
int readVarint = reader.getReadVarint();
|
if (preIndex == in.readerIndex()) {
|
||||||
int bytesRead = reader.getBytesRead();
|
return;
|
||||||
if (readVarint < 0) {
|
}
|
||||||
in.clear();
|
if (length < 0) {
|
||||||
throw BAD_LENGTH_CACHED;
|
throw BAD_PACKET_LENGTH;
|
||||||
} else if (readVarint == 0) {
|
}
|
||||||
// skip over the empty packet(s) and ignore it
|
|
||||||
in.readerIndex(varintEnd + 1);
|
// note that zero-length packets are ignored
|
||||||
|
if (length > 0) {
|
||||||
|
if (in.readableBytes() < length) {
|
||||||
|
in.resetReaderIndex();
|
||||||
} else {
|
} else {
|
||||||
int minimumRead = bytesRead + readVarint;
|
out.add(in.readRetainedSlice(length));
|
||||||
if (in.isReadable(minimumRead)) {
|
|
||||||
out.add(in.retainedSlice(varintEnd + 1, readVarint));
|
|
||||||
in.skipBytes(minimumRead);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else if (reader.getResult() == DecodeResult.TOO_BIG) {
|
|
||||||
in.clear();
|
|
||||||
throw VARINT_BIG_CACHED;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads a VarInt from the buffer of up to 21 bits in size.
|
||||||
|
*
|
||||||
|
* @param buffer the buffer to read from
|
||||||
|
* @return the VarInt decoded, {@code 0} if no varint could be read
|
||||||
|
* @throws QuietDecoderException if the VarInt is too big to be decoded
|
||||||
|
*/
|
||||||
|
private static int readRawVarInt21(ByteBuf buffer) {
|
||||||
|
if (buffer.readableBytes() < 4) {
|
||||||
|
// we don't have enough that we can read a potentially full varint, so fall back to
|
||||||
|
// the slow path.
|
||||||
|
return readRawVarintSmallBuf(buffer);
|
||||||
|
}
|
||||||
|
int wholeOrMore = buffer.getIntLE(buffer.readerIndex());
|
||||||
|
|
||||||
|
// take the last three bytes and check if any of them have the high bit set
|
||||||
|
int atStop = ~wholeOrMore & 0x808080;
|
||||||
|
if (atStop == 0) {
|
||||||
|
// all bytes have the high bit set, so the varint we are trying to decode is too wide
|
||||||
|
throw VARINT_TOO_BIG;
|
||||||
|
}
|
||||||
|
|
||||||
|
int bitsToKeep = Integer.numberOfTrailingZeros(atStop) + 1;
|
||||||
|
buffer.skipBytes(bitsToKeep >> 3);
|
||||||
|
|
||||||
|
// remove all bits we don't need to keep, a trick from
|
||||||
|
// https://github.com/netty/netty/pull/14050#issuecomment-2107750734:
|
||||||
|
//
|
||||||
|
// > The idea is that thisVarintMask has 0s above the first one of firstOneOnStop, and 1s at
|
||||||
|
// > and below it. For example if firstOneOnStop is 0x800080 (where the last 0x80 is the only
|
||||||
|
// > one that matters), then thisVarintMask is 0xFF.
|
||||||
|
//
|
||||||
|
// this is also documented in Hacker's Delight, section 2-1 "Manipulating Rightmost Bits"
|
||||||
|
int preservedBytes = wholeOrMore & (atStop ^ (atStop - 1));
|
||||||
|
|
||||||
|
// merge together using this trick: https://github.com/netty/netty/pull/14050#discussion_r1597896639
|
||||||
|
preservedBytes = (preservedBytes & 0x007F007F) | ((preservedBytes & 0x00007F00) >> 1);
|
||||||
|
preservedBytes = (preservedBytes & 0x00003FFF) | ((preservedBytes & 0x3FFF0000) >> 2);
|
||||||
|
return preservedBytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int readRawVarintSmallBuf(ByteBuf buffer) {
|
||||||
|
if (!buffer.isReadable()) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
buffer.markReaderIndex();
|
||||||
|
|
||||||
|
byte tmp = buffer.readByte();
|
||||||
|
if (tmp >= 0) {
|
||||||
|
return tmp;
|
||||||
|
}
|
||||||
|
int result = tmp & 0x7F;
|
||||||
|
if (!buffer.isReadable()) {
|
||||||
|
buffer.resetReaderIndex();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if ((tmp = buffer.readByte()) >= 0) {
|
||||||
|
return result | tmp << 7;
|
||||||
|
}
|
||||||
|
result |= (tmp & 0x7F) << 7;
|
||||||
|
if (!buffer.isReadable()) {
|
||||||
|
buffer.resetReaderIndex();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if ((tmp = buffer.readByte()) >= 0) {
|
||||||
|
return result | tmp << 14;
|
||||||
|
}
|
||||||
|
return result | (tmp & 0x7F) << 14;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+16
-15
@@ -23,33 +23,34 @@ import com.velocitypowered.proxy.protocol.ProtocolUtils;
|
|||||||
import io.netty.buffer.ByteBuf;
|
import io.netty.buffer.ByteBuf;
|
||||||
import io.netty.channel.ChannelHandler;
|
import io.netty.channel.ChannelHandler;
|
||||||
import io.netty.channel.ChannelHandlerContext;
|
import io.netty.channel.ChannelHandlerContext;
|
||||||
import io.netty.handler.codec.MessageToByteEncoder;
|
import io.netty.handler.codec.MessageToMessageEncoder;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handler for appending a length for Minecraft packets.
|
* Handler for appending a length for Minecraft packets.
|
||||||
*/
|
*/
|
||||||
@ChannelHandler.Sharable
|
@ChannelHandler.Sharable
|
||||||
public class MinecraftVarintLengthEncoder extends MessageToByteEncoder<ByteBuf> {
|
public class MinecraftVarintLengthEncoder extends MessageToMessageEncoder<ByteBuf> {
|
||||||
|
|
||||||
public static final MinecraftVarintLengthEncoder INSTANCE = new MinecraftVarintLengthEncoder();
|
public static final MinecraftVarintLengthEncoder INSTANCE = new MinecraftVarintLengthEncoder();
|
||||||
public static final boolean IS_JAVA_CIPHER = Natives.cipher.get() == JavaVelocityCipher.FACTORY;
|
|
||||||
|
static final boolean IS_JAVA_CIPHER = Natives.cipher.get() == JavaVelocityCipher.FACTORY;
|
||||||
|
|
||||||
private MinecraftVarintLengthEncoder() {
|
private MinecraftVarintLengthEncoder() {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void encode(ChannelHandlerContext ctx, ByteBuf msg, ByteBuf out) throws Exception {
|
protected void encode(ChannelHandlerContext ctx, ByteBuf buf,
|
||||||
ProtocolUtils.writeVarInt(out, msg.readableBytes());
|
List<Object> list) throws Exception {
|
||||||
out.writeBytes(msg);
|
final int length = buf.readableBytes();
|
||||||
}
|
final int varintLength = ProtocolUtils.varIntBytes(length);
|
||||||
|
|
||||||
@Override
|
final ByteBuf lenBuf = IS_JAVA_CIPHER
|
||||||
protected ByteBuf allocateBuffer(ChannelHandlerContext ctx, ByteBuf msg, boolean preferDirect)
|
? ctx.alloc().heapBuffer(varintLength)
|
||||||
throws Exception {
|
: ctx.alloc().directBuffer(varintLength);
|
||||||
int anticipatedRequiredCapacity = ProtocolUtils.varIntBytes(msg.readableBytes())
|
|
||||||
+ msg.readableBytes();
|
ProtocolUtils.writeVarInt(lenBuf, length);
|
||||||
return IS_JAVA_CIPHER
|
list.add(lenBuf);
|
||||||
? ctx.alloc().heapBuffer(anticipatedRequiredCapacity)
|
list.add(buf.retain());
|
||||||
: ctx.alloc().directBuffer(anticipatedRequiredCapacity);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,68 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2020-2021 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.netty;
|
|
||||||
|
|
||||||
import io.netty.util.ByteProcessor;
|
|
||||||
|
|
||||||
class VarintByteDecoder implements ByteProcessor {
|
|
||||||
|
|
||||||
private int readVarint;
|
|
||||||
private int bytesRead;
|
|
||||||
private DecodeResult result = DecodeResult.TOO_SHORT;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean process(byte k) {
|
|
||||||
if (k == 0 && bytesRead == 0) {
|
|
||||||
// tentatively say it's invalid, but there's a possibility of redemption
|
|
||||||
result = DecodeResult.RUN_OF_ZEROES;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (result == DecodeResult.RUN_OF_ZEROES) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
readVarint |= (k & 0x7F) << bytesRead++ * 7;
|
|
||||||
if (bytesRead > 3) {
|
|
||||||
result = DecodeResult.TOO_BIG;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if ((k & 0x80) != 128) {
|
|
||||||
result = DecodeResult.SUCCESS;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getReadVarint() {
|
|
||||||
return readVarint;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getBytesRead() {
|
|
||||||
return bytesRead;
|
|
||||||
}
|
|
||||||
|
|
||||||
public DecodeResult getResult() {
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
public enum DecodeResult {
|
|
||||||
SUCCESS,
|
|
||||||
TOO_SHORT,
|
|
||||||
TOO_BIG,
|
|
||||||
RUN_OF_ZEROES
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+35
-13
@@ -34,21 +34,25 @@ public class ClientSettingsPacket implements MinecraftPacket {
|
|||||||
private byte difficulty; // 1.7 Protocol
|
private byte difficulty; // 1.7 Protocol
|
||||||
private short skinParts;
|
private short skinParts;
|
||||||
private int mainHand;
|
private int mainHand;
|
||||||
private boolean chatFilteringEnabled; // Added in 1.17
|
private boolean textFilteringEnabled; // Added in 1.17
|
||||||
private boolean clientListingAllowed; // Added in 1.18, overwrites server-list "anonymous" mode
|
private boolean clientListingAllowed; // Added in 1.18, overwrites server-list "anonymous" mode
|
||||||
|
private int particleStatus; // Added in 1.21.2
|
||||||
|
|
||||||
public ClientSettingsPacket() {
|
public ClientSettingsPacket() {
|
||||||
}
|
}
|
||||||
|
|
||||||
public ClientSettingsPacket(String locale, byte viewDistance, int chatVisibility, boolean chatColors,
|
public ClientSettingsPacket(String locale, byte viewDistance, int chatVisibility, boolean chatColors,
|
||||||
short skinParts, int mainHand, boolean chatFilteringEnabled, boolean clientListingAllowed) {
|
short skinParts, int mainHand, boolean textFilteringEnabled, boolean clientListingAllowed,
|
||||||
|
int particleStatus) {
|
||||||
this.locale = locale;
|
this.locale = locale;
|
||||||
this.viewDistance = viewDistance;
|
this.viewDistance = viewDistance;
|
||||||
this.chatVisibility = chatVisibility;
|
this.chatVisibility = chatVisibility;
|
||||||
this.chatColors = chatColors;
|
this.chatColors = chatColors;
|
||||||
this.skinParts = skinParts;
|
this.skinParts = skinParts;
|
||||||
this.mainHand = mainHand;
|
this.mainHand = mainHand;
|
||||||
|
this.textFilteringEnabled = textFilteringEnabled;
|
||||||
this.clientListingAllowed = clientListingAllowed;
|
this.clientListingAllowed = clientListingAllowed;
|
||||||
|
this.particleStatus = particleStatus;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getLocale() {
|
public String getLocale() {
|
||||||
@@ -102,12 +106,12 @@ public class ClientSettingsPacket implements MinecraftPacket {
|
|||||||
this.mainHand = mainHand;
|
this.mainHand = mainHand;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isChatFilteringEnabled() {
|
public boolean isTextFilteringEnabled() {
|
||||||
return chatFilteringEnabled;
|
return textFilteringEnabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setChatFilteringEnabled(boolean chatFilteringEnabled) {
|
public void setTextFilteringEnabled(boolean textFilteringEnabled) {
|
||||||
this.chatFilteringEnabled = chatFilteringEnabled;
|
this.textFilteringEnabled = textFilteringEnabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isClientListingAllowed() {
|
public boolean isClientListingAllowed() {
|
||||||
@@ -118,12 +122,20 @@ public class ClientSettingsPacket implements MinecraftPacket {
|
|||||||
this.clientListingAllowed = clientListingAllowed;
|
this.clientListingAllowed = clientListingAllowed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int getParticleStatus() {
|
||||||
|
return particleStatus;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setParticleStatus(int particleStatus) {
|
||||||
|
this.particleStatus = particleStatus;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "ClientSettings{" + "locale='" + locale + '\'' + ", viewDistance=" + viewDistance +
|
return "ClientSettings{" + "locale='" + locale + '\'' + ", viewDistance=" + viewDistance +
|
||||||
", chatVisibility=" + chatVisibility + ", chatColors=" + chatColors + ", skinParts=" +
|
", chatVisibility=" + chatVisibility + ", chatColors=" + chatColors + ", skinParts=" +
|
||||||
skinParts + ", mainHand=" + mainHand + ", chatFilteringEnabled=" + chatFilteringEnabled +
|
skinParts + ", mainHand=" + mainHand + ", chatFilteringEnabled=" + textFilteringEnabled +
|
||||||
", clientListingAllowed=" + clientListingAllowed + '}';
|
", clientListingAllowed=" + clientListingAllowed + ", particleStatus=" + particleStatus + '}';
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -143,10 +155,14 @@ public class ClientSettingsPacket implements MinecraftPacket {
|
|||||||
this.mainHand = ProtocolUtils.readVarInt(buf);
|
this.mainHand = ProtocolUtils.readVarInt(buf);
|
||||||
|
|
||||||
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_17)) {
|
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_17)) {
|
||||||
this.chatFilteringEnabled = buf.readBoolean();
|
this.textFilteringEnabled = buf.readBoolean();
|
||||||
|
|
||||||
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_18)) {
|
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_18)) {
|
||||||
this.clientListingAllowed = buf.readBoolean();
|
this.clientListingAllowed = buf.readBoolean();
|
||||||
|
|
||||||
|
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_21_2)) {
|
||||||
|
this.particleStatus = ProtocolUtils.readVarInt(buf);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -172,11 +188,15 @@ public class ClientSettingsPacket implements MinecraftPacket {
|
|||||||
ProtocolUtils.writeVarInt(buf, mainHand);
|
ProtocolUtils.writeVarInt(buf, mainHand);
|
||||||
|
|
||||||
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_17)) {
|
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_17)) {
|
||||||
buf.writeBoolean(chatFilteringEnabled);
|
buf.writeBoolean(textFilteringEnabled);
|
||||||
|
|
||||||
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_18)) {
|
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_18)) {
|
||||||
buf.writeBoolean(clientListingAllowed);
|
buf.writeBoolean(clientListingAllowed);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_21_2)) {
|
||||||
|
ProtocolUtils.writeVarInt(buf, particleStatus);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -201,8 +221,9 @@ public class ClientSettingsPacket implements MinecraftPacket {
|
|||||||
&& difficulty == that.difficulty
|
&& difficulty == that.difficulty
|
||||||
&& skinParts == that.skinParts
|
&& skinParts == that.skinParts
|
||||||
&& mainHand == that.mainHand
|
&& mainHand == that.mainHand
|
||||||
&& chatFilteringEnabled == that.chatFilteringEnabled
|
&& textFilteringEnabled == that.textFilteringEnabled
|
||||||
&& clientListingAllowed == that.clientListingAllowed
|
&& clientListingAllowed == that.clientListingAllowed
|
||||||
|
&& particleStatus == that.particleStatus
|
||||||
&& Objects.equals(locale, that.locale);
|
&& Objects.equals(locale, that.locale);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -216,7 +237,8 @@ public class ClientSettingsPacket implements MinecraftPacket {
|
|||||||
difficulty,
|
difficulty,
|
||||||
skinParts,
|
skinParts,
|
||||||
mainHand,
|
mainHand,
|
||||||
chatFilteringEnabled,
|
textFilteringEnabled,
|
||||||
clientListingAllowed);
|
clientListingAllowed,
|
||||||
|
particleStatus);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,6 +51,7 @@ public class JoinGamePacket implements MinecraftPacket {
|
|||||||
private int simulationDistance; // 1.18+
|
private int simulationDistance; // 1.18+
|
||||||
private @Nullable Pair<String, Long> lastDeathPosition; // 1.19+
|
private @Nullable Pair<String, Long> lastDeathPosition; // 1.19+
|
||||||
private int portalCooldown; // 1.20+
|
private int portalCooldown; // 1.20+
|
||||||
|
private int seaLevel; // 1.21.2+
|
||||||
private boolean enforcesSecureChat; // 1.20.5+
|
private boolean enforcesSecureChat; // 1.20.5+
|
||||||
|
|
||||||
public int getEntityId() {
|
public int getEntityId() {
|
||||||
@@ -181,6 +182,14 @@ public class JoinGamePacket implements MinecraftPacket {
|
|||||||
this.portalCooldown = portalCooldown;
|
this.portalCooldown = portalCooldown;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int getSeaLevel() {
|
||||||
|
return seaLevel;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSeaLevel(int seaLevel) {
|
||||||
|
this.seaLevel = seaLevel;
|
||||||
|
}
|
||||||
|
|
||||||
public boolean getEnforcesSecureChat() {
|
public boolean getEnforcesSecureChat() {
|
||||||
return this.enforcesSecureChat;
|
return this.enforcesSecureChat;
|
||||||
}
|
}
|
||||||
@@ -204,6 +213,7 @@ public class JoinGamePacket implements MinecraftPacket {
|
|||||||
dimensionInfo + '\'' + ", currentDimensionData='" + currentDimensionData + '\'' +
|
dimensionInfo + '\'' + ", currentDimensionData='" + currentDimensionData + '\'' +
|
||||||
", previousGamemode=" + previousGamemode + ", simulationDistance=" + simulationDistance +
|
", previousGamemode=" + previousGamemode + ", simulationDistance=" + simulationDistance +
|
||||||
", lastDeathPosition='" + lastDeathPosition + '\'' + ", portalCooldown=" + portalCooldown +
|
", lastDeathPosition='" + lastDeathPosition + '\'' + ", portalCooldown=" + portalCooldown +
|
||||||
|
", seaLevel=" + seaLevel +
|
||||||
'}';
|
'}';
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -343,6 +353,11 @@ public class JoinGamePacket implements MinecraftPacket {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.portalCooldown = ProtocolUtils.readVarInt(buf);
|
this.portalCooldown = ProtocolUtils.readVarInt(buf);
|
||||||
|
|
||||||
|
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_21_2)) {
|
||||||
|
this.seaLevel = ProtocolUtils.readVarInt(buf);
|
||||||
|
}
|
||||||
|
|
||||||
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_20_5)) {
|
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_20_5)) {
|
||||||
this.enforcesSecureChat = buf.readBoolean();
|
this.enforcesSecureChat = buf.readBoolean();
|
||||||
}
|
}
|
||||||
@@ -491,6 +506,10 @@ public class JoinGamePacket implements MinecraftPacket {
|
|||||||
|
|
||||||
ProtocolUtils.writeVarInt(buf, portalCooldown);
|
ProtocolUtils.writeVarInt(buf, portalCooldown);
|
||||||
|
|
||||||
|
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_21_2)) {
|
||||||
|
ProtocolUtils.writeVarInt(buf, seaLevel);
|
||||||
|
}
|
||||||
|
|
||||||
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_20_5)) {
|
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_20_5)) {
|
||||||
buf.writeBoolean(this.enforcesSecureChat);
|
buf.writeBoolean(this.enforcesSecureChat);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ public class RespawnPacket implements MinecraftPacket {
|
|||||||
private CompoundBinaryTag currentDimensionData; // 1.16.2+
|
private CompoundBinaryTag currentDimensionData; // 1.16.2+
|
||||||
private @Nullable Pair<String, Long> lastDeathPosition; // 1.19+
|
private @Nullable Pair<String, Long> lastDeathPosition; // 1.19+
|
||||||
private int portalCooldown; // 1.20+
|
private int portalCooldown; // 1.20+
|
||||||
|
private int seaLevel; // 1.21.2+
|
||||||
|
|
||||||
public RespawnPacket() {
|
public RespawnPacket() {
|
||||||
}
|
}
|
||||||
@@ -48,7 +49,8 @@ public class RespawnPacket implements MinecraftPacket {
|
|||||||
public RespawnPacket(int dimension, long partialHashedSeed, short difficulty, short gamemode,
|
public RespawnPacket(int dimension, long partialHashedSeed, short difficulty, short gamemode,
|
||||||
String levelType, byte dataToKeep, DimensionInfo dimensionInfo,
|
String levelType, byte dataToKeep, DimensionInfo dimensionInfo,
|
||||||
short previousGamemode, CompoundBinaryTag currentDimensionData,
|
short previousGamemode, CompoundBinaryTag currentDimensionData,
|
||||||
@Nullable Pair<String, Long> lastDeathPosition, int portalCooldown) {
|
@Nullable Pair<String, Long> lastDeathPosition, int portalCooldown,
|
||||||
|
int seaLevel) {
|
||||||
this.dimension = dimension;
|
this.dimension = dimension;
|
||||||
this.partialHashedSeed = partialHashedSeed;
|
this.partialHashedSeed = partialHashedSeed;
|
||||||
this.difficulty = difficulty;
|
this.difficulty = difficulty;
|
||||||
@@ -60,13 +62,15 @@ public class RespawnPacket implements MinecraftPacket {
|
|||||||
this.currentDimensionData = currentDimensionData;
|
this.currentDimensionData = currentDimensionData;
|
||||||
this.lastDeathPosition = lastDeathPosition;
|
this.lastDeathPosition = lastDeathPosition;
|
||||||
this.portalCooldown = portalCooldown;
|
this.portalCooldown = portalCooldown;
|
||||||
|
this.seaLevel = seaLevel;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static RespawnPacket fromJoinGame(JoinGamePacket joinGame) {
|
public static RespawnPacket fromJoinGame(JoinGamePacket joinGame) {
|
||||||
return new RespawnPacket(joinGame.getDimension(), joinGame.getPartialHashedSeed(),
|
return new RespawnPacket(joinGame.getDimension(), joinGame.getPartialHashedSeed(),
|
||||||
joinGame.getDifficulty(), joinGame.getGamemode(), joinGame.getLevelType(),
|
joinGame.getDifficulty(), joinGame.getGamemode(), joinGame.getLevelType(),
|
||||||
(byte) 0, joinGame.getDimensionInfo(), joinGame.getPreviousGamemode(),
|
(byte) 0, joinGame.getDimensionInfo(), joinGame.getPreviousGamemode(),
|
||||||
joinGame.getCurrentDimensionData(), joinGame.getLastDeathPosition(), joinGame.getPortalCooldown());
|
joinGame.getCurrentDimensionData(), joinGame.getLastDeathPosition(),
|
||||||
|
joinGame.getPortalCooldown(), joinGame.getSeaLevel());
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getDimension() {
|
public int getDimension() {
|
||||||
@@ -141,6 +145,14 @@ public class RespawnPacket implements MinecraftPacket {
|
|||||||
this.portalCooldown = portalCooldown;
|
this.portalCooldown = portalCooldown;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int getSeaLevel() {
|
||||||
|
return seaLevel;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSeaLevel(int seaLevel) {
|
||||||
|
this.seaLevel = seaLevel;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "Respawn{"
|
return "Respawn{"
|
||||||
@@ -155,6 +167,7 @@ public class RespawnPacket implements MinecraftPacket {
|
|||||||
+ ", previousGamemode=" + previousGamemode
|
+ ", previousGamemode=" + previousGamemode
|
||||||
+ ", dimensionData=" + currentDimensionData
|
+ ", dimensionData=" + currentDimensionData
|
||||||
+ ", portalCooldown=" + portalCooldown
|
+ ", portalCooldown=" + portalCooldown
|
||||||
|
+ ", seaLevel=" + seaLevel
|
||||||
+ '}';
|
+ '}';
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -204,6 +217,9 @@ public class RespawnPacket implements MinecraftPacket {
|
|||||||
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_20)) {
|
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_20)) {
|
||||||
this.portalCooldown = ProtocolUtils.readVarInt(buf);
|
this.portalCooldown = ProtocolUtils.readVarInt(buf);
|
||||||
}
|
}
|
||||||
|
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_21_2)) {
|
||||||
|
this.seaLevel = ProtocolUtils.readVarInt(buf);
|
||||||
|
}
|
||||||
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_20_2)) {
|
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_20_2)) {
|
||||||
this.dataToKeep = buf.readByte();
|
this.dataToKeep = buf.readByte();
|
||||||
}
|
}
|
||||||
@@ -262,6 +278,10 @@ public class RespawnPacket implements MinecraftPacket {
|
|||||||
ProtocolUtils.writeVarInt(buf, portalCooldown);
|
ProtocolUtils.writeVarInt(buf, portalCooldown);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_21_2)) {
|
||||||
|
ProtocolUtils.writeVarInt(buf, seaLevel);
|
||||||
|
}
|
||||||
|
|
||||||
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_20_2)) {
|
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_20_2)) {
|
||||||
buf.writeByte(dataToKeep);
|
buf.writeByte(dataToKeep);
|
||||||
}
|
}
|
||||||
|
|||||||
+15
@@ -188,6 +188,11 @@ public class UpsertPlayerInfoPacket implements MinecraftPacket {
|
|||||||
if (info.displayName != null) {
|
if (info.displayName != null) {
|
||||||
info.displayName.write(buf);
|
info.displayName.write(buf);
|
||||||
}
|
}
|
||||||
|
}),
|
||||||
|
UPDATE_LIST_ORDER((version, buf, info) -> { // read
|
||||||
|
info.listOrder = ProtocolUtils.readVarInt(buf);
|
||||||
|
}, (version, buf, info) -> { // write
|
||||||
|
ProtocolUtils.writeVarInt(buf, info.listOrder);
|
||||||
});
|
});
|
||||||
|
|
||||||
private final Read read;
|
private final Read read;
|
||||||
@@ -218,6 +223,7 @@ public class UpsertPlayerInfoPacket implements MinecraftPacket {
|
|||||||
private int gameMode;
|
private int gameMode;
|
||||||
@Nullable
|
@Nullable
|
||||||
private ComponentHolder displayName;
|
private ComponentHolder displayName;
|
||||||
|
private int listOrder;
|
||||||
@Nullable
|
@Nullable
|
||||||
private RemoteChatSession chatSession;
|
private RemoteChatSession chatSession;
|
||||||
|
|
||||||
@@ -250,6 +256,10 @@ public class UpsertPlayerInfoPacket implements MinecraftPacket {
|
|||||||
return displayName;
|
return displayName;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int getListOrder() {
|
||||||
|
return listOrder;
|
||||||
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
public RemoteChatSession getChatSession() {
|
public RemoteChatSession getChatSession() {
|
||||||
return chatSession;
|
return chatSession;
|
||||||
@@ -275,6 +285,10 @@ public class UpsertPlayerInfoPacket implements MinecraftPacket {
|
|||||||
this.displayName = displayName;
|
this.displayName = displayName;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setListOrder(int listOrder) {
|
||||||
|
this.listOrder = listOrder;
|
||||||
|
}
|
||||||
|
|
||||||
public void setChatSession(@Nullable RemoteChatSession chatSession) {
|
public void setChatSession(@Nullable RemoteChatSession chatSession) {
|
||||||
this.chatSession = chatSession;
|
this.chatSession = chatSession;
|
||||||
}
|
}
|
||||||
@@ -288,6 +302,7 @@ public class UpsertPlayerInfoPacket implements MinecraftPacket {
|
|||||||
", latency=" + latency +
|
", latency=" + latency +
|
||||||
", gameMode=" + gameMode +
|
", gameMode=" + gameMode +
|
||||||
", displayName=" + displayName +
|
", displayName=" + displayName +
|
||||||
|
", listOrder=" + listOrder +
|
||||||
", chatSession=" + chatSession +
|
", chatSession=" + chatSession +
|
||||||
'}';
|
'}';
|
||||||
}
|
}
|
||||||
|
|||||||
+6
-3
@@ -54,6 +54,7 @@ import java.util.Map;
|
|||||||
|
|
||||||
public class ComponentHolder {
|
public class ComponentHolder {
|
||||||
private static final Logger logger = LogManager.getLogger(ComponentHolder.class);
|
private static final Logger logger = LogManager.getLogger(ComponentHolder.class);
|
||||||
|
public static final int DEFAULT_MAX_STRING_SIZE = 262143;
|
||||||
|
|
||||||
private final ProtocolVersion version;
|
private final ProtocolVersion version;
|
||||||
private @MonotonicNonNull Component component;
|
private @MonotonicNonNull Component component;
|
||||||
@@ -172,21 +173,21 @@ public class ComponentHolder {
|
|||||||
case 1://BinaryTagTypes.BYTE:
|
case 1://BinaryTagTypes.BYTE:
|
||||||
byte[] bytes = new byte[jsonArray.size()];
|
byte[] bytes = new byte[jsonArray.size()];
|
||||||
for (int i = 0; i < bytes.length; i++) {
|
for (int i = 0; i < bytes.length; i++) {
|
||||||
bytes[i] = (Byte) jsonArray.get(i).getAsNumber();
|
bytes[i] = jsonArray.get(i).getAsNumber().byteValue();
|
||||||
}
|
}
|
||||||
|
|
||||||
return ByteArrayBinaryTag.byteArrayBinaryTag(bytes);
|
return ByteArrayBinaryTag.byteArrayBinaryTag(bytes);
|
||||||
case 3://BinaryTagTypes.INT:
|
case 3://BinaryTagTypes.INT:
|
||||||
int[] ints = new int[jsonArray.size()];
|
int[] ints = new int[jsonArray.size()];
|
||||||
for (int i = 0; i < ints.length; i++) {
|
for (int i = 0; i < ints.length; i++) {
|
||||||
ints[i] = (Integer) jsonArray.get(i).getAsNumber();
|
ints[i] = jsonArray.get(i).getAsNumber().intValue();
|
||||||
}
|
}
|
||||||
|
|
||||||
return IntArrayBinaryTag.intArrayBinaryTag(ints);
|
return IntArrayBinaryTag.intArrayBinaryTag(ints);
|
||||||
case 4://BinaryTagTypes.LONG:
|
case 4://BinaryTagTypes.LONG:
|
||||||
long[] longs = new long[jsonArray.size()];
|
long[] longs = new long[jsonArray.size()];
|
||||||
for (int i = 0; i < longs.length; i++) {
|
for (int i = 0; i < longs.length; i++) {
|
||||||
longs[i] = (Long) jsonArray.get(i).getAsNumber();
|
longs[i] = jsonArray.get(i).getAsNumber().longValue();
|
||||||
}
|
}
|
||||||
|
|
||||||
return LongArrayBinaryTag.longArrayBinaryTag(longs);
|
return LongArrayBinaryTag.longArrayBinaryTag(longs);
|
||||||
@@ -282,6 +283,8 @@ public class ComponentHolder {
|
|||||||
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_20_3)) {
|
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_20_3)) {
|
||||||
return new ComponentHolder(version,
|
return new ComponentHolder(version,
|
||||||
ProtocolUtils.readBinaryTag(buf, version, BinaryTagIO.reader()));
|
ProtocolUtils.readBinaryTag(buf, version, BinaryTagIO.reader()));
|
||||||
|
} else if (version.noLessThan(ProtocolVersion.MINECRAFT_1_13)) {
|
||||||
|
return new ComponentHolder(version, ProtocolUtils.readString(buf, DEFAULT_MAX_STRING_SIZE));
|
||||||
} else {
|
} else {
|
||||||
return new ComponentHolder(version, ProtocolUtils.readString(buf));
|
return new ComponentHolder(version, ProtocolUtils.readString(buf));
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-1
@@ -71,7 +71,8 @@ public class SessionChatHandler implements ChatHandler<SessionPlayerChatPacket>
|
|||||||
invalidChange(logger, player);
|
invalidChange(logger, player);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return this.player.getChatBuilderFactory().builder().message(packet.message)
|
return this.player.getChatBuilderFactory().builder()
|
||||||
|
.message(chatResult.getMessage().orElse(packet.getMessage()))
|
||||||
.setTimestamp(packet.timestamp)
|
.setTimestamp(packet.timestamp)
|
||||||
.setLastSeenMessages(newLastSeenMessages)
|
.setLastSeenMessages(newLastSeenMessages)
|
||||||
.toServer();
|
.toServer();
|
||||||
|
|||||||
-123
@@ -1,123 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2020-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.protocol.util;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.UUID;
|
|
||||||
import net.kyori.adventure.key.Key;
|
|
||||||
import net.kyori.adventure.nbt.CompoundBinaryTag;
|
|
||||||
import net.kyori.adventure.nbt.TagStringIO;
|
|
||||||
import net.kyori.adventure.nbt.api.BinaryTagHolder;
|
|
||||||
import net.kyori.adventure.text.Component;
|
|
||||||
import net.kyori.adventure.text.event.HoverEvent;
|
|
||||||
import net.kyori.adventure.text.event.HoverEvent.ShowEntity;
|
|
||||||
import net.kyori.adventure.text.event.HoverEvent.ShowItem;
|
|
||||||
import net.kyori.adventure.text.serializer.gson.LegacyHoverEventSerializer;
|
|
||||||
import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer;
|
|
||||||
import net.kyori.adventure.util.Codec.Decoder;
|
|
||||||
import net.kyori.adventure.util.Codec.Encoder;
|
|
||||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An implementation of {@link LegacyHoverEventSerializer} that implements the interface in the most
|
|
||||||
* literal, albeit "incompatible" way possible.
|
|
||||||
*/
|
|
||||||
public class VelocityLegacyHoverEventSerializer implements LegacyHoverEventSerializer {
|
|
||||||
|
|
||||||
public static final LegacyHoverEventSerializer INSTANCE =
|
|
||||||
new VelocityLegacyHoverEventSerializer();
|
|
||||||
|
|
||||||
private VelocityLegacyHoverEventSerializer() {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Key legacyIdToFakeKey(byte id) {
|
|
||||||
return Key.key("velocity", "legacy_hover/id_" + id);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public HoverEvent.@NonNull ShowItem deserializeShowItem(@NonNull Component input)
|
|
||||||
throws IOException {
|
|
||||||
String snbt = PlainTextComponentSerializer.plainText().serialize(input);
|
|
||||||
CompoundBinaryTag item = TagStringIO.get().asCompound(snbt);
|
|
||||||
|
|
||||||
Key key;
|
|
||||||
String idIfString = item.getString("id", "");
|
|
||||||
if (idIfString.isEmpty()) {
|
|
||||||
key = legacyIdToFakeKey(item.getByte("id"));
|
|
||||||
} else {
|
|
||||||
key = Key.key(idIfString);
|
|
||||||
}
|
|
||||||
|
|
||||||
byte count = item.getByte("Count", (byte) 1);
|
|
||||||
return ShowItem.of(key, count, BinaryTagHolder.binaryTagHolder(snbt));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public HoverEvent.@NonNull ShowEntity deserializeShowEntity(@NonNull Component input,
|
|
||||||
Decoder<Component, String, ? extends RuntimeException> componentDecoder) throws IOException {
|
|
||||||
String snbt = PlainTextComponentSerializer.plainText().serialize(input);
|
|
||||||
CompoundBinaryTag item = TagStringIO.get().asCompound(snbt);
|
|
||||||
|
|
||||||
Component name;
|
|
||||||
try {
|
|
||||||
name = componentDecoder.decode(item.getString("name"));
|
|
||||||
} catch (Exception e) {
|
|
||||||
name = Component.text(item.getString("name"));
|
|
||||||
}
|
|
||||||
|
|
||||||
return ShowEntity.of(Key.key(item.getString("type")),
|
|
||||||
UUID.fromString(item.getString("id")),
|
|
||||||
name);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public @NonNull Component serializeShowItem(HoverEvent.@NonNull ShowItem input)
|
|
||||||
throws IOException {
|
|
||||||
final CompoundBinaryTag.Builder builder = CompoundBinaryTag.builder()
|
|
||||||
.putByte("Count", (byte) input.count());
|
|
||||||
|
|
||||||
String keyAsString = input.item().asString();
|
|
||||||
if (keyAsString.startsWith("velocity:legacy_hover/id_")) {
|
|
||||||
builder.putByte("id", Byte.parseByte(keyAsString
|
|
||||||
.substring("velocity:legacy_hover/id_".length())));
|
|
||||||
} else {
|
|
||||||
builder.putString("id", keyAsString);
|
|
||||||
}
|
|
||||||
|
|
||||||
BinaryTagHolder nbt = input.nbt();
|
|
||||||
if (nbt != null) {
|
|
||||||
builder.put("tag", TagStringIO.get().asCompound(nbt.string()));
|
|
||||||
}
|
|
||||||
|
|
||||||
return Component.text(TagStringIO.get().asString(builder.build()));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public @NonNull Component serializeShowEntity(HoverEvent.@NonNull ShowEntity input,
|
|
||||||
Encoder<Component, String, ? extends RuntimeException> componentEncoder) throws IOException {
|
|
||||||
CompoundBinaryTag.Builder tag = CompoundBinaryTag.builder()
|
|
||||||
.putString("id", input.id().toString())
|
|
||||||
.putString("type", input.type().asString());
|
|
||||||
Component name = input.name();
|
|
||||||
if (name != null) {
|
|
||||||
tag.putString("name", componentEncoder.encode(name));
|
|
||||||
}
|
|
||||||
return Component.text(TagStringIO.get().asString(tag.build()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -159,12 +159,17 @@ public class KeyedVelocityTabList implements InternalTabList {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public TabListEntry buildEntry(GameProfile profile, @Nullable Component displayName, int latency,
|
public TabListEntry buildEntry(GameProfile profile, @Nullable Component displayName, int latency,
|
||||||
int gameMode,
|
int gameMode, @Nullable ChatSession chatSession, boolean listed) {
|
||||||
@Nullable ChatSession chatSession, boolean listed) {
|
|
||||||
return new KeyedVelocityTabListEntry(this, profile, displayName, latency, gameMode,
|
return new KeyedVelocityTabListEntry(this, profile, displayName, latency, gameMode,
|
||||||
chatSession == null ? null : chatSession.getIdentifiedKey());
|
chatSession == null ? null : chatSession.getIdentifiedKey());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public 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);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void processLegacy(LegacyPlayerListItemPacket packet) {
|
public void processLegacy(LegacyPlayerListItemPacket packet) {
|
||||||
// Packets are already forwarded on, so no need to do that here
|
// Packets are already forwarded on, so no need to do that here
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ package com.velocitypowered.proxy.tablist;
|
|||||||
|
|
||||||
import com.google.common.base.Preconditions;
|
import com.google.common.base.Preconditions;
|
||||||
import com.google.common.collect.Maps;
|
import com.google.common.collect.Maps;
|
||||||
|
import com.velocitypowered.api.network.ProtocolVersion;
|
||||||
import com.velocitypowered.api.proxy.Player;
|
import com.velocitypowered.api.proxy.Player;
|
||||||
import com.velocitypowered.api.proxy.player.ChatSession;
|
import com.velocitypowered.api.proxy.player.ChatSession;
|
||||||
import com.velocitypowered.api.proxy.player.TabListEntry;
|
import com.velocitypowered.api.proxy.player.TabListEntry;
|
||||||
@@ -89,7 +90,7 @@ public class VelocityTabList implements InternalTabList {
|
|||||||
} else {
|
} else {
|
||||||
entry = new VelocityTabListEntry(this, entry1.getProfile(),
|
entry = new VelocityTabListEntry(this, entry1.getProfile(),
|
||||||
entry1.getDisplayNameComponent().orElse(null),
|
entry1.getDisplayNameComponent().orElse(null),
|
||||||
entry1.getLatency(), entry1.getGameMode(), entry1.getChatSession(), entry1.isListed());
|
entry1.getLatency(), entry1.getGameMode(), entry1.getChatSession(), entry1.isListed(), entry1.getListOrder());
|
||||||
}
|
}
|
||||||
|
|
||||||
EnumSet<UpsertPlayerInfoPacket.Action> actions = EnumSet
|
EnumSet<UpsertPlayerInfoPacket.Action> actions = EnumSet
|
||||||
@@ -128,6 +129,11 @@ public class VelocityTabList implements InternalTabList {
|
|||||||
actions.add(UpsertPlayerInfoPacket.Action.UPDATE_LISTED);
|
actions.add(UpsertPlayerInfoPacket.Action.UPDATE_LISTED);
|
||||||
playerInfoEntry.setListed(entry.isListed());
|
playerInfoEntry.setListed(entry.isListed());
|
||||||
}
|
}
|
||||||
|
if (!Objects.equals(previousEntry.getListOrder(), entry.getListOrder())
|
||||||
|
&& player.getProtocolVersion().noLessThan(ProtocolVersion.MINECRAFT_1_21_2)) {
|
||||||
|
actions.add(UpsertPlayerInfoPacket.Action.UPDATE_LIST_ORDER);
|
||||||
|
playerInfoEntry.setListOrder(entry.getListOrder());
|
||||||
|
}
|
||||||
if (!Objects.equals(previousEntry.getChatSession(), entry.getChatSession())) {
|
if (!Objects.equals(previousEntry.getChatSession(), entry.getChatSession())) {
|
||||||
ChatSession from = entry.getChatSession();
|
ChatSession from = entry.getChatSession();
|
||||||
if (from != null) {
|
if (from != null) {
|
||||||
@@ -162,6 +168,11 @@ public class VelocityTabList implements InternalTabList {
|
|||||||
}
|
}
|
||||||
playerInfoEntry.setLatency(entry.getLatency());
|
playerInfoEntry.setLatency(entry.getLatency());
|
||||||
playerInfoEntry.setListed(entry.isListed());
|
playerInfoEntry.setListed(entry.isListed());
|
||||||
|
if (entry.getListOrder() != 0
|
||||||
|
&& player.getProtocolVersion().noLessThan(ProtocolVersion.MINECRAFT_1_21_2)) {
|
||||||
|
actions.add(UpsertPlayerInfoPacket.Action.UPDATE_LIST_ORDER);
|
||||||
|
playerInfoEntry.setListOrder(entry.getListOrder());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return entry;
|
return entry;
|
||||||
});
|
});
|
||||||
@@ -207,9 +218,9 @@ public class VelocityTabList implements InternalTabList {
|
|||||||
@Override
|
@Override
|
||||||
public TabListEntry buildEntry(GameProfile profile, @Nullable Component displayName, int latency,
|
public TabListEntry buildEntry(GameProfile profile, @Nullable Component displayName, int latency,
|
||||||
int gameMode,
|
int gameMode,
|
||||||
@Nullable ChatSession chatSession, boolean listed) {
|
@Nullable ChatSession chatSession, boolean listed, int listOrder) {
|
||||||
return new VelocityTabListEntry(this, profile, displayName, latency, gameMode, chatSession,
|
return new VelocityTabListEntry(this, profile, displayName, latency, gameMode, chatSession,
|
||||||
listed);
|
listed, listOrder);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -246,7 +257,8 @@ public class VelocityTabList implements InternalTabList {
|
|||||||
0,
|
0,
|
||||||
-1,
|
-1,
|
||||||
null,
|
null,
|
||||||
false
|
false,
|
||||||
|
0
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
@@ -274,6 +286,9 @@ public class VelocityTabList implements InternalTabList {
|
|||||||
if (actions.contains(UpsertPlayerInfoPacket.Action.UPDATE_LISTED)) {
|
if (actions.contains(UpsertPlayerInfoPacket.Action.UPDATE_LISTED)) {
|
||||||
currentEntry.setListedWithoutUpdate(entry.isListed());
|
currentEntry.setListedWithoutUpdate(entry.isListed());
|
||||||
}
|
}
|
||||||
|
if (actions.contains(UpsertPlayerInfoPacket.Action.UPDATE_LIST_ORDER)) {
|
||||||
|
currentEntry.setListOrderWithoutUpdate(entry.getListOrder());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -17,6 +17,7 @@
|
|||||||
|
|
||||||
package com.velocitypowered.proxy.tablist;
|
package com.velocitypowered.proxy.tablist;
|
||||||
|
|
||||||
|
import com.velocitypowered.api.network.ProtocolVersion;
|
||||||
import com.velocitypowered.api.proxy.player.ChatSession;
|
import com.velocitypowered.api.proxy.player.ChatSession;
|
||||||
import com.velocitypowered.api.proxy.player.TabList;
|
import com.velocitypowered.api.proxy.player.TabList;
|
||||||
import com.velocitypowered.api.proxy.player.TabListEntry;
|
import com.velocitypowered.api.proxy.player.TabListEntry;
|
||||||
@@ -38,6 +39,7 @@ public class VelocityTabListEntry implements TabListEntry {
|
|||||||
private int latency;
|
private int latency;
|
||||||
private int gameMode;
|
private int gameMode;
|
||||||
private boolean listed;
|
private boolean listed;
|
||||||
|
private int listOrder;
|
||||||
private @Nullable ChatSession session;
|
private @Nullable ChatSession session;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -45,7 +47,7 @@ public class VelocityTabListEntry implements TabListEntry {
|
|||||||
*/
|
*/
|
||||||
public VelocityTabListEntry(VelocityTabList tabList, GameProfile profile, Component displayName,
|
public VelocityTabListEntry(VelocityTabList tabList, GameProfile profile, Component displayName,
|
||||||
int latency,
|
int latency,
|
||||||
int gameMode, @Nullable ChatSession session, boolean listed) {
|
int gameMode, @Nullable ChatSession session, boolean listed, int listOrder) {
|
||||||
this.tabList = tabList;
|
this.tabList = tabList;
|
||||||
this.profile = profile;
|
this.profile = profile;
|
||||||
this.displayName = displayName;
|
this.displayName = displayName;
|
||||||
@@ -53,6 +55,7 @@ public class VelocityTabListEntry implements TabListEntry {
|
|||||||
this.gameMode = gameMode;
|
this.gameMode = gameMode;
|
||||||
this.session = session;
|
this.session = session;
|
||||||
this.listed = listed;
|
this.listed = listed;
|
||||||
|
this.listOrder = listOrder;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -150,4 +153,24 @@ public class VelocityTabListEntry implements TabListEntry {
|
|||||||
void setListedWithoutUpdate(boolean listed) {
|
void setListedWithoutUpdate(boolean listed) {
|
||||||
this.listed = listed;
|
this.listed = listed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getListOrder() {
|
||||||
|
return listOrder;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public VelocityTabListEntry setListOrder(int listOrder) {
|
||||||
|
this.listOrder = listOrder;
|
||||||
|
if (tabList.getPlayer().getProtocolVersion().noLessThan(ProtocolVersion.MINECRAFT_1_21_2)) {
|
||||||
|
UpsertPlayerInfoPacket.Entry upsertEntry = this.tabList.createRawEntry(this);
|
||||||
|
upsertEntry.setListOrder(listOrder);
|
||||||
|
tabList.emitActionRaw(UpsertPlayerInfoPacket.Action.UPDATE_LIST_ORDER, upsertEntry);
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setListOrderWithoutUpdate(int listOrder) {
|
||||||
|
this.listOrder = listOrder;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -230,7 +230,7 @@ public class BrigadierCommandTests extends CommandTestSuite {
|
|||||||
final Exception wrapper = assertThrows(CompletionException.class, () ->
|
final Exception wrapper = assertThrows(CompletionException.class, () ->
|
||||||
manager.executeAsync(source, "hello").join());
|
manager.executeAsync(source, "hello").join());
|
||||||
|
|
||||||
assertSame(expected, wrapper.getCause().getCause());
|
assertSame(expected, wrapper.getCause());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Suggestions
|
// Suggestions
|
||||||
|
|||||||
@@ -29,9 +29,9 @@ import com.velocitypowered.api.permission.Tristate;
|
|||||||
import com.velocitypowered.api.proxy.Player;
|
import com.velocitypowered.api.proxy.Player;
|
||||||
import com.velocitypowered.proxy.event.MockEventManager;
|
import com.velocitypowered.proxy.event.MockEventManager;
|
||||||
import com.velocitypowered.proxy.event.VelocityEventManager;
|
import com.velocitypowered.proxy.event.VelocityEventManager;
|
||||||
|
import com.velocitypowered.proxy.testutil.FakePluginManager;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import org.junit.jupiter.api.AfterAll;
|
|
||||||
import org.junit.jupiter.api.BeforeAll;
|
import org.junit.jupiter.api.BeforeAll;
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
|
||||||
@@ -47,19 +47,9 @@ abstract class CommandTestSuite {
|
|||||||
eventManager = new MockEventManager();
|
eventManager = new MockEventManager();
|
||||||
}
|
}
|
||||||
|
|
||||||
@AfterAll
|
|
||||||
static void afterAll() {
|
|
||||||
try {
|
|
||||||
eventManager.shutdown();
|
|
||||||
eventManager = null;
|
|
||||||
} catch (final InterruptedException e) {
|
|
||||||
Thread.currentThread().interrupt();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
void setUp() {
|
void setUp() {
|
||||||
this.manager = new VelocityCommandManager(eventManager);
|
this.manager = new VelocityCommandManager(eventManager, new FakePluginManager());
|
||||||
}
|
}
|
||||||
|
|
||||||
final void assertHandled(final String input) {
|
final void assertHandled(final String input) {
|
||||||
|
|||||||
@@ -41,20 +41,24 @@ import org.junit.jupiter.api.TestInstance;
|
|||||||
public class EventTest {
|
public class EventTest {
|
||||||
|
|
||||||
public static final String CONTINUATION_TEST_THREAD_NAME = "Continuation test thread";
|
public static final String CONTINUATION_TEST_THREAD_NAME = "Continuation test thread";
|
||||||
private final VelocityEventManager eventManager =
|
private final FakePluginManager pluginManager = new FakePluginManager();
|
||||||
new VelocityEventManager(new FakePluginManager());
|
private final VelocityEventManager eventManager = new VelocityEventManager(pluginManager);
|
||||||
|
|
||||||
@AfterAll
|
@AfterAll
|
||||||
void shutdown() throws Exception {
|
void shutdown() throws Exception {
|
||||||
eventManager.shutdown();
|
pluginManager.shutdown();
|
||||||
}
|
}
|
||||||
|
|
||||||
static final class TestEvent {
|
static final class TestEvent {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void assertSyncThread(final Thread thread) {
|
||||||
|
assertEquals(Thread.currentThread(), thread);
|
||||||
|
}
|
||||||
|
|
||||||
static void assertAsyncThread(final Thread thread) {
|
static void assertAsyncThread(final Thread thread) {
|
||||||
assertTrue(thread.getName().contains("Velocity Async Event Executor"));
|
assertTrue(thread.getName().contains("Test Async Thread"));
|
||||||
}
|
}
|
||||||
|
|
||||||
static void assertContinuationThread(final Thread thread) {
|
static void assertContinuationThread(final Thread thread) {
|
||||||
@@ -90,6 +94,7 @@ public class EventTest {
|
|||||||
eventManager.fire(new TestEvent()).get();
|
eventManager.fire(new TestEvent()).get();
|
||||||
} finally {
|
} finally {
|
||||||
eventManager.unregisterListeners(FakePluginManager.PLUGIN_A);
|
eventManager.unregisterListeners(FakePluginManager.PLUGIN_A);
|
||||||
|
eventManager.unregisterListeners(FakePluginManager.PLUGIN_B);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check that the order is A < B < C.
|
// Check that the order is A < B < C.
|
||||||
@@ -119,6 +124,7 @@ public class EventTest {
|
|||||||
eventManager.fire(new TestEvent()).get();
|
eventManager.fire(new TestEvent()).get();
|
||||||
} finally {
|
} finally {
|
||||||
eventManager.unregisterListeners(FakePluginManager.PLUGIN_A);
|
eventManager.unregisterListeners(FakePluginManager.PLUGIN_A);
|
||||||
|
eventManager.unregisterListeners(FakePluginManager.PLUGIN_B);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check that the order is A < B < C.
|
// Check that the order is A < B < C.
|
||||||
@@ -126,6 +132,26 @@ public class EventTest {
|
|||||||
assertTrue(listener2Invoked.get() < listener3Invoked.get(), "Listener C invoked before B!");
|
assertTrue(listener2Invoked.get() < listener3Invoked.get(), "Listener C invoked before B!");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testAlwaysSync() throws Exception {
|
||||||
|
final AlwaysSyncListener listener = new AlwaysSyncListener();
|
||||||
|
handleMethodListener(listener);
|
||||||
|
assertSyncThread(listener.thread);
|
||||||
|
assertEquals(1, listener.result);
|
||||||
|
}
|
||||||
|
|
||||||
|
static final class AlwaysSyncListener {
|
||||||
|
|
||||||
|
@MonotonicNonNull Thread thread;
|
||||||
|
int result;
|
||||||
|
|
||||||
|
@Subscribe(async = false)
|
||||||
|
void sync(TestEvent event) {
|
||||||
|
result++;
|
||||||
|
thread = Thread.currentThread();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testAlwaysAsync() throws Exception {
|
void testAlwaysAsync() throws Exception {
|
||||||
final AlwaysAsyncListener listener = new AlwaysAsyncListener();
|
final AlwaysAsyncListener listener = new AlwaysAsyncListener();
|
||||||
@@ -143,7 +169,7 @@ public class EventTest {
|
|||||||
@MonotonicNonNull Thread threadC;
|
@MonotonicNonNull Thread threadC;
|
||||||
int result;
|
int result;
|
||||||
|
|
||||||
@Subscribe
|
@Subscribe(async = true, order = PostOrder.EARLY)
|
||||||
void firstAsync(TestEvent event) {
|
void firstAsync(TestEvent event) {
|
||||||
result++;
|
result++;
|
||||||
threadA = Thread.currentThread();
|
threadA = Thread.currentThread();
|
||||||
@@ -155,50 +181,93 @@ public class EventTest {
|
|||||||
return EventTask.async(() -> result++);
|
return EventTask.async(() -> result++);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Subscribe
|
@Subscribe(order = PostOrder.LATE)
|
||||||
void thirdAsync(TestEvent event) {
|
void thirdAsync(TestEvent event) {
|
||||||
result++;
|
result++;
|
||||||
threadC = Thread.currentThread();
|
threadC = Thread.currentThread();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testSometimesAsync() throws Exception {
|
||||||
|
final SometimesAsyncListener listener = new SometimesAsyncListener();
|
||||||
|
handleMethodListener(listener);
|
||||||
|
assertSyncThread(listener.threadA);
|
||||||
|
assertSyncThread(listener.threadB);
|
||||||
|
assertAsyncThread(listener.threadC);
|
||||||
|
assertAsyncThread(listener.threadD);
|
||||||
|
assertEquals(3, listener.result);
|
||||||
|
}
|
||||||
|
|
||||||
|
static final class SometimesAsyncListener {
|
||||||
|
|
||||||
|
@MonotonicNonNull Thread threadA;
|
||||||
|
@MonotonicNonNull Thread threadB;
|
||||||
|
@MonotonicNonNull Thread threadC;
|
||||||
|
@MonotonicNonNull Thread threadD;
|
||||||
|
int result;
|
||||||
|
|
||||||
|
@Subscribe(order = PostOrder.EARLY, async = false)
|
||||||
|
void notAsync(TestEvent event) {
|
||||||
|
result++;
|
||||||
|
threadA = Thread.currentThread();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Subscribe
|
||||||
|
EventTask notAsyncUntilTask(TestEvent event) {
|
||||||
|
threadB = Thread.currentThread();
|
||||||
|
return EventTask.async(() -> {
|
||||||
|
threadC = Thread.currentThread();
|
||||||
|
result++;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Subscribe(order = PostOrder.LATE, async = false)
|
||||||
|
void stillAsyncAfterTask(TestEvent event) {
|
||||||
|
threadD = Thread.currentThread();
|
||||||
|
result++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testContinuation() throws Exception {
|
void testContinuation() throws Exception {
|
||||||
final ContinuationListener listener = new ContinuationListener();
|
final ContinuationListener listener = new ContinuationListener();
|
||||||
handleMethodListener(listener);
|
handleMethodListener(listener);
|
||||||
assertAsyncThread(listener.thread1);
|
assertSyncThread(listener.threadA);
|
||||||
assertAsyncThread(listener.thread2);
|
assertSyncThread(listener.threadB);
|
||||||
assertContinuationThread(listener.thread2Custom);
|
assertAsyncThread(listener.threadC);
|
||||||
assertAsyncThread(listener.thread3);
|
|
||||||
assertEquals(2, listener.value.get());
|
assertEquals(2, listener.value.get());
|
||||||
}
|
}
|
||||||
|
|
||||||
static final class ContinuationListener {
|
static final class ContinuationListener {
|
||||||
|
|
||||||
@MonotonicNonNull Thread thread1;
|
@MonotonicNonNull Thread threadA;
|
||||||
@MonotonicNonNull Thread thread2;
|
@MonotonicNonNull Thread threadB;
|
||||||
@MonotonicNonNull Thread thread2Custom;
|
@MonotonicNonNull Thread threadC;
|
||||||
@MonotonicNonNull Thread thread3;
|
|
||||||
|
|
||||||
final AtomicInteger value = new AtomicInteger();
|
final AtomicInteger value = new AtomicInteger();
|
||||||
|
|
||||||
@Subscribe(order = PostOrder.EARLY)
|
@Subscribe(order = PostOrder.EARLY)
|
||||||
EventTask continuation(TestEvent event) {
|
EventTask continuation(TestEvent event) {
|
||||||
thread1 = Thread.currentThread();
|
threadA = Thread.currentThread();
|
||||||
return EventTask.withContinuation(continuation -> {
|
return EventTask.withContinuation(continuation -> {
|
||||||
value.incrementAndGet();
|
value.incrementAndGet();
|
||||||
thread2 = Thread.currentThread();
|
threadB = Thread.currentThread();
|
||||||
new Thread(() -> {
|
new Thread(() -> {
|
||||||
thread2Custom = Thread.currentThread();
|
try {
|
||||||
|
Thread.sleep(100);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
value.incrementAndGet();
|
value.incrementAndGet();
|
||||||
continuation.resume();
|
continuation.resume();
|
||||||
}, CONTINUATION_TEST_THREAD_NAME).start();
|
}).start();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Subscribe(order = PostOrder.LATE)
|
@Subscribe(order = PostOrder.LATE)
|
||||||
void afterContinuation(TestEvent event) {
|
void afterContinuation(TestEvent event) {
|
||||||
thread3 = Thread.currentThread();
|
threadC = Thread.currentThread();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -207,9 +276,9 @@ public class EventTest {
|
|||||||
final ResumeContinuationImmediatelyListener listener =
|
final ResumeContinuationImmediatelyListener listener =
|
||||||
new ResumeContinuationImmediatelyListener();
|
new ResumeContinuationImmediatelyListener();
|
||||||
handleMethodListener(listener);
|
handleMethodListener(listener);
|
||||||
assertAsyncThread(listener.threadA);
|
assertSyncThread(listener.threadA);
|
||||||
assertAsyncThread(listener.threadB);
|
assertSyncThread(listener.threadB);
|
||||||
assertAsyncThread(listener.threadC);
|
assertSyncThread(listener.threadC);
|
||||||
assertEquals(2, listener.result);
|
assertEquals(2, listener.result);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -241,42 +310,44 @@ public class EventTest {
|
|||||||
void testContinuationParameter() throws Exception {
|
void testContinuationParameter() throws Exception {
|
||||||
final ContinuationParameterListener listener = new ContinuationParameterListener();
|
final ContinuationParameterListener listener = new ContinuationParameterListener();
|
||||||
handleMethodListener(listener);
|
handleMethodListener(listener);
|
||||||
assertAsyncThread(listener.thread1);
|
assertSyncThread(listener.threadA);
|
||||||
assertAsyncThread(listener.thread2);
|
assertSyncThread(listener.threadB);
|
||||||
assertContinuationThread(listener.thread2Custom);
|
assertAsyncThread(listener.threadC);
|
||||||
assertAsyncThread(listener.thread3);
|
|
||||||
assertEquals(3, listener.result.get());
|
assertEquals(3, listener.result.get());
|
||||||
}
|
}
|
||||||
|
|
||||||
static final class ContinuationParameterListener {
|
static final class ContinuationParameterListener {
|
||||||
|
|
||||||
@MonotonicNonNull Thread thread1;
|
@MonotonicNonNull Thread threadA;
|
||||||
@MonotonicNonNull Thread thread2;
|
@MonotonicNonNull Thread threadB;
|
||||||
@MonotonicNonNull Thread thread2Custom;
|
@MonotonicNonNull Thread threadC;
|
||||||
@MonotonicNonNull Thread thread3;
|
|
||||||
|
|
||||||
final AtomicInteger result = new AtomicInteger();
|
final AtomicInteger result = new AtomicInteger();
|
||||||
|
|
||||||
@Subscribe
|
@Subscribe
|
||||||
void resume(TestEvent event, Continuation continuation) {
|
void resume(TestEvent event, Continuation continuation) {
|
||||||
thread1 = Thread.currentThread();
|
threadA = Thread.currentThread();
|
||||||
result.incrementAndGet();
|
result.incrementAndGet();
|
||||||
continuation.resume();
|
continuation.resume();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Subscribe(order = PostOrder.LATE)
|
@Subscribe(order = PostOrder.LATE)
|
||||||
void resumeFromCustomThread(TestEvent event, Continuation continuation) {
|
void resumeFromCustomThread(TestEvent event, Continuation continuation) {
|
||||||
thread2 = Thread.currentThread();
|
threadB = Thread.currentThread();
|
||||||
new Thread(() -> {
|
new Thread(() -> {
|
||||||
thread2Custom = Thread.currentThread();
|
try {
|
||||||
|
Thread.sleep(100);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
result.incrementAndGet();
|
result.incrementAndGet();
|
||||||
continuation.resume();
|
continuation.resume();
|
||||||
}, CONTINUATION_TEST_THREAD_NAME).start();
|
}).start();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Subscribe(order = PostOrder.LAST)
|
@Subscribe(order = PostOrder.LAST)
|
||||||
void afterCustomThread(TestEvent event, Continuation continuation) {
|
void afterCustomThread(TestEvent event, Continuation continuation) {
|
||||||
thread3 = Thread.currentThread();
|
threadC = Thread.currentThread();
|
||||||
result.incrementAndGet();
|
result.incrementAndGet();
|
||||||
continuation.resume();
|
continuation.resume();
|
||||||
}
|
}
|
||||||
@@ -328,8 +399,7 @@ public class EventTest {
|
|||||||
+ "the second is the fancy continuation");
|
+ "the second is the fancy continuation");
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
new TypeToken<TriConsumer<Object, Object, FancyContinuation>>() {
|
new TypeToken<TriConsumer<Object, Object, FancyContinuation>>() {},
|
||||||
},
|
|
||||||
invokeFunction -> (instance, event) ->
|
invokeFunction -> (instance, event) ->
|
||||||
EventTask.withContinuation(continuation ->
|
EventTask.withContinuation(continuation ->
|
||||||
invokeFunction.accept(instance, event, new FancyContinuationImpl(continuation))
|
invokeFunction.accept(instance, event, new FancyContinuationImpl(continuation))
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ public class ProtocolUtilsTest {
|
|||||||
private void writeReadTestOld(ByteBuf buf, int test) {
|
private void writeReadTestOld(ByteBuf buf, int test) {
|
||||||
buf.clear();
|
buf.clear();
|
||||||
writeVarIntOld(buf, test);
|
writeVarIntOld(buf, test);
|
||||||
assertEquals(test, ProtocolUtils.readVarIntSafely(buf));
|
assertEquals(test, ProtocolUtils.readVarInt(buf));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -103,7 +103,7 @@ public class ProtocolUtilsTest {
|
|||||||
"Encoding of " + i + " was invalid");
|
"Encoding of " + i + " was invalid");
|
||||||
|
|
||||||
assertEquals(i, oldReadVarIntSafely(varintNew));
|
assertEquals(i, oldReadVarIntSafely(varintNew));
|
||||||
assertEquals(i, ProtocolUtils.readVarIntSafely(varintOld));
|
assertEquals(i, ProtocolUtils.readVarInt(varintOld));
|
||||||
|
|
||||||
varintNew.clear();
|
varintNew.clear();
|
||||||
varintOld.clear();
|
varintOld.clear();
|
||||||
|
|||||||
@@ -18,14 +18,16 @@
|
|||||||
package com.velocitypowered.proxy.testutil;
|
package com.velocitypowered.proxy.testutil;
|
||||||
|
|
||||||
import com.google.common.collect.ImmutableList;
|
import com.google.common.collect.ImmutableList;
|
||||||
|
import com.google.common.util.concurrent.ThreadFactoryBuilder;
|
||||||
import com.velocitypowered.api.plugin.PluginContainer;
|
import com.velocitypowered.api.plugin.PluginContainer;
|
||||||
import com.velocitypowered.api.plugin.PluginDescription;
|
import com.velocitypowered.api.plugin.PluginDescription;
|
||||||
import com.velocitypowered.api.plugin.PluginManager;
|
import com.velocitypowered.api.plugin.PluginManager;
|
||||||
|
import com.velocitypowered.proxy.plugin.virtual.VelocityVirtualPlugin;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.concurrent.ExecutorService;
|
import java.util.concurrent.ExecutorService;
|
||||||
import java.util.concurrent.ForkJoinPool;
|
import java.util.concurrent.Executors;
|
||||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -36,15 +38,23 @@ public class FakePluginManager implements PluginManager {
|
|||||||
public static final Object PLUGIN_A = new Object();
|
public static final Object PLUGIN_A = new Object();
|
||||||
public static final Object PLUGIN_B = new Object();
|
public static final Object PLUGIN_B = new Object();
|
||||||
|
|
||||||
private static final PluginContainer PC_A = new FakePluginContainer("a", PLUGIN_A);
|
private final PluginContainer containerA = new FakePluginContainer("a", PLUGIN_A);
|
||||||
private static final PluginContainer PC_B = new FakePluginContainer("b", PLUGIN_B);
|
private final PluginContainer containerB = new FakePluginContainer("b", PLUGIN_B);
|
||||||
|
private final PluginContainer containerVelocity = new FakePluginContainer("velocity",
|
||||||
|
VelocityVirtualPlugin.INSTANCE);
|
||||||
|
|
||||||
|
private ExecutorService service = Executors.newCachedThreadPool(
|
||||||
|
new ThreadFactoryBuilder().setNameFormat("Test Async Thread").setDaemon(true).build()
|
||||||
|
);
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public @NonNull Optional<PluginContainer> fromInstance(@NonNull Object instance) {
|
public @NonNull Optional<PluginContainer> fromInstance(@NonNull Object instance) {
|
||||||
if (instance == PLUGIN_A) {
|
if (instance == PLUGIN_A) {
|
||||||
return Optional.of(PC_A);
|
return Optional.of(containerA);
|
||||||
} else if (instance == PLUGIN_B) {
|
} else if (instance == PLUGIN_B) {
|
||||||
return Optional.of(PC_B);
|
return Optional.of(containerB);
|
||||||
|
} else if (instance == VelocityVirtualPlugin.INSTANCE) {
|
||||||
|
return Optional.of(containerVelocity);
|
||||||
} else {
|
} else {
|
||||||
return Optional.empty();
|
return Optional.empty();
|
||||||
}
|
}
|
||||||
@@ -54,9 +64,11 @@ public class FakePluginManager implements PluginManager {
|
|||||||
public @NonNull Optional<PluginContainer> getPlugin(@NonNull String id) {
|
public @NonNull Optional<PluginContainer> getPlugin(@NonNull String id) {
|
||||||
switch (id) {
|
switch (id) {
|
||||||
case "a":
|
case "a":
|
||||||
return Optional.of(PC_A);
|
return Optional.of(containerA);
|
||||||
case "b":
|
case "b":
|
||||||
return Optional.of(PC_B);
|
return Optional.of(containerB);
|
||||||
|
case "velocity":
|
||||||
|
return Optional.of(containerVelocity);
|
||||||
default:
|
default:
|
||||||
return Optional.empty();
|
return Optional.empty();
|
||||||
}
|
}
|
||||||
@@ -64,7 +76,7 @@ public class FakePluginManager implements PluginManager {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public @NonNull Collection<PluginContainer> getPlugins() {
|
public @NonNull Collection<PluginContainer> getPlugins() {
|
||||||
return ImmutableList.of(PC_A, PC_B);
|
return ImmutableList.of(containerVelocity, containerA, containerB);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -77,16 +89,18 @@ public class FakePluginManager implements PluginManager {
|
|||||||
throw new UnsupportedOperationException();
|
throw new UnsupportedOperationException();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class FakePluginContainer implements PluginContainer {
|
public void shutdown() {
|
||||||
|
this.service.shutdownNow();
|
||||||
|
}
|
||||||
|
|
||||||
|
private class FakePluginContainer implements PluginContainer {
|
||||||
|
|
||||||
private final String id;
|
private final String id;
|
||||||
private final Object instance;
|
private final Object instance;
|
||||||
private final ExecutorService service;
|
|
||||||
|
|
||||||
private FakePluginContainer(String id, Object instance) {
|
private FakePluginContainer(String id, Object instance) {
|
||||||
this.id = id;
|
this.id = id;
|
||||||
this.instance = instance;
|
this.instance = instance;
|
||||||
this.service = ForkJoinPool.commonPool();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
Reference in New Issue
Block a user