3
0
Mirror von https://github.com/PaperMC/Velocity.git synchronisiert 2024-11-17 05:20:14 +01:00

Merge branch 'dev/1.1.0' into dev/2.0.0

# Conflicts:
#	api/build.gradle
#	api/src/main/java/com/velocitypowered/api/proxy/connection/Player.java
#	api/src/main/java/com/velocitypowered/api/proxy/player/TabList.java
#	build.gradle
#	proxy/build.gradle
#	proxy/src/main/java/com/velocitypowered/proxy/Velocity.java
#	proxy/src/main/java/com/velocitypowered/proxy/command/CommandNodeFactory.java
#	proxy/src/main/java/com/velocitypowered/proxy/connection/backend/BackendPlaySessionHandler.java
#	proxy/src/main/java/com/velocitypowered/proxy/connection/backend/BungeeCordMessageResponder.java
#	proxy/src/main/java/com/velocitypowered/proxy/connection/backend/VelocityServerConnection.java
#	proxy/src/main/java/com/velocitypowered/proxy/connection/client/ConnectedPlayer.java
#	proxy/src/main/java/com/velocitypowered/proxy/network/packet/clientbound/ClientboundAvailableCommandsPacket.java
#	proxy/src/main/java/com/velocitypowered/proxy/network/packet/clientbound/ClientboundJoinGamePacket.java
#	proxy/src/main/java/com/velocitypowered/proxy/network/packet/clientbound/ClientboundRespawnPacket.java
#	proxy/src/main/java/com/velocitypowered/proxy/network/pipeline/MinecraftDecoder.java
#	proxy/src/main/java/com/velocitypowered/proxy/protocol/MinecraftPacket.java
#	proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/EncryptionResponse.java
#	proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/HeaderAndFooter.java
#	proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/LoginPluginMessage.java
#	proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/PluginMessage.java
#	proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/ServerLogin.java
#	proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/StatusPing.java
#	proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/StatusRequest.java
#	proxy/src/main/java/com/velocitypowered/proxy/tablist/VelocityTabList.java
#	proxy/src/main/java/com/velocitypowered/proxy/tablist/VelocityTabListLegacy.java
#	proxy/src/test/java/com/velocitypowered/proxy/command/CommandManagerTests.java
Dieser Commit ist enthalten in:
Andrew Steinborn 2021-02-21 17:56:40 -05:00
Commit 699147c916
36 geänderte Dateien mit 870 neuen und 874 gelöschten Zeilen

32
.github/workflows/gradle.yml vendored Normale Datei
Datei anzeigen

@ -0,0 +1,32 @@
# This workflow will build a Java project with Gradle
# For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-gradle
name: Java CI with Gradle
on: [push, pull_request]
jobs:
build-8:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up JDK 1.8
uses: actions/setup-java@v1
with:
java-version: 1.8
- name: Grant execute permission for gradlew
run: chmod +x gradlew
- name: Build with Gradle
run: ./gradlew build
build-11:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up JDK 11
uses: actions/setup-java@v1
with:
java-version: 11
- name: Grant execute permission for gradlew
run: chmod +x gradlew
- name: Build with Gradle
run: ./gradlew build

Datei anzeigen

@ -28,9 +28,7 @@ sure that you are properly adhering to the code style.
To reduce bugs and ensure code quality, we run the following tools on all commits To reduce bugs and ensure code quality, we run the following tools on all commits
and pull requests: and pull requests:
* [Checker Framework](https://checkerframework.org/): an enhancement to Java's type * [SpotBugs](https://spotbugs.github.io/): ensures that common errors do not
system that is designed to help catch bugs. Velocity runs the _Nullness Checker_ get into the codebase. The build will fail if SpotBugs finds an issue.
and the _Optional Checker_. The build will fail if Checker Framework notices an
issue.
* [Checkstyle](http://checkstyle.sourceforge.net/): ensures that your code is * [Checkstyle](http://checkstyle.sourceforge.net/): ensures that your code is
correctly formatted. The build will fail if Checkstyle detects a problem. correctly formatted. The build will fail if Checkstyle detects a problem.

Datei anzeigen

@ -84,6 +84,15 @@ javadoc {
// Mark sources as Java 8 source compatible // Mark sources as Java 8 source compatible
options.source = '8' options.source = '8'
// Remove 'undefined' from seach paths when generating javadoc for a non-modular project (JDK-8215291)
if (JavaVersion.current() >= JavaVersion.VERSION_1_9 && JavaVersion.current() < JavaVersion.VERSION_12) {
options.addBooleanOption('-no-module-directories', true)
}
}
test {
useJUnitPlatform()
} }
test { test {

Datei anzeigen

@ -0,0 +1,38 @@
package com.velocitypowered.api.event.player;
import com.google.common.base.Preconditions;
import com.velocitypowered.api.proxy.Player;
import com.velocitypowered.api.proxy.messages.ChannelIdentifier;
import java.util.List;
/**
* This event is fired when a client ({@link Player}) sends a plugin message through the
* register channel.
*/
public final class PlayerChannelRegisterEvent {
private final Player player;
private final List<ChannelIdentifier> channels;
public PlayerChannelRegisterEvent(Player player, List<ChannelIdentifier> channels) {
this.player = Preconditions.checkNotNull(player, "player");
this.channels = Preconditions.checkNotNull(channels, "channels");
}
public Player getPlayer() {
return player;
}
public List<ChannelIdentifier> getChannels() {
return channels;
}
@Override
public String toString() {
return "PlayerChannelRegisterEvent{"
+ "player=" + player
+ ", channels=" + channels
+ '}';
}
}

Datei anzeigen

@ -12,7 +12,7 @@ import org.checkerframework.checker.nullness.qual.Nullable;
*/ */
public final class MinecraftChannelIdentifier implements ChannelIdentifier { public final class MinecraftChannelIdentifier implements ChannelIdentifier {
private static final Pattern VALID_IDENTIFIER_REGEX = Pattern.compile("[a-z0-9\\-_]*"); private static final Pattern VALID_IDENTIFIER_REGEX = Pattern.compile("[a-z0-9/\\-_]*");
private final String namespace; private final String namespace;
private final String name; private final String name;

Datei anzeigen

@ -227,6 +227,11 @@ public final class ServerPing {
return this; return this;
} }
public Builder clearFavicon() {
this.favicon = null;
return this;
}
/** /**
* Uses the information from this builder to create a new {@link ServerPing} instance. The * Uses the information from this builder to create a new {@link ServerPing} instance. The
* builder can be re-used after this event has been called. * builder can be re-used after this event has been called.

Datei anzeigen

@ -35,6 +35,11 @@ class MinecraftChannelIdentifierTest {
assertEquals(expected, MinecraftChannelIdentifier.from("velocity:test")); assertEquals(expected, MinecraftChannelIdentifier.from("velocity:test"));
} }
@Test
void createAllowsSlashes() {
create("velocity", "test/test2");
}
@Test @Test
void fromIdentifierThrowsOnBadValues() { void fromIdentifierThrowsOnBadValues() {
assertAll( assertAll(

Datei anzeigen

@ -20,11 +20,11 @@ allprojects {
ext { ext {
// dependency versions // dependency versions
adventureVersion = '4.1.1' adventureVersion = '4.5.0'
junitVersion = '5.7.0' junitVersion = '5.7.0'
slf4jVersion = '1.7.30' slf4jVersion = '1.7.30'
log4jVersion = '2.13.3' log4jVersion = '2.13.3'
nettyVersion = '4.1.56.Final' nettyVersion = '4.1.59.Final'
guavaVersion = '30.0-jre' guavaVersion = '30.0-jre'
checkerFrameworkVersion = '3.6.1' checkerFrameworkVersion = '3.6.1'
configurateVersion = '4.0.0-SNAPSHOT' configurateVersion = '4.0.0-SNAPSHOT'

Datei anzeigen

@ -55,13 +55,13 @@ dependencies {
implementation "io.netty:netty-handler:${nettyVersion}" implementation "io.netty:netty-handler:${nettyVersion}"
implementation "io.netty:netty-transport-native-epoll:${nettyVersion}" implementation "io.netty:netty-transport-native-epoll:${nettyVersion}"
implementation "io.netty:netty-transport-native-epoll:${nettyVersion}:linux-x86_64" implementation "io.netty:netty-transport-native-epoll:${nettyVersion}:linux-x86_64"
implementation "io.netty:netty-transport-native-epoll:${nettyVersion}:linux-aarch64" implementation "io.netty:netty-transport-native-epoll:${nettyVersion}:linux-aarch_64"
implementation "io.netty:netty-resolver-dns:${nettyVersion}"
implementation "org.apache.logging.log4j:log4j-api:${log4jVersion}" implementation "org.apache.logging.log4j:log4j-api:${log4jVersion}"
implementation "org.apache.logging.log4j:log4j-core:${log4jVersion}" implementation "org.apache.logging.log4j:log4j-core:${log4jVersion}"
implementation "org.apache.logging.log4j:log4j-slf4j-impl:${log4jVersion}" implementation "org.apache.logging.log4j:log4j-slf4j-impl:${log4jVersion}"
implementation "org.apache.logging.log4j:log4j-iostreams:${log4jVersion}" implementation "org.apache.logging.log4j:log4j-iostreams:${log4jVersion}"
implementation "org.apache.logging.log4j:log4j-jul:${log4jVersion}"
implementation 'net.sf.jopt-simple:jopt-simple:5.0.4' // command-line options implementation 'net.sf.jopt-simple:jopt-simple:5.0.4' // command-line options
implementation 'net.minecrell:terminalconsoleappender:1.2.0' implementation 'net.minecrell:terminalconsoleappender:1.2.0'
@ -76,7 +76,7 @@ dependencies {
implementation 'com.spotify:completable-futures:0.3.3' implementation 'com.spotify:completable-futures:0.3.3'
implementation 'com.electronwill.night-config:toml:3.6.3' implementation 'com.electronwill.night-config:toml:3.6.3'
implementation 'org.bstats:bstats-base:2.2.0'
implementation 'org.lanternpowered:lmbda:2.0.0-SNAPSHOT' implementation 'org.lanternpowered:lmbda:2.0.0-SNAPSHOT'
implementation 'com.github.ben-manes.caffeine:caffeine:2.8.8' implementation 'com.github.ben-manes.caffeine:caffeine:2.8.8'
@ -117,7 +117,6 @@ shadowJar {
exclude 'it/unimi/dsi/fastutil/objects/*Object2Float*' exclude 'it/unimi/dsi/fastutil/objects/*Object2Float*'
exclude 'it/unimi/dsi/fastutil/objects/*Object2IntArray*' exclude 'it/unimi/dsi/fastutil/objects/*Object2IntArray*'
exclude 'it/unimi/dsi/fastutil/objects/*Object2IntAVL*' exclude 'it/unimi/dsi/fastutil/objects/*Object2IntAVL*'
exclude 'it/unimi/dsi/fastutil/objects/*Object*OpenCustom*'
exclude 'it/unimi/dsi/fastutil/objects/*Object2IntRB*' exclude 'it/unimi/dsi/fastutil/objects/*Object2IntRB*'
exclude 'it/unimi/dsi/fastutil/objects/*Object2Long*' exclude 'it/unimi/dsi/fastutil/objects/*Object2Long*'
exclude 'it/unimi/dsi/fastutil/objects/*Object2Object*' exclude 'it/unimi/dsi/fastutil/objects/*Object2Object*'
@ -127,12 +126,10 @@ shadowJar {
exclude 'it/unimi/dsi/fastutil/objects/*Reference*' exclude 'it/unimi/dsi/fastutil/objects/*Reference*'
exclude 'it/unimi/dsi/fastutil/shorts/**' exclude 'it/unimi/dsi/fastutil/shorts/**'
exclude 'org/checkerframework/checker/**' exclude 'org/checkerframework/checker/**'
relocate 'org.bstats', 'com.velocitypowered.proxy.bstats'
} }
artifacts { artifacts {
archives shadowJar archives shadowJar
} }
test {
useJUnitPlatform()
}

Datei anzeigen

@ -1,82 +1,67 @@
package com.velocitypowered.proxy; package com.velocitypowered.proxy;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import com.velocitypowered.proxy.config.VelocityConfiguration; import com.velocitypowered.proxy.config.VelocityConfiguration;
import io.netty.handler.codec.http.HttpHeaderNames;
import java.io.BufferedWriter; import java.io.File;
import java.io.ByteArrayOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.OutputStreamWriter; import java.nio.file.Paths;
import java.io.Writer;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import java.util.zip.GZIPOutputStream;
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
import org.asynchttpclient.ListenableFuture; import org.bstats.MetricsBase;
import org.asynchttpclient.Response; import org.bstats.charts.CustomChart;
import org.bstats.charts.DrilldownPie;
import org.bstats.charts.SimplePie;
import org.bstats.charts.SingleLineChart;
import org.bstats.config.MetricsConfig;
import org.bstats.json.JsonObjectBuilder;
/**
* bStats collects some data for plugin authors.
* <p/>
* Check out https://bStats.org/ to learn more about bStats!
*/
public class Metrics { public class Metrics {
// The version of this bStats class private MetricsBase metricsBase;
private static final int B_STATS_METRICS_REVISION = 2;
// The url to which the data is sent private Metrics(Logger logger, int serviceId, boolean defaultEnabled) {
private static final String URL = "https://bstats.org/submitData/server-implementation"; File configFile = Paths.get("plugins").resolve("bStats").resolve("config.txt").toFile();
MetricsConfig config;
try {
config = new MetricsConfig(configFile, defaultEnabled);
} catch (IOException e) {
logger.error("Failed to create bStats config", e);
return;
}
// The logger for the failed requests metricsBase = new MetricsBase(
private static final Logger logger = LogManager.getLogger(Metrics.class); "server-implementation",
config.getServerUUID(),
serviceId,
config.isEnabled(),
this::appendPlatformData,
jsonObjectBuilder -> { /* NOP */ },
null,
() -> true,
logger::warn,
logger::info,
config.isLogErrorsEnabled(),
config.isLogSentDataEnabled(),
config.isLogResponseStatusTextEnabled()
);
// Should failed requests be logged? if (!config.didExistBefore()) {
private boolean logFailedRequests = false; // Send an info message when the bStats config file gets created for the first time
logger.info("Velocity and some of its plugins collect metrics"
// The name of the server software + " and send them to bStats (https://bStats.org).");
private final String name; logger.info("bStats collects some basic information for plugin"
+ " authors, like how many people use");
// The plugin ID for the server software as assigned by bStats. logger.info("their plugin and their total player count."
private final int pluginId; + " It's recommended to keep bStats enabled, but");
logger.info("if you're not comfortable with this, you can opt-out"
// The uuid of the server + " by editing the config.txt file in");
private final String serverUuid; logger.info("the '/plugins/bStats/' folder and setting enabled to false.");
}
// A list with all custom charts
private final List<CustomChart> charts = new ArrayList<>();
private final VelocityServer server;
/**
* Class constructor.
* @param name The name of the server software.
* @param pluginId The plugin ID for the server software as assigned by bStats.
* @param serverUuid The uuid of the server.
* @param logFailedRequests Whether failed requests should be logged or not.
* @param server The Velocity server instance.
*/
private Metrics(String name, int pluginId, String serverUuid, boolean logFailedRequests,
VelocityServer server) {
this.name = name;
this.pluginId = pluginId;
this.serverUuid = serverUuid;
this.logFailedRequests = logFailedRequests;
this.server = server;
// Start submitting the data
startSubmitting();
} }
/** /**
@ -85,511 +70,37 @@ public class Metrics {
* @param chart The chart to add. * @param chart The chart to add.
*/ */
public void addCustomChart(CustomChart chart) { public void addCustomChart(CustomChart chart) {
if (chart == null) { metricsBase.addCustomChart(chart);
throw new IllegalArgumentException("Chart cannot be null!");
}
charts.add(chart);
}
/**
* Starts the Scheduler which submits our data every 30 minutes.
*/
private void startSubmitting() {
final Timer timer = new Timer(true);
timer.scheduleAtFixedRate(new TimerTask() {
@Override
public void run() {
submitData();
}
}, 1000, 1000 * 60 * 30);
// Submit the data every 30 minutes, first time after 5 minutes to give other plugins enough
// time to start.
//
// WARNING: Changing the frequency has no effect but your plugin WILL be blocked/deleted!
// WARNING: Just don't do it!
}
/**
* Gets the plugin specific data.
*
* @return The plugin specific data.
*/
private JsonObject getPluginData() {
JsonObject data = new JsonObject();
data.addProperty("pluginName", name); // Append the name of the server software
data.addProperty("id", pluginId);
data.addProperty("metricsRevision", B_STATS_METRICS_REVISION);
JsonArray customCharts = new JsonArray();
for (CustomChart customChart : charts) {
// Add the data of the custom charts
JsonObject chart = customChart.getRequestJsonObject();
if (chart == null) { // If the chart is null, we skip it
continue;
}
customCharts.add(chart);
}
data.add("customCharts", customCharts);
return data;
}
/**
* Gets the server specific data.
*
* @return The server specific data.
*/
private JsonObject getServerData() {
// OS specific data
String osName = System.getProperty("os.name");
String osArch = System.getProperty("os.arch");
String osVersion = System.getProperty("os.version");
int coreCount = Runtime.getRuntime().availableProcessors();
JsonObject data = new JsonObject();
data.addProperty("serverUUID", serverUuid);
data.addProperty("osName", osName);
data.addProperty("osArch", osArch);
data.addProperty("osVersion", osVersion);
data.addProperty("coreCount", coreCount);
return data;
}
/**
* Collects the data and sends it afterwards.
*/
private void submitData() {
final JsonObject data = getServerData();
JsonArray pluginData = new JsonArray();
pluginData.add(getPluginData());
data.add("plugins", pluginData);
try {
// We are still in the Thread of the timer, so nothing get blocked :)
sendData(data);
} catch (Exception e) {
// Something went wrong! :(
if (logFailedRequests) {
logger.warn("Could not submit stats of {}", name, e);
}
}
}
/**
* Sends the data to the bStats server.
*
* @param data The data to send.
* @throws Exception If the request failed.
*/
private void sendData(JsonObject data) throws Exception {
if (data == null) {
throw new IllegalArgumentException("Data cannot be null!");
}
// Compress the data to save bandwidth
ListenableFuture<Response> future = server.getAsyncHttpClient()
.preparePost(URL)
.addHeader(HttpHeaderNames.CONTENT_ENCODING, "gzip")
.addHeader(HttpHeaderNames.ACCEPT, "application/json")
.addHeader(HttpHeaderNames.CONTENT_TYPE, "application/json")
.setBody(createResponseBody(data))
.execute();
future.addListener(() -> {
if (logFailedRequests) {
try {
Response r = future.get();
if (r.getStatusCode() != 429) {
logger.error("Got HTTP status code {} when sending metrics to bStats",
r.getStatusCode());
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} catch (ExecutionException e) {
logger.error("Unable to send metrics to bStats", e);
}
}
}, null);
}
private static byte[] createResponseBody(JsonObject object) throws IOException {
ByteArrayOutputStream os = new ByteArrayOutputStream();
try (Writer writer =
new BufferedWriter(
new OutputStreamWriter(
new GZIPOutputStream(os), StandardCharsets.UTF_8
)
)
) {
VelocityServer.GENERAL_GSON.toJson(object, writer);
}
return os.toByteArray();
}
/**
* Represents a custom chart.
*/
public abstract static class CustomChart {
// The id of the chart
final String chartId;
/**
* Class constructor.
*
* @param chartId The id of the chart.
*/
CustomChart(String chartId) {
if (chartId == null || chartId.isEmpty()) {
throw new IllegalArgumentException("ChartId cannot be null or empty!");
}
this.chartId = chartId;
}
private JsonObject getRequestJsonObject() {
JsonObject chart = new JsonObject();
chart.addProperty("chartId", chartId);
try {
JsonObject data = getChartData();
if (data == null) {
// If the data is null we don't send the chart.
return null;
}
chart.add("data", data);
} catch (Throwable t) {
return null;
}
return chart;
}
protected abstract JsonObject getChartData() throws Exception;
}
/**
* Represents a custom simple pie.
*/
public static class SimplePie extends CustomChart {
private final Callable<String> callable;
/**
* Class constructor.
*
* @param chartId The id of the chart.
* @param callable The callable which is used to request the chart data.
*/
public SimplePie(String chartId, Callable<String> callable) {
super(chartId);
this.callable = callable;
}
@Override
protected JsonObject getChartData() throws Exception {
JsonObject data = new JsonObject();
String value = callable.call();
if (value == null || value.isEmpty()) {
// Null = skip the chart
return null;
}
data.addProperty("value", value);
return data;
}
}
/**
* Represents a custom advanced pie.
*/
public static class AdvancedPie extends CustomChart {
private final Callable<Map<String, Integer>> callable;
/**
* Class constructor.
*
* @param chartId The id of the chart.
* @param callable The callable which is used to request the chart data.
*/
public AdvancedPie(String chartId, Callable<Map<String, Integer>> callable) {
super(chartId);
this.callable = callable;
}
@Override
protected JsonObject getChartData() throws Exception {
Map<String, Integer> map = callable.call();
if (map == null || map.isEmpty()) {
// Null = skip the chart
return null;
}
JsonObject data = new JsonObject();
JsonObject values = new JsonObject();
boolean allSkipped = true;
for (Map.Entry<String, Integer> entry : map.entrySet()) {
if (entry.getValue() == 0) {
continue; // Skip this invalid
}
allSkipped = false;
values.addProperty(entry.getKey(), entry.getValue());
}
if (allSkipped) {
// Null = skip the chart
return null;
}
data.add("values", values);
return data;
}
}
/**
* Represents a custom drilldown pie.
*/
public static class DrilldownPie extends CustomChart {
private final Callable<Map<String, Map<String, Integer>>> callable;
/**
* Class constructor.
*
* @param chartId The id of the chart.
* @param callable The callable which is used to request the chart data.
*/
public DrilldownPie(String chartId, Callable<Map<String, Map<String, Integer>>> callable) {
super(chartId);
this.callable = callable;
}
@Override
public JsonObject getChartData() throws Exception {
Map<String, Map<String, Integer>> map = callable.call();
if (map == null || map.isEmpty()) {
// Null = skip the chart
return null;
}
boolean reallyAllSkipped = true;
JsonObject data = new JsonObject();
JsonObject values = new JsonObject();
for (Map.Entry<String, Map<String, Integer>> entryValues : map.entrySet()) {
JsonObject value = new JsonObject();
boolean allSkipped = true;
for (Map.Entry<String, Integer> valueEntry : map.get(entryValues.getKey()).entrySet()) {
value.addProperty(valueEntry.getKey(), valueEntry.getValue());
allSkipped = false;
}
if (!allSkipped) {
reallyAllSkipped = false;
values.add(entryValues.getKey(), value);
}
}
if (reallyAllSkipped) {
// Null = skip the chart
return null;
}
data.add("values", values);
return data;
}
}
/**
* Represents a custom single line chart.
*/
public static class SingleLineChart extends CustomChart {
private final Callable<Integer> callable;
/**
* Class constructor.
*
* @param chartId The id of the chart.
* @param callable The callable which is used to request the chart data.
*/
public SingleLineChart(String chartId, Callable<Integer> callable) {
super(chartId);
this.callable = callable;
}
@Override
protected JsonObject getChartData() throws Exception {
JsonObject data = new JsonObject();
int value = callable.call();
if (value == 0) {
// Null = skip the chart
return null;
}
data.addProperty("value", value);
return data;
}
}
/**
* Represents a custom multi line chart.
*/
public static class MultiLineChart extends CustomChart {
private final Callable<Map<String, Integer>> callable;
/**
* Class constructor.
*
* @param chartId The id of the chart.
* @param callable The callable which is used to request the chart data.
*/
public MultiLineChart(String chartId, Callable<Map<String, Integer>> callable) {
super(chartId);
this.callable = callable;
}
@Override
protected JsonObject getChartData() throws Exception {
Map<String, Integer> map = callable.call();
if (map == null || map.isEmpty()) {
// Null = skip the chart
return null;
}
JsonObject data = new JsonObject();
JsonObject values = new JsonObject();
boolean allSkipped = true;
for (Map.Entry<String, Integer> entry : map.entrySet()) {
if (entry.getValue() == 0) {
continue; // Skip this invalid
}
allSkipped = false;
values.addProperty(entry.getKey(), entry.getValue());
}
if (allSkipped) {
// Null = skip the chart
return null;
}
data.add("values", values);
return data;
}
}
/**
* Represents a custom simple bar chart.
*/
public static class SimpleBarChart extends CustomChart {
private final Callable<Map<String, Integer>> callable;
/**
* Class constructor.
*
* @param chartId The id of the chart.
* @param callable The callable which is used to request the chart data.
*/
public SimpleBarChart(String chartId, Callable<Map<String, Integer>> callable) {
super(chartId);
this.callable = callable;
}
@Override
protected JsonObject getChartData() throws Exception {
JsonObject data = new JsonObject();
JsonObject values = new JsonObject();
Map<String, Integer> map = callable.call();
if (map == null || map.isEmpty()) {
// Null = skip the chart
return null;
}
for (Map.Entry<String, Integer> entry : map.entrySet()) {
JsonArray categoryValues = new JsonArray();
categoryValues.add(entry.getValue());
values.add(entry.getKey(), categoryValues);
}
data.add("values", values);
return data;
}
}
/**
* Represents a custom advanced bar chart.
*/
public static class AdvancedBarChart extends CustomChart {
private final Callable<Map<String, int[]>> callable;
/**
* Class constructor.
*
* @param chartId The id of the chart.
* @param callable The callable which is used to request the chart data.
*/
public AdvancedBarChart(String chartId, Callable<Map<String, int[]>> callable) {
super(chartId);
this.callable = callable;
}
@Override
protected JsonObject getChartData() throws Exception {
JsonObject values = new JsonObject();
Map<String, int[]> map = callable.call();
if (map == null || map.isEmpty()) {
// Null = skip the chart
return null;
}
boolean allSkipped = true;
for (Map.Entry<String, int[]> entry : map.entrySet()) {
if (entry.getValue().length == 0) {
continue; // Skip this invalid
}
allSkipped = false;
JsonArray categoryValues = new JsonArray();
for (int categoryValue : entry.getValue()) {
categoryValues.add(categoryValue);
}
values.add(entry.getKey(), categoryValues);
}
if (allSkipped) {
// Null = skip the chart
return null;
}
JsonObject data = new JsonObject();
data.add("values", values);
return data;
} }
private void appendPlatformData(JsonObjectBuilder builder) {
builder.appendField("osName", System.getProperty("os.name"));
builder.appendField("osArch", System.getProperty("os.arch"));
builder.appendField("osVersion", System.getProperty("os.version"));
builder.appendField("coreCount", Runtime.getRuntime().availableProcessors());
} }
static class VelocityMetrics { static class VelocityMetrics {
private static final Logger logger = LogManager.getLogger(Metrics.class);
static void startMetrics(VelocityServer server, VelocityConfiguration.Metrics metricsConfig) { static void startMetrics(VelocityServer server, VelocityConfiguration.Metrics metricsConfig) {
if (!metricsConfig.isFromConfig()) { Metrics metrics = new Metrics(logger, 4752, metricsConfig.isEnabled());
// Log an informational message.
logger.info("Velocity collects metrics and sends them to bStats (https://bstats.org).");
logger.info("bStats collects some basic information like how many people use Velocity and");
logger.info("their player count. This has no impact on performance and this data does not");
logger.info("identify your server in any way. However, you may opt-out by editing your");
logger.info("velocity.toml and setting enabled = false in the [metrics] section.");
}
// Load the data
String serverUuid = metricsConfig.getId();
boolean logFailedRequests = metricsConfig.isLogFailure();
// Only start Metrics, if it's enabled in the config
if (metricsConfig.isEnabled()) {
Metrics metrics = new Metrics("Velocity", 4752, serverUuid, logFailedRequests, server);
metrics.addCustomChart( metrics.addCustomChart(
new Metrics.SingleLineChart("players", server::getPlayerCount) new SingleLineChart("players", server::getPlayerCount)
); );
metrics.addCustomChart( metrics.addCustomChart(
new Metrics.SingleLineChart("managed_servers", () -> server.getAllServers().size()) new SingleLineChart("managed_servers", () -> server.getAllServers().size())
); );
metrics.addCustomChart( metrics.addCustomChart(
new Metrics.SimplePie("online_mode", new SimplePie("online_mode",
() -> server.getConfiguration().isOnlineMode() ? "online" : "offline") () -> server.getConfiguration().isOnlineMode() ? "online" : "offline")
); );
metrics.addCustomChart(new Metrics.SimplePie("velocity_version", metrics.addCustomChart(new SimplePie("velocity_version",
() -> server.getVersion().getVersion())); () -> server.getVersion().getVersion()));
metrics.addCustomChart(new Metrics.DrilldownPie("java_version", () -> { metrics.addCustomChart(new DrilldownPie("java_version", () -> {
Map<String, Map<String, Integer>> map = new HashMap<>(); Map<String, Map<String, Integer>> map = new HashMap<>();
String javaVersion = System.getProperty("java.version"); String javaVersion = System.getProperty("java.version");
Map<String, Integer> entry = new HashMap<>(); Map<String, Integer> entry = new HashMap<>();
@ -622,7 +133,6 @@ public class Metrics {
return map; return map;
})); }));
} }
}
} }
}
}

Datei anzeigen

@ -8,9 +8,12 @@ import org.apache.logging.log4j.Logger;
public class Velocity { public class Velocity {
private static final Logger logger = LogManager.getLogger(Velocity.class); private static final Logger logger;
static { static {
System.setProperty("java.util.logging.manager", "org.apache.logging.log4j.jul.LogManager");
logger = LogManager.getLogger(Velocity.class);
// By default, Netty allocates 16MiB arenas for the PooledByteBufAllocator. This is too much // By default, Netty allocates 16MiB arenas for the PooledByteBufAllocator. This is too much
// memory for Minecraft, which imposes a maximum packet size of 2MiB! We'll use 4MiB as a more // memory for Minecraft, which imposes a maximum packet size of 2MiB! We'll use 4MiB as a more
// sane default. // sane default.

Datei anzeigen

@ -59,6 +59,9 @@ public interface CommandNodeFactory<T extends Command> {
(context, builder) -> { (context, builder) -> {
I invocation = createInvocation(context); I invocation = createInvocation(context);
if (!command.hasPermission(invocation)) {
return builder.buildFuture();
}
return command.suggestAsync(invocation).thenApply(values -> { return command.suggestAsync(invocation).thenApply(values -> {
for (String value : values) { for (String value : values) {
builder.suggest(value); builder.suggest(value);

Datei anzeigen

@ -38,6 +38,7 @@ import net.kyori.adventure.text.TextComponent;
import net.kyori.adventure.text.event.ClickEvent; import net.kyori.adventure.text.event.ClickEvent;
import net.kyori.adventure.text.event.HoverEvent; import net.kyori.adventure.text.event.HoverEvent;
import net.kyori.adventure.text.format.NamedTextColor; import net.kyori.adventure.text.format.NamedTextColor;
import net.kyori.adventure.text.format.TextColor;
import net.kyori.adventure.text.format.TextDecoration; import net.kyori.adventure.text.format.TextDecoration;
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
@ -188,6 +189,7 @@ public class VelocityCommand implements SimpleCommand {
private static class Info implements SubCommand { private static class Info implements SubCommand {
private static final TextColor VELOCITY_COLOR = TextColor.fromHexString("#09add3");
private final ProxyServer server; private final ProxyServer server;
private Info(ProxyServer server) { private Info(ProxyServer server) {
@ -205,11 +207,11 @@ public class VelocityCommand implements SimpleCommand {
TextComponent velocity = Component.text().content(version.getName() + " ") TextComponent velocity = Component.text().content(version.getName() + " ")
.decoration(TextDecoration.BOLD, true) .decoration(TextDecoration.BOLD, true)
.color(NamedTextColor.DARK_AQUA) .color(VELOCITY_COLOR)
.append(Component.text(version.getVersion()).decoration(TextDecoration.BOLD, false)) .append(Component.text(version.getVersion()).decoration(TextDecoration.BOLD, false))
.build(); .build();
TextComponent copyright = Component TextComponent copyright = Component
.text("Copyright 2018-2020 " + version.getVendor() + ". " + version.getName() .text("Copyright 2018-2021 " + version.getVendor() + ". " + version.getName()
+ " is freely licensed under the terms of the MIT License."); + " is freely licensed under the terms of the MIT License.");
source.sendMessage(Identity.nil(), velocity); source.sendMessage(Identity.nil(), velocity);
source.sendMessage(Identity.nil(), copyright); source.sendMessage(Identity.nil(), copyright);

Datei anzeigen

@ -30,7 +30,6 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Optional; import java.util.Optional;
import java.util.Random; import java.util.Random;
import java.util.UUID;
import net.kyori.adventure.text.Component; import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer; import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer;
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
@ -426,11 +425,6 @@ public class VelocityConfiguration implements ProxyConfig {
} }
forwardingSecret = forwardingSecretString.getBytes(StandardCharsets.UTF_8); forwardingSecret = forwardingSecretString.getBytes(StandardCharsets.UTF_8);
if (!config.contains("metrics.id") || config.<String>get("metrics.id").isEmpty()) {
config.set("metrics.id", UUID.randomUUID().toString());
mustResave = true;
}
if (mustResave) { if (mustResave) {
config.save(); config.save();
} }
@ -448,7 +442,7 @@ public class VelocityConfiguration implements ProxyConfig {
PingPassthroughMode.DISABLED); PingPassthroughMode.DISABLED);
String bind = config.getOrElse("bind", "0.0.0.0:25577"); String bind = config.getOrElse("bind", "0.0.0.0:25577");
String motd = config.getOrElse("motd", "&3A Velocity Server"); String motd = config.getOrElse("motd", "&#09add3A Velocity Server");
int maxPlayers = config.getIntOrElse("show-max-players", 500); int maxPlayers = config.getIntOrElse("show-max-players", 500);
Boolean onlineMode = config.getOrElse("online-mode", true); Boolean onlineMode = config.getOrElse("online-mode", true);
Boolean announceForge = config.getOrElse("announce-forge", true); Boolean announceForge = config.getOrElse("announce-forge", true);
@ -636,7 +630,11 @@ public class VelocityConfiguration implements ProxyConfig {
this.loginRatelimit = config.getIntOrElse("login-ratelimit", 3000); this.loginRatelimit = config.getIntOrElse("login-ratelimit", 3000);
this.connectionTimeout = config.getIntOrElse("connection-timeout", 5000); this.connectionTimeout = config.getIntOrElse("connection-timeout", 5000);
this.readTimeout = config.getIntOrElse("read-timeout", 30000); this.readTimeout = config.getIntOrElse("read-timeout", 30000);
if (config.contains("haproxy-protocol")) {
this.proxyProtocol = config.getOrElse("haproxy-protocol", false);
} else {
this.proxyProtocol = config.getOrElse("proxy-protocol", false); this.proxyProtocol = config.getOrElse("proxy-protocol", false);
}
this.tcpFastOpen = config.getOrElse("tcp-fast-open", false); this.tcpFastOpen = config.getOrElse("tcp-fast-open", false);
this.bungeePluginMessageChannel = config.getOrElse("bungee-plugin-message-channel", true); this.bungeePluginMessageChannel = config.getOrElse("bungee-plugin-message-channel", true);
this.showPingRequests = config.getOrElse("show-ping-requests", false); this.showPingRequests = config.getOrElse("show-ping-requests", false);
@ -769,39 +767,16 @@ public class VelocityConfiguration implements ProxyConfig {
public static class Metrics { public static class Metrics {
private boolean enabled = true; private boolean enabled = true;
private String id = UUID.randomUUID().toString();
private boolean logFailure = false;
private boolean fromConfig;
private Metrics() {
this.fromConfig = false;
}
private Metrics(CommentedConfig toml) { private Metrics(CommentedConfig toml) {
if (toml != null) { if (toml != null) {
this.enabled = toml.getOrElse("enabled", false); this.enabled = toml.getOrElse("enabled", true);
this.id = toml.getOrElse("id", UUID.randomUUID().toString());
this.logFailure = toml.getOrElse("log-failure", false);
this.fromConfig = true;
} }
} }
public boolean isEnabled() { public boolean isEnabled() {
return enabled; return enabled;
} }
public String getId() {
return id;
}
public boolean isLogFailure() {
return logFailure;
}
public boolean isFromConfig() {
return fromConfig;
}
} }
public static class Messages { public static class Messages {

Datei anzeigen

@ -196,6 +196,8 @@ public class MinecraftConnection extends ChannelInboundHandlerAdapter {
public void write(Object msg) { public void write(Object msg) {
if (channel.isActive()) { if (channel.isActive()) {
channel.writeAndFlush(msg, channel.voidPromise()); channel.writeAndFlush(msg, channel.voidPromise());
} else {
ReferenceCountUtil.release(msg);
} }
} }
@ -206,6 +208,8 @@ public class MinecraftConnection extends ChannelInboundHandlerAdapter {
public void delayedWrite(Object msg) { public void delayedWrite(Object msg) {
if (channel.isActive()) { if (channel.isActive()) {
channel.write(msg, channel.voidPromise()); channel.write(msg, channel.voidPromise());
} else {
ReferenceCountUtil.release(msg);
} }
} }
@ -379,17 +383,26 @@ public class MinecraftConnection extends ChannelInboundHandlerAdapter {
if (threshold == -1) { if (threshold == -1) {
channel.pipeline().remove(COMPRESSION_DECODER); channel.pipeline().remove(COMPRESSION_DECODER);
channel.pipeline().remove(COMPRESSION_ENCODER); channel.pipeline().remove(COMPRESSION_ENCODER);
return; } else {
} MinecraftCompressDecoder decoder = (MinecraftCompressDecoder) channel.pipeline()
.get(COMPRESSION_DECODER);
MinecraftCompressEncoder encoder = (MinecraftCompressEncoder) channel.pipeline()
.get(COMPRESSION_ENCODER);
if (decoder != null && encoder != null) {
decoder.setThreshold(threshold);
encoder.setThreshold(threshold);
} else {
int level = server.getConfiguration().getCompressionLevel(); int level = server.getConfiguration().getCompressionLevel();
VelocityCompressor compressor = Natives.compress.get().create(level); VelocityCompressor compressor = Natives.compress.get().create(level);
MinecraftCompressEncoder encoder = new MinecraftCompressEncoder(threshold, compressor);
MinecraftCompressDecoder decoder = new MinecraftCompressDecoder(threshold, compressor); encoder = new MinecraftCompressEncoder(threshold, compressor);
decoder = new MinecraftCompressDecoder(threshold, compressor);
channel.pipeline().addBefore(MINECRAFT_DECODER, COMPRESSION_DECODER, decoder); channel.pipeline().addBefore(MINECRAFT_DECODER, COMPRESSION_DECODER, decoder);
channel.pipeline().addBefore(MINECRAFT_ENCODER, COMPRESSION_ENCODER, encoder); channel.pipeline().addBefore(MINECRAFT_ENCODER, COMPRESSION_ENCODER, encoder);
} }
}
}
/** /**
* Enables encryption on the connection. * Enables encryption on the connection.

Datei anzeigen

@ -30,6 +30,7 @@ import com.velocitypowered.proxy.network.packet.serverbound.ServerboundPluginMes
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufUtil; import io.netty.buffer.ByteBufUtil;
import io.netty.buffer.Unpooled; import io.netty.buffer.Unpooled;
import io.netty.channel.Channel;
import io.netty.handler.timeout.ReadTimeoutException; import io.netty.handler.timeout.ReadTimeoutException;
import java.util.Collection; import java.util.Collection;
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
@ -38,6 +39,8 @@ import org.apache.logging.log4j.Logger;
public class BackendPlaySessionHandler implements MinecraftSessionHandler { public class BackendPlaySessionHandler implements MinecraftSessionHandler {
private static final Logger logger = LogManager.getLogger(BackendPlaySessionHandler.class); private static final Logger logger = LogManager.getLogger(BackendPlaySessionHandler.class);
private static final boolean BACKPRESSURE_LOG = Boolean
.getBoolean("velocity.log-server-backpressure");
private final VelocityServer server; private final VelocityServer server;
private final VelocityServerConnection serverConn; private final VelocityServerConnection serverConn;
private final ClientPlaySessionHandler playerSessionHandler; private final ClientPlaySessionHandler playerSessionHandler;
@ -64,11 +67,14 @@ public class BackendPlaySessionHandler implements MinecraftSessionHandler {
@Override @Override
public void activated() { public void activated() {
serverConn.getServer().addPlayer(serverConn.getPlayer()); serverConn.getServer().addPlayer(serverConn.getPlayer());
if (server.getConfiguration().isBungeePluginChannelEnabled()) {
MinecraftConnection serverMc = serverConn.ensureConnected(); MinecraftConnection serverMc = serverConn.ensureConnected();
serverMc.write(PluginMessageUtil.constructChannelsPacket(serverMc.getProtocolVersion(), serverMc.write(PluginMessageUtil.constructChannelsPacket(serverMc.getProtocolVersion(),
ImmutableList.of(getBungeeCordChannel(serverMc.getProtocolVersion())), ServerboundPluginMessagePacket.FACTORY ImmutableList.of(getBungeeCordChannel(serverMc.getProtocolVersion()))
)); ));
} }
}
@Override @Override
public boolean beforeHandle() { public boolean beforeHandle() {
@ -286,4 +292,20 @@ public class BackendPlaySessionHandler implements MinecraftSessionHandler {
} }
} }
} }
@Override
public void writabilityChanged() {
Channel serverChan = serverConn.ensureConnected().getChannel();
boolean writable = serverChan.isWritable();
if (BACKPRESSURE_LOG) {
if (writable) {
logger.info("{} is not writable, not auto-reading player connection data", this.serverConn);
} else {
logger.info("{} is writable, will auto-read player connection data", this.serverConn);
}
}
playerConnection.setAutoReading(writable);
}
} }

Datei anzeigen

@ -262,45 +262,34 @@ public class BungeeCordMessageResponder {
}); });
} }
private ByteBuf prepareForwardMessage(ByteBufDataInput in) {
String channel = in.readUTF();
short messageLength = in.readShort();
ByteBuf buf = Unpooled.buffer();
ByteBufDataOutput forwarded = new ByteBufDataOutput(buf);
forwarded.writeUTF(channel);
forwarded.writeShort(messageLength);
buf.writeBytes(in.unwrap().readSlice(messageLength));
return buf;
}
private void processForwardToPlayer(ByteBufDataInput in) { private void processForwardToPlayer(ByteBufDataInput in) {
proxy.getPlayer(in.readUTF()) Optional<Player> player = proxy.getPlayer(in.readUTF());
.flatMap(Player::getCurrentServer) if (player.isPresent()) {
.ifPresent(server -> sendServerResponse(player, prepareForwardMessage(in))); ByteBuf toForward = in.unwrap().copy();
sendServerResponse((ConnectedPlayer) player.get(), toForward);
}
} }
private void processForwardToServer(ByteBufDataInput in) { private void processForwardToServer(ByteBufDataInput in) {
String target = in.readUTF(); String target = in.readUTF();
ByteBuf toForward = prepareForwardMessage(in); ByteBuf toForward = in.unwrap().copy();
if (target.equals("ALL")) { if (target.equals("ALL")) {
ByteBuf unreleasableForward = Unpooled.unreleasableBuffer(toForward);
try { try {
for (RegisteredServer rs : proxy.getAllServers()) { for (RegisteredServer rs : proxy.getAllServers()) {
((VelocityRegisteredServer) rs).sendPluginMessage(LEGACY_CHANNEL, unreleasableForward); ((VelocityRegisteredServer) rs).sendPluginMessage(LEGACY_CHANNEL,
toForward.retainedSlice());
} }
} finally { } finally {
toForward.release(); toForward.release();
} }
} else { } else {
proxy.getServer(target).ifPresent(rs -> ((VelocityRegisteredServer) rs) Optional<RegisteredServer> server = proxy.getServer(target);
.sendPluginMessage(LEGACY_CHANNEL, toForward)); if (server.isPresent()) {
((VelocityRegisteredServer) server.get()).sendPluginMessage(LEGACY_CHANNEL, toForward);
} else {
toForward.release();
} }
} }
// Note: this method will always release the buffer!
private void sendResponseOnConnection(ByteBuf buf) {
sendServerResponse(this.player, buf);
} }
static String getBungeeCordChannel(ProtocolVersion version) { static String getBungeeCordChannel(ProtocolVersion version) {
@ -308,29 +297,17 @@ public class BungeeCordMessageResponder {
: LEGACY_CHANNEL.getId(); : LEGACY_CHANNEL.getId();
} }
// Note: this method will always release the buffer!
private void sendResponseOnConnection(ByteBuf buf) {
sendServerResponse(this.player, buf);
}
// Note: this method will always release the buffer! // Note: this method will always release the buffer!
private static void sendServerResponse(ConnectedPlayer player, ByteBuf buf) { private static void sendServerResponse(ConnectedPlayer player, ByteBuf buf) {
MinecraftConnection serverConnection = player.ensureAndGetCurrentServer().ensureConnected(); MinecraftConnection serverConnection = player.ensureAndGetCurrentServer().ensureConnected();
String chan = getBungeeCordChannel(serverConnection.getProtocolVersion()); String chan = getBungeeCordChannel(serverConnection.getProtocolVersion());
ServerboundPluginMessagePacket msg = new ServerboundPluginMessagePacket(chan, buf);
ServerboundPluginMessagePacket msg = null; serverConnection.write(msg);
boolean released = false;
try {
VelocityServerConnection vsc = player.getConnectedServer();
if (vsc == null) {
return;
}
MinecraftConnection serverConn = vsc.ensureConnected();
msg = new ServerboundPluginMessagePacket(chan, buf);
serverConn.write(msg);
released = true;
} finally {
if (!released && msg != null) {
msg.release();
}
}
} }
boolean process(AbstractPluginMessagePacket<?> message) { boolean process(AbstractPluginMessagePacket<?> message) {

Datei anzeigen

@ -32,7 +32,9 @@ import io.netty.channel.ChannelFutureListener;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.net.SocketAddress; import java.net.SocketAddress;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import java.util.function.UnaryOperator; import java.util.function.UnaryOperator;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull; import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
@ -47,8 +49,7 @@ public class VelocityServerConnection implements MinecraftConnectionAssociation,
private boolean hasCompletedJoin = false; private boolean hasCompletedJoin = false;
private boolean gracefulDisconnect = false; private boolean gracefulDisconnect = false;
private BackendConnectionPhase connectionPhase = BackendConnectionPhases.UNKNOWN; private BackendConnectionPhase connectionPhase = BackendConnectionPhases.UNKNOWN;
private long lastPingId; private final Map<Long, Long> pendingPings = new HashMap<>();
private long lastPingSent;
private @MonotonicNonNull DimensionRegistry activeDimensionRegistry; private @MonotonicNonNull DimensionRegistry activeDimensionRegistry;
/** /**
@ -112,7 +113,7 @@ public class VelocityServerConnection implements MinecraftConnectionAssociation,
return getHandshakeRemoteAddress(); return getHandshakeRemoteAddress();
} }
StringBuilder data = new StringBuilder() StringBuilder data = new StringBuilder()
.append(getHandshakeRemoteAddress()) .append(registeredServer.getServerInfo().getAddress().getHostString())
.append('\0') .append('\0')
.append(((InetSocketAddress) proxyPlayer.getRemoteAddress()).getHostString()) .append(((InetSocketAddress) proxyPlayer.getRemoteAddress()).getHostString())
.append('\0') .append('\0')
@ -259,21 +260,8 @@ public class VelocityServerConnection implements MinecraftConnectionAssociation,
return gracefulDisconnect; return gracefulDisconnect;
} }
public long getLastPingId() { public Map<Long, Long> getPendingPings() {
return lastPingId; return pendingPings;
}
public long getLastPingSent() {
return lastPingSent;
}
void setLastPingId(long lastPingId) {
this.lastPingId = lastPingId;
this.lastPingSent = System.currentTimeMillis();
}
public void resetLastPingId() {
this.lastPingId = -1;
} }
/** /**

Datei anzeigen

@ -5,13 +5,17 @@ import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_16;
import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_8; import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_8;
import static com.velocitypowered.proxy.network.PluginMessageUtil.constructChannelsPacket; import static com.velocitypowered.proxy.network.PluginMessageUtil.constructChannelsPacket;
import com.google.common.collect.ImmutableList;
import com.velocitypowered.api.event.command.CommandExecuteEvent.CommandResult; import com.velocitypowered.api.event.command.CommandExecuteEvent.CommandResult;
import com.velocitypowered.api.event.connection.PluginMessageEvent; import com.velocitypowered.api.event.connection.PluginMessageEvent;
import com.velocitypowered.api.event.player.PlayerChannelRegisterEvent;
import com.velocitypowered.api.event.player.PlayerChatEvent; import com.velocitypowered.api.event.player.PlayerChatEvent;
import com.velocitypowered.api.event.player.PlayerResourcePackStatusEvent; import com.velocitypowered.api.event.player.PlayerResourcePackStatusEvent;
import com.velocitypowered.api.event.player.TabCompleteEvent; import com.velocitypowered.api.event.player.TabCompleteEvent;
import com.velocitypowered.api.network.ProtocolVersion; import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.api.proxy.messages.ChannelIdentifier; import com.velocitypowered.api.proxy.messages.ChannelIdentifier;
import com.velocitypowered.api.proxy.messages.LegacyChannelIdentifier;
import com.velocitypowered.api.proxy.messages.MinecraftChannelIdentifier;
import com.velocitypowered.proxy.VelocityServer; import com.velocitypowered.proxy.VelocityServer;
import com.velocitypowered.proxy.connection.ConnectionTypes; import com.velocitypowered.proxy.connection.ConnectionTypes;
import com.velocitypowered.proxy.connection.MinecraftConnection; import com.velocitypowered.proxy.connection.MinecraftConnection;
@ -102,12 +106,14 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
@Override @Override
public boolean handle(ServerboundKeepAlivePacket packet) { public boolean handle(ServerboundKeepAlivePacket packet) {
VelocityServerConnection serverConnection = player.getConnectedServer(); VelocityServerConnection serverConnection = player.getConnectedServer();
if (serverConnection != null && packet.getRandomId() == serverConnection.getLastPingId()) { if (serverConnection != null) {
Long sentTime = serverConnection.getPendingPings().remove(packet.getRandomId());
if (sentTime != null) {
MinecraftConnection smc = serverConnection.getConnection(); MinecraftConnection smc = serverConnection.getConnection();
if (smc != null) { if (smc != null) {
player.setPing(System.currentTimeMillis() - serverConnection.getLastPingSent()); player.setPing(System.currentTimeMillis() - sentTime);
smc.write(packet); smc.write(packet);
serverConnection.resetLastPingId(); }
} }
} }
return true; return true;
@ -191,7 +197,18 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
logger.warn("A plugin message was received while the backend server was not " logger.warn("A plugin message was received while the backend server was not "
+ "ready. Channel: {}. Packet discarded.", packet.getChannel()); + "ready. Channel: {}. Packet discarded.", packet.getChannel());
} else if (PluginMessageUtil.isRegister(packet)) { } else if (PluginMessageUtil.isRegister(packet)) {
player.getKnownChannels().addAll(PluginMessageUtil.getChannels(packet)); List<String> channels = PluginMessageUtil.getChannels(packet);
player.getKnownChannels().addAll(channels);
List<ChannelIdentifier> channelIdentifiers = new ArrayList<>();
for (String channel : channels) {
try {
channelIdentifiers.add(MinecraftChannelIdentifier.from(channel));
} catch (IllegalArgumentException e) {
channelIdentifiers.add(new LegacyChannelIdentifier(channel));
}
}
server.getEventManager().fireAndForget(new PlayerChannelRegisterEvent(player,
ImmutableList.copyOf(channelIdentifiers)));
backendConn.write(packet.retain()); backendConn.write(packet.retain());
} else if (PluginMessageUtil.isUnregister(packet)) { } else if (PluginMessageUtil.isUnregister(packet)) {
player.getKnownChannels().removeAll(PluginMessageUtil.getChannels(packet)); player.getKnownChannels().removeAll(PluginMessageUtil.getChannels(packet));
@ -303,7 +320,7 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
boolean writable = player.getConnection().getChannel().isWritable(); boolean writable = player.getConnection().getChannel().isWritable();
if (!writable) { if (!writable) {
// We might have packets queued for the server, so flush them now to free up memory. // We might have packets queued from the server, so flush them now to free up memory.
player.getConnection().flush(); player.getConnection().flush();
} }

Datei anzeigen

@ -59,6 +59,7 @@ import java.net.SocketAddress;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Objects;
import java.util.Optional; import java.util.Optional;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
@ -103,6 +104,8 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player {
private @Nullable VelocityServerConnection connectionInFlight; private @Nullable VelocityServerConnection connectionInFlight;
private @Nullable PlayerSettings settings; private @Nullable PlayerSettings settings;
private @Nullable ModInfo modInfo; private @Nullable ModInfo modInfo;
private Component playerListHeader = Component.empty();
private Component playerListFooter = Component.empty();
private final VelocityTabList tabList; private final VelocityTabList tabList;
private final VelocityServer server; private final VelocityServer server;
private ClientConnectionPhase connectionPhase; private ClientConnectionPhase connectionPhase;
@ -113,11 +116,6 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player {
ConnectedPlayer(VelocityServer server, GameProfile profile, MinecraftConnection connection, ConnectedPlayer(VelocityServer server, GameProfile profile, MinecraftConnection connection,
@Nullable InetSocketAddress virtualHost, boolean onlineMode) { @Nullable InetSocketAddress virtualHost, boolean onlineMode) {
this.server = server; this.server = server;
if (connection.getProtocolVersion().gte(ProtocolVersion.MINECRAFT_1_8)) {
this.tabList = new VelocityTabList(connection);
} else {
this.tabList = new VelocityTabListLegacy(connection);
}
this.profile = profile; this.profile = profile;
this.connection = connection; this.connection = connection;
this.virtualHost = virtualHost; this.virtualHost = virtualHost;
@ -125,6 +123,12 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player {
this.connectionPhase = connection.getType().getInitialClientPhase(); this.connectionPhase = connection.getType().getInitialClientPhase();
this.knownChannels = CappedSet.create(MAX_PLUGIN_CHANNELS); this.knownChannels = CappedSet.create(MAX_PLUGIN_CHANNELS);
this.onlineMode = onlineMode; this.onlineMode = onlineMode;
if (connection.getProtocolVersion().compareTo(ProtocolVersion.MINECRAFT_1_8) >= 0) {
this.tabList = new VelocityTabList(this);
} else {
this.tabList = new VelocityTabListLegacy(this);
}
} }
@Override @Override
@ -264,8 +268,38 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player {
} }
} }
@Override
public Component getPlayerListHeader() {
return this.playerListHeader;
}
@Override
public Component getPlayerListFooter() {
return this.playerListFooter;
}
@Override
public void sendPlayerListHeader(@NonNull final Component header) {
this.sendPlayerListHeaderAndFooter(header, this.playerListFooter);
}
@Override
public void sendPlayerListFooter(@NonNull final Component footer) {
this.sendPlayerListHeaderAndFooter(this.playerListHeader, footer);
}
@Override
public void sendPlayerListHeaderAndFooter(final Component header, final Component footer) {
this.playerListHeader = Objects.requireNonNull(header, "header");
this.playerListFooter = Objects.requireNonNull(footer, "footer");
if (this.getProtocolVersion().compareTo(ProtocolVersion.MINECRAFT_1_8) >= 0) {
this.connection.write(HeaderAndFooter.create(header, footer, this.getProtocolVersion()));
}
}
@Override @Override
public void showTitle(net.kyori.adventure.title.@NonNull Title title) { public void showTitle(net.kyori.adventure.title.@NonNull Title title) {
if (this.getProtocolVersion().compareTo(ProtocolVersion.MINECRAFT_1_8) >= 0) {
GsonComponentSerializer serializer = ProtocolUtils.getJsonChatSerializer(this GsonComponentSerializer serializer = ProtocolUtils.getJsonChatSerializer(this
.getProtocolVersion()); .getProtocolVersion());
@ -286,26 +320,35 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player {
connection.flush(); connection.flush();
} }
}
@Override @Override
public void clearTitle() { public void clearTitle() {
if (this.getProtocolVersion().compareTo(ProtocolVersion.MINECRAFT_1_8) >= 0) {
connection.write(ClientboundTitlePacket.hide(this.getProtocolVersion())); connection.write(ClientboundTitlePacket.hide(this.getProtocolVersion()));
} }
}
@Override @Override
public void resetTitle() { public void resetTitle() {
if (this.getProtocolVersion().compareTo(ProtocolVersion.MINECRAFT_1_8) >= 0) {
connection.write(ClientboundTitlePacket.reset(this.getProtocolVersion())); connection.write(ClientboundTitlePacket.reset(this.getProtocolVersion()));
} }
}
@Override @Override
public void hideBossBar(@NonNull BossBar bar) { public void hideBossBar(@NonNull BossBar bar) {
if (this.getProtocolVersion().compareTo(ProtocolVersion.MINECRAFT_1_9) >= 0) {
this.server.getBossBarManager().removeBossBar(this, bar); this.server.getBossBarManager().removeBossBar(this, bar);
} }
}
@Override @Override
public void showBossBar(@NonNull BossBar bar) { public void showBossBar(@NonNull BossBar bar) {
if (this.getProtocolVersion().compareTo(ProtocolVersion.MINECRAFT_1_9) >= 0) {
this.server.getBossBarManager().addBossBar(this, bar); this.server.getBossBarManager().addBossBar(this, bar);
} }
}
@Override @Override
public ConnectionRequestBuilder createConnectionRequest(RegisteredServer server) { public ConnectionRequestBuilder createConnectionRequest(RegisteredServer server) {
@ -317,6 +360,11 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player {
this.profile = profile.withProperties(properties); this.profile = profile.withProperties(properties);
} }
@Override
public void clearHeaderAndFooter() {
tabList.clearHeaderAndFooter();
}
@Override @Override
public VelocityTabList getTabList() { public VelocityTabList getTabList() {
return tabList; return tabList;
@ -412,7 +460,8 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player {
logger.error("{}: kicked from server {}: {}", this, server.getServerInfo().getName(), logger.error("{}: kicked from server {}: {}", this, server.getServerInfo().getName(),
plainTextReason); plainTextReason);
handleConnectionException(server, disconnectReason, Component.text() handleConnectionException(server, disconnectReason, Component.text()
.append(messages.getKickPrefix(server.getServerInfo().getName())) .append(messages.getKickPrefix(server.getServerInfo().getName())
.colorIfAbsent(NamedTextColor.RED))
.color(NamedTextColor.RED) .color(NamedTextColor.RED)
.append(disconnectReason) .append(disconnectReason)
.build(), safe); .build(), safe);
@ -420,8 +469,8 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player {
logger.error("{}: disconnected while connecting to {}: {}", this, logger.error("{}: disconnected while connecting to {}: {}", this,
server.getServerInfo().getName(), plainTextReason); server.getServerInfo().getName(), plainTextReason);
handleConnectionException(server, disconnectReason, Component.text() handleConnectionException(server, disconnectReason, Component.text()
.append(messages.getDisconnectPrefix(server.getServerInfo().getName())) .append(messages.getDisconnectPrefix(server.getServerInfo().getName())
.color(NamedTextColor.RED) .colorIfAbsent(NamedTextColor.RED))
.append(disconnectReason) .append(disconnectReason)
.build(), safe); .build(), safe);
} }
@ -678,8 +727,10 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player {
public void sendResourcePack(String url) { public void sendResourcePack(String url) {
Preconditions.checkNotNull(url, "url"); Preconditions.checkNotNull(url, "url");
if (this.getProtocolVersion().compareTo(ProtocolVersion.MINECRAFT_1_8) >= 0) {
connection.write(new ClientboundResourcePackRequestPacket(url, "")); connection.write(new ClientboundResourcePackRequestPacket(url, ""));
} }
}
@Override @Override
public void sendResourcePack(String url, byte[] hash) { public void sendResourcePack(String url, byte[] hash) {
@ -687,8 +738,10 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player {
Preconditions.checkNotNull(hash, "hash"); Preconditions.checkNotNull(hash, "hash");
Preconditions.checkArgument(hash.length == 20, "Hash length is not 20"); Preconditions.checkArgument(hash.length == 20, "Hash length is not 20");
if (this.getProtocolVersion().compareTo(ProtocolVersion.MINECRAFT_1_8) >= 0) {
connection.write(new ClientboundResourcePackRequestPacket(url, ByteBufUtil.hexDump(hash))); connection.write(new ClientboundResourcePackRequestPacket(url, ByteBufUtil.hexDump(hash)));
} }
}
/** /**
* Sends a {@link ClientboundKeepAlivePacket} packet to the player with a random ID. * Sends a {@link ClientboundKeepAlivePacket} packet to the player with a random ID.

Datei anzeigen

@ -36,6 +36,7 @@ import io.netty.buffer.ByteBuf;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.security.GeneralSecurityException; import java.security.GeneralSecurityException;
import java.security.KeyPair; import java.security.KeyPair;
import java.security.MessageDigest;
import java.util.Arrays; import java.util.Arrays;
import java.util.Optional; import java.util.Optional;
import java.util.UUID; import java.util.UUID;
@ -90,7 +91,7 @@ public class LoginSessionHandler implements MinecraftSessionHandler {
try { try {
KeyPair serverKeyPair = server.getServerKeyPair(); KeyPair serverKeyPair = server.getServerKeyPair();
byte[] decryptedVerifyToken = decryptRsa(serverKeyPair, packet.getVerifyToken()); byte[] decryptedVerifyToken = decryptRsa(serverKeyPair, packet.getVerifyToken());
if (!Arrays.equals(verify, decryptedVerifyToken)) { if (!MessageDigest.isEqual(verify, decryptedVerifyToken)) {
throw new IllegalStateException("Unable to successfully decrypt the verification token."); throw new IllegalStateException("Unable to successfully decrypt the verification token.");
} }

Datei anzeigen

@ -16,6 +16,8 @@ import io.netty.handler.codec.CorruptedFrameException;
import io.netty.handler.codec.DecoderException; import io.netty.handler.codec.DecoderException;
import io.netty.handler.codec.EncoderException; import io.netty.handler.codec.EncoderException;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException; import java.io.IOException;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.ArrayList; import java.util.ArrayList;
@ -227,11 +229,12 @@ public enum ProtocolUtils {
/** /**
* Reads a {@link net.kyori.adventure.nbt.CompoundBinaryTag} from the {@code buf}. * Reads a {@link net.kyori.adventure.nbt.CompoundBinaryTag} from the {@code buf}.
* @param buf the buffer to read from * @param buf the buffer to read from
* @param reader the NBT reader to use
* @return {@link net.kyori.adventure.nbt.CompoundBinaryTag} the CompoundTag from the buffer * @return {@link net.kyori.adventure.nbt.CompoundBinaryTag} the CompoundTag from the buffer
*/ */
public static CompoundBinaryTag readCompoundTag(ByteBuf buf) { public static CompoundBinaryTag readCompoundTag(ByteBuf buf, BinaryTagIO.Reader reader) {
try { try {
return BinaryTagIO.readDataInput(new ByteBufInputStream(buf)); return reader.read((DataInput) new ByteBufInputStream(buf));
} catch (IOException thrown) { } catch (IOException thrown) {
throw new DecoderException( throw new DecoderException(
"Unable to parse NBT CompoundTag, full error: " + thrown.getMessage()); "Unable to parse NBT CompoundTag, full error: " + thrown.getMessage());
@ -245,7 +248,7 @@ public enum ProtocolUtils {
*/ */
public static void writeCompoundTag(ByteBuf buf, CompoundBinaryTag compoundTag) { public static void writeCompoundTag(ByteBuf buf, CompoundBinaryTag compoundTag) {
try { try {
BinaryTagIO.writeDataOutput(compoundTag, new ByteBufOutputStream(buf)); BinaryTagIO.writer().write(compoundTag, (DataOutput) new ByteBufOutputStream(buf));
} catch (IOException e) { } catch (IOException e) {
throw new EncoderException("Unable to encode NBT CompoundTag"); throw new EncoderException("Unable to encode NBT CompoundTag");
} }

Datei anzeigen

@ -25,6 +25,7 @@ import com.velocitypowered.proxy.network.packet.PacketHandler;
import com.velocitypowered.proxy.network.packet.PacketReader; import com.velocitypowered.proxy.network.packet.PacketReader;
import com.velocitypowered.proxy.network.serialization.brigadier.ArgumentPropertyRegistry; import com.velocitypowered.proxy.network.serialization.brigadier.ArgumentPropertyRegistry;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
import it.unimi.dsi.fastutil.objects.Object2IntLinkedOpenCustomHashMap;
import it.unimi.dsi.fastutil.objects.Object2IntLinkedOpenHashMap; import it.unimi.dsi.fastutil.objects.Object2IntLinkedOpenHashMap;
import it.unimi.dsi.fastutil.objects.Object2IntMap; import it.unimi.dsi.fastutil.objects.Object2IntMap;
import java.util.ArrayDeque; import java.util.ArrayDeque;
@ -99,7 +100,8 @@ public class ClientboundAvailableCommandsPacket implements Packet {
public void encode(ByteBuf buf, PacketDirection direction, ProtocolVersion protocolVersion) { public void encode(ByteBuf buf, PacketDirection direction, ProtocolVersion protocolVersion) {
// Assign all the children an index. // Assign all the children an index.
Deque<CommandNode<CommandSource>> childrenQueue = new ArrayDeque<>(ImmutableList.of(rootNode)); Deque<CommandNode<CommandSource>> childrenQueue = new ArrayDeque<>(ImmutableList.of(rootNode));
Object2IntMap<CommandNode<CommandSource>> idMappings = new Object2IntLinkedOpenHashMap<>(); Object2IntMap<CommandNode<CommandSource>> idMappings = new Object2IntLinkedOpenCustomHashMap<>(
IdentityHashStrategy.instance());
while (!childrenQueue.isEmpty()) { while (!childrenQueue.isEmpty()) {
CommandNode<CommandSource> child = childrenQueue.poll(); CommandNode<CommandSource> child = childrenQueue.poll();
if (!idMappings.containsKey(child)) { if (!idMappings.containsKey(child)) {
@ -207,6 +209,7 @@ public class ClientboundAvailableCommandsPacket implements Packet {
private final int redirectTo; private final int redirectTo;
private final @Nullable ArgumentBuilder<CommandSource, ?> args; private final @Nullable ArgumentBuilder<CommandSource, ?> args;
private @MonotonicNonNull CommandNode<CommandSource> built; private @MonotonicNonNull CommandNode<CommandSource> built;
private boolean validated;
private WireNode(int idx, byte flags, int[] children, int redirectTo, private WireNode(int idx, byte flags, int[] children, int redirectTo,
@Nullable ArgumentBuilder<CommandSource, ?> args) { @Nullable ArgumentBuilder<CommandSource, ?> args) {
@ -215,18 +218,34 @@ public class ClientboundAvailableCommandsPacket implements Packet {
this.children = children; this.children = children;
this.redirectTo = redirectTo; this.redirectTo = redirectTo;
this.args = args; this.args = args;
this.validated = false;
} }
boolean toNode(WireNode[] wireNodes) { void validate(WireNode[] wireNodes) {
if (this.built == null) {
// Ensure all children exist. Note that we delay checking if the node has been built yet; // Ensure all children exist. Note that we delay checking if the node has been built yet;
// that needs to come after this node is built. // that needs to come after this node is built.
for (int child : children) { for (int child : children) {
if (child >= wireNodes.length) { if (child < 0 || child >= wireNodes.length) {
throw new IllegalStateException("Node points to non-existent index " + redirectTo); throw new IllegalStateException("Node points to non-existent index " + child);
} }
} }
if (redirectTo != -1) {
if (redirectTo < 0 || redirectTo >= wireNodes.length) {
throw new IllegalStateException("Redirect node points to non-existent index "
+ redirectTo);
}
}
this.validated = true;
}
boolean toNode(WireNode[] wireNodes) {
if (!this.validated) {
this.validate(wireNodes);
}
if (this.built == null) {
int type = flags & FLAG_NODE_TYPE; int type = flags & FLAG_NODE_TYPE;
if (type == NODE_TYPE_ROOT) { if (type == NODE_TYPE_ROOT) {
this.built = new RootCommandNode<>(); this.built = new RootCommandNode<>();
@ -237,10 +256,6 @@ public class ClientboundAvailableCommandsPacket implements Packet {
// Add any redirects // Add any redirects
if (redirectTo != -1) { if (redirectTo != -1) {
if (redirectTo >= wireNodes.length) {
throw new IllegalStateException("Node points to non-existent index " + redirectTo);
}
WireNode redirect = wireNodes[redirectTo]; WireNode redirect = wireNodes[redirectTo];
if (redirect.built != null) { if (redirect.built != null) {
args.redirect(redirect.built); args.redirect(redirect.built);

Datei anzeigen

@ -12,6 +12,7 @@ import com.velocitypowered.proxy.network.packet.PacketDirection;
import com.velocitypowered.proxy.network.packet.PacketHandler; import com.velocitypowered.proxy.network.packet.PacketHandler;
import com.velocitypowered.proxy.network.packet.PacketReader; import com.velocitypowered.proxy.network.packet.PacketReader;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
import net.kyori.adventure.nbt.BinaryTagIO;
import net.kyori.adventure.nbt.BinaryTagTypes; import net.kyori.adventure.nbt.BinaryTagTypes;
import net.kyori.adventure.nbt.CompoundBinaryTag; import net.kyori.adventure.nbt.CompoundBinaryTag;
import net.kyori.adventure.nbt.ListBinaryTag; import net.kyori.adventure.nbt.ListBinaryTag;
@ -20,6 +21,7 @@ import org.checkerframework.checker.nullness.qual.Nullable;
public class ClientboundJoinGamePacket implements Packet { public class ClientboundJoinGamePacket implements Packet {
public static final PacketReader<ClientboundJoinGamePacket> DECODER = PacketReader.method(ClientboundJoinGamePacket::new); public static final PacketReader<ClientboundJoinGamePacket> DECODER = PacketReader.method(ClientboundJoinGamePacket::new);
private static final BinaryTagIO.Reader JOINGAME_READER = BinaryTagIO.reader(2 * 1024 * 1024);
private int entityId; private int entityId;
private short gamemode; private short gamemode;
private int dimension; private int dimension;
@ -37,14 +39,136 @@ public class ClientboundJoinGamePacket implements Packet {
private short previousGamemode; // 1.16+ private short previousGamemode; // 1.16+
private CompoundBinaryTag biomeRegistry; // 1.16.2+ private CompoundBinaryTag biomeRegistry; // 1.16.2+
public void withDimension(int dimension) { public int getEntityId() {
this.dimension = dimension; return entityId;
}
public void setEntityId(int entityId) {
this.entityId = entityId;
}
public short getGamemode() {
return gamemode;
}
public void setGamemode(short gamemode) {
this.gamemode = gamemode;
}
public int getDimension() {
return dimension;
} }
public void setDimension(int dimension) { public void setDimension(int dimension) {
this.dimension = dimension; this.dimension = dimension;
} }
public long getPartialHashedSeed() {
return partialHashedSeed;
}
public short getDifficulty() {
return difficulty;
}
public void setDifficulty(short difficulty) {
this.difficulty = difficulty;
}
public int getMaxPlayers() {
return maxPlayers;
}
public void setMaxPlayers(int maxPlayers) {
this.maxPlayers = maxPlayers;
}
public @Nullable String getLevelType() {
return levelType;
}
public void setLevelType(String levelType) {
this.levelType = levelType;
}
public int getViewDistance() {
return viewDistance;
}
public void setViewDistance(int viewDistance) {
this.viewDistance = viewDistance;
}
public boolean isReducedDebugInfo() {
return reducedDebugInfo;
}
public void setReducedDebugInfo(boolean reducedDebugInfo) {
this.reducedDebugInfo = reducedDebugInfo;
}
public DimensionInfo getDimensionInfo() {
return dimensionInfo;
}
public void setDimensionInfo(DimensionInfo dimensionInfo) {
this.dimensionInfo = dimensionInfo;
}
public DimensionRegistry getDimensionRegistry() {
return dimensionRegistry;
}
public void setDimensionRegistry(DimensionRegistry dimensionRegistry) {
this.dimensionRegistry = dimensionRegistry;
}
public short getPreviousGamemode() {
return previousGamemode;
}
public void setPreviousGamemode(short previousGamemode) {
this.previousGamemode = previousGamemode;
}
public boolean getIsHardcore() {
return isHardcore;
}
public void setIsHardcore(boolean isHardcore) {
this.isHardcore = isHardcore;
}
public CompoundBinaryTag getBiomeRegistry() {
return biomeRegistry;
}
public void setBiomeRegistry(CompoundBinaryTag biomeRegistry) {
this.biomeRegistry = biomeRegistry;
}
public DimensionData getCurrentDimensionData() {
return currentDimensionData;
}
@Override
public String toString() {
return "JoinGame{"
+ "entityId=" + entityId
+ ", gamemode=" + gamemode
+ ", dimension=" + dimension
+ ", partialHashedSeed=" + partialHashedSeed
+ ", difficulty=" + difficulty
+ ", maxPlayers=" + maxPlayers
+ ", levelType='" + levelType + '\''
+ ", viewDistance=" + viewDistance
+ ", reducedDebugInfo=" + reducedDebugInfo
+ ", dimensionRegistry='" + dimensionRegistry + '\''
+ ", dimensionInfo='" + dimensionInfo + '\''
+ ", previousGamemode=" + previousGamemode
+ '}';
}
@Override @Override
public void decode(ByteBuf buf, PacketDirection direction, ProtocolVersion version) { public void decode(ByteBuf buf, PacketDirection direction, ProtocolVersion version) {
this.entityId = buf.readInt(); this.entityId = buf.readInt();
@ -61,7 +185,7 @@ public class ClientboundJoinGamePacket implements Packet {
if (version.gte(ProtocolVersion.MINECRAFT_1_16)) { if (version.gte(ProtocolVersion.MINECRAFT_1_16)) {
this.previousGamemode = buf.readByte(); this.previousGamemode = buf.readByte();
ImmutableSet<String> levelNames = ImmutableSet.copyOf(ProtocolUtils.readStringArray(buf)); ImmutableSet<String> levelNames = ImmutableSet.copyOf(ProtocolUtils.readStringArray(buf));
CompoundBinaryTag registryContainer = ProtocolUtils.readCompoundTag(buf); CompoundBinaryTag registryContainer = ProtocolUtils.readCompoundTag(buf, JOINGAME_READER);
ListBinaryTag dimensionRegistryContainer = null; ListBinaryTag dimensionRegistryContainer = null;
if (version.gte(ProtocolVersion.MINECRAFT_1_16_2)) { if (version.gte(ProtocolVersion.MINECRAFT_1_16_2)) {
dimensionRegistryContainer = registryContainer.getCompound("minecraft:dimension_type") dimensionRegistryContainer = registryContainer.getCompound("minecraft:dimension_type")
@ -74,8 +198,8 @@ public class ClientboundJoinGamePacket implements Packet {
ImmutableSet<DimensionData> readData = ImmutableSet<DimensionData> readData =
DimensionRegistry.fromGameData(dimensionRegistryContainer, version); DimensionRegistry.fromGameData(dimensionRegistryContainer, version);
this.dimensionRegistry = new DimensionRegistry(readData, levelNames); this.dimensionRegistry = new DimensionRegistry(readData, levelNames);
if (version.gte(ProtocolVersion.MINECRAFT_1_16_2)) { if (version.compareTo(ProtocolVersion.MINECRAFT_1_16_2) >= 0) {
CompoundBinaryTag currentDimDataTag = ProtocolUtils.readCompoundTag(buf); CompoundBinaryTag currentDimDataTag = ProtocolUtils.readCompoundTag(buf, JOINGAME_READER);
dimensionIdentifier = ProtocolUtils.readString(buf); dimensionIdentifier = ProtocolUtils.readString(buf);
this.currentDimensionData = DimensionData.decodeBaseCompoundTag(currentDimDataTag, version) this.currentDimensionData = DimensionData.decodeBaseCompoundTag(currentDimDataTag, version)
.annotateWith(dimensionIdentifier, null); .annotateWith(dimensionIdentifier, null);
@ -186,66 +310,6 @@ public class ClientboundJoinGamePacket implements Packet {
} }
} }
public int getEntityId() {
return entityId;
}
public short getGamemode() {
return gamemode;
}
public int getDimension() {
return dimension;
}
public long getPartialHashedSeed() {
return partialHashedSeed;
}
public short getDifficulty() {
return difficulty;
}
public int getMaxPlayers() {
return maxPlayers;
}
public @Nullable String getLevelType() {
return levelType;
}
public int getViewDistance() {
return viewDistance;
}
public boolean isReducedDebugInfo() {
return reducedDebugInfo;
}
public DimensionInfo getDimensionInfo() {
return dimensionInfo;
}
public DimensionRegistry getDimensionRegistry() {
return dimensionRegistry;
}
public short getPreviousGamemode() {
return previousGamemode;
}
public boolean getIsHardcore() {
return isHardcore;
}
public CompoundBinaryTag getBiomeRegistry() {
return biomeRegistry;
}
public DimensionData getCurrentDimensionData() {
return currentDimensionData;
}
@Override @Override
public boolean handle(PacketHandler handler) { public boolean handle(PacketHandler handler) {
return handler.handle(this); return handler.handle(this);

Datei anzeigen

@ -10,6 +10,7 @@ import com.velocitypowered.proxy.network.packet.PacketDirection;
import com.velocitypowered.proxy.network.packet.PacketHandler; import com.velocitypowered.proxy.network.packet.PacketHandler;
import com.velocitypowered.proxy.network.packet.PacketReader; import com.velocitypowered.proxy.network.packet.PacketReader;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
import net.kyori.adventure.nbt.BinaryTagIO;
import net.kyori.adventure.nbt.CompoundBinaryTag; import net.kyori.adventure.nbt.CompoundBinaryTag;
public class ClientboundRespawnPacket implements Packet { public class ClientboundRespawnPacket implements Packet {
@ -102,9 +103,9 @@ public class ClientboundRespawnPacket implements Packet {
public void decode(ByteBuf buf, PacketDirection direction, ProtocolVersion version) { public void decode(ByteBuf buf, PacketDirection direction, ProtocolVersion version) {
String dimensionIdentifier = null; String dimensionIdentifier = null;
String levelName = null; String levelName = null;
if (version.gte(ProtocolVersion.MINECRAFT_1_16)) { if (version.compareTo(ProtocolVersion.MINECRAFT_1_16) >= 0) {
if (version.gte(ProtocolVersion.MINECRAFT_1_16_2)) { if (version.compareTo(ProtocolVersion.MINECRAFT_1_16_2) >= 0) {
CompoundBinaryTag dimDataTag = ProtocolUtils.readCompoundTag(buf); CompoundBinaryTag dimDataTag = ProtocolUtils.readCompoundTag(buf, BinaryTagIO.reader());
dimensionIdentifier = ProtocolUtils.readString(buf); dimensionIdentifier = ProtocolUtils.readString(buf);
this.currentDimensionData = DimensionData.decodeBaseCompoundTag(dimDataTag, version) this.currentDimensionData = DimensionData.decodeBaseCompoundTag(dimDataTag, version)
.annotateWith(dimensionIdentifier, null); .annotateWith(dimensionIdentifier, null);

Datei anzeigen

@ -20,7 +20,7 @@ public class MinecraftCompressDecoder extends MessageToMessageDecoder<ByteBuf> {
Boolean.getBoolean("velocity.increased-compression-cap") Boolean.getBoolean("velocity.increased-compression-cap")
? HARD_MAXIMUM_UNCOMPRESSED_SIZE : VANILLA_MAXIMUM_UNCOMPRESSED_SIZE; ? HARD_MAXIMUM_UNCOMPRESSED_SIZE : VANILLA_MAXIMUM_UNCOMPRESSED_SIZE;
private final int threshold; private int threshold;
private final VelocityCompressor compressor; private final VelocityCompressor compressor;
public MinecraftCompressDecoder(int threshold, VelocityCompressor compressor) { public MinecraftCompressDecoder(int threshold, VelocityCompressor compressor) {
@ -60,4 +60,8 @@ public class MinecraftCompressDecoder extends MessageToMessageDecoder<ByteBuf> {
public void handlerRemoved(ChannelHandlerContext ctx) throws Exception { public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
compressor.close(); compressor.close();
} }
public void setThreshold(int threshold) {
this.threshold = threshold;
}
} }

Datei anzeigen

@ -9,7 +9,7 @@ import io.netty.handler.codec.MessageToByteEncoder;
public class MinecraftCompressEncoder extends MessageToByteEncoder<ByteBuf> { public class MinecraftCompressEncoder extends MessageToByteEncoder<ByteBuf> {
private final int threshold; private int threshold;
private final VelocityCompressor compressor; private final VelocityCompressor compressor;
public MinecraftCompressEncoder(int threshold, VelocityCompressor compressor) { public MinecraftCompressEncoder(int threshold, VelocityCompressor compressor) {
@ -20,7 +20,7 @@ public class MinecraftCompressEncoder extends MessageToByteEncoder<ByteBuf> {
@Override @Override
protected void encode(ChannelHandlerContext ctx, ByteBuf msg, ByteBuf out) throws Exception { protected void encode(ChannelHandlerContext ctx, ByteBuf msg, ByteBuf out) throws Exception {
int uncompressed = msg.readableBytes(); int uncompressed = msg.readableBytes();
if (uncompressed <= threshold) { if (uncompressed < threshold) {
// Under the threshold, there is nothing to do. // Under the threshold, there is nothing to do.
ProtocolUtils.writeVarInt(out, 0); ProtocolUtils.writeVarInt(out, 0);
out.writeBytes(msg); out.writeBytes(msg);
@ -54,4 +54,8 @@ public class MinecraftCompressEncoder extends MessageToByteEncoder<ByteBuf> {
public void handlerRemoved(ChannelHandlerContext ctx) throws Exception { public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
compressor.close(); compressor.close();
} }
public void setThreshold(int threshold) {
this.threshold = threshold;
}
} }

Datei anzeigen

@ -15,8 +15,8 @@ import io.netty.handler.codec.CorruptedFrameException;
public class MinecraftDecoder extends ChannelInboundHandlerAdapter { public class MinecraftDecoder extends ChannelInboundHandlerAdapter {
public static final boolean DEBUG = Boolean.getBoolean("velocity.packet-decode-logging"); public static final boolean DEBUG = Boolean.getBoolean("velocity.packet-decode-logging");
private static final QuietDecoderException DECODE_FAILED = private static final QuietRuntimeException DECODE_FAILED =
new QuietDecoderException("A packet did not decode successfully (invalid data). If you are a " new QuietRuntimeException("A packet did not decode successfully (invalid data). If you are a "
+ "developer, launch Velocity with -Dvelocity.packet-decode-logging=true to see more."); + "developer, launch Velocity with -Dvelocity.packet-decode-logging=true to see more.");
private final PacketDirection direction; private final PacketDirection direction;
@ -64,8 +64,16 @@ public class MinecraftDecoder extends ChannelInboundHandlerAdapter {
ctx.fireChannelRead(buf); ctx.fireChannelRead(buf);
} else { } else {
try { try {
doLengthSanityChecks(buf, packet);
try {
packet.decode(buf, direction, registry.version);
} catch (Exception e) {
throw handleDecodeFailure(e, packet, packetId);
}
if (buf.isReadable()) { if (buf.isReadable()) {
throw handleNotReadEnough(packet, packetId); throw handleOverflow(packet, buf.readerIndex(), buf.writerIndex());
} }
ctx.fireChannelRead(packet); ctx.fireChannelRead(packet);
} finally { } finally {
@ -74,10 +82,30 @@ public class MinecraftDecoder extends ChannelInboundHandlerAdapter {
} }
} }
private Exception handleNotReadEnough(Packet packet, int packetId) { private void doLengthSanityChecks(ByteBuf buf, Packet packet) throws Exception {
int expectedMinLen = packet.expectedMinLength(buf, direction, registry.version);
int expectedMaxLen = packet.expectedMaxLength(buf, direction, registry.version);
if (expectedMaxLen != -1 && buf.readableBytes() > expectedMaxLen) {
throw handleOverflow(packet, expectedMaxLen, buf.readableBytes());
}
if (buf.readableBytes() < expectedMinLen) {
throw handleUnderflow(packet, expectedMaxLen, buf.readableBytes());
}
}
private Exception handleOverflow(Packet packet, int expected, int actual) {
if (DEBUG) { if (DEBUG) {
return new CorruptedFrameException("Did not read full packet for " + packet.getClass() + " " return new CorruptedFrameException("Packet sent for " + packet.getClass() + " was too "
+ getExtraConnectionDetail(packetId)); + "big (expected " + expected + " bytes, got " + actual + " bytes)");
} else {
return DECODE_FAILED;
}
}
private Exception handleUnderflow(Packet packet, int expected, int actual) {
if (DEBUG) {
return new CorruptedFrameException("Packet sent for " + packet.getClass() + " was too "
+ "small (expected " + expected + " bytes, got " + actual + " bytes)");
} else { } else {
return DECODE_FAILED; return DECODE_FAILED;
} }

Datei anzeigen

@ -0,0 +1,24 @@
package com.velocitypowered.proxy.protocol;
import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import io.netty.buffer.ByteBuf;
public interface MinecraftPacket {
void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion);
void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion);
boolean handle(MinecraftSessionHandler handler);
default int expectedMaxLength(ByteBuf buf, ProtocolUtils.Direction direction,
ProtocolVersion version) {
return -1;
}
default int expectedMinLength(ByteBuf buf, ProtocolUtils.Direction direction,
ProtocolVersion version) {
return 0;
}
}

Datei anzeigen

@ -0,0 +1,129 @@
package com.velocitypowered.proxy.protocol.packet;
import static com.velocitypowered.proxy.protocol.util.PluginMessageUtil.transformLegacyToModernChannel;
import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import com.velocitypowered.proxy.protocol.util.DeferredByteBufHolder;
import io.netty.buffer.ByteBuf;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
public class PluginMessage extends DeferredByteBufHolder implements MinecraftPacket {
private @Nullable String channel;
public PluginMessage() {
super(null);
}
public PluginMessage(String channel,
@MonotonicNonNull ByteBuf backing) {
super(backing);
this.channel = channel;
}
public String getChannel() {
if (channel == null) {
throw new IllegalStateException("Channel is not specified.");
}
return channel;
}
public void setChannel(String channel) {
this.channel = channel;
}
@Override
public String toString() {
return "PluginMessage{"
+ "channel='" + channel + '\''
+ ", data=" + super.toString()
+ '}';
}
@Override
public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) {
this.channel = ProtocolUtils.readString(buf);
if (version.compareTo(ProtocolVersion.MINECRAFT_1_13) >= 0) {
this.channel = transformLegacyToModernChannel(this.channel);
}
if (version.compareTo(ProtocolVersion.MINECRAFT_1_8) >= 0) {
this.replace(buf.readRetainedSlice(buf.readableBytes()));
} else {
this.replace(ProtocolUtils.readRetainedByteBufSlice17(buf));
}
}
@Override
public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) {
if (channel == null) {
throw new IllegalStateException("Channel is not specified.");
}
if (refCnt() == 0) {
throw new IllegalStateException("Plugin message contents for " + this.channel
+ " freed too many times.");
}
if (version.compareTo(ProtocolVersion.MINECRAFT_1_13) >= 0) {
ProtocolUtils.writeString(buf, transformLegacyToModernChannel(this.channel));
} else {
ProtocolUtils.writeString(buf, this.channel);
}
if (version.compareTo(ProtocolVersion.MINECRAFT_1_8) >= 0) {
buf.writeBytes(content());
} else {
ProtocolUtils.writeByteBuf17(content(), buf, true); // True for Forge support
}
}
@Override
public boolean handle(MinecraftSessionHandler handler) {
return handler.handle(this);
}
@Override
public PluginMessage copy() {
return (PluginMessage) super.copy();
}
@Override
public PluginMessage duplicate() {
return (PluginMessage) super.duplicate();
}
@Override
public PluginMessage retainedDuplicate() {
return (PluginMessage) super.retainedDuplicate();
}
@Override
public PluginMessage replace(ByteBuf content) {
return (PluginMessage) super.replace(content);
}
@Override
public PluginMessage retain() {
return (PluginMessage) super.retain();
}
@Override
public PluginMessage retain(int increment) {
return (PluginMessage) super.retain(increment);
}
@Override
public PluginMessage touch() {
return (PluginMessage) super.touch();
}
@Override
public PluginMessage touch(Object hint) {
return (PluginMessage) super.touch(hint);
}
}

Datei anzeigen

@ -7,6 +7,7 @@ import com.google.common.collect.ImmutableList;
import com.google.common.collect.Multimap; import com.google.common.collect.Multimap;
import com.google.common.collect.Multimaps; import com.google.common.collect.Multimaps;
import com.google.common.util.concurrent.ThreadFactoryBuilder; import com.google.common.util.concurrent.ThreadFactoryBuilder;
import com.velocitypowered.api.plugin.PluginContainer;
import com.velocitypowered.api.plugin.PluginManager; import com.velocitypowered.api.plugin.PluginManager;
import com.velocitypowered.api.scheduler.ScheduledTask; import com.velocitypowered.api.scheduler.ScheduledTask;
import com.velocitypowered.api.scheduler.Scheduler; import com.velocitypowered.api.scheduler.Scheduler;
@ -183,8 +184,18 @@ public class VelocityScheduler implements Scheduler {
currentTaskThread = Thread.currentThread(); currentTaskThread = Thread.currentThread();
try { try {
runnable.run(); runnable.run();
} catch (Exception e) { } catch (Throwable e) {
Log.logger.error("Exception in task {} by plugin {}", runnable, plugin, e); //noinspection ConstantConditions
if (e instanceof InterruptedException) {
Thread.currentThread().interrupt();
} else {
String friendlyPluginName = pluginManager.fromInstance(plugin)
.map(container -> container.getDescription().getName()
.orElse(container.getDescription().getId()))
.orElse("UNKNOWN");
Log.logger.error("Exception in task {} by plugin {}", runnable, friendlyPluginName,
e);
}
} finally { } finally {
if (repeat == 0) { if (repeat == 0) {
onFinish(); onFinish();

Datei anzeigen

@ -125,7 +125,9 @@ public class VelocityRegisteredServer implements RegisteredServer, ForwardingAud
} }
/** /**
* Sends a plugin message to the server through this connection. * Sends a plugin message to the server through this connection. The message will be released
* afterwards.
*
* @param identifier the channel ID to use * @param identifier the channel ID to use
* @param data the data * @param data the data
* @return whether or not the message was sent * @return whether or not the message was sent
@ -133,11 +135,12 @@ public class VelocityRegisteredServer implements RegisteredServer, ForwardingAud
public boolean sendPluginMessage(ChannelIdentifier identifier, ByteBuf data) { public boolean sendPluginMessage(ChannelIdentifier identifier, ByteBuf data) {
for (ConnectedPlayer player : players.values()) { for (ConnectedPlayer player : players.values()) {
VelocityServerConnection connection = player.getConnectedServer(); VelocityServerConnection connection = player.getConnectedServer();
if (connection != null && connection.getServerInfo().equals(serverInfo)) { if (connection != null && connection.getServer() == this) {
return connection.sendPluginMessage(identifier, data); return connection.sendPluginMessage(identifier, data);
} }
} }
data.release();
return false; return false;
} }

Datei anzeigen

@ -21,16 +21,17 @@ import org.checkerframework.checker.nullness.qual.Nullable;
public class VelocityTabList implements TabList { public class VelocityTabList implements TabList {
protected final ConnectedPlayer player;
protected final MinecraftConnection connection; protected final MinecraftConnection connection;
protected final Map<UUID, VelocityTabListEntry> entries = new ConcurrentHashMap<>(); protected final Map<UUID, VelocityTabListEntry> entries = new ConcurrentHashMap<>();
public VelocityTabList(MinecraftConnection connection) { public VelocityTabList(final ConnectedPlayer player) {
this.connection = connection; this.player = player;
this.connection = player.getConnection();
} }
@Override @Override
public void setHeaderAndFooter(net.kyori.adventure.text.Component header, public void setHeaderAndFooter(Component header, Component footer) {
net.kyori.adventure.text.Component footer) {
Preconditions.checkNotNull(header, "header"); Preconditions.checkNotNull(header, "header");
Preconditions.checkNotNull(footer, "footer"); Preconditions.checkNotNull(footer, "footer");
GsonComponentSerializer serializer = ProtocolUtils.getJsonChatSerializer( GsonComponentSerializer serializer = ProtocolUtils.getJsonChatSerializer(

Datei anzeigen

@ -0,0 +1,24 @@
package com.velocitypowered.proxy.util.collect;
import it.unimi.dsi.fastutil.Hash.Strategy;
public final class IdentityHashStrategy<T> implements Strategy<T> {
@SuppressWarnings("rawtypes")
private static final IdentityHashStrategy INSTANCE = new IdentityHashStrategy();
public static <T> Strategy<T> instance() {
//noinspection unchecked
return INSTANCE;
}
@Override
public int hashCode(T o) {
return System.identityHashCode(o);
}
@Override
public boolean equals(T a, T b) {
return a == b;
}
}

Datei anzeigen

@ -6,7 +6,7 @@ bind = "0.0.0.0:25577"
# What should be the MOTD? This gets displayed when the player adds your server to # What should be the MOTD? This gets displayed when the player adds your server to
# their server list. Legacy color codes and JSON are accepted. # their server list. Legacy color codes and JSON are accepted.
motd = "&3A Velocity Server" motd = "&#09add3A Velocity Server"
# What should we display for the maximum number of players? (Velocity does not support a cap # What should we display for the maximum number of players? (Velocity does not support a cap
# on the number of players online.) # on the number of players online.)
@ -33,7 +33,7 @@ prevent-client-proxy-connections = false
# Velocity's native forwarding. Only applicable for Minecraft 1.13 or higher. # Velocity's native forwarding. Only applicable for Minecraft 1.13 or higher.
player-info-forwarding-mode = "NONE" player-info-forwarding-mode = "NONE"
# If you are using modern or BungeeGuard IP forwarding, configure an unique secret here. # If you are using modern or BungeeGuard IP forwarding, configure a unique secret here.
forwarding-secret = "" forwarding-secret = ""
# Announce whether or not your server supports Forge. If you run a modded server, we # Announce whether or not your server supports Forge. If you run a modded server, we
@ -105,8 +105,9 @@ connection-timeout = 5000
# Specify a read timeout for connections here. The default is 30 seconds. # Specify a read timeout for connections here. The default is 30 seconds.
read-timeout = 30000 read-timeout = 30000
# Enables compatibility with HAProxy. # Enables compatibility with HAProxy's PROXY protocol. If you don't know what this is for, then
proxy-protocol = false # don't enable it.
haproxy-protocol = false
# Enables TCP fast open support on the proxy. Requires the proxy to run on Linux. # Enables TCP fast open support on the proxy. Requires the proxy to run on Linux.
tcp-fast-open = false tcp-fast-open = false
@ -142,19 +143,6 @@ map = "Velocity"
# Whether plugins should be shown in query response by default or not # Whether plugins should be shown in query response by default or not
show-plugins = false show-plugins = false
[metrics]
# Whether metrics will be reported to bStats (https://bstats.org).
# bStats collects some basic information, like how many people use Velocity and their
# player count. We recommend keeping bStats enabled, but if you're not comfortable with
# this, you can turn this setting off. There is no performance penalty associated with
# having metrics enabled, and data sent to bStats can't identify your server.
enabled = true
# A unique, anonymous ID to identify this proxy with.
id = ""
log-failure = false
# Legacy color codes and JSON are accepted in all messages. # Legacy color codes and JSON are accepted in all messages.
[messages] [messages]
# Prefix when the player gets kicked from a server. # Prefix when the player gets kicked from a server.

Datei anzeigen

@ -24,6 +24,7 @@ import com.velocitypowered.api.command.SimpleCommand;
import com.velocitypowered.proxy.event.MockEventManager; import com.velocitypowered.proxy.event.MockEventManager;
import com.velocitypowered.proxy.event.VelocityEventManager; import com.velocitypowered.proxy.event.VelocityEventManager;
import java.util.List; import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.atomic.AtomicReference;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
@ -423,6 +424,54 @@ public class CommandManagerTests {
manager.offerSuggestions(MockCommandSource.INSTANCE, "foo2 baz ").join()); manager.offerSuggestions(MockCommandSource.INSTANCE, "foo2 baz ").join());
} }
@Test
void testSuggestionPermissions() throws ExecutionException, InterruptedException {
VelocityCommandManager manager = createManager();
RawCommand rawCommand = new RawCommand() {
@Override
public void execute(final Invocation invocation) {
fail("The Command should not be executed while testing suggestions");
}
@Override
public boolean hasPermission(Invocation invocation) {
return invocation.arguments().length() > 0;
}
@Override
public List<String> suggest(final Invocation invocation) {
return ImmutableList.of("suggestion");
}
};
manager.register(rawCommand, "foo");
assertTrue(manager.offerSuggestions(MockCommandSource.INSTANCE, "foo").get().isEmpty());
assertFalse(manager.offerSuggestions(MockCommandSource.INSTANCE, "foo bar").get().isEmpty());
Command oldCommand = new Command() {
@Override
public void execute(CommandSource source, String @NonNull [] args) {
fail("The Command should not be executed while testing suggestions");
}
@Override
public boolean hasPermission(CommandSource source, String @NonNull [] args) {
return args.length > 0;
}
@Override
public List<String> suggest(CommandSource source, String @NonNull [] currentArgs) {
return ImmutableList.of("suggestion");
}
};
manager.register(oldCommand, "bar");
assertTrue(manager.offerSuggestions(MockCommandSource.INSTANCE, "bar").get().isEmpty());
assertFalse(manager.offerSuggestions(MockCommandSource.INSTANCE, "bar foo").get().isEmpty());
}
static class NoopSimpleCommand implements SimpleCommand { static class NoopSimpleCommand implements SimpleCommand {
@Override @Override
public void execute(final Invocation invocation) { public void execute(final Invocation invocation) {