3
0
Mirror von https://github.com/PaperMC/Velocity.git synchronisiert 2024-11-16 21:10:30 +01:00

Merge branch 'dev/3.0.0' into dev/resource-packs

Dieser Commit ist enthalten in:
Shane Freeder 2024-01-17 11:51:18 +00:00
Commit 2d2747ea96
Es konnte kein GPG-Schlüssel zu dieser Signatur gefunden werden
GPG-Schlüssel-ID: A3F61EA5A085289C
37 geänderte Dateien mit 780 neuen und 236 gelöschten Zeilen

Datei anzeigen

@ -61,7 +61,8 @@ tasks {
"https://guava.dev/releases/${libs.guava.get().version}/api/docs/",
"https://google.github.io/guice/api-docs/${libs.guice.get().version}/javadoc/",
"https://docs.oracle.com/en/java/javase/17/docs/api/",
"https://jd.advntr.dev/api/${libs.adventure.bom.get().version}/",
//"https://jd.advntr.dev/api/${libs.adventure.bom.get().version}/",
"https://jd.advntr.dev/api/4.14.0/",
"https://javadoc.io/doc/com.github.ben-manes.caffeine/caffeine"
)

Datei anzeigen

@ -8,8 +8,11 @@
package com.velocitypowered.api.command;
import com.google.common.base.Preconditions;
import com.mojang.brigadier.arguments.ArgumentType;
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
import com.mojang.brigadier.builder.RequiredArgumentBuilder;
import com.mojang.brigadier.tree.LiteralCommandNode;
import org.jetbrains.annotations.NotNull;
/**
* A command that uses Brigadier for parsing the command and
@ -31,7 +34,7 @@ public final class BrigadierCommand implements Command {
*
* @param builder the {@link LiteralCommandNode} builder
*/
public BrigadierCommand(final LiteralArgumentBuilder<CommandSource> builder) {
public BrigadierCommand(final @NotNull LiteralArgumentBuilder<CommandSource> builder) {
this(Preconditions.checkNotNull(builder, "builder").build());
}
@ -40,7 +43,7 @@ public final class BrigadierCommand implements Command {
*
* @param node the command node
*/
public BrigadierCommand(final LiteralCommandNode<CommandSource> node) {
public BrigadierCommand(final @NotNull LiteralCommandNode<CommandSource> node) {
this.node = Preconditions.checkNotNull(node, "node");
}
@ -52,4 +55,34 @@ public final class BrigadierCommand implements Command {
public LiteralCommandNode<CommandSource> getNode() {
return node;
}
/**
* Creates a new LiteralArgumentBuilder of the required name.
*
* @param name the literal name.
* @return a new LiteralArgumentBuilder.
*/
public static LiteralArgumentBuilder<CommandSource> literalArgumentBuilder(
final @NotNull String name) {
Preconditions.checkNotNull(name, "name");
// Validation to avoid beginner's errors in case someone includes a space in the argument name
Preconditions.checkArgument(name.indexOf(' ') == -1, "the argument name cannot contain spaces");
return LiteralArgumentBuilder.literal(name);
}
/**
* Creates a new RequiredArgumentBuilder of the required name and type.
*
* @param name the argument name
* @param argumentType the argument type required
* @param <T> the ArgumentType required type
* @return a new RequiredArgumentBuilder
*/
public static <T> RequiredArgumentBuilder<CommandSource, T> requiredArgumentBuilder(
final @NotNull String name, @NotNull final ArgumentType<T> argumentType) {
Preconditions.checkNotNull(name, "name");
Preconditions.checkNotNull(argumentType, "argument type");
return RequiredArgumentBuilder.argument(name, argumentType);
}
}

Datei anzeigen

@ -11,7 +11,8 @@ shadow = "com.github.johnrengelman.shadow:8.1.0"
spotless = "com.diffplug.spotless:6.12.0"
[libraries]
adventure-bom = "net.kyori:adventure-bom:4.14.0"
# See JD links in velocity-apo when moving to non-snapshot versions
adventure-bom = "net.kyori:adventure-bom:4.15.0-SNAPSHOT"
adventure-facet = "net.kyori:adventure-platform-facet:4.3.0"
asm = "org.ow2.asm:asm:9.5"
asynchttpclient = "org.asynchttpclient:async-http-client:2.12.3"
@ -51,7 +52,7 @@ netty-codec-http = { module = "io.netty:netty-codec-http", version.ref = "netty"
netty-handler = { module = "io.netty:netty-handler", version.ref = "netty" }
netty-transport-native-epoll = { module = "io.netty:netty-transport-native-epoll", version.ref = "netty" }
netty-transport-native-kqueue = { module = "io.netty:netty-transport-native-kqueue", version.ref = "netty" }
nightconfig = "com.electronwill.night-config:toml:3.6.6"
nightconfig = "com.electronwill.night-config:toml:3.6.7"
slf4j = "org.slf4j:slf4j-api:2.0.7"
snakeyaml = "org.yaml:snakeyaml:1.33"
spotbugs-annotations = "com.github.spotbugs:spotbugs-annotations:4.7.3"

Datei anzeigen

@ -96,6 +96,7 @@ tasks {
dependencies {
implementation(project(":velocity-api"))
implementation(project(":velocity-native"))
implementation(project(":velocity-proxy-log4j2-plugin"))
implementation(libs.bundles.log4j)
implementation(libs.kyori.ansi)

Datei anzeigen

@ -0,0 +1,4 @@
dependencies {
implementation(libs.bundles.log4j)
annotationProcessor(libs.log4j.core)
}

Datei anzeigen

@ -0,0 +1,80 @@
/*
* Copyright (C) 2023 Velocity Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.velocitypowered.proxy.util;
import java.util.List;
import java.util.regex.Pattern;
import org.apache.logging.log4j.core.LogEvent;
import org.apache.logging.log4j.core.config.Configuration;
import org.apache.logging.log4j.core.config.plugins.Plugin;
import org.apache.logging.log4j.core.layout.PatternLayout;
import org.apache.logging.log4j.core.pattern.ConverterKeys;
import org.apache.logging.log4j.core.pattern.LogEventPatternConverter;
import org.apache.logging.log4j.core.pattern.PatternConverter;
import org.apache.logging.log4j.core.pattern.PatternFormatter;
import org.apache.logging.log4j.core.pattern.PatternParser;
/**
* Strip Format Converter.
* Based on <a href="https://github.com/PaperMC/Paper/pull/9313/files#diff-6c1396d60730e7053f0b761bdb487752467c9bc0444a4aac41908376b38a56bdR198-R233">Paper's patch</a>
*/
@Plugin(name = "stripAnsi", category = PatternConverter.CATEGORY)
@ConverterKeys("stripAnsi")
public class StripAnsiConverter extends LogEventPatternConverter {
private static final Pattern ANSI_PATTERN = Pattern.compile("\u001B\\[[;\\d]*m");
private final List<PatternFormatter> formatters;
/**
* Constructs an instance of StripAnsiConverter.
*/
protected StripAnsiConverter(List<PatternFormatter> formatters) {
super("stripAnsi", null);
this.formatters = formatters;
}
@Override
public void format(final LogEvent event, final StringBuilder toAppendTo) {
int start = toAppendTo.length();
for (final PatternFormatter formatter : formatters) {
formatter.format(event, toAppendTo);
}
String content = toAppendTo.substring(start);
content = ANSI_PATTERN.matcher(content).replaceAll("");
toAppendTo.setLength(start);
toAppendTo.append(content);
}
/**
* Creates a new Instance of this Converter.
*
* @param config the configuration
* @param options the options
* @return a new instance
*/
public static StripAnsiConverter newInstance(Configuration config, String[] options) {
if (options.length != 1) {
LOGGER.error("Incorrect number of options on stripFormat. Expected 1 received "
+ options.length);
return null;
}
PatternParser parser = PatternLayout.createPatternParser(config);
List<PatternFormatter> formatters = parser.parse(options[0]);
return new StripAnsiConverter(formatters);
}
}

Datei anzeigen

@ -20,6 +20,7 @@ package com.velocitypowered.proxy;
import io.netty.util.ResourceLeakDetector;
import io.netty.util.ResourceLeakDetector.Level;
import java.text.DecimalFormat;
import java.util.concurrent.TimeUnit;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
@ -63,14 +64,14 @@ public class Velocity {
return;
}
long startTime = System.currentTimeMillis();
long startTime = System.nanoTime();
VelocityServer server = new VelocityServer(options);
server.start();
Runtime.getRuntime().addShutdownHook(new Thread(() -> server.shutdown(false),
"Shutdown thread"));
double bootTime = (System.currentTimeMillis() - startTime) / 1000d;
double bootTime = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime) / 1000d;
logger.info("Done ({}s)!", new DecimalFormat("#.##").format(bootTime));
server.getConsoleCommandSource().start();

Datei anzeigen

@ -116,16 +116,28 @@ public class VelocityServer implements ProxyServer, ForwardingAudience {
.registerTypeHierarchyAdapter(Favicon.class, FaviconSerializer.INSTANCE)
.registerTypeHierarchyAdapter(GameProfile.class, GameProfileSerializer.INSTANCE)
.create();
private static final Gson PRE_1_16_PING_SERIALIZER = ProtocolUtils
.getJsonChatSerializer(ProtocolVersion.MINECRAFT_1_15_2)
.serializer()
.newBuilder()
private static final Gson PRE_1_16_PING_SERIALIZER = new GsonBuilder()
.registerTypeHierarchyAdapter(
Component.class,
ProtocolUtils.getJsonChatSerializer(ProtocolVersion.MINECRAFT_1_15_2)
.serializer().getAdapter(Component.class)
)
.registerTypeHierarchyAdapter(Favicon.class, FaviconSerializer.INSTANCE)
.create();
private static final Gson POST_1_16_PING_SERIALIZER = ProtocolUtils
.getJsonChatSerializer(ProtocolVersion.MINECRAFT_1_16)
.serializer()
.newBuilder()
private static final Gson PRE_1_20_3_PING_SERIALIZER = new GsonBuilder()
.registerTypeHierarchyAdapter(
Component.class,
ProtocolUtils.getJsonChatSerializer(ProtocolVersion.MINECRAFT_1_20_2)
.serializer().getAdapter(Component.class)
)
.registerTypeHierarchyAdapter(Favicon.class, FaviconSerializer.INSTANCE)
.create();
private static final Gson MODERN_PING_SERIALIZER = new GsonBuilder()
.registerTypeHierarchyAdapter(
Component.class,
ProtocolUtils.getJsonChatSerializer(ProtocolVersion.MINECRAFT_1_20_3)
.serializer().getAdapter(Component.class)
)
.registerTypeHierarchyAdapter(Favicon.class, FaviconSerializer.INSTANCE)
.create();
@ -762,8 +774,11 @@ public class VelocityServer implements ProxyServer, ForwardingAudience {
*/
public static Gson getPingGsonInstance(ProtocolVersion version) {
if (version == ProtocolVersion.UNKNOWN
|| version.compareTo(ProtocolVersion.MINECRAFT_1_16) >= 0) {
return POST_1_16_PING_SERIALIZER;
|| version.compareTo(ProtocolVersion.MINECRAFT_1_20_3) >= 0) {
return MODERN_PING_SERIALIZER;
}
if (version.compareTo(ProtocolVersion.MINECRAFT_1_16) >= 0) {
return PRE_1_20_3_PING_SERIALIZER;
}
return PRE_1_16_PING_SERIALIZER;
}

Datei anzeigen

@ -26,6 +26,10 @@ import com.google.common.collect.ImmutableMap;
import com.google.gson.annotations.Expose;
import com.velocitypowered.api.proxy.config.ProxyConfig;
import com.velocitypowered.api.util.Favicon;
import com.velocitypowered.proxy.config.migration.ConfigurationMigration;
import com.velocitypowered.proxy.config.migration.ForwardingMigration;
import com.velocitypowered.proxy.config.migration.KeyAuthenticationMigration;
import com.velocitypowered.proxy.config.migration.MotdMigration;
import com.velocitypowered.proxy.util.AddressUtil;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import java.io.IOException;
@ -42,8 +46,6 @@ import java.util.Map;
import java.util.Optional;
import java.util.Random;
import net.kyori.adventure.text.minimessage.MiniMessage;
import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer;
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
@ -59,7 +61,7 @@ public class VelocityConfiguration implements ProxyConfig {
@Expose
private String bind = "0.0.0.0:25577";
@Expose
private String motd = "&3A Velocity Server";
private String motd = "<aqua>A Velocity Server";
@Expose
private int showMaxPlayers = 500;
@Expose
@ -353,7 +355,7 @@ public class VelocityConfiguration implements ProxyConfig {
}
public boolean useTcpFastOpen() {
return advanced.tcpFastOpen;
return advanced.isTcpFastOpen();
}
public Metrics getMetrics() {
@ -433,171 +435,115 @@ public class VelocityConfiguration implements ProxyConfig {
}
// Create the forwarding-secret file on first-time startup if it doesn't exist
Path defaultForwardingSecretPath = Path.of("forwarding.secret");
final Path defaultForwardingSecretPath = Path.of("forwarding.secret");
if (Files.notExists(path) && Files.notExists(defaultForwardingSecretPath)) {
Files.writeString(defaultForwardingSecretPath, generateRandomString(12));
}
boolean mustResave = false;
CommentedFileConfig config = CommentedFileConfig.builder(path)
.defaultData(defaultConfigLocation)
.autosave()
.preserveInsertionOrder()
.sync()
.build();
config.load();
try (final CommentedFileConfig config = CommentedFileConfig.builder(path)
.defaultData(defaultConfigLocation)
.autosave()
.preserveInsertionOrder()
.sync()
.build()
) {
config.load();
// TODO: migrate this on Velocity Polymer
double configVersion;
try {
configVersion = Double.parseDouble(config.getOrElse("config-version", "1.0"));
} catch (NumberFormatException e) {
configVersion = 1.0;
}
final ConfigurationMigration[] migrations = {
new ForwardingMigration(),
new KeyAuthenticationMigration(),
new MotdMigration()
};
// Whether or not this config version is older than 2.0 which uses the deprecated
// "forwarding-secret" parameter
boolean legacyConfig = configVersion < 2.0;
String forwardingSecretString;
byte[] forwardingSecret;
// Handle the previous (version 1.0) config
// There is duplicate/old code here in effort to make the future commit which abandons legacy
// config handling easier to implement. All that would be required is removing the if statement
// here and keeping the contents of the else block (with slight tidying).
if (legacyConfig) {
logger.warn(
"You are currently using a deprecated configuration version. The \"forwarding-secret\""
+ " parameter is a security hazard and was removed in config version 2.0."
+ " You should rename your current \"velocity.toml\" to something else to allow"
+ " Velocity to generate a config file for the new version. You may then configure "
+ " that file as you normally would. The only differences are the config-version "
+ "and \"forwarding-secret\" has been replaced by \"forwarding-secret-file\".");
// Default legacy handling
forwardingSecretString = System.getenv()
.getOrDefault("VELOCITY_FORWARDING_SECRET", config.get("forwarding-secret"));
if (forwardingSecretString == null || forwardingSecretString.isEmpty()) {
forwardingSecretString = generateRandomString(12);
config.set("forwarding-secret", forwardingSecretString);
mustResave = true;
for (final ConfigurationMigration migration : migrations) {
if (migration.shouldMigrate(config)) {
migration.migrate(config, logger);
}
}
} else {
// New handling
forwardingSecretString = System.getenv().getOrDefault("VELOCITY_FORWARDING_SECRET", "");
String forwardingSecretString = System.getenv().getOrDefault(
"VELOCITY_FORWARDING_SECRET", "");
if (forwardingSecretString.isEmpty()) {
String forwardSecretFile = config.get("forwarding-secret-file");
Path secretPath = forwardSecretFile == null
? defaultForwardingSecretPath
: Path.of(forwardSecretFile);
final String forwardSecretFile = config.get("forwarding-secret-file");
final Path secretPath = forwardSecretFile == null
? defaultForwardingSecretPath
: Path.of(forwardSecretFile);
if (Files.exists(secretPath)) {
if (Files.isRegularFile(secretPath)) {
forwardingSecretString = String.join("", Files.readAllLines(secretPath));
} else {
throw new RuntimeException(
"The file " + forwardSecretFile + " is not a valid file or it is a directory.");
"The file " + forwardSecretFile + " is not a valid file or it is a directory.");
}
} else {
throw new RuntimeException("The forwarding-secret-file does not exist.");
}
}
}
forwardingSecret = forwardingSecretString.getBytes(StandardCharsets.UTF_8);
final byte[] forwardingSecret = forwardingSecretString.getBytes(StandardCharsets.UTF_8);
final String motd = config.getOrElse("motd", "<#09add3>A Velocity Server");
if (configVersion == 1.0 || configVersion == 2.0) {
config.set("force-key-authentication", config.getOrElse("force-key-authentication", true));
config.setComment("force-key-authentication",
"Should the proxy enforce the new public key security standard? By default, this is on.");
config.set("config-version", configVersion == 2.0 ? "2.5" : "1.5");
mustResave = true;
}
// Read the rest of the config
final CommentedConfig serversConfig = config.get("servers");
final CommentedConfig forcedHostsConfig = config.get("forced-hosts");
final CommentedConfig advancedConfig = config.get("advanced");
final CommentedConfig queryConfig = config.get("query");
final CommentedConfig metricsConfig = config.get("metrics");
final PlayerInfoForwarding forwardingMode = config.getEnumOrElse(
"player-info-forwarding-mode", PlayerInfoForwarding.NONE);
final PingPassthroughMode pingPassthroughMode = config.getEnumOrElse("ping-passthrough",
PingPassthroughMode.DISABLED);
String motd = config.getOrElse("motd", "<#09add3>A Velocity Server");
final String bind = config.getOrElse("bind", "0.0.0.0:25577");
final int maxPlayers = config.getIntOrElse("show-max-players", 500);
final boolean onlineMode = config.getOrElse("online-mode", true);
final boolean forceKeyAuthentication = config.getOrElse("force-key-authentication", true);
final boolean announceForge = config.getOrElse("announce-forge", true);
final boolean preventClientProxyConnections = config.getOrElse(
"prevent-client-proxy-connections", true);
final boolean kickExisting = config.getOrElse("kick-existing-players", false);
final boolean enablePlayerAddressLogging = config.getOrElse(
"enable-player-address-logging", true);
// Old MOTD Migration
if (configVersion < 2.6) {
final String migratedMotd;
// JSON Format Migration
if (motd.strip().startsWith("{")) {
migratedMotd = MiniMessage.miniMessage().serialize(
GsonComponentSerializer.gson().deserialize(motd))
.replace("\\", "");
} else {
// Legacy '&' Format Migration
migratedMotd = MiniMessage.miniMessage().serialize(
LegacyComponentSerializer.legacyAmpersand().deserialize(motd));
// Throw an exception if the forwarding-secret file is empty and the proxy is using a
// forwarding mode that requires it.
if (forwardingSecret.length == 0
&& (forwardingMode == PlayerInfoForwarding.MODERN
|| forwardingMode == PlayerInfoForwarding.BUNGEEGUARD)) {
throw new RuntimeException("The forwarding-secret file must not be empty.");
}
config.set("motd", migratedMotd);
motd = migratedMotd;
config.setComment("motd",
" What should be the MOTD? This gets displayed when the player adds your server to\n"
+ " their server list. Only MiniMessage format is accepted.");
config.set("config-version", "2.6");
mustResave = true;
return new VelocityConfiguration(
bind,
motd,
maxPlayers,
onlineMode,
preventClientProxyConnections,
announceForge,
forwardingMode,
forwardingSecret,
kickExisting,
pingPassthroughMode,
enablePlayerAddressLogging,
new Servers(serversConfig),
new ForcedHosts(forcedHostsConfig),
new Advanced(advancedConfig),
new Query(queryConfig),
new Metrics(metricsConfig),
forceKeyAuthentication
);
}
// Handle any cases where the config needs to be saved again
if (mustResave) {
config.save();
}
// Read the rest of the config
CommentedConfig serversConfig = config.get("servers");
CommentedConfig forcedHostsConfig = config.get("forced-hosts");
CommentedConfig advancedConfig = config.get("advanced");
CommentedConfig queryConfig = config.get("query");
CommentedConfig metricsConfig = config.get("metrics");
PlayerInfoForwarding forwardingMode = config.getEnumOrElse("player-info-forwarding-mode",
PlayerInfoForwarding.NONE);
PingPassthroughMode pingPassthroughMode = config.getEnumOrElse("ping-passthrough",
PingPassthroughMode.DISABLED);
String bind = config.getOrElse("bind", "0.0.0.0:25577");
int maxPlayers = config.getIntOrElse("show-max-players", 500);
Boolean onlineMode = config.getOrElse("online-mode", true);
Boolean forceKeyAuthentication = config.getOrElse("force-key-authentication", true);
Boolean announceForge = config.getOrElse("announce-forge", true);
Boolean preventClientProxyConnections = config.getOrElse("prevent-client-proxy-connections",
true);
Boolean kickExisting = config.getOrElse("kick-existing-players", false);
Boolean enablePlayerAddressLogging = config.getOrElse("enable-player-address-logging", true);
// Throw an exception if the forwarding-secret file is empty and the proxy is using a
// forwarding mode that requires it.
if (forwardingSecret.length == 0
&& (forwardingMode == PlayerInfoForwarding.MODERN
|| forwardingMode == PlayerInfoForwarding.BUNGEEGUARD)) {
throw new RuntimeException("The forwarding-secret file must not be empty.");
}
return new VelocityConfiguration(
bind,
motd,
maxPlayers,
onlineMode,
preventClientProxyConnections,
announceForge,
forwardingMode,
forwardingSecret,
kickExisting,
pingPassthroughMode,
enablePlayerAddressLogging,
new Servers(serversConfig),
new ForcedHosts(forcedHostsConfig),
new Advanced(advancedConfig),
new Query(queryConfig),
new Metrics(metricsConfig),
forceKeyAuthentication
);
}
private static String generateRandomString(int length) {
String chars = "AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz1234567890";
StringBuilder builder = new StringBuilder();
Random rnd = new SecureRandom();
/**
* Generates a Random String.
*
* @param length the required string size.
* @return a new random string.
*/
public static String generateRandomString(int length) {
final String chars = "AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz1234567890";
final StringBuilder builder = new StringBuilder();
final Random rnd = new SecureRandom();
for (int i = 0; i < length; i++) {
builder.append(chars.charAt(rnd.nextInt(chars.length())));
}

Datei anzeigen

@ -0,0 +1,47 @@
/*
* Copyright (C) 2023 Velocity Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.velocitypowered.proxy.config.migration;
import com.electronwill.nightconfig.core.file.CommentedFileConfig;
import java.io.IOException;
import org.apache.logging.log4j.Logger;
/**
* Configuration Migration interface.
*/
public sealed interface ConfigurationMigration
permits ForwardingMigration, KeyAuthenticationMigration, MotdMigration {
boolean shouldMigrate(CommentedFileConfig config);
void migrate(CommentedFileConfig config, Logger logger) throws IOException;
/**
* Gets the configuration version.
*
* @param config the configuration.
* @return configuration version
*/
default double configVersion(CommentedFileConfig config) {
final String stringVersion = config.getOrElse("config-version", "1.0");
try {
return Double.parseDouble(stringVersion);
} catch (Exception e) {
return 1.0;
}
}
}

Datei anzeigen

@ -0,0 +1,65 @@
/*
* Copyright (C) 2023 Velocity Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.velocitypowered.proxy.config.migration;
import static com.velocitypowered.proxy.config.VelocityConfiguration.generateRandomString;
import com.electronwill.nightconfig.core.file.CommentedFileConfig;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import org.apache.logging.log4j.Logger;
/**
* Migrate old forwarding secret settings to modern version using an external file.
*/
public final class ForwardingMigration implements ConfigurationMigration {
@Override
public boolean shouldMigrate(final CommentedFileConfig config) {
return configVersion(config) < 2.0;
}
@Override
public void migrate(final CommentedFileConfig config, final Logger logger) throws IOException {
logger.warn("""
You are currently using a deprecated configuration version.
The "forwarding-secret" parameter is a security hazard and was removed in \
config version 2.0.
We will migrate your secret to the "forwarding.secret" file.""");
final String actualSecret = config.get("forwarding-secret");
final Path path = Path.of(config.getOrElse("forwarding-secret-file", "forwarding.secret"));
if (Files.exists(path)) {
final String fileContents = Files.readString(path);
if (fileContents.isBlank()) {
Files.writeString(path, actualSecret == null ? generateRandomString(12) : actualSecret);
}
} else {
Files.createFile(path);
Files.writeString(path, actualSecret == null ? generateRandomString(12) : actualSecret);
}
if (actualSecret != null) {
config.remove("forwarding-secret");
}
config.set("forwarding-secret-file", "forwarding.secret");
config.setComment("forwarding-secret-file", """
If you are using modern or BungeeGuard IP forwarding, \
configure a file that contains a unique secret here.
The file is expected to be UTF-8 encoded and not empty.""");
config.set("config-version", "2.0");
}
}

Datei anzeigen

@ -0,0 +1,41 @@
/*
* Copyright (C) 2023 Velocity Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.velocitypowered.proxy.config.migration;
import com.electronwill.nightconfig.core.file.CommentedFileConfig;
import org.apache.logging.log4j.Logger;
/**
* Creation of the configuration option "force-key-authentication".
*/
public final class KeyAuthenticationMigration implements ConfigurationMigration {
@Override
public boolean shouldMigrate(final CommentedFileConfig config) {
final double version = configVersion(config);
return version == 1.0 || version == 2.0;
}
@Override
public void migrate(final CommentedFileConfig config, final Logger logger) {
config.set("force-key-authentication", config.getOrElse("force-key-authentication", true));
config.setComment("force-key-authentication",
"Should the proxy enforce the new public key security standard? By default,"
+ " this is on.");
config.set("config-version", configVersion(config) == 2.0 ? "2.5" : "1.5");
}
}

Datei anzeigen

@ -0,0 +1,57 @@
/*
* Copyright (C) 2023 Velocity Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.velocitypowered.proxy.config.migration;
import com.electronwill.nightconfig.core.file.CommentedFileConfig;
import net.kyori.adventure.text.minimessage.MiniMessage;
import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer;
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
import org.apache.logging.log4j.Logger;
/**
* Migrates MOTD builtin configuration from legacy or json format to MiniMessage.
*/
public final class MotdMigration implements ConfigurationMigration {
@Override
public boolean shouldMigrate(final CommentedFileConfig config) {
return configVersion(config) < 2.6;
}
@Override
public void migrate(final CommentedFileConfig config, final Logger logger) {
final String oldMotd = config.getOrElse("motd", "<#09add3>A Velocity Server");
final String migratedMotd;
// JSON Format Migration
if (oldMotd.strip().startsWith("{")) {
migratedMotd = MiniMessage.miniMessage().serialize(
GsonComponentSerializer.gson().deserialize(oldMotd))
.replace("\\", "");
} else {
// Legacy '&' Format Migration
migratedMotd = MiniMessage.miniMessage().serialize(
LegacyComponentSerializer.legacyAmpersand().deserialize(oldMotd));
}
config.set("motd", migratedMotd);
config.setComment("motd",
" What should be the MOTD? This gets displayed when the player adds your server to\n"
+ " their server list. Only MiniMessage format is accepted.");
config.set("config-version", "2.6");
}
}

Datei anzeigen

@ -51,6 +51,7 @@ import com.velocitypowered.proxy.protocol.netty.PlayPacketQueueHandler;
import com.velocitypowered.proxy.util.except.QuietDecoderException;
import io.netty.buffer.ByteBuf;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
@ -224,12 +225,16 @@ public class MinecraftConnection extends ChannelInboundHandlerAdapter {
* Writes and immediately flushes a message to the connection.
*
* @param msg the message to write
*
* @return A {@link ChannelFuture} that will complete when packet is successfully sent
*/
public void write(Object msg) {
@Nullable
public ChannelFuture write(Object msg) {
if (channel.isActive()) {
channel.writeAndFlush(msg, channel.voidPromise());
return channel.writeAndFlush(msg, channel.newPromise());
} else {
ReferenceCountUtil.release(msg);
return null;
}
}
@ -362,8 +367,17 @@ public class MinecraftConnection extends ChannelInboundHandlerAdapter {
ensureInEventLoop();
this.state = state;
this.channel.pipeline().get(MinecraftEncoder.class).setState(state);
this.channel.pipeline().get(MinecraftDecoder.class).setState(state);
// If the connection is LEGACY (<1.6), the decoder and encoder are not set.
final MinecraftEncoder minecraftEncoder = this.channel.pipeline()
.get(MinecraftEncoder.class);
if (minecraftEncoder != null) {
minecraftEncoder.setState(state);
}
final MinecraftDecoder minecraftDecoder = this.channel.pipeline()
.get(MinecraftDecoder.class);
if (minecraftDecoder != null) {
minecraftDecoder.setState(state);
}
if (state == StateRegistry.CONFIG) {
// Activate the play packet queue

Datei anzeigen

@ -137,7 +137,7 @@ public class BackendPlaySessionHandler implements MinecraftSessionHandler {
@Override
public boolean handle(KeepAlive packet) {
serverConn.getPendingPings().put(packet.getRandomId(), System.currentTimeMillis());
serverConn.getPendingPings().put(packet.getRandomId(), System.nanoTime());
return false; // forwards on
}

Datei anzeigen

@ -91,10 +91,9 @@ public class TransitionSessionHandler implements MinecraftSessionHandler {
@Override
public boolean handle(JoinGame packet) {
MinecraftConnection smc = serverConn.ensureConnected();
RegisteredServer previousServer = serverConn.getPreviousServer().orElse(null);
VelocityServerConnection existingConnection = serverConn.getPlayer().getConnectedServer();
final RegisteredServer previousServer = serverConn.getPreviousServer().orElse(null);
final ConnectedPlayer player = serverConn.getPlayer();
final VelocityServerConnection existingConnection = player.getConnectedServer();
if (existingConnection != null) {
// Shut down the existing server connection.
@ -103,11 +102,11 @@ public class TransitionSessionHandler implements MinecraftSessionHandler {
// Send keep alive to try to avoid timeouts
player.sendKeepAlive();
// Reset Tablist header and footer to prevent desync
player.clearPlayerListHeaderAndFooter();
}
// Reset Tablist header and footer to prevent desync
player.clearPlayerListHeaderAndFooter();
// The goods are in hand! We got JoinGame. Let's transition completely to the new state.
smc.setAutoReading(false);
server.getEventManager()

Datei anzeigen

@ -36,6 +36,7 @@ import com.velocitypowered.proxy.connection.MinecraftConnection;
import com.velocitypowered.proxy.connection.MinecraftConnectionAssociation;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import com.velocitypowered.proxy.connection.client.ConnectedPlayer;
import com.velocitypowered.proxy.connection.forge.modern.ModernForgeConnectionType;
import com.velocitypowered.proxy.connection.util.ConnectionRequestResults.Impl;
import com.velocitypowered.proxy.protocol.StateRegistry;
import com.velocitypowered.proxy.protocol.packet.Handshake;
@ -188,6 +189,9 @@ public class VelocityServerConnection implements MinecraftConnectionAssociation,
handshake.setServerAddress(createBungeeGuardForwardingAddress(secret));
} else if (proxyPlayer.getConnection().getType() == ConnectionTypes.LEGACY_FORGE) {
handshake.setServerAddress(playerVhost + HANDSHAKE_HOSTNAME_TOKEN);
} else if (proxyPlayer.getConnection().getType() instanceof ModernForgeConnectionType) {
handshake.setServerAddress(playerVhost + ((ModernForgeConnectionType) proxyPlayer
.getConnection().getType()).getModernToken());
} else {
handshake.setServerAddress(playerVhost);
}

Datei anzeigen

@ -25,6 +25,7 @@ import com.velocitypowered.proxy.connection.backend.VelocityServerConnection;
import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import com.velocitypowered.proxy.protocol.StateRegistry;
import com.velocitypowered.proxy.protocol.netty.MinecraftEncoder;
import com.velocitypowered.proxy.protocol.packet.ClientSettings;
import com.velocitypowered.proxy.protocol.packet.KeepAlive;
import com.velocitypowered.proxy.protocol.packet.PingIdentify;
@ -35,6 +36,7 @@ import com.velocitypowered.proxy.protocol.util.PluginMessageUtil;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import org.apache.logging.log4j.LogManager;
@ -80,7 +82,7 @@ public class ClientConfigSessionHandler implements MinecraftSessionHandler {
if (sentTime != null) {
MinecraftConnection smc = serverConnection.getConnection();
if (smc != null) {
player.setPing(System.currentTimeMillis() - sentTime);
player.setPing(TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - sentTime));
smc.write(packet);
}
}
@ -186,16 +188,21 @@ public class ClientConfigSessionHandler implements MinecraftSessionHandler {
* @return a future that completes when the config stage is finished
*/
public CompletableFuture<Void> handleBackendFinishUpdate(VelocityServerConnection serverConn) {
MinecraftConnection smc = serverConn.ensureConnected();
String brand = serverConn.getPlayer().getClientBrand();
if (brand != null && brandChannel != null) {
ByteBuf buf = Unpooled.buffer();
ProtocolUtils.writeString(buf, brand);
PluginMessage brandPacket = new PluginMessage(brandChannel, buf);
serverConn.ensureConnected().write(brandPacket);
smc.write(brandPacket);
}
player.getConnection().write(new FinishedUpdate());
serverConn.ensureConnected().write(new FinishedUpdate());
smc.write(new FinishedUpdate());
smc.getChannel().pipeline().get(MinecraftEncoder.class).setState(StateRegistry.PLAY);
return configSwitchFuture;
}
}

Datei anzeigen

@ -81,6 +81,7 @@ import java.util.Queue;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.TimeUnit;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import org.apache.logging.log4j.LogManager;
@ -177,7 +178,7 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
if (sentTime != null) {
MinecraftConnection smc = serverConnection.getConnection();
if (smc != null) {
player.setPing(System.currentTimeMillis() - sentTime);
player.setPing(TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - sentTime));
smc.write(packet);
}
}

Datei anzeigen

@ -429,7 +429,8 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player,
this.playerListHeader = translatedHeader;
this.playerListFooter = translatedFooter;
if (this.getProtocolVersion().compareTo(ProtocolVersion.MINECRAFT_1_8) >= 0) {
this.connection.write(HeaderAndFooter.create(header, footer, this.getProtocolVersion()));
this.connection.write(HeaderAndFooter.create(
translatedHeader, translatedFooter, this.getProtocolVersion()));
}
}
@ -1301,4 +1302,4 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player,
connectWithIndication();
}
}
}
}

Datei anzeigen

@ -28,6 +28,8 @@ import com.velocitypowered.proxy.connection.ConnectionTypes;
import com.velocitypowered.proxy.connection.MinecraftConnection;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import com.velocitypowered.proxy.connection.forge.legacy.LegacyForgeConstants;
import com.velocitypowered.proxy.connection.forge.modern.ModernForgeConnectionType;
import com.velocitypowered.proxy.connection.forge.modern.ModernForgeConstants;
import com.velocitypowered.proxy.connection.util.VelocityInboundConnection;
import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.StateRegistry;
@ -152,6 +154,10 @@ public class HandshakeSessionHandler implements MinecraftSessionHandler {
}
private ConnectionType getHandshakeConnectionType(Handshake handshake) {
if (handshake.getServerAddress().contains(ModernForgeConstants.MODERN_FORGE_TOKEN)
&& handshake.getProtocolVersion().compareTo(ProtocolVersion.MINECRAFT_1_20_2) >= 0) {
return new ModernForgeConnectionType(handshake.getServerAddress());
}
// Determine if we're using Forge (1.8 to 1.12, may not be the case in 1.13).
if (handshake.getServerAddress().endsWith(LegacyForgeConstants.HANDSHAKE_HOSTNAME_TOKEN)
&& handshake.getProtocolVersion().compareTo(ProtocolVersion.MINECRAFT_1_13) < 0) {
@ -236,7 +242,12 @@ public class HandshakeSessionHandler implements MinecraftSessionHandler {
@Override
public String toString() {
return "[legacy connection] " + this.getRemoteAddress().toString();
boolean isPlayerAddressLoggingEnabled = connection.server.getConfiguration()
.isPlayerAddressLoggingEnabled();
String playerIp =
isPlayerAddressLoggingEnabled
? this.getRemoteAddress().toString() : "<ip address withheld>";
return "[legacy connection] " + playerIp;
}
@Override

Datei anzeigen

@ -75,7 +75,12 @@ public final class InitialInboundConnection implements VelocityInboundConnection
@Override
public String toString() {
return "[initial connection] " + connection.getRemoteAddress().toString();
boolean isPlayerAddressLoggingEnabled = connection.server.getConfiguration()
.isPlayerAddressLoggingEnabled();
String playerIp =
isPlayerAddressLoggingEnabled
? connection.getRemoteAddress().toString() : "<ip address withheld>";
return "[initial connection] " + playerIp;
}
@Override

Datei anzeigen

@ -0,0 +1,65 @@
/*
* Copyright (C) 2018-2023 Velocity Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.velocitypowered.proxy.connection.forge.modern;
import static com.velocitypowered.proxy.connection.forge.modern.ModernForgeConstants.MODERN_FORGE_TOKEN;
import com.velocitypowered.proxy.connection.backend.BackendConnectionPhases;
import com.velocitypowered.proxy.connection.client.ClientConnectionPhases;
import com.velocitypowered.proxy.connection.util.ConnectionTypeImpl;
/**
* Contains extra logic.
*/
public class ModernForgeConnectionType extends ConnectionTypeImpl {
public final String hostName;
/**
* initialize the host name into an internal variable.
*
* @param hostName address from the client
*/
public ModernForgeConnectionType(String hostName) {
super(ClientConnectionPhases.VANILLA,
BackendConnectionPhases.VANILLA);
this.hostName = hostName;
}
/**
* Align the acquisition logic with the internal code of Forge.
*
* @return returns the final correct hostname
*/
public String getModernToken() {
int natVersion = 0;
int idx = hostName.indexOf('\0');
if (idx != -1) {
for (var pt : hostName.split("\0")) {
if (pt.startsWith(MODERN_FORGE_TOKEN)) {
if (pt.length() > MODERN_FORGE_TOKEN.length()) {
natVersion = Integer.parseInt(
pt.substring(MODERN_FORGE_TOKEN.length()));
}
}
}
}
return natVersion == 0 ? "\0" + MODERN_FORGE_TOKEN : "\0"
+ MODERN_FORGE_TOKEN + natVersion;
}
}

Datei anzeigen

@ -0,0 +1,25 @@
/*
* Copyright (C) 2018-2023 Velocity Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.velocitypowered.proxy.connection.forge.modern;
/**
* Constants for use with Modern Forge systems.
*/
public class ModernForgeConstants {
public static final String MODERN_FORGE_TOKEN = "FORGE";
}

Datei anzeigen

@ -63,7 +63,7 @@ public class JavaPluginLoader implements PluginLoader {
public PluginDescription loadCandidate(Path source) throws Exception {
Optional<SerializedPluginDescription> serialized = getSerializedPluginInfo(source);
if (!serialized.isPresent()) {
if (serialized.isEmpty()) {
throw new InvalidPluginException("Did not find a valid velocity-plugin.json.");
}
@ -81,19 +81,21 @@ public class JavaPluginLoader implements PluginLoader {
throw new IllegalArgumentException("Description provided isn't of the Java plugin loader");
}
URL pluginJarUrl = candidate.getSource().get().toUri().toURL();
URL pluginJarUrl = candidate.getSource().orElseThrow(
() -> new InvalidPluginException("Description provided does not have a source path")
).toUri().toURL();
PluginClassLoader loader = AccessController.doPrivileged(
(PrivilegedAction<PluginClassLoader>) () -> new PluginClassLoader(new URL[]{pluginJarUrl}));
loader.addToClassloaders();
JavaVelocityPluginDescriptionCandidate candidateInst =
(JavaVelocityPluginDescriptionCandidate) candidate;
Class mainClass = loader.loadClass(candidateInst.getMainClass());
Class<?> mainClass = loader.loadClass(candidateInst.getMainClass());
return createDescription(candidateInst, mainClass);
}
@Override
public Module createModule(PluginContainer container) throws Exception {
public Module createModule(PluginContainer container) {
PluginDescription description = container.getDescription();
if (!(description instanceof JavaVelocityPluginDescription)) {
throw new IllegalArgumentException("Description provided isn't of the Java plugin loader");
@ -102,11 +104,11 @@ public class JavaPluginLoader implements PluginLoader {
JavaVelocityPluginDescription javaDescription = (JavaVelocityPluginDescription) description;
Optional<Path> source = javaDescription.getSource();
if (!source.isPresent()) {
if (source.isEmpty()) {
throw new IllegalArgumentException("No path in plugin description");
}
return new VelocityPluginModule(server, javaDescription, container, baseDirectory);
return new VelocityPluginModule(javaDescription, container, baseDirectory);
}
@Override
@ -184,7 +186,7 @@ public class JavaPluginLoader implements PluginLoader {
private VelocityPluginDescription createDescription(
JavaVelocityPluginDescriptionCandidate description,
Class mainClass) {
Class<?> mainClass) {
return new JavaVelocityPluginDescription(
description.getId(),
description.getName().orElse(null),

Datei anzeigen

@ -23,7 +23,6 @@ import com.google.inject.Scopes;
import com.velocitypowered.api.plugin.PluginContainer;
import com.velocitypowered.api.plugin.PluginDescription;
import com.velocitypowered.api.plugin.annotation.DataDirectory;
import com.velocitypowered.api.proxy.ProxyServer;
import java.nio.file.Path;
import java.util.concurrent.ExecutorService;
import net.kyori.adventure.text.logger.slf4j.ComponentLogger;
@ -32,14 +31,12 @@ import org.slf4j.LoggerFactory;
class VelocityPluginModule implements Module {
private final ProxyServer server;
private final JavaVelocityPluginDescription description;
private final PluginContainer pluginContainer;
private final Path basePluginPath;
VelocityPluginModule(ProxyServer server, JavaVelocityPluginDescription description,
PluginContainer pluginContainer, Path basePluginPath) {
this.server = server;
VelocityPluginModule(JavaVelocityPluginDescription description, PluginContainer pluginContainer,
Path basePluginPath) {
this.description = description;
this.pluginContainer = pluginContainer;
this.basePluginPath = basePluginPath;

Datei anzeigen

@ -46,6 +46,8 @@ import net.kyori.adventure.nbt.BinaryTagType;
import net.kyori.adventure.nbt.BinaryTagTypes;
import net.kyori.adventure.nbt.CompoundBinaryTag;
import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer;
import net.kyori.adventure.text.serializer.json.JSONOptions;
import net.kyori.option.OptionState;
/**
* Utilities for writing and reading data in the Minecraft protocol.
@ -58,10 +60,47 @@ public enum ProtocolUtils {
.downsampleColors()
.emitLegacyHoverEvent()
.legacyHoverEventSerializer(VelocityLegacyHoverEventSerializer.INSTANCE)
.options(
OptionState.optionState()
// before 1.16
.value(JSONOptions.EMIT_RGB, Boolean.FALSE)
.value(JSONOptions.EMIT_HOVER_EVENT_TYPE, JSONOptions.HoverEventValueMode.LEGACY_ONLY)
// before 1.20.3
.value(JSONOptions.EMIT_COMPACT_TEXT_COMPONENT, Boolean.FALSE)
.value(JSONOptions.EMIT_HOVER_SHOW_ENTITY_ID_AS_INT_ARRAY, Boolean.FALSE)
.value(JSONOptions.VALIDATE_STRICT_EVENTS, Boolean.FALSE)
.build()
)
.build();
private static final GsonComponentSerializer PRE_1_20_3_SERIALIZER =
GsonComponentSerializer.builder()
.legacyHoverEventSerializer(VelocityLegacyHoverEventSerializer.INSTANCE)
.options(
OptionState.optionState()
// after 1.16
.value(JSONOptions.EMIT_RGB, Boolean.TRUE)
.value(JSONOptions.EMIT_HOVER_EVENT_TYPE, JSONOptions.HoverEventValueMode.MODERN_ONLY)
// before 1.20.3
.value(JSONOptions.EMIT_COMPACT_TEXT_COMPONENT, Boolean.FALSE)
.value(JSONOptions.EMIT_HOVER_SHOW_ENTITY_ID_AS_INT_ARRAY, Boolean.FALSE)
.value(JSONOptions.VALIDATE_STRICT_EVENTS, Boolean.FALSE)
.build()
)
.build();
private static final GsonComponentSerializer MODERN_SERIALIZER =
GsonComponentSerializer.builder()
.legacyHoverEventSerializer(VelocityLegacyHoverEventSerializer.INSTANCE)
.options(
OptionState.optionState()
// after 1.16
.value(JSONOptions.EMIT_RGB, Boolean.TRUE)
.value(JSONOptions.EMIT_HOVER_EVENT_TYPE, JSONOptions.HoverEventValueMode.MODERN_ONLY)
// after 1.20.3
.value(JSONOptions.EMIT_COMPACT_TEXT_COMPONENT, Boolean.TRUE)
.value(JSONOptions.EMIT_HOVER_SHOW_ENTITY_ID_AS_INT_ARRAY, Boolean.TRUE)
.value(JSONOptions.VALIDATE_STRICT_EVENTS, Boolean.TRUE)
.build()
)
.build();
public static final int DEFAULT_MAX_STRING_SIZE = 65536; // 64KiB
@ -671,9 +710,12 @@ public enum ProtocolUtils {
* @return the appropriate {@link GsonComponentSerializer}
*/
public static GsonComponentSerializer getJsonChatSerializer(ProtocolVersion version) {
if (version.compareTo(ProtocolVersion.MINECRAFT_1_16) >= 0) {
if (version.compareTo(ProtocolVersion.MINECRAFT_1_20_3) >= 0) {
return MODERN_SERIALIZER;
}
if (version.compareTo(ProtocolVersion.MINECRAFT_1_16) >= 0) {
return PRE_1_20_3_SERIALIZER;
}
return PRE_1_16_SERIALIZER;
}

Datei anzeigen

@ -20,9 +20,11 @@ package com.velocitypowered.proxy.protocol.packet.chat;
import com.velocitypowered.proxy.connection.MinecraftConnection;
import com.velocitypowered.proxy.connection.client.ConnectedPlayer;
import com.velocitypowered.proxy.protocol.MinecraftPacket;
import io.netty.channel.ChannelFuture;
import org.checkerframework.checker.nullness.qual.Nullable;
import java.time.Instant;
import java.util.concurrent.CompletableFuture;
import java.util.function.BiConsumer;
import java.util.function.Function;
/**
* A precisely ordered queue which allows for outside entries into the ordered queue through
@ -58,9 +60,8 @@ public class ChatQueue {
MinecraftConnection smc = player.ensureAndGetCurrentServer().ensureConnected();
CompletableFuture<WrappedPacket> nextInLine = WrappedPacket.wrap(timestamp, nextPacket);
awaitChat(smc, this.packetFuture,
this.packetFuture = awaitChat(smc, this.packetFuture,
nextInLine); // we await chat, binding `this.packetFuture` -> `nextInLine`
this.packetFuture = nextInLine;
}
}
@ -84,21 +85,26 @@ public class ChatQueue {
}
}
private static BiConsumer<WrappedPacket, Throwable> writePacket(MinecraftConnection connection) {
return (wrappedPacket, throwable) -> {
if (wrappedPacket != null && !connection.isClosed()) {
wrappedPacket.write(connection);
private static Function<WrappedPacket, WrappedPacket> writePacket(MinecraftConnection connection) {
return wrappedPacket -> {
if (!connection.isClosed()) {
ChannelFuture future = wrappedPacket.write(connection);
if (future != null) {
future.awaitUninterruptibly();
}
}
return wrappedPacket;
};
}
private static <T extends MinecraftPacket> void awaitChat(
private static <T extends MinecraftPacket> CompletableFuture<WrappedPacket> awaitChat(
MinecraftConnection connection,
CompletableFuture<WrappedPacket> binder,
CompletableFuture<WrappedPacket> future
) {
// the binder will run -> then the future will get the `write packet` caller
binder.whenComplete((ignored1, ignored2) -> future.whenComplete(writePacket(connection)));
return binder.thenCompose(ignored -> future.thenApply(writePacket(connection)));
}
private static <K, V extends MinecraftPacket> CompletableFuture<WrappedPacket> hijackCurrentPacket(
@ -113,7 +119,7 @@ public class ChatQueue {
// map the new packet into a better "designed" packet with the hijacked packet's timestamp
WrappedPacket.wrap(previous.timestamp,
future.thenApply(item -> packetMapper.map(previous.timestamp, item)))
.whenCompleteAsync(writePacket(connection), connection.eventLoop())
.thenApplyAsync(writePacket(connection), connection.eventLoop())
.whenComplete(
(packet, throwable) -> awaitedFuture.complete(throwable != null ? null : packet));
});
@ -148,10 +154,12 @@ public class ChatQueue {
this.packet = packet;
}
public void write(MinecraftConnection connection) {
@Nullable
public ChannelFuture write(MinecraftConnection connection) {
if (packet != null) {
connection.write(packet);
return connection.write(packet);
}
return null;
}
private static CompletableFuture<WrappedPacket> wrap(Instant timestamp,

Datei anzeigen

@ -28,6 +28,7 @@ import io.netty.buffer.ByteBuf;
import net.kyori.adventure.nbt.BinaryTag;
import net.kyori.adventure.nbt.BinaryTagIO;
import net.kyori.adventure.nbt.BinaryTagType;
import net.kyori.adventure.nbt.BinaryTagTypes;
import net.kyori.adventure.nbt.ByteArrayBinaryTag;
import net.kyori.adventure.nbt.ByteBinaryTag;
import net.kyori.adventure.nbt.CompoundBinaryTag;
@ -153,8 +154,19 @@ public class ComponentHolder {
return ListBinaryTag.empty();
}
BinaryTag listTag;
BinaryTagType<? extends BinaryTag> listType = serialize(jsonArray.get(0)).type();
List<BinaryTag> tagItems = new ArrayList<>(jsonArray.size());
BinaryTagType<? extends BinaryTag> listType = null;
for (JsonElement jsonEl : jsonArray) {
BinaryTag tag = serialize(jsonEl);
tagItems.add(tag);
if (listType == null) {
listType = tag.type();
} else if (listType != tag.type()) {
listType = BinaryTagTypes.COMPOUND;
}
}
switch (listType.id()) {
case 1://BinaryTagTypes.BYTE:
@ -163,42 +175,33 @@ public class ComponentHolder {
bytes[i] = (Byte) jsonArray.get(i).getAsNumber();
}
listTag = ByteArrayBinaryTag.byteArrayBinaryTag(bytes);
break;
return ByteArrayBinaryTag.byteArrayBinaryTag(bytes);
case 3://BinaryTagTypes.INT:
int[] ints = new int[jsonArray.size()];
for (int i = 0; i < ints.length; i++) {
ints[i] = (Integer) jsonArray.get(i).getAsNumber();
}
listTag = IntArrayBinaryTag.intArrayBinaryTag(ints);
break;
return IntArrayBinaryTag.intArrayBinaryTag(ints);
case 4://BinaryTagTypes.LONG:
long[] longs = new long[jsonArray.size()];
for (int i = 0; i < longs.length; i++) {
longs[i] = (Long) jsonArray.get(i).getAsNumber();
}
listTag = LongArrayBinaryTag.longArrayBinaryTag(longs);
break;
default:
List<BinaryTag> tagItems = new ArrayList<>(jsonArray.size());
for (JsonElement jsonEl : jsonArray) {
BinaryTag subTag = serialize(jsonEl);
if (subTag.type() != listType) {
throw new IllegalArgumentException("Cannot convert mixed JsonArray to Tag");
return LongArrayBinaryTag.longArrayBinaryTag(longs);
case 10://BinaryTagTypes.COMPOUND:
tagItems.replaceAll(tag -> {
if (tag.type() == BinaryTagTypes.COMPOUND) {
return tag;
} else {
return CompoundBinaryTag.builder().put("", tag).build();
}
tagItems.add(subTag);
}
listTag = ListBinaryTag.listBinaryTag(listType, tagItems);
});
break;
}
return listTag;
return ListBinaryTag.listBinaryTag(listType, tagItems);
}
return EndBinaryTag.endBinaryTag();

Datei anzeigen

@ -110,8 +110,12 @@ public class VelocityTabList implements InternalTabList {
if (!Objects.equals(previousEntry.getDisplayNameComponent().orElse(null),
entry.getDisplayNameComponent().orElse(null))) {
actions.add(UpsertPlayerInfo.Action.UPDATE_DISPLAY_NAME);
playerInfoEntry.setDisplayName(new ComponentHolder(player.getProtocolVersion(),
entry.getDisplayNameComponent().get()));
playerInfoEntry.setDisplayName(entry.getDisplayNameComponent().isEmpty()
?
null :
new ComponentHolder(player.getProtocolVersion(),
entry.getDisplayNameComponent().get())
);
}
if (!Objects.equals(previousEntry.getLatency(), entry.getLatency())) {
actions.add(UpsertPlayerInfo.Action.UPDATE_LATENCY);
@ -140,8 +144,12 @@ public class VelocityTabList implements InternalTabList {
playerInfoEntry.setProfile(entry.getProfile());
if (entry.getDisplayNameComponent().isPresent()) {
actions.add(UpsertPlayerInfo.Action.UPDATE_DISPLAY_NAME);
playerInfoEntry.setDisplayName(new ComponentHolder(player.getProtocolVersion(),
entry.getDisplayNameComponent().get()));
playerInfoEntry.setDisplayName(entry.getDisplayNameComponent().isEmpty()
?
null :
new ComponentHolder(player.getProtocolVersion(),
entry.getDisplayNameComponent().get())
);
}
if (entry.getChatSession() != null) {
actions.add(UpsertPlayerInfo.Action.INITIALIZE_CHAT);

Datei anzeigen

@ -80,7 +80,11 @@ public class VelocityTabListEntry implements TabListEntry {
this.displayName = displayName;
UpsertPlayerInfo.Entry upsertEntry = this.tabList.createRawEntry(this);
upsertEntry.setDisplayName(
new ComponentHolder(this.tabList.getPlayer().getProtocolVersion(), displayName));
displayName == null
?
null :
new ComponentHolder(this.tabList.getPlayer().getProtocolVersion(), displayName)
);
this.tabList.emitActionRaw(UpsertPlayerInfo.Action.UPDATE_DISPLAY_NAME, upsertEntry);
return this;
}

Datei anzeigen

@ -30,7 +30,7 @@ public final class CharacterUtil {
*/
public static boolean isAllowedCharacter(char c) {
// 167 = §, 127 = DEL
// https://minecraft.fandom.com/wiki/Multiplayer#Chat
// https://minecraft.wiki/w/Chat
return c != 167 && c >= ' ' && c != 127;
}

Datei anzeigen

@ -28,7 +28,6 @@ import net.kyori.adventure.translation.TranslationRegistry;
import net.kyori.adventure.translation.Translator;
import org.jetbrains.annotations.Nullable;
/**
* Velocity Translation Mapper.
*/

Datei anzeigen

@ -22,10 +22,10 @@
<TerminalConsole name="TerminalConsole">
<PatternLayout>
<LoggerNamePatternSelector
defaultPattern="%highlightError{[%d{HH:mm:ss} %level] [%logger]: %minecraftFormatting{%msg}%n%xEx}">
defaultPattern="%highlightError{[%d{HH:mm:ss} %level] [%logger]: %msg%n%xEx}">
<!-- Velocity doesn't need a prefix -->
<PatternMatch key="com.velocitypowered."
pattern="%highlightError{[%d{HH:mm:ss} %level]: %minecraftFormatting{%msg}%n%xEx}"/>
pattern="%highlightError{[%d{HH:mm:ss} %level]: %msg%n%xEx}"/>
</LoggerNamePatternSelector>
</PatternLayout>
</TerminalConsole>
@ -33,7 +33,7 @@
filePattern="logs/%d{yyyy-MM-dd}-%i.log.gz"
immediateFlush="false">
<PatternLayout
pattern="[%d{HH:mm:ss}] [%t/%level] [%logger]: %minecraftFormatting{%msg}{strip}%n"/>
pattern="[%d{HH:mm:ss}] [%t/%level] [%logger]: %stripAnsi{%msg}%n"/>
<Policies>
<TimeBasedTriggeringPolicy/>
<OnStartupTriggeringPolicy/>

Datei anzeigen

@ -20,11 +20,13 @@ package com.velocitypowered.proxy.command;
import static com.mojang.brigadier.arguments.IntegerArgumentType.getInteger;
import static com.mojang.brigadier.arguments.IntegerArgumentType.integer;
import static com.mojang.brigadier.arguments.StringArgumentType.word;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertSame;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.fail;
import com.mojang.brigadier.arguments.StringArgumentType;
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
import com.mojang.brigadier.builder.RequiredArgumentBuilder;
import com.velocitypowered.api.command.BrigadierCommand;
@ -349,4 +351,14 @@ public class BrigadierCommandTests extends CommandTestSuite {
assertThrows(CompletionException.class, () ->
manager.offerSuggestions(source, "parent ").join());
}
@Test
void testArgumentBuilderCreationUsingStaticFactory() {
assertDoesNotThrow(() -> BrigadierCommand.literalArgumentBuilder("someCommand"));
assertThrows(IllegalArgumentException.class,
() -> BrigadierCommand.literalArgumentBuilder("some random command"));
assertDoesNotThrow(
() -> BrigadierCommand.requiredArgumentBuilder(
"someRequiredArgument", StringArgumentType.word()));
}
}

Datei anzeigen

@ -0,0 +1,39 @@
/*
* Copyright (C) 2021-2023 Velocity Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.velocitypowered.proxy.component;
import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.proxy.protocol.packet.chat.ComponentHolder;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.minimessage.MiniMessage;
import org.junit.jupiter.api.Test;
/**
* ComponentHolder tests.
*/
public class ComponentHolderTest {
@Test
void testJsonToBinary() {
Component component = MiniMessage.miniMessage().deserialize(
"<#09add3>A <reset><reset>Velocity <#09add3>Server");
ComponentHolder holder = new ComponentHolder(ProtocolVersion.MINECRAFT_1_20_3, component);
holder.getJson();
holder.getBinaryTag();
}
}

Datei anzeigen

@ -33,6 +33,12 @@ sequenceOf(
project(project).projectDir = file(it)
}
// Include Configurate 3
val deprecatedConfigurateModule = ":deprecated-configurate3"
include(deprecatedConfigurateModule)
project(deprecatedConfigurateModule).projectDir = file("proxy/deprecated/configurate3")
// Log4J2 plugin
val log4j2ProxyPlugin = ":velocity-proxy-log4j2-plugin"
include(log4j2ProxyPlugin)
project(log4j2ProxyPlugin).projectDir = file("proxy/log4j2-plugin")