diff --git a/api/src/main/java/com/velocitypowered/api/network/ProtocolVersion.java b/api/src/main/java/com/velocitypowered/api/network/ProtocolVersion.java index ef7024efe..076c7f8d1 100644 --- a/api/src/main/java/com/velocitypowered/api/network/ProtocolVersion.java +++ b/api/src/main/java/com/velocitypowered/api/network/ProtocolVersion.java @@ -63,7 +63,8 @@ public enum ProtocolVersion { MINECRAFT_1_19_3(761, "1.19.3"), MINECRAFT_1_19_4(762, "1.19.4"), MINECRAFT_1_20(763, "1.20", "1.20.1"), - MINECRAFT_1_20_2(764, "1.20.2"); + MINECRAFT_1_20_2(764, "1.20.2"), + MINECRAFT_1_20_3(765, "1.20.3"); private static final int SNAPSHOT_BIT = 30; diff --git a/api/src/main/java/com/velocitypowered/api/proxy/Player.java b/api/src/main/java/com/velocitypowered/api/proxy/Player.java index 99bf09da0..8be1931f3 100644 --- a/api/src/main/java/com/velocitypowered/api/proxy/Player.java +++ b/api/src/main/java/com/velocitypowered/api/proxy/Player.java @@ -19,6 +19,8 @@ import com.velocitypowered.api.proxy.player.TabList; import com.velocitypowered.api.proxy.server.RegisteredServer; import com.velocitypowered.api.util.GameProfile; import com.velocitypowered.api.util.ModInfo; + +import java.util.Collection; import java.util.List; import java.util.Locale; import java.util.Optional; @@ -236,6 +238,7 @@ public interface Player extends * @return the applied resource pack or null if none. */ @Nullable + @Deprecated ResourcePackInfo getAppliedResourcePack(); /** @@ -246,8 +249,26 @@ public interface Player extends * @return the pending resource pack or null if none */ @Nullable + @Deprecated ResourcePackInfo getPendingResourcePack(); + /** + * Gets the {@link ResourcePackInfo} of the currently applied + * resource-packs. + * + * @return collection of the applied resource packs. + */ + Collection getAppliedResourcePacks(); + + /** + * Gets the {@link ResourcePackInfo} of the resource packs + * the user is currently downloading or is currently + * prompted to install. + * + * @return collection of the pending resource packs + */ + Collection getPendingResourcePacks(); + /** * Note that this method does not send a plugin message to the server the player * is connected to. You should only use this method if you are trying to communicate diff --git a/api/src/main/java/com/velocitypowered/api/proxy/player/ResourcePackInfo.java b/api/src/main/java/com/velocitypowered/api/proxy/player/ResourcePackInfo.java index c0023310c..779037526 100644 --- a/api/src/main/java/com/velocitypowered/api/proxy/player/ResourcePackInfo.java +++ b/api/src/main/java/com/velocitypowered/api/proxy/player/ResourcePackInfo.java @@ -10,11 +10,20 @@ package com.velocitypowered.api.proxy.player; import net.kyori.adventure.text.Component; import org.checkerframework.checker.nullness.qual.Nullable; +import java.util.UUID; + /** * Represents the information for a resource pack to apply that can be sent to the client. */ public interface ResourcePackInfo { + /** + * Gets the id of this resource-pack. + * + * @return the id of the resource-pack + */ + UUID getId(); + /** * Gets the link the resource-pack can be found at. * @@ -96,6 +105,13 @@ public interface ResourcePackInfo { */ interface Builder { + /** + * Sets the id of the resource pack. + * + * @param id the id the resource-pack + */ + Builder setId(UUID id); + /** * Sets the resource-pack as required to play on the network. * This feature was introduced in 1.17. 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 189820e04..4e8af7f68 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.LoginPluginResponse; import com.velocitypowered.proxy.protocol.packet.PingIdentify; import com.velocitypowered.proxy.protocol.packet.PluginMessage; import com.velocitypowered.proxy.protocol.packet.RemovePlayerInfo; +import com.velocitypowered.proxy.protocol.packet.RemoveResourcePack; import com.velocitypowered.proxy.protocol.packet.ResourcePackRequest; import com.velocitypowered.proxy.protocol.packet.ResourcePackResponse; import com.velocitypowered.proxy.protocol.packet.Respawn; @@ -248,6 +249,10 @@ public interface MinecraftSessionHandler { return false; } + default boolean handle(RemoveResourcePack packet) { + return false; + } + default boolean handle(ResourcePackResponse 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 c21ae6ecd..dbe853c50 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 @@ -49,11 +49,13 @@ import com.velocitypowered.proxy.protocol.packet.KeepAlive; import com.velocitypowered.proxy.protocol.packet.LegacyPlayerListItem; import com.velocitypowered.proxy.protocol.packet.PluginMessage; import com.velocitypowered.proxy.protocol.packet.RemovePlayerInfo; +import com.velocitypowered.proxy.protocol.packet.RemoveResourcePack; 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.packet.UpsertPlayerInfo; +import com.velocitypowered.proxy.protocol.packet.chat.ComponentHolder; import com.velocitypowered.proxy.protocol.packet.config.StartUpdate; import com.velocitypowered.proxy.protocol.util.PluginMessageUtil; import io.netty.buffer.ByteBuf; @@ -167,7 +169,8 @@ public class BackendPlaySessionHandler implements MinecraftSessionHandler { public boolean handle(ResourcePackRequest packet) { ResourcePackInfo.Builder builder = new VelocityResourcePackInfo.BuilderImpl( Preconditions.checkNotNull(packet.getUrl())) - .setPrompt(packet.getPrompt()) + .setId(packet.getId()) + .setPrompt(packet.getPrompt() == null ? null : packet.getPrompt().getComponent()) .setShouldForce(packet.isRequired()) .setOrigin(ResourcePackInfo.Origin.DOWNSTREAM_SERVER); @@ -213,6 +216,11 @@ public class BackendPlaySessionHandler implements MinecraftSessionHandler { return true; } + @Override + public boolean handle(RemoveResourcePack packet) { + //TODO + } + @Override public boolean handle(PluginMessage packet) { if (bungeecordMessageResponder.process(packet)) { @@ -304,7 +312,8 @@ public class BackendPlaySessionHandler implements MinecraftSessionHandler { ping -> server.getEventManager() .fire(new ProxyPingEvent(this.serverConn.getPlayer(), ping)), playerConnection.eventLoop()).thenAcceptAsync(pingEvent -> this.playerConnection.write( - new ServerData(pingEvent.getPing().getDescriptionComponent(), + new ServerData(new ComponentHolder(this.serverConn.ensureConnected().getProtocolVersion(), + pingEvent.getPing().getDescriptionComponent()), pingEvent.getPing().getFavicon().orElse(null), packet.isSecureChatEnforced())), playerConnection.eventLoop()); return true; 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 4c76f2f23..ea38a7ab2 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 @@ -68,6 +68,7 @@ import com.velocitypowered.proxy.protocol.packet.PluginMessage; import com.velocitypowered.proxy.protocol.packet.ResourcePackRequest; import com.velocitypowered.proxy.protocol.packet.chat.ChatQueue; import com.velocitypowered.proxy.protocol.packet.chat.ChatType; +import com.velocitypowered.proxy.protocol.packet.chat.ComponentHolder; import com.velocitypowered.proxy.protocol.packet.chat.builder.ChatBuilderFactory; import com.velocitypowered.proxy.protocol.packet.chat.legacy.LegacyChat; import com.velocitypowered.proxy.protocol.packet.config.StartUpdate; @@ -84,6 +85,7 @@ import io.netty.buffer.ByteBufUtil; import io.netty.buffer.Unpooled; import java.net.InetSocketAddress; import java.util.ArrayDeque; +import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Locale; @@ -1015,22 +1017,36 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player, request.setHash(""); } request.setRequired(queued.getShouldForce()); - request.setPrompt(queued.getPrompt()); + request.setPrompt(queued.getPrompt() == null ? null : new ComponentHolder(getProtocolVersion(), queued.getPrompt())); connection.write(request); } } @Override + @Deprecated public @Nullable ResourcePackInfo getAppliedResourcePack() { + //TODO which resource pack should be returned here? return appliedResourcePack; } @Override + @Deprecated public @Nullable ResourcePackInfo getPendingResourcePack() { + //TODO which resource pack should be returned here? return pendingResourcePack; } + @Override + public Collection getAppliedResourcePacks() { + //TODO + } + + @Override + public Collection getPendingResourcePacks() { + //TODO + } + /** * Clears the applied resource pack field. */ @@ -1089,6 +1105,7 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player, * Gives an indication about the previous resource pack responses. */ public @Nullable Boolean getPreviousResourceResponse() { + //TODO can probably be removed return previousResourceResponse; } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/player/VelocityResourcePackInfo.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/player/VelocityResourcePackInfo.java index 134aee180..109d53be7 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/player/VelocityResourcePackInfo.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/player/VelocityResourcePackInfo.java @@ -22,11 +22,14 @@ import com.velocitypowered.api.proxy.player.ResourcePackInfo; import net.kyori.adventure.text.Component; import org.checkerframework.checker.nullness.qual.Nullable; +import java.util.UUID; + /** * Implements {@link ResourcePackInfo}. */ public final class VelocityResourcePackInfo implements ResourcePackInfo { + private final UUID id; private final String url; private final @Nullable byte[] hash; private final boolean shouldForce; @@ -34,8 +37,9 @@ public final class VelocityResourcePackInfo implements ResourcePackInfo { private final Origin origin; private Origin originalOrigin; - private VelocityResourcePackInfo(String url, @Nullable byte[] hash, boolean shouldForce, + private VelocityResourcePackInfo(UUID id, String url, @Nullable byte[] hash, boolean shouldForce, @Nullable Component prompt, Origin origin) { + this.id = id; this.url = url; this.hash = hash; this.shouldForce = shouldForce; @@ -44,6 +48,11 @@ public final class VelocityResourcePackInfo implements ResourcePackInfo { this.originalOrigin = origin; } + @Override + public UUID getId() { + return id; + } + @Override public String getUrl() { return url; @@ -81,6 +90,7 @@ public final class VelocityResourcePackInfo implements ResourcePackInfo { @Override public Builder asBuilder() { return new BuilderImpl(url) + .setId(id) .setShouldForce(shouldForce) .setHash(hash) .setPrompt(prompt); @@ -89,6 +99,7 @@ public final class VelocityResourcePackInfo implements ResourcePackInfo { @Override public Builder asBuilder(String newUrl) { return new BuilderImpl(newUrl) + .setId(id) .setShouldForce(shouldForce) .setHash(hash) .setPrompt(prompt); @@ -99,6 +110,7 @@ public final class VelocityResourcePackInfo implements ResourcePackInfo { */ public static final class BuilderImpl implements ResourcePackInfo.Builder { + private UUID id; private final String url; private boolean shouldForce; private @Nullable byte[] hash; @@ -109,6 +121,12 @@ public final class VelocityResourcePackInfo implements ResourcePackInfo { this.url = Preconditions.checkNotNull(url, "url"); } + @Override + public BuilderImpl setId(UUID id) { + this.id = id; + return this; + } + @Override public BuilderImpl setShouldForce(boolean shouldForce) { this.shouldForce = shouldForce; @@ -134,7 +152,7 @@ public final class VelocityResourcePackInfo implements ResourcePackInfo { @Override public ResourcePackInfo build() { - return new VelocityResourcePackInfo(url, hash, shouldForce, prompt, origin); + return new VelocityResourcePackInfo(id, url, hash, shouldForce, prompt, origin); } public BuilderImpl setOrigin(Origin origin) { diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/registry/ClientConfigData.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/registry/ClientConfigData.java deleted file mode 100644 index 1bca74515..000000000 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/registry/ClientConfigData.java +++ /dev/null @@ -1,128 +0,0 @@ -/* - * 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.registry; - -import com.velocitypowered.proxy.connection.player.VelocityResourcePackInfo; -import com.velocitypowered.proxy.protocol.packet.config.RegistrySync; -import net.kyori.adventure.key.Key; -import org.jetbrains.annotations.Nullable; - -/** - * Holds the registry data that is sent - * to the client during the config stage. - */ -public class ClientConfigData { - - private final @Nullable VelocityResourcePackInfo resourcePackInfo; - private final DataTag tag; - private final RegistrySync registry; - private final Key[] features; - private final String brand; - - private ClientConfigData(@Nullable VelocityResourcePackInfo resourcePackInfo, DataTag tag, - RegistrySync registry, Key[] features, String brand) { - this.resourcePackInfo = resourcePackInfo; - this.tag = tag; - this.registry = registry; - this.features = features; - this.brand = brand; - } - - public RegistrySync getRegistry() { - return registry; - } - - public DataTag getTag() { - return tag; - } - - public Key[] getFeatures() { - return features; - } - - public @Nullable VelocityResourcePackInfo getResourcePackInfo() { - return resourcePackInfo; - } - - public String getBrand() { - return brand; - } - - /** - * Creates a new builder. - * - * @return ClientConfigData.Builder - */ - public static ClientConfigData.Builder builder() { - return new Builder(); - } - - /** - * Builder for ClientConfigData. - */ - public static class Builder { - private VelocityResourcePackInfo resourcePackInfo; - private DataTag tag; - private RegistrySync registry; - private Key[] features; - private String brand; - - private Builder() { - } - - /** - * Clears the builder. - */ - public void clear() { - this.resourcePackInfo = null; - this.tag = null; - this.registry = null; - this.features = null; - this.brand = null; - } - - public Builder resourcePack(@Nullable VelocityResourcePackInfo resourcePackInfo) { - this.resourcePackInfo = resourcePackInfo; - return this; - } - - public Builder dataTag(DataTag tag) { - this.tag = tag; - return this; - } - - public Builder registry(RegistrySync registry) { - this.registry = registry; - return this; - } - - public Builder features(Key[] features) { - this.features = features; - return this; - } - - public Builder brand(String brand) { - this.brand = brand; - return this; - } - - public ClientConfigData build() { - return new ClientConfigData(resourcePackInfo, tag, registry, features, brand); - } - } -} diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/registry/DataTag.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/registry/DataTag.java deleted file mode 100644 index 9a7d0de73..000000000 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/registry/DataTag.java +++ /dev/null @@ -1,95 +0,0 @@ -/* - * Copyright (C) 2019-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.registry; - -import com.google.common.collect.ImmutableList; -import java.util.List; -import net.kyori.adventure.key.Key; -import net.kyori.adventure.key.Keyed; -import org.jetbrains.annotations.NotNull; - -/** - * Represents a data tag. - */ -public class DataTag { - private final ImmutableList entrySets; - - public DataTag(ImmutableList entrySets) { - this.entrySets = entrySets; - } - - /** - * Returns the entry sets. - * - * @return List of entry sets - */ - public List getEntrySets() { - return entrySets; - } - - /** - * Represents a data tag set. - */ - public static class Set implements Keyed { - - private final Key key; - private final ImmutableList entries; - - public Set(Key key, ImmutableList entries) { - this.key = key; - this.entries = entries; - } - - /** - * Returns the entries. - * - * @return List of entries - */ - public List getEntries() { - return entries; - } - - @Override - public @NotNull Key key() { - return key; - } - } - - /** - * Represents a data tag entry. - */ - public static class Entry implements Keyed { - - private final Key key; - private final int[] elements; - - public Entry(Key key, int[] elements) { - this.key = key; - this.elements = elements; - } - - public int[] getElements() { - return elements; - } - - @Override - public @NotNull Key key() { - return key; - } - } -} 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 e65ee056b..381f8e308 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/ProtocolUtils.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/ProtocolUtils.java @@ -34,15 +34,16 @@ import io.netty.buffer.ByteBufUtil; import io.netty.handler.codec.CorruptedFrameException; import io.netty.handler.codec.DecoderException; import io.netty.handler.codec.EncoderException; -import java.io.DataInput; -import java.io.DataOutput; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.List; import java.util.UUID; import net.kyori.adventure.key.Key; +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.CompoundBinaryTag; import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer; @@ -64,6 +65,11 @@ public enum ProtocolUtils { .build(); public static final int DEFAULT_MAX_STRING_SIZE = 65536; // 64KiB + private static final BinaryTagType[] BINARY_TAG_TYPES = new BinaryTagType[] { + BinaryTagTypes.END, BinaryTagTypes.BYTE, BinaryTagTypes.SHORT, BinaryTagTypes.INT, + BinaryTagTypes.LONG, BinaryTagTypes.FLOAT, BinaryTagTypes.DOUBLE, + BinaryTagTypes.BYTE_ARRAY, BinaryTagTypes.STRING, BinaryTagTypes.LIST, + BinaryTagTypes.COMPOUND, BinaryTagTypes.INT_ARRAY, BinaryTagTypes.LONG_ARRAY}; private static final QuietDecoderException BAD_VARINT_CACHED = new QuietDecoderException("Bad VarInt decoded"); private static final int[] VARINT_EXACT_BYTE_LENGTHS = new int[33]; @@ -367,29 +373,53 @@ public enum ProtocolUtils { * Reads a {@link net.kyori.adventure.nbt.CompoundBinaryTag} from the {@code buf}. * * @param buf the buffer to read from - * @param reader the NBT reader to use + * @param reader the {@link BinaryTagIO.Reader} to use * @return {@link net.kyori.adventure.nbt.CompoundBinaryTag} the CompoundTag from the buffer */ - public static CompoundBinaryTag readCompoundTag(ByteBuf buf, BinaryTagIO.Reader reader) { + public static CompoundBinaryTag readCompoundTag(ByteBuf buf, ProtocolVersion version, BinaryTagIO.Reader reader) { + BinaryTag binaryTag = readBinaryTag(buf, version, reader); + if (binaryTag.type() != BinaryTagTypes.COMPOUND) { + throw new DecoderException("Expected root tag to be CompoundTag, but is " + binaryTag.getClass().getSimpleName()); + } + return (CompoundBinaryTag) binaryTag; + } + + /** + * Reads a {@link net.kyori.adventure.nbt.BinaryTag} from the {@code buf}. + * + * @param buf the buffer to read from + * @param reader the {@link BinaryTagIO.Reader} to use + * @return {@link net.kyori.adventure.nbt.BinaryTag} the BinaryTag from the buffer + */ + public static BinaryTag readBinaryTag(ByteBuf buf, ProtocolVersion version, BinaryTagIO.Reader reader) { + BinaryTagType type = BINARY_TAG_TYPES[buf.readByte()]; + if (version.compareTo(ProtocolVersion.MINECRAFT_1_20_2) < 0) { + buf.skipBytes(buf.readUnsignedShort()); + } try { - return reader.read((DataInput) new ByteBufInputStream(buf)); + return type.read(new ByteBufInputStream(buf)); } catch (IOException thrown) { - throw new DecoderException( - "Unable to parse NBT CompoundTag, full error: " + thrown.getMessage()); + throw new DecoderException("Unable to parse BinaryTag, full error: " + thrown.getMessage()); } } /** - * Writes a CompoundTag to the {@code buf}. + * Writes a {@link net.kyori.adventure.nbt.BinaryTag} to the {@code buf}. * - * @param buf the buffer to write to - * @param compoundTag the CompoundTag to write + * @param buf the buffer to write to + * @param tag the BinaryTag to write */ - public static void writeCompoundTag(ByteBuf buf, CompoundBinaryTag compoundTag) { + public static void writeBinaryTag(ByteBuf buf, ProtocolVersion version, T tag) { + BinaryTagType type = (BinaryTagType) tag.type(); + buf.writeByte(type.id()); try { - BinaryTagIO.writer().write(compoundTag, (DataOutput) new ByteBufOutputStream(buf)); + if (version.compareTo(ProtocolVersion.MINECRAFT_1_20_2) < 0) { + // Empty name + buf.writeShort(0); + } + type.write(tag, new ByteBufOutputStream(buf)); } catch (IOException e) { - throw new EncoderException("Unable to encode NBT CompoundTag"); + throw new EncoderException("Unable to encode BinaryTag"); } } 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 8ebb7eadc..b02436f12 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/StateRegistry.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/StateRegistry.java @@ -34,6 +34,7 @@ import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_19_1; import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_19_3; import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_19_4; import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_20_2; +import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_20_3; import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_7_2; import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_8; import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_9; @@ -62,6 +63,7 @@ import com.velocitypowered.proxy.protocol.packet.LoginPluginResponse; import com.velocitypowered.proxy.protocol.packet.PingIdentify; import com.velocitypowered.proxy.protocol.packet.PluginMessage; import com.velocitypowered.proxy.protocol.packet.RemovePlayerInfo; +import com.velocitypowered.proxy.protocol.packet.RemoveResourcePack; import com.velocitypowered.proxy.protocol.packet.ResourcePackRequest; import com.velocitypowered.proxy.protocol.packet.ResourcePackResponse; import com.velocitypowered.proxy.protocol.packet.Respawn; @@ -154,10 +156,16 @@ public enum StateRegistry { clientbound.register( RegistrySync.class, RegistrySync::new, map(0x05, MINECRAFT_1_20_2, false)); clientbound.register( - ResourcePackRequest.class, ResourcePackRequest::new, map(0x06, MINECRAFT_1_20_2, false)); - clientbound.register( - ActiveFeatures.class, ActiveFeatures::new, map(0x07, MINECRAFT_1_20_2, false)); - clientbound.register(TagsUpdate.class, TagsUpdate::new, map(0x08, MINECRAFT_1_20_2, false)); + RemoveResourcePack.class, RemoveResourcePack::new, map(0x06, MINECRAFT_1_20_3, false)); + clientbound.register(ResourcePackRequest.class, ResourcePackRequest::new, + map(0x06, MINECRAFT_1_20_2, false), + map(0x07, MINECRAFT_1_20_3, false)); + clientbound.register(ActiveFeatures.class, ActiveFeatures::new, + map(0x07, MINECRAFT_1_20_2, false), + map(0x08, MINECRAFT_1_20_3, false)); + clientbound.register(TagsUpdate.class, TagsUpdate::new, + map(0x08, MINECRAFT_1_20_2, false), + map(0x09, MINECRAFT_1_20_3, false)); } }, PLAY { @@ -228,7 +236,8 @@ public enum StateRegistry { map(0x0D, MINECRAFT_1_19_1, false), map(0x0C, MINECRAFT_1_19_3, false), map(0x0D, MINECRAFT_1_19_4, false), - map(0x0F, MINECRAFT_1_20_2, false)); + map(0x0F, MINECRAFT_1_20_2, false), + map(0x10, MINECRAFT_1_20_3, false)); serverbound.register( KeepAlive.class, KeepAlive::new, @@ -244,7 +253,8 @@ public enum StateRegistry { map(0x12, MINECRAFT_1_19_1, false), map(0x11, MINECRAFT_1_19_3, false), map(0x12, MINECRAFT_1_19_4, false), - map(0x14, MINECRAFT_1_20_2, false)); + map(0x14, MINECRAFT_1_20_2, false), + map(0x15, MINECRAFT_1_20_3, false)); serverbound.register( ResourcePackResponse.class, ResourcePackResponse::new, @@ -257,7 +267,8 @@ public enum StateRegistry { map(0x21, MINECRAFT_1_16_2, false), map(0x23, MINECRAFT_1_19, false), map(0x24, MINECRAFT_1_19_1, false), - map(0x27, MINECRAFT_1_20_2, false)); + map(0x27, MINECRAFT_1_20_2, false), + map(0x28, MINECRAFT_1_20_3, false)); serverbound.register( FinishedUpdate.class, FinishedUpdate::new, map(0x0B, MINECRAFT_1_20_2, false)); @@ -385,7 +396,10 @@ public enum StateRegistry { map(0x3E, MINECRAFT_1_19_1, true), map(0x3D, MINECRAFT_1_19_3, true), map(0x41, MINECRAFT_1_19_4, true), - map(0x43, MINECRAFT_1_20_2, true)); + map(0x43, MINECRAFT_1_20_2, true), + map(0x45, MINECRAFT_1_20_3, true)); + clientbound.register( + RemoveResourcePack.class, RemoveResourcePack::new, map(0x43, MINECRAFT_1_20_3, false)); clientbound.register( ResourcePackRequest.class, ResourcePackRequest::new, @@ -403,7 +417,8 @@ public enum StateRegistry { map(0x3D, MINECRAFT_1_19_1, false), map(0x3C, MINECRAFT_1_19_3, false), map(0x40, MINECRAFT_1_19_4, false), - map(0x42, MINECRAFT_1_20_2, false)); + map(0x42, MINECRAFT_1_20_2, false), + map(0x44, MINECRAFT_1_20_3, false)); clientbound.register( HeaderAndFooter.class, HeaderAndFooter::new, @@ -422,7 +437,8 @@ public enum StateRegistry { map(0x63, MINECRAFT_1_19_1, true), map(0x61, MINECRAFT_1_19_3, true), map(0x65, MINECRAFT_1_19_4, true), - map(0x68, MINECRAFT_1_20_2, true)); + map(0x68, MINECRAFT_1_20_2, true), + map(0x6A, MINECRAFT_1_20_3, true)); clientbound.register( LegacyTitlePacket.class, LegacyTitlePacket::new, @@ -440,7 +456,8 @@ public enum StateRegistry { map(0x5B, MINECRAFT_1_19_1, true), map(0x59, MINECRAFT_1_19_3, true), map(0x5D, MINECRAFT_1_19_4, true), - map(0x5F, MINECRAFT_1_20_2, true)); + map(0x5F, MINECRAFT_1_20_2, true), + map(0x61, MINECRAFT_1_20_3, true)); clientbound.register( TitleTextPacket.class, TitleTextPacket::new, @@ -449,7 +466,8 @@ public enum StateRegistry { map(0x5D, MINECRAFT_1_19_1, true), map(0x5B, MINECRAFT_1_19_3, true), map(0x5F, MINECRAFT_1_19_4, true), - map(0x61, MINECRAFT_1_20_2, true)); + map(0x61, MINECRAFT_1_20_2, true), + map(0x63, MINECRAFT_1_20_3, true)); clientbound.register( TitleActionbarPacket.class, TitleActionbarPacket::new, @@ -458,7 +476,8 @@ public enum StateRegistry { map(0x43, MINECRAFT_1_19_1, true), map(0x42, MINECRAFT_1_19_3, true), map(0x46, MINECRAFT_1_19_4, true), - map(0x48, MINECRAFT_1_20_2, true)); + map(0x48, MINECRAFT_1_20_2, true), + map(0x4A, MINECRAFT_1_20_3, true)); clientbound.register( TitleTimesPacket.class, TitleTimesPacket::new, @@ -467,7 +486,8 @@ public enum StateRegistry { map(0x5E, MINECRAFT_1_19_1, true), map(0x5C, MINECRAFT_1_19_3, true), map(0x60, MINECRAFT_1_19_4, true), - map(0x62, MINECRAFT_1_20_2, true)); + map(0x62, MINECRAFT_1_20_2, true), + map(0x64, MINECRAFT_1_20_3, true)); clientbound.register( TitleClearPacket.class, TitleClearPacket::new, @@ -507,7 +527,8 @@ public enum StateRegistry { map(0x62, MINECRAFT_1_19_1, true), map(0x60, MINECRAFT_1_19_3, true), map(0x64, MINECRAFT_1_19_4, true), - map(0x67, MINECRAFT_1_20_2, true)); + map(0x67, MINECRAFT_1_20_2, true), + map(0x69, MINECRAFT_1_20_3, true)); clientbound.register( PlayerChatCompletion.class, PlayerChatCompletion::new, @@ -522,8 +543,13 @@ public enum StateRegistry { map(0x42, MINECRAFT_1_19_1, false), map(0x41, MINECRAFT_1_19_3, false), map(0x45, MINECRAFT_1_19_4, false), - map(0x47, MINECRAFT_1_20_2, false)); - clientbound.register(StartUpdate.class, StartUpdate::new, map(0x65, MINECRAFT_1_20_2, false)); + map(0x47, MINECRAFT_1_20_2, false), + map(0x49, MINECRAFT_1_20_3, false)); + clientbound.register( + StartUpdate.class, + StartUpdate::new, + map(0x65, MINECRAFT_1_20_2, false), + map(0x67, MINECRAFT_1_20_3, false)); } }, LOGIN { diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/BossBar.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/BossBar.java index 91f4bacff..135804af6 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/BossBar.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/BossBar.java @@ -21,6 +21,7 @@ import com.velocitypowered.api.network.ProtocolVersion; import com.velocitypowered.proxy.connection.MinecraftSessionHandler; import com.velocitypowered.proxy.protocol.MinecraftPacket; import com.velocitypowered.proxy.protocol.ProtocolUtils; +import com.velocitypowered.proxy.protocol.packet.chat.ComponentHolder; import io.netty.buffer.ByteBuf; import java.util.UUID; import org.checkerframework.checker.nullness.qual.Nullable; @@ -35,7 +36,7 @@ public class BossBar implements MinecraftPacket { public static final int UPDATE_PROPERTIES = 5; private @Nullable UUID uuid; private int action; - private @Nullable String name; + private @Nullable ComponentHolder name; private float percent; private int color; private int overlay; @@ -60,11 +61,11 @@ public class BossBar implements MinecraftPacket { this.action = action; } - public @Nullable String getName() { + public @Nullable ComponentHolder getName() { return name; } - public void setName(String name) { + public void setName(ComponentHolder name) { this.name = name; } @@ -119,7 +120,7 @@ public class BossBar implements MinecraftPacket { this.action = ProtocolUtils.readVarInt(buf); switch (action) { case ADD: - this.name = ProtocolUtils.readString(buf); + this.name = ComponentHolder.read(buf, version); this.percent = buf.readFloat(); this.color = ProtocolUtils.readVarInt(buf); this.overlay = ProtocolUtils.readVarInt(buf); @@ -131,7 +132,7 @@ public class BossBar implements MinecraftPacket { this.percent = buf.readFloat(); break; case UPDATE_NAME: - this.name = ProtocolUtils.readString(buf); + this.name = ComponentHolder.read(buf, version); break; case UPDATE_STYLE: this.color = ProtocolUtils.readVarInt(buf); @@ -157,7 +158,7 @@ public class BossBar implements MinecraftPacket { if (name == null) { throw new IllegalStateException("No name specified!"); } - ProtocolUtils.writeString(buf, name); + name.write(buf, version); buf.writeFloat(percent); ProtocolUtils.writeVarInt(buf, color); ProtocolUtils.writeVarInt(buf, overlay); @@ -172,7 +173,7 @@ public class BossBar implements MinecraftPacket { if (name == null) { throw new IllegalStateException("No name specified!"); } - ProtocolUtils.writeString(buf, name); + name.write(buf, version); break; case UPDATE_STYLE: ProtocolUtils.writeVarInt(buf, color); diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/HeaderAndFooter.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/HeaderAndFooter.java index 43e1ac7e8..987bfd477 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/HeaderAndFooter.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/HeaderAndFooter.java @@ -17,39 +17,36 @@ package com.velocitypowered.proxy.protocol.packet; -import static com.velocitypowered.proxy.protocol.ProtocolUtils.writeString; - import com.google.common.base.Preconditions; import com.velocitypowered.api.network.ProtocolVersion; import com.velocitypowered.proxy.connection.MinecraftSessionHandler; import com.velocitypowered.proxy.protocol.MinecraftPacket; import com.velocitypowered.proxy.protocol.ProtocolUtils; +import com.velocitypowered.proxy.protocol.packet.chat.ComponentHolder; import io.netty.buffer.ByteBuf; import net.kyori.adventure.text.Component; -import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer; public class HeaderAndFooter implements MinecraftPacket { - private static final String EMPTY_COMPONENT = "{\"translate\":\"\"}"; private static final HeaderAndFooter RESET = new HeaderAndFooter(); - private final String header; - private final String footer; + private final ComponentHolder header; + private final ComponentHolder footer; public HeaderAndFooter() { - this(EMPTY_COMPONENT, EMPTY_COMPONENT); + this(ComponentHolder.EMPTY, ComponentHolder.EMPTY); } - public HeaderAndFooter(String header, String footer) { + public HeaderAndFooter(ComponentHolder header, ComponentHolder footer) { this.header = Preconditions.checkNotNull(header, "header"); this.footer = Preconditions.checkNotNull(footer, "footer"); } - public String getHeader() { + public ComponentHolder getHeader() { return header; } - public String getFooter() { + public ComponentHolder getFooter() { return footer; } @@ -60,8 +57,8 @@ public class HeaderAndFooter implements MinecraftPacket { @Override public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) { - writeString(buf, header); - writeString(buf, footer); + header.write(buf, version); + footer.write(buf, version); } @Override @@ -71,8 +68,8 @@ public class HeaderAndFooter implements MinecraftPacket { public static HeaderAndFooter create(Component header, Component footer, ProtocolVersion protocolVersion) { - GsonComponentSerializer serializer = ProtocolUtils.getJsonChatSerializer(protocolVersion); - return new HeaderAndFooter(serializer.serialize(header), serializer.serialize(footer)); + return new HeaderAndFooter(new ComponentHolder(protocolVersion, header), + new ComponentHolder(protocolVersion, footer)); } public static HeaderAndFooter reset() { diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/JoinGame.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/JoinGame.java index 938d1190a..6c4b19e97 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/JoinGame.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/JoinGame.java @@ -255,12 +255,12 @@ public class JoinGame implements MinecraftPacket { this.previousGamemode = buf.readByte(); this.levelNames = ImmutableSet.copyOf(ProtocolUtils.readStringArray(buf)); - this.registry = ProtocolUtils.readCompoundTag(buf, JOINGAME_READER); + this.registry = ProtocolUtils.readCompoundTag(buf, version, JOINGAME_READER); String dimensionIdentifier; String levelName = null; if (version.compareTo(ProtocolVersion.MINECRAFT_1_16_2) >= 0 && version.compareTo(ProtocolVersion.MINECRAFT_1_19) < 0) { - this.currentDimensionData = ProtocolUtils.readCompoundTag(buf, JOINGAME_READER); + this.currentDimensionData = ProtocolUtils.readCompoundTag(buf, version, JOINGAME_READER); dimensionIdentifier = ProtocolUtils.readString(buf); } else { dimensionIdentifier = ProtocolUtils.readString(buf); @@ -390,10 +390,10 @@ public class JoinGame implements MinecraftPacket { buf.writeByte(previousGamemode); ProtocolUtils.writeStringArray(buf, levelNames.toArray(String[]::new)); - ProtocolUtils.writeCompoundTag(buf, this.registry); + ProtocolUtils.writeBinaryTag(buf, version, this.registry); if (version.compareTo(ProtocolVersion.MINECRAFT_1_16_2) >= 0 && version.compareTo(ProtocolVersion.MINECRAFT_1_19) < 0) { - ProtocolUtils.writeCompoundTag(buf, currentDimensionData); + ProtocolUtils.writeBinaryTag(buf, version, currentDimensionData); ProtocolUtils.writeString(buf, dimensionInfo.getRegistryIdentifier()); } else { ProtocolUtils.writeString(buf, dimensionInfo.getRegistryIdentifier()); diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/RemoveResourcePack.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/RemoveResourcePack.java new file mode 100644 index 000000000..82e487ea7 --- /dev/null +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/RemoveResourcePack.java @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2018-2021 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.MinecraftPacket; +import com.velocitypowered.proxy.protocol.ProtocolUtils; +import com.velocitypowered.proxy.protocol.ProtocolUtils.Direction; +import io.netty.buffer.ByteBuf; + +import java.util.UUID; + +public class RemoveResourcePack implements MinecraftPacket { + + private UUID id; + + public RemoveResourcePack() { + } + + public RemoveResourcePack(UUID id) { + this.id = id; + } + + @Override + public void decode(ByteBuf buf, Direction direction, ProtocolVersion protocolVersion) { + this.id = ProtocolUtils.readUuid(buf); + } + + @Override + public void encode(ByteBuf buf, Direction direction, ProtocolVersion protocolVersion) { + ProtocolUtils.writeUuid(buf, id); + } + + @Override + public boolean handle(MinecraftSessionHandler handler) { + return handler.handle(this); + } +} diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/ResourcePackRequest.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/ResourcePackRequest.java index c80b97c16..dbef1b14d 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/ResourcePackRequest.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/ResourcePackRequest.java @@ -25,24 +25,33 @@ import com.velocitypowered.proxy.connection.player.VelocityResourcePackInfo; import com.velocitypowered.proxy.protocol.MinecraftPacket; import com.velocitypowered.proxy.protocol.ProtocolUtils; import com.velocitypowered.proxy.protocol.ProtocolUtils.Direction; +import com.velocitypowered.proxy.protocol.packet.chat.ComponentHolder; import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufUtil; -import net.kyori.adventure.text.Component; -import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer; import org.checkerframework.checker.nullness.qual.MonotonicNonNull; import org.checkerframework.checker.nullness.qual.Nullable; +import java.util.UUID; import java.util.regex.Pattern; public class ResourcePackRequest implements MinecraftPacket { + private @MonotonicNonNull UUID id; // 1.20.3+ private @MonotonicNonNull String url; private @MonotonicNonNull String hash; private boolean isRequired; // 1.17+ - private @Nullable Component prompt; // 1.17+ + private @Nullable ComponentHolder prompt; // 1.17+ private static final Pattern PLAUSIBLE_SHA1_HASH = Pattern.compile("^[a-z0-9]{40}$"); // 1.20.2+ + public @Nullable UUID getId() { + return id; + } + + public void setId(UUID id) { + this.id = id; + } + public @Nullable String getUrl() { return url; } @@ -67,22 +76,25 @@ public class ResourcePackRequest implements MinecraftPacket { isRequired = required; } - public @Nullable Component getPrompt() { + public @Nullable ComponentHolder getPrompt() { return prompt; } - public void setPrompt(Component prompt) { + public void setPrompt(ComponentHolder prompt) { this.prompt = prompt; } @Override public void decode(ByteBuf buf, Direction direction, ProtocolVersion protocolVersion) { + if (protocolVersion.compareTo(ProtocolVersion.MINECRAFT_1_20_3) >= 0) { + this.id = ProtocolUtils.readUuid(buf); + } this.url = ProtocolUtils.readString(buf); this.hash = ProtocolUtils.readString(buf); if (protocolVersion.compareTo(ProtocolVersion.MINECRAFT_1_17) >= 0) { this.isRequired = buf.readBoolean(); if (buf.readBoolean()) { - this.prompt = GsonComponentSerializer.gson().deserialize(ProtocolUtils.readString(buf)); + this.prompt = ComponentHolder.read(buf, protocolVersion); } else { this.prompt = null; } @@ -91,6 +103,12 @@ public class ResourcePackRequest implements MinecraftPacket { @Override public void encode(ByteBuf buf, Direction direction, ProtocolVersion protocolVersion) { + if (protocolVersion.compareTo(ProtocolVersion.MINECRAFT_1_20_3) >= 0) { + if (id == null) { + throw new IllegalStateException("Resource pack id not set yet!"); + } + ProtocolUtils.writeUuid(buf, id); + } if (url == null || hash == null) { throw new IllegalStateException("Packet not fully filled in yet!"); } @@ -100,7 +118,7 @@ public class ResourcePackRequest implements MinecraftPacket { buf.writeBoolean(isRequired); if (prompt != null) { buf.writeBoolean(true); - ProtocolUtils.writeString(buf, GsonComponentSerializer.gson().serialize(prompt)); + prompt.write(buf, protocolVersion); } else { buf.writeBoolean(false); } @@ -109,7 +127,8 @@ public class ResourcePackRequest implements MinecraftPacket { public VelocityResourcePackInfo toServerPromptedPack() { ResourcePackInfo.Builder builder = - new VelocityResourcePackInfo.BuilderImpl(Preconditions.checkNotNull(url)).setPrompt(prompt) + new VelocityResourcePackInfo.BuilderImpl(Preconditions.checkNotNull(url)) + .setId(id).setPrompt(prompt == null ? null : prompt.getComponent()) .setShouldForce(isRequired).setOrigin(ResourcePackInfo.Origin.DOWNSTREAM_SERVER); if (hash != null && !hash.isEmpty()) { diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/Respawn.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/Respawn.java index fe7b15441..e302cfbb4 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/Respawn.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/Respawn.java @@ -165,7 +165,7 @@ public class Respawn implements MinecraftPacket { if (version.compareTo(ProtocolVersion.MINECRAFT_1_16) >= 0) { if (version.compareTo(ProtocolVersion.MINECRAFT_1_16_2) >= 0 && version.compareTo(ProtocolVersion.MINECRAFT_1_19) < 0) { - this.currentDimensionData = ProtocolUtils.readCompoundTag(buf, BinaryTagIO.reader()); + this.currentDimensionData = ProtocolUtils.readCompoundTag(buf, version, BinaryTagIO.reader()); dimensionIdentifier = ProtocolUtils.readString(buf); } else { dimensionIdentifier = ProtocolUtils.readString(buf); @@ -210,7 +210,7 @@ public class Respawn implements MinecraftPacket { if (version.compareTo(ProtocolVersion.MINECRAFT_1_16) >= 0) { if (version.compareTo(ProtocolVersion.MINECRAFT_1_16_2) >= 0 && version.compareTo(ProtocolVersion.MINECRAFT_1_19) < 0) { - ProtocolUtils.writeCompoundTag(buf, currentDimensionData); + ProtocolUtils.writeBinaryTag(buf, version, currentDimensionData); ProtocolUtils.writeString(buf, dimensionInfo.getRegistryIdentifier()); } else { ProtocolUtils.writeString(buf, dimensionInfo.getRegistryIdentifier()); 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 index 4223c488f..470a1ffb2 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/ServerData.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/ServerData.java @@ -22,23 +22,23 @@ 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 com.velocitypowered.proxy.protocol.packet.chat.ComponentHolder; import io.netty.buffer.ByteBuf; -import net.kyori.adventure.text.Component; import org.jetbrains.annotations.Nullable; import java.nio.charset.StandardCharsets; import java.util.Base64; public class ServerData implements MinecraftPacket { - private @Nullable Component description; + private @Nullable ComponentHolder description; private @Nullable Favicon favicon; private boolean secureChatEnforced; // Added in 1.19.1 public ServerData() { } - public ServerData(@Nullable Component description, @Nullable Favicon favicon, - boolean secureChatEnforced) { + public ServerData(@Nullable ComponentHolder description, @Nullable Favicon favicon, + boolean secureChatEnforced) { this.description = description; this.favicon = favicon; this.secureChatEnforced = secureChatEnforced; @@ -48,8 +48,7 @@ public class ServerData implements MinecraftPacket { public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) { if (protocolVersion.compareTo(ProtocolVersion.MINECRAFT_1_19_4) >= 0 || buf.readBoolean()) { - this.description = ProtocolUtils.getJsonChatSerializer(protocolVersion) - .deserialize(ProtocolUtils.readString(buf)); + this.description = ComponentHolder.read(buf, protocolVersion); } if (buf.readBoolean()) { String iconBase64; @@ -77,10 +76,7 @@ public class ServerData implements MinecraftPacket { buf.writeBoolean(hasDescription); } if (protocolVersion.compareTo(ProtocolVersion.MINECRAFT_1_19_4) >= 0 || hasDescription) { - ProtocolUtils.writeString( - buf, - ProtocolUtils.getJsonChatSerializer(protocolVersion).serialize(this.description) - ); + this.description.write(buf, protocolVersion); } boolean hasFavicon = this.favicon != null; @@ -108,7 +104,7 @@ public class ServerData implements MinecraftPacket { return handler.handle(this); } - public @Nullable Component getDescription() { + public @Nullable ComponentHolder getDescription() { return description; } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/UpsertPlayerInfo.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/UpsertPlayerInfo.java index a63eb6941..1eac48007 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/UpsertPlayerInfo.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/UpsertPlayerInfo.java @@ -22,6 +22,7 @@ import com.velocitypowered.api.util.GameProfile; import com.velocitypowered.proxy.connection.MinecraftSessionHandler; import com.velocitypowered.proxy.protocol.MinecraftPacket; import com.velocitypowered.proxy.protocol.ProtocolUtils; +import com.velocitypowered.proxy.protocol.packet.chat.ComponentHolder; import com.velocitypowered.proxy.protocol.packet.chat.RemoteChatSession; import io.netty.buffer.ByteBuf; import java.util.ArrayList; @@ -31,7 +32,6 @@ import java.util.Collection; import java.util.EnumSet; import java.util.List; import java.util.UUID; -import net.kyori.adventure.text.Component; import org.jetbrains.annotations.Nullable; public class UpsertPlayerInfo implements MinecraftPacket { @@ -179,16 +179,14 @@ public class UpsertPlayerInfo implements MinecraftPacket { }), UPDATE_DISPLAY_NAME((version, buf, info) -> { // read if (buf.readBoolean()) { - info.displayName = ProtocolUtils.getJsonChatSerializer(version) - .deserialize(ProtocolUtils.readString(buf)); + info.displayName = ComponentHolder.read(buf, version); } else { info.displayName = null; } }, (version, buf, info) -> { // write buf.writeBoolean(info.displayName != null); if (info.displayName != null) { - ProtocolUtils.writeString(buf, ProtocolUtils.getJsonChatSerializer(version) - .serialize(info.displayName)); + info.displayName.write(buf, version); } }); @@ -219,7 +217,7 @@ public class UpsertPlayerInfo implements MinecraftPacket { private int latency; private int gameMode; @Nullable - private Component displayName; + private ComponentHolder displayName; @Nullable private RemoteChatSession chatSession; @@ -248,7 +246,7 @@ public class UpsertPlayerInfo implements MinecraftPacket { } @Nullable - public Component getDisplayName() { + public ComponentHolder getDisplayName() { return displayName; } @@ -273,7 +271,7 @@ public class UpsertPlayerInfo implements MinecraftPacket { this.gameMode = gameMode; } - public void setDisplayName(@Nullable Component displayName) { + public void setDisplayName(@Nullable ComponentHolder displayName) { this.displayName = displayName; } 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 new file mode 100644 index 000000000..4aa22f5bf --- /dev/null +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/chat/ComponentHolder.java @@ -0,0 +1,82 @@ +package com.velocitypowered.proxy.protocol.packet.chat; + +import com.velocitypowered.api.network.ProtocolVersion; +import com.velocitypowered.proxy.protocol.ProtocolUtils; +import io.netty.buffer.ByteBuf; +import net.kyori.adventure.nbt.BinaryTag; +import net.kyori.adventure.nbt.BinaryTagIO; +import net.kyori.adventure.nbt.StringBinaryTag; +import net.kyori.adventure.text.Component; +import org.checkerframework.checker.nullness.qual.MonotonicNonNull; + +public class ComponentHolder { + + public static ComponentHolder EMPTY = new ComponentHolder(null, Component.empty()); + static { + EMPTY.json = "{\"text\":\"\"}"; + EMPTY.binaryTag = StringBinaryTag.stringBinaryTag(""); + } + + private final ProtocolVersion version; + private @MonotonicNonNull Component component; + private @MonotonicNonNull String json; + private @MonotonicNonNull BinaryTag binaryTag; + + public ComponentHolder(ProtocolVersion version, Component component) { + this.version = version; + this.component = component; + } + + public ComponentHolder(ProtocolVersion version, String json) { + this.version = version; + this.json = json; + } + + public ComponentHolder(ProtocolVersion version, BinaryTag binaryTag) { + this.version = version; + this.binaryTag = binaryTag; + } + + public Component getComponent() { + if (component == null) { + if (json != null) { + component = ProtocolUtils.getJsonChatSerializer(version).deserialize(json); + } else if (binaryTag != null) { + //TODO component = deserialize(binaryTag); + throw new UnsupportedOperationException("binary tag -> component not implemented yet"); + } + } + return component; + } + + public String getJson() { + if (json == null) { + json = ProtocolUtils.getJsonChatSerializer(version).serialize(getComponent()); + } + return json; + } + + public BinaryTag getBinaryTag() { + if (binaryTag == null) { + //TODO binaryTag = serialize(getComponent()); + throw new UnsupportedOperationException("component -> binary tag not implemented yet"); + } + return binaryTag; + } + + public static ComponentHolder read(ByteBuf buf, ProtocolVersion version) { + if (version.compareTo(ProtocolVersion.MINECRAFT_1_20_3) >= 0) { + return new ComponentHolder(version, ProtocolUtils.readBinaryTag(buf, version, BinaryTagIO.reader())); + } else { + return new ComponentHolder(version, ProtocolUtils.readString(buf)); + } + } + + public void write(ByteBuf buf, ProtocolVersion version) { + if (version.compareTo(ProtocolVersion.MINECRAFT_1_20_3) >= 0) { + ProtocolUtils.writeBinaryTag(buf, version, getBinaryTag()); + } else { + ProtocolUtils.writeString(buf, getJson()); + } + } +} diff --git a/proxy/src/main/java/com/velocitypowered/proxy/tablist/InternalTabList.java b/proxy/src/main/java/com/velocitypowered/proxy/tablist/InternalTabList.java index dcf00ecae..25d82d87a 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/tablist/InternalTabList.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/tablist/InternalTabList.java @@ -17,6 +17,7 @@ package com.velocitypowered.proxy.tablist; +import com.velocitypowered.api.proxy.Player; import com.velocitypowered.api.proxy.player.TabList; import com.velocitypowered.proxy.protocol.packet.LegacyPlayerListItem; import com.velocitypowered.proxy.protocol.packet.RemovePlayerInfo; @@ -27,6 +28,8 @@ import com.velocitypowered.proxy.protocol.packet.UpsertPlayerInfo; */ public interface InternalTabList extends TabList { + Player getPlayer(); + default void processLegacy(LegacyPlayerListItem packet) { } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/tablist/KeyedVelocityTabList.java b/proxy/src/main/java/com/velocitypowered/proxy/tablist/KeyedVelocityTabList.java index e24c337ed..69080af47 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/tablist/KeyedVelocityTabList.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/tablist/KeyedVelocityTabList.java @@ -59,6 +59,11 @@ public class KeyedVelocityTabList implements InternalTabList { this.connection = player.getConnection(); } + @Override + public Player getPlayer() { + return player; + } + @Deprecated @Override public void setHeaderAndFooter(Component header, Component footer) { 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 4a95b00de..d910135e9 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/tablist/VelocityTabList.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/tablist/VelocityTabList.java @@ -19,6 +19,7 @@ package com.velocitypowered.proxy.tablist; import com.google.common.base.Preconditions; import com.google.common.collect.Maps; +import com.velocitypowered.api.proxy.Player; import com.velocitypowered.api.proxy.player.ChatSession; import com.velocitypowered.api.proxy.player.TabListEntry; import com.velocitypowered.api.util.GameProfile; @@ -27,6 +28,7 @@ import com.velocitypowered.proxy.connection.client.ConnectedPlayer; import com.velocitypowered.proxy.console.VelocityConsole; import com.velocitypowered.proxy.protocol.packet.RemovePlayerInfo; import com.velocitypowered.proxy.protocol.packet.UpsertPlayerInfo; +import com.velocitypowered.proxy.protocol.packet.chat.ComponentHolder; import com.velocitypowered.proxy.protocol.packet.chat.RemoteChatSession; import java.util.ArrayList; import java.util.Collection; @@ -64,6 +66,11 @@ public class VelocityTabList implements InternalTabList { this.entries = Maps.newHashMap(); } + @Override + public Player getPlayer() { + return player; + } + @Override public void setHeaderAndFooter(Component header, Component footer) { Preconditions.checkNotNull(header, "header"); @@ -103,7 +110,8 @@ 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(entry.getDisplayNameComponent().get()); + playerInfoEntry.setDisplayName(new ComponentHolder(player.getProtocolVersion(), + entry.getDisplayNameComponent().get())); } if (!Objects.equals(previousEntry.getLatency(), entry.getLatency())) { actions.add(UpsertPlayerInfo.Action.UPDATE_LATENCY); @@ -132,7 +140,8 @@ public class VelocityTabList implements InternalTabList { playerInfoEntry.setProfile(entry.getProfile()); if (entry.getDisplayNameComponent().isPresent()) { actions.add(UpsertPlayerInfo.Action.UPDATE_DISPLAY_NAME); - playerInfoEntry.setDisplayName(entry.getDisplayNameComponent().get()); + playerInfoEntry.setDisplayName(new ComponentHolder(player.getProtocolVersion(), + entry.getDisplayNameComponent().get())); } if (entry.getChatSession() != null) { actions.add(UpsertPlayerInfo.Action.INITIALIZE_CHAT); @@ -243,7 +252,7 @@ public class VelocityTabList implements InternalTabList { currentEntry.setLatencyWithoutUpdate(entry.getLatency()); } if (actions.contains(UpsertPlayerInfo.Action.UPDATE_DISPLAY_NAME)) { - currentEntry.setDisplayNameWithoutUpdate(entry.getDisplayName()); + currentEntry.setDisplayNameWithoutUpdate(entry.getDisplayName().getComponent()); } if (actions.contains(UpsertPlayerInfo.Action.INITIALIZE_CHAT)) { currentEntry.setChatSession(entry.getChatSession()); 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 f8bd9760a..72b3b75f4 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/tablist/VelocityTabListEntry.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/tablist/VelocityTabListEntry.java @@ -23,6 +23,8 @@ import com.velocitypowered.api.proxy.player.TabListEntry; import com.velocitypowered.api.util.GameProfile; import com.velocitypowered.proxy.protocol.packet.UpsertPlayerInfo; import java.util.Optional; + +import com.velocitypowered.proxy.protocol.packet.chat.ComponentHolder; import net.kyori.adventure.text.Component; import org.checkerframework.checker.nullness.qual.Nullable; @@ -78,7 +80,7 @@ public class VelocityTabListEntry implements TabListEntry { public TabListEntry setDisplayName(@Nullable Component displayName) { this.displayName = displayName; UpsertPlayerInfo.Entry upsertEntry = this.tabList.createRawEntry(this); - upsertEntry.setDisplayName(displayName); + upsertEntry.setDisplayName(new ComponentHolder(this.tabList.getPlayer().getProtocolVersion(), displayName)); this.tabList.emitActionRaw(UpsertPlayerInfo.Action.UPDATE_DISPLAY_NAME, upsertEntry); return this; } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/util/bossbar/AdventureBossBarManager.java b/proxy/src/main/java/com/velocitypowered/proxy/util/bossbar/AdventureBossBarManager.java index db3560ae0..5f98b7482 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/util/bossbar/AdventureBossBarManager.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/util/bossbar/AdventureBossBarManager.java @@ -20,7 +20,7 @@ package com.velocitypowered.proxy.util.bossbar; import com.google.common.collect.MapMaker; import com.velocitypowered.api.network.ProtocolVersion; import com.velocitypowered.proxy.connection.client.ConnectedPlayer; -import com.velocitypowered.proxy.protocol.ProtocolUtils; +import com.velocitypowered.proxy.protocol.packet.chat.ComponentHolder; import com.velocitypowered.proxy.util.collect.Enum2IntMap; import com.velocitypowered.proxy.util.concurrent.Once; import java.util.Collections; @@ -212,8 +212,7 @@ public class AdventureBossBarManager implements BossBar.Listener { .proxy.protocol.packet.BossBar(); packet.setUuid(this.id); packet.setAction(com.velocitypowered.proxy.protocol.packet.BossBar.ADD); - packet.setName(ProtocolUtils.getJsonChatSerializer(player.getProtocolVersion()) - .serialize(player.translateMessage(bar.name()))); + packet.setName(new ComponentHolder(player.getProtocolVersion(), player.translateMessage(bar.name()))); packet.setColor(COLORS_TO_PROTOCOL.get(bar.color())); packet.setOverlay(OVERLAY_TO_PROTOCOL.get(bar.overlay())); packet.setPercent(bar.progress()); @@ -247,7 +246,7 @@ public class AdventureBossBarManager implements BossBar.Listener { .proxy.protocol.packet.BossBar(); packet.setUuid(this.id); packet.setAction(com.velocitypowered.proxy.protocol.packet.BossBar.UPDATE_NAME); - packet.setName(ProtocolUtils.getJsonChatSerializer(version).serialize(name)); + packet.setName(new ComponentHolder(version, name)); return packet; }