Skip to content

Fix custom_click_action payload (length-prefixed optional NBT) for 1.21.6+ - #1196

Open
atiweb wants to merge 4 commits into
PrismarineJS:masterfrom
atiweb:fix/custom-click-action-nbt
Open

Fix custom_click_action payload (length-prefixed optional NBT) for 1.21.6+#1196
atiweb wants to merge 4 commits into
PrismarineJS:masterfrom
atiweb:fix/custom-click-action-nbt

Conversation

@atiweb

@atiweb atiweb commented Jun 15, 2026

Copy link
Copy Markdown
Contributor

Problem

ServerboundCustomClickActionPacket (the packet a client sends when a dialog button is clicked; dialogs were added in 1.21.6) has:

UNTRUSTED_TAG_CODEC = ByteBufCodecs.optionalTagCodec(() -> new NbtAccounter(32768L, 16))
                          .apply(ByteBufCodecs.lengthPrefixed(65536));
STREAM_CODEC = StreamCodec.composite(Identifier.STREAM_CODEC, ::id, UNTRUSTED_TAG_CODEC, ::payload, ::new);

So the payload is a VarInt byte-length followed by an anonymous optional NBT tag (an empty tag is a single TAG_END byte).

It was typed as nbt?: anonymousNbt (i.e. option<anonymousNbt>), which emits a spurious presence boolean and omits the length prefix. The server then reads that boolean (0x01) as a TAG_Byte and rejects the packet with an "invalid value" error (PrismarineJS/node-minecraft-protocol#1484).

Fix

Type the payload as the new nbtOptionalLengthPrefixed datatype (added in PrismarineJS/node-minecraft-protocol#1495), and rename the field nbtpayload to match the packet record.

Validation

End-to-end with the new datatype, a real form submit — id = "nlogin:login/yes", payload = { password: "hello world" } — serializes to:

10 6e6c6f67696e3a6c6f67696e2f796573   # id string
1a 0a 08 0008 password 000b "hello world" 00   # VarInt(26) + anonymous NBT

which matches the buffer reported as correct in #1484, and round-trips.

Depends on PrismarineJS/node-minecraft-protocol#1495.

ServerboundCustomClickActionPacket's payload is
`ByteBufCodecs.optionalTagCodec(...).apply(ByteBufCodecs.lengthPrefixed(N))`: a
VarInt byte-length followed by an anonymous optional NBT tag (TAG_END = absent).

It was typed as `nbt?: anonymousNbt` (= option<anonymousNbt>), which emits a
spurious presence boolean and no length prefix, so the server rejects the packet
with an "invalid value" error. Type it as the new `nbtOptionalLengthPrefixed`
datatype instead.

Verified end to end: a real form submit with id "nlogin:login/yes" and payload
{ password: "hello world" } now serializes to the exact bytes the client expects.

Fixes PrismarineJS/node-minecraft-protocol#1484.
Requires PrismarineJS/node-minecraft-protocol#1495 (the datatype).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@MayorLeon20

MayorLeon20 commented Jun 17, 2026

Copy link
Copy Markdown
Contributor

You modified the custom click action packet only for 1.21.11.
This packet is not unique to this version

…ions

custom_click_action exists since 1.21.6, not only 1.21.11, so the fix must cover
every version that has the packet. Mirror the 1.21.11 change (register
nbtOptionalLengthPrefixed as native + switch the packet payload to it) in 1.21.6,
1.21.8 and 1.21.9. 1.21.7 inherits 1.21.6 and 1.21.10 inherits 1.21.9 via
dataPaths.json, so those are covered too.
@atiweb

atiweb commented Jun 17, 2026

Copy link
Copy Markdown
Contributor Author

Good catch, thanks — you're right. I've extended the fix to every version that has the packet: 1.21.6, 1.21.8 and 1.21.9 (1.21.7 inherits 1.21.6 and 1.21.10 inherits 1.21.9 via dataPaths.json; 1.21.11 was already in this PR).

I verified each one against its official Mojang server jar — all encode the payload identically:

ByteBufCodecs.optionalTagCodec(() -> new NbtAccounter(32768L, 16)).apply(ByteBufCodecs.lengthPrefixed(65536))

So the packet has been length-prefixed since it was introduced in 1.21.6; minecraft-data just defined it as option<anonymousNbt> uniformly. Same change applied to each: register nbtOptionalLengthPrefixed as native and switch the packet payload to it.

@MayorLeon20

Copy link
Copy Markdown
Contributor

Good catch, thanks — you're right. I've extended the fix to every version that has the packet: 1.21.6, 1.21.8 and 1.21.9 (1.21.7 inherits 1.21.6 and 1.21.10 inherits 1.21.9 via dataPaths.json; 1.21.11 was already in this PR).

I verified each one against its official Mojang server jar — all encode the payload identically:

ByteBufCodecs.optionalTagCodec(() -> new NbtAccounter(32768L, 16)).apply(ByteBufCodecs.lengthPrefixed(65536))

So the packet has been length-prefixed since it was introduced in 1.21.6; minecraft-data just defined it as option<anonymousNbt> uniformly. Same change applied to each: register nbtOptionalLengthPrefixed as native and switch the packet payload to it.

what about 1.21.7 and 1.21.10?

@atiweb

atiweb commented Jun 18, 2026

Copy link
Copy Markdown
Contributor Author

Good question — and it made me re-check my earlier note, which had a mistake.

1.21.10: it's a supported version, but its dataPaths.json entry points everything (including protocol) to pc/1.21.9, so it shares 1.21.9's protocol.json — the fix already covers it (both are protocol 773).

1.21.7: minecraft-data has no 1.21.7 entry at all (not in versions.json / dataPaths.json, no data dir). 1.21.7 and 1.21.8 share protocol 772, and 1.21.8 is the dataset that carries it — which this PR fixes. So protocol 772 is covered by the 1.21.8 change. (My earlier "inherits 1.21.6" was wrong — 1.21.6 is protocol 771. Sorry for the mix-up.)

For reference: 1.21.6=771, 1.21.7/1.21.8=772, 1.21.9/1.21.10=773, 1.21.11=774 — so every protocol carrying custom_click_action (771–774) is covered by the four datasets changed here (1.21.6, 1.21.8, 1.21.9, 1.21.11).

extremeheat and others added 2 commits July 28, 2026 04:36
Master moved 1.21.11 to its own data/pc/1.21.11/proto.yml and repointed pc/latest at
26.1, leaving 1.21.11's protocol.json (already fixed) desynced from its new, unfixed
yaml. Registered nbtOptionalLengthPrefixed and switched the payload there too, then
reran npm run build; 26.1 picks the fix up from pc/latest as well - its
ServerboundCustomClickActionPacket encodes the payload identically:
optionalTagCodec(() -> new NbtAccounter(32768L, 16)).apply(ByteBufCodecs.lengthPrefixed(65536)).
validate and the schema tests pass.
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.

3 participants