HOOZi文档
Skip to content

mem

DMA 内存读 + 写 + 模式扫描。配合实体 .base(见 game)和 offsets 表,可自助读写任意未封装的游戏属性。

写内存有风险,自负:write_* 是裸内存写,无任何护栏。写错地址或值可直接破坏游戏状态、崩游戏,极端情况引来反作弊关注。读不会污染状态,危险全在写侧。无效 addr 静默跳过(与读一致),但地址/值对不对没人替你兜

性能:单次 read_* / write_* 阻塞 ~200-500µs(同步 DMA 往返)。每帧多字段读写必须用 mem.scatter / mem.write_scatter 批量,否则 frame_update 会被卡死。


模块信息

mem.get_module_base(name) → uint64

进程内模块基址。失败返 0

mem.get_module_size(name) → uint64

模块代码段大小。失败返 0

mem.is_valid(addr) → bool

快速指针有效性判断(非 0、非 sentinel、用户空间地址)。不实际访问内存,纯本地校验。

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

模式扫描

mem.find_pattern(module_name, pattern) → uint64

IDA 风格 byte 模式扫描。? 表示任意字节,字节间空格分隔。失败返 0

每次调用扫整个模块代码段(几 MB),开销几十毫秒级。找到 addr 后必须 cache,不要每帧调。典型用法是脚本加载时一次性 resolve 偏移,后续用 cache 的 addr 配合 read_* / scatter

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

单次读(同步,适合冷路径 / 调试)

函数返回类型备注
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)uint64指针常用
mem.read_float(addr)numberf32
mem.read_double(addr)numberf64
mem.read_vec2(addr)Vec28 字节
mem.read_vec3(addr)Vec312 字节
mem.read_bytes(addr, len) → table<byte>arraylen > 1MB 返空表
mem.read_string(addr, max_len?) → stringstringnull-terminated 截断,默认 max 256,上限 4096

无效 addr / read 失败 → 返默认值(0 / "" / 全零 vec)。不抛异常

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

多级指针解引用:Read<u64>(base + off1) → Read<u64>(prev + off2) → ...。任一步读到 0 即返 0。

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

单次写(同步,危险)

命名 / 类型集与 read_* 1:1 对齐,签名是读多一个 value 参数。

函数value 类型备注
mem.write_i8(addr, v)int截到 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 字节
mem.write_vec3(addr, v)Vec312 字节
mem.write_bytes(addr, {0x12, 0x34, ...})byte 表上限 1MB
mem.write_string(addr, str)string不含结尾 NUL;要 NUL 自己在串里带("foo\0")

无效 addr → 静默跳过,不抛异常。地址 / 值的正确性由作者负责。

lua
local lp  = game.localplayer
local off = offsets.RecvTable.DT_Player.m_iHealth   -- 具名偏移,见 offsets 页
mem.write_i32(lp.self_base + off, 100)               -- 改自己血量(示范,自负)

批量读(scatter,每帧多字段必用)

mem.scatter(reqs_table) → results_table

单次 DMA round-trip 完成 N 个读取请求。每个 req 是 { type, addr [, size] }。返回 1-based result table,按 req 顺序排。

type含义第 3 参数
"i8" / "u8" / ... "i64" / "u64"整数
"f32" / "float"32-bit float
"f64" / "double"64-bit float
"vec2" / "vec3"向量
"bytes"byte 表size 必填
"string"null-terminated 截断字符串size = max_len 必填
lua
-- 一帧批量读 3 个字段
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)

性能对比(60 个 player × 每帧 4 字段 = 240 reads):

  • mem.read_u32 × 240 → ~96ms / 帧(完全卡死)
  • mem.scatter(...240 reqs) → 单往返,~2ms / 帧

无效 addr / 失败的 entry 返默认值,不中断后续 entry

错误信号:

  • 写错 type(如 "u9")→ 该槽返 nil + 控制台 LOG_WARN(跟读到 0 区分)
  • entry 不是 table(传成字符串 / 数字)→ 该槽返 nil + LOG_WARN

批量写(write_scatter)

mem.write_scatter(reqs_table) → 实际写入条目数

单次 DMA round-trip 写 N 个地址。每个 req 是 { type, addr, value }——镜像 mem.scatter,只是第 3 元素从 size(读)变成 value(写)。type 集与 scatter 读相同。

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

无效 addr / 编码失败(值类型对不上)的 entry 跳过,不整批失败 + LOG_WARN;返回真正写进去的条目数。


安全 / 限制

  • 写内存无护栏:地址 / 值的正确性全靠作者;写错可崩游戏。偏移随游戏更新会漂(配 offsets 表用,它跟 dumper 自动同步)。
  • 所有读写走当前 attach 的游戏进程,无 pid 参数。
  • DMA 未 attach / 进程未启动时,get_module_base 返 0,read_* 返默认值,write_* 无效跳过。脚本应先用 mem.is_valid 把关。
  • mem.scatter / mem.write_scatter 的 reqs 表如果有 1000+ 条会显著卡当前帧 — 把超大批量拆成多帧。