packet

Read, write, send and block game packets, in the spirit of the gLua net library. It lives at xiao.packet.

A packet is named by its id (0 to 1023) or by its name: "TextPacket", "Text" and "text" all resolve to the same id (case does not matter, the Packet suffix is optional). An unknown name raises an error.

Payloads are the bytes after the packet header, in Bedrock wire format (little endian, unsigned varints, zigzag signed varints). The header (id and sub-client bytes) is added by the client on send.

For the loop that calls these functions each frame, see xiao.signal and Running scripts.


Sending

gLua style, with a current message:

xiao.packet.start("Text")
xiao.packet.write_byte(1)
xiao.packet.write_bool(false)
xiao.packet.write_string("")
local ok, err = xiao.packet.send()

Chainable and reusable:

local w = xiao.packet.writer("Animate")
w:write_varint(1):write_varuint64(runtime_id)
w:send()
print(w:size(), #w:data())

xiao.packet.start(packet: number | string): writer

xiao.packet.start(packet: number | string): writer

  • Starts the current message and returns its writer.

xiao.packet.write_*(...)

xiao.packet.write_*(...)

  • Writes into the current message, with the same write_* functions as the writer methods.
  • Raises an error without start.

xiao.packet.send(): boolean, string

xiao.packet.send(): boolean, string

  • Sends the current message and clears it.
  • Returns true or false, error.

xiao.packet.cancel()

xiao.packet.cancel()

  • Drops the current message.

xiao.packet.writer(packet?: number | string): writer

xiao.packet.writer(packet?: number | string): writer

  • Returns a new independent writer.

xiao.packet.send_raw(packet: number | string, bytes: string)

xiao.packet.send_raw(packet: number | string, bytes: string)

  • Sends a payload already built as a string.

Writer

Each write_* returns the writer.

writer:write_bool(v: boolean): writer

writer:write_bool(v: boolean): writer

  • 1 byte, 0 or 1.

writer:write_byte(n: number): writer

writer:write_byte(n: number): writer

  • 1 byte, -128 to 255.

writer:write_short(n: number): writer

writer:write_short(n: number): writer

  • 2 bytes.

writer:write_ushort(n: number): writer

writer:write_ushort(n: number): writer

  • Alias of writer:write_short.

writer:write_int(n: number): writer

writer:write_int(n: number): writer

  • 4 bytes.

writer:write_uint(n: number): writer

writer:write_uint(n: number): writer

  • Alias of writer:write_int.

writer:write_int64(n: number): writer

writer:write_int64(n: number): writer

  • 8 bytes.

writer:write_uint64(n: number): writer

writer:write_uint64(n: number): writer

  • Alias of writer:write_int64.

writer:write_float(n: number): writer

writer:write_float(n: number): writer

  • IEEE 754, 4 bytes.

writer:write_double(n: number): writer

writer:write_double(n: number): writer

  • IEEE 754, 8 bytes.

writer:write_varint(n: number): writer

writer:write_varint(n: number): writer

  • Zigzag varint, 32 bits.

writer:write_varint64(n: number): writer

writer:write_varint64(n: number): writer

  • Zigzag varint, 64 bits.

writer:write_varuint(n: number): writer

writer:write_varuint(n: number): writer

  • Unsigned varint, 32 bits.

writer:write_varuint64(n: number): writer

writer:write_varuint64(n: number): writer

  • Unsigned varint, 64 bits.

writer:write_string(s: string): writer

writer:write_string(s: string): writer

  • Unsigned varint length, then the bytes.

writer:write_bytes(s: string): writer

writer:write_bytes(s: string): writer

  • Raw bytes, no length.

writer:write_vec2(x: number, y: number): writer

writer:write_vec2(x: number, y: number): writer

  • 2 floats; also takes a table with x, y.

writer:write_vec3(x: number, y: number, z: number): writer

writer:write_vec3(x: number, y: number, z: number): writer

  • 3 floats; also takes a table with x, y, z.

writer:write_blockpos(x: number, y: number, z: number): writer

writer:write_blockpos(x: number, y: number, z: number): writer

  • Zigzag varint x, unsigned varint y (negative y wraps), zigzag varint z.

writer:id(packet?: number | string): number

writer:id(packet?: number | string): number

  • Gets or sets the packet id.

writer:size(): number

writer:size(): number

  • Returns the payload length.

writer:data(): string

writer:data(): string

  • Returns the payload string.

writer:clear()

writer:clear()

  • Resets the payload to empty.

writer:send(): boolean, string

writer:send(): boolean, string

  • Sends the packet; returns true or false, error.
  • The writer can be sent again.

Receiving

xiao.packet.receive(packet: number | string, identity: any, callback: function(msg: packet))

xiao.packet.receive(packet: number | string, identity: any, callback: function(msg: packet))

  • packet is an id, a name, or "*" for every packet.
xiao.packet.receive("Text", "chat_log", function(msg)
    msg:read_byte()
    msg:read_bool()
    local source = msg:read_string()
    print(msg.name, msg.outgoing and "sent" or "received", source)
end)

xiao.packet.receive("*", "counter", function(msg)
    count = (count or 0) + 1
end)

xiao.packet.remove(packet: number | string, identity: any): boolean

xiao.packet.remove(packet: number | string, identity: any): boolean

  • Returns true if the receiver existed.

xiao.packet.reader(bytes: string, packet?: number | string): packet

xiao.packet.reader(bytes: string, packet?: number | string): packet

  • Packet object over any string, to parse saved payloads.

xiao.packet.decode(packet: number | string, bytes: string): string[] | nil

xiao.packet.decode(packet: number | string, bytes: string): string[] | nil

  • Returns the lines of the built-in decoder (same as the packet log), or nil.

xiao.packet.id(name: string): number | nil

xiao.packet.id(name: string): number | nil

  • Returns the id or nil.

xiao.packet.name(id: number): string | nil

xiao.packet.name(id: number): string | nil

  • Returns the name or nil.

xiao.packet.dropped(): number

xiao.packet.dropped(): number

  • Returns the number of packets lost because the queue was full.

Packet object

Callbacks get a packet object, in both directions (sent by the game and received from the server).

packet

Field Type Description
id number packet id
name string packet name
outgoing boolean true for client to server
blocked boolean true if the client dropped it (block list)
size number real payload size
complete boolean false if payload was cut (capture limit, or over 64 KiB)
payload string payload bytes as a string
pos number read position, reset to 0 before each callback

Reading past the end of the packet raises read past end of packet.

packet:read_bool(): boolean

packet:read_bool(): boolean

  • Reads a boolean.

packet:read_byte(): number

packet:read_byte(): number

  • Reads a byte.

packet:read_short(): number

packet:read_short(): number

  • Reads a short.

packet:read_ushort(): number

packet:read_ushort(): number

  • Reads an unsigned short.

packet:read_int(): number

packet:read_int(): number

  • Reads an int.

packet:read_uint(): number

packet:read_uint(): number

  • Reads an unsigned int.

packet:read_int64(): number

packet:read_int64(): number

  • Reads a 64-bit int.

packet:read_uint64(): number

packet:read_uint64(): number

  • Reads an unsigned 64-bit int.

packet:read_float(): number

packet:read_float(): number

  • Reads a float.

packet:read_double(): number

packet:read_double(): number

  • Reads a double.

packet:read_varint(): number

packet:read_varint(): number

  • Reads a zigzag varint.

packet:read_varuint(): number

packet:read_varuint(): number

  • Reads an unsigned varint.

packet:read_varint64(): number

packet:read_varint64(): number

  • Reads a 64-bit zigzag varint.

packet:read_varuint64(): number

packet:read_varuint64(): number

  • Reads a 64-bit unsigned varint.

packet:read_string(): string

packet:read_string(): string

  • Reads a string.

packet:read_bytes(n?: number): string

packet:read_bytes(n?: number): string

  • Reads n bytes; without n, returns the rest of the payload.

packet:read_vec2(): vector2

packet:read_vec2(): vector2

  • Reads 2 floats (8 bytes) and returns them as a vector2 with x and y.

packet:read_vec3(): vector3

packet:read_vec3(): vector3

  • Reads 3 floats (12 bytes) and returns them as a vector3 with x, y and z, usable directly as a paint point.

packet:read_blockpos(): vector3

packet:read_blockpos(): vector3

  • Reads a block position: zigzag varint x, unsigned varint y (wrapped to a signed 32-bit value), zigzag varint z.
  • Returns a vector3 with integer x, y, z.

packet:tell(): number

packet:tell(): number

  • Returns the read position.

packet:seek(pos: number)

packet:seek(pos: number)

  • Sets the read position.

packet:remaining(): number

packet:remaining(): number

  • Returns the number of bytes left to read.

packet:decode(): string[] | nil

packet:decode(): string[] | nil

  • Decodes the packet with the built-in decoder.

Copy from the packet log

In the packet log, the menu of a packet has Copy as Lua. It copies code that sends the same packet again (shortened example):

xiao.packet.start("MovePlayer")
xiao.packet.write_varuint64(42)
xiao.packet.write_float(1)
xiao.packet.write_float(2)
xiao.packet.write_float(3)
xiao.packet.send()
  • Each field the decoder knows becomes a typed write_* call with its value, so it can be edited.
  • Bytes the decoder does not cover (unknown packet, NBT, value that a Lua number cannot hold exactly) become write_bytes("\x..").
  • The copied code always rebuilds the original payload byte for byte.
  • The button is disabled when the log only kept part of the payload.

Blocking

The script block list adds to the packet log block list and is cleared on Clear VM.

xiao.packet.block(packet: number | string)

xiao.packet.block(packet: number | string)

  • The client drops this packet id in both directions.

xiao.packet.unblock(packet: number | string)

xiao.packet.unblock(packet: number | string)

  • Stops dropping it.

xiao.packet.unblock_all()

xiao.packet.unblock_all()

  • Clears the script block list.

xiao.packet.is_blocked(packet: number | string): boolean

xiao.packet.is_blocked(packet: number | string): boolean

  • Script block list only.
xiao.packet.block("Animate")
xiao.signal.add("unload", "restore", function() xiao.packet.unblock_all() end)

Limits

  • A payload holds at most 512 bytes; writing past that raises packet payload exceeds 512 bytes.
  • An integer outside its type range, or with a fraction, raises integer out of range.
  • 64-bit values are Lua numbers: exact up to 2^53.
  • The packet goes to the same connection, with the same reliability, as the last packet of that id the game sent.
  • Up to 4096 packets are queued per frame for receivers.
  • send queues at most 8 pending sends.