xiao.config New

Read and change the client settings by key, and load, save and list the saved configs.

Read and change every client setting by a dotted key such as misc.hit_sound.volume, and load, save and list the configs of the Configs tab.

At a glance

Function Returns What it does
get(key) value the value of a setting
set(key, value) value changes a setting, returns the value now stored
type(key) string \| nil the type of a key, nil for an unknown key
load(name) string applies a saved config
save(name) string saves the current settings as a config
list() string[] the names of the saved configs
directory() string the absolute path of the config folder
names() string[] every key, and writes config_names.txt again

The keys are the paths of the JSON a config file holds: {"misc":{"hit_sound":{"volume":45}}} is the key misc.hit_sound.volume. The list is never written by hand, it is read from the same serializer as the config files, so a new setting in the menu is a new key here.

Every function raises an error on failure (unknown key, wrong type, missing file), like xiao.fs. Wrap a call in pcall when a failure is expected. Before the menu is ready, every function raises settings are not ready: the menu is not initialized yet.


Types

Type Lua value from get Lua value for set How the key is recognized in the JSON
"bool" boolean boolean true or false
"int" number integral number an integer (7)
"float" number number a number with a decimal point (45.0)
"string" string string text, usually one choice of a list ("pop_can", "hold")
"color" color table color table "#RRGGBBAA" text, or an array of 4 numbers from 0 to 1 (ESP layout)
"multi" table id = true table id = boolean, or array of ids an ids list (target pickers)
  • Colors come back as the same table as xiao.paint.color.rgba: fields r, g, b, a from 0 to 255. set takes {r = 255, g = 0, b = 0, a = 128} (a defaults to 255), {255, 0, 0}, {255, 0, 0, 128} or a xiao.paint.color value. Channels are rounded to the nearest integer, a channel outside 0 to 255 is an error. The ESP layout keys store colors from 0 to 1: the conversion is done for you.
  • Multi is a selection of ids: {["minecraft:creeper"] = true, ["minecraft:zombie"] = true}. set takes the same table (keys with false are ignored) or an array {"minecraft:creeper", "minecraft:zombie"}, and replaces the whole selection. Duplicates are merged. Ids are 1 to 128 bytes.
  • Numbers are clamped to the bounds of the setting, as the menu slider does: set returns the value really stored, so compare it when the bounds matter. An int key refuses 2.5 and integers past 2^53, a float key refuses NaN and infinity.
  • Strings that are a choice (misc.hit_sound.sound, misc.fast_mine.method, *.bind.mode) refuse a value outside the list: the error says rejected the value and the setting keeps its previous value.

Keys that are not listed

Left out Why
format, version file metadata, not settings
players.relations the friend and enemy list, its keys are player names
*.targets.overrides, esp.*.glow maps whose keys exist only once you created an entry
*.bind.name, interface.*_key.name the readable name of a key, computed from bind.key
pathfinder.goal three coordinates, not one value

HUD positions (hud.watermark.x, hud.watermark.y...) are listed: they are plain numbers. -1 means automatic placement, a position needs x and y both at 0 or more. A set of x sends the current y with it, and the other way round.


Settings

xiao.config.get(key: string): value

xiao.config.get(key: string): value

Returns the value of a setting.

Name Type Description
key string dotted key, as listed in config_names.txt

Returns: a boolean, a number, a string, a color table or a multi table, by the type of the key.

Errors: unknown config key 'x'.

local volume = xiao.config.get("misc.hit_sound.volume")
local accent = xiao.config.get("theme.accent")
print(volume, accent.r, accent.g, accent.b, accent.a)

xiao.config.set(key: string, value: any): value

xiao.config.set(key: string, value: any): value

Changes a setting as if you changed it in the menu, and returns the value now stored (after clamping).

Name Type Description
key string dotted key
value any a value of the type of the key

Returns: the stored value, same shape as get. A value equal to the stored one changes nothing.

Errors:

Message Cause
unknown config key 'x' the key does not exist
config key 'x' expects a boolean, got number wrong Lua type for the key (a boolean, an integer, a number, a string)
config key 'x' expects an integer, got 2.5 a decimal number, or an integer past 2^53, for an int key
config key 'x' expects a finite number, got inf NaN or infinity for a float key
config key 'x' expects a color table: r, g, b and optional a, or 3 to 4 numbers, from 0 to 255 not a color table, or a channel outside 0 to 255
config key 'x' expects a table of id = boolean or an array of id strings bad multi value
config key 'x' takes ids of 1 to 128 bytes an empty or too long id in a multi value
config key 'x' rejected the value, the client keeps y the client refused the value (unknown choice, unsupported option), nothing changed; a multi key ends after rejected the value
the client did not accept a change in section 'x' the menu refused the whole change
xiao.config.set("misc.hit_sound.enabled", true)
xiao.config.set("misc.hit_sound.sound", "pop_can")
print(xiao.config.set("misc.hit_sound.volume", 150))
xiao.config.set("visual.chams.color", {r = 0, g = 192, b = 255})
xiao.config.set("visual.entities.targets.ids", {"minecraft:creeper", "minecraft:zombie"})

print shows 100: the volume slider goes from 0 to 100.

xiao.config.type(key: string): string | nil

xiao.config.type(key: string): string | nil

Returns the type of a key.

Name Type Description
key string dotted key

Returns: "bool", "int", "float", "color", "multi" or "string", or nil when the key does not exist.

Errors: none for an unknown key; only the hook and not ready errors.

if xiao.config.type("misc.reach.range") == "float" then
    xiao.config.set("misc.reach.range", 4.5)
end

Configs

The configs are the files of the Configs tab, <name>.json in the config folder. A name is 1 to 31 letters, digits, spaces and - _ . ( ), given without the .json extension. Names are matched without case: save("pvp") overwrites PvP.json. The library holds 64 configs at most.

xiao.config.load(name: string): string

xiao.config.load(name: string): string

Applies a saved config, exactly like Load in the Configs tab: every setting of the file is applied and the config becomes the active one (the autosave target).

Name Type Description
name string config name, without .json

Returns: the name of the config as stored on disk.

Errors:

Message Cause
invalid config name 'x': 1 to 31 letters, digits, spaces and - _ . ( ), without the .json extension bad name
config 'x' not found in <folder> no such file
config 'x' cannot be read (locked or over 1 MiB) the file cannot be read
config 'x' is not valid JSON not a JSON object
config 'x' is not in the library (64 configs at most) the library is full
config 'x' holds no setting the client knows nothing was applied
the config folder is unavailable, configs are kept in memory no config folder
local ok, message = pcall(xiao.config.load, "pvp")
if not ok then print(message) end

xiao.config.save(name: string): string

xiao.config.save(name: string): string

Saves the current settings as a config, like Save in the Configs tab: the file is created when missing (the folder too), overwritten otherwise, and the config becomes the active one.

Name Type Description
name string config name, without .json

Returns: the name of the config as stored on disk.

Errors:

Message Cause
invalid config name 'x': ... bad name, same text as load
config 'x' cannot be created: the library holds 64 configs at most the library is full
cannot create the config folder <folder> the folder cannot be created
config 'x' could not be written to <folder> the file was not written
the config folder is unavailable, configs are kept in memory no config folder
xiao.config.set("misc.hit_sound.volume", 60)
xiao.config.save("quiet")

xiao.config.list(): string[]

xiao.config.list(): string[]

Returns: the names of the configs saved in the folder, sorted, without the extension. Files whose name is not a valid config name are left out; a missing folder gives an empty array.

Errors: the config folder is unavailable, configs are kept in memory.

for index, name in ipairs(xiao.config.list()) do
    print(index, name)
end

xiao.config.directory(): string

xiao.config.directory(): string

Returns: the absolute path of the config folder, C:\xiaohook\configs by default.

Errors: the config folder is unavailable, configs are kept in memory when the client could not create any folder.

print("configs in " .. xiao.config.directory())

Key list

xiao.config.names(): string[]

xiao.config.names(): string[]

Returns every key in the order of the config file, and writes config_names.txt again in the config folder when its content changed.

Returns: an array of keys.

local keys = xiao.config.names()
print(#keys .. " settings")

The menu writes config_names.txt when it starts, then again on each names() call whose list differs from the file. One line per key, separated by tabs:

misc.hit_sound.volume	float	70
misc.hit_sound.sound	string	bell
automation.auto_fish.enabled	bool	false
visual.chams.color	color	#FF4D4DFF
visual.players.targets.ids	multi

The third column is the factory value, read before the last config is loaded. A multi key has no third column. Colors are written #RRGGBBAA.


Speed

Operation Cost
get, type a hash lookup on a cache, about 0.2 microsecond
set applies one key, then reads back only the section of the key (misc, visual...), about 20 microseconds
first get of a frame in a section reads that section again, so a change made in the menu is seen

A set of a visual key also refreshes esp (the box style is shared), and a set of an interface key refreshes every section (a new menu key can release a key bind). A set that stores the value already stored does nothing. Thousands of get and a hundred set fit in one frame.


Complete example

Flip the hit sound, switch the menu theme, and load the pvp config each time you join a world:

local config = xiao.config

local function flip(key)
    return config.set(key, not config.get(key))
end

local dark = {accent = {240, 194, 80}, background = {5, 6, 7}, panels = {17, 18, 19}}
local function apply_theme(theme)
    for name, rgb in pairs(theme) do
        config.set("theme." .. name, rgb)
    end
end

local last_session
xiao.signal.add("world_change", "profiles", function(old, new, reason)
    if reason ~= "enter" or new.session == last_session then return end
    last_session = new.session
    local names = {}
    for _, name in ipairs(config.list()) do names[name] = true end
    if names.pvp then
        print("loaded " .. config.load("pvp"))
    end
end)

print("hit sound", flip("misc.hit_sound.enabled"))
apply_theme(dark)
print("configs in " .. config.directory())