Implement the ServerData packet by firing ProxyPingEvent (#771)

* Implement the ServerData packet by firing ProxyPingEvent

Mojang introduced the enable-status server property with Minecraft 1.19, which if enabled causes servers to close the connection when a client tries to ping them. Mojang wants to show the MOTD and favicon on the server select screen for those who manage to log in, so we need to implement this packet as well.

The good news is that we can send this packet as many times as needed on the same connection

This matches the behavior of pinging the server. This is a minor, but completely backwards-compatible, API breakage: Player inherits from InboundConnection so we do not have to change ProxyPingEvent, however plugins not expecting a Player might get confused.

* typo
This commit is contained in:
Andrew Steinborn 2022-06-23 23:59:13 -04:00 committed by GitHub
parent 86c65f3910
commit 662fbc4e3c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 330 additions and 133 deletions

View File

@ -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 {

View File

@ -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;
}

View File

@ -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;
}
}

View File

@ -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) {

View File

@ -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()

View File

@ -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;
}
}
}

View File

@ -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;
}

View File

@ -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<ServerPing> attemptPingPassthrough(PingPassthroughMode mode,
List<String> servers, ProtocolVersion pingingVersion) {
ServerPing fallback = constructLocalPing(pingingVersion);
List<CompletableFuture<ServerPing>> pings = new ArrayList<>();
for (String s : servers) {
Optional<RegisteredServer> 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<List<ServerPing>> 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> 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<ServerPing> 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<String> 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) -> {

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
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<ServerPing> attemptPingPassthrough(VelocityInboundConnection connection,
PingPassthroughMode mode, List<String> servers, ProtocolVersion responseProtocolVersion) {
ServerPing fallback = constructLocalPing(connection.getProtocolVersion());
List<CompletableFuture<ServerPing>> pings = new ArrayList<>();
for (String s : servers) {
Optional<RegisteredServer> 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<List<ServerPing>> 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> 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<ServerPing> 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<String> serversToTry = server.getConfiguration().getForcedHosts().getOrDefault(
virtualHostStr, server.getConfiguration().getAttemptConnectionOrder());
return attemptPingPassthrough(connection, passthroughMode, serversToTry, shownVersion);
}
}
}

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
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();
}

View File

@ -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 {

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
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;
}
}