From 9b1798d6438107fdf0d5939b79a8cf71f4d16e2c Mon Sep 17 00:00:00 2001 From: Nassim Jahnke <nassim@njahnke.dev> Date: Thu, 27 Mar 2025 14:22:38 +0100 Subject: [PATCH] Simplify custom payload handling (#12347) --- build-data/paper.at | 2 +- .../common/custom/DiscardedPayload.java.patch | 13 ++- .../ServerCommonPacketListenerImpl.java.patch | 90 ++++++++++--------- .../inventory/HorseInventoryMenu.java.patch | 2 +- .../craftbukkit/entity/CraftPlayer.java | 7 +- 5 files changed, 59 insertions(+), 55 deletions(-) diff --git a/build-data/paper.at b/build-data/paper.at index 90f4e41687..e5c60c62d4 100644 --- a/build-data/paper.at +++ b/build-data/paper.at @@ -491,8 +491,8 @@ public net.minecraft.world.inventory.AnvilMenu repairItemCountCost public net.minecraft.world.inventory.BrewingStandMenu brewingStandData public net.minecraft.world.inventory.CraftingMenu access public net.minecraft.world.inventory.DispenserMenu dispenser -public net.minecraft.world.inventory.HorseInventoryMenu horse public net.minecraft.world.inventory.HorseInventoryMenu SLOT_BODY_ARMOR +public net.minecraft.world.inventory.HorseInventoryMenu horse public net.minecraft.world.inventory.MerchantContainer selectionHint public net.minecraft.world.inventory.Slot slot public net.minecraft.world.item.AdventureModePredicate predicates diff --git a/paper-server/patches/sources/net/minecraft/network/protocol/common/custom/DiscardedPayload.java.patch b/paper-server/patches/sources/net/minecraft/network/protocol/common/custom/DiscardedPayload.java.patch index 38c086da6b..d0e27d1590 100644 --- a/paper-server/patches/sources/net/minecraft/network/protocol/common/custom/DiscardedPayload.java.patch +++ b/paper-server/patches/sources/net/minecraft/network/protocol/common/custom/DiscardedPayload.java.patch @@ -1,21 +1,26 @@ --- a/net/minecraft/network/protocol/common/custom/DiscardedPayload.java +++ b/net/minecraft/network/protocol/common/custom/DiscardedPayload.java -@@ -4,13 +_,14 @@ +@@ -4,13 +_,19 @@ import net.minecraft.network.codec.StreamCodec; import net.minecraft.resources.ResourceLocation; -public record DiscardedPayload(ResourceLocation id) implements CustomPacketPayload { -+public record DiscardedPayload(ResourceLocation id, io.netty.buffer.ByteBuf data) implements CustomPacketPayload { // CraftBukkit - store data ++public record DiscardedPayload(ResourceLocation id, byte[] data) implements CustomPacketPayload { // Paper - store data public static <T extends FriendlyByteBuf> StreamCodec<T, DiscardedPayload> codec(ResourceLocation id, int maxSize) { - return CustomPacketPayload.codec((value, output) -> {}, buffer -> { + return CustomPacketPayload.codec((value, output) -> { -+ output.writeBytes(value.data); // CraftBukkit - serialize ++ // Paper start ++ // Always write data ++ output.writeBytes(value.data); + }, buffer -> { int i = buffer.readableBytes(); if (i >= 0 && i <= maxSize) { - buffer.skipBytes(i); - return new DiscardedPayload(id); -+ return new DiscardedPayload(id, buffer.readBytes(i)); // CraftBukkit ++ final byte[] data = new byte[i]; ++ buffer.readBytes(data); ++ return new DiscardedPayload(id, data); ++ // Paper end } else { throw new IllegalArgumentException("Payload may not be larger than " + maxSize + " bytes"); } diff --git a/paper-server/patches/sources/net/minecraft/server/network/ServerCommonPacketListenerImpl.java.patch b/paper-server/patches/sources/net/minecraft/server/network/ServerCommonPacketListenerImpl.java.patch index af6cdd217d..74d485b667 100644 --- a/paper-server/patches/sources/net/minecraft/server/network/ServerCommonPacketListenerImpl.java.patch +++ b/paper-server/patches/sources/net/minecraft/server/network/ServerCommonPacketListenerImpl.java.patch @@ -95,7 +95,7 @@ } } -@@ -88,30 +_,119 @@ +@@ -88,30 +_,123 @@ public void handlePong(ServerboundPongPacket packet) { } @@ -105,64 +105,68 @@ @Override public void handleCustomPayload(ServerboundCustomPayloadPacket packet) { - } -+ // CraftBukkit start -+ // Paper start - Brand support ++ // Paper start + if (packet.payload() instanceof net.minecraft.network.protocol.common.custom.BrandPayload(String brand)) { + this.player.clientBrandName = brand; + } -+ // Paper end - Brand support ++ + if (!(packet.payload() instanceof final net.minecraft.network.protocol.common.custom.DiscardedPayload discardedPayload)) { + return; + } -+ PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel()); -+ net.minecraft.resources.ResourceLocation identifier = packet.payload().type().id(); -+ io.netty.buffer.ByteBuf payload = discardedPayload.data(); + -+ if (identifier.equals(ServerCommonPacketListenerImpl.CUSTOM_REGISTER)) { -+ try { -+ String channels = payload.toString(com.google.common.base.Charsets.UTF_8); -+ for (String channel : channels.split("\0")) { -+ this.getCraftPlayer().addChannel(channel); -+ } -+ } catch (Exception ex) { -+ ServerGamePacketListenerImpl.LOGGER.error("Couldn't register custom payload", ex); -+ this.disconnect(Component.literal("Invalid payload REGISTER!"), org.bukkit.event.player.PlayerKickEvent.Cause.INVALID_PAYLOAD); // Paper - kick event cause -+ } -+ } else if (identifier.equals(ServerCommonPacketListenerImpl.CUSTOM_UNREGISTER)) { -+ try { -+ String channels = payload.toString(com.google.common.base.Charsets.UTF_8); -+ for (String channel : channels.split("\0")) { -+ this.getCraftPlayer().removeChannel(channel); -+ } -+ } catch (Exception ex) { -+ ServerGamePacketListenerImpl.LOGGER.error("Couldn't unregister custom payload", ex); -+ this.disconnect(Component.literal("Invalid payload UNREGISTER!"), org.bukkit.event.player.PlayerKickEvent.Cause.INVALID_PAYLOAD); // Paper - kick event cause -+ } -+ } else { -+ try { -+ byte[] data = new byte[payload.readableBytes()]; -+ payload.readBytes(data); -+ // Paper start - Brand support; Retain this incase upstream decides to 'break' the new mechanism in favour of backwards compat... -+ if (identifier.equals(MINECRAFT_BRAND)) { -+ try { -+ this.player.clientBrandName = new net.minecraft.network.FriendlyByteBuf(io.netty.buffer.Unpooled.copiedBuffer(data)).readUtf(256); -+ } catch (StringIndexOutOfBoundsException ex) { -+ this.player.clientBrandName = "illegal"; ++ PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel()); ++ ++ final net.minecraft.resources.ResourceLocation identifier = packet.payload().type().id(); ++ final byte[] data = discardedPayload.data(); ++ try { ++ final boolean registerChannel = ServerCommonPacketListenerImpl.CUSTOM_REGISTER.equals(identifier); ++ if (registerChannel || ServerCommonPacketListenerImpl.CUSTOM_UNREGISTER.equals(identifier)) { ++ // Strings separated by zeros instead of length prefixes ++ int startIndex = 0; ++ for (int i = 0; i < data.length; i++) { ++ final byte b = data[i]; ++ if (b != 0) { ++ continue; + } ++ ++ readChannelIdentifier(data, startIndex, i, registerChannel); ++ startIndex = i + 1; + } -+ // Paper end - Brand support -+ this.cserver.getMessenger().dispatchIncomingMessage(this.player.getBukkitEntity(), identifier.toString(), data); -+ } catch (Exception ex) { -+ ServerGamePacketListenerImpl.LOGGER.error("Couldn't dispatch custom payload", ex); -+ this.disconnect(Component.literal("Invalid custom payload!"), org.bukkit.event.player.PlayerKickEvent.Cause.INVALID_PAYLOAD); // Paper - kick event cause ++ ++ // Read the last one ++ readChannelIdentifier(data, startIndex, data.length, registerChannel); ++ return; + } ++ ++ if (identifier.equals(MINECRAFT_BRAND)) { ++ this.player.clientBrandName = new net.minecraft.network.FriendlyByteBuf(io.netty.buffer.Unpooled.wrappedBuffer(data)).readUtf(256); ++ } ++ ++ this.cserver.getMessenger().dispatchIncomingMessage(this.player.getBukkitEntity(), identifier.toString(), data); ++ } catch (final Exception e) { ++ ServerGamePacketListenerImpl.LOGGER.error("Couldn't handle custom payload on channel {}", identifier, e); ++ this.disconnect(Component.literal("Invalid custom payload payload!"), org.bukkit.event.player.PlayerKickEvent.Cause.INVALID_PAYLOAD); // Paper - kick event cause ++ } ++ } ++ ++ private void readChannelIdentifier(final byte[] data, final int from, final int to, final boolean register) { ++ final int length = to - from; ++ if (length == 0) { ++ return; ++ } ++ ++ final String channel = new String(data, from, length, java.nio.charset.StandardCharsets.US_ASCII); ++ if (register) { ++ this.getCraftPlayer().addChannel(channel); ++ } else { ++ this.getCraftPlayer().removeChannel(channel); + } + } + + public final boolean isDisconnected() { + return (!this.player.joining && !this.connection.isConnected()) || this.processedDisconnect; // Paper - Fix duplication bugs + } -+ // CraftBukkit end ++ // Paper end @Override public void handleResourcePackResponse(ServerboundResourcePackPacket packet) { diff --git a/paper-server/patches/sources/net/minecraft/world/inventory/HorseInventoryMenu.java.patch b/paper-server/patches/sources/net/minecraft/world/inventory/HorseInventoryMenu.java.patch index 8128aca0d5..5f7e1b7747 100644 --- a/paper-server/patches/sources/net/minecraft/world/inventory/HorseInventoryMenu.java.patch +++ b/paper-server/patches/sources/net/minecraft/world/inventory/HorseInventoryMenu.java.patch @@ -1,7 +1,7 @@ --- a/net/minecraft/world/inventory/HorseInventoryMenu.java +++ b/net/minecraft/world/inventory/HorseInventoryMenu.java @@ -19,9 +_,23 @@ - private final AbstractHorse horse; + public final AbstractHorse horse; public static final int SLOT_BODY_ARMOR = 1; private static final int SLOT_HORSE_INVENTORY_START = 2; + // CraftBukkit start diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java index 5050f446e7..cc4b2061ae 100644 --- a/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +++ b/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java @@ -6,7 +6,6 @@ import com.google.common.collect.ImmutableSet; import com.google.common.io.BaseEncoding; import com.mojang.authlib.GameProfile; import com.mojang.datafixers.util.Pair; -import io.netty.buffer.Unpooled; import io.papermc.paper.FeatureHooks; import io.papermc.paper.configuration.GlobalConfiguration; import io.papermc.paper.entity.LookAnchor; @@ -103,7 +102,6 @@ import net.minecraft.server.players.UserWhiteListEntry; import net.minecraft.sounds.SoundEvent; import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.ai.attributes.AttributeInstance; -import net.minecraft.world.entity.ai.attributes.AttributeMap; import net.minecraft.world.entity.ai.attributes.Attributes; import net.minecraft.world.entity.item.ItemEntity; import net.minecraft.world.food.FoodData; @@ -174,7 +172,6 @@ import org.bukkit.craftbukkit.map.CraftMapView; import org.bukkit.craftbukkit.map.RenderData; import org.bukkit.craftbukkit.potion.CraftPotionEffectType; import org.bukkit.craftbukkit.potion.CraftPotionUtil; -import org.bukkit.craftbukkit.profile.CraftPlayerProfile; import org.bukkit.craftbukkit.scoreboard.CraftScoreboard; import org.bukkit.craftbukkit.util.CraftChatMessage; import org.bukkit.craftbukkit.util.CraftLocation; @@ -189,7 +186,6 @@ import org.bukkit.event.player.PlayerExpCooldownChangeEvent; import org.bukkit.event.player.PlayerHideEntityEvent; import org.bukkit.event.player.PlayerRegisterChannelEvent; import org.bukkit.event.player.PlayerShowEntityEvent; -import org.bukkit.event.player.PlayerSpawnChangeEvent; import org.bukkit.event.player.PlayerTeleportEvent; import org.bukkit.event.player.PlayerUnregisterChannelEvent; import org.bukkit.inventory.EquipmentSlot; @@ -202,7 +198,6 @@ import org.bukkit.plugin.Plugin; import org.bukkit.plugin.messaging.StandardMessenger; import org.bukkit.potion.PotionEffect; import org.bukkit.potion.PotionEffectType; -import org.bukkit.profile.PlayerProfile; import org.bukkit.scoreboard.Scoreboard; import org.jetbrains.annotations.NotNull; @@ -2463,7 +2458,7 @@ public class CraftPlayer extends CraftHumanEntity implements Player { } private void sendCustomPayload(ResourceLocation id, byte[] message) { - ClientboundCustomPayloadPacket packet = new ClientboundCustomPayloadPacket(new DiscardedPayload(id, Unpooled.wrappedBuffer(message))); + ClientboundCustomPayloadPacket packet = new ClientboundCustomPayloadPacket(new DiscardedPayload(id, message)); this.getHandle().connection.send(packet); }