From 21ae82e7c0c11ba73343996d82f8d5810d110a2d Mon Sep 17 00:00:00 2001 From: Shane Freeder Date: Thu, 14 Dec 2023 16:36:18 +0000 Subject: [PATCH 01/17] Update adventure to snapshot version --- api/build.gradle.kts | 3 ++- gradle/libs.versions.toml | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/api/build.gradle.kts b/api/build.gradle.kts index 6c99ac61e..c6e35abca 100644 --- a/api/build.gradle.kts +++ b/api/build.gradle.kts @@ -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" ) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 4ccf52cda..07f96fdbd 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -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" From a54d8c681fe82eaa0e44e29888578597fa968c1b Mon Sep 17 00:00:00 2001 From: Gero Date: Tue, 19 Dec 2023 12:13:11 +0100 Subject: [PATCH 02/17] Support conversion of mixed json array components --- .../protocol/packet/chat/ComponentHolder.java | 47 ++++++++++--------- .../proxy/component/ComponentHolderTest.java | 39 +++++++++++++++ 2 files changed, 64 insertions(+), 22 deletions(-) create mode 100644 proxy/src/test/java/com/velocitypowered/proxy/component/ComponentHolderTest.java diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/chat/ComponentHolder.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/chat/ComponentHolder.java index debbb8348..246676154 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/chat/ComponentHolder.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/chat/ComponentHolder.java @@ -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 listType = serialize(jsonArray.get(0)).type(); + List tagItems = new ArrayList<>(jsonArray.size()); + BinaryTagType 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 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(); diff --git a/proxy/src/test/java/com/velocitypowered/proxy/component/ComponentHolderTest.java b/proxy/src/test/java/com/velocitypowered/proxy/component/ComponentHolderTest.java new file mode 100644 index 000000000..9bab68a01 --- /dev/null +++ b/proxy/src/test/java/com/velocitypowered/proxy/component/ComponentHolderTest.java @@ -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 . + */ + +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 Velocity <#09add3>Server"); + ComponentHolder holder = new ComponentHolder(ProtocolVersion.MINECRAFT_1_20_3, component); + holder.getJson(); + holder.getBinaryTag(); + } +} From a3a6b3fd07082afc5c153d4d7f5cf64e9446b291 Mon Sep 17 00:00:00 2001 From: Spottedleaf Date: Fri, 22 Dec 2023 02:29:36 -0800 Subject: [PATCH 03/17] Correctly version component serialization 1.20.3+ has some additional options (EMIT_COMPACT_TEXT_COMPONENT, EMIT_HOVER_SHOW_ENTITY_ID_AS_INT_ARRAY, VALIDATE_STRICT_EVENTS) that should be disabled in older versions. The OptionState is constructed manually due to https://github.com/KyoriPowered/adventure/issues/1015, which is a bug that makes JSONOptions.BY_DATA_VERSION use incorrect options for 1.20.3+ options. This also fixes incorrect building of the ping serializer, as it should only use the component serializer to serialize Component.class and nothing else. --- .../velocitypowered/proxy/VelocityServer.java | 35 ++++++++++----- .../proxy/protocol/ProtocolUtils.java | 44 ++++++++++++++++++- 2 files changed, 68 insertions(+), 11 deletions(-) diff --git a/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java b/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java index 76d087fab..f113164b8 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java @@ -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; } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/ProtocolUtils.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/ProtocolUtils.java index bd1e6257f..04a609e93 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/ProtocolUtils.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/ProtocolUtils.java @@ -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; } From 9450e6600cd823f7cdb207fe0c6bfb392af3e862 Mon Sep 17 00:00:00 2001 From: Gero Date: Sat, 23 Dec 2023 10:58:32 +0100 Subject: [PATCH 04/17] Fix race condition when switching from config to play state --- .../connection/client/ClientConfigSessionHandler.java | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ClientConfigSessionHandler.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ClientConfigSessionHandler.java index 8281b40e9..d14e3b823 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ClientConfigSessionHandler.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ClientConfigSessionHandler.java @@ -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; @@ -186,16 +187,21 @@ public class ClientConfigSessionHandler implements MinecraftSessionHandler { * @return a future that completes when the config stage is finished */ public CompletableFuture 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; } } From 6fd03d6f5c9f92b5921d9cb88535bf57d09272bb Mon Sep 17 00:00:00 2001 From: Adrian <68704415+4drian3d@users.noreply.github.com> Date: Wed, 27 Dec 2023 13:33:44 -0500 Subject: [PATCH 05/17] Added static methods to create ArgumentBuilders directly in the BrigadierCommand class (#1161) --- .../api/command/BrigadierCommand.java | 37 ++++++++++++++++++- .../proxy/command/BrigadierCommandTests.java | 12 ++++++ 2 files changed, 47 insertions(+), 2 deletions(-) diff --git a/api/src/main/java/com/velocitypowered/api/command/BrigadierCommand.java b/api/src/main/java/com/velocitypowered/api/command/BrigadierCommand.java index a8331c5b9..365106926 100644 --- a/api/src/main/java/com/velocitypowered/api/command/BrigadierCommand.java +++ b/api/src/main/java/com/velocitypowered/api/command/BrigadierCommand.java @@ -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 builder) { + public BrigadierCommand(final @NotNull LiteralArgumentBuilder 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 node) { + public BrigadierCommand(final @NotNull LiteralCommandNode node) { this.node = Preconditions.checkNotNull(node, "node"); } @@ -52,4 +55,34 @@ public final class BrigadierCommand implements Command { public LiteralCommandNode getNode() { return node; } + + /** + * Creates a new LiteralArgumentBuilder of the required name. + * + * @param name the literal name. + * @return a new LiteralArgumentBuilder. + */ + public static LiteralArgumentBuilder 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 the ArgumentType required type + * @return a new RequiredArgumentBuilder + */ + public static RequiredArgumentBuilder requiredArgumentBuilder( + final @NotNull String name, @NotNull final ArgumentType argumentType) { + Preconditions.checkNotNull(name, "name"); + Preconditions.checkNotNull(argumentType, "argument type"); + + return RequiredArgumentBuilder.argument(name, argumentType); + } } diff --git a/proxy/src/test/java/com/velocitypowered/proxy/command/BrigadierCommandTests.java b/proxy/src/test/java/com/velocitypowered/proxy/command/BrigadierCommandTests.java index 9db03ff94..f4110ba52 100644 --- a/proxy/src/test/java/com/velocitypowered/proxy/command/BrigadierCommandTests.java +++ b/proxy/src/test/java/com/velocitypowered/proxy/command/BrigadierCommandTests.java @@ -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())); + } } From 00ef92bc5c82c46ed4770aaaf126c285a6ab705c Mon Sep 17 00:00:00 2001 From: Timon Date: Thu, 28 Dec 2023 16:37:31 +0100 Subject: [PATCH 06/17] Restore nullability for displayName in UpsertPlayerInfo (#1172) This makes the UpsertPlayerInfo's displayName truly nullable as before the ComponentHolder was introduced. --- .../proxy/tablist/VelocityTabList.java | 16 ++++++++++++---- .../proxy/tablist/VelocityTabListEntry.java | 6 +++++- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/proxy/src/main/java/com/velocitypowered/proxy/tablist/VelocityTabList.java b/proxy/src/main/java/com/velocitypowered/proxy/tablist/VelocityTabList.java index b207ce4bf..9565dc9e5 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/tablist/VelocityTabList.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/tablist/VelocityTabList.java @@ -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); diff --git a/proxy/src/main/java/com/velocitypowered/proxy/tablist/VelocityTabListEntry.java b/proxy/src/main/java/com/velocitypowered/proxy/tablist/VelocityTabListEntry.java index c6adc7d21..c6649070b 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/tablist/VelocityTabListEntry.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/tablist/VelocityTabListEntry.java @@ -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; } From de57563eaba845304cd4407ae75126ebe2def5c4 Mon Sep 17 00:00:00 2001 From: Pancake Date: Thu, 4 Jan 2024 14:50:47 +0100 Subject: [PATCH 07/17] Hide ip address in initial and legacy connections (#1025) --- .../proxy/connection/client/HandshakeSessionHandler.java | 7 ++++++- .../proxy/connection/client/InitialInboundConnection.java | 7 ++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/HandshakeSessionHandler.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/HandshakeSessionHandler.java index 079ce035f..2b5c171ae 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/HandshakeSessionHandler.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/HandshakeSessionHandler.java @@ -236,7 +236,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() : ""; + return "[legacy connection] " + playerIp; } @Override diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/InitialInboundConnection.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/InitialInboundConnection.java index 2af8c3961..5a67dab30 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/InitialInboundConnection.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/InitialInboundConnection.java @@ -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() : ""; + return "[initial connection] " + playerIp; } @Override From 28acf9eac1440947e6635d420c706326470c316a Mon Sep 17 00:00:00 2001 From: Adrian <68704415+4drian3d@users.noreply.github.com> Date: Thu, 4 Jan 2024 10:40:11 -0500 Subject: [PATCH 08/17] Removed console legacy color support (#1105) Co-authored-by: Shane Freeder <1228900+electronicboy@users.noreply.github.com> --- proxy/build.gradle.kts | 1 + proxy/log4j2-plugin/build.gradle.kts | 4 + .../proxy/util/StripAnsiConverter.java | 80 +++++++++++++++++++ .../proxy/util/TranslatableMapper.java | 1 - proxy/src/main/resources/log4j2.xml | 6 +- settings.gradle.kts | 6 ++ 6 files changed, 94 insertions(+), 4 deletions(-) create mode 100644 proxy/log4j2-plugin/build.gradle.kts create mode 100644 proxy/log4j2-plugin/src/main/java/com/velocitypowered/proxy/util/StripAnsiConverter.java diff --git a/proxy/build.gradle.kts b/proxy/build.gradle.kts index 14eb4b19d..643689d40 100644 --- a/proxy/build.gradle.kts +++ b/proxy/build.gradle.kts @@ -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) diff --git a/proxy/log4j2-plugin/build.gradle.kts b/proxy/log4j2-plugin/build.gradle.kts new file mode 100644 index 000000000..0a193944a --- /dev/null +++ b/proxy/log4j2-plugin/build.gradle.kts @@ -0,0 +1,4 @@ +dependencies { + implementation(libs.bundles.log4j) + annotationProcessor(libs.log4j.core) +} \ No newline at end of file diff --git a/proxy/log4j2-plugin/src/main/java/com/velocitypowered/proxy/util/StripAnsiConverter.java b/proxy/log4j2-plugin/src/main/java/com/velocitypowered/proxy/util/StripAnsiConverter.java new file mode 100644 index 000000000..f3722ffaa --- /dev/null +++ b/proxy/log4j2-plugin/src/main/java/com/velocitypowered/proxy/util/StripAnsiConverter.java @@ -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 . + */ + +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 Paper's patch + */ +@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 formatters; + + /** + * Constructs an instance of StripAnsiConverter. + */ + protected StripAnsiConverter(List 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 formatters = parser.parse(options[0]); + return new StripAnsiConverter(formatters); + } +} \ No newline at end of file diff --git a/proxy/src/main/java/com/velocitypowered/proxy/util/TranslatableMapper.java b/proxy/src/main/java/com/velocitypowered/proxy/util/TranslatableMapper.java index c2ca2366f..a7b9ad4b6 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/util/TranslatableMapper.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/util/TranslatableMapper.java @@ -28,7 +28,6 @@ import net.kyori.adventure.translation.TranslationRegistry; import net.kyori.adventure.translation.Translator; import org.jetbrains.annotations.Nullable; - /** * Velocity Translation Mapper. */ diff --git a/proxy/src/main/resources/log4j2.xml b/proxy/src/main/resources/log4j2.xml index b93b9a7a1..ebf86e2fc 100644 --- a/proxy/src/main/resources/log4j2.xml +++ b/proxy/src/main/resources/log4j2.xml @@ -22,10 +22,10 @@ + defaultPattern="%highlightError{[%d{HH:mm:ss} %level] [%logger]: %msg%n%xEx}"> + pattern="%highlightError{[%d{HH:mm:ss} %level]: %msg%n%xEx}"/> @@ -33,7 +33,7 @@ filePattern="logs/%d{yyyy-MM-dd}-%i.log.gz" immediateFlush="false"> + pattern="[%d{HH:mm:ss}] [%t/%level] [%logger]: %stripAnsi{%msg}%n"/> diff --git a/settings.gradle.kts b/settings.gradle.kts index 7b375a6f9..aa2367be0 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -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") From 897ff819150691c82ea70b11dd18759e08594d54 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20St=C3=A4ding?= Date: Thu, 4 Jan 2024 17:04:25 +0100 Subject: [PATCH 09/17] Clean up JavaPluginLoader and VelocityPluginModule (#1140) --- .../plugin/loader/java/JavaPluginLoader.java | 16 +++++++++------- .../plugin/loader/java/VelocityPluginModule.java | 7 ++----- 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/proxy/src/main/java/com/velocitypowered/proxy/plugin/loader/java/JavaPluginLoader.java b/proxy/src/main/java/com/velocitypowered/proxy/plugin/loader/java/JavaPluginLoader.java index 5674794ef..d783f476e 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/plugin/loader/java/JavaPluginLoader.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/plugin/loader/java/JavaPluginLoader.java @@ -63,7 +63,7 @@ public class JavaPluginLoader implements PluginLoader { public PluginDescription loadCandidate(Path source) throws Exception { Optional 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) () -> 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 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), diff --git a/proxy/src/main/java/com/velocitypowered/proxy/plugin/loader/java/VelocityPluginModule.java b/proxy/src/main/java/com/velocitypowered/proxy/plugin/loader/java/VelocityPluginModule.java index a6331dd48..6ca2a9d30 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/plugin/loader/java/VelocityPluginModule.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/plugin/loader/java/VelocityPluginModule.java @@ -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; From 408b420af7e04909ecb27cca7225875059eaca07 Mon Sep 17 00:00:00 2001 From: 0xAda Date: Thu, 4 Jan 2024 17:35:03 +0000 Subject: [PATCH 10/17] Use System.nanoTime in place of System.currentTimeMillis when comparing timestamps. (#1174) --- proxy/src/main/java/com/velocitypowered/proxy/Velocity.java | 5 +++-- .../proxy/connection/backend/BackendPlaySessionHandler.java | 2 +- .../proxy/connection/client/ClientConfigSessionHandler.java | 3 ++- .../proxy/connection/client/ClientPlaySessionHandler.java | 3 ++- 4 files changed, 8 insertions(+), 5 deletions(-) diff --git a/proxy/src/main/java/com/velocitypowered/proxy/Velocity.java b/proxy/src/main/java/com/velocitypowered/proxy/Velocity.java index 779879eeb..cd4720cf5 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/Velocity.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/Velocity.java @@ -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(); diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/BackendPlaySessionHandler.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/BackendPlaySessionHandler.java index c34b6fa7c..612b858da 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/BackendPlaySessionHandler.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/BackendPlaySessionHandler.java @@ -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 } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ClientConfigSessionHandler.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ClientConfigSessionHandler.java index d14e3b823..e2d53e989 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ClientConfigSessionHandler.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ClientConfigSessionHandler.java @@ -36,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; @@ -81,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); } } 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 08da36ab5..b79a6a8e6 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 @@ -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); } } From cc906000bcf6c978de1c230b8580758996ff9854 Mon Sep 17 00:00:00 2001 From: Adrian <68704415+4drian3d@users.noreply.github.com> Date: Thu, 11 Jan 2024 01:57:03 -0500 Subject: [PATCH 11/17] Fixed Legacy Ping handling (#1180) --- .../proxy/connection/MinecraftConnection.java | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/MinecraftConnection.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/MinecraftConnection.java index bb0c4db72..e9f503f7d 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/MinecraftConnection.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/MinecraftConnection.java @@ -362,8 +362,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 From a008464ede2c6435c28a7e957212f9977664272c Mon Sep 17 00:00:00 2001 From: Adrian <68704415+4drian3d@users.noreply.github.com> Date: Thu, 11 Jan 2024 05:38:00 -0500 Subject: [PATCH 12/17] Improved configuration migration (#1111) --- gradle/libs.versions.toml | 2 +- .../proxy/config/VelocityConfiguration.java | 232 +++++++----------- .../migration/ConfigurationMigration.java | 47 ++++ .../config/migration/ForwardingMigration.java | 65 +++++ .../migration/KeyAuthenticationMigration.java | 41 ++++ .../proxy/config/migration/MotdMigration.java | 57 +++++ 6 files changed, 300 insertions(+), 144 deletions(-) create mode 100644 proxy/src/main/java/com/velocitypowered/proxy/config/migration/ConfigurationMigration.java create mode 100644 proxy/src/main/java/com/velocitypowered/proxy/config/migration/ForwardingMigration.java create mode 100644 proxy/src/main/java/com/velocitypowered/proxy/config/migration/KeyAuthenticationMigration.java create mode 100644 proxy/src/main/java/com/velocitypowered/proxy/config/migration/MotdMigration.java diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 07f96fdbd..25f58c3ac 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -52,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" 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 3afd9fc10..e55c0e9ae 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/config/VelocityConfiguration.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/config/VelocityConfiguration.java @@ -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 = "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()))); } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/config/migration/ConfigurationMigration.java b/proxy/src/main/java/com/velocitypowered/proxy/config/migration/ConfigurationMigration.java new file mode 100644 index 000000000..2dc37cb44 --- /dev/null +++ b/proxy/src/main/java/com/velocitypowered/proxy/config/migration/ConfigurationMigration.java @@ -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 . + */ + +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; + } + } +} diff --git a/proxy/src/main/java/com/velocitypowered/proxy/config/migration/ForwardingMigration.java b/proxy/src/main/java/com/velocitypowered/proxy/config/migration/ForwardingMigration.java new file mode 100644 index 000000000..c127920d5 --- /dev/null +++ b/proxy/src/main/java/com/velocitypowered/proxy/config/migration/ForwardingMigration.java @@ -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 . + */ + +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"); + } +} diff --git a/proxy/src/main/java/com/velocitypowered/proxy/config/migration/KeyAuthenticationMigration.java b/proxy/src/main/java/com/velocitypowered/proxy/config/migration/KeyAuthenticationMigration.java new file mode 100644 index 000000000..9c9ce0836 --- /dev/null +++ b/proxy/src/main/java/com/velocitypowered/proxy/config/migration/KeyAuthenticationMigration.java @@ -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 . + */ + +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"); + } +} diff --git a/proxy/src/main/java/com/velocitypowered/proxy/config/migration/MotdMigration.java b/proxy/src/main/java/com/velocitypowered/proxy/config/migration/MotdMigration.java new file mode 100644 index 000000000..a058357e3 --- /dev/null +++ b/proxy/src/main/java/com/velocitypowered/proxy/config/migration/MotdMigration.java @@ -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 . + */ + +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"); + } +} From 02c4d61fc63ee48d6408934683e7507f71eb1f61 Mon Sep 17 00:00:00 2001 From: Mgazul Date: Thu, 11 Jan 2024 19:24:11 +0800 Subject: [PATCH 13/17] Add ModernForgeConnectionType to supports Forge-1.20.2+ (#1176) --- .../backend/VelocityServerConnection.java | 4 ++ .../client/HandshakeSessionHandler.java | 6 ++ .../modern/ModernForgeConnectionType.java | 65 +++++++++++++++++++ .../forge/modern/ModernForgeConstants.java | 25 +++++++ 4 files changed, 100 insertions(+) create mode 100644 proxy/src/main/java/com/velocitypowered/proxy/connection/forge/modern/ModernForgeConnectionType.java create mode 100644 proxy/src/main/java/com/velocitypowered/proxy/connection/forge/modern/ModernForgeConstants.java diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/VelocityServerConnection.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/VelocityServerConnection.java index 9a4b7d72d..38a442151 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/VelocityServerConnection.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/VelocityServerConnection.java @@ -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); } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/HandshakeSessionHandler.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/HandshakeSessionHandler.java index 2b5c171ae..b3897395d 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/HandshakeSessionHandler.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/HandshakeSessionHandler.java @@ -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) { diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/forge/modern/ModernForgeConnectionType.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/forge/modern/ModernForgeConnectionType.java new file mode 100644 index 000000000..d58e4eae0 --- /dev/null +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/forge/modern/ModernForgeConnectionType.java @@ -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 . + */ + +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; + } +} diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/forge/modern/ModernForgeConstants.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/forge/modern/ModernForgeConstants.java new file mode 100644 index 000000000..15add83bc --- /dev/null +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/forge/modern/ModernForgeConstants.java @@ -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 . + */ + +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"; +} From 08c03aaea2bf69673a739494e7d8884d31c5002d Mon Sep 17 00:00:00 2001 From: EpicPlayerA10 <62206933+EpicPlayerA10@users.noreply.github.com> Date: Thu, 11 Jan 2024 12:44:16 +0100 Subject: [PATCH 14/17] Fix chat race condition (#1042) --- .../proxy/connection/MinecraftConnection.java | 9 ++++-- .../proxy/protocol/packet/chat/ChatQueue.java | 32 ++++++++++++------- 2 files changed, 27 insertions(+), 14 deletions(-) diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/MinecraftConnection.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/MinecraftConnection.java index e9f503f7d..aefddd157 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/MinecraftConnection.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/MinecraftConnection.java @@ -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; } } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/chat/ChatQueue.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/chat/ChatQueue.java index 67242d9a5..2ddcff17d 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/chat/ChatQueue.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/chat/ChatQueue.java @@ -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 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 writePacket(MinecraftConnection connection) { - return (wrappedPacket, throwable) -> { - if (wrappedPacket != null && !connection.isClosed()) { - wrappedPacket.write(connection); + private static Function writePacket(MinecraftConnection connection) { + return wrappedPacket -> { + if (!connection.isClosed()) { + ChannelFuture future = wrappedPacket.write(connection); + if (future != null) { + future.awaitUninterruptibly(); + } } + + return wrappedPacket; }; } - private static void awaitChat( + private static CompletableFuture awaitChat( MinecraftConnection connection, CompletableFuture binder, CompletableFuture 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 CompletableFuture 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 wrap(Instant timestamp, From dab64ebb595a31da952d59d5d8ecf9aaaba9b979 Mon Sep 17 00:00:00 2001 From: WonderfulPanic <152610174+WonderfulPanic@users.noreply.github.com> Date: Wed, 17 Jan 2024 01:06:05 +1200 Subject: [PATCH 15/17] Fix player list header/footer not sending translated version (#1186) --- .../proxy/connection/client/ConnectedPlayer.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) 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 5bd49bd9e..e531c2101 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 @@ -418,7 +418,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())); } } @@ -1290,4 +1291,4 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player, connectWithIndication(); } } -} \ No newline at end of file +} From f8cdf4fa1aab10cf7e86b715717c12338896824e Mon Sep 17 00:00:00 2001 From: Spongecade Date: Tue, 16 Jan 2024 07:07:14 -0600 Subject: [PATCH 16/17] [ci skip] Update Minecraft wiki references (#1092) The Minecraft Fandom wiki has been forked to a new domain: minecraft.wiki. Learn more here: https://minecraft.wiki/w/Minecraft_Wiki:Moving_from_Fandom. This PR updates all references accordingly. Let me know if you want me to open this PR on other branches! --- .../main/java/com/velocitypowered/proxy/util/CharacterUtil.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proxy/src/main/java/com/velocitypowered/proxy/util/CharacterUtil.java b/proxy/src/main/java/com/velocitypowered/proxy/util/CharacterUtil.java index 2193860a3..107b6a395 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/util/CharacterUtil.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/util/CharacterUtil.java @@ -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; } From fe052e516323929e6e2f09a5632fc6620bee5e2e Mon Sep 17 00:00:00 2001 From: VelVeV <147647046+VelVeV@users.noreply.github.com> Date: Tue, 16 Jan 2024 22:19:45 +0900 Subject: [PATCH 17/17] Fix tablist header and footer desync (#1103) --- .../connection/backend/TransitionSessionHandler.java | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/TransitionSessionHandler.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/TransitionSessionHandler.java index 0f32fbefb..02203478f 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/TransitionSessionHandler.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/TransitionSessionHandler.java @@ -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()