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
and pull requests:
* [Checker Framework](https://checkerframework.org/): an enhancement to Java's type
system that is designed to help catch bugs. Velocity runs the _Nullness Checker_
and the _Optional Checker_. The build will fail if Checker Framework notices an
issue.
* [SpotBugs](https://spotbugs.github.io/): ensures that common errors do not
get into the codebase. The build will fail if SpotBugs finds an issue.
* [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

@ -81,9 +81,18 @@ javadoc {
// Disable the crazy super-strict doclint tool in Java 8
options.addStringOption('Xdoclint:none', '-quiet')
// Mark sources as Java 8 source compatible
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 {

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 {
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 name;

Datei anzeigen

@ -227,6 +227,11 @@ public final class ServerPing {
return this;
}
public Builder clearFavicon() {
this.favicon = null;
return this;
}
/**
* 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.

Datei anzeigen

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

Datei anzeigen

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

Datei anzeigen

@ -55,13 +55,13 @@ dependencies {
implementation "io.netty:netty-handler:${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-aarch64"
implementation "io.netty:netty-resolver-dns:${nettyVersion}"
implementation "io.netty:netty-transport-native-epoll:${nettyVersion}:linux-aarch_64"
implementation "org.apache.logging.log4j:log4j-api:${log4jVersion}"
implementation "org.apache.logging.log4j:log4j-core:${log4jVersion}"
implementation "org.apache.logging.log4j:log4j-slf4j-impl:${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.minecrell:terminalconsoleappender:1.2.0'
@ -76,7 +76,7 @@ dependencies {
implementation 'com.spotify:completable-futures:0.3.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 '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/*Object2IntArray*'
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/*Object2Long*'
exclude 'it/unimi/dsi/fastutil/objects/*Object2Object*'
@ -127,12 +126,10 @@ shadowJar {
exclude 'it/unimi/dsi/fastutil/objects/*Reference*'
exclude 'it/unimi/dsi/fastutil/shorts/**'
exclude 'org/checkerframework/checker/**'
relocate 'org.bstats', 'com.velocitypowered.proxy.bstats'
}
artifacts {
archives shadowJar
}
test {
useJUnitPlatform()
}

Datei anzeigen

@ -1,82 +1,67 @@
package com.velocitypowered.proxy;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import com.velocitypowered.proxy.config.VelocityConfiguration;
import io.netty.handler.codec.http.HttpHeaderNames;
import java.io.BufferedWriter;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.List;
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.Pattern;
import java.util.zip.GZIPOutputStream;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.asynchttpclient.ListenableFuture;
import org.asynchttpclient.Response;
import org.bstats.MetricsBase;
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 {
// The version of this bStats class
private static final int B_STATS_METRICS_REVISION = 2;
private MetricsBase metricsBase;
// The url to which the data is sent
private static final String URL = "https://bstats.org/submitData/server-implementation";
private Metrics(Logger logger, int serviceId, boolean defaultEnabled) {
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
private static final Logger logger = LogManager.getLogger(Metrics.class);
metricsBase = new MetricsBase(
"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?
private boolean logFailedRequests = false;
// The name of the server software
private final String name;
// The plugin ID for the server software as assigned by bStats.
private final int pluginId;
// The uuid of the server
private final String serverUuid;
// 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();
if (!config.didExistBefore()) {
// 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"
+ " and send them to bStats (https://bStats.org).");
logger.info("bStats collects some basic information for plugin"
+ " authors, like how many people use");
logger.info("their plugin and their total player count."
+ " It's recommended to keep bStats enabled, but");
logger.info("if you're not comfortable with this, you can opt-out"
+ " by editing the config.txt file in");
logger.info("the '/plugins/bStats/' folder and setting enabled to false.");
}
}
/**
@ -85,544 +70,69 @@ public class Metrics {
* @param chart The chart to add.
*/
public void addCustomChart(CustomChart chart) {
if (chart == null) {
throw new IllegalArgumentException("Chart cannot be null!");
}
charts.add(chart);
metricsBase.addCustomChart(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 {
private static final Logger logger = LogManager.getLogger(Metrics.class);
static void startMetrics(VelocityServer server, VelocityConfiguration.Metrics metricsConfig) {
if (!metricsConfig.isFromConfig()) {
// 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.");
}
Metrics metrics = new Metrics(logger, 4752, metricsConfig.isEnabled());
// 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(
new SingleLineChart("players", server::getPlayerCount)
);
metrics.addCustomChart(
new SingleLineChart("managed_servers", () -> server.getAllServers().size())
);
metrics.addCustomChart(
new SimplePie("online_mode",
() -> server.getConfiguration().isOnlineMode() ? "online" : "offline")
);
metrics.addCustomChart(new SimplePie("velocity_version",
() -> server.getVersion().getVersion()));
metrics.addCustomChart(
new Metrics.SingleLineChart("players", server::getPlayerCount)
);
metrics.addCustomChart(
new Metrics.SingleLineChart("managed_servers", () -> server.getAllServers().size())
);
metrics.addCustomChart(
new Metrics.SimplePie("online_mode",
() -> server.getConfiguration().isOnlineMode() ? "online" : "offline")
);
metrics.addCustomChart(new Metrics.SimplePie("velocity_version",
() -> server.getVersion().getVersion()));
metrics.addCustomChart(new DrilldownPie("java_version", () -> {
Map<String, Map<String, Integer>> map = new HashMap<>();
String javaVersion = System.getProperty("java.version");
Map<String, Integer> entry = new HashMap<>();
entry.put(javaVersion, 1);
metrics.addCustomChart(new Metrics.DrilldownPie("java_version", () -> {
Map<String, Map<String, Integer>> map = new HashMap<>();
String javaVersion = System.getProperty("java.version");
Map<String, Integer> entry = new HashMap<>();
entry.put(javaVersion, 1);
// http://openjdk.java.net/jeps/223
// Java decided to change their versioning scheme and in doing so modified the
// java.version system property to return $major[.$minor][.$security][-ea], as opposed to
// 1.$major.0_$identifier we can handle pre-9 by checking if the "major" is equal to "1",
// otherwise, 9+
String majorVersion = javaVersion.split("\\.")[0];
String release;
// http://openjdk.java.net/jeps/223
// Java decided to change their versioning scheme and in doing so modified the
// java.version system property to return $major[.$minor][.$security][-ea], as opposed to
// 1.$major.0_$identifier we can handle pre-9 by checking if the "major" is equal to "1",
// otherwise, 9+
String majorVersion = javaVersion.split("\\.")[0];
String release;
int indexOf = javaVersion.lastIndexOf('.');
int indexOf = javaVersion.lastIndexOf('.');
if (majorVersion.equals("1")) {
release = "Java " + javaVersion.substring(0, indexOf);
} else {
// of course, it really wouldn't be all that simple if they didn't add a quirk, now
// would it valid strings for the major may potentially include values such as -ea to
// denote a pre release
Matcher versionMatcher = Pattern.compile("\\d+").matcher(majorVersion);
if (versionMatcher.find()) {
majorVersion = versionMatcher.group(0);
}
release = "Java " + majorVersion;
if (majorVersion.equals("1")) {
release = "Java " + javaVersion.substring(0, indexOf);
} else {
// of course, it really wouldn't be all that simple if they didn't add a quirk, now
// would it valid strings for the major may potentially include values such as -ea to
// denote a pre release
Matcher versionMatcher = Pattern.compile("\\d+").matcher(majorVersion);
if (versionMatcher.find()) {
majorVersion = versionMatcher.group(0);
}
map.put(release, entry);
return map;
}));
}
release = "Java " + majorVersion;
}
map.put(release, entry);
return map;
}));
}
}
}
}

Datei anzeigen

@ -8,9 +8,12 @@ import org.apache.logging.log4j.Logger;
public class Velocity {
private static final Logger logger = LogManager.getLogger(Velocity.class);
private static final Logger logger;
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
// memory for Minecraft, which imposes a maximum packet size of 2MiB! We'll use 4MiB as a more
// sane default.

Datei anzeigen

@ -59,6 +59,9 @@ public interface CommandNodeFactory<T extends Command> {
(context, builder) -> {
I invocation = createInvocation(context);
if (!command.hasPermission(invocation)) {
return builder.buildFuture();
}
return command.suggestAsync(invocation).thenApply(values -> {
for (String value : values) {
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.HoverEvent;
import net.kyori.adventure.text.format.NamedTextColor;
import net.kyori.adventure.text.format.TextColor;
import net.kyori.adventure.text.format.TextDecoration;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
@ -188,6 +189,7 @@ public class VelocityCommand implements SimpleCommand {
private static class Info implements SubCommand {
private static final TextColor VELOCITY_COLOR = TextColor.fromHexString("#09add3");
private final ProxyServer server;
private Info(ProxyServer server) {
@ -205,11 +207,11 @@ public class VelocityCommand implements SimpleCommand {
TextComponent velocity = Component.text().content(version.getName() + " ")
.decoration(TextDecoration.BOLD, true)
.color(NamedTextColor.DARK_AQUA)
.color(VELOCITY_COLOR)
.append(Component.text(version.getVersion()).decoration(TextDecoration.BOLD, false))
.build();
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.");
source.sendMessage(Identity.nil(), velocity);
source.sendMessage(Identity.nil(), copyright);

Datei anzeigen

@ -30,7 +30,6 @@ import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Random;
import java.util.UUID;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer;
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
@ -426,11 +425,6 @@ public class VelocityConfiguration implements ProxyConfig {
}
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) {
config.save();
}
@ -448,7 +442,7 @@ public class VelocityConfiguration implements ProxyConfig {
PingPassthroughMode.DISABLED);
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);
Boolean onlineMode = config.getOrElse("online-mode", 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.connectionTimeout = config.getIntOrElse("connection-timeout", 5000);
this.readTimeout = config.getIntOrElse("read-timeout", 30000);
this.proxyProtocol = config.getOrElse("proxy-protocol", false);
if (config.contains("haproxy-protocol")) {
this.proxyProtocol = config.getOrElse("haproxy-protocol", false);
} else {
this.proxyProtocol = config.getOrElse("proxy-protocol", false);
}
this.tcpFastOpen = config.getOrElse("tcp-fast-open", false);
this.bungeePluginMessageChannel = config.getOrElse("bungee-plugin-message-channel", true);
this.showPingRequests = config.getOrElse("show-ping-requests", false);
@ -769,39 +767,16 @@ public class VelocityConfiguration implements ProxyConfig {
public static class Metrics {
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) {
if (toml != null) {
this.enabled = toml.getOrElse("enabled", false);
this.id = toml.getOrElse("id", UUID.randomUUID().toString());
this.logFailure = toml.getOrElse("log-failure", false);
this.fromConfig = true;
this.enabled = toml.getOrElse("enabled", true);
}
}
public boolean isEnabled() {
return enabled;
}
public String getId() {
return id;
}
public boolean isLogFailure() {
return logFailure;
}
public boolean isFromConfig() {
return fromConfig;
}
}
public static class Messages {

Datei anzeigen

@ -196,6 +196,8 @@ public class MinecraftConnection extends ChannelInboundHandlerAdapter {
public void write(Object msg) {
if (channel.isActive()) {
channel.writeAndFlush(msg, channel.voidPromise());
} else {
ReferenceCountUtil.release(msg);
}
}
@ -206,6 +208,8 @@ public class MinecraftConnection extends ChannelInboundHandlerAdapter {
public void delayedWrite(Object msg) {
if (channel.isActive()) {
channel.write(msg, channel.voidPromise());
} else {
ReferenceCountUtil.release(msg);
}
}
@ -379,16 +383,25 @@ public class MinecraftConnection extends ChannelInboundHandlerAdapter {
if (threshold == -1) {
channel.pipeline().remove(COMPRESSION_DECODER);
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();
VelocityCompressor compressor = Natives.compress.get().create(level);
encoder = new MinecraftCompressEncoder(threshold, compressor);
decoder = new MinecraftCompressDecoder(threshold, compressor);
channel.pipeline().addBefore(MINECRAFT_DECODER, COMPRESSION_DECODER, decoder);
channel.pipeline().addBefore(MINECRAFT_ENCODER, COMPRESSION_ENCODER, encoder);
}
}
int level = server.getConfiguration().getCompressionLevel();
VelocityCompressor compressor = Natives.compress.get().create(level);
MinecraftCompressEncoder encoder = new MinecraftCompressEncoder(threshold, compressor);
MinecraftCompressDecoder decoder = new MinecraftCompressDecoder(threshold, compressor);
channel.pipeline().addBefore(MINECRAFT_DECODER, COMPRESSION_DECODER, decoder);
channel.pipeline().addBefore(MINECRAFT_ENCODER, COMPRESSION_ENCODER, encoder);
}
/**

Datei anzeigen

@ -30,6 +30,7 @@ import com.velocitypowered.proxy.network.packet.serverbound.ServerboundPluginMes
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufUtil;
import io.netty.buffer.Unpooled;
import io.netty.channel.Channel;
import io.netty.handler.timeout.ReadTimeoutException;
import java.util.Collection;
import org.apache.logging.log4j.LogManager;
@ -38,6 +39,8 @@ import org.apache.logging.log4j.Logger;
public class BackendPlaySessionHandler implements MinecraftSessionHandler {
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 VelocityServerConnection serverConn;
private final ClientPlaySessionHandler playerSessionHandler;
@ -64,10 +67,13 @@ public class BackendPlaySessionHandler implements MinecraftSessionHandler {
@Override
public void activated() {
serverConn.getServer().addPlayer(serverConn.getPlayer());
MinecraftConnection serverMc = serverConn.ensureConnected();
serverMc.write(PluginMessageUtil.constructChannelsPacket(serverMc.getProtocolVersion(),
ImmutableList.of(getBungeeCordChannel(serverMc.getProtocolVersion())), ServerboundPluginMessagePacket.FACTORY
));
if (server.getConfiguration().isBungeePluginChannelEnabled()) {
MinecraftConnection serverMc = serverConn.ensureConnected();
serverMc.write(PluginMessageUtil.constructChannelsPacket(serverMc.getProtocolVersion(),
ImmutableList.of(getBungeeCordChannel(serverMc.getProtocolVersion()))
));
}
}
@Override
@ -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,75 +262,52 @@ 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) {
proxy.getPlayer(in.readUTF())
.flatMap(Player::getCurrentServer)
.ifPresent(server -> sendServerResponse(player, prepareForwardMessage(in)));
Optional<Player> player = proxy.getPlayer(in.readUTF());
if (player.isPresent()) {
ByteBuf toForward = in.unwrap().copy();
sendServerResponse((ConnectedPlayer) player.get(), toForward);
}
}
private void processForwardToServer(ByteBufDataInput in) {
String target = in.readUTF();
ByteBuf toForward = prepareForwardMessage(in);
ByteBuf toForward = in.unwrap().copy();
if (target.equals("ALL")) {
ByteBuf unreleasableForward = Unpooled.unreleasableBuffer(toForward);
try {
for (RegisteredServer rs : proxy.getAllServers()) {
((VelocityRegisteredServer) rs).sendPluginMessage(LEGACY_CHANNEL, unreleasableForward);
((VelocityRegisteredServer) rs).sendPluginMessage(LEGACY_CHANNEL,
toForward.retainedSlice());
}
} finally {
toForward.release();
}
} else {
proxy.getServer(target).ifPresent(rs -> ((VelocityRegisteredServer) rs)
.sendPluginMessage(LEGACY_CHANNEL, toForward));
Optional<RegisteredServer> server = proxy.getServer(target);
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) {
return version.gte(ProtocolVersion.MINECRAFT_1_13) ? MODERN_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!
private static void sendServerResponse(ConnectedPlayer player, ByteBuf buf) {
MinecraftConnection serverConnection = player.ensureAndGetCurrentServer().ensureConnected();
String chan = getBungeeCordChannel(serverConnection.getProtocolVersion());
ServerboundPluginMessagePacket msg = null;
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();
}
}
ServerboundPluginMessagePacket msg = new ServerboundPluginMessagePacket(chan, buf);
serverConnection.write(msg);
}
boolean process(AbstractPluginMessagePacket<?> message) {

Datei anzeigen

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

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.proxy.network.PluginMessageUtil.constructChannelsPacket;
import com.google.common.collect.ImmutableList;
import com.velocitypowered.api.event.command.CommandExecuteEvent.CommandResult;
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.PlayerResourcePackStatusEvent;
import com.velocitypowered.api.event.player.TabCompleteEvent;
import com.velocitypowered.api.network.ProtocolVersion;
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.connection.ConnectionTypes;
import com.velocitypowered.proxy.connection.MinecraftConnection;
@ -102,12 +106,14 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
@Override
public boolean handle(ServerboundKeepAlivePacket packet) {
VelocityServerConnection serverConnection = player.getConnectedServer();
if (serverConnection != null && packet.getRandomId() == serverConnection.getLastPingId()) {
MinecraftConnection smc = serverConnection.getConnection();
if (smc != null) {
player.setPing(System.currentTimeMillis() - serverConnection.getLastPingSent());
smc.write(packet);
serverConnection.resetLastPingId();
if (serverConnection != null) {
Long sentTime = serverConnection.getPendingPings().remove(packet.getRandomId());
if (sentTime != null) {
MinecraftConnection smc = serverConnection.getConnection();
if (smc != null) {
player.setPing(System.currentTimeMillis() - sentTime);
smc.write(packet);
}
}
}
return true;
@ -191,7 +197,18 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
logger.warn("A plugin message was received while the backend server was not "
+ "ready. Channel: {}. Packet discarded.", packet.getChannel());
} else if (PluginMessageUtil.isRegister(packet)) {
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());
} else if (PluginMessageUtil.isUnregister(packet)) {
player.getKnownChannels().removeAll(PluginMessageUtil.getChannels(packet));
@ -303,7 +320,7 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
boolean writable = player.getConnection().getChannel().isWritable();
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();
}

Datei anzeigen

@ -59,6 +59,7 @@ import java.net.SocketAddress;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
@ -103,6 +104,8 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player {
private @Nullable VelocityServerConnection connectionInFlight;
private @Nullable PlayerSettings settings;
private @Nullable ModInfo modInfo;
private Component playerListHeader = Component.empty();
private Component playerListFooter = Component.empty();
private final VelocityTabList tabList;
private final VelocityServer server;
private ClientConnectionPhase connectionPhase;
@ -113,11 +116,6 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player {
ConnectedPlayer(VelocityServer server, GameProfile profile, MinecraftConnection connection,
@Nullable InetSocketAddress virtualHost, boolean onlineMode) {
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.connection = connection;
this.virtualHost = virtualHost;
@ -125,6 +123,12 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player {
this.connectionPhase = connection.getType().getInitialClientPhase();
this.knownChannels = CappedSet.create(MAX_PLUGIN_CHANNELS);
this.onlineMode = onlineMode;
if (connection.getProtocolVersion().compareTo(ProtocolVersion.MINECRAFT_1_8) >= 0) {
this.tabList = new VelocityTabList(this);
} else {
this.tabList = new VelocityTabListLegacy(this);
}
}
@Override
@ -265,46 +269,85 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player {
}
@Override
public void showTitle(net.kyori.adventure.title.@NonNull Title title) {
GsonComponentSerializer serializer = ProtocolUtils.getJsonChatSerializer(this
.getProtocolVersion());
public Component getPlayerListHeader() {
return this.playerListHeader;
}
connection.delayedWrite(new ClientboundTitlePacket(
ClientboundTitlePacket.SET_TITLE,
serializer.serialize(title.title())
));
@Override
public Component getPlayerListFooter() {
return this.playerListFooter;
}
connection.delayedWrite(new ClientboundTitlePacket(
ClientboundTitlePacket.SET_SUBTITLE,
serializer.serialize(title.subtitle())
));
@Override
public void sendPlayerListHeader(@NonNull final Component header) {
this.sendPlayerListHeaderAndFooter(header, this.playerListFooter);
}
net.kyori.adventure.title.Title.Times times = title.times();
if (times != null) {
connection.delayedWrite(ClientboundTitlePacket.times(this.getProtocolVersion(), times));
@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()));
}
}
connection.flush();
@Override
public void showTitle(net.kyori.adventure.title.@NonNull Title title) {
if (this.getProtocolVersion().compareTo(ProtocolVersion.MINECRAFT_1_8) >= 0) {
GsonComponentSerializer serializer = ProtocolUtils.getJsonChatSerializer(this
.getProtocolVersion());
connection.delayedWrite(new ClientboundTitlePacket(
ClientboundTitlePacket.SET_TITLE,
serializer.serialize(title.title())
));
connection.delayedWrite(new ClientboundTitlePacket(
ClientboundTitlePacket.SET_SUBTITLE,
serializer.serialize(title.subtitle())
));
net.kyori.adventure.title.Title.Times times = title.times();
if (times != null) {
connection.delayedWrite(ClientboundTitlePacket.times(this.getProtocolVersion(), times));
}
connection.flush();
}
}
@Override
public void clearTitle() {
connection.write(ClientboundTitlePacket.hide(this.getProtocolVersion()));
if (this.getProtocolVersion().compareTo(ProtocolVersion.MINECRAFT_1_8) >= 0) {
connection.write(ClientboundTitlePacket.hide(this.getProtocolVersion()));
}
}
@Override
public void resetTitle() {
connection.write(ClientboundTitlePacket.reset(this.getProtocolVersion()));
if (this.getProtocolVersion().compareTo(ProtocolVersion.MINECRAFT_1_8) >= 0) {
connection.write(ClientboundTitlePacket.reset(this.getProtocolVersion()));
}
}
@Override
public void hideBossBar(@NonNull BossBar bar) {
this.server.getBossBarManager().removeBossBar(this, bar);
if (this.getProtocolVersion().compareTo(ProtocolVersion.MINECRAFT_1_9) >= 0) {
this.server.getBossBarManager().removeBossBar(this, bar);
}
}
@Override
public void showBossBar(@NonNull BossBar bar) {
this.server.getBossBarManager().addBossBar(this, bar);
if (this.getProtocolVersion().compareTo(ProtocolVersion.MINECRAFT_1_9) >= 0) {
this.server.getBossBarManager().addBossBar(this, bar);
}
}
@Override
@ -317,6 +360,11 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player {
this.profile = profile.withProperties(properties);
}
@Override
public void clearHeaderAndFooter() {
tabList.clearHeaderAndFooter();
}
@Override
public VelocityTabList getTabList() {
return tabList;
@ -412,7 +460,8 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player {
logger.error("{}: kicked from server {}: {}", this, server.getServerInfo().getName(),
plainTextReason);
handleConnectionException(server, disconnectReason, Component.text()
.append(messages.getKickPrefix(server.getServerInfo().getName()))
.append(messages.getKickPrefix(server.getServerInfo().getName())
.colorIfAbsent(NamedTextColor.RED))
.color(NamedTextColor.RED)
.append(disconnectReason)
.build(), safe);
@ -420,8 +469,8 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player {
logger.error("{}: disconnected while connecting to {}: {}", this,
server.getServerInfo().getName(), plainTextReason);
handleConnectionException(server, disconnectReason, Component.text()
.append(messages.getDisconnectPrefix(server.getServerInfo().getName()))
.color(NamedTextColor.RED)
.append(messages.getDisconnectPrefix(server.getServerInfo().getName())
.colorIfAbsent(NamedTextColor.RED))
.append(disconnectReason)
.build(), safe);
}
@ -678,7 +727,9 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player {
public void sendResourcePack(String url) {
Preconditions.checkNotNull(url, "url");
connection.write(new ClientboundResourcePackRequestPacket(url, ""));
if (this.getProtocolVersion().compareTo(ProtocolVersion.MINECRAFT_1_8) >= 0) {
connection.write(new ClientboundResourcePackRequestPacket(url, ""));
}
}
@Override
@ -687,7 +738,9 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player {
Preconditions.checkNotNull(hash, "hash");
Preconditions.checkArgument(hash.length == 20, "Hash length is not 20");
connection.write(new ClientboundResourcePackRequestPacket(url, ByteBufUtil.hexDump(hash)));
if (this.getProtocolVersion().compareTo(ProtocolVersion.MINECRAFT_1_8) >= 0) {
connection.write(new ClientboundResourcePackRequestPacket(url, ByteBufUtil.hexDump(hash)));
}
}
/**

Datei anzeigen

@ -36,6 +36,7 @@ import io.netty.buffer.ByteBuf;
import java.net.InetSocketAddress;
import java.security.GeneralSecurityException;
import java.security.KeyPair;
import java.security.MessageDigest;
import java.util.Arrays;
import java.util.Optional;
import java.util.UUID;
@ -90,7 +91,7 @@ public class LoginSessionHandler implements MinecraftSessionHandler {
try {
KeyPair serverKeyPair = server.getServerKeyPair();
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.");
}

Datei anzeigen

@ -16,6 +16,8 @@ import io.netty.handler.codec.CorruptedFrameException;
import io.netty.handler.codec.DecoderException;
import io.netty.handler.codec.EncoderException;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
@ -227,11 +229,12 @@ public enum ProtocolUtils {
/**
* Reads a {@link net.kyori.adventure.nbt.CompoundBinaryTag} from the {@code buf}.
* @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
*/
public static CompoundBinaryTag readCompoundTag(ByteBuf buf) {
public static CompoundBinaryTag readCompoundTag(ByteBuf buf, BinaryTagIO.Reader reader) {
try {
return BinaryTagIO.readDataInput(new ByteBufInputStream(buf));
return reader.read((DataInput) new ByteBufInputStream(buf));
} catch (IOException thrown) {
throw new DecoderException(
"Unable to parse NBT CompoundTag, full error: " + thrown.getMessage());
@ -245,7 +248,7 @@ public enum ProtocolUtils {
*/
public static void writeCompoundTag(ByteBuf buf, CompoundBinaryTag compoundTag) {
try {
BinaryTagIO.writeDataOutput(compoundTag, new ByteBufOutputStream(buf));
BinaryTagIO.writer().write(compoundTag, (DataOutput) new ByteBufOutputStream(buf));
} catch (IOException e) {
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.serialization.brigadier.ArgumentPropertyRegistry;
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.Object2IntMap;
import java.util.ArrayDeque;
@ -99,7 +100,8 @@ public class ClientboundAvailableCommandsPacket implements Packet {
public void encode(ByteBuf buf, PacketDirection direction, ProtocolVersion protocolVersion) {
// Assign all the children an index.
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()) {
CommandNode<CommandSource> child = childrenQueue.poll();
if (!idMappings.containsKey(child)) {
@ -207,6 +209,7 @@ public class ClientboundAvailableCommandsPacket implements Packet {
private final int redirectTo;
private final @Nullable ArgumentBuilder<CommandSource, ?> args;
private @MonotonicNonNull CommandNode<CommandSource> built;
private boolean validated;
private WireNode(int idx, byte flags, int[] children, int redirectTo,
@Nullable ArgumentBuilder<CommandSource, ?> args) {
@ -215,18 +218,34 @@ public class ClientboundAvailableCommandsPacket implements Packet {
this.children = children;
this.redirectTo = redirectTo;
this.args = args;
this.validated = false;
}
void validate(WireNode[] wireNodes) {
// Ensure all children exist. Note that we delay checking if the node has been built yet;
// that needs to come after this node is built.
for (int child : children) {
if (child < 0 || child >= wireNodes.length) {
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.built == null) {
// Ensure all children exist. Note that we delay checking if the node has been built yet;
// that needs to come after this node is built.
for (int child : children) {
if (child >= wireNodes.length) {
throw new IllegalStateException("Node points to non-existent index " + redirectTo);
}
}
if (!this.validated) {
this.validate(wireNodes);
}
if (this.built == null) {
int type = flags & FLAG_NODE_TYPE;
if (type == NODE_TYPE_ROOT) {
this.built = new RootCommandNode<>();
@ -237,10 +256,6 @@ public class ClientboundAvailableCommandsPacket implements Packet {
// Add any redirects
if (redirectTo != -1) {
if (redirectTo >= wireNodes.length) {
throw new IllegalStateException("Node points to non-existent index " + redirectTo);
}
WireNode redirect = wireNodes[redirectTo];
if (redirect.built != null) {
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.PacketReader;
import io.netty.buffer.ByteBuf;
import net.kyori.adventure.nbt.BinaryTagIO;
import net.kyori.adventure.nbt.BinaryTagTypes;
import net.kyori.adventure.nbt.CompoundBinaryTag;
import net.kyori.adventure.nbt.ListBinaryTag;
@ -20,6 +21,7 @@ import org.checkerframework.checker.nullness.qual.Nullable;
public class ClientboundJoinGamePacket implements Packet {
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 short gamemode;
private int dimension;
@ -37,14 +39,136 @@ public class ClientboundJoinGamePacket implements Packet {
private short previousGamemode; // 1.16+
private CompoundBinaryTag biomeRegistry; // 1.16.2+
public void withDimension(int dimension) {
this.dimension = dimension;
public int getEntityId() {
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) {
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
public void decode(ByteBuf buf, PacketDirection direction, ProtocolVersion version) {
this.entityId = buf.readInt();
@ -61,7 +185,7 @@ public class ClientboundJoinGamePacket implements Packet {
if (version.gte(ProtocolVersion.MINECRAFT_1_16)) {
this.previousGamemode = buf.readByte();
ImmutableSet<String> levelNames = ImmutableSet.copyOf(ProtocolUtils.readStringArray(buf));
CompoundBinaryTag registryContainer = ProtocolUtils.readCompoundTag(buf);
CompoundBinaryTag registryContainer = ProtocolUtils.readCompoundTag(buf, JOINGAME_READER);
ListBinaryTag dimensionRegistryContainer = null;
if (version.gte(ProtocolVersion.MINECRAFT_1_16_2)) {
dimensionRegistryContainer = registryContainer.getCompound("minecraft:dimension_type")
@ -74,8 +198,8 @@ public class ClientboundJoinGamePacket implements Packet {
ImmutableSet<DimensionData> readData =
DimensionRegistry.fromGameData(dimensionRegistryContainer, version);
this.dimensionRegistry = new DimensionRegistry(readData, levelNames);
if (version.gte(ProtocolVersion.MINECRAFT_1_16_2)) {
CompoundBinaryTag currentDimDataTag = ProtocolUtils.readCompoundTag(buf);
if (version.compareTo(ProtocolVersion.MINECRAFT_1_16_2) >= 0) {
CompoundBinaryTag currentDimDataTag = ProtocolUtils.readCompoundTag(buf, JOINGAME_READER);
dimensionIdentifier = ProtocolUtils.readString(buf);
this.currentDimensionData = DimensionData.decodeBaseCompoundTag(currentDimDataTag, version)
.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
public boolean handle(PacketHandler handler) {
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.PacketReader;
import io.netty.buffer.ByteBuf;
import net.kyori.adventure.nbt.BinaryTagIO;
import net.kyori.adventure.nbt.CompoundBinaryTag;
public class ClientboundRespawnPacket implements Packet {
@ -102,9 +103,9 @@ public class ClientboundRespawnPacket implements Packet {
public void decode(ByteBuf buf, PacketDirection direction, ProtocolVersion version) {
String dimensionIdentifier = null;
String levelName = null;
if (version.gte(ProtocolVersion.MINECRAFT_1_16)) {
if (version.gte(ProtocolVersion.MINECRAFT_1_16_2)) {
CompoundBinaryTag dimDataTag = ProtocolUtils.readCompoundTag(buf);
if (version.compareTo(ProtocolVersion.MINECRAFT_1_16) >= 0) {
if (version.compareTo(ProtocolVersion.MINECRAFT_1_16_2) >= 0) {
CompoundBinaryTag dimDataTag = ProtocolUtils.readCompoundTag(buf, BinaryTagIO.reader());
dimensionIdentifier = ProtocolUtils.readString(buf);
this.currentDimensionData = DimensionData.decodeBaseCompoundTag(dimDataTag, version)
.annotateWith(dimensionIdentifier, null);

Datei anzeigen

@ -20,7 +20,7 @@ public class MinecraftCompressDecoder extends MessageToMessageDecoder<ByteBuf> {
Boolean.getBoolean("velocity.increased-compression-cap")
? HARD_MAXIMUM_UNCOMPRESSED_SIZE : VANILLA_MAXIMUM_UNCOMPRESSED_SIZE;
private final int threshold;
private int threshold;
private final 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 {
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> {
private final int threshold;
private int threshold;
private final VelocityCompressor compressor;
public MinecraftCompressEncoder(int threshold, VelocityCompressor compressor) {
@ -20,7 +20,7 @@ public class MinecraftCompressEncoder extends MessageToByteEncoder<ByteBuf> {
@Override
protected void encode(ChannelHandlerContext ctx, ByteBuf msg, ByteBuf out) throws Exception {
int uncompressed = msg.readableBytes();
if (uncompressed <= threshold) {
if (uncompressed < threshold) {
// Under the threshold, there is nothing to do.
ProtocolUtils.writeVarInt(out, 0);
out.writeBytes(msg);
@ -54,4 +54,8 @@ public class MinecraftCompressEncoder extends MessageToByteEncoder<ByteBuf> {
public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
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 static final boolean DEBUG = Boolean.getBoolean("velocity.packet-decode-logging");
private static final QuietDecoderException DECODE_FAILED =
new QuietDecoderException("A packet did not decode successfully (invalid data). If you are a "
private static final QuietRuntimeException DECODE_FAILED =
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.");
private final PacketDirection direction;
@ -64,8 +64,16 @@ public class MinecraftDecoder extends ChannelInboundHandlerAdapter {
ctx.fireChannelRead(buf);
} else {
try {
doLengthSanityChecks(buf, packet);
try {
packet.decode(buf, direction, registry.version);
} catch (Exception e) {
throw handleDecodeFailure(e, packet, packetId);
}
if (buf.isReadable()) {
throw handleNotReadEnough(packet, packetId);
throw handleOverflow(packet, buf.readerIndex(), buf.writerIndex());
}
ctx.fireChannelRead(packet);
} 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) {
return new CorruptedFrameException("Did not read full packet for " + packet.getClass() + " "
+ getExtraConnectionDetail(packetId));
return new CorruptedFrameException("Packet sent for " + packet.getClass() + " was too "
+ "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 {
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.Multimaps;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import com.velocitypowered.api.plugin.PluginContainer;
import com.velocitypowered.api.plugin.PluginManager;
import com.velocitypowered.api.scheduler.ScheduledTask;
import com.velocitypowered.api.scheduler.Scheduler;
@ -183,8 +184,18 @@ public class VelocityScheduler implements Scheduler {
currentTaskThread = Thread.currentThread();
try {
runnable.run();
} catch (Exception e) {
Log.logger.error("Exception in task {} by plugin {}", runnable, plugin, e);
} catch (Throwable 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 {
if (repeat == 0) {
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 data the data
* @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) {
for (ConnectedPlayer player : players.values()) {
VelocityServerConnection connection = player.getConnectedServer();
if (connection != null && connection.getServerInfo().equals(serverInfo)) {
if (connection != null && connection.getServer() == this) {
return connection.sendPluginMessage(identifier, data);
}
}
data.release();
return false;
}

Datei anzeigen

@ -21,16 +21,17 @@ import org.checkerframework.checker.nullness.qual.Nullable;
public class VelocityTabList implements TabList {
protected final ConnectedPlayer player;
protected final MinecraftConnection connection;
protected final Map<UUID, VelocityTabListEntry> entries = new ConcurrentHashMap<>();
public VelocityTabList(MinecraftConnection connection) {
this.connection = connection;
public VelocityTabList(final ConnectedPlayer player) {
this.player = player;
this.connection = player.getConnection();
}
@Override
public void setHeaderAndFooter(net.kyori.adventure.text.Component header,
net.kyori.adventure.text.Component footer) {
public void setHeaderAndFooter(Component header, Component footer) {
Preconditions.checkNotNull(header, "header");
Preconditions.checkNotNull(footer, "footer");
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
# 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
# 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.
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 = ""
# Announce whether or not your server supports Forge. If you run a modded server, we
@ -69,7 +69,7 @@ lobby = "127.0.0.1:30066"
factions = "127.0.0.1:30067"
minigames = "127.0.0.1:30068"
# In what order we should try servers when a player logs in or is kicked from aserver.
# In what order we should try servers when a player logs in or is kicked from a server.
try = [
"lobby"
]
@ -105,8 +105,9 @@ connection-timeout = 5000
# Specify a read timeout for connections here. The default is 30 seconds.
read-timeout = 30000
# Enables compatibility with HAProxy.
proxy-protocol = false
# Enables compatibility with HAProxy's PROXY protocol. If you don't know what this is for, then
# don't enable it.
haproxy-protocol = false
# Enables TCP fast open support on the proxy. Requires the proxy to run on Linux.
tcp-fast-open = false
@ -142,19 +143,6 @@ map = "Velocity"
# Whether plugins should be shown in query response by default or not
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.
[messages]
# Prefix when the player gets kicked from a server.
@ -169,4 +157,4 @@ online-mode-only = "&cThis server only accepts connections from online-mode clie
no-available-servers = "&cThere are no available servers."
already-connected = "&cYou are already connected to this proxy!"
moved-to-new-server-prefix = "&cThe server you were on kicked you: "
generic-connection-error = "&cAn internal error occurred in your connection."
generic-connection-error = "&cAn internal error occurred in your connection."

Datei anzeigen

@ -24,6 +24,7 @@ import com.velocitypowered.api.command.SimpleCommand;
import com.velocitypowered.proxy.event.MockEventManager;
import com.velocitypowered.proxy.event.VelocityEventManager;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import org.junit.jupiter.api.Test;
@ -423,6 +424,54 @@ public class CommandManagerTests {
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 {
@Override
public void execute(final Invocation invocation) {