diff --git a/api/src/main/java/com/velocitypowered/api/event/proxy/ProxyPingEvent.java b/api/src/main/java/com/velocitypowered/api/event/proxy/ProxyPingEvent.java index b7e040eef..d8b89b2be 100644 --- a/api/src/main/java/com/velocitypowered/api/event/proxy/ProxyPingEvent.java +++ b/api/src/main/java/com/velocitypowered/api/event/proxy/ProxyPingEvent.java @@ -13,10 +13,11 @@ import com.velocitypowered.api.proxy.InboundConnection; import com.velocitypowered.api.proxy.server.ServerPing; /** - * This event is fired when a server list ping request is sent by a remote client. Velocity will + * This event is fired when a request for server information is sent by a remote client, or when the + * server sends the MOTD and favicon to the client after a successful login. Velocity will * wait on this event to finish firing before delivering the results to the remote client, but - * you are urged to be as parsimonious as possible when handling this event due to the amount of - * ping packets a client can send. + * you are urged to handle this event as quickly as possible when handling this event due to the + * amount of ping packets a client can send. */ @AwaitingEvent public final class ProxyPingEvent { diff --git a/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java b/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java index c3272c85a..2d36cf8ef 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java @@ -44,6 +44,7 @@ import com.velocitypowered.proxy.command.builtin.VelocityCommand; import com.velocitypowered.proxy.config.VelocityConfiguration; import com.velocitypowered.proxy.connection.client.ConnectedPlayer; import com.velocitypowered.proxy.connection.player.VelocityResourcePackInfo; +import com.velocitypowered.proxy.connection.util.ServerListPingHandler; import com.velocitypowered.proxy.console.VelocityConsole; import com.velocitypowered.proxy.crypto.EncryptionUtils; import com.velocitypowered.proxy.event.VelocityEventManager; @@ -142,6 +143,7 @@ public class VelocityServer implements ProxyServer, ForwardingAudience { private final VelocityEventManager eventManager; private final VelocityScheduler scheduler; private final VelocityChannelRegistrar channelRegistrar = new VelocityChannelRegistrar(); + private ServerListPingHandler serverListPingHandler; VelocityServer(final ProxyOptions options) { pluginManager = new VelocityPluginManager(this); @@ -151,6 +153,7 @@ public class VelocityServer implements ProxyServer, ForwardingAudience { console = new VelocityConsole(this); cm = new ConnectionManager(this); servers = new ServerMap(this); + serverListPingHandler = new ServerListPingHandler(this); this.options = options; this.bossBarManager = new AdventureBossBarManager(); } @@ -370,6 +373,10 @@ public class VelocityServer implements ProxyServer, ForwardingAudience { return this.cm.backendChannelInitializer.get(); } + public ServerListPingHandler getServerListPingHandler() { + return serverListPingHandler; + } + public boolean isShutdown() { return shutdown; } 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 bdd3b5a0b..63d3c36a7 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.PluginMessage; import com.velocitypowered.proxy.protocol.packet.ResourcePackRequest; import com.velocitypowered.proxy.protocol.packet.ResourcePackResponse; import com.velocitypowered.proxy.protocol.packet.Respawn; +import com.velocitypowered.proxy.protocol.packet.ServerData; import com.velocitypowered.proxy.protocol.packet.ServerLogin; import com.velocitypowered.proxy.protocol.packet.ServerLoginSuccess; import com.velocitypowered.proxy.protocol.packet.SetCompression; @@ -261,4 +262,8 @@ public interface MinecraftSessionHandler { default boolean handle(PlayerCommand packet) { return false; } + + default boolean handle(ServerData serverData) { + 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 c485be158..7d9e9f1c9 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 @@ -27,6 +27,7 @@ import com.velocitypowered.api.event.command.PlayerAvailableCommandsEvent; import com.velocitypowered.api.event.connection.PluginMessageEvent; import com.velocitypowered.api.event.player.PlayerResourcePackStatusEvent; import com.velocitypowered.api.event.player.ServerResourcePackSendEvent; +import com.velocitypowered.api.event.proxy.ProxyPingEvent; import com.velocitypowered.api.network.ProtocolVersion; import com.velocitypowered.api.proxy.messages.ChannelIdentifier; import com.velocitypowered.api.proxy.player.ResourcePackInfo; @@ -46,6 +47,7 @@ import com.velocitypowered.proxy.protocol.packet.PlayerListItem; import com.velocitypowered.proxy.protocol.packet.PluginMessage; import com.velocitypowered.proxy.protocol.packet.ResourcePackRequest; import com.velocitypowered.proxy.protocol.packet.ResourcePackResponse; +import com.velocitypowered.proxy.protocol.packet.ServerData; import com.velocitypowered.proxy.protocol.packet.TabCompleteResponse; import com.velocitypowered.proxy.protocol.util.PluginMessageUtil; import io.netty.buffer.ByteBuf; @@ -268,6 +270,22 @@ public class BackendPlaySessionHandler implements MinecraftSessionHandler { return true; } + @Override + public boolean handle(ServerData packet) { + server.getServerListPingHandler().getInitialPing(this.serverConn.getPlayer()) + .thenComposeAsync( + ping -> server.getEventManager().fire(new ProxyPingEvent(this.serverConn.getPlayer(), ping)), + playerConnection.eventLoop() + ) + .thenAcceptAsync(pingEvent -> + this.playerConnection.write( + new ServerData(pingEvent.getPing().getDescriptionComponent(), + pingEvent.getPing().getFavicon().orElse(null), + packet.isPreviewsChat()) + ), playerConnection.eventLoop()); + return true; + } + @Override public void handleGeneric(MinecraftPacket packet) { if (packet instanceof PluginMessage) { 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 33cae2f9a..8c63673bd 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 @@ -56,6 +56,7 @@ import com.velocitypowered.proxy.connection.backend.VelocityServerConnection; import com.velocitypowered.proxy.connection.player.VelocityResourcePackInfo; import com.velocitypowered.proxy.connection.util.ConnectionMessages; import com.velocitypowered.proxy.connection.util.ConnectionRequestResults.Impl; +import com.velocitypowered.proxy.connection.util.VelocityInboundConnection; import com.velocitypowered.proxy.protocol.ProtocolUtils; import com.velocitypowered.proxy.protocol.StateRegistry; import com.velocitypowered.proxy.protocol.packet.ClientSettings; @@ -112,7 +113,8 @@ import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; import org.jetbrains.annotations.NotNull; -public class ConnectedPlayer implements MinecraftConnectionAssociation, Player, KeyIdentifiable { +public class ConnectedPlayer implements MinecraftConnectionAssociation, Player, KeyIdentifiable, + VelocityInboundConnection { private static final int MAX_PLUGIN_CHANNELS = 1024; private static final PlainTextComponentSerializer PASS_THRU_TRANSLATE = PlainTextComponentSerializer.builder() 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 2870b64bc..0d578a4f1 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 @@ -29,6 +29,7 @@ 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.util.VelocityInboundConnection; import com.velocitypowered.proxy.protocol.MinecraftPacket; import com.velocitypowered.proxy.protocol.StateRegistry; import com.velocitypowered.proxy.protocol.packet.Handshake; @@ -60,7 +61,7 @@ public class HandshakeSessionHandler implements MinecraftSessionHandler { @Override public boolean handle(LegacyPing packet) { connection.setProtocolVersion(ProtocolVersion.LEGACY); - StatusSessionHandler handler = new StatusSessionHandler(server, connection, + StatusSessionHandler handler = new StatusSessionHandler(server, new LegacyInboundConnection(connection, packet)); connection.setSessionHandler(handler); handler.handle(packet); @@ -91,7 +92,7 @@ public class HandshakeSessionHandler implements MinecraftSessionHandler { switch (nextState) { case STATUS: - connection.setSessionHandler(new StatusSessionHandler(server, connection, ic)); + connection.setSessionHandler(new StatusSessionHandler(server, ic)); break; case LOGIN: this.handleLogin(handshake, ic); @@ -197,7 +198,7 @@ public class HandshakeSessionHandler implements MinecraftSessionHandler { connection.close(true); } - private static class LegacyInboundConnection implements InboundConnection { + private static class LegacyInboundConnection implements VelocityInboundConnection { private final MinecraftConnection connection; private final LegacyPing ping; @@ -232,5 +233,10 @@ public class HandshakeSessionHandler implements MinecraftSessionHandler { public String toString() { return "[legacy connection] " + this.getRemoteAddress().toString(); } + + @Override + public MinecraftConnection getConnection() { + return connection; + } } } 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 d243e339e..d350c4841 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 @@ -21,6 +21,7 @@ import com.velocitypowered.api.network.ProtocolVersion; import com.velocitypowered.api.proxy.InboundConnection; import com.velocitypowered.proxy.connection.MinecraftConnection; import com.velocitypowered.proxy.connection.MinecraftConnectionAssociation; +import com.velocitypowered.proxy.connection.util.VelocityInboundConnection; import com.velocitypowered.proxy.protocol.packet.Disconnect; import com.velocitypowered.proxy.protocol.packet.Handshake; import com.velocitypowered.proxy.util.ClosestLocaleMatcher; @@ -33,7 +34,7 @@ import net.kyori.adventure.translation.GlobalTranslator; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -public final class InitialInboundConnection implements InboundConnection, +public final class InitialInboundConnection implements VelocityInboundConnection, MinecraftConnectionAssociation { private static final Logger logger = LogManager.getLogger(InitialInboundConnection.class); @@ -74,6 +75,7 @@ public final class InitialInboundConnection implements InboundConnection, return "[initial connection] " + connection.getRemoteAddress().toString(); } + @Override public MinecraftConnection getConnection() { return connection; } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/StatusSessionHandler.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/StatusSessionHandler.java index 792c5b458..328f9492d 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/StatusSessionHandler.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/StatusSessionHandler.java @@ -17,33 +17,18 @@ package com.velocitypowered.proxy.connection.client; -import com.google.common.collect.ImmutableList; -import com.spotify.futures.CompletableFutures; import com.velocitypowered.api.event.proxy.ProxyPingEvent; -import com.velocitypowered.api.network.ProtocolVersion; -import com.velocitypowered.api.proxy.InboundConnection; -import com.velocitypowered.api.proxy.server.RegisteredServer; -import com.velocitypowered.api.proxy.server.ServerPing; -import com.velocitypowered.api.util.ModInfo; import com.velocitypowered.proxy.VelocityServer; -import com.velocitypowered.proxy.config.PingPassthroughMode; -import com.velocitypowered.proxy.config.VelocityConfiguration; import com.velocitypowered.proxy.connection.MinecraftConnection; import com.velocitypowered.proxy.connection.MinecraftSessionHandler; +import com.velocitypowered.proxy.connection.util.VelocityInboundConnection; import com.velocitypowered.proxy.protocol.packet.LegacyDisconnect; import com.velocitypowered.proxy.protocol.packet.LegacyPing; import com.velocitypowered.proxy.protocol.packet.StatusPing; import com.velocitypowered.proxy.protocol.packet.StatusRequest; import com.velocitypowered.proxy.protocol.packet.StatusResponse; -import com.velocitypowered.proxy.server.VelocityRegisteredServer; import com.velocitypowered.proxy.util.except.QuietRuntimeException; import io.netty.buffer.ByteBuf; -import java.net.InetSocketAddress; -import java.util.ArrayList; -import java.util.List; -import java.util.Locale; -import java.util.Optional; -import java.util.concurrent.CompletableFuture; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -55,13 +40,12 @@ public class StatusSessionHandler implements MinecraftSessionHandler { private final VelocityServer server; private final MinecraftConnection connection; - private final InboundConnection inbound; + private final VelocityInboundConnection inbound; private boolean pingReceived = false; - StatusSessionHandler(VelocityServer server, MinecraftConnection connection, - InboundConnection inbound) { + StatusSessionHandler(VelocityServer server, VelocityInboundConnection inbound) { this.server = server; - this.connection = connection; + this.connection = inbound.getConnection(); this.inbound = inbound; } @@ -73,116 +57,13 @@ public class StatusSessionHandler implements MinecraftSessionHandler { } } - private ServerPing constructLocalPing(ProtocolVersion version) { - VelocityConfiguration configuration = server.getConfiguration(); - return new ServerPing( - new ServerPing.Version(version.getProtocol(), - "Velocity " + ProtocolVersion.SUPPORTED_VERSION_STRING), - new ServerPing.Players(server.getPlayerCount(), configuration.getShowMaxPlayers(), - ImmutableList.of()), - configuration.getMotd(), - configuration.getFavicon().orElse(null), - configuration.isAnnounceForge() ? ModInfo.DEFAULT : null - ); - } - - private CompletableFuture attemptPingPassthrough(PingPassthroughMode mode, - List servers, ProtocolVersion pingingVersion) { - ServerPing fallback = constructLocalPing(pingingVersion); - List> pings = new ArrayList<>(); - for (String s : servers) { - Optional rs = server.getServer(s); - if (!rs.isPresent()) { - continue; - } - VelocityRegisteredServer vrs = (VelocityRegisteredServer) rs.get(); - pings.add(vrs.ping(connection.eventLoop(), pingingVersion)); - } - if (pings.isEmpty()) { - return CompletableFuture.completedFuture(fallback); - } - - CompletableFuture> pingResponses = CompletableFutures.successfulAsList(pings, - (ex) -> fallback); - switch (mode) { - case ALL: - return pingResponses.thenApply(responses -> { - // Find the first non-fallback - for (ServerPing response : responses) { - if (response == fallback) { - continue; - } - return response; - } - return fallback; - }); - case MODS: - return pingResponses.thenApply(responses -> { - // Find the first non-fallback that contains a mod list - for (ServerPing response : responses) { - if (response == fallback) { - continue; - } - Optional modInfo = response.getModinfo(); - if (modInfo.isPresent()) { - return fallback.asBuilder().mods(modInfo.get()).build(); - } - } - return fallback; - }); - case DESCRIPTION: - return pingResponses.thenApply(responses -> { - // Find the first non-fallback. If it includes a modlist, add it too. - for (ServerPing response : responses) { - if (response == fallback) { - continue; - } - - if (response.getDescriptionComponent() == null) { - continue; - } - - return new ServerPing( - fallback.getVersion(), - fallback.getPlayers().orElse(null), - response.getDescriptionComponent(), - fallback.getFavicon().orElse(null), - response.getModinfo().orElse(null) - ); - } - return fallback; - }); - default: - // Not possible, but covered for completeness. - return CompletableFuture.completedFuture(fallback); - } - } - - private CompletableFuture getInitialPing() { - VelocityConfiguration configuration = server.getConfiguration(); - ProtocolVersion shownVersion = ProtocolVersion.isSupported(connection.getProtocolVersion()) - ? connection.getProtocolVersion() : ProtocolVersion.MAXIMUM_VERSION; - PingPassthroughMode passthrough = configuration.getPingPassthrough(); - - if (passthrough == PingPassthroughMode.DISABLED) { - return CompletableFuture.completedFuture(constructLocalPing(shownVersion)); - } else { - String virtualHostStr = inbound.getVirtualHost().map(InetSocketAddress::getHostString) - .map(str -> str.toLowerCase(Locale.ROOT)) - .orElse(""); - List serversToTry = server.getConfiguration().getForcedHosts().getOrDefault( - virtualHostStr, server.getConfiguration().getAttemptConnectionOrder()); - return attemptPingPassthrough(configuration.getPingPassthrough(), serversToTry, shownVersion); - } - } - @Override public boolean handle(LegacyPing packet) { if (this.pingReceived) { throw EXPECTED_AWAITING_REQUEST; } this.pingReceived = true; - getInitialPing() + server.getServerListPingHandler().getInitialPing(this.inbound) .thenCompose(ping -> server.getEventManager().fire(new ProxyPingEvent(inbound, ping))) .thenAcceptAsync(event -> connection.closeWith( LegacyDisconnect.fromServerPing(event.getPing(), packet.getVersion())), @@ -207,7 +88,7 @@ public class StatusSessionHandler implements MinecraftSessionHandler { } this.pingReceived = true; - getInitialPing() + this.server.getServerListPingHandler().getInitialPing(inbound) .thenCompose(ping -> server.getEventManager().fire(new ProxyPingEvent(inbound, ping))) .thenAcceptAsync( (event) -> { diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/util/ServerListPingHandler.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/util/ServerListPingHandler.java new file mode 100644 index 000000000..6bc29e5ca --- /dev/null +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/util/ServerListPingHandler.java @@ -0,0 +1,153 @@ +/* + * Copyright (C) 2018 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.util; + +import com.google.common.collect.ImmutableList; +import com.spotify.futures.CompletableFutures; +import com.velocitypowered.api.network.ProtocolVersion; +import com.velocitypowered.api.proxy.server.RegisteredServer; +import com.velocitypowered.api.proxy.server.ServerPing; +import com.velocitypowered.api.util.ModInfo; +import com.velocitypowered.proxy.VelocityServer; +import com.velocitypowered.proxy.config.PingPassthroughMode; +import com.velocitypowered.proxy.config.VelocityConfiguration; +import com.velocitypowered.proxy.server.VelocityRegisteredServer; +import java.net.InetSocketAddress; +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; +import java.util.Optional; +import java.util.concurrent.CompletableFuture; + +public class ServerListPingHandler { + + private final VelocityServer server; + + public ServerListPingHandler(VelocityServer server) { + this.server = server; + } + + private ServerPing constructLocalPing(ProtocolVersion version) { + VelocityConfiguration configuration = server.getConfiguration(); + return new ServerPing( + new ServerPing.Version(version.getProtocol(), + "Velocity " + ProtocolVersion.SUPPORTED_VERSION_STRING), + new ServerPing.Players(server.getPlayerCount(), configuration.getShowMaxPlayers(), + ImmutableList.of()), + configuration.getMotd(), + configuration.getFavicon().orElse(null), + configuration.isAnnounceForge() ? ModInfo.DEFAULT : null + ); + } + + private CompletableFuture attemptPingPassthrough(VelocityInboundConnection connection, + PingPassthroughMode mode, List servers, ProtocolVersion responseProtocolVersion) { + ServerPing fallback = constructLocalPing(connection.getProtocolVersion()); + List> pings = new ArrayList<>(); + for (String s : servers) { + Optional rs = server.getServer(s); + if (!rs.isPresent()) { + continue; + } + VelocityRegisteredServer vrs = (VelocityRegisteredServer) rs.get(); + pings.add(vrs.ping(connection.getConnection().eventLoop(), responseProtocolVersion)); + } + if (pings.isEmpty()) { + return CompletableFuture.completedFuture(fallback); + } + + CompletableFuture> pingResponses = CompletableFutures.successfulAsList(pings, + (ex) -> fallback); + switch (mode) { + case ALL: + return pingResponses.thenApply(responses -> { + // Find the first non-fallback + for (ServerPing response : responses) { + if (response == fallback) { + continue; + } + return response; + } + return fallback; + }); + case MODS: + return pingResponses.thenApply(responses -> { + // Find the first non-fallback that contains a mod list + for (ServerPing response : responses) { + if (response == fallback) { + continue; + } + Optional modInfo = response.getModinfo(); + if (modInfo.isPresent()) { + return fallback.asBuilder().mods(modInfo.get()).build(); + } + } + return fallback; + }); + case DESCRIPTION: + return pingResponses.thenApply(responses -> { + // Find the first non-fallback. If it includes a modlist, add it too. + for (ServerPing response : responses) { + if (response == fallback) { + continue; + } + + if (response.getDescriptionComponent() == null) { + continue; + } + + return new ServerPing( + fallback.getVersion(), + fallback.getPlayers().orElse(null), + response.getDescriptionComponent(), + fallback.getFavicon().orElse(null), + response.getModinfo().orElse(null) + ); + } + return fallback; + }); + // Not possible, but covered for completeness. + default: + return CompletableFuture.completedFuture(fallback); + } + } + + /** + * Fetches the "default" server ping for a player. + * + * @param connection the connection + * @return a future with the initial ping result + */ + public CompletableFuture getInitialPing(VelocityInboundConnection connection) { + VelocityConfiguration configuration = server.getConfiguration(); + ProtocolVersion shownVersion = ProtocolVersion.isSupported(connection.getProtocolVersion()) + ? connection.getProtocolVersion() : ProtocolVersion.MAXIMUM_VERSION; + PingPassthroughMode passthroughMode = configuration.getPingPassthrough(); + + if (passthroughMode == PingPassthroughMode.DISABLED) { + return CompletableFuture.completedFuture(constructLocalPing(shownVersion)); + } else { + String virtualHostStr = connection.getVirtualHost().map(InetSocketAddress::getHostString) + .map(str -> str.toLowerCase(Locale.ROOT)) + .orElse(""); + List serversToTry = server.getConfiguration().getForcedHosts().getOrDefault( + virtualHostStr, server.getConfiguration().getAttemptConnectionOrder()); + return attemptPingPassthrough(connection, passthroughMode, serversToTry, shownVersion); + } + } +} diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/util/VelocityInboundConnection.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/util/VelocityInboundConnection.java new file mode 100644 index 000000000..8cde57bd1 --- /dev/null +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/util/VelocityInboundConnection.java @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2018 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.util; + +import com.velocitypowered.api.proxy.InboundConnection; +import com.velocitypowered.proxy.connection.MinecraftConnection; + +public interface VelocityInboundConnection extends InboundConnection { + MinecraftConnection getConnection(); +} 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 ccedb7dc8..49ce6c56d 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/StateRegistry.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/StateRegistry.java @@ -56,6 +56,7 @@ import com.velocitypowered.proxy.protocol.packet.PluginMessage; import com.velocitypowered.proxy.protocol.packet.ResourcePackRequest; import com.velocitypowered.proxy.protocol.packet.ResourcePackResponse; import com.velocitypowered.proxy.protocol.packet.Respawn; +import com.velocitypowered.proxy.protocol.packet.ServerData; import com.velocitypowered.proxy.protocol.packet.ServerLogin; import com.velocitypowered.proxy.protocol.packet.ServerLoginSuccess; import com.velocitypowered.proxy.protocol.packet.SetCompression; @@ -310,6 +311,8 @@ public enum StateRegistry { map(0x34, MINECRAFT_1_19, false)); clientbound.register(SystemChat.class, SystemChat::new, map(0x5F, MINECRAFT_1_19, true)); + clientbound.register(ServerData.class, ServerData::new, + map(0x3F, MINECRAFT_1_19, true)); } }, LOGIN { diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/ServerData.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/ServerData.java new file mode 100644 index 000000000..5f4857bc8 --- /dev/null +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/ServerData.java @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2018 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.api.util.Favicon; +import com.velocitypowered.proxy.connection.MinecraftSessionHandler; +import com.velocitypowered.proxy.protocol.MinecraftPacket; +import com.velocitypowered.proxy.protocol.ProtocolUtils; +import io.netty.buffer.ByteBuf; +import net.kyori.adventure.text.Component; +import org.jetbrains.annotations.Nullable; + +public class ServerData implements MinecraftPacket { + + private @Nullable Component description; + private @Nullable Favicon favicon; + private boolean previewsChat; + + public ServerData() { + } + + public ServerData(@Nullable Component description, @Nullable Favicon favicon, + boolean previewsChat) { + this.description = description; + this.favicon = favicon; + this.previewsChat = previewsChat; + } + + @Override + public void decode(ByteBuf buf, ProtocolUtils.Direction direction, + ProtocolVersion protocolVersion) { + if (buf.readBoolean()) { + this.description = ProtocolUtils.getJsonChatSerializer(protocolVersion) + .deserialize(ProtocolUtils.readString(buf)); + } + if (buf.readBoolean()) { + this.favicon = new Favicon(ProtocolUtils.readString(buf)); + } + this.previewsChat = buf.readBoolean(); + } + + @Override + public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) { + boolean hasDescription = this.description != null; + buf.writeBoolean(hasDescription); + if (hasDescription) { + ProtocolUtils.writeString( + buf, + ProtocolUtils.getJsonChatSerializer(protocolVersion).serialize(this.description) + ); + } + + boolean hasFavicon = this.favicon != null; + buf.writeBoolean(hasFavicon); + if (hasFavicon) { + ProtocolUtils.writeString(buf, favicon.getBase64Url()); + } + + buf.writeBoolean(this.previewsChat); + } + + @Override + public boolean handle(MinecraftSessionHandler handler) { + return handler.handle(this); + } + + public Component getDescription() { + return description; + } + + public Favicon getFavicon() { + return favicon; + } + + public boolean isPreviewsChat() { + return previewsChat; + } +}