Derive macros for Krunker WebSocket packets.
Krunker sends each message as an event name followed by a flat positional array:
["k", 42, 1.0, 5.0, ..., 33, 0] ← player update
["f", 2] ← failed input
["po"] ← ping (no payload)
This crate lets you describe those packets as plain Rust structs and generates the encode/decode boilerplate for you.
krunker_packet_core(imported aspacket_core) — runtime traits and decode helpers (PacketMeta,PacketDecode,OutgoingPacket,OutgoingFields,OutgoingEventEnum).krunker_packet_macros(imported aspacket_macros) — proc macros that emit theimplblocks:Packet,OutgoingFields,EventEnum,OutgoingEventEnum.
They are split because Rust proc macros cannot be used by the same crate that defines them.
use packet_macros::Packet;
#[derive(Debug, Packet)]
#[packet(event = "f")]
pub struct FailedInput {
pub issue: i64, // payload[0]
}That's it — FailedInput now decodes from a payload array and encodes back to one.
A more interesting example, with a chunked sub-array:
#[derive(Debug, Packet)]
#[packet(event = "k", allow_extra)]
pub struct PlayerUpdate {
#[packet(chunks(PlayerUpdateData, 13))]
pub updates: Vec<PlayerUpdateData>,
pub rate: i64,
pub timestamp: i64,
}And a top-level dispatcher:
#[derive(Debug, EventEnum)]
pub enum Event {
FailedInput(FailedInput),
PlayerUpdate(PlayerUpdate),
#[event(unknown)]
Unknown(String, Vec<Value>),
}Event::from_event_name(name, payload) matches on each variant's EVENT_NAME and decodes for you.
Runnable binaries live in examples/:
incoming_decode—scalar_as_seq,Optionfields, andchunks(T, N)outgoing_encode— empty packets, nested fields, andcoerce(bool_to_num)event_dispatch— theEventEnumdispatch loopproxy_intercept—OutgoingEventEnumdecode-mutate-reencode round-trip
Run any of them with cargo run -p packet_examples --bin <name>.
There's a small inspector binary in packets-cli/ for poking at captured frames:
$ cargo run -q -p packets-cli -- decode "91 a2 70 6f 00 00"
event: po
trailer: 00 00
payload: 0 value(s)
Accepts packed hex, 0x-prefixed bytes, --file <path> for raw binary, or hex on stdin.
The wire decoder parses untrusted bytes, so there's a small cargo-fuzz harness in fuzz/:
unpack_frame— arbitrary bytes intounpack_frame, shouldn't panic.roundtrip— unpack, repack, unpack again, assert the values match.
cargo +nightly fuzz run unpack_frame
cargo +nightly fuzz run roundtrip