diff --git a/proxy/src/main/java/com/velocitypowered/proxy/config/VelocityConfiguration.java b/proxy/src/main/java/com/velocitypowered/proxy/config/VelocityConfiguration.java index 6420f0a42..ec15d8b22 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/config/VelocityConfiguration.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/config/VelocityConfiguration.java @@ -3,25 +3,32 @@ package com.velocitypowered.proxy.config; import com.electronwill.nightconfig.core.CommentedConfig; import com.electronwill.nightconfig.core.UnmodifiableConfig; import com.electronwill.nightconfig.core.file.CommentedFileConfig; +import com.electronwill.nightconfig.toml.TomlFormat; import com.google.common.base.MoreObjects; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.velocitypowered.api.proxy.config.ProxyConfig; import com.velocitypowered.api.util.Favicon; import com.velocitypowered.proxy.util.AddressUtil; + +import java.io.File; import java.io.IOException; +import java.io.InputStream; import java.net.InetSocketAddress; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; +import java.nio.file.StandardCopyOption; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Optional; import java.util.Random; import java.util.UUID; +import net.kyori.adventure.text.Component; import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer; import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; import net.kyori.adventure.text.serializer.legacytext3.LegacyText3ComponentSerializer; @@ -49,22 +56,24 @@ public class VelocityConfiguration implements ProxyConfig { private final Advanced advanced; private final Query query; private final Metrics metrics; + private final Messages messages; private net.kyori.adventure.text.@MonotonicNonNull Component motdAsComponent; private @Nullable Favicon favicon; private VelocityConfiguration(Servers servers, ForcedHosts forcedHosts, Advanced advanced, - Query query, Metrics metrics) { + Query query, Metrics metrics, Messages messages) { this.servers = servers; this.forcedHosts = forcedHosts; this.advanced = advanced; this.query = query; this.metrics = metrics; + this.messages = messages; } private VelocityConfiguration(String bind, String motd, int showMaxPlayers, boolean onlineMode, boolean announceForge, PlayerInfoForwarding playerInfoForwardingMode, byte[] forwardingSecret, boolean onlineModeKickExistingPlayers, PingPassthroughMode pingPassthrough, Servers servers, - ForcedHosts forcedHosts, Advanced advanced, Query query, Metrics metrics) { + ForcedHosts forcedHosts, Advanced advanced, Query query, Metrics metrics, Messages messages) { this.bind = bind; this.motd = motd; this.showMaxPlayers = showMaxPlayers; @@ -79,6 +88,7 @@ public class VelocityConfiguration implements ProxyConfig { this.advanced = advanced; this.query = query; this.metrics = metrics; + this.messages = messages; } /** @@ -359,6 +369,10 @@ public class VelocityConfiguration implements ProxyConfig { return advanced.isLogCommandExecutions(); } + public Messages getMessages() { + return messages; + } + @Override public String toString() { return MoreObjects.toStringHelper(this) @@ -384,15 +398,28 @@ public class VelocityConfiguration implements ProxyConfig { * @throws IOException if we could not read from the {@code path}. */ public static VelocityConfiguration read(Path path) throws IOException { + String defaultResource = "default-velocity.toml"; boolean mustResave = false; CommentedFileConfig config = CommentedFileConfig.builder(path) - .defaultResource("/default-velocity.toml") + .defaultResource(defaultResource) .autosave() .preserveInsertionOrder() .sync() .build(); config.load(); + // Create temporary default configuration + File tmpFile = File.createTempFile(defaultResource, null); + tmpFile.deleteOnExit(); + + // Copy over default file to tmp location + ClassLoader loader = VelocityConfiguration.class.getClassLoader(); + try (InputStream in = loader.getResourceAsStream(defaultResource)) { + Files.copy(Objects.requireNonNull(in), tmpFile.toPath(), StandardCopyOption.REPLACE_EXISTING); + } + CommentedFileConfig defaultConfig = CommentedFileConfig.of(tmpFile, TomlFormat.instance()); + defaultConfig.load(); + // Handle any cases where the config needs to be saved again byte[] forwardingSecret; String forwardingSecretString = config.get("forwarding-secret"); @@ -418,6 +445,7 @@ public class VelocityConfiguration implements ProxyConfig { CommentedConfig advancedConfig = config.get("advanced"); CommentedConfig queryConfig = config.get("query"); CommentedConfig metricsConfig = config.get("metrics"); + CommentedConfig messagesConfig = config.get("messages"); PlayerInfoForwarding forwardingMode = config.getEnumOrElse("player-info-forwarding-mode", PlayerInfoForwarding.NONE); PingPassthroughMode pingPassthroughMode = config.getEnumOrElse("ping-passthrough", @@ -444,7 +472,8 @@ public class VelocityConfiguration implements ProxyConfig { new ForcedHosts(forcedHostsConfig), new Advanced(advancedConfig), new Query(queryConfig), - new Metrics(metricsConfig) + new Metrics(metricsConfig), + new Messages(messagesConfig, defaultConfig.get("messages")) ); } @@ -781,4 +810,69 @@ public class VelocityConfiguration implements ProxyConfig { return fromConfig; } } + + public static class Messages { + + private final CommentedConfig toml; + private final CommentedConfig defaultToml; + + private final String kickPrefix; + private final String disconnectPrefix; + private final String onlineModeOnly; + private final String noAvailableServers; + private final String alreadyConnected; + private final String movedToNewServerPrefix; + private final String genericConnectionError; + + private Messages(CommentedConfig toml, CommentedConfig defaultToml) { + this.toml = toml; + this.defaultToml = defaultToml; + this.kickPrefix = getString("kick-prefix"); + this.disconnectPrefix = getString("disconnect-prefix"); + this.onlineModeOnly = getString("online-mode-only"); + this.noAvailableServers = getString("no-available-servers"); + this.alreadyConnected = getString("already-connected"); + this.movedToNewServerPrefix = getString("moved-to-new-server-prefix"); + this.genericConnectionError = getString("generic-connection-error"); + } + + private String getString(String path) { + return toml.getOrElse(path, defaultToml.getOrElse(path, "")); + } + + public Component getKickPrefix(String server) { + return serialize(String.format(kickPrefix, server)); + } + + public Component getDisconnectPrefix(String server) { + return serialize(String.format(disconnectPrefix, server)); + } + + public Component getOnlineModeOnly() { + return serialize(onlineModeOnly); + } + + public Component getNoAvailableServers() { + return serialize(noAvailableServers); + } + + public Component getAlreadyConnected() { + return serialize(alreadyConnected); + } + + public Component getMovedToNewServerPrefix() { + return serialize(movedToNewServerPrefix); + } + + public Component getGenericConnectionError() { + return serialize(genericConnectionError); + } + + private Component serialize(String str) { + if (str.startsWith("{")) { + return GsonComponentSerializer.gson().deserialize(str); + } + return LegacyComponentSerializer.legacyAmpersand().deserialize(str); + } + } } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ClientPlaySessionHandler.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ClientPlaySessionHandler.java index b8ee436da..e44f83ce6 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ClientPlaySessionHandler.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ClientPlaySessionHandler.java @@ -31,7 +31,6 @@ import com.velocitypowered.proxy.protocol.packet.TabCompleteResponse; import com.velocitypowered.proxy.protocol.packet.TabCompleteResponse.Offer; import com.velocitypowered.proxy.protocol.packet.TitlePacket; import com.velocitypowered.proxy.protocol.util.PluginMessageUtil; -import com.velocitypowered.proxy.util.VelocityMessages; import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufUtil; import io.netty.buffer.Unpooled; @@ -279,7 +278,7 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler { @Override public void exception(Throwable throwable) { - player.disconnect(VelocityMessages.GENERIC_CONNECTION_ERROR); + player.disconnect(server.getConfiguration().getMessages().getGenericConnectionError()); } @Override diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ConnectedPlayer.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ConnectedPlayer.java index 455232382..14258bf4c 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ConnectedPlayer.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ConnectedPlayer.java @@ -32,6 +32,7 @@ import com.velocitypowered.api.util.title.TextTitle; import com.velocitypowered.api.util.title.Title; import com.velocitypowered.api.util.title.Titles; import com.velocitypowered.proxy.VelocityServer; +import com.velocitypowered.proxy.config.VelocityConfiguration; import com.velocitypowered.proxy.connection.MinecraftConnection; import com.velocitypowered.proxy.connection.MinecraftConnectionAssociation; import com.velocitypowered.proxy.connection.backend.VelocityServerConnection; @@ -52,7 +53,6 @@ import com.velocitypowered.proxy.server.VelocityRegisteredServer; import com.velocitypowered.proxy.tablist.VelocityTabList; import com.velocitypowered.proxy.tablist.VelocityTabListLegacy; import com.velocitypowered.proxy.util.DurationUtils; -import com.velocitypowered.proxy.util.VelocityMessages; import com.velocitypowered.proxy.util.collect.CappedSet; import io.netty.buffer.ByteBufUtil; import io.netty.buffer.Unpooled; @@ -520,13 +520,14 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player { return; } + VelocityConfiguration.Messages messages = this.server.getConfiguration().getMessages(); Component disconnectReason = GsonComponentSerializer.gson().deserialize(disconnect.getReason()); String plainTextReason = PASS_THRU_TRANSLATE.serialize(disconnectReason); if (connectedServer != null && connectedServer.getServerInfo().equals(server.getServerInfo())) { logger.error("{}: kicked from server {}: {}", this, server.getServerInfo().getName(), plainTextReason); handleConnectionException(server, disconnectReason, TextComponent.builder() - .content("Kicked from " + server.getServerInfo().getName() + ": ") + .append(messages.getKickPrefix(server.getServerInfo().getName())) .color(NamedTextColor.RED) .append(disconnectReason) .build(), safe); @@ -534,7 +535,7 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player { logger.error("{}: disconnected while connecting to {}: {}", this, server.getServerInfo().getName(), plainTextReason); handleConnectionException(server, disconnectReason, TextComponent.builder() - .content("Can't connect to server " + server.getServerInfo().getName() + ": ") + .append(messages.getDisconnectPrefix(server.getServerInfo().getName())) .color(NamedTextColor.RED) .append(disconnectReason) .build(), safe); @@ -597,7 +598,8 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player { disconnect(friendlyReason); } else { if (res.getMessageComponent() == null) { - sendMessage(VelocityMessages.MOVED_TO_NEW_SERVER.append(friendlyReason)); + sendMessage(server.getConfiguration().getMessages() + .getMovedToNewServerPrefix().append(friendlyReason)); } else { sendMessage(res.getMessageComponent()); } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/LoginSessionHandler.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/LoginSessionHandler.java index 8061710b8..c0f7c2f9e 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/LoginSessionHandler.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/LoginSessionHandler.java @@ -33,7 +33,6 @@ import com.velocitypowered.proxy.protocol.packet.EncryptionResponse; import com.velocitypowered.proxy.protocol.packet.ServerLogin; import com.velocitypowered.proxy.protocol.packet.ServerLoginSuccess; import com.velocitypowered.proxy.protocol.packet.SetCompression; -import com.velocitypowered.proxy.util.VelocityMessages; import io.netty.buffer.ByteBuf; import io.netty.handler.timeout.ReadTimeoutHandler; import java.net.InetSocketAddress; @@ -133,7 +132,7 @@ public class LoginSessionHandler implements MinecraftSessionHandler { GameProfile.class), true); } else if (profileResponse.getStatusCode() == 204) { // Apparently an offline-mode user logged onto this online-mode proxy. - inbound.disconnect(VelocityMessages.ONLINE_MODE_ONLY); + inbound.disconnect(server.getConfiguration().getMessages().getOnlineModeOnly()); } else { // Something else went wrong logger.error( @@ -224,7 +223,7 @@ public class LoginSessionHandler implements MinecraftSessionHandler { mcConnection, inbound.getVirtualHost().orElse(null), onlineMode); this.connectedPlayer = player; if (!server.canRegisterConnection(player)) { - player.disconnect0(VelocityMessages.ALREADY_CONNECTED, true); + player.disconnect0(server.getConfiguration().getMessages().getAlreadyConnected(), true); return CompletableFuture.completedFuture(null); } @@ -275,7 +274,8 @@ public class LoginSessionHandler implements MinecraftSessionHandler { player.disconnect0(reason.get(), true); } else { if (!server.registerConnection(player)) { - player.disconnect0(VelocityMessages.ALREADY_CONNECTED, true); + player.disconnect0(server.getConfiguration().getMessages() + .getAlreadyConnected(), true); return; } @@ -295,7 +295,8 @@ public class LoginSessionHandler implements MinecraftSessionHandler { .thenRunAsync(() -> { Optional toTry = event.getInitialServer(); if (!toTry.isPresent()) { - player.disconnect0(VelocityMessages.NO_AVAILABLE_SERVERS, true); + player.disconnect0(server.getConfiguration().getMessages() + .getNoAvailableServers(), true); return; } player.createConnectionRequest(toTry.get()).fireAndForget(); diff --git a/proxy/src/main/java/com/velocitypowered/proxy/util/VelocityMessages.java b/proxy/src/main/java/com/velocitypowered/proxy/util/VelocityMessages.java deleted file mode 100644 index a85a8b588..000000000 --- a/proxy/src/main/java/com/velocitypowered/proxy/util/VelocityMessages.java +++ /dev/null @@ -1,29 +0,0 @@ -package com.velocitypowered.proxy.util; - -import net.kyori.adventure.text.Component; -import net.kyori.adventure.text.TextComponent; -import net.kyori.adventure.text.format.NamedTextColor; - -public final class VelocityMessages { - - public static final Component ONLINE_MODE_ONLY = TextComponent - .builder("This server only accepts connections from online-mode clients.") - .color(NamedTextColor.RED) - .append( - TextComponent.of("\n\nDid you change your username? Sign out of Minecraft, sign back in, " - + "and try again.", NamedTextColor.GRAY) - ) - .build(); - public static final Component NO_AVAILABLE_SERVERS = TextComponent - .of("There are no available servers.", NamedTextColor.RED); - public static final Component ALREADY_CONNECTED = TextComponent - .of("You are already connected to this proxy!", NamedTextColor.RED); - public static final Component MOVED_TO_NEW_SERVER = TextComponent - .of("The server you were on kicked you: ", NamedTextColor.RED); - public static final Component GENERIC_CONNECTION_ERROR = TextComponent - .of("An internal error occurred in your connection.", NamedTextColor.RED); - - private VelocityMessages() { - throw new AssertionError(); - } -} diff --git a/proxy/src/main/resources/default-velocity.toml b/proxy/src/main/resources/default-velocity.toml index aad435f11..0b2dbdfb8 100644 --- a/proxy/src/main/resources/default-velocity.toml +++ b/proxy/src/main/resources/default-velocity.toml @@ -158,3 +158,18 @@ id = "" log-failure = false +# Legacy color codes and JSON are accepted in all messages. +[messages] +# Prefix when the player gets kicked from a server. +# First argument '%s': the server name +kick-prefix = "&cKicked from %s: " + +# Prefix when the player is disconnected from a server. +# First argument '%s': the server name +disconnect-prefix = "&cCan't connect to %s: " + +online-mode-only = "&cThis server only accepts connections from online-mode clients.\n\n&7Did you change your username? Sign out of Minecraft, sign back in, and try again." +no-available-servers = "&cThere are no available servers." +already-connected = "&cYou are already connected to this proxy!" +moved-to-new-server-prefix = "&cThe server you were on kicked you: " +generic-connection-error = "&cAn internal error occurred in your connection." \ No newline at end of file