Packet log New

Turn a captured packet into a ready-to-run Lua sender or receiver with Copy as Lua and Copy as receiver.

In the packet log, the menu of a packet has Copy as Lua and Copy as receiver. They write a script for that exact packet to the clipboard, with readable names taken from the schemas of the game.

Both are generated off the render thread and announced by a notification when the code is in the clipboard (COPIED AS LUA, COPIED AS RECEIVER, or COPY FAILED).

Copy as Lua

Copies code that sends the same packet again. The values come first, named after the decoder fields, and the write_* calls use them. Generated from a MovePlayer packet:

local MODE = { normal = 0, respawn = 1, teleport = 2, only_head_rot = 3 }
local runtime_id = 1
local position = { x = -70.5, y = 64.62, z = 8.4 }
local pitch = 0
local yaw = 90
local head_yaw = 90
local mode = MODE.normal
local on_ground = true
local ridden_runtime_id = 0
local has_teleport = false
local tick = 1234
xiao.packet.start("MovePlayer")
xiao.packet.write_varuint64(runtime_id)
xiao.packet.write_vec3(position.x, position.y, position.z)
xiao.packet.write_float(pitch)
xiao.packet.write_float(yaw)
xiao.packet.write_float(head_yaw)
xiao.packet.write_byte(mode)
xiao.packet.write_bool(on_ground)
xiao.packet.write_varuint64(ridden_runtime_id)
xiao.packet.write_bool(has_teleport)
xiao.packet.write_varuint64(tick)
xiao.packet.send()

What the generator does:

  • Enums get a table of their names (MODE) and the value uses it (MODE.normal); a value without a unique name stays a number. An enum of up to 16 values lists every name, a larger one only the names the packet uses.
  • Vectors are tables written with write_vec2, write_vec3 or write_blockpos.
  • Lists and sub-structures are nested tables: attribute[1].maximum, item.id, request[1].action[1].count.
  • Bytes the decoder does not cover (unknown packet, end of a partial decode) become named strings, local raw_1 = "\x.." then write_bytes(raw_1); runs over 256 bytes stay inline write_bytes("\x..") lines of 32 bytes.
  • NBT, UUIDs, skins and values a Lua number cannot hold exactly are named strings written with write_bytes; a length written just before such a string is #name (write_varuint(#item.extra)), or its length field when the string is empty (write_varuint(item.extra_length)).
  • Presence flags and counts use their field (write_bool(has_teleport), write_varuint(links)): a decoded packet has no literal write_* argument.
  • Past 150 values and enum tables, the values go in one table local p = { ... } (p.runtime_id) to stay under the Lua limit of 200 locals.
  • The copied code always rebuilds the original payload byte for byte; if a value cannot be written back exactly, the whole payload is copied as write_bytes lines instead.

Copy as receiver

Copies a receiver that reads the same packet field by field, in payload order, then prints the first 6 fields. Generated from the same packet:

xiao.packet.receive("MovePlayer", "move_player_copy", function(msg)
    local runtime_id = msg:read_varuint64()
    local position = msg:read_vec3()
    local pitch = msg:read_float()
    local yaw = msg:read_float()
    local head_yaw = msg:read_float()
    local mode = msg:read_byte()
    local on_ground = msg:read_bool()
    local ridden_runtime_id = msg:read_varuint64()
    local has_teleport = msg:read_bool()
    local tick = msg:read_varuint64()
    print("MovePlayer", "runtime_id", runtime_id, "position", position, "pitch", pitch, "yaw", yaw, "head_yaw", head_yaw, "mode", mode)
end)
  • Names are the field paths with _: attribute_1_name, item_extra.
  • Bytes the decoder does not cover, NBT and other raw values are read with msg:read_bytes(n).
  • On the source packet the receiver reads exactly the whole payload. Lists and optional fields are read as they were in that packet: another packet of the same id with a different count needs a loop, or msg:fields().
  • Past 150 fields, the values go in local p = {} (p.attribute_32_name = msg:read_string()).

Workflow

  1. Capture the packet in the packet log (do the action in game).
  2. Open its menu and pick Copy as Lua or Copy as receiver.
  3. Paste in the Lua tab, adjust values, press Execute.