diff --git a/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java b/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java index 0e746d5a7..88a5d213d 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java @@ -32,6 +32,7 @@ import com.velocitypowered.proxy.console.VelocityConsole; import com.velocitypowered.proxy.network.ConnectionManager; import com.velocitypowered.proxy.plugin.VelocityEventManager; import com.velocitypowered.proxy.plugin.VelocityPluginManager; +import com.velocitypowered.proxy.protocol.ProtocolUtils; import com.velocitypowered.proxy.protocol.packet.Chat; import com.velocitypowered.proxy.protocol.util.FaviconSerializer; import com.velocitypowered.proxy.protocol.util.GameProfileSerializer; @@ -91,13 +92,14 @@ public class VelocityServer implements ProxyServer, ForwardingAudience { .registerTypeHierarchyAdapter(Favicon.class, new FaviconSerializer()) .registerTypeHierarchyAdapter(GameProfile.class, new GameProfileSerializer()) .create(); - private static final Gson PRE_1_16_PING_SERIALIZER = GsonComponentSerializer - .colorDownsamplingGson() + private static final Gson PRE_1_16_PING_SERIALIZER = ProtocolUtils + .getJsonChatSerializer(ProtocolVersion.MINECRAFT_1_15_2) .serializer() .newBuilder() .registerTypeHierarchyAdapter(Favicon.class, new FaviconSerializer()) .create(); - private static final Gson POST_1_16_PING_SERIALIZER = GsonComponentSerializer.gson() + private static final Gson POST_1_16_PING_SERIALIZER = ProtocolUtils + .getJsonChatSerializer(ProtocolVersion.MINECRAFT_1_16) .serializer() .newBuilder() .registerTypeHierarchyAdapter(Favicon.class, new FaviconSerializer()) 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 e9b98b33e..baa5bc176 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/ProtocolUtils.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/ProtocolUtils.java @@ -3,10 +3,10 @@ package com.velocitypowered.proxy.protocol; import static com.google.common.base.Preconditions.checkArgument; import static com.velocitypowered.proxy.protocol.util.NettyPreconditions.checkFrame; -import com.google.common.base.Preconditions; import com.velocitypowered.api.network.ProtocolVersion; import com.velocitypowered.api.util.GameProfile; import com.velocitypowered.proxy.protocol.netty.MinecraftDecoder; +import com.velocitypowered.proxy.protocol.util.VelocityLegacyHoverEventSerializer; import com.velocitypowered.proxy.util.except.QuietDecoderException; import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufInputStream; @@ -28,6 +28,18 @@ import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer; public enum ProtocolUtils { ; + + private static final GsonComponentSerializer PRE_1_16_SERIALIZER = + GsonComponentSerializer.builder() + .downsampleColors() + .emitLegacyHoverEvent() + .legacyHoverEventSerializer(VelocityLegacyHoverEventSerializer.INSTANCE) + .build(); + private static final GsonComponentSerializer MODERN_SERIALIZER = + GsonComponentSerializer.builder() + .legacyHoverEventSerializer(VelocityLegacyHoverEventSerializer.INSTANCE) + .build(); + private static final int DEFAULT_MAX_STRING_SIZE = 65536; // 64KiB private static final QuietDecoderException BAD_VARINT_CACHED = new QuietDecoderException("Bad varint decoded"); @@ -468,9 +480,9 @@ public enum ProtocolUtils { */ public static GsonComponentSerializer getJsonChatSerializer(ProtocolVersion version) { if (version.compareTo(ProtocolVersion.MINECRAFT_1_16) >= 0) { - return GsonComponentSerializer.gson(); + return MODERN_SERIALIZER; } - return GsonComponentSerializer.colorDownsamplingGson(); + return PRE_1_16_SERIALIZER; } public enum Direction { diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/util/VelocityLegacyHoverEventSerializer.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/util/VelocityLegacyHoverEventSerializer.java new file mode 100644 index 000000000..043286f87 --- /dev/null +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/util/VelocityLegacyHoverEventSerializer.java @@ -0,0 +1,96 @@ +package com.velocitypowered.proxy.protocol.util; + +import java.io.IOException; +import java.util.UUID; +import net.kyori.adventure.key.Key; +import net.kyori.adventure.nbt.CompoundBinaryTag; +import net.kyori.adventure.nbt.TagStringIO; +import net.kyori.adventure.nbt.api.BinaryTagHolder; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.TextComponent; +import net.kyori.adventure.text.event.HoverEvent; +import net.kyori.adventure.text.event.HoverEvent.ShowEntity; +import net.kyori.adventure.text.event.HoverEvent.ShowItem; +import net.kyori.adventure.text.serializer.gson.LegacyHoverEventSerializer; +import net.kyori.adventure.text.serializer.plain.PlainComponentSerializer; +import net.kyori.adventure.util.Codec.Decoder; +import net.kyori.adventure.util.Codec.Encoder; +import org.checkerframework.checker.nullness.qual.NonNull; + +/** + * An implementation of {@link LegacyHoverEventSerializer} that implements the interface in the + * most literal, albeit "incompatible" way possible. + */ +public class VelocityLegacyHoverEventSerializer implements LegacyHoverEventSerializer { + + public static final LegacyHoverEventSerializer INSTANCE = + new VelocityLegacyHoverEventSerializer(); + + private VelocityLegacyHoverEventSerializer() { + + } + + private static Key legacyIdToFakeKey(byte id) { + return Key.of("velocity", "legacy_hover/id_" + id); + } + + @Override + public HoverEvent.@NonNull ShowItem deserializeShowItem(@NonNull Component input) + throws IOException { + String snbt = PlainComponentSerializer.plain().serialize(input); + CompoundBinaryTag item = TagStringIO.get().asCompound(snbt); + + Key key; + String idIfString = item.getString("id", ""); + if (idIfString.isEmpty()) { + key = legacyIdToFakeKey(item.getByte("id")); + } else { + key = Key.of(idIfString); + } + + byte count = item.getByte("Count", (byte) 1); + return new ShowItem(key, count, BinaryTagHolder.of(snbt)); + } + + @Override + public HoverEvent.@NonNull ShowEntity deserializeShowEntity(@NonNull Component input, + Decoder componentDecoder) throws IOException { + String snbt = PlainComponentSerializer.plain().serialize(input); + CompoundBinaryTag item = TagStringIO.get().asCompound(snbt); + return new ShowEntity(Key.of(item.getString("type")), + UUID.fromString(item.getString("id")), + componentDecoder.decode(item.getString("name"))); + } + + @Override + public @NonNull Component serializeShowItem(HoverEvent.@NonNull ShowItem input) + throws IOException { + final CompoundBinaryTag.Builder builder = CompoundBinaryTag.builder() + .putByte("Count", (byte) input.count()); + + String keyAsString = input.item().asString(); + if (keyAsString.startsWith("velocity:legacy_hover/id_")) { + builder.putByte("id", Byte.parseByte(keyAsString + .substring("velocity:legacy_hover/id_".length()))); + } else { + builder.putString("id", keyAsString); + } + if (input.nbt() != null) { + builder.put("tag", TagStringIO.get().asCompound(input.nbt().string())); + } + + return TextComponent.of(TagStringIO.get().asString(builder.build())); + } + + @Override + public @NonNull Component serializeShowEntity(HoverEvent.@NonNull ShowEntity input, + Encoder componentEncoder) throws IOException { + CompoundBinaryTag.Builder tag = CompoundBinaryTag.builder() + .putString("id", input.id().toString()) + .putString("type", input.type().asString()); + if (input.name() != null) { + tag.putString("name", componentEncoder.encode(input.name())); + } + return TextComponent.of(TagStringIO.get().asString(tag.build())); + } +}