Changelog

What changed in the XiaoHook.cc Lua API, release by release.

Changes to the Lua API, most recent first. Supported game: Minecraft Bedrock 1.26.6201.0.


2026-11-06

Movement states New

  • xiao.game.state(name) and new player() fields sprinting, sneaking, swimming, gliding, crawling, flying, jumping: read from the local player each frame (actor flags of the game, flying ability, "is jumping" marker), absent or nil when unreadable.

  • xiao.game.set_state(name, true | false | nil) forces a state until it is released with nil: applied every tick in the client input (sprinting, sneaking, jumping) and in every PlayerAuthInputPacket (start_* once, held buttons, stop_* on release). xiao.game.requested(name) returns the request.

  • xiao.cmd keeps the last word: createmove and authinput callbacks run after the forced states and see them. xiao.cmd.stats() adds state_client and state_server.

  • Clear VM, unload and a new script release every request; the server gets the stop_* it still needs on the next packet.

2026-11-05

Fixes after the in-game test run

  • xiao.game.player(): hunger, saturation, level and experience are now known after an injection in the middle of a game (the attribute packet is matched to the local runtime id read in memory) and again after a dimension change.

  • damage signal: a hit without a health drop seen on the target has no amount and no health (it reported amount = 0); a hit with a drop gives health and max_health, the health left on the target (0 on a kill).

  • xiao.packet.send_fields raises the encoding error (missing field runtime_id) like encode; it still returns false, error for send failures (size, queue, connection).

  • cmd:command_number() in createmove is the Client Tick of the PlayerAuthInputPacket that leaves with this tick (it was a separate counter), nil before the first packet. xiao.cmd.stats() adds createmove.matched, createmove.mismatched, createmove.lead_us and silent_matched.

  • C++ errors inside a xiao function (for example xiao.json.decode on invalid text) give their message, with line and column for JSON, instead of C++ exception.

xiao.timer New

  • xiao.timer: after(seconds, fn), every(seconds, fn) and cancel(id), on the frame clock (sum of dt), one frame of resolution, at most 4096 pending.

  • spawn(fn, ...) runs a coroutine at once until its first wait(seconds); wait returns the time waited and a plain coroutine.yield() waits one frame.

  • Timers run at a new step of the order of a frame: after the game signals, before on_frame. They count in the 100 ms budget, a failing callback or coroutine is removed with its error in the console, Clear VM drops them all.

  • after, every and cancel work in packet and cmd hooks, their callback runs later on the render thread; spawn is refused there.

xiao.json, xiao.workspace and xiao.notify New

  • xiao.json: encode(value, opts) with pretty and indent, decode(text) with line and column in errors, array() for [], null. Sequences 1..n are arrays, other tables objects with sorted string keys, integers exact to 2^53, cycles and depth over 128 refused with the path of the value.

  • xiao.workspace: load(name) and save(name, t), one JSON file per name in C:\xiaohook\lua\workspace, named after the script by default, 1 MiB at most, written atomically and again on Clear VM and unload.

  • xiao.notify(title, text, kind): toast of the client menu, kinds info, success, warning, 3 per second, ASCII, title 48 and text 128 characters.

  • xiao.workspace and xiao.notify raise an error inside a packet hook or a cmd hook, xiao.json works there.

xiao.cmd New

  • New category xiao.cmd, the Bedrock usercmd / CreateMove: a callback gets a cmd object once per tick with the view angles, the movement and the input buttons of the local player.

  • Two layers with the same object: xiao.cmd.hook("createmove", ...) changes the input the client simulates, before the tick (client and server agree); xiao.cmd.hook("authinput", ...) changes the PlayerAuthInputPacket the server receives. xiao.cmd.unhook, current(), available(event) and stats() complete the module.

  • xiao.cmd.buttons names the 66 input buttons of the game schema (jump_down, sprint_down, start_sprinting...), by index: get_buttons, set_buttons, has_button, set_button, remove_button.

  • get_view_angles, set_view_angles, get_head_yaw, set_head_yaw and lock_angles (silent angles, movement turned for the server). Bedrock has no roll.

  • get_move, set_move, get/set_forward_move, get/set_left_move, get/set_up_move, get/set_analog_move and rotate_movement(yaw).

  • Same time rules as packet hooks: 2 ms budget, 3 overruns remove a callback. xiao.paint is inert and xiao.config, xiao.workspace, xiao.notify and xiao.timer.spawn raise an error, see Execution contexts. A packet nothing changed is sent untouched; no callback costs nothing.

  • The script wins over the native aim: changed angles cancel the aim delivery of that packet, and cmd:native_aim() tells when the packet was aimed.

  • Packet hooks: a hook that changes the angles of an aimed PlayerAuthInput or MovePlayer now cancels the aim delivery too.

xiao.config New

  • xiao.config.get(key), set(key, value) and type(key) read and change every client setting by a dotted key (misc.hit_sound.volume), with six types: bool, int, float, string, color and multi.

  • A set acts like a change in the menu: applied at once, shown in the menu, written by the autosave. Numbers are clamped and the stored value is returned, a refused choice raises rejected the value.

  • xiao.config.load(name), save(name), list() and directory() work on the configs of the Configs tab.

  • config_names.txt in the config folder lists every key with its type and factory value, xiao.config.names() writes it again.

  • Every xiao.config function raises xiao.config is not available inside a packet hook or a cmd hook there.

Packets: decoding and field names New

  • Field names of every packet come from the game schemas, so msg:fields() and the packet log use the same names.

  • msg:fields() and xiao.packet.fields(packet, bytes) return a packet as a Lua table: numbers, booleans, enum names, vectors, lists and nested tables. Cut or invalid payloads return the fields read so far plus _error, and uninterpreted trailing bytes are in _rest.

  • Layout bytes have names too: has_<field> for presence bytes, <string>_length for lengths, the plural of a list for its count.

  • Integers past 2^53 become decimal strings, NBT is also given as bytes in <name>_raw.

  • 12 enum values take the exact schema name: the PlayerAuthInput input_flag names are now the xiao.cmd buttons (block_break_delay -> block_breaking_delay_enabled, hotbar_touch_only -> is_hotbar_only_touch...), input_mode gamepad -> game_pad, Animate swing_arm -> swing. The encoder still accepts the old names, see Renamed fields.

Packets: encoding New

  • xiao.packet.send_fields(packet, fields) encodes a fields table and sends it.
  • xiao.packet.encode(packet, fields) returns the payload without sending it.
  • writer:fields(fields) replaces the payload of a writer.
  • Computable fields are optional: has_<name>, <name>_length and list counts.
  • encode(id, fields(id, bytes)) == bytes for every complete decode.
  • Errors name the field: missing field position.x, field mode: unknown value 'fly'.

Packets: interception New

  • xiao.packet.hook(packet, identity, callback) sees a packet in both directions and can return nil (unchanged), false (cancel), a string (new payload) or a fields table (encoded for you).

  • xiao.packet.unhook(packet, identity) removes a hook.

  • Time constraint: 1 ms wait for the VM and a 2 ms budget per packet. A busy VM passes the packet unchanged and counts in busy, an over budget hook counts in overruns, and 3 overruns in a row remove the hook. At most 64 hooks.

  • xiao.packet.hook_stats() returns the counters (calls, busy, overruns, errors, dropped, replaced, removed, avg_us, max_us, total_us, budget_us, wait_us).

Packets: sending New

  • Sends are flushed in the order of the send calls.
  • xiao.packet.pending() and xiao.packet.capacity() (8 places) expose the queue.
  • A send waits for the next packet the game sends on the same connection and is dropped after 2 seconds without one.

Packet log New

  • Copy as Lua writes readable code: values first as named variables, enum tables, nested tables for lists and sub-structures, and write_* calls that rebuild the original payload byte for byte.

  • Copy as receiver copies a receiver that reads the packet field by field and prints the first fields.

  • Both are generated off the render thread, announced by a notification, and refuse payloads over 4 MiB.

Signals New

  • chat: incoming text, with source, type and a meta table (xuid, needs_translation, parameters).

  • entity_add and entity_remove: entities entering and leaving, with id, unique_id, name, type, is_player, position and distance.

  • damage: hits by the local player and damage taken by the local player.

  • world_change: old, new and a reason among enter, leave, dimension, session.

  • Fixed order inside a frame and limits per frame, see Running scripts.

xiao.game New

  • player(): position, rotation, velocity, health, hunger, saturation, level, experience, selected slot, dimension.
  • world(): in_world, session, dimension, dimension_name.
  • hotbar(), inventory(), armor(), offhand(), held_item(), selected_slot().
  • block(x, y, z) with failure reasons unloaded, busy, no_world, budget, failed, unavailable.
  • Entities now have type, is_player and distance, plus health and max_health when known.
  • local_player() is kept for older scripts.
  • players() and entities() are filled whenever a script runs, even with the ESP turned off.

Editor New

  • Copy, cut and paste no longer block the game: the clipboard is read off the render thread.

Fixes Fixed

  • os.date: an unknown directive (%Q, %k, a lone %...) ended the game process inside the Windows C runtime. The format is now checked first and the error os.date: invalid conversion specifier '%Q' can be caught with pcall; valid directives give the same text as before.

  • debug.sethook could remove the 2 ms budget inside a packet or cmd hook, and a looping hook function escaped the 100 ms watchdog of a frame. It now raises debug.sethook is not available inside a packet hook or a cmd hook in a hook and refuses to install a hook anywhere; debug.sethook() without a function does nothing.

  • jit.attach and the jit.profile module are removed: their callbacks ran with the hooks turned off, outside every time limit.

  • xiao.packet.send, send_raw and send_fields called from a packet hook read the send route of the network thread while the render thread updated it: the route is now locked, a send never mixes two routes.

Documentation

  • The documentation has a new structure: Introduction, Engine, Minecraft Bedrock and Guides.

Initial release

Core

  • Embedded LuaJIT 2.1 VM with a sandbox: no io, no ffi, no bytecode, require limited to C:\xiaohook\lua.
  • Lua tab with an editor, the console, Execute and Clear VM.
  • Lifecycle: on_frame(dt), on_unload(), the think and unload signals.
  • Time limits: 2000 ms for the main chunk, 100 ms per frame.

xiao.paint

  • Paint listeners, points (screen and world), colors (rgb, rgba, hex, hsv, hsl).
  • Shapes: line, circle, triangle, rectangle (rounded, gradient), rotated rectangle, quad, polygon.
  • Text with baked fonts, images from files or raw RGBA.

xiao.packet

  • Receive packets by id, name or "*", read them with a packet object and read_* functions.
  • Build and send packets with start/write_*/send or writer objects, and send_raw.
  • decode, reader, id, name, dropped.
  • Block packet ids in both directions with block, unblock, unblock_all, is_blocked.

xiao.game

  • players(), entities(), local_player(), world_to_screen() and time().

xiao.signal and xiao.fs

  • Named signals with add, remove, get, call and their aliases.
  • File access inside C:\xiaohook: read, write, append, list, create and delete, plus path helpers.