geforkt von Mirrors/Velocity
Update 1.21.2 client support #5
2
.github/ISSUE_TEMPLATE/bug-report.yml
vendored
2
.github/ISSUE_TEMPLATE/bug-report.yml
vendored
@ -44,7 +44,7 @@ body:
|
||||
```
|
||||
[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]: velocitypowered.com - GitHub
|
||||
[17:44:10 INFO]: PaperMC - GitHub
|
||||
```
|
||||
</details>
|
||||
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
|
||||
* the given command does not implement a registrable {@link Command} subinterface
|
||||
* @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) {
|
||||
register(metaBuilder(alias).aliases(otherAliases).build(), command);
|
||||
}
|
||||
@ -55,7 +57,9 @@ public interface CommandManager {
|
||||
*
|
||||
* @param command the command to register
|
||||
* @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);
|
||||
|
||||
/**
|
||||
|
@ -45,10 +45,28 @@ public interface EventManager {
|
||||
* @param postOrder the order in which events should be posted to the handler
|
||||
* @param handler the handler to register
|
||||
* @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,
|
||||
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
|
||||
* 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 {
|
||||
|
||||
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.
|
||||
*
|
||||
* @deprecated specify the order using {@link #priority()} instead
|
||||
* @return the order
|
||||
*/
|
||||
@Deprecated
|
||||
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,
|
||||
* all event handlers run asynchronously by default. You are encouraged to determine whether or
|
||||
* not to enable it now. This option is being provided as a migration aid.</strong></p>
|
||||
* <p>Note that due to compatibility constraints, you must specify {@link PostOrder#CUSTOM}
|
||||
* in order to use this field.</p>
|
||||
*
|
||||
* <p>If this method returns {@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>
|
||||
* @return the priority
|
||||
*/
|
||||
short priority() default Short.MIN_VALUE;
|
||||
|
||||
/**
|
||||
* 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
|
||||
* handler targeting that event type will be executed asynchronously.</p>
|
||||
* <p>For performance (for instance, if you use {@link EventTask#withContinuation}), you can
|
||||
* 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
|
||||
*/
|
||||
|
@ -51,6 +51,13 @@ public final class PlayerChatEvent implements ResultedEvent<PlayerChatEvent.Chat
|
||||
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
|
||||
public void setResult(ChatResult 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_3(765, "1.20.3", "1.20.4"),
|
||||
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;
|
||||
|
||||
|
@ -26,11 +26,20 @@ public interface InboundConnection {
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
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.
|
||||
*
|
||||
|
@ -68,6 +68,20 @@ public interface PlayerSettings {
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
@ -84,4 +98,13 @@ public interface PlayerSettings {
|
||||
LEFT,
|
||||
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
|
||||
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,
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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}.
|
||||
*
|
||||
@ -161,6 +182,7 @@ public interface TabListEntry extends KeyIdentifiable {
|
||||
private int latency = 0;
|
||||
private int gameMode = 0;
|
||||
private boolean listed = true;
|
||||
private int listOrder = 0;
|
||||
|
||||
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
|
||||
* @return ${code this}, for chaining
|
||||
@ -254,6 +276,19 @@ public interface TabListEntry extends KeyIdentifiable {
|
||||
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}.
|
||||
*
|
||||
@ -266,7 +301,7 @@ public interface TabListEntry extends KeyIdentifiable {
|
||||
if (profile == null) {
|
||||
throw new IllegalStateException("The GameProfile must be set when building a TabListEntry");
|
||||
}
|
||||
return tabList.buildEntry(profile, displayName, latency, gameMode, chatSession, listed);
|
||||
return tabList.buildEntry(profile, displayName, latency, gameMode, chatSession, listed, listOrder);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,2 +1,2 @@
|
||||
group=com.velocitypowered
|
||||
version=3.3.0-SNAPSHOT
|
||||
version=3.4.0-SNAPSHOT
|
||||
|
@ -2,8 +2,8 @@
|
||||
configurate3 = "3.7.3"
|
||||
configurate4 = "4.1.2"
|
||||
flare = "2.0.1"
|
||||
log4j = "2.22.1"
|
||||
netty = "4.1.106.Final"
|
||||
log4j = "2.24.1"
|
||||
netty = "4.1.114.Final"
|
||||
|
||||
[plugins]
|
||||
indra-publishing = "net.kyori.indra.publishing:2.0.6"
|
||||
@ -12,13 +12,14 @@ spotless = "com.diffplug.spotless:6.25.0"
|
||||
|
||||
[libraries]
|
||||
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"
|
||||
auto-service = "com.google.auto.service:auto-service:1.0.1"
|
||||
auto-service-annotations = "com.google.auto.service:auto-service-annotations:1.0.1"
|
||||
brigadier = "com.velocitypowered:velocity-brigadier:1.0.0-SNAPSHOT"
|
||||
bstats = "org.bstats:bstats-base:3.0.2"
|
||||
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"
|
||||
checkstyle = "com.puppycrawl.tools:checkstyle:10.9.3"
|
||||
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-yaml = { module = "org.spongepowered:configurate-yaml", version.ref = "configurate4" }
|
||||
configurate4-gson = { module = "org.spongepowered:configurate-gson", version.ref = "configurate4" }
|
||||
disruptor = "com.lmax:disruptor:3.4.4"
|
||||
fastutil = "it.unimi.dsi:fastutil:8.5.12"
|
||||
disruptor = "com.lmax:disruptor:4.0.0"
|
||||
fastutil = "it.unimi.dsi:fastutil:8.5.15"
|
||||
flare-core = { module = "space.vectrix.flare:flare", version.ref = "flare" }
|
||||
flare-fastutil = { module = "space.vectrix.flare:flare-fastutil", version.ref = "flare" }
|
||||
jline = "org.jline:jline-terminal-jansi:3.23.0"
|
||||
jline = "org.jline:jline-terminal-jansi:3.27.1"
|
||||
jopt = "net.sf.jopt-simple:jopt-simple:5.0.4"
|
||||
junit = "org.junit.jupiter:junit-jupiter:5.10.2"
|
||||
jspecify = "org.jspecify:jspecify:0.3.0"
|
||||
kyori-ansi = "net.kyori:ansi:1.0.3"
|
||||
kyori-ansi = "net.kyori:ansi:1.1.0"
|
||||
guava = "com.google.guava:guava:25.1-jre"
|
||||
gson = "com.google.code.gson:gson:2.10.1"
|
||||
guice = "com.google.inject:guice:6.0.0"
|
||||
|
@ -1,31 +1,64 @@
|
||||
# velocity-natives
|
||||
|
||||
This directory contains native acceleration code for Velocity, along with
|
||||
traditional Java fallbacks.
|
||||
This directory contains native acceleration code for Velocity, along with 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.
|
||||
Compiled on CentOS 7.
|
||||
* **Rationale**: Using a native zlib wrapper, we can avoid multiple trips into Java just to copy memory around.
|
||||
Encryption is based on OpenSSL for Linux, which is the most widely-used encryption library in the world.
|
||||
OpenSSL has had several different ABIs over the years, so we provide multiple versions of the native
|
||||
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)
|
||||
* **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.
|
||||
`velocity-natives` is built for the following platforms:
|
||||
|
||||
## 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
|
||||
(defined as those being released in or after 2014).
|
||||
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.
|
||||
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),
|
||||
but interest in other systems is minimal at best, thus we focus on Linux x86_64 and aarch64 as they
|
||||
are commonly used platforms.
|
||||
- Ubuntu 20.04 for OpenSSL 1.1.x support and for compression
|
||||
- Ubuntu 22.04 for OpenSSL 3.x.x support
|
||||
- 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.
|
9
native/build-support/alpine.Dockerfile
Normale Datei
9
native/build-support/alpine.Dockerfile
Normale Datei
@ -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
native/build-support/build-all-linux-natives.sh
Ausführbare Datei
33
native/build-support/build-all-linux-natives.sh
Ausführbare Datei
@ -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
|
||||
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
|
||||
|
||||
echo "Compiling libdeflate..."
|
||||
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 ..
|
||||
|
||||
# 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"
|
||||
ARCH=$(uname -m)
|
||||
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 \
|
||||
libdeflate/build/libdeflate.a -o src/main/resources/linux_$ARCH/velocity-compress.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
|
||||
libdeflate/build/libdeflate.a -o src/main/resources/linux_$ARCH/velocity-compress$suffix.so
|
34
native/build-support/compile-linux-crypto.sh
Ausführbare Datei
34
native/build-support/compile-linux-crypto.sh
Ausführbare Datei
@ -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
|
||||
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
|
||||
|
||||
echo "Compiling libdeflate..."
|
||||
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 ..
|
||||
|
||||
CFLAGS="-O2 -I$JAVA_HOME/include/ -I$JAVA_HOME/include/darwin/ -fPIC -shared -Wall -Werror -fomit-frame-pointer"
|
20
native/build-support/ubuntu-focal.Dockerfile
Normale Datei
20
native/build-support/ubuntu-focal.Dockerfile
Normale Datei
@ -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
|
19
native/build-support/ubuntu-jammy.Dockerfile
Normale Datei
19
native/build-support/ubuntu-jammy.Dockerfile
Normale Datei
@ -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;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import java.util.zip.DataFormatException;
|
||||
|
||||
class CompressorUtils {
|
||||
/**
|
||||
* The default preferred output buffer size for zlib.
|
||||
*/
|
||||
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() {
|
||||
throw new AssertionError();
|
||||
}
|
||||
|
@ -52,18 +52,13 @@ public class MoreByteBufUtils {
|
||||
|
||||
private static boolean isCompatible(Native nativeStuff, ByteBuf buf) {
|
||||
BufferPreference preferred = nativeStuff.preferredBufferType();
|
||||
switch (preferred) {
|
||||
case DIRECT_PREFERRED:
|
||||
case HEAP_PREFERRED:
|
||||
return switch (preferred) {
|
||||
case DIRECT_PREFERRED, HEAP_PREFERRED ->
|
||||
// The native prefers this type, but doesn't strictly require we provide it.
|
||||
return true;
|
||||
case DIRECT_REQUIRED:
|
||||
return buf.hasMemoryAddress();
|
||||
case HEAP_REQUIRED:
|
||||
return buf.hasArray();
|
||||
default:
|
||||
throw new AssertionError("Preferred buffer type unknown");
|
||||
}
|
||||
true;
|
||||
case DIRECT_REQUIRED -> buf.hasMemoryAddress();
|
||||
case HEAP_REQUIRED -> buf.hasArray();
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
@ -77,15 +72,9 @@ public class MoreByteBufUtils {
|
||||
*/
|
||||
public static ByteBuf preferredBuffer(ByteBufAllocator alloc, Native nativeStuff,
|
||||
int initialCapacity) {
|
||||
switch (nativeStuff.preferredBufferType()) {
|
||||
case HEAP_REQUIRED:
|
||||
case HEAP_PREFERRED:
|
||||
return alloc.heapBuffer(initialCapacity);
|
||||
case DIRECT_PREFERRED:
|
||||
case DIRECT_REQUIRED:
|
||||
return alloc.directBuffer(initialCapacity);
|
||||
default:
|
||||
throw new AssertionError("Preferred buffer type unknown");
|
||||
}
|
||||
return switch (nativeStuff.preferredBufferType()) {
|
||||
case HEAP_REQUIRED, HEAP_PREFERRED -> alloc.heapBuffer(initialCapacity);
|
||||
case DIRECT_PREFERRED, DIRECT_REQUIRED -> alloc.directBuffer(initialCapacity);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -19,6 +19,7 @@ package com.velocitypowered.natives.util;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.function.BooleanSupplier;
|
||||
|
||||
/**
|
||||
@ -29,6 +30,8 @@ public class NativeConstraints {
|
||||
private static final boolean IS_AMD64;
|
||||
private static final boolean IS_AARCH64;
|
||||
private static final boolean CAN_GET_MEMORYADDRESS;
|
||||
private static final boolean IS_LINUX;
|
||||
private static final boolean IS_MUSL_LIBC;
|
||||
|
||||
static {
|
||||
ByteBuf test = Unpooled.directBuffer();
|
||||
@ -39,21 +42,46 @@ public class NativeConstraints {
|
||||
}
|
||||
|
||||
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_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 LINUX_X86_64 = () -> NATIVE_BASE.getAsBoolean()
|
||||
&& System.getProperty("os.name", "").equalsIgnoreCase("Linux")
|
||||
&& IS_AMD64;
|
||||
&& IS_LINUX && IS_AMD64 && !IS_MUSL_LIBC;
|
||||
|
||||
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()
|
||||
&& System.getProperty("os.name", "").equalsIgnoreCase("Linux")
|
||||
&& IS_AARCH64;
|
||||
&& IS_LINUX && IS_AARCH64 && !IS_MUSL_LIBC;
|
||||
|
||||
static final BooleanSupplier LINUX_AARCH64_MUSL = () -> NATIVE_BASE.getAsBoolean()
|
||||
&& IS_LINUX && IS_AARCH64 && IS_MUSL_LIBC;
|
||||
|
||||
static final BooleanSupplier MACOS_AARCH64 = () -> NATIVE_BASE.getAsBoolean()
|
||||
&& System.getProperty("os.name", "").equalsIgnoreCase("Mac OS X")
|
||||
|
@ -83,11 +83,21 @@ public class Natives {
|
||||
new NativeCodeLoader.Variant<>(NativeConstraints.LINUX_X86_64,
|
||||
copyAndLoadNative("/linux_x86_64/velocity-compress.so"),
|
||||
"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,
|
||||
copyAndLoadNative("/linux_aarch64/velocity-compress.so"),
|
||||
"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,
|
||||
copyAndLoadNative("/macos_arm64/velocity-compress.dylib"),
|
||||
"libdeflate (macOS ARM64 / Apple Silicon)",
|
||||
@ -103,21 +113,27 @@ public class Natives {
|
||||
copyAndLoadNative("/linux_x86_64/velocity-cipher.so"), // Any local version
|
||||
"OpenSSL local (Linux x86_64)", NativeVelocityCipher.FACTORY),
|
||||
new NativeCodeLoader.Variant<>(NativeConstraints.LINUX_X86_64,
|
||||
copyAndLoadNative("/linux_x86_64/velocity-cipher-ossl30x.so"), // Debian "Bookworm"
|
||||
"OpenSSL 3.0.x (Linux x86_64)", NativeVelocityCipher.FACTORY),
|
||||
copyAndLoadNative("/linux_x86_64/velocity-cipher-ossl30x.so"), // Ubuntu 22.04
|
||||
"OpenSSL 3.x.x (Linux x86_64)", NativeVelocityCipher.FACTORY),
|
||||
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),
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
copyAndLoadNative("/macos_arm64/velocity-cipher.dylib"),
|
||||
|
Binäre Datei nicht angezeigt.
BIN
native/src/main/resources/linux_aarch64/velocity-cipher-ossl30x-musl.so
Ausführbare Datei
BIN
native/src/main/resources/linux_aarch64/velocity-cipher-ossl30x-musl.so
Ausführbare Datei
Binäre Datei nicht angezeigt.
Binäre Datei nicht angezeigt.
BIN
native/src/main/resources/linux_aarch64/velocity-compress-musl.so
Ausführbare Datei
BIN
native/src/main/resources/linux_aarch64/velocity-compress-musl.so
Ausführbare Datei
Binäre Datei nicht angezeigt.
Binäre Datei nicht angezeigt.
Binäre Datei nicht angezeigt.
BIN
native/src/main/resources/linux_x86_64/velocity-cipher-ossl30x-musl.so
Ausführbare Datei
BIN
native/src/main/resources/linux_x86_64/velocity-cipher-ossl30x-musl.so
Ausführbare Datei
Binäre Datei nicht angezeigt.
Binäre Datei nicht angezeigt.
BIN
native/src/main/resources/linux_x86_64/velocity-compress-musl.so
Ausführbare Datei
BIN
native/src/main/resources/linux_x86_64/velocity-compress-musl.so
Ausführbare Datei
Binäre Datei nicht angezeigt.
Binäre Datei nicht angezeigt.
Binäre Datei nicht angezeigt.
Binäre Datei nicht angezeigt.
@ -92,6 +92,15 @@ tasks {
|
||||
dependsOn(configurateBuildTask)
|
||||
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 {
|
||||
@ -121,7 +130,7 @@ dependencies {
|
||||
runtimeOnly(libs.disruptor)
|
||||
implementation(libs.fastutil)
|
||||
implementation(platform(libs.adventure.bom))
|
||||
implementation("net.kyori:adventure-nbt")
|
||||
implementation(libs.adventure.text.serializer.json.legacy.impl)
|
||||
implementation(libs.adventure.facet)
|
||||
implementation(libs.completablefutures)
|
||||
implementation(libs.nightconfig)
|
||||
|
@ -17,11 +17,17 @@
|
||||
|
||||
package com.velocitypowered.proxy;
|
||||
|
||||
import com.velocitypowered.api.proxy.server.ServerInfo;
|
||||
import com.velocitypowered.proxy.util.AddressUtil;
|
||||
import java.io.IOException;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import joptsimple.OptionParser;
|
||||
import joptsimple.OptionSet;
|
||||
import joptsimple.OptionSpec;
|
||||
import joptsimple.ValueConversionException;
|
||||
import joptsimple.ValueConverter;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
@ -35,6 +41,8 @@ public final class ProxyOptions {
|
||||
private final boolean help;
|
||||
private final @Nullable Integer port;
|
||||
private final @Nullable Boolean haproxy;
|
||||
private final boolean ignoreConfigServers;
|
||||
private final List<ServerInfo> servers;
|
||||
|
||||
ProxyOptions(final String[] args) {
|
||||
final OptionParser parser = new OptionParser();
|
||||
@ -49,11 +57,20 @@ public final class ProxyOptions {
|
||||
"Choose whether to enable haproxy protocol. "
|
||||
+ "The configuration haproxy protocol will be ignored.")
|
||||
.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);
|
||||
|
||||
this.help = set.has(help);
|
||||
this.port = port.value(set);
|
||||
this.haproxy = haproxy.value(set);
|
||||
this.servers = servers.values(set);
|
||||
this.ignoreConfigServers = set.has(ignoreConfigServers);
|
||||
|
||||
if (this.help) {
|
||||
try {
|
||||
@ -75,4 +92,40 @@ public final class ProxyOptions {
|
||||
public @Nullable Boolean isHaproxy() {
|
||||
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.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.velocitypowered.api.command.BrigadierCommand;
|
||||
import com.velocitypowered.api.event.proxy.ProxyInitializeEvent;
|
||||
import com.velocitypowered.api.event.proxy.ProxyReloadEvent;
|
||||
import com.velocitypowered.api.event.proxy.ProxyShutdownEvent;
|
||||
import com.velocitypowered.api.network.ProtocolVersion;
|
||||
import com.velocitypowered.api.plugin.PluginContainer;
|
||||
import com.velocitypowered.api.plugin.PluginDescription;
|
||||
import com.velocitypowered.api.plugin.PluginManager;
|
||||
import com.velocitypowered.api.proxy.Player;
|
||||
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.network.ConnectionManager;
|
||||
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.util.FaviconSerializer;
|
||||
import com.velocitypowered.proxy.protocol.util.GameProfileSerializer;
|
||||
@ -77,6 +82,7 @@ import java.nio.file.Path;
|
||||
import java.security.KeyPair;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
@ -100,6 +106,7 @@ import net.kyori.adventure.translation.GlobalTranslator;
|
||||
import net.kyori.adventure.translation.TranslationRegistry;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.bstats.MetricsBase;
|
||||
import org.checkerframework.checker.nullness.qual.EnsuresNonNull;
|
||||
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
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 static final String VELOCITY_URL = "https://velocitypowered.com";
|
||||
|
||||
private static final Logger logger = LogManager.getLogger(VelocityServer.class);
|
||||
public static final Gson GENERAL_GSON = new GsonBuilder()
|
||||
.registerTypeHierarchyAdapter(Favicon.class, FaviconSerializer.INSTANCE)
|
||||
@ -162,7 +171,7 @@ public class VelocityServer implements ProxyServer, ForwardingAudience {
|
||||
VelocityServer(final ProxyOptions options) {
|
||||
pluginManager = new VelocityPluginManager(this);
|
||||
eventManager = new VelocityEventManager(pluginManager);
|
||||
commandManager = new VelocityCommandManager(eventManager);
|
||||
commandManager = new VelocityCommandManager(eventManager, pluginManager);
|
||||
scheduler = new VelocityScheduler(pluginManager);
|
||||
console = new VelocityConsole(this);
|
||||
cm = new ConnectionManager(this);
|
||||
@ -199,6 +208,16 @@ public class VelocityServer implements ProxyServer, ForwardingAudience {
|
||||
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
|
||||
public VelocityCommandManager getCommandManager() {
|
||||
return commandManager;
|
||||
@ -213,6 +232,7 @@ public class VelocityServer implements ProxyServer, ForwardingAudience {
|
||||
void start() {
|
||||
logger.info("Booting up {} {}...", getVersion().getName(), getVersion().getVersion());
|
||||
console.setupStreams();
|
||||
pluginManager.registerPlugin(this.createVirtualPlugin());
|
||||
|
||||
registerTranslations();
|
||||
|
||||
@ -221,19 +241,49 @@ public class VelocityServer implements ProxyServer, ForwardingAudience {
|
||||
cm.logChannelInformation();
|
||||
|
||||
// Initialize commands first
|
||||
commandManager.register(VelocityCommand.create(this));
|
||||
commandManager.register(CallbackCommand.create());
|
||||
commandManager.register(ServerCommand.create(this));
|
||||
commandManager.register("shutdown", ShutdownCommand.command(this),
|
||||
"end", "stop");
|
||||
final BrigadierCommand velocityParentCommand = VelocityCommand.create(this);
|
||||
commandManager.register(
|
||||
commandManager.metaBuilder(velocityParentCommand)
|
||||
.plugin(VelocityVirtualPlugin.INSTANCE)
|
||||
.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 SendCommand(this).register();
|
||||
|
||||
this.doStartupConfigLoad();
|
||||
|
||||
for (ServerInfo cliServer : options.getServers()) {
|
||||
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());
|
||||
loadPlugins();
|
||||
@ -263,7 +313,13 @@ public class VelocityServer implements ProxyServer, ForwardingAudience {
|
||||
this.cm.queryBind(configuration.getBind().getHostString(), configuration.getQueryPort());
|
||||
}
|
||||
|
||||
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() {
|
||||
@ -533,7 +589,6 @@ public class VelocityServer implements ProxyServer, ForwardingAudience {
|
||||
|
||||
eventManager.fire(new ProxyShutdownEvent()).join();
|
||||
|
||||
timedOut = !eventManager.shutdown() || timedOut;
|
||||
timedOut = !scheduler.shutdown() || timedOut;
|
||||
|
||||
if (timedOut) {
|
||||
|
@ -20,6 +20,7 @@ package com.velocitypowered.proxy.command;
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.util.concurrent.MoreExecutors;
|
||||
import com.mojang.brigadier.CommandDispatcher;
|
||||
import com.mojang.brigadier.Message;
|
||||
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.event.command.CommandExecuteEvent;
|
||||
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.CommandRegistrar;
|
||||
import com.velocitypowered.proxy.command.registrar.RawCommandRegistrar;
|
||||
import com.velocitypowered.proxy.command.registrar.SimpleCommandRegistrar;
|
||||
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.Collection;
|
||||
import java.util.List;
|
||||
@ -49,6 +54,7 @@ import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.locks.ReadWriteLock;
|
||||
import java.util.concurrent.locks.ReentrantReadWriteLock;
|
||||
import java.util.stream.Collectors;
|
||||
@ -71,13 +77,16 @@ public class VelocityCommandManager implements CommandManager {
|
||||
private final SuggestionsProvider<CommandSource> suggestionsProvider;
|
||||
private final CommandGraphInjector<CommandSource> injector;
|
||||
private final Map<String, CommandMeta> commandMetas;
|
||||
private final PluginManager pluginManager;
|
||||
|
||||
/**
|
||||
* Constructs a command 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.dispatcher = new CommandDispatcher<>();
|
||||
this.eventManager = Preconditions.checkNotNull(eventManager);
|
||||
@ -218,16 +227,13 @@ public class VelocityCommandManager implements CommandManager {
|
||||
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(cmdLine, "cmdLine");
|
||||
|
||||
final String normalizedInput = VelocityCommands.normalizeInput(cmdLine, true);
|
||||
CommandResult result = CommandResult.EXCEPTION;
|
||||
try {
|
||||
// The parse can fail if the requirement predicates throw
|
||||
final ParseResults<CommandSource> parse = this.parse(normalizedInput, source);
|
||||
boolean executed = dispatcher.execute(parse) != BrigadierCommand.FORWARD;
|
||||
boolean executed = dispatcher.execute(parsed) != BrigadierCommand.FORWARD;
|
||||
result = executed ? CommandResult.EXECUTED : CommandResult.FORWARDED;
|
||||
return executed;
|
||||
} catch (final CommandSyntaxException e) {
|
||||
@ -249,9 +255,9 @@ public class VelocityCommandManager implements CommandManager {
|
||||
}
|
||||
} catch (final Throwable e) {
|
||||
// Ugly, ugly swallowing of everything Throwable, because plugins are naughty.
|
||||
throw new RuntimeException("Unable to invoke command " + cmdLine + " for " + source, e);
|
||||
throw new RuntimeException("Unable to invoke command " + parsed.getReader().getString() + "for " + source, e);
|
||||
} 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(cmdLine, "cmdLine");
|
||||
|
||||
return callCommandEvent(source, cmdLine).thenApplyAsync(event -> {
|
||||
return callCommandEvent(source, cmdLine).thenComposeAsync(event -> {
|
||||
CommandExecuteEvent.CommandResult commandResult = event.getResult();
|
||||
if (commandResult.isForwardToServer() || !commandResult.isAllowed()) {
|
||||
return false;
|
||||
return CompletableFuture.completedFuture(false);
|
||||
}
|
||||
return executeImmediately0(source, commandResult.getCommand().orElse(event.getCommand()));
|
||||
}, eventManager.getAsyncExecutor());
|
||||
final ParseResults<CommandSource> parsed = this.parse(
|
||||
commandResult.getCommand().orElse(cmdLine), source);
|
||||
return CompletableFuture.supplyAsync(
|
||||
() -> executeImmediately0(source, parsed), this.getAsyncExecutor(parsed)
|
||||
);
|
||||
}, figureAsyncExecutorForParsing());
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -276,7 +286,12 @@ public class VelocityCommandManager implements CommandManager {
|
||||
Preconditions.checkNotNull(cmdLine, "cmdLine");
|
||||
|
||||
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
|
||||
*/
|
||||
private ParseResults<CommandSource> parse(final String input, final CommandSource source) {
|
||||
final String normalizedInput = VelocityCommands.normalizeInput(input, true);
|
||||
lock.readLock().lock();
|
||||
try {
|
||||
return dispatcher.parse(input, source);
|
||||
return dispatcher.parse(normalizedInput, source);
|
||||
} finally {
|
||||
lock.readLock().unlock();
|
||||
}
|
||||
@ -370,4 +386,25 @@ public class VelocityCommandManager implements CommandManager {
|
||||
public CommandGraphInjector<CommandSource> getInjector() {
|
||||
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.ParsedArgument;
|
||||
import com.mojang.brigadier.context.ParsedCommandNode;
|
||||
import com.mojang.brigadier.tree.ArgumentCommandNode;
|
||||
import com.mojang.brigadier.tree.CommandNode;
|
||||
import com.mojang.brigadier.tree.LiteralCommandNode;
|
||||
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.InvocableCommand;
|
||||
import com.velocitypowered.proxy.command.brigadier.VelocityArgumentCommandNode;
|
||||
import com.velocitypowered.proxy.command.brigadier.VelocityBrigadierCommandWrapper;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
@ -44,6 +46,59 @@ import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
*/
|
||||
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
|
||||
|
||||
/**
|
||||
@ -135,6 +190,33 @@ public final class VelocityCommands {
|
||||
*/
|
||||
public static LiteralCommandNode<CommandSource> shallowCopy(
|
||||
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.
|
||||
// Let <bar> be a literal node having a redirect to a <foo> literal. Then,
|
||||
// the context returned by CommandDispatcher#parseNodes when given the input
|
||||
@ -150,10 +232,12 @@ public final class VelocityCommands {
|
||||
.requiresWithContext(original.getContextRequirement())
|
||||
.forward(original.getRedirect(), original.getRedirectModifier(), original.isFork())
|
||||
.executes(original.getCommand());
|
||||
if (!skipChildren) {
|
||||
for (final CommandNode<CommandSource> child : original.getChildren()) {
|
||||
builder.then(child);
|
||||
}
|
||||
return builder.build();
|
||||
}
|
||||
return builder;
|
||||
}
|
||||
|
||||
// Arguments node
|
||||
|
@ -93,6 +93,16 @@ public class VelocityArgumentCommandNode<S, T> extends ArgumentCommandNode<S, St
|
||||
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
|
||||
public boolean isValidInput(final String input) {
|
||||
return true;
|
||||
|
@ -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.ProxyServer;
|
||||
import com.velocitypowered.api.proxy.server.RegisteredServer;
|
||||
import com.velocitypowered.proxy.plugin.virtual.VelocityVirtualPlugin;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import net.kyori.adventure.text.Component;
|
||||
@ -80,7 +81,13 @@ public class GlistCommand {
|
||||
.executes(this::serverCount)
|
||||
.build();
|
||||
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) {
|
||||
|
@ -30,6 +30,7 @@ import com.velocitypowered.api.proxy.Player;
|
||||
import com.velocitypowered.api.proxy.ProxyServer;
|
||||
import com.velocitypowered.api.proxy.ServerConnection;
|
||||
import com.velocitypowered.api.proxy.server.RegisteredServer;
|
||||
import com.velocitypowered.proxy.plugin.virtual.VelocityVirtualPlugin;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import net.kyori.adventure.text.Component;
|
||||
@ -96,7 +97,13 @@ public class SendCommand {
|
||||
.build();
|
||||
playerNode.then(serverNode);
|
||||
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) {
|
||||
|
@ -23,6 +23,7 @@ import com.mojang.brigadier.builder.LiteralArgumentBuilder;
|
||||
import com.mojang.brigadier.builder.RequiredArgumentBuilder;
|
||||
import com.velocitypowered.api.command.BrigadierCommand;
|
||||
import com.velocitypowered.api.command.CommandSource;
|
||||
import com.velocitypowered.api.proxy.ConsoleCommandSource;
|
||||
import com.velocitypowered.proxy.VelocityServer;
|
||||
import net.kyori.adventure.text.minimessage.MiniMessage;
|
||||
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
|
||||
@ -43,7 +44,7 @@ public final class ShutdownCommand {
|
||||
*/
|
||||
public static BrigadierCommand command(final VelocityServer server) {
|
||||
return new BrigadierCommand(LiteralArgumentBuilder.<CommandSource>literal("shutdown")
|
||||
.requires(source -> source == server.getConsoleCommandSource())
|
||||
.requires(source -> source instanceof ConsoleCommandSource)
|
||||
.executes(context -> {
|
||||
server.shutdown(true);
|
||||
return Command.SINGLE_SUCCESS;
|
||||
|
@ -174,10 +174,10 @@ public final class VelocityCommand {
|
||||
if (version.getName().equals("Velocity")) {
|
||||
final TextComponent embellishment = Component.text()
|
||||
.append(Component.text()
|
||||
.content("velocitypowered.com")
|
||||
.content("PaperMC")
|
||||
.color(NamedTextColor.GREEN)
|
||||
.clickEvent(
|
||||
ClickEvent.openUrl("https://velocitypowered.com"))
|
||||
ClickEvent.openUrl("https://papermc.io/software/velocity"))
|
||||
.build())
|
||||
.append(Component.text(" - "))
|
||||
.append(Component.text()
|
||||
|
@ -40,17 +40,19 @@ public final class BrigadierCommandRegistrar extends AbstractCommandRegistrar<Br
|
||||
// Register it (if valid), since it's probably what the user expects.
|
||||
// If invalid, the metadata contains the same alias, but in lowercase.
|
||||
final LiteralCommandNode<CommandSource> literal = command.getNode();
|
||||
final LiteralCommandNode<CommandSource> wrapped =
|
||||
(LiteralCommandNode<CommandSource>) VelocityCommands.wrap(literal, meta.getPlugin());
|
||||
final String primaryAlias = literal.getName();
|
||||
if (VelocityCommands.isValidAlias(primaryAlias)) {
|
||||
// Register directly without copying
|
||||
this.register(literal);
|
||||
this.register(wrapped);
|
||||
}
|
||||
|
||||
for (final String alias : meta.getAliases()) {
|
||||
if (primaryAlias.equals(alias)) {
|
||||
continue;
|
||||
}
|
||||
this.register(literal, alias);
|
||||
this.register(wrapped, alias);
|
||||
}
|
||||
|
||||
// Brigadier commands don't support hinting, ignore
|
||||
|
@ -32,6 +32,7 @@ import com.velocitypowered.api.command.InvocableCommand;
|
||||
import com.velocitypowered.proxy.command.VelocityCommandMeta;
|
||||
import com.velocitypowered.proxy.command.VelocityCommands;
|
||||
import com.velocitypowered.proxy.command.brigadier.VelocityArgumentBuilder;
|
||||
import com.velocitypowered.proxy.command.brigadier.VelocityBrigadierCommandWrapper;
|
||||
import com.velocitypowered.proxy.command.invocation.CommandInvocationFactory;
|
||||
import java.util.Iterator;
|
||||
import java.util.concurrent.locks.Lock;
|
||||
@ -76,11 +77,11 @@ abstract class InvocableCommandRegistrar<T extends InvocableCommand<I>,
|
||||
final I invocation = invocationFactory.create(context);
|
||||
return command.hasPermission(invocation);
|
||||
};
|
||||
final Command<CommandSource> callback = context -> {
|
||||
final Command<CommandSource> callback = VelocityBrigadierCommandWrapper.wrap(context -> {
|
||||
final I invocation = invocationFactory.create(context);
|
||||
command.execute(invocation);
|
||||
return 1; // handled
|
||||
};
|
||||
}, meta.getPlugin());
|
||||
|
||||
final LiteralCommandNode<CommandSource> literal = LiteralArgumentBuilder
|
||||
.<CommandSource>literal(alias)
|
||||
|
@ -53,8 +53,6 @@ import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
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.jetbrains.annotations.NotNull;
|
||||
|
||||
@ -72,7 +70,6 @@ public class VelocityServerConnection implements MinecraftConnectionAssociation,
|
||||
private boolean gracefulDisconnect = false;
|
||||
private BackendConnectionPhase connectionPhase = BackendConnectionPhases.UNKNOWN;
|
||||
private final Map<Long, Long> pendingPings = new HashMap<>();
|
||||
private @MonotonicNonNull CompoundBinaryTag activeDimensionRegistry;
|
||||
|
||||
/**
|
||||
* Initializes a new server connection.
|
||||
@ -366,12 +363,4 @@ public class VelocityServerConnection implements MinecraftConnectionAssociation,
|
||||
public boolean hasCompletedJoin() {
|
||||
return hasCompletedJoin;
|
||||
}
|
||||
|
||||
public CompoundBinaryTag getActiveDimensionRegistry() {
|
||||
return activeDimensionRegistry;
|
||||
}
|
||||
|
||||
public void setActiveDimensionRegistry(CompoundBinaryTag activeDimensionRegistry) {
|
||||
this.activeDimensionRegistry = activeDimensionRegistry;
|
||||
}
|
||||
}
|
||||
|
@ -96,7 +96,7 @@ public class AuthSessionHandler implements MinecraftSessionHandler {
|
||||
|
||||
// Initiate a regular connection and move over to it.
|
||||
ConnectedPlayer player = new ConnectedPlayer(server, profileEvent.getGameProfile(),
|
||||
mcConnection, inbound.getVirtualHost().orElse(null), onlineMode,
|
||||
mcConnection, inbound.getVirtualHost().orElse(null), inbound.getRawVirtualHost().orElse(null), onlineMode,
|
||||
inbound.getIdentifiedKey());
|
||||
this.connectedPlayer = player;
|
||||
if (!server.canRegisterConnection(player)) {
|
||||
|
@ -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
|
||||
// track them.
|
||||
for (UUID serverBossBar : serverBossBars) {
|
||||
|
@ -30,7 +30,7 @@ import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
public class ClientSettingsWrapper implements PlayerSettings {
|
||||
|
||||
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 SkinParts parts;
|
||||
@ -56,11 +56,11 @@ public class ClientSettingsWrapper implements PlayerSettings {
|
||||
|
||||
@Override
|
||||
public ChatMode getChatMode() {
|
||||
int chat = settings.getChatVisibility();
|
||||
if (chat < 0 || chat > 2) {
|
||||
return ChatMode.SHOWN;
|
||||
}
|
||||
return ChatMode.values()[chat];
|
||||
return switch (settings.getChatVisibility()) {
|
||||
case 1 -> ChatMode.COMMANDS_ONLY;
|
||||
case 2 -> ChatMode.HIDDEN;
|
||||
default -> ChatMode.SHOWN;
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -83,6 +83,20 @@ public class ClientSettingsWrapper implements PlayerSettings {
|
||||
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
|
||||
public boolean equals(@Nullable final Object o) {
|
||||
if (this == o) {
|
||||
|
@ -155,6 +155,7 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player,
|
||||
*/
|
||||
private final MinecraftConnection connection;
|
||||
private final @Nullable InetSocketAddress virtualHost;
|
||||
private final @Nullable String rawVirtualHost;
|
||||
private GameProfile profile;
|
||||
private PermissionFunction permissionFunction;
|
||||
private int tryIndex = 0;
|
||||
@ -191,12 +192,13 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player,
|
||||
private final ChatBuilderFactory chatBuilderFactory;
|
||||
|
||||
ConnectedPlayer(VelocityServer server, GameProfile profile, MinecraftConnection connection,
|
||||
@Nullable InetSocketAddress virtualHost, boolean onlineMode,
|
||||
@Nullable InetSocketAddress virtualHost, @Nullable String rawVirtualHost, boolean onlineMode,
|
||||
@Nullable IdentifiedKey playerKey) {
|
||||
this.server = server;
|
||||
this.profile = profile;
|
||||
this.connection = connection;
|
||||
this.virtualHost = virtualHost;
|
||||
this.rawVirtualHost = rawVirtualHost;
|
||||
this.permissionFunction = PermissionFunction.ALWAYS_UNDEFINED;
|
||||
this.connectionPhase = connection.getType().getInitialClientPhase();
|
||||
this.onlineMode = onlineMode;
|
||||
@ -356,6 +358,11 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player,
|
||||
return Optional.ofNullable(virtualHost);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<String> getRawVirtualHost() {
|
||||
return Optional.ofNullable(rawVirtualHost);
|
||||
}
|
||||
|
||||
void setPermissionFunction(PermissionFunction permissionFunction) {
|
||||
this.permissionFunction = permissionFunction;
|
||||
}
|
||||
|
@ -243,6 +243,11 @@ public class HandshakeSessionHandler implements MinecraftSessionHandler {
|
||||
return Optional.ofNullable(ping.getVhost());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<String> getRawVirtualHost() {
|
||||
return getVirtualHost().map(InetSocketAddress::getHostName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isActive() {
|
||||
return !connection.isClosed();
|
||||
|
@ -63,6 +63,11 @@ public final class InitialInboundConnection implements VelocityInboundConnection
|
||||
return Optional.of(InetSocketAddress.createUnresolved(cleanedAddress, handshake.getPort()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<String> getRawVirtualHost() {
|
||||
return Optional.of(handshake.getServerAddress());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isActive() {
|
||||
return connection.getChannel().isActive();
|
||||
|
@ -30,9 +30,9 @@ import io.netty.buffer.ByteBufUtil;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.Optional;
|
||||
import java.util.Queue;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
@ -58,7 +58,7 @@ public class LoginInboundConnection implements LoginPhaseConnection, KeyIdentifi
|
||||
InitialInboundConnection delegate) {
|
||||
this.delegate = delegate;
|
||||
this.outstandingResponses = Int2ObjectSyncMap.hashmap();
|
||||
this.loginMessagesToSend = new ArrayDeque<>();
|
||||
this.loginMessagesToSend = new ConcurrentLinkedQueue<>();
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -71,6 +71,11 @@ public class LoginInboundConnection implements LoginPhaseConnection, KeyIdentifi
|
||||
return delegate.getVirtualHost();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<String> getRawVirtualHost() {
|
||||
return delegate.getRawVirtualHost();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isActive() {
|
||||
return delegate.isActive();
|
||||
|
@ -33,8 +33,9 @@ class EventTypeTracker {
|
||||
}
|
||||
|
||||
public Collection<Class<?>> getFriendsOf(final Class<?> eventType) {
|
||||
if (friends.containsKey(eventType)) {
|
||||
return friends.get(eventType);
|
||||
ImmutableSet<Class<?>> existingFriends = friends.get(eventType);
|
||||
if (existingFriends != null) {
|
||||
return existingFriends;
|
||||
}
|
||||
|
||||
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.ListMultimap;
|
||||
import com.google.common.reflect.TypeToken;
|
||||
import com.google.common.util.concurrent.ThreadFactoryBuilder;
|
||||
import com.velocitypowered.api.event.Continuation;
|
||||
import com.velocitypowered.api.event.EventHandler;
|
||||
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.VoidHandler;
|
||||
import com.velocitypowered.proxy.event.UntargetedEventHandler.WithContinuationHandler;
|
||||
import com.velocitypowered.proxy.util.collect.Enum2IntMap;
|
||||
import java.lang.invoke.MethodHandle;
|
||||
import java.lang.invoke.MethodHandles;
|
||||
import java.lang.invoke.VarHandle;
|
||||
@ -55,9 +55,6 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
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.ReentrantReadWriteLock;
|
||||
import java.util.function.BiConsumer;
|
||||
@ -76,6 +73,14 @@ import org.lanternpowered.lmbda.LambdaType;
|
||||
*/
|
||||
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 MethodHandles.Lookup methodHandlesLookup = MethodHandles.lookup();
|
||||
@ -87,9 +92,8 @@ public class VelocityEventManager implements EventManager {
|
||||
LambdaType.of(WithContinuationHandler.class);
|
||||
|
||||
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 ListMultimap<Class<?>, HandlerRegistration> handlersByType =
|
||||
@ -112,9 +116,6 @@ public class VelocityEventManager implements EventManager {
|
||||
*/
|
||||
public VelocityEventManager(final 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 Class<?> eventType;
|
||||
final EventHandler<Object> handler;
|
||||
final AsyncType asyncType;
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
||||
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.order = order;
|
||||
this.eventType = eventType;
|
||||
this.instance = instance;
|
||||
this.handler = handler;
|
||||
this.asyncType = 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.
|
||||
*/
|
||||
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 {
|
||||
|
||||
final AsyncType asyncType;
|
||||
final HandlerRegistration[] handlers;
|
||||
|
||||
HandlersCache(final HandlerRegistration[] handlers) {
|
||||
HandlersCache(AsyncType asyncType, final HandlerRegistration[] handlers) {
|
||||
this.asyncType = asyncType;
|
||||
this.handlers = handlers;
|
||||
}
|
||||
}
|
||||
@ -194,7 +205,15 @@ public class VelocityEventManager implements EventManager {
|
||||
}
|
||||
|
||||
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 {
|
||||
|
||||
final Method method;
|
||||
final AsyncType asyncType;
|
||||
final @Nullable Class<?> eventType;
|
||||
final short order;
|
||||
final @Nullable String errors;
|
||||
final @Nullable Class<?> continuationType;
|
||||
|
||||
private MethodHandlerInfo(final Method method, final @Nullable Class<?> eventType,
|
||||
final short order, final @Nullable String errors,
|
||||
private MethodHandlerInfo(final Method method, final AsyncType asyncType,
|
||||
final @Nullable Class<?> eventType, final short order, final @Nullable String errors,
|
||||
final @Nullable Class<?> continuationType) {
|
||||
this.method = method;
|
||||
this.asyncType = asyncType;
|
||||
this.eventType = eventType;
|
||||
this.order = order;
|
||||
this.errors = errors;
|
||||
@ -302,17 +323,41 @@ public class VelocityEventManager implements EventManager {
|
||||
}
|
||||
}
|
||||
}
|
||||
if (handlerAdapter == null) {
|
||||
AsyncType asyncType = AsyncType.NEVER;
|
||||
final Class<?> returnType = method.getReturnType();
|
||||
if (handlerAdapter == null) {
|
||||
if (returnType != void.class && continuationType == Continuation.class) {
|
||||
errors.add("method return type must be void if a continuation parameter is provided");
|
||||
} 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);
|
||||
collected.put(key, new MethodHandlerInfo(method, eventType, order, errorsJoined,
|
||||
collected.put(key, new MethodHandlerInfo(method, asyncType, eventType, order, errorsJoined,
|
||||
continuationType));
|
||||
}
|
||||
final Class<?> superclass = targetClass.getSuperclass();
|
||||
@ -351,12 +396,29 @@ public class VelocityEventManager implements EventManager {
|
||||
@SuppressWarnings("unchecked")
|
||||
public <E> void register(final Object plugin, final Class<E> eventClass,
|
||||
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);
|
||||
requireNonNull(eventClass, "eventClass");
|
||||
requireNonNull(handler, "handler");
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
@ -386,7 +448,7 @@ public class VelocityEventManager implements EventManager {
|
||||
|
||||
final EventHandler<Object> handler = untargetedHandler.buildHandler(listener);
|
||||
registrations.add(new HandlerRegistration(pluginContainer, info.order,
|
||||
info.eventType, listener, handler));
|
||||
info.eventType, listener, handler, info.asyncType));
|
||||
}
|
||||
|
||||
register(registrations);
|
||||
@ -473,10 +535,13 @@ public class VelocityEventManager implements EventManager {
|
||||
|
||||
private <E> void fire(final @Nullable CompletableFuture<E> future,
|
||||
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
|
||||
// largely (albeit not 100%) compatible with 1.1.x, we also fire events async. This behavior
|
||||
// will go away in Velocity Polymer.
|
||||
asyncExecutor.execute(() -> fire(future, event, 0, true, handlersCache.handlers));
|
||||
final HandlerRegistration registration = handlersCache.handlers[0];
|
||||
if (registration.asyncType == AsyncType.ALWAYS) {
|
||||
registration.plugin.getExecutorService().execute(
|
||||
() -> fire(future, event, 0, true, handlersCache.handlers));
|
||||
} else {
|
||||
fire(future, event, 0, false, handlersCache.handlers);
|
||||
}
|
||||
}
|
||||
|
||||
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 boolean currentlyAsync;
|
||||
private final E event;
|
||||
private final Thread firedOnThread;
|
||||
|
||||
// This field is modified via a VarHandle, so this field is used and cannot be final.
|
||||
@SuppressWarnings({"UnusedVariable", "FieldMayBeFinal", "FieldCanBeLocal"})
|
||||
@ -527,6 +593,7 @@ public class VelocityEventManager implements EventManager {
|
||||
this.event = event;
|
||||
this.index = index;
|
||||
this.currentlyAsync = currentlyAsync;
|
||||
this.firedOnThread = Thread.currentThread();
|
||||
}
|
||||
|
||||
@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
|
||||
* one without scheduling.
|
||||
* Executes the task and returns whether the next handler should be executed immediately
|
||||
* after this one, without additional scheduling.
|
||||
*/
|
||||
boolean execute() {
|
||||
state = TASK_STATE_EXECUTING;
|
||||
@ -580,7 +647,18 @@ public class VelocityEventManager implements EventManager {
|
||||
}
|
||||
if (!CONTINUATION_TASK_STATE.compareAndSet(
|
||||
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;
|
||||
}
|
||||
} else {
|
||||
asyncExecutor.execute(continuationTask);
|
||||
registration.plugin.getExecutorService().execute(continuationTask);
|
||||
}
|
||||
// fire will continue in another thread once the async task is
|
||||
// executed and the continuation is resumed
|
||||
@ -626,13 +704,4 @@ public class VelocityEventManager implements EventManager {
|
||||
logger.error("Couldn't pass {} to {} {}", registration.eventType.getSimpleName(),
|
||||
pluginDescription.getId(), pluginDescription.getVersion().orElse(""), t);
|
||||
}
|
||||
|
||||
public boolean shutdown() throws InterruptedException {
|
||||
asyncExecutor.shutdown();
|
||||
return asyncExecutor.awaitTermination(10, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
public ExecutorService getAsyncExecutor() {
|
||||
return asyncExecutor;
|
||||
}
|
||||
}
|
@ -49,7 +49,7 @@ public class BackendChannelInitializer extends ChannelInitializer<Channel> {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initChannel(Channel ch) throws Exception {
|
||||
protected void initChannel(Channel ch) {
|
||||
ch.pipeline()
|
||||
.addLast(FRAME_DECODER, new MinecraftVarintFrameDecoder())
|
||||
.addLast(READ_TIMEOUT,
|
||||
|
@ -109,6 +109,12 @@ public final class ConnectionManager {
|
||||
final Channel channel = future.channel();
|
||||
if (future.isSuccess()) {
|
||||
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());
|
||||
|
||||
// Fire the proxy bound event after the socket is bound
|
||||
|
@ -68,7 +68,12 @@ public class VelocityPluginManager implements PluginManager {
|
||||
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);
|
||||
Optional<?> instance = plugin.getInstance();
|
||||
instance.ifPresent(o -> pluginInstances.put(o, plugin));
|
||||
|
@ -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.proxy.crypto.IdentifiedKeyImpl;
|
||||
import com.velocitypowered.proxy.protocol.netty.MinecraftDecoder;
|
||||
import com.velocitypowered.proxy.protocol.util.VelocityLegacyHoverEventSerializer;
|
||||
import com.velocitypowered.proxy.util.except.QuietDecoderException;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
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.text.serializer.gson.GsonComponentSerializer;
|
||||
import net.kyori.adventure.text.serializer.json.JSONOptions;
|
||||
import net.kyori.adventure.text.serializer.json.legacyimpl.NBTLegacyHoverEventSerializer;
|
||||
import net.kyori.option.OptionState;
|
||||
|
||||
/**
|
||||
@ -58,8 +58,7 @@ public enum ProtocolUtils {
|
||||
private static final GsonComponentSerializer PRE_1_16_SERIALIZER =
|
||||
GsonComponentSerializer.builder()
|
||||
.downsampleColors()
|
||||
.emitLegacyHoverEvent()
|
||||
.legacyHoverEventSerializer(VelocityLegacyHoverEventSerializer.INSTANCE)
|
||||
.legacyHoverEventSerializer(NBTLegacyHoverEventSerializer.get())
|
||||
.options(
|
||||
OptionState.optionState()
|
||||
// before 1.16
|
||||
@ -74,7 +73,7 @@ public enum ProtocolUtils {
|
||||
.build();
|
||||
private static final GsonComponentSerializer PRE_1_20_3_SERIALIZER =
|
||||
GsonComponentSerializer.builder()
|
||||
.legacyHoverEventSerializer(VelocityLegacyHoverEventSerializer.INSTANCE)
|
||||
.legacyHoverEventSerializer(NBTLegacyHoverEventSerializer.get())
|
||||
.options(
|
||||
OptionState.optionState()
|
||||
// after 1.16
|
||||
@ -89,7 +88,7 @@ public enum ProtocolUtils {
|
||||
.build();
|
||||
private static final GsonComponentSerializer MODERN_SERIALIZER =
|
||||
GsonComponentSerializer.builder()
|
||||
.legacyHoverEventSerializer(VelocityLegacyHoverEventSerializer.INSTANCE)
|
||||
.legacyHoverEventSerializer(NBTLegacyHoverEventSerializer.get())
|
||||
.options(
|
||||
OptionState.optionState()
|
||||
// after 1.16
|
||||
@ -104,6 +103,7 @@ public enum ProtocolUtils {
|
||||
.build();
|
||||
|
||||
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[] {
|
||||
BinaryTagTypes.END, BinaryTagTypes.BYTE, BinaryTagTypes.SHORT, BinaryTagTypes.INT,
|
||||
BinaryTagTypes.LONG, BinaryTagTypes.FLOAT, BinaryTagTypes.DOUBLE,
|
||||
@ -111,13 +111,18 @@ public enum ProtocolUtils {
|
||||
BinaryTagTypes.COMPOUND, BinaryTagTypes.INT_ARRAY, BinaryTagTypes.LONG_ARRAY};
|
||||
private static final QuietDecoderException BAD_VARINT_CACHED =
|
||||
new QuietDecoderException("Bad VarInt decoded");
|
||||
private static final int[] VARINT_EXACT_BYTE_LENGTHS = new int[33];
|
||||
private static final int[] VAR_INT_LENGTHS = new int[65];
|
||||
|
||||
static {
|
||||
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
|
||||
*/
|
||||
public static int readVarInt(ByteBuf buf) {
|
||||
int read = readVarIntSafely(buf);
|
||||
if (read == Integer.MIN_VALUE) {
|
||||
throw MinecraftDecoder.DEBUG ? new CorruptedFrameException("Bad VarInt decoded")
|
||||
: BAD_VARINT_CACHED;
|
||||
}
|
||||
return read;
|
||||
int readable = buf.readableBytes();
|
||||
if (readable == 0) {
|
||||
// special case for empty buffer
|
||||
throw badVarint();
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads a Minecraft-style VarInt from the specified {@code buf}. The difference between this
|
||||
* method and {@link #readVarInt(ByteBuf)} is that this function returns a sentinel value if the
|
||||
* varint is invalid.
|
||||
*
|
||||
* @param buf the buffer to read from
|
||||
* @return the decoded VarInt, or {@code Integer.MIN_VALUE} if the varint is invalid
|
||||
*/
|
||||
public static int readVarIntSafely(ByteBuf buf) {
|
||||
int i = 0;
|
||||
int maxRead = Math.min(5, buf.readableBytes());
|
||||
for (int j = 0; j < maxRead; j++) {
|
||||
// we can read at least one byte, and this should be a common case
|
||||
int k = buf.readByte();
|
||||
if ((k & 0x80) != 128) {
|
||||
return k;
|
||||
}
|
||||
|
||||
// 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);
|
||||
int i = k & 0x7F;
|
||||
for (int j = 1; j < maxRead; j++) {
|
||||
k = buf.readByte();
|
||||
i |= (k & 0x7F) << j * 7;
|
||||
if ((k & 0x80) != 128) {
|
||||
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
|
||||
*/
|
||||
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) {
|
||||
// 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) {
|
||||
buf.writeByte(value);
|
||||
} 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_5;
|
||||
import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_21;
|
||||
import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_21_2;
|
||||
import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_7_2;
|
||||
import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_8;
|
||||
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(0x09, MINECRAFT_1_19_4, 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(
|
||||
LegacyChatPacket.class,
|
||||
LegacyChatPacket::new,
|
||||
@ -265,7 +267,8 @@ public enum StateRegistry {
|
||||
serverbound.register(
|
||||
ChatAcknowledgementPacket.class,
|
||||
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,
|
||||
map(0x03, MINECRAFT_1_19, 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));
|
||||
serverbound.register(SessionPlayerCommandPacket.class, SessionPlayerCommandPacket::new,
|
||||
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,
|
||||
map(0x04, MINECRAFT_1_20_5, false));
|
||||
map(0x04, MINECRAFT_1_20_5, false),
|
||||
map(0x05, MINECRAFT_1_21_2, false));
|
||||
serverbound.register(
|
||||
SessionPlayerChatPacket.class,
|
||||
SessionPlayerChatPacket::new,
|
||||
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(
|
||||
ClientSettingsPacket.class,
|
||||
ClientSettingsPacket::new,
|
||||
@ -295,10 +301,12 @@ public enum StateRegistry {
|
||||
map(0x07, MINECRAFT_1_19_3, false),
|
||||
map(0x08, MINECRAFT_1_19_4, 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(
|
||||
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(
|
||||
PluginMessagePacket.class,
|
||||
PluginMessagePacket::new,
|
||||
@ -315,7 +323,8 @@ public enum StateRegistry {
|
||||
map(0x0D, MINECRAFT_1_19_4, false),
|
||||
map(0x0F, MINECRAFT_1_20_2, 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(
|
||||
KeepAlivePacket.class,
|
||||
KeepAlivePacket::new,
|
||||
@ -333,7 +342,8 @@ public enum StateRegistry {
|
||||
map(0x12, MINECRAFT_1_19_4, false),
|
||||
map(0x14, MINECRAFT_1_20_2, 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(
|
||||
ResourcePackResponsePacket.class,
|
||||
ResourcePackResponsePacket::new,
|
||||
@ -348,11 +358,13 @@ public enum StateRegistry {
|
||||
map(0x24, MINECRAFT_1_19_1, false),
|
||||
map(0x27, MINECRAFT_1_20_2, 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(
|
||||
FinishedUpdatePacket.class, () -> FinishedUpdatePacket.INSTANCE,
|
||||
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(
|
||||
BossBarPacket.class,
|
||||
@ -450,7 +462,8 @@ public enum StateRegistry {
|
||||
map(0x1F, MINECRAFT_1_19_3, false),
|
||||
map(0x23, MINECRAFT_1_19_4, 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(
|
||||
JoinGamePacket.class,
|
||||
JoinGamePacket::new,
|
||||
@ -467,7 +480,8 @@ public enum StateRegistry {
|
||||
map(0x24, MINECRAFT_1_19_3, false),
|
||||
map(0x28, MINECRAFT_1_19_4, 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(
|
||||
RespawnPacket.class,
|
||||
RespawnPacket::new,
|
||||
@ -487,12 +501,14 @@ public enum StateRegistry {
|
||||
map(0x41, MINECRAFT_1_19_4, true),
|
||||
map(0x43, MINECRAFT_1_20_2, 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(
|
||||
RemoveResourcePackPacket.class,
|
||||
RemoveResourcePackPacket::new,
|
||||
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(
|
||||
ResourcePackRequestPacket.class,
|
||||
ResourcePackRequestPacket::new,
|
||||
@ -512,7 +528,8 @@ public enum StateRegistry {
|
||||
map(0x40, MINECRAFT_1_19_4, false),
|
||||
map(0x42, MINECRAFT_1_20_2, 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(
|
||||
HeaderAndFooterPacket.class,
|
||||
HeaderAndFooterPacket::new,
|
||||
@ -533,7 +550,8 @@ public enum StateRegistry {
|
||||
map(0x65, MINECRAFT_1_19_4, true),
|
||||
map(0x68, MINECRAFT_1_20_2, 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(
|
||||
LegacyTitlePacket.class,
|
||||
LegacyTitlePacket::new,
|
||||
@ -553,7 +571,8 @@ public enum StateRegistry {
|
||||
map(0x5D, MINECRAFT_1_19_4, true),
|
||||
map(0x5F, MINECRAFT_1_20_2, 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(
|
||||
TitleTextPacket.class,
|
||||
TitleTextPacket::new,
|
||||
@ -564,7 +583,8 @@ public enum StateRegistry {
|
||||
map(0x5F, MINECRAFT_1_19_4, true),
|
||||
map(0x61, MINECRAFT_1_20_2, 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(
|
||||
TitleActionbarPacket.class,
|
||||
TitleActionbarPacket::new,
|
||||
@ -575,7 +595,8 @@ public enum StateRegistry {
|
||||
map(0x46, MINECRAFT_1_19_4, true),
|
||||
map(0x48, MINECRAFT_1_20_2, 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(
|
||||
TitleTimesPacket.class,
|
||||
TitleTimesPacket::new,
|
||||
@ -586,7 +607,8 @@ public enum StateRegistry {
|
||||
map(0x60, MINECRAFT_1_19_4, true),
|
||||
map(0x62, MINECRAFT_1_20_2, 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(
|
||||
TitleClearPacket.class,
|
||||
TitleClearPacket::new,
|
||||
@ -613,17 +635,20 @@ public enum StateRegistry {
|
||||
map(0x35, MINECRAFT_1_19_3, false),
|
||||
map(0x39, MINECRAFT_1_19_4, 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(
|
||||
UpsertPlayerInfoPacket.class,
|
||||
UpsertPlayerInfoPacket::new,
|
||||
map(0x36, MINECRAFT_1_19_3, false),
|
||||
map(0x3A, MINECRAFT_1_19_4, 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(
|
||||
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(
|
||||
SystemChatPacket.class,
|
||||
SystemChatPacket::new,
|
||||
@ -633,7 +658,8 @@ public enum StateRegistry {
|
||||
map(0x64, MINECRAFT_1_19_4, true),
|
||||
map(0x67, MINECRAFT_1_20_2, 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(
|
||||
PlayerChatCompletionPacket.class,
|
||||
PlayerChatCompletionPacket::new,
|
||||
@ -651,13 +677,15 @@ public enum StateRegistry {
|
||||
map(0x45, MINECRAFT_1_19_4, false),
|
||||
map(0x47, MINECRAFT_1_20_2, 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(
|
||||
StartUpdatePacket.class,
|
||||
() -> StartUpdatePacket.INSTANCE,
|
||||
map(0x65, MINECRAFT_1_20_2, false),
|
||||
map(0x67, MINECRAFT_1_20_3, false),
|
||||
map(0x69, MINECRAFT_1_20_5, false));
|
||||
map(0x69, MINECRAFT_1_20_5, false),
|
||||
map(0x70, MINECRAFT_1_21_2, false));
|
||||
clientbound.register(
|
||||
BundleDelimiterPacket.class,
|
||||
() -> BundleDelimiterPacket.INSTANCE,
|
||||
@ -665,12 +693,18 @@ public enum StateRegistry {
|
||||
clientbound.register(
|
||||
TransferPacket.class,
|
||||
TransferPacket::new,
|
||||
map(0x73, MINECRAFT_1_20_5, false)
|
||||
);
|
||||
clientbound.register(ClientboundCustomReportDetailsPacket.class, ClientboundCustomReportDetailsPacket::new,
|
||||
map(0x7A, MINECRAFT_1_21, false));
|
||||
clientbound.register(ClientboundServerLinksPacket.class, ClientboundServerLinksPacket::new,
|
||||
map(0x7B, MINECRAFT_1_21, false));
|
||||
map(0x73, MINECRAFT_1_20_5, false),
|
||||
map(0x7A, MINECRAFT_1_21_2, false));
|
||||
clientbound.register(
|
||||
ClientboundCustomReportDetailsPacket.class,
|
||||
ClientboundCustomReportDetailsPacket::new,
|
||||
map(0x7A, MINECRAFT_1_21, false),
|
||||
map(0x81, MINECRAFT_1_21_2, false));
|
||||
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,
|
||||
map(0x41, ProtocolVersion.MINECRAFT_1_9, true),
|
||||
map(0x43, ProtocolVersion.MINECRAFT_1_12, true),
|
||||
@ -684,7 +718,8 @@ public enum StateRegistry {
|
||||
map(0x5A, ProtocolVersion.MINECRAFT_1_19_4, true),
|
||||
map(0x5C, ProtocolVersion.MINECRAFT_1_20_2, 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)
|
||||
);
|
||||
}
|
||||
},
|
||||
|
@ -17,7 +17,8 @@
|
||||
|
||||
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 io.netty.buffer.ByteBuf;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
@ -29,53 +30,115 @@ import java.util.List;
|
||||
*/
|
||||
public class MinecraftVarintFrameDecoder extends ByteToMessageDecoder {
|
||||
|
||||
private static final QuietDecoderException BAD_LENGTH_CACHED =
|
||||
private static final 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");
|
||||
|
||||
@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()) {
|
||||
in.clear();
|
||||
return;
|
||||
}
|
||||
|
||||
final VarintByteDecoder reader = new VarintByteDecoder();
|
||||
|
||||
int varintEnd = in.forEachByte(reader);
|
||||
if (varintEnd == -1) {
|
||||
// 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.
|
||||
// skip any runs of 0x00 we might find
|
||||
int packetStart = in.forEachByte(FIND_NON_NUL);
|
||||
if (packetStart == -1) {
|
||||
in.clear();
|
||||
}
|
||||
return;
|
||||
}
|
||||
in.readerIndex(packetStart);
|
||||
|
||||
if (reader.getResult() == DecodeResult.RUN_OF_ZEROES) {
|
||||
// this will return to the point where the next varint starts
|
||||
in.readerIndex(varintEnd);
|
||||
} else if (reader.getResult() == DecodeResult.SUCCESS) {
|
||||
int readVarint = reader.getReadVarint();
|
||||
int bytesRead = reader.getBytesRead();
|
||||
if (readVarint < 0) {
|
||||
in.clear();
|
||||
throw BAD_LENGTH_CACHED;
|
||||
} else if (readVarint == 0) {
|
||||
// skip over the empty packet(s) and ignore it
|
||||
in.readerIndex(varintEnd + 1);
|
||||
// try to read the length of the packet
|
||||
in.markReaderIndex();
|
||||
int preIndex = in.readerIndex();
|
||||
int length = readRawVarInt21(in);
|
||||
if (preIndex == in.readerIndex()) {
|
||||
return;
|
||||
}
|
||||
if (length < 0) {
|
||||
throw BAD_PACKET_LENGTH;
|
||||
}
|
||||
|
||||
// note that zero-length packets are ignored
|
||||
if (length > 0) {
|
||||
if (in.readableBytes() < length) {
|
||||
in.resetReaderIndex();
|
||||
} else {
|
||||
int minimumRead = bytesRead + readVarint;
|
||||
if (in.isReadable(minimumRead)) {
|
||||
out.add(in.retainedSlice(varintEnd + 1, readVarint));
|
||||
in.skipBytes(minimumRead);
|
||||
out.add(in.readRetainedSlice(length));
|
||||
}
|
||||
}
|
||||
} 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;
|
||||
}
|
||||
}
|
||||
|
@ -23,33 +23,34 @@ import com.velocitypowered.proxy.protocol.ProtocolUtils;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.channel.ChannelHandler;
|
||||
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.
|
||||
*/
|
||||
@ChannelHandler.Sharable
|
||||
public class MinecraftVarintLengthEncoder extends MessageToByteEncoder<ByteBuf> {
|
||||
public class MinecraftVarintLengthEncoder extends MessageToMessageEncoder<ByteBuf> {
|
||||
|
||||
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() {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void encode(ChannelHandlerContext ctx, ByteBuf msg, ByteBuf out) throws Exception {
|
||||
ProtocolUtils.writeVarInt(out, msg.readableBytes());
|
||||
out.writeBytes(msg);
|
||||
}
|
||||
protected void encode(ChannelHandlerContext ctx, ByteBuf buf,
|
||||
List<Object> list) throws Exception {
|
||||
final int length = buf.readableBytes();
|
||||
final int varintLength = ProtocolUtils.varIntBytes(length);
|
||||
|
||||
@Override
|
||||
protected ByteBuf allocateBuffer(ChannelHandlerContext ctx, ByteBuf msg, boolean preferDirect)
|
||||
throws Exception {
|
||||
int anticipatedRequiredCapacity = ProtocolUtils.varIntBytes(msg.readableBytes())
|
||||
+ msg.readableBytes();
|
||||
return IS_JAVA_CIPHER
|
||||
? ctx.alloc().heapBuffer(anticipatedRequiredCapacity)
|
||||
: ctx.alloc().directBuffer(anticipatedRequiredCapacity);
|
||||
final ByteBuf lenBuf = IS_JAVA_CIPHER
|
||||
? ctx.alloc().heapBuffer(varintLength)
|
||||
: ctx.alloc().directBuffer(varintLength);
|
||||
|
||||
ProtocolUtils.writeVarInt(lenBuf, length);
|
||||
list.add(lenBuf);
|
||||
list.add(buf.retain());
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
}
|
@ -34,21 +34,25 @@ public class ClientSettingsPacket implements MinecraftPacket {
|
||||
private byte difficulty; // 1.7 Protocol
|
||||
private short skinParts;
|
||||
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 int particleStatus; // Added in 1.21.2
|
||||
|
||||
public ClientSettingsPacket() {
|
||||
}
|
||||
|
||||
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.viewDistance = viewDistance;
|
||||
this.chatVisibility = chatVisibility;
|
||||
this.chatColors = chatColors;
|
||||
this.skinParts = skinParts;
|
||||
this.mainHand = mainHand;
|
||||
this.textFilteringEnabled = textFilteringEnabled;
|
||||
this.clientListingAllowed = clientListingAllowed;
|
||||
this.particleStatus = particleStatus;
|
||||
}
|
||||
|
||||
public String getLocale() {
|
||||
@ -102,12 +106,12 @@ public class ClientSettingsPacket implements MinecraftPacket {
|
||||
this.mainHand = mainHand;
|
||||
}
|
||||
|
||||
public boolean isChatFilteringEnabled() {
|
||||
return chatFilteringEnabled;
|
||||
public boolean isTextFilteringEnabled() {
|
||||
return textFilteringEnabled;
|
||||
}
|
||||
|
||||
public void setChatFilteringEnabled(boolean chatFilteringEnabled) {
|
||||
this.chatFilteringEnabled = chatFilteringEnabled;
|
||||
public void setTextFilteringEnabled(boolean textFilteringEnabled) {
|
||||
this.textFilteringEnabled = textFilteringEnabled;
|
||||
}
|
||||
|
||||
public boolean isClientListingAllowed() {
|
||||
@ -118,12 +122,20 @@ public class ClientSettingsPacket implements MinecraftPacket {
|
||||
this.clientListingAllowed = clientListingAllowed;
|
||||
}
|
||||
|
||||
public int getParticleStatus() {
|
||||
return particleStatus;
|
||||
}
|
||||
|
||||
public void setParticleStatus(int particleStatus) {
|
||||
this.particleStatus = particleStatus;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ClientSettings{" + "locale='" + locale + '\'' + ", viewDistance=" + viewDistance +
|
||||
", chatVisibility=" + chatVisibility + ", chatColors=" + chatColors + ", skinParts=" +
|
||||
skinParts + ", mainHand=" + mainHand + ", chatFilteringEnabled=" + chatFilteringEnabled +
|
||||
", clientListingAllowed=" + clientListingAllowed + '}';
|
||||
skinParts + ", mainHand=" + mainHand + ", chatFilteringEnabled=" + textFilteringEnabled +
|
||||
", clientListingAllowed=" + clientListingAllowed + ", particleStatus=" + particleStatus + '}';
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -143,10 +155,14 @@ public class ClientSettingsPacket implements MinecraftPacket {
|
||||
this.mainHand = ProtocolUtils.readVarInt(buf);
|
||||
|
||||
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_17)) {
|
||||
this.chatFilteringEnabled = buf.readBoolean();
|
||||
this.textFilteringEnabled = buf.readBoolean();
|
||||
|
||||
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_18)) {
|
||||
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);
|
||||
|
||||
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_17)) {
|
||||
buf.writeBoolean(chatFilteringEnabled);
|
||||
buf.writeBoolean(textFilteringEnabled);
|
||||
|
||||
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_18)) {
|
||||
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
|
||||
&& skinParts == that.skinParts
|
||||
&& mainHand == that.mainHand
|
||||
&& chatFilteringEnabled == that.chatFilteringEnabled
|
||||
&& textFilteringEnabled == that.textFilteringEnabled
|
||||
&& clientListingAllowed == that.clientListingAllowed
|
||||
&& particleStatus == that.particleStatus
|
||||
&& Objects.equals(locale, that.locale);
|
||||
}
|
||||
|
||||
@ -216,7 +237,8 @@ public class ClientSettingsPacket implements MinecraftPacket {
|
||||
difficulty,
|
||||
skinParts,
|
||||
mainHand,
|
||||
chatFilteringEnabled,
|
||||
clientListingAllowed);
|
||||
textFilteringEnabled,
|
||||
clientListingAllowed,
|
||||
particleStatus);
|
||||
}
|
||||
}
|
||||
|
@ -51,6 +51,7 @@ public class JoinGamePacket implements MinecraftPacket {
|
||||
private int simulationDistance; // 1.18+
|
||||
private @Nullable Pair<String, Long> lastDeathPosition; // 1.19+
|
||||
private int portalCooldown; // 1.20+
|
||||
private int seaLevel; // 1.21.2+
|
||||
private boolean enforcesSecureChat; // 1.20.5+
|
||||
|
||||
public int getEntityId() {
|
||||
@ -181,6 +182,14 @@ public class JoinGamePacket implements MinecraftPacket {
|
||||
this.portalCooldown = portalCooldown;
|
||||
}
|
||||
|
||||
public int getSeaLevel() {
|
||||
return seaLevel;
|
||||
}
|
||||
|
||||
public void setSeaLevel(int seaLevel) {
|
||||
this.seaLevel = seaLevel;
|
||||
}
|
||||
|
||||
public boolean getEnforcesSecureChat() {
|
||||
return this.enforcesSecureChat;
|
||||
}
|
||||
@ -204,6 +213,7 @@ public class JoinGamePacket implements MinecraftPacket {
|
||||
dimensionInfo + '\'' + ", currentDimensionData='" + currentDimensionData + '\'' +
|
||||
", previousGamemode=" + previousGamemode + ", simulationDistance=" + simulationDistance +
|
||||
", lastDeathPosition='" + lastDeathPosition + '\'' + ", portalCooldown=" + portalCooldown +
|
||||
", seaLevel=" + seaLevel +
|
||||
'}';
|
||||
}
|
||||
|
||||
@ -343,6 +353,11 @@ public class JoinGamePacket implements MinecraftPacket {
|
||||
}
|
||||
|
||||
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)) {
|
||||
this.enforcesSecureChat = buf.readBoolean();
|
||||
}
|
||||
@ -491,6 +506,10 @@ public class JoinGamePacket implements MinecraftPacket {
|
||||
|
||||
ProtocolUtils.writeVarInt(buf, portalCooldown);
|
||||
|
||||
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_21_2)) {
|
||||
ProtocolUtils.writeVarInt(buf, seaLevel);
|
||||
}
|
||||
|
||||
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_20_5)) {
|
||||
buf.writeBoolean(this.enforcesSecureChat);
|
||||
}
|
||||
|
@ -41,6 +41,7 @@ public class RespawnPacket implements MinecraftPacket {
|
||||
private CompoundBinaryTag currentDimensionData; // 1.16.2+
|
||||
private @Nullable Pair<String, Long> lastDeathPosition; // 1.19+
|
||||
private int portalCooldown; // 1.20+
|
||||
private int seaLevel; // 1.21.2+
|
||||
|
||||
public RespawnPacket() {
|
||||
}
|
||||
@ -48,7 +49,8 @@ public class RespawnPacket implements MinecraftPacket {
|
||||
public RespawnPacket(int dimension, long partialHashedSeed, short difficulty, short gamemode,
|
||||
String levelType, byte dataToKeep, DimensionInfo dimensionInfo,
|
||||
short previousGamemode, CompoundBinaryTag currentDimensionData,
|
||||
@Nullable Pair<String, Long> lastDeathPosition, int portalCooldown) {
|
||||
@Nullable Pair<String, Long> lastDeathPosition, int portalCooldown,
|
||||
int seaLevel) {
|
||||
this.dimension = dimension;
|
||||
this.partialHashedSeed = partialHashedSeed;
|
||||
this.difficulty = difficulty;
|
||||
@ -60,13 +62,15 @@ public class RespawnPacket implements MinecraftPacket {
|
||||
this.currentDimensionData = currentDimensionData;
|
||||
this.lastDeathPosition = lastDeathPosition;
|
||||
this.portalCooldown = portalCooldown;
|
||||
this.seaLevel = seaLevel;
|
||||
}
|
||||
|
||||
public static RespawnPacket fromJoinGame(JoinGamePacket joinGame) {
|
||||
return new RespawnPacket(joinGame.getDimension(), joinGame.getPartialHashedSeed(),
|
||||
joinGame.getDifficulty(), joinGame.getGamemode(), joinGame.getLevelType(),
|
||||
(byte) 0, joinGame.getDimensionInfo(), joinGame.getPreviousGamemode(),
|
||||
joinGame.getCurrentDimensionData(), joinGame.getLastDeathPosition(), joinGame.getPortalCooldown());
|
||||
joinGame.getCurrentDimensionData(), joinGame.getLastDeathPosition(),
|
||||
joinGame.getPortalCooldown(), joinGame.getSeaLevel());
|
||||
}
|
||||
|
||||
public int getDimension() {
|
||||
@ -141,6 +145,14 @@ public class RespawnPacket implements MinecraftPacket {
|
||||
this.portalCooldown = portalCooldown;
|
||||
}
|
||||
|
||||
public int getSeaLevel() {
|
||||
return seaLevel;
|
||||
}
|
||||
|
||||
public void setSeaLevel(int seaLevel) {
|
||||
this.seaLevel = seaLevel;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Respawn{"
|
||||
@ -155,6 +167,7 @@ public class RespawnPacket implements MinecraftPacket {
|
||||
+ ", previousGamemode=" + previousGamemode
|
||||
+ ", dimensionData=" + currentDimensionData
|
||||
+ ", portalCooldown=" + portalCooldown
|
||||
+ ", seaLevel=" + seaLevel
|
||||
+ '}';
|
||||
}
|
||||
|
||||
@ -204,6 +217,9 @@ public class RespawnPacket implements MinecraftPacket {
|
||||
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_20)) {
|
||||
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)) {
|
||||
this.dataToKeep = buf.readByte();
|
||||
}
|
||||
@ -262,6 +278,10 @@ public class RespawnPacket implements MinecraftPacket {
|
||||
ProtocolUtils.writeVarInt(buf, portalCooldown);
|
||||
}
|
||||
|
||||
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_21_2)) {
|
||||
ProtocolUtils.writeVarInt(buf, seaLevel);
|
||||
}
|
||||
|
||||
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_20_2)) {
|
||||
buf.writeByte(dataToKeep);
|
||||
}
|
||||
|
@ -188,6 +188,11 @@ public class UpsertPlayerInfoPacket implements MinecraftPacket {
|
||||
if (info.displayName != null) {
|
||||
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;
|
||||
@ -218,6 +223,7 @@ public class UpsertPlayerInfoPacket implements MinecraftPacket {
|
||||
private int gameMode;
|
||||
@Nullable
|
||||
private ComponentHolder displayName;
|
||||
private int listOrder;
|
||||
@Nullable
|
||||
private RemoteChatSession chatSession;
|
||||
|
||||
@ -250,6 +256,10 @@ public class UpsertPlayerInfoPacket implements MinecraftPacket {
|
||||
return displayName;
|
||||
}
|
||||
|
||||
public int getListOrder() {
|
||||
return listOrder;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public RemoteChatSession getChatSession() {
|
||||
return chatSession;
|
||||
@ -275,6 +285,10 @@ public class UpsertPlayerInfoPacket implements MinecraftPacket {
|
||||
this.displayName = displayName;
|
||||
}
|
||||
|
||||
public void setListOrder(int listOrder) {
|
||||
this.listOrder = listOrder;
|
||||
}
|
||||
|
||||
public void setChatSession(@Nullable RemoteChatSession chatSession) {
|
||||
this.chatSession = chatSession;
|
||||
}
|
||||
@ -288,6 +302,7 @@ public class UpsertPlayerInfoPacket implements MinecraftPacket {
|
||||
", latency=" + latency +
|
||||
", gameMode=" + gameMode +
|
||||
", displayName=" + displayName +
|
||||
", listOrder=" + listOrder +
|
||||
", chatSession=" + chatSession +
|
||||
'}';
|
||||
}
|
||||
|
@ -54,6 +54,7 @@ import java.util.Map;
|
||||
|
||||
public class ComponentHolder {
|
||||
private static final Logger logger = LogManager.getLogger(ComponentHolder.class);
|
||||
public static final int DEFAULT_MAX_STRING_SIZE = 262143;
|
||||
|
||||
private final ProtocolVersion version;
|
||||
private @MonotonicNonNull Component component;
|
||||
@ -172,21 +173,21 @@ public class ComponentHolder {
|
||||
case 1://BinaryTagTypes.BYTE:
|
||||
byte[] bytes = new byte[jsonArray.size()];
|
||||
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);
|
||||
case 3://BinaryTagTypes.INT:
|
||||
int[] ints = new int[jsonArray.size()];
|
||||
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);
|
||||
case 4://BinaryTagTypes.LONG:
|
||||
long[] longs = new long[jsonArray.size()];
|
||||
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);
|
||||
@ -282,6 +283,8 @@ public class ComponentHolder {
|
||||
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_20_3)) {
|
||||
return new ComponentHolder(version,
|
||||
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 {
|
||||
return new ComponentHolder(version, ProtocolUtils.readString(buf));
|
||||
}
|
||||
|
@ -71,7 +71,8 @@ public class SessionChatHandler implements ChatHandler<SessionPlayerChatPacket>
|
||||
invalidChange(logger, player);
|
||||
return null;
|
||||
}
|
||||
return this.player.getChatBuilderFactory().builder().message(packet.message)
|
||||
return this.player.getChatBuilderFactory().builder()
|
||||
.message(chatResult.getMessage().orElse(packet.getMessage()))
|
||||
.setTimestamp(packet.timestamp)
|
||||
.setLastSeenMessages(newLastSeenMessages)
|
||||
.toServer();
|
||||
|
@ -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
|
||||
public TabListEntry buildEntry(GameProfile profile, @Nullable Component displayName, int latency,
|
||||
int gameMode,
|
||||
@Nullable ChatSession chatSession, boolean listed) {
|
||||
int gameMode, @Nullable ChatSession chatSession, boolean listed) {
|
||||
return new KeyedVelocityTabListEntry(this, profile, displayName, latency, gameMode,
|
||||
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
|
||||
public void processLegacy(LegacyPlayerListItemPacket packet) {
|
||||
// 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.collect.Maps;
|
||||
import com.velocitypowered.api.network.ProtocolVersion;
|
||||
import com.velocitypowered.api.proxy.Player;
|
||||
import com.velocitypowered.api.proxy.player.ChatSession;
|
||||
import com.velocitypowered.api.proxy.player.TabListEntry;
|
||||
@ -89,7 +90,7 @@ public class VelocityTabList implements InternalTabList {
|
||||
} else {
|
||||
entry = new VelocityTabListEntry(this, entry1.getProfile(),
|
||||
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
|
||||
@ -128,6 +129,11 @@ public class VelocityTabList implements InternalTabList {
|
||||
actions.add(UpsertPlayerInfoPacket.Action.UPDATE_LISTED);
|
||||
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())) {
|
||||
ChatSession from = entry.getChatSession();
|
||||
if (from != null) {
|
||||
@ -162,6 +168,11 @@ public class VelocityTabList implements InternalTabList {
|
||||
}
|
||||
playerInfoEntry.setLatency(entry.getLatency());
|
||||
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;
|
||||
});
|
||||
@ -207,9 +218,9 @@ public class VelocityTabList implements InternalTabList {
|
||||
@Override
|
||||
public TabListEntry buildEntry(GameProfile profile, @Nullable Component displayName, int latency,
|
||||
int gameMode,
|
||||
@Nullable ChatSession chatSession, boolean listed) {
|
||||
@Nullable ChatSession chatSession, boolean listed, int listOrder) {
|
||||
return new VelocityTabListEntry(this, profile, displayName, latency, gameMode, chatSession,
|
||||
listed);
|
||||
listed, listOrder);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -246,7 +257,8 @@ public class VelocityTabList implements InternalTabList {
|
||||
0,
|
||||
-1,
|
||||
null,
|
||||
false
|
||||
false,
|
||||
0
|
||||
)
|
||||
);
|
||||
} else {
|
||||
@ -274,6 +286,9 @@ public class VelocityTabList implements InternalTabList {
|
||||
if (actions.contains(UpsertPlayerInfoPacket.Action.UPDATE_LISTED)) {
|
||||
currentEntry.setListedWithoutUpdate(entry.isListed());
|
||||
}
|
||||
if (actions.contains(UpsertPlayerInfoPacket.Action.UPDATE_LIST_ORDER)) {
|
||||
currentEntry.setListOrderWithoutUpdate(entry.getListOrder());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -17,6 +17,7 @@
|
||||
|
||||
package com.velocitypowered.proxy.tablist;
|
||||
|
||||
import com.velocitypowered.api.network.ProtocolVersion;
|
||||
import com.velocitypowered.api.proxy.player.ChatSession;
|
||||
import com.velocitypowered.api.proxy.player.TabList;
|
||||
import com.velocitypowered.api.proxy.player.TabListEntry;
|
||||
@ -38,6 +39,7 @@ public class VelocityTabListEntry implements TabListEntry {
|
||||
private int latency;
|
||||
private int gameMode;
|
||||
private boolean listed;
|
||||
private int listOrder;
|
||||
private @Nullable ChatSession session;
|
||||
|
||||
/**
|
||||
@ -45,7 +47,7 @@ public class VelocityTabListEntry implements TabListEntry {
|
||||
*/
|
||||
public VelocityTabListEntry(VelocityTabList tabList, GameProfile profile, Component displayName,
|
||||
int latency,
|
||||
int gameMode, @Nullable ChatSession session, boolean listed) {
|
||||
int gameMode, @Nullable ChatSession session, boolean listed, int listOrder) {
|
||||
this.tabList = tabList;
|
||||
this.profile = profile;
|
||||
this.displayName = displayName;
|
||||
@ -53,6 +55,7 @@ public class VelocityTabListEntry implements TabListEntry {
|
||||
this.gameMode = gameMode;
|
||||
this.session = session;
|
||||
this.listed = listed;
|
||||
this.listOrder = listOrder;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -150,4 +153,24 @@ public class VelocityTabListEntry implements TabListEntry {
|
||||
void setListedWithoutUpdate(boolean 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, () ->
|
||||
manager.executeAsync(source, "hello").join());
|
||||
|
||||
assertSame(expected, wrapper.getCause().getCause());
|
||||
assertSame(expected, wrapper.getCause());
|
||||
}
|
||||
|
||||
// Suggestions
|
||||
|
@ -29,9 +29,9 @@ import com.velocitypowered.api.permission.Tristate;
|
||||
import com.velocitypowered.api.proxy.Player;
|
||||
import com.velocitypowered.proxy.event.MockEventManager;
|
||||
import com.velocitypowered.proxy.event.VelocityEventManager;
|
||||
import com.velocitypowered.proxy.testutil.FakePluginManager;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
|
||||
@ -47,19 +47,9 @@ abstract class CommandTestSuite {
|
||||
eventManager = new MockEventManager();
|
||||
}
|
||||
|
||||
@AfterAll
|
||||
static void afterAll() {
|
||||
try {
|
||||
eventManager.shutdown();
|
||||
eventManager = null;
|
||||
} catch (final InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
}
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
this.manager = new VelocityCommandManager(eventManager);
|
||||
this.manager = new VelocityCommandManager(eventManager, new FakePluginManager());
|
||||
}
|
||||
|
||||
final void assertHandled(final String input) {
|
||||
|
@ -41,20 +41,24 @@ import org.junit.jupiter.api.TestInstance;
|
||||
public class EventTest {
|
||||
|
||||
public static final String CONTINUATION_TEST_THREAD_NAME = "Continuation test thread";
|
||||
private final VelocityEventManager eventManager =
|
||||
new VelocityEventManager(new FakePluginManager());
|
||||
private final FakePluginManager pluginManager = new FakePluginManager();
|
||||
private final VelocityEventManager eventManager = new VelocityEventManager(pluginManager);
|
||||
|
||||
@AfterAll
|
||||
void shutdown() throws Exception {
|
||||
eventManager.shutdown();
|
||||
pluginManager.shutdown();
|
||||
}
|
||||
|
||||
static final class TestEvent {
|
||||
|
||||
}
|
||||
|
||||
static void assertSyncThread(final Thread thread) {
|
||||
assertEquals(Thread.currentThread(), 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) {
|
||||
@ -90,6 +94,7 @@ public class EventTest {
|
||||
eventManager.fire(new TestEvent()).get();
|
||||
} finally {
|
||||
eventManager.unregisterListeners(FakePluginManager.PLUGIN_A);
|
||||
eventManager.unregisterListeners(FakePluginManager.PLUGIN_B);
|
||||
}
|
||||
|
||||
// Check that the order is A < B < C.
|
||||
@ -119,6 +124,7 @@ public class EventTest {
|
||||
eventManager.fire(new TestEvent()).get();
|
||||
} finally {
|
||||
eventManager.unregisterListeners(FakePluginManager.PLUGIN_A);
|
||||
eventManager.unregisterListeners(FakePluginManager.PLUGIN_B);
|
||||
}
|
||||
|
||||
// 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!");
|
||||
}
|
||||
|
||||
@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
|
||||
void testAlwaysAsync() throws Exception {
|
||||
final AlwaysAsyncListener listener = new AlwaysAsyncListener();
|
||||
@ -143,7 +169,7 @@ public class EventTest {
|
||||
@MonotonicNonNull Thread threadC;
|
||||
int result;
|
||||
|
||||
@Subscribe
|
||||
@Subscribe(async = true, order = PostOrder.EARLY)
|
||||
void firstAsync(TestEvent event) {
|
||||
result++;
|
||||
threadA = Thread.currentThread();
|
||||
@ -155,50 +181,93 @@ public class EventTest {
|
||||
return EventTask.async(() -> result++);
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
@Subscribe(order = PostOrder.LATE)
|
||||
void thirdAsync(TestEvent event) {
|
||||
result++;
|
||||
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
|
||||
void testContinuation() throws Exception {
|
||||
final ContinuationListener listener = new ContinuationListener();
|
||||
handleMethodListener(listener);
|
||||
assertAsyncThread(listener.thread1);
|
||||
assertAsyncThread(listener.thread2);
|
||||
assertContinuationThread(listener.thread2Custom);
|
||||
assertAsyncThread(listener.thread3);
|
||||
assertSyncThread(listener.threadA);
|
||||
assertSyncThread(listener.threadB);
|
||||
assertAsyncThread(listener.threadC);
|
||||
assertEquals(2, listener.value.get());
|
||||
}
|
||||
|
||||
static final class ContinuationListener {
|
||||
|
||||
@MonotonicNonNull Thread thread1;
|
||||
@MonotonicNonNull Thread thread2;
|
||||
@MonotonicNonNull Thread thread2Custom;
|
||||
@MonotonicNonNull Thread thread3;
|
||||
@MonotonicNonNull Thread threadA;
|
||||
@MonotonicNonNull Thread threadB;
|
||||
@MonotonicNonNull Thread threadC;
|
||||
|
||||
final AtomicInteger value = new AtomicInteger();
|
||||
|
||||
@Subscribe(order = PostOrder.EARLY)
|
||||
EventTask continuation(TestEvent event) {
|
||||
thread1 = Thread.currentThread();
|
||||
threadA = Thread.currentThread();
|
||||
return EventTask.withContinuation(continuation -> {
|
||||
value.incrementAndGet();
|
||||
thread2 = Thread.currentThread();
|
||||
threadB = Thread.currentThread();
|
||||
new Thread(() -> {
|
||||
thread2Custom = Thread.currentThread();
|
||||
try {
|
||||
Thread.sleep(100);
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
value.incrementAndGet();
|
||||
continuation.resume();
|
||||
}, CONTINUATION_TEST_THREAD_NAME).start();
|
||||
}).start();
|
||||
});
|
||||
}
|
||||
|
||||
@Subscribe(order = PostOrder.LATE)
|
||||
void afterContinuation(TestEvent event) {
|
||||
thread3 = Thread.currentThread();
|
||||
threadC = Thread.currentThread();
|
||||
}
|
||||
}
|
||||
|
||||
@ -207,9 +276,9 @@ public class EventTest {
|
||||
final ResumeContinuationImmediatelyListener listener =
|
||||
new ResumeContinuationImmediatelyListener();
|
||||
handleMethodListener(listener);
|
||||
assertAsyncThread(listener.threadA);
|
||||
assertAsyncThread(listener.threadB);
|
||||
assertAsyncThread(listener.threadC);
|
||||
assertSyncThread(listener.threadA);
|
||||
assertSyncThread(listener.threadB);
|
||||
assertSyncThread(listener.threadC);
|
||||
assertEquals(2, listener.result);
|
||||
}
|
||||
|
||||
@ -241,42 +310,44 @@ public class EventTest {
|
||||
void testContinuationParameter() throws Exception {
|
||||
final ContinuationParameterListener listener = new ContinuationParameterListener();
|
||||
handleMethodListener(listener);
|
||||
assertAsyncThread(listener.thread1);
|
||||
assertAsyncThread(listener.thread2);
|
||||
assertContinuationThread(listener.thread2Custom);
|
||||
assertAsyncThread(listener.thread3);
|
||||
assertSyncThread(listener.threadA);
|
||||
assertSyncThread(listener.threadB);
|
||||
assertAsyncThread(listener.threadC);
|
||||
assertEquals(3, listener.result.get());
|
||||
}
|
||||
|
||||
static final class ContinuationParameterListener {
|
||||
|
||||
@MonotonicNonNull Thread thread1;
|
||||
@MonotonicNonNull Thread thread2;
|
||||
@MonotonicNonNull Thread thread2Custom;
|
||||
@MonotonicNonNull Thread thread3;
|
||||
@MonotonicNonNull Thread threadA;
|
||||
@MonotonicNonNull Thread threadB;
|
||||
@MonotonicNonNull Thread threadC;
|
||||
|
||||
final AtomicInteger result = new AtomicInteger();
|
||||
|
||||
@Subscribe
|
||||
void resume(TestEvent event, Continuation continuation) {
|
||||
thread1 = Thread.currentThread();
|
||||
threadA = Thread.currentThread();
|
||||
result.incrementAndGet();
|
||||
continuation.resume();
|
||||
}
|
||||
|
||||
@Subscribe(order = PostOrder.LATE)
|
||||
void resumeFromCustomThread(TestEvent event, Continuation continuation) {
|
||||
thread2 = Thread.currentThread();
|
||||
threadB = Thread.currentThread();
|
||||
new Thread(() -> {
|
||||
thread2Custom = Thread.currentThread();
|
||||
try {
|
||||
Thread.sleep(100);
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
result.incrementAndGet();
|
||||
continuation.resume();
|
||||
}, CONTINUATION_TEST_THREAD_NAME).start();
|
||||
}).start();
|
||||
}
|
||||
|
||||
@Subscribe(order = PostOrder.LAST)
|
||||
void afterCustomThread(TestEvent event, Continuation continuation) {
|
||||
thread3 = Thread.currentThread();
|
||||
threadC = Thread.currentThread();
|
||||
result.incrementAndGet();
|
||||
continuation.resume();
|
||||
}
|
||||
@ -328,8 +399,7 @@ public class EventTest {
|
||||
+ "the second is the fancy continuation");
|
||||
}
|
||||
},
|
||||
new TypeToken<TriConsumer<Object, Object, FancyContinuation>>() {
|
||||
},
|
||||
new TypeToken<TriConsumer<Object, Object, FancyContinuation>>() {},
|
||||
invokeFunction -> (instance, event) ->
|
||||
EventTask.withContinuation(continuation ->
|
||||
invokeFunction.accept(instance, event, new FancyContinuationImpl(continuation))
|
||||
|
@ -70,7 +70,7 @@ public class ProtocolUtilsTest {
|
||||
private void writeReadTestOld(ByteBuf buf, int test) {
|
||||
buf.clear();
|
||||
writeVarIntOld(buf, test);
|
||||
assertEquals(test, ProtocolUtils.readVarIntSafely(buf));
|
||||
assertEquals(test, ProtocolUtils.readVarInt(buf));
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -103,7 +103,7 @@ public class ProtocolUtilsTest {
|
||||
"Encoding of " + i + " was invalid");
|
||||
|
||||
assertEquals(i, oldReadVarIntSafely(varintNew));
|
||||
assertEquals(i, ProtocolUtils.readVarIntSafely(varintOld));
|
||||
assertEquals(i, ProtocolUtils.readVarInt(varintOld));
|
||||
|
||||
varintNew.clear();
|
||||
varintOld.clear();
|
||||
|
@ -18,14 +18,16 @@
|
||||
package com.velocitypowered.proxy.testutil;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.util.concurrent.ThreadFactoryBuilder;
|
||||
import com.velocitypowered.api.plugin.PluginContainer;
|
||||
import com.velocitypowered.api.plugin.PluginDescription;
|
||||
import com.velocitypowered.api.plugin.PluginManager;
|
||||
import com.velocitypowered.proxy.plugin.virtual.VelocityVirtualPlugin;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Collection;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.ForkJoinPool;
|
||||
import java.util.concurrent.Executors;
|
||||
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_B = new Object();
|
||||
|
||||
private static final PluginContainer PC_A = new FakePluginContainer("a", PLUGIN_A);
|
||||
private static final PluginContainer PC_B = new FakePluginContainer("b", PLUGIN_B);
|
||||
private final PluginContainer containerA = new FakePluginContainer("a", PLUGIN_A);
|
||||
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
|
||||
public @NonNull Optional<PluginContainer> fromInstance(@NonNull Object instance) {
|
||||
if (instance == PLUGIN_A) {
|
||||
return Optional.of(PC_A);
|
||||
return Optional.of(containerA);
|
||||
} else if (instance == PLUGIN_B) {
|
||||
return Optional.of(PC_B);
|
||||
return Optional.of(containerB);
|
||||
} else if (instance == VelocityVirtualPlugin.INSTANCE) {
|
||||
return Optional.of(containerVelocity);
|
||||
} else {
|
||||
return Optional.empty();
|
||||
}
|
||||
@ -54,9 +64,11 @@ public class FakePluginManager implements PluginManager {
|
||||
public @NonNull Optional<PluginContainer> getPlugin(@NonNull String id) {
|
||||
switch (id) {
|
||||
case "a":
|
||||
return Optional.of(PC_A);
|
||||
return Optional.of(containerA);
|
||||
case "b":
|
||||
return Optional.of(PC_B);
|
||||
return Optional.of(containerB);
|
||||
case "velocity":
|
||||
return Optional.of(containerVelocity);
|
||||
default:
|
||||
return Optional.empty();
|
||||
}
|
||||
@ -64,7 +76,7 @@ public class FakePluginManager implements PluginManager {
|
||||
|
||||
@Override
|
||||
public @NonNull Collection<PluginContainer> getPlugins() {
|
||||
return ImmutableList.of(PC_A, PC_B);
|
||||
return ImmutableList.of(containerVelocity, containerA, containerB);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -77,16 +89,18 @@ public class FakePluginManager implements PluginManager {
|
||||
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 Object instance;
|
||||
private final ExecutorService service;
|
||||
|
||||
private FakePluginContainer(String id, Object instance) {
|
||||
this.id = id;
|
||||
this.instance = instance;
|
||||
this.service = ForkJoinPool.commonPool();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
Laden…
In neuem Issue referenzieren
Einen Benutzer sperren