Skip to content

Memory ​

Direct memory reads, for offsets you have found yourself.

If you have no offsets of your own, use the player and world objects instead. They expose everything TSUKI itself uses.

Functions ​

FunctionReturnsDescription
memory.read(type, address)variesRead a single value at an address
memory.read_chain(type, base, offsets)variesFollow a pointer chain and read the final value
memory.batch_read(entries)tableRead multiple values in one call
memory.get_client_base()integerBase address of the game client module
memory.get_entity_list()integerPointer to the entity list

Supported types ​

Type stringReads
"float"4-byte float
"double"8-byte double
"int"4-byte signed int
"uint"4-byte unsigned int
"int64"8-byte signed int
"uint64"8-byte unsigned int
"short"2-byte signed int
"ushort"2-byte unsigned int
"byte"1-byte unsigned int
"bool"1-byte boolean
"ptr"Pointer-sized integer
"pointer"Alias for "ptr"

read ​

Single value at an address.

lua
local base = memory.get_client_base()
local some_offset = 0x1A2B3C4
local x = memory.read("float", base + some_offset)

read_chain ​

Follow a pointer chain. base is the starting address and is not dereferenced. Each offset except the last is added to the current address, and the pointer stored there becomes the new address. The first step is Read<ptr>(base + offsets[1]). The last offset is added and the value there is read as type, so with one offset this reads type at base + offsets[1].

lua
-- Equivalent to: *(float*)(*(*(base + o1) + o2) + o3)
local hp = memory.read_chain("float", base, { 0x10, 0x28, 0x4C })

batch_read ​

Read many values in one call. This is cheaper than calling read in a loop.

Each entry is a positional array { type_string, address }. The type string is [1] and the address is [2]. Named keys are not supported.

lua
local results = memory.batch_read({
    { "float", 0x12340000 },
    { "int",   0x12340004 },
    { "ptr",   0x12340008 },
})
-- results[1], results[2], results[3]

get_client_base / get_entity_list ​

lua
local client = memory.get_client_base()
local list   = memory.get_entity_list()

Use these as starting points if you have offsets relative to the client module or relative to the entity list.

Caveats ​

  • A bad address returns 0 or nil. read returns nil for address 0 or an unknown type string. read_chain returns nil when base or any pointer along the chain is 0.
  • batch_read reads at most 64 entries. Entries past index 64 are ignored with no error.
  • Within a batch_read result, entries with address 0 or an unrecognised type string return nil (not 0) at that index.
  • Offsets shift between game patches, so update your own after a patch.
  • Call these from on_tick or a callback. In top-level code that runs when the script loads, get_client_base() and get_entity_list() can return 0, and reads nil.
  • Memory reads are slower than the player/world API. Cache results if you're reading the same address every tick.