xiao.fs
xiao.fs reads and writes files inside the C:\xiaohook folder and offers string helpers to build paths.
Paths are relative to C:\xiaohook, both / and \ work, a single leading / or \ is ignored, and .. is allowed as long as the resolved path stays inside C:\xiaohook. Every failure raises an error: wrap calls in pcall when a missing file is a normal case.
At a glance
Refused paths
file system unavailable: no workspace: theC:\xiaohookfolder does not existdrive letters and ':' are not allowed: ...: any:, so absolute paths (C:/...,c:file) and NTFS streams (file.txt:stream)network paths are not allowed: ...: a path starting with two separators (\\server\share,//server/share)path escapes the xiaohook folder: ...: the resolved path leavesC:\xiaohook, also through a junction or a symbolic link pointing outsideinvalid path component in: ...: a Windows device name in any component (CON,PRN,AUX,NUL,CONIN$,CONOUT$,COM0toCOM9,LPT0toLPT9, with or without extension), or a component ending with a dot or a spaceinvalid path: ...: a NUL byte, or a path Windows cannot resolve
Packet hooks and cmd hooks
xiao.fs is not refused inside a packet hook or a cmd hook, but a disk access is one long C call that the 2 ms hook budget cannot interrupt. Defer it with xiao.timer.after(0, ...). See Execution contexts.
Disk functions
xiao.fs.read(path): string
xiao.fs.read(path: string): string
Returns the whole file. The content is binary safe, so it works for fonts and images too.
Returns: the bytes of the file.
Errors: a refused path (see cannot read file: ... when the file does not exist, is a folder or cannot be opened.
local ok, data = pcall(xiao.fs.read, "config/settings.txt")
if not ok then
data = ""
end
xiao.fs.write(path, data)
xiao.fs.write(path: string, data: string)
Creates the file, or replaces its whole content. Bytes are written as is, without newline conversion. The folder must already exist: create it first with makefolder.
Returns: nothing.
Errors: a refused path; cannot write file: ... when the folder is missing or the file is locked.
xiao.fs.write("notes.txt", "first line\n")
xiao.fs.append(path, data)
xiao.fs.append(path: string, data: string)
Appends data at the end of the file, creating it if needed. The folder must already exist.
Returns: nothing.
Errors: a refused path; cannot write file: ....
xiao.fs.append("notes.txt", "another line\n")
xiao.fs.isfile(path): boolean
xiao.fs.isfile(path: string): boolean
Returns: true if path is an existing file, false otherwise.
Errors: a refused path only.
if xiao.fs.isfile("notes.txt") then print("found") end
xiao.fs.isfolder(path): boolean
xiao.fs.isfolder(path: string): boolean
Returns: true if path is an existing folder, false otherwise.
Errors: a refused path only.
print(xiao.fs.isfolder("lua"))
xiao.fs.listfiles(path): string[]
xiao.fs.listfiles(path?: string): string[]
Lists the files and folders directly inside path, without recursion.
Returns: the entries as paths relative to C:\xiaohook, written with / ("lua/main.lua"), sorted by byte value.
Errors: a refused path; not a folder: ....
for _, entry in ipairs(xiao.fs.listfiles("lua")) do
print(entry, xiao.fs.isfolder(entry))
end
xiao.fs.makefolder(path)
xiao.fs.makefolder(path: string)
Creates the folder and every missing parent. An existing folder is not an error.
Returns: nothing.
Errors: a refused path; cannot create folder: ....
xiao.fs.makefolder("cache/session")
xiao.fs.delfolder(path)
xiao.fs.delfolder(path: string)
Deletes the folder and everything inside it.
Returns: nothing.
Errors: a refused path; not a folder: ...; the workspace itself cannot be deleted for C:\xiaohook; cannot delete: ....
xiao.fs.delfolder("cache/session")
xiao.fs.delfile(path)
xiao.fs.delfile(path: string)
Returns: nothing.
Errors: a refused path; not a file: ...; cannot delete: ....
pcall(xiao.fs.delfile, "notes.txt")
Path helpers
These helpers only transform strings. They never touch the disk, never raise a path error and are not subject to the restrictions above.
xiao.fs.forward(path): string
xiao.fs.forward(path: string): string
Returns: path with every \ replaced by /.
xiao.fs.backward(path): string
xiao.fs.backward(path: string): string
Returns: path with every / replaced by \.
xiao.fs.join(a, b): string
xiao.fs.join(a: string, b: string): string
Returns: a and b joined by a single separator: / is added when a does not already end with / or \. With an empty a, returns b without its leading separators.
print(xiao.fs.join("cache/", "/log.txt"))
cache/log.txt
xiao.fs.extname(path, replace): string
xiao.fs.extname(path: string, replace?: string): string
Returns: without replace, the extension without the dot ("gz" for a.tar.gz, "" for .hidden or README). With replace, the whole path with that extension.
print(xiao.fs.extname("lua/main.lua"), xiao.fs.extname("lua/main.lua", "txt"))
lua lua/main.txt
xiao.fs.filename(path, replace): string
xiao.fs.filename(path: string, replace?: string): string
Returns: without replace, the part after the last separator. With replace, the path with that file name.
xiao.fs.dirname(path, replace): string
xiao.fs.dirname(path: string, replace?: string): string
Returns: without replace, the part before the last separator, "" when there is none. With replace, the path moved to that folder.
print(xiao.fs.dirname("cache/session/log.txt"), xiao.fs.dirname("cache/session/log.txt", "backup"))
cache/session backup/log.txt
xiao.fs.sanitize(name): string
xiao.fs.sanitize(name: string): string
Replaces control characters and < > : " | ? * with _. / and \ are kept: sanitize each name that comes from outside (a player name, a chat message) before joining it into a path.
Returns: the cleaned string, same length as name.
local safe = xiao.fs.sanitize("a:b?c")
print(safe)
a_b_c
Complete example
Keep a log and load a font from the disk, then draw with it (see xiao.paint). For a Lua table that must survive restarts, xiao.workspace does the file handling for you.
local fs = xiao.fs
fs.makefolder("cache/session")
fs.write("cache/session/log.txt", os.date("%x %X") .. "\n")
fs.append("cache/session/log.txt", "started\n")
for _, path in ipairs(fs.listfiles("cache/session")) do
print(path, fs.extname(path))
end
local ok, font = pcall(fs.read, "lua/fonts/proggy.ttf")
if ok and xiao.paint.text.bake("Proggy", font, 14) then
local white = xiao.paint.color.rgb(255, 255, 255)
local origin = xiao.paint.point.vector2(20, 20)
xiao.paint.add("proggy", function()
xiao.paint.text.font("Proggy")
xiao.paint.text.left(origin, "hello", white)
xiao.paint.text.font("default")
end)
end