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:
Commit
2d2747ea96
@ -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"
|
||||
)
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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"
|
||||
|
@ -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)
|
||||
|
4
proxy/log4j2-plugin/build.gradle.kts
Normale Datei
4
proxy/log4j2-plugin/build.gradle.kts
Normale Datei
@ -0,0 +1,4 @@
|
||||
dependencies {
|
||||
implementation(libs.bundles.log4j)
|
||||
annotationProcessor(libs.log4j.core)
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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();
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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())));
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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");
|
||||
}
|
||||
}
|
@ -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");
|
||||
}
|
||||
}
|
@ -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");
|
||||
}
|
||||
}
|
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -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()
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -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";
|
||||
}
|
@ -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),
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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();
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -28,7 +28,6 @@ import net.kyori.adventure.translation.TranslationRegistry;
|
||||
import net.kyori.adventure.translation.Translator;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
|
||||
/**
|
||||
* Velocity Translation Mapper.
|
||||
*/
|
||||
|
@ -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/>
|
||||
|
@ -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()));
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
@ -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")
|
||||
|
Laden…
In neuem Issue referenzieren
Einen Benutzer sperren