Skip to content

Fix CustomPacketPayload for MC 1.21.11+ / Paper 26 (Id record, DiscardedPayload, serialize)#3622

Open
rkfsociety wants to merge 4 commits into
dmulloy2:masterfrom
rkfsociety:master
Open

Fix CustomPacketPayload for MC 1.21.11+ / Paper 26 (Id record, DiscardedPayload, serialize)#3622
rkfsociety wants to merge 4 commits into
dmulloy2:masterfrom
rkfsociety:master

Conversation

@rkfsociety
Copy link
Copy Markdown

@rkfsociety rkfsociety commented May 4, 2026

Summary

Improves ProtocolLib compatibility with CustomPacketPayload on Minecraft 1.21.11+ and Paper 26.x (including Folia): correct key extraction, body serialization, outbound payloads that the vanilla network codec accepts, and a small updater robustness fix.

Problems

  1. Key lookup — The interface often no longer exposes a no-arg method that returns Identifier / ResourceLocation directly. getId() returns the nested Id record; the Mojang identifier comes from a nested accessor (e.g. id()). The previous fuzzy match failed during <clinit> with ExceptionInInitializerError.
  2. Obfuscated / remapped runtimes — The nested type may not have simple name Id, so resolving CustomPacketPayload$Id by name alone is unreliable. The PR uses the return type of the bridge method (e.g. getId()) and getDeclaredConstructors for the record constructor.
  3. Serialize / write method — There is often no void *(ByteBuf) on the interface. Serialization is a single-arg void method whose parameter must accept the real FriendlyByteBuf (or compatible) instance returned by MinecraftReflection.getPacketDataSerializer. The PR falls back to a BFS over the type hierarchy and matches parameters with paramType.isAssignableFrom(actualSerializerClass).
  4. Outbound CUSTOM_PAYLOAD encoding — The play codec path casts payloads to net.minecraft.network.protocol.common.custom.DiscardedPayload. A ByteBuddy-generated CustomPacketPayload implementation is not a DiscardedPayload, which caused ClassCastException when sending (e.g. plugin channels). When DiscardedPayload(identifier, byte[]) exists (Paper 26+), newHandle() uses that constructor instead of ByteBuddy.
  5. SpigotUpdaterNoClassDefFoundError for SpigotUpdater$SpigotUpdateRunnable in some setups. The inner runnable was moved to a top-level class SpigotUpdateRunnable.

Changes (files)

Area Change
CustomPacketPayloadWrapper.java Chained key accessor; optional DiscardedPayload path; broader serialize + Id record resolution; byte[] fallback when reading unknown payloads
SpigotUpdateRunnable.java New file; SpigotUpdater uses new SpigotUpdateRunnable(this)
SpigotUpdater.java Remove inner class; add setRemoteVersion; trim imports

Testing

  • ./gradlew compileJava, ./gradlew shadowJar — OK
  • Paper 26.1.2 (build 60, Folia): login / plugin messaging; no CustomPacketPayloadWrapper static init failures; no ClassCastException on outbound clientbound/minecraft:custom_payload

Notes for reviewers

  • When DiscardedPayload(id, byte[]) is absent (older versions), behavior falls back to the existing ByteBuddy ProtocolLibCustomPacketPayload path.
  • The Id record + ByteBuddy branch remains for versions that need getId()Id but do not yet use the DiscardedPayload outbound constructor.

@rkfsociety rkfsociety changed the title Fix CustomPacketPayload key lookup for MC 1.21.11+ / Paper 26 (Id record) Fix CustomPacketPayload for MC 1.21.11+ / Paper 26 (Id record, DiscardedPayload, serialize) May 5, 2026
@dmulloy2 dmulloy2 requested a review from Copilot May 9, 2026 14:59
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Improves ProtocolLib’s compatibility with modern CustomPacketPayload implementations (MC 1.21.11+ / Paper 26.x) by hardening key extraction and payload serialization, and adjusts outbound payload construction to satisfy newer network codec expectations. Also refactors the Spigot updater worker into a top-level class to avoid runtime classloading issues.

Changes:

  • Resolve CustomPacketPayload keys via chained accessors (supporting Id record patterns) and more robust nested type/constructor discovery.
  • Serialize payload bodies via a concrete-class method lookup when the interface no longer exposes a suitable write(...) method; add a byte[] reflection fallback for unknown payloads.
  • Prefer constructing DiscardedPayload(id, byte[]) when present to avoid outbound codec ClassCastException; move SpigotUpdateRunnable to a top-level class and add setRemoteVersion.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 3 comments.

File Description
src/main/java/com/comphenix/protocol/wrappers/CustomPacketPayloadWrapper.java Updates key/Id resolution, serialization lookup strategy, and outbound handle construction (including DiscardedPayload path).
src/main/java/com/comphenix/protocol/updater/SpigotUpdateRunnable.java Introduces top-level update worker runnable used by SpigotUpdater.
src/main/java/com/comphenix/protocol/updater/SpigotUpdater.java Switches to the top-level runnable and exposes a setter for remoteVersion.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +357 to +358
Object sample = MinecraftReflection.getPacketDataSerializer(Unpooled.buffer());
serializerArgumentClassCache = c = sample.getClass();
Comment on lines +427 to +429
Object serializer = MinecraftReflection.getPacketDataSerializer(buffer);
serialize.invoke(payload, serializer);
return StreamSerializer.getDefault().getBytesAndRelease(buffer);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants