Skip to content

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

Open
atiweb wants to merge 2 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 2 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

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

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).

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