From b73868205c0152411822bc6dd66690834b464067 Mon Sep 17 00:00:00 2001 From: valaphee <32491319+valaphee@users.noreply.github.com> Date: Tue, 17 Dec 2024 15:08:48 +0100 Subject: [PATCH] Remove most-times unnecessary client reconfiguration --- .../connection/MinecraftSessionHandler.java | 5 + .../backend/BackendPlaySessionHandler.java | 11 ++ .../backend/ConfigSessionHandler.java | 60 ++++++--- .../backend/LoginSessionHandler.java | 5 +- .../client/ClientConfigSessionHandler.java | 16 +-- .../client/ClientPlaySessionHandler.java | 16 +++ .../proxy/protocol/StateRegistry.java | 19 +++ .../protocol/packet/ObjectivePacket.java | 123 ++++++++++++++++++ 8 files changed, 227 insertions(+), 28 deletions(-) create mode 100644 proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/ObjectivePacket.java diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/MinecraftSessionHandler.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/MinecraftSessionHandler.java index b36d9f0ab4..6a36d9f397 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/MinecraftSessionHandler.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/MinecraftSessionHandler.java @@ -37,6 +37,7 @@ import com.velocitypowered.proxy.protocol.packet.LoginAcknowledgedPacket; import com.velocitypowered.proxy.protocol.packet.LoginPluginMessagePacket; import com.velocitypowered.proxy.protocol.packet.LoginPluginResponsePacket; +import com.velocitypowered.proxy.protocol.packet.ObjectivePacket; import com.velocitypowered.proxy.protocol.packet.PingIdentifyPacket; import com.velocitypowered.proxy.protocol.packet.PluginMessagePacket; import com.velocitypowered.proxy.protocol.packet.RemovePlayerInfoPacket; @@ -364,4 +365,8 @@ default boolean handle(ClientboundCustomReportDetailsPacket packet) { default boolean handle(ClientboundServerLinksPacket packet) { return false; } + + default boolean handle(ObjectivePacket packet) { + return false; + } } 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 128d5c370a..dbeb3453c2 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 @@ -56,6 +56,7 @@ import com.velocitypowered.proxy.protocol.packet.DisconnectPacket; import com.velocitypowered.proxy.protocol.packet.KeepAlivePacket; import com.velocitypowered.proxy.protocol.packet.LegacyPlayerListItemPacket; +import com.velocitypowered.proxy.protocol.packet.ObjectivePacket; import com.velocitypowered.proxy.protocol.packet.PluginMessagePacket; import com.velocitypowered.proxy.protocol.packet.RemovePlayerInfoPacket; import com.velocitypowered.proxy.protocol.packet.RemoveResourcePackPacket; @@ -183,6 +184,16 @@ public boolean handle(BossBarPacket packet) { return false; // forward } + @Override + public boolean handle(ObjectivePacket packet) { + if (packet.getAction() == ObjectivePacket.ADD) { + playerSessionHandler.getServerObjectives().add(packet.getName()); + } else if (packet.getAction() == ObjectivePacket.REMOVE) { + playerSessionHandler.getServerObjectives().remove(packet.getName()); + } + return false; // forward + } + @Override public boolean handle(final ResourcePackRequestPacket packet) { final ResourcePackInfo.Builder builder = new VelocityResourcePackInfo.BuilderImpl( diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/ConfigSessionHandler.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/ConfigSessionHandler.java index 74f0576c17..dd250cf5e3 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/ConfigSessionHandler.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/ConfigSessionHandler.java @@ -36,6 +36,7 @@ import com.velocitypowered.proxy.connection.util.ConnectionRequestResults; import com.velocitypowered.proxy.connection.util.ConnectionRequestResults.Impl; import com.velocitypowered.proxy.protocol.MinecraftPacket; +import com.velocitypowered.proxy.protocol.ProtocolUtils; import com.velocitypowered.proxy.protocol.StateRegistry; import com.velocitypowered.proxy.protocol.netty.MinecraftDecoder; import com.velocitypowered.proxy.protocol.packet.ClientboundCookieRequestPacket; @@ -50,6 +51,7 @@ import com.velocitypowered.proxy.protocol.packet.config.ClientboundCustomReportDetailsPacket; import com.velocitypowered.proxy.protocol.packet.config.ClientboundServerLinksPacket; import com.velocitypowered.proxy.protocol.packet.config.FinishedUpdatePacket; +import com.velocitypowered.proxy.protocol.packet.config.KnownPacksPacket; import com.velocitypowered.proxy.protocol.packet.config.RegistrySyncPacket; import com.velocitypowered.proxy.protocol.packet.config.StartUpdatePacket; import com.velocitypowered.proxy.protocol.packet.config.TagsUpdatePacket; @@ -57,6 +59,8 @@ import java.io.IOException; import java.net.InetSocketAddress; import java.util.concurrent.CompletableFuture; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; import net.kyori.adventure.key.Key; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -226,24 +230,21 @@ public boolean handle(RemoveResourcePackPacket packet) { public boolean handle(FinishedUpdatePacket packet) { final MinecraftConnection smc = serverConn.ensureConnected(); final ConnectedPlayer player = serverConn.getPlayer(); - final ClientConfigSessionHandler configHandler = (ClientConfigSessionHandler) player.getConnection().getActiveSessionHandler(); smc.getChannel().pipeline().get(MinecraftDecoder.class).setState(StateRegistry.PLAY); - //noinspection DataFlowIssue - configHandler.handleBackendFinishUpdate(serverConn).thenRunAsync(() -> { - smc.write(FinishedUpdatePacket.INSTANCE); - if (serverConn == player.getConnectedServer()) { - smc.setActiveSessionHandler(StateRegistry.PLAY); - player.sendPlayerListHeaderAndFooter(player.getPlayerListHeader(), player.getPlayerListFooter()); - // The client cleared the tab list. TODO: Restore changes done via TabList API - player.getTabList().clearAllSilent(); - } else { - smc.setActiveSessionHandler(StateRegistry.PLAY, new TransitionSessionHandler(server, serverConn, resultFuture)); - } - if (player.resourcePackHandler().getFirstAppliedPack() == null && resourcePackToApply != null) { - player.resourcePackHandler().queueResourcePack(resourcePackToApply); + if (player.getConnection().getActiveSessionHandler() instanceof ClientConfigSessionHandler configHandler) { + configHandler.handleBackendFinishUpdate(serverConn).thenRunAsync(this::finish, smc.eventLoop()); + } else { + final String brand = serverConn.getPlayer().getClientBrand(); + if (brand != null) { + final ByteBuf buf = Unpooled.buffer(); + ProtocolUtils.writeString(buf, brand); + final PluginMessagePacket brandPacket = new PluginMessagePacket("minecraft:brand", buf); + smc.write(brandPacket); } - }, smc.eventLoop()); + + finish(); + } return true; } @@ -296,6 +297,17 @@ public boolean handle(TransferPacket packet) { return true; } + @Override + public boolean handle(KnownPacksPacket packet) { + // Server expects us to reply to this packet + if (serverConn.getPlayer().getConnection().getState() != StateRegistry.CONFIG) { + // TODO: just replay the first packet the user sent + serverConn.ensureConnected().write(packet); + return true; + } + return false; // forward + } + @Override public boolean handle(ClientboundStoreCookiePacket packet) { server.getEventManager() @@ -348,6 +360,24 @@ private void switchFailure(Throwable cause) { resultFuture.completeExceptionally(cause); } + private void finish() { + final MinecraftConnection smc = serverConn.ensureConnected(); + final ConnectedPlayer player = serverConn.getPlayer(); + + smc.write(FinishedUpdatePacket.INSTANCE); + if (serverConn == player.getConnectedServer()) { + smc.setActiveSessionHandler(StateRegistry.PLAY); + player.sendPlayerListHeaderAndFooter(player.getPlayerListHeader(), player.getPlayerListFooter()); + // The client cleared the tab list. TODO: Restore changes done via TabList API + player.getTabList().clearAllSilent(); + } else { + smc.setActiveSessionHandler(StateRegistry.PLAY, new TransitionSessionHandler(server, serverConn, resultFuture)); + } + if (player.resourcePackHandler().getFirstAppliedPack() == null && resourcePackToApply != null) { + player.resourcePackHandler().queueResourcePack(resourcePackToApply); + } + } + /** * Represents the state of the configuration stage. */ diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/LoginSessionHandler.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/LoginSessionHandler.java index 612e9c25a0..ac32c49f0c 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/LoginSessionHandler.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/LoginSessionHandler.java @@ -163,10 +163,7 @@ public boolean handle(ServerLoginSuccessPacket packet) { if (player.getClientSettingsPacket() != null) { smc.write(player.getClientSettingsPacket()); } - if (player.getConnection().getActiveSessionHandler() instanceof ClientPlaySessionHandler clientPlaySessionHandler) { - smc.setAutoReading(false); - clientPlaySessionHandler.doSwitch().thenAcceptAsync((unused) -> smc.setAutoReading(true), smc.eventLoop()); - } else { + if (!(player.getConnection().getActiveSessionHandler() instanceof ClientPlaySessionHandler)) { // Initial login - the player is already in configuration state. server.getEventManager().fireAndForget(new PlayerEnteredConfigurationEvent(player, serverConn)); } 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 7bb7bedfa2..f7e91e984f 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 @@ -125,8 +125,9 @@ public boolean handle(final PluginMessagePacket packet) { // but at this time the backend server may not be ready } else if (serverConn != null) { serverConn.ensureConnected().write(packet.retain()); + return true; } - return true; + return false; } @Override @@ -141,14 +142,11 @@ public boolean handle(PingIdentifyPacket packet) { @Override public boolean handle(KnownPacksPacket packet) { - callConfigurationEvent().thenRun(() -> { - player.getConnectionInFlightOrConnectedServer().ensureConnected().write(packet); - }).exceptionally(ex -> { - logger.error("Error forwarding known packs response to backend:", ex); - return null; - }); - - return true; + if (player.getConnectionInFlight() != null) { + player.getConnectionInFlight().ensureConnected().write(packet); + return true; + } + return false; } @Override 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 b41a24ccfb..918d2ffb8a 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 @@ -47,6 +47,7 @@ import com.velocitypowered.proxy.protocol.packet.ClientSettingsPacket; import com.velocitypowered.proxy.protocol.packet.JoinGamePacket; import com.velocitypowered.proxy.protocol.packet.KeepAlivePacket; +import com.velocitypowered.proxy.protocol.packet.ObjectivePacket; import com.velocitypowered.proxy.protocol.packet.PluginMessagePacket; import com.velocitypowered.proxy.protocol.packet.ResourcePackResponsePacket; import com.velocitypowered.proxy.protocol.packet.RespawnPacket; @@ -81,8 +82,10 @@ import java.time.Instant; import java.util.ArrayList; import java.util.Collection; +import java.util.HashSet; import java.util.List; import java.util.Queue; +import java.util.Set; import java.util.UUID; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentLinkedQueue; @@ -104,6 +107,7 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler { private final ConnectedPlayer player; private boolean spawned = false; private final List serverBossBars = new ArrayList<>(); + private final Set serverObjectives = new HashSet<>(); private final Queue loginPluginMessages = new ConcurrentLinkedQueue<>(); private final VelocityServer server; private @Nullable TabCompleteRequestPacket outstandingTabComplete; @@ -526,6 +530,7 @@ public CompletableFuture doSwitch() { // Config state clears everything in the client. No need to clear later. spawned = false; serverBossBars.clear(); + serverObjectives.clear(); player.clearPlayerListHeaderAndFooterSilent(); player.getTabList().clearAllSilent(); } @@ -574,6 +579,13 @@ public void handleBackendJoinGame(JoinGamePacket joinGame, VelocityServerConnect player.getConnection().delayedWrite(deletePacket); } serverBossBars.clear(); + for (String serverObjective : serverObjectives) { + ObjectivePacket deletePacket = new ObjectivePacket(); + deletePacket.setName(serverObjective); + deletePacket.setAction(ObjectivePacket.REMOVE); + player.getConnection().delayedWrite(deletePacket); + } + serverObjectives.clear(); // Tell the server about the proxy's plugin message channels. ProtocolVersion serverVersion = serverMc.getProtocolVersion(); @@ -645,6 +657,10 @@ public List getServerBossBars() { return serverBossBars; } + public Set getServerObjectives() { + return serverObjectives; + } + private boolean handleCommandTabComplete(TabCompleteRequestPacket packet) { // In 1.13+, we need to do additional work for the richer suggestions available. String command = packet.getCommand().substring(1); diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/StateRegistry.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/StateRegistry.java index 5a2fb8b866..9b02a6c710 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/StateRegistry.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/StateRegistry.java @@ -68,6 +68,7 @@ import com.velocitypowered.proxy.protocol.packet.LoginAcknowledgedPacket; import com.velocitypowered.proxy.protocol.packet.LoginPluginMessagePacket; import com.velocitypowered.proxy.protocol.packet.LoginPluginResponsePacket; +import com.velocitypowered.proxy.protocol.packet.ObjectivePacket; import com.velocitypowered.proxy.protocol.packet.PingIdentifyPacket; import com.velocitypowered.proxy.protocol.packet.PluginMessagePacket; import com.velocitypowered.proxy.protocol.packet.RemovePlayerInfoPacket; @@ -706,6 +707,24 @@ public enum StateRegistry { ClientboundServerLinksPacket::new, map(0x7B, MINECRAFT_1_21, false), map(0x82, MINECRAFT_1_21_2, false)); + clientbound.register( + ObjectivePacket.class, + ObjectivePacket::new, + map(0x3B, MINECRAFT_1_8, false), + map(0x3F, MINECRAFT_1_9, false), + map(0x41, MINECRAFT_1_12, false), + map(0x42, MINECRAFT_1_12_1, false), + map(0x45, MINECRAFT_1_13, false), + map(0x49, MINECRAFT_1_14, false), + map(0x4A, MINECRAFT_1_15, false), + map(0x53, MINECRAFT_1_17, false), + map(0x56, MINECRAFT_1_19_1, false), + map(0x54, MINECRAFT_1_19_3, false), + map(0x58, MINECRAFT_1_19_4, false), + map(0x5A, MINECRAFT_1_20_2, false), + map(0x5C, MINECRAFT_1_20_3, false), + map(0x5E, MINECRAFT_1_20_5, false), + map(0x64, MINECRAFT_1_21_2, false)); } }, LOGIN { diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/ObjectivePacket.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/ObjectivePacket.java new file mode 100644 index 0000000000..14724ff4f8 --- /dev/null +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/ObjectivePacket.java @@ -0,0 +1,123 @@ +/* + * 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.protocol.packet; + +import com.velocitypowered.api.network.ProtocolVersion; +import com.velocitypowered.proxy.connection.MinecraftSessionHandler; +import com.velocitypowered.proxy.protocol.*; +import com.velocitypowered.proxy.protocol.packet.chat.ComponentHolder; +import io.netty.buffer.ByteBuf; +import net.kyori.adventure.nbt.BinaryTag; +import net.kyori.adventure.nbt.BinaryTagIO; + +public class ObjectivePacket implements MinecraftPacket { + + public static final byte ADD = 0; + public static final byte REMOVE = 1; + public static final byte UPDATE = 2; + + private String name; + private byte action; + private String value; + private ComponentHolder componentValue; + private String type; + private int intType; + private boolean numberFormat; + private ComponentHolder numberFormatFixed; + private BinaryTag numberFormatStyled; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public byte getAction() { + return action; + } + + public void setAction(byte action) { + this.action = action; + } + + @Override + public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) { + name = ProtocolUtils.readString(buf); + action = buf.readByte(); + if (action == ADD || action == UPDATE) { + if (version.noLessThan(ProtocolVersion.MINECRAFT_1_13)) { + componentValue = ComponentHolder.read(buf, version); + intType = ProtocolUtils.readVarInt(buf); + } else { + value = ProtocolUtils.readString(buf); + type = ProtocolUtils.readString(buf); + } + if (version.noLessThan(ProtocolVersion.MINECRAFT_1_20_3)) { + numberFormat = buf.readBoolean(); + if (numberFormat) { + switch (ProtocolUtils.readVarInt(buf)) { + case 0: + break; + case 1: + numberFormatStyled = ProtocolUtils.readBinaryTag(buf, version, BinaryTagIO.reader()); + break; + case 2: + numberFormatFixed = ComponentHolder.read(buf, version); + break; + } + } + } + } + } + + @Override + public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) { + ProtocolUtils.writeString(buf, name); + buf.writeByte(action); + if (action == ADD || action == UPDATE) { + if (version.noLessThan(ProtocolVersion.MINECRAFT_1_13)) { + componentValue.write(buf); + ProtocolUtils.writeVarInt(buf, intType); + } else { + ProtocolUtils.writeString(buf, value); + ProtocolUtils.writeString(buf, type); + } + if (version.noLessThan(ProtocolVersion.MINECRAFT_1_20_3)) { + buf.writeBoolean(numberFormat); + if (numberFormat) { + if (numberFormatStyled != null) { + ProtocolUtils.writeVarInt(buf, 1); + ProtocolUtils.writeBinaryTag(buf, version, numberFormatStyled); + } else if (numberFormatFixed != null) { + ProtocolUtils.writeVarInt(buf, 2); + numberFormatFixed.write(buf); + } else { + ProtocolUtils.writeVarInt(buf, 0); + } + } + } + } + } + + @Override + public boolean handle(MinecraftSessionHandler handler) { + return handler.handle(this); + } +}