HOOZiDocs
Skip to content

mem

DMA memory read + write + pattern scanning. Combined with entity .base (see game) and the offsets table, scripts can self-service read/write any unwrapped game attribute.

Writes are risky — on you: write_* is raw memory write with no guardrails. A wrong address or value can corrupt game state, crash the game, and in the worst case draw anti-cheat attention. Reads never corrupt state; all the danger is on the write side. Invalid addr is silently skipped (same as reads), but whether the address/value is correct is nobody's job but yours.

Performance: a single read_* / write_* blocks ~200-500µs (synchronous DMA round-trip). Per-frame multi-field read/write must use mem.scatter / mem.write_scatter to batch — otherwise frame_update stalls.


Module info

mem.get_module_base(name) → uint64

Module base address. Returns 0 on failure.

mem.get_module_size(name) → uint64

Module code-segment size. Returns 0 on failure.

mem.is_valid(addr) → bool

Fast pointer sanity check (non-zero, non-sentinel, in user-mode range). Does not access memory — purely local validation.

lua
local base = mem.get_module_base("r5apex.exe")
print(string.format("base = 0x%X", base))

Pattern scan

mem.find_pattern(module_name, pattern) → uint64

IDA-style byte-pattern scan. ? matches any byte; bytes separated by spaces. Returns 0 on miss.

Each call scans the entire module code segment (several MB), costing tens of milliseconds. Always cache the resolved addr — never call per-frame. Typical pattern: resolve once at script load, then use the cached addr with read_* / scatter.

lua
local addr = mem.find_pattern("r5apex.exe", "48 8B 05 ? ? ? ? 48 8B 88")
if addr ~= 0 then
    -- RIP-relative resolve: disp32 at addr+3, target = addr+7+disp
    local disp = mem.read_i32(addr + 3)
    local target = addr + 7 + disp
    print(string.format("target = 0x%X", target))
end

Single reads (synchronous, for cold paths / debugging)

FunctionReturnNotes
mem.read_i8(addr)int-128..127
mem.read_u8(addr)int0..255
mem.read_i16(addr)int
mem.read_u16(addr)int
mem.read_i32(addr)int
mem.read_u32(addr)int (unsigned)
mem.read_i64(addr)int64
mem.read_u64(addr)uint64typical for pointers
mem.read_float(addr)numberf32
mem.read_double(addr)numberf64
mem.read_vec2(addr)Vec28 bytes
mem.read_vec3(addr)Vec312 bytes
mem.read_bytes(addr, len) → table<byte>arrayempty table when len > 1MB
mem.read_string(addr, max_len?) → stringstringnull-terminated, default max 256, cap 4096

Invalid addr / read failure → returns default value (0 / "" / zero vec). Never throws.

mem.read_chain(base, {off1, off2, ...}) → uint64

Multi-level pointer dereference: Read<u64>(base + off1) → Read<u64>(prev + off2) → .... Returns 0 as soon as any step reads zero.

lua
local lp_ptr = mem.read_chain(base, { 0x1F12345, 0x80, 0x18 })

Single writes (synchronous, dangerous)

Naming / type set mirror read_* 1:1 — the signature is the read plus one value argument.

Functionvalue typeNotes
mem.write_i8(addr, v)inttruncated to 8-bit
mem.write_u8(addr, v)int
mem.write_i16(addr, v) / mem.write_u16(addr, v)int
mem.write_i32(addr, v) / mem.write_u32(addr, v)int
mem.write_i64(addr, v) / mem.write_u64(addr, v)int64 / uint64
mem.write_float(addr, v)numberf32
mem.write_double(addr, v)numberf64
mem.write_vec2(addr, v)Vec28 bytes
mem.write_vec3(addr, v)Vec312 bytes
mem.write_bytes(addr, {0x12, 0x34, ...})byte tablecap 1MB
mem.write_string(addr, str)stringno trailing NUL; include it yourself if needed ("foo\0")

Invalid addr → silently skipped, never throws. Address / value correctness is the author's responsibility.

lua
local lp  = game.localplayer
local off = offsets.RecvTable.DT_Player.m_iHealth   -- named offset, see the offsets page
mem.write_i32(lp.self_base + off, 100)               -- set own health (demo, on you)

Batch reads (scatter, mandatory for per-frame multi-field)

mem.scatter(reqs_table) → results_table

Single DMA round-trip processes N read requests. Each req is { type, addr [, size] }. Returns 1-based result table in request order.

typemeaning3rd arg
"i8" / "u8" / ... "i64" / "u64"integersnone
"f32" / "float"32-bit floatnone
"f64" / "double"64-bit floatnone
"vec2" / "vec3"vectorsnone
"bytes"byte tablesize required
"string"null-terminated stringsize = max_len required
lua
-- Batch 3 fields in one frame
local r = mem.scatter({
    { "u32", lp_ptr + 0x0238 },         -- health
    { "vec3", lp_ptr + 0x014C },        -- origin
    { "string", lp_ptr + 0x500, 32 },   -- name
})

local health, origin, name = r[1], r[2], r[3]
print(health, origin.x, name)

Performance (60 players × 4 fields per frame = 240 reads):

  • Single mem.read_u32 × 240 → ~96ms / frame (frame-killing)
  • mem.scatter(...240 reqs) → one round-trip, ~2ms / frame

Invalid addr / failed entry returns default — does not abort subsequent entries.

Error signals:

  • Wrong type (e.g. "u9") → that slot returns nil + console LOG_WARN (distinguishable from reading 0)
  • Non-table entry (passed a string / number) → that slot returns nil + LOG_WARN

Batch writes (write_scatter)

mem.write_scatter(reqs_table) → count actually written

Single DMA round-trip writes N addresses. Each req is { type, addr, value } — mirrors mem.scatter, only the 3rd element changes from size (read) to value (write). Type set is identical to scatter reads.

lua
mem.write_scatter({
    { "i32",   base + off_a, 100 },
    { "float", base + off_b, 1.5 },
    { "vec3",  base + off_c, vec3(x, y, z) },
})

Entries with invalid addr / encode failure (value type mismatch) are skipped — no whole-batch failure + LOG_WARN; returns the count actually written.


Safety / Limits

  • Writes have no guardrails: address / value correctness is entirely on the author; a wrong write can crash the game. Offsets drift across game updates (use the offsets table, which auto-syncs with the dumper).
  • All reads/writes target the currently-attached game process; no pid parameter.
  • When DMA has not attached / process is gone, get_module_base returns 0, read_* returns defaults, write_* skips. Guard with mem.is_valid.
  • A mem.scatter / mem.write_scatter reqs table with 1000+ entries can visibly stall the current frame — split very large batches across frames.