Sandbox
Scripts cannot reach the disk or the system outside C:\xiaohook: this page lists what was removed or changed in the standard Lua libraries, what to use instead, and what each execution context allows.
What was removed
What was changed
Everything else of base, string, table, math, bit, coroutine and the LuaJIT jit library is the stock LuaJIT 2.1 version, built without the Lua 5.2 extensions. require also finds the modules LuaJIT preloads: table.new, table.clear, string.buffer and jit.util.
Loading bytecode, from string.dump or from a file, fails with attempt to load chunk with wrong mode.
Files
xiao.fs works inside C:\xiaohook. Paths are relative to that folder, / and \ both work, a leading / is ignored, and .. is allowed as long as the resolved path stays inside.
New
xiao.workspace writes only C:\xiaohook\lua\workspace\<name>.json, with names limited to letters, digits, _ and - (plus the one time move of older files).
Refused paths
Every xiao.fs failure raises an error, so wrap risky calls in pcall:
local ok, content = pcall(xiao.fs.read, "config/settings.txt")
if not ok then
xiao.fs.makefolder("config")
xiao.fs.write("config/settings.txt", "enabled=1\n")
content = "enabled=1\n"
end
print(content)
Running another file
dofile and loadfile do not exist. Read the file and compile it, or use require for modules:
local chunk, err = load(xiao.fs.read("lua/helpers.lua"), "=helpers")
if chunk == nil then error(err) end
chunk()
xiao.fs paths start at C:\xiaohook, so a script in the scripts folder is lua/<name>.lua.
Modules
require("lib.util") loads C:\xiaohook\lua\lib\util.lua (or lib/util/init.lua) once. The same path rules apply, with C:\xiaohook\lua as the root: a module name cannot leave that folder. Error messages and details are in require and Modules with require.
Execution contexts
New
Code does not run with the same rights everywhere. The main chunk, timers, signals, on_frame and paint listeners run on the render thread with every function available. Packet hooks and cmd hooks run on game threads, inside a 2000 us budget, and refuse what could block or touch the render thread:
xiao.timer.after and xiao.timer.every work in a hook: their callback runs later on the render thread, where everything is allowed again. Plain coroutines work too, inside the hook budget. The full list is in Execution contexts.
Time limits
The client stops code that runs too long: 2000 ms for the main chunk, 100 ms for the packet receivers and for each frame, 2000 us for each packet or cmd hook. A timeout cannot be caught with pcall, and a script cannot install a debug hook that would get around it, see debug and Time limits.