Packets¶
Section notes in abi.h
Β§I NBT binary, KvDb (thread-safe), system & server info
Packet interception, appended and struct_size-gated.
Raw wire-format interception in both directions. This is the primitive send_packet could not provide: it observes and rewrites bytes that already exist, instead of manufacturing new ones.
Delivery unit is exactly ONE packet β the leading unsigned-varint header followed by the packet body. Batching and compression live further down the peer chain (BatchedNetworkPeer splits inbound batches and re-batches outbound ones), so a callback never sees a batch and never has to produce a length prefix.
The bridge decodes the header: packet_id is its low 10 bits, sender_sub_id / target_sub_id the two 2-bit fields above it, and body/body_len point PAST the header. A REPLACE verdict supplies a new BODY only; the bridge re-encodes the header from edit, so a rewrite never reproduces varint framing and packet-id remapping is a field assignment rather than a byte-surgery exercise.
Dispatch chains: with several subscribers, each one sees the output of the previous, in registration order. The first DROP wins and the rest are skipped. Subscriber lists are snapshotted before dispatch, so a callback may register or unregister (including itself) safely.
Threading β read this before touching game state. Inbound callbacks run wherever the connection is pumped and outbound ones wherever the send originates. In practice that is the server thread, but async flush means it is not guaranteed. Treat these as "not necessarily the game thread": a handler stays short, guards its own state, and routes anything that touches the world through schedule.
Detours install lazily on the first subscriber and are never unpatched (an unsubscribe can arrive from inside the hooked function). With no subscribers the hook bodies fast-path straight to origin.
Appended: packet interception filtered by id
Slots¶
send_packet¶
| C | |
|---|---|
Group note
Raw per-connection packet send (additive, gated by struct_size) β the generic primitive spawn_particle_for derives from. packet_id is a MinecraftPacketIds value; body/body_len is the packet's wire-format body for the CURRENT game version. The bridge deserializes it into a real packet object (MinecraftPackets::createPacket + Packet::read) and delivers it to the resolved player's connection only. False if: player offline, unknown/unconstructible id, body fails to parse, or bytes are left over after parsing (wrong shape for this version). ESCAPE HATCH: the wire format is version-specific and is the caller's responsibility; prefer typed entries when one exists.
- Call:
api->send_packet(sel, packet_id, body, body_len) - Parameters:
- sel :
PierPlayerSel - packet_id :
int32_t - body :
uint8_t const* - body_len :
size_t
- sel :
- Return type:
bool - Section of abi.h: Β§I NBT binary, KvDb (thread-safe), system & server info
- Position in the table: slot 79, counting from 0
- Callers in each binding:
- Rust:
Player::send_packet - Go:
Raw.SendPacket
- Rust:
packet_hook_register¶
| C | |
|---|---|
Register a raw packet interceptor. dir_mask is PIER_PKT_MASK_INBOUND | PIER_PKT_MASK_OUTBOUND (a zero mask registers nothing and returns NULL). Returns NULL on failure.
- Call:
api->packet_hook_register(mod, dir_mask, cb, user) - Parameters:
- mod :
PierModHandle - dir_mask :
int32_t - cb :
PierPacketCb - user :
void*
- mod :
- Return type:
PierPacketHookHandle - Section of abi.h: Packet interception, appended and struct_size-gated.
- Position in the table: slot 136, counting from 0
- Callers in each binding:
packet_hook_unregister¶
| C | |
|---|---|
Unregister. Safe to call from inside the callback.
- Call:
api->packet_hook_unregister(mod, handle) - Parameters:
- mod :
PierModHandle - handle :
PierPacketHookHandle
- mod :
- Return type:
bool - Section of abi.h: Packet interception, appended and struct_size-gated.
- Position in the table: slot 137, counting from 0
- Callers in each binding:
packet_conn_hook_register¶
| C | |
|---|---|
Register a connection open/close observer. Returns NULL on failure. The close notification is the only reliable signal for dropping per-connection state: a connection that never finishes the login handshake never becomes a Player, so no player event covers it.
- Call:
api->packet_conn_hook_register(mod, cb, user) - Parameters:
- mod :
PierModHandle - cb :
PierConnCb - user :
void*
- mod :
- Return type:
PierPacketHookHandle - Section of abi.h: Packet interception, appended and struct_size-gated.
- Position in the table: slot 138, counting from 0
- Callers in each binding:
- Rust:
Packets::on_connection - Go:
RegisterConnHook
- Rust:
packet_conn_hook_unregister¶
| C | |
|---|---|
Unregister. Safe to call from inside the callback.
- Call:
api->packet_conn_hook_unregister(mod, handle) - Parameters:
- mod :
PierModHandle - handle :
PierPacketHookHandle
- mod :
- Return type:
bool - Section of abi.h: Packet interception, appended and struct_size-gated.
- Position in the table: slot 139, counting from 0
- Callers in each binding:
packet_hook_register_ids¶
| C | |
|---|---|
As packet_hook_register, but the callback fires only for the listed packet ids (MinecraftPacketIds values, 0..1023). packet_hook_register subscribes to every id and therefore costs one callback per packet in that direction, chunk data included; a mod watching a few ids should use this slot, since a packet no subscriber listed is passed through before any lock is taken. An empty list, or one whose ids are all out of range, is refused. The same unregister slot applies. Any thread.
- Call:
api->packet_hook_register_ids(mod, dir_mask, ids, count, cb, user) - Parameters:
- mod :
PierModHandle - dir_mask :
int32_t - ids :
int32_t const* - count :
size_t - cb :
PierPacketCb - user :
void*
- mod :
- Return type:
PierPacketHookHandle - Section of abi.h: Appended: packet interception filtered by id
- Position in the table: slot 188, counting from 0
- Callers in each binding:
- Rust:
Packets::intercept_ids - Go:
RegisterPacketHookIDs
- Rust:
Types¶
PierPacketEvent¶
One intercepted packet. Every pointer inside is borrowed and valid only for the duration of the callback. Anything kept past it must be copied.
PierPacketEdit¶
| C | |
|---|---|
Mutable header fields, pre-filled from the event. Assignments here only take effect when the callback returns PIER_PKT_REPLACE.
PierPacketHookHandle¶
| C | |
|---|---|
Drop via packet_hook_unregister / packet_conn_hook_unregister.
PierPacketCb¶
| C | |
|---|---|
Packet interceptor. To rewrite, call replace(replace_ctx, bytes, len) with the NEW BODY (header excluded) and return PIER_PKT_REPLACE. Calling replace more than once keeps the last body; returning REPLACE without ever calling it means "empty body".
PierConnCb¶
| C | |
|---|---|
Connection lifecycle: opened is true on accept, false on close.
PIER_PKT_*¶
PierPacketEvent::direction, and the bit positions used by dir_mask.
| Name | Value | Description |
|---|---|---|
PIER_PKT_INBOUND |
0 |
client -> server |
PIER_PKT_OUTBOUND |
1 |
server -> client |
PIER_PKT_MASK_*¶
| Name | Value | Description |
|---|---|---|
PIER_PKT_MASK_INBOUND |
(1 << PIER_PKT_INBOUND) |
|
PIER_PKT_MASK_OUTBOUND |
(1 << PIER_PKT_OUTBOUND) |
PIER_PKT_*¶
PierPacketCb return value. Anything else is treated as PASS.
| Name | Value | Description |
|---|---|---|
PIER_PKT_PASS |
0 |
forward unchanged; replace output ignored |
PIER_PKT_REPLACE |
1 |
forward the body handed to replace |
PIER_PKT_DROP |
2 |
swallow the packet entirely |