xiao.packet
Read, write, send, intercept and block game packets from Lua, in the spirit of the gLua net library. Everything lives at xiao.packet.
This page explains what you are manipulating. The other pages are references, and the guides walk through typical tasks.
What is a Bedrock packet
Everything a Bedrock client and server say to each other is a game packet. On the wire they travel in layers:
xiao.packet works at the last layer, on the game network thread of the client: you see decrypted, decompressed game packets, and you never deal with RakNet, batching or encryption.
Header and payload
A game packet is a header and a payload:
- The header is an unsigned varint that holds the packet id (
0to1023) and the sub-client ids (split screen). - The payload is everything after the header. It is what Lua sees in
msg.payloadand what you build with a writer.
The client adds the header on send, so you only ever write the payload.
Wire encoding
Payloads use the Bedrock binary format:
Each reader has its writer twin (read_x and write_x).
64-bit values are Lua numbers, exact up to 2^53. The field decoder returns larger values as decimal strings.
Names and ids
A packet is named by its id or by its name. "TextPacket", "Text" and "text" all resolve to the same id: case does not matter and the Packet suffix is optional. An unknown name raises unknown packet '<name>' and an id outside 0 to 1023 raises integer out of range; only xiao.packet.id(name) returns nil instead. xiao.packet.name(id) and msg.name give the full name ("TextPacket").
print(xiao.packet.id("MovePlayer"))
print(xiao.packet.name(9))
Where the client plugs in
Receivers run on the render thread, once per frame, after the fact. Hooks run synchronously on the network thread and decide what the game sees, with the restrictions of their execution context. Sends are queued and flushed by the network thread, just before the next packet the game sends.
Page map
Quick look
Print every MovePlayer the game receives, decoded by field name:
xiao.packet.receive("MovePlayer", "where", function(msg)
local f = msg:fields()
if f ~= nil and f._error == nil then
print(msg.outgoing and "sent" or "received", f.position.x, f.position.y, f.position.z, f.mode)
end
end)