From 3803ece37537e09470985b0b5eda6588694f3b3c Mon Sep 17 00:00:00 2001 From: IzzelAliz Date: Sat, 30 Dec 2023 16:13:47 +0800 Subject: [PATCH] Velocity modern forward support --- .../ServerHandshakeNetHandlerMixin.java | 42 ++++--- .../network/ServerLoginNetHandlerMixin.java | 117 +++++++++++++++++- ...dCommandsPacket_ArgumentNodeStubMixin.java | 3 +- .../common/mod/server/ArclightServer.java | 8 +- .../common/mod/util/VelocitySupport.java | 92 ++++++++++++++ .../izzel/arclight/i18n/conf/ConfigSpec.java | 7 ++ .../arclight/i18n/conf/VelocitySpec.java | 22 ++++ .../src/main/resources/META-INF/arclight.conf | 4 + .../main/resources/META-INF/i18n/en_us.conf | 10 ++ .../main/resources/META-INF/i18n/zh_cn.conf | 9 ++ 10 files changed, 293 insertions(+), 21 deletions(-) create mode 100644 arclight-common/src/main/java/io/izzel/arclight/common/mod/util/VelocitySupport.java create mode 100644 i18n-config/src/main/java/io/izzel/arclight/i18n/conf/VelocitySpec.java diff --git a/arclight-common/src/main/java/io/izzel/arclight/common/mixin/core/network/ServerHandshakeNetHandlerMixin.java b/arclight-common/src/main/java/io/izzel/arclight/common/mixin/core/network/ServerHandshakeNetHandlerMixin.java index dc4e6ceb9..55da5862a 100644 --- a/arclight-common/src/main/java/io/izzel/arclight/common/mixin/core/network/ServerHandshakeNetHandlerMixin.java +++ b/arclight-common/src/main/java/io/izzel/arclight/common/mixin/core/network/ServerHandshakeNetHandlerMixin.java @@ -4,6 +4,7 @@ import com.mojang.authlib.properties.Property; import com.mojang.util.UndashedUuid; import io.izzel.arclight.common.bridge.core.network.NetworkManagerBridge; +import io.izzel.arclight.common.mod.util.VelocitySupport; import net.minecraft.SharedConstants; import net.minecraft.network.Connection; import net.minecraft.network.chat.Component; @@ -15,6 +16,7 @@ import net.minecraft.server.network.ServerHandshakePacketListenerImpl; import net.minecraft.server.network.ServerLoginPacketListenerImpl; import net.minecraft.server.network.ServerStatusPacketListenerImpl; +import net.minecraftforge.network.NetworkContext; import net.minecraftforge.server.ServerLifecycleHooks; import org.apache.logging.log4j.LogManager; import org.bukkit.Bukkit; @@ -94,26 +96,28 @@ public void handleIntention(ClientIntentionPacket packetIn) { } this.connection.setListener(new ServerLoginPacketListenerImpl(this.server, this.connection)); - String[] split = packetIn.hostName().split("\00"); - if (SpigotConfig.bungee) { - if ((split.length == 3 || split.length == 4) && (HOST_PATTERN.matcher(split[1]).matches())) { - ((NetworkManagerBridge) this.connection).bridge$setHostname(split[0]); - this.connection.address = new InetSocketAddress(split[1], ((InetSocketAddress) this.connection.getRemoteAddress()).getPort()); - ((NetworkManagerBridge) this.connection).bridge$setSpoofedUUID(UndashedUuid.fromStringLenient(split[2])); - } else { - var component = Component.literal("If you wish to use IP forwarding, please enable it in your BungeeCord config as well!"); + if (!VelocitySupport.isEnabled()) { + String[] split = packetIn.hostName().split("\00"); + if (SpigotConfig.bungee) { + if ((split.length == 3 || split.length == 4) && (HOST_PATTERN.matcher(split[1]).matches())) { + ((NetworkManagerBridge) this.connection).bridge$setHostname(split[0]); + this.connection.address = new InetSocketAddress(split[1], ((InetSocketAddress) this.connection.getRemoteAddress()).getPort()); + ((NetworkManagerBridge) this.connection).bridge$setSpoofedUUID(UndashedUuid.fromStringLenient(split[2])); + } else { + var component = Component.literal("If you wish to use IP forwarding, please enable it in your BungeeCord config as well!"); + this.connection.send(new ClientboundLoginDisconnectPacket(component)); + this.connection.disconnect(component); + return; + } + if (split.length == 4) { + ((NetworkManagerBridge) this.connection).bridge$setSpoofedProfile(gson.fromJson(split[3], Property[].class)); + } + } else if ((split.length == 3 || split.length == 4) && (HOST_PATTERN.matcher(split[1]).matches())) { + Component component = Component.literal("Unknown data in login hostname, did you forget to enable BungeeCord in spigot.yml?"); this.connection.send(new ClientboundLoginDisconnectPacket(component)); this.connection.disconnect(component); return; } - if (split.length == 4) { - ((NetworkManagerBridge) this.connection).bridge$setSpoofedProfile(gson.fromJson(split[3], Property[].class)); - } - } else if ((split.length == 3 || split.length == 4) && (HOST_PATTERN.matcher(split[1]).matches())) { - Component component = Component.literal("Unknown data in login hostname, did you forget to enable BungeeCord in spigot.yml?"); - this.connection.send(new ClientboundLoginDisconnectPacket(component)); - this.connection.disconnect(component); - return; } break; @@ -139,7 +143,11 @@ public void handleIntention(ClientIntentionPacket packetIn) { private boolean arclight$handleSpecialLogin(ClientIntentionPacket packet) { String ip = packet.hostName(); - if (SpigotConfig.bungee) { + if (VelocitySupport.isEnabled()) { + // as if forge client connects + var forgePacket = new ClientIntentionPacket(packet.protocolVersion(), NetworkContext.enhanceHostName(ip), packet.port(), packet.intention()); + return ServerLifecycleHooks.handleServerLogin(forgePacket, this.connection); + } else if (SpigotConfig.bungee) { String[] split = ip.split("\0"); if (split.length == 4) { Property[] properties = GSON.fromJson(split[3], Property[].class); diff --git a/arclight-common/src/main/java/io/izzel/arclight/common/mixin/core/network/ServerLoginNetHandlerMixin.java b/arclight-common/src/main/java/io/izzel/arclight/common/mixin/core/network/ServerLoginNetHandlerMixin.java index 455182c8e..eb57fa793 100644 --- a/arclight-common/src/main/java/io/izzel/arclight/common/mixin/core/network/ServerLoginNetHandlerMixin.java +++ b/arclight-common/src/main/java/io/izzel/arclight/common/mixin/core/network/ServerLoginNetHandlerMixin.java @@ -7,14 +7,20 @@ import io.izzel.arclight.common.bridge.core.network.common.ServerCommonPacketListenerBridge; import io.izzel.arclight.common.bridge.core.server.MinecraftServerBridge; import io.izzel.arclight.common.bridge.core.server.management.PlayerListBridge; +import io.izzel.arclight.common.mod.util.VelocitySupport; import net.minecraft.DefaultUncaughtExceptionHandler; +import net.minecraft.Util; import net.minecraft.core.UUIDUtil; import net.minecraft.network.Connection; +import net.minecraft.network.FriendlyByteBuf; import net.minecraft.network.chat.Component; +import net.minecraft.network.protocol.login.ClientboundCustomQueryPacket; import net.minecraft.network.protocol.login.ClientboundHelloPacket; +import net.minecraft.network.protocol.login.ServerboundCustomQueryAnswerPacket; import net.minecraft.network.protocol.login.ServerboundHelloPacket; import net.minecraft.network.protocol.login.ServerboundKeyPacket; import net.minecraft.network.protocol.login.ServerboundLoginAcknowledgedPacket; +import net.minecraft.network.protocol.login.custom.DiscardedQueryAnswerPayload; import net.minecraft.server.MinecraftServer; import net.minecraft.server.level.ServerPlayer; import net.minecraft.server.network.CommonListenerCookie; @@ -25,6 +31,10 @@ import net.minecraft.util.CryptException; import net.minecraft.world.entity.player.Player; import net.minecraftforge.fml.util.thread.SidedThreadGroups; +import net.minecraftforge.network.ConnectionType; +import net.minecraftforge.network.NetworkContext; +import net.minecraftforge.network.NetworkRegistry; +import net.minecraftforge.network.filters.NetworkFilters; import org.apache.commons.lang3.Validate; import org.bukkit.Bukkit; import org.bukkit.craftbukkit.v.CraftServer; @@ -36,6 +46,7 @@ import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Overwrite; import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.Unique; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.Redirect; @@ -52,6 +63,7 @@ import java.security.PrivateKey; import java.util.Objects; import java.util.UUID; +import java.util.concurrent.ThreadLocalRandom; import java.util.concurrent.atomic.AtomicInteger; @Mixin(ServerLoginPacketListenerImpl.class) @@ -75,6 +87,7 @@ public abstract class ServerLoginNetHandlerMixin { private static final java.util.regex.Pattern PROP_PATTERN = java.util.regex.Pattern.compile("\\w{0,16}"); private ServerPlayer player; + @Unique private int arclight$velocityLoginId = -1; public void disconnect(final String s) { this.disconnect(Component.literal(s)); @@ -97,6 +110,12 @@ public void handleHello(ServerboundHelloPacket packetIn) { this.state = ServerLoginPacketListenerImpl.State.KEY; this.connection.send(new ClientboundHelloPacket("", this.server.getKeyPair().getPublic().getEncoded(), this.challenge)); } else { + if (VelocitySupport.isEnabled()) { + this.arclight$velocityLoginId = ThreadLocalRandom.current().nextInt(); + var packet = new ClientboundCustomQueryPacket(this.arclight$velocityLoginId, VelocitySupport.createPacket()); + this.connection.send(packet); + return; + } class Handler extends Thread { Handler() { @@ -109,7 +128,7 @@ public void run() { var gameProfile = arclight$createOfflineProfile(connection, requestedUsername); arclight$preLogin(gameProfile); } catch (Exception ex) { - disconnect("Failed to verify username!"); + disconnect(Component.translatable("multiplayer.disconnect.unverified_username")); LOGGER.warn("Exception verifying {} ", requestedUsername, ex); } } @@ -221,7 +240,7 @@ public void run() { LOGGER.error("Couldn't verify username because servers are unavailable"); } } catch (Exception e) { - disconnect("Failed to verify username!"); + disconnect(Component.translatable("multiplayer.disconnect.unverified_username")); LOGGER.error("Exception verifying " + name, e); } @@ -238,7 +257,101 @@ private InetAddress getAddress() { thread.start(); } + private static final String EXTRA_DATA = "extraData"; + + @Inject(method = "handleCustomQueryPacket", cancellable = true, at = @At("HEAD")) + private void arclight$modernForwardReply(ServerboundCustomQueryAnswerPacket packet, CallbackInfo ci) { + if (VelocitySupport.isEnabled() && packet.transactionId() == this.arclight$velocityLoginId) { + if (!(packet.payload() instanceof DiscardedQueryAnswerPayload payload && payload.data() != null)) { + this.disconnect("This server requires you to connect with Velocity."); + ci.cancel(); + return; + } + var buf = payload.data().readNullable(r -> { + int i = r.readableBytes(); + if (i >= 0 && i <= 1048576) { + return new FriendlyByteBuf(r.readBytes(i)); + } else { + throw new IllegalArgumentException("Payload may not be larger than 1048576 bytes"); + } + }); + if (buf == null) { + this.disconnect("This server requires you to connect with Velocity."); + ci.cancel(); + return; + } + + if (!VelocitySupport.checkIntegrity(buf)) { + this.disconnect("Unable to verify player details"); + ci.cancel(); + return; + } + + int version = buf.readVarInt(); + if (version > VelocitySupport.MAX_SUPPORTED_FORWARDING_VERSION) { + throw new IllegalStateException("Unsupported forwarding version " + version + ", wanted upto " + VelocitySupport.MAX_SUPPORTED_FORWARDING_VERSION); + } + java.net.SocketAddress listening = this.connection.getRemoteAddress(); + int port = 0; + if (listening instanceof java.net.InetSocketAddress) { + port = ((java.net.InetSocketAddress) listening).getPort(); + } + this.connection.address = new java.net.InetSocketAddress(VelocitySupport.readAddress(buf), port); + this.authenticatedProfile = VelocitySupport.createProfile(buf); + + // late forge setup + boolean forwarded = false; + for (var property : this.authenticatedProfile.getProperties().values()) { + if (Objects.equals(property.name(), EXTRA_DATA)) { + String extraData = property.value().replace("\1", "\0"); + var ctx = NetworkContext.get(this.connection); + ctx.processIntention(extraData); + if (ctx.getType() == ConnectionType.MODDED && ctx.getNetVersion() != NetworkContext.NET_VERSION) { + this.disconnect("This modded server is not impl compatible with your modded client. Please verify your Forge version closely matches the server. Got net version " + ctx.getNetVersion() + " this server is net version " + NetworkContext.NET_VERSION); + ci.cancel(); + return; + } + if (ctx.getType() == ConnectionType.VANILLA && !NetworkRegistry.acceptsVanillaClientConnections()) { + this.disconnect("This server has mods that require Forge to be installed on the client. Contact your server admin for more details."); + ci.cancel(); + return; + } + NetworkFilters.injectIfNecessary(this.connection); + forwarded = true; + break; + } + } + if (!forwarded) { + // considered vanilla + var ctx = NetworkContext.get(this.connection); + ctx.processIntention(""); + if (ctx.getType() == ConnectionType.VANILLA && !NetworkRegistry.acceptsVanillaClientConnections()) { + this.disconnect("This server has mods that require Forge to be installed on the client. Contact your server admin for more details."); + ci.cancel(); + return; + } + NetworkFilters.injectIfNecessary(this.connection); + } + + // Proceed with login + Util.backgroundExecutor().execute(() -> { + try { + this.arclight$preLogin(this.authenticatedProfile); + } catch (Exception ex) { + disconnect(Component.translatable("multiplayer.disconnect.unverified_username")); + LOGGER.warn("Exception verifying {} ", this.authenticatedProfile.getName(), ex); + } + }); + ci.cancel(); + } + } + + @Unique void arclight$preLogin(GameProfile gameProfile) throws Exception { + if (this.arclight$velocityLoginId == -1 && VelocitySupport.isEnabled()) { + disconnect("This server requires you to connect with Velocity."); + return; + } String playerName = gameProfile.getName(); InetAddress address = ((InetSocketAddress) connection.getRemoteAddress()).getAddress(); UUID uniqueId = gameProfile.getId(); diff --git a/arclight-common/src/main/java/io/izzel/arclight/common/mixin/core/network/protocol/game/ClientboundCommandsPacket_ArgumentNodeStubMixin.java b/arclight-common/src/main/java/io/izzel/arclight/common/mixin/core/network/protocol/game/ClientboundCommandsPacket_ArgumentNodeStubMixin.java index aefedbd9a..3b8d759e9 100644 --- a/arclight-common/src/main/java/io/izzel/arclight/common/mixin/core/network/protocol/game/ClientboundCommandsPacket_ArgumentNodeStubMixin.java +++ b/arclight-common/src/main/java/io/izzel/arclight/common/mixin/core/network/protocol/game/ClientboundCommandsPacket_ArgumentNodeStubMixin.java @@ -2,6 +2,7 @@ import com.mojang.brigadier.arguments.ArgumentType; import io.izzel.arclight.common.mod.ArclightMod; +import io.izzel.arclight.common.mod.util.VelocitySupport; import io.netty.buffer.Unpooled; import net.minecraft.commands.synchronization.ArgumentTypeInfo; import net.minecraft.core.registries.BuiltInRegistries; @@ -21,7 +22,7 @@ public class ClientboundCommandsPacket_ArgumentNodeStubMixin { @Inject(method = "serializeCap(Lnet/minecraft/network/FriendlyByteBuf;Lnet/minecraft/commands/synchronization/ArgumentTypeInfo;Lnet/minecraft/commands/synchronization/ArgumentTypeInfo$Template;)V", cancellable = true, at = @At("HEAD")) private static , T extends ArgumentTypeInfo.Template> void arclight$wrapArgument(FriendlyByteBuf buf, ArgumentTypeInfo type, ArgumentTypeInfo.Template node, CallbackInfo ci) { - if (!SpigotConfig.bungee) { + if (!(SpigotConfig.bungee || VelocitySupport.isEnabled())) { return; } var key = ForgeRegistries.COMMAND_ARGUMENT_TYPES.getKey(type); diff --git a/arclight-common/src/main/java/io/izzel/arclight/common/mod/server/ArclightServer.java b/arclight-common/src/main/java/io/izzel/arclight/common/mod/server/ArclightServer.java index 482db0711..5f6218701 100644 --- a/arclight-common/src/main/java/io/izzel/arclight/common/mod/server/ArclightServer.java +++ b/arclight-common/src/main/java/io/izzel/arclight/common/mod/server/ArclightServer.java @@ -6,6 +6,7 @@ import io.izzel.arclight.common.bridge.core.server.MinecraftServerBridge; import io.izzel.arclight.common.mod.ArclightMod; import io.izzel.arclight.common.mod.server.api.DefaultArclightServer; +import io.izzel.arclight.common.mod.util.VelocitySupport; import net.minecraft.resources.ResourceKey; import net.minecraft.server.MinecraftServer; import net.minecraft.server.dedicated.DedicatedServer; @@ -16,6 +17,7 @@ import org.bukkit.craftbukkit.v.CraftServer; import org.bukkit.craftbukkit.v.command.ColouredConsoleSender; import org.jetbrains.annotations.NotNull; +import org.spigotmc.SpigotConfig; import java.io.File; import java.util.Objects; @@ -28,7 +30,8 @@ public class ArclightServer { - private interface ExecutorWithThread extends Executor, Supplier {} + private interface ExecutorWithThread extends Executor, Supplier { + } private static final ExecutorWithThread mainThreadExecutor = new ExecutorWithThread() { @Override @@ -76,6 +79,9 @@ public static CraftServer createOrLoad(DedicatedServer console, PlayerList playe BukkitRegistry.registerAll(console); org.spigotmc.SpigotConfig.init(new File("./spigot.yml")); org.spigotmc.SpigotConfig.registerCommands(); + if (VelocitySupport.isEnabled()) { + SpigotConfig.bungee = true; + } } catch (Throwable t) { ArclightMod.LOGGER.error("registry.error", t); throw t; diff --git a/arclight-common/src/main/java/io/izzel/arclight/common/mod/util/VelocitySupport.java b/arclight-common/src/main/java/io/izzel/arclight/common/mod/util/VelocitySupport.java new file mode 100644 index 000000000..b1a6fbc3f --- /dev/null +++ b/arclight-common/src/main/java/io/izzel/arclight/common/mod/util/VelocitySupport.java @@ -0,0 +1,92 @@ +package io.izzel.arclight.common.mod.util; + +import com.google.common.net.InetAddresses; +import com.mojang.authlib.GameProfile; +import com.mojang.authlib.properties.Property; +import io.izzel.arclight.i18n.ArclightConfig; +import io.netty.buffer.Unpooled; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.network.protocol.login.custom.CustomQueryPayload; +import net.minecraft.network.protocol.login.custom.DiscardedQueryPayload; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.entity.player.ProfilePublicKey; + +import javax.crypto.Mac; +import javax.crypto.spec.SecretKeySpec; +import java.net.InetAddress; +import java.security.InvalidKeyException; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.UUID; + +// Codes from paper, GPLv3/MIT +// https://github.com/PaperMC/Paper/blob/master/patches/server/0832-Add-Velocity-IP-Forwarding-Support.patch +// Changes are made to use Arclight configuration system +public class VelocitySupport { + + public static boolean isEnabled() { + return ArclightConfig.spec().getVelocity().isEnable(); + } + + private static final int SUPPORTED_FORWARDING_VERSION = 1; + public static final int MODERN_FORWARDING_WITH_KEY = 2; + public static final int MODERN_FORWARDING_WITH_KEY_V2 = 3; + public static final int MODERN_LAZY_SESSION = 4; + public static final byte MAX_SUPPORTED_FORWARDING_VERSION = MODERN_LAZY_SESSION; + public static final ResourceLocation PLAYER_INFO_CHANNEL = new ResourceLocation("velocity", "player_info"); + + public static CustomQueryPayload createPacket() { + var buf = new FriendlyByteBuf(Unpooled.buffer()); + buf.writeByte(VelocitySupport.MAX_SUPPORTED_FORWARDING_VERSION); + return new DiscardedQueryPayload(PLAYER_INFO_CHANNEL, buf); + } + + public static boolean checkIntegrity(final FriendlyByteBuf buf) { + final byte[] signature = new byte[32]; + buf.readBytes(signature); + + final byte[] data = new byte[buf.readableBytes()]; + buf.getBytes(buf.readerIndex(), data); + + try { + final Mac mac = Mac.getInstance("HmacSHA256"); + mac.init(new SecretKeySpec(ArclightConfig.spec().getVelocity().getSecret().getBytes(java.nio.charset.StandardCharsets.UTF_8), "HmacSHA256")); + final byte[] mySignature = mac.doFinal(data); + if (!MessageDigest.isEqual(signature, mySignature)) { + return false; + } + } catch (final InvalidKeyException | NoSuchAlgorithmException e) { + throw new AssertionError(e); + } + + return true; + } + + public static InetAddress readAddress(final FriendlyByteBuf buf) { + return InetAddresses.forString(buf.readUtf(Short.MAX_VALUE)); + } + + public static GameProfile createProfile(final FriendlyByteBuf buf) { + final GameProfile profile = new GameProfile(buf.readUUID(), buf.readUtf(16)); + readProperties(buf, profile); + return profile; + } + + private static void readProperties(final FriendlyByteBuf buf, final GameProfile profile) { + final int properties = buf.readVarInt(); + for (int i1 = 0; i1 < properties; i1++) { + final String name = buf.readUtf(Short.MAX_VALUE); + final String value = buf.readUtf(Short.MAX_VALUE); + final String signature = buf.readBoolean() ? buf.readUtf(Short.MAX_VALUE) : null; + profile.getProperties().put(name, new Property(name, value, signature)); + } + } + + public static ProfilePublicKey.Data readForwardedKey(FriendlyByteBuf buf) { + return new ProfilePublicKey.Data(buf); + } + + public static UUID readSignerUuidOrElse(FriendlyByteBuf buf, UUID orElse) { + return buf.readBoolean() ? buf.readUUID() : orElse; + } +} diff --git a/i18n-config/src/main/java/io/izzel/arclight/i18n/conf/ConfigSpec.java b/i18n-config/src/main/java/io/izzel/arclight/i18n/conf/ConfigSpec.java index 93bedf484..8d7faf893 100644 --- a/i18n-config/src/main/java/io/izzel/arclight/i18n/conf/ConfigSpec.java +++ b/i18n-config/src/main/java/io/izzel/arclight/i18n/conf/ConfigSpec.java @@ -21,6 +21,9 @@ public class ConfigSpec { @Setting("async-catcher") private AsyncCatcherSpec asyncCatcherSpec; + @Setting("velocity") + private VelocitySpec velocitySpec; + public int getVersion() { return version; } @@ -40,4 +43,8 @@ public CompatSpec getCompat() { public AsyncCatcherSpec getAsyncCatcher() { return asyncCatcherSpec; } + + public VelocitySpec getVelocity() { + return velocitySpec; + } } diff --git a/i18n-config/src/main/java/io/izzel/arclight/i18n/conf/VelocitySpec.java b/i18n-config/src/main/java/io/izzel/arclight/i18n/conf/VelocitySpec.java new file mode 100644 index 000000000..0c2569415 --- /dev/null +++ b/i18n-config/src/main/java/io/izzel/arclight/i18n/conf/VelocitySpec.java @@ -0,0 +1,22 @@ +package io.izzel.arclight.i18n.conf; + +import ninja.leaping.configurate.objectmapping.Setting; +import ninja.leaping.configurate.objectmapping.serialize.ConfigSerializable; + +@ConfigSerializable +public class VelocitySpec { + + @Setting("enable") + private boolean enable; + + @Setting("secret") + private String secret; + + public boolean isEnable() { + return enable; + } + + public String getSecret() { + return secret; + } +} diff --git a/i18n-config/src/main/resources/META-INF/arclight.conf b/i18n-config/src/main/resources/META-INF/arclight.conf index 440ad8732..4aabd60ff 100644 --- a/i18n-config/src/main/resources/META-INF/arclight.conf +++ b/i18n-config/src/main/resources/META-INF/arclight.conf @@ -26,4 +26,8 @@ async-catcher { defaultOperation = block overrides { } +} +velocity { + enable = false + secret = "" } \ No newline at end of file diff --git a/i18n-config/src/main/resources/META-INF/i18n/en_us.conf b/i18n-config/src/main/resources/META-INF/i18n/en_us.conf index 29338d7aa..0485e2c8b 100644 --- a/i18n-config/src/main/resources/META-INF/i18n/en_us.conf +++ b/i18n-config/src/main/resources/META-INF/i18n/en_us.conf @@ -80,6 +80,8 @@ dfu-disable { map-convert = "World upgrading is not allowed when optimization.disable-data-fixer is enabled" } error-symlink = "File system do not support symbol links" +symlink-file-exist = "A file already exists when creating symlink {}" +lightcity-not-installed = "This server requires Lightcity plugin installed on your Velocity proxy" comments { _v.comment = [ @@ -132,4 +134,12 @@ comments { "valid-username-regex = \".+\"" ] } + velocity { + enable.comment = [ + "Enable Velocity modern player info forwarding" + ] + secret.comment = [ + "The secret used in Velocity modern player info forwarding" + ] + } } diff --git a/i18n-config/src/main/resources/META-INF/i18n/zh_cn.conf b/i18n-config/src/main/resources/META-INF/i18n/zh_cn.conf index 2f28ccf86..a1edbff69 100644 --- a/i18n-config/src/main/resources/META-INF/i18n/zh_cn.conf +++ b/i18n-config/src/main/resources/META-INF/i18n/zh_cn.conf @@ -79,6 +79,7 @@ dfu-disable { } error-symlink = "文件系统不支持符号链接" symlink-file-exist = "创建符号链接 {} 时文件已存在" +lightcity-not-installed = "你需要在 Velocity 上安装 Lightcity 插件" comments { _v.comment = [ @@ -136,4 +137,12 @@ comments { "valid-username-regex = \".+\"" ] } + velocity { + enable.comment = [ + "启用 Velocity 的玩家信息转发支持" + ] + secret.comment = [ + "Velocity 玩家信息转发使用的密钥" + ] + } }