第一次用 Cheat Engine 找血量,我盯着满屏问号发呆了半小时
很多人对内存读写的第一印象,来自某个游戏修改器。我当初也是,兴冲冲下载了 Cheat Engine,打开一个游戏,然后在「要找的值」里懵了——我连血量现在是多少都得先去游戏里看一眼,更别说它在内存的哪个角落。后来才明白,CE 这种工具干的事其实特别朴素:在进程那一大片地址空间里,按你给的条件,把符合的地址筛出来。
它不神秘,本质是一次又一次地「缩小范围」。
精确值扫描为什么常常失灵
新手最爱用「精确数值」扫描:现在血量 853,就搜 853。问题是,游戏里显示的 853 和内存里存的未必是同一个类型——它可能是 4 字节整数,也可能是浮点,甚至被加密或偏移过。我见过最坑的一种:界面显示整数,内存里存的却是 浮点数。你按整数搜 853 永远搜不到,因为它实际是 853.0 的 IEEE754 编码。所以扫描前先想清楚数据类型,CE 里 value type 选错,后面全白费。
还有对齐(alignment)这个隐形的坑。很多程序按 4 字节对齐存放数据,如果你勾了「快速扫描」但类型选错,会漏掉真实地址。我有次找了半天找不到,关掉快速扫描、把类型从 2 字节改成 4 字节,一下就出来了。
不知道初始值怎么办
更多时候你根本不知道目标值当前是多少,只知道「它变了」。这时候用 CE 的「未知初始值」扫一遍全内存,然后回到游戏里让数值变动(扣血、加血都行),再用「变动的数值 / 未变动的数值」反复筛。原理很简单:每次只保留「这次扫描结果里,跟上一次比确实变了(或没变)」的地址。三四轮下来,候选往往从几百万掉到十几个。
# 思路等价(真实 CE 是 GUI 操作,这是本质逻辑)
candidates = all_addresses
for round in scans:
new = []
for a in candidates:
if changed(a) == expected(after_action): # 变动/未变动
new.append(a)
candidates = new
为什么重启游戏地址就变了
这是几乎所有新手都会撞的墙:好不容易改好血量,重启游戏,地址全失效。原因在 ASLR(地址空间随机化)——每次进程加载,基址都随机。所以你真正要找的不是「绝对地址」,而是「基址 + 偏移」这条链。CE 的指针扫描(pointer scan)就是干这个的:它顺着「谁指向了这个地址」往上找,找到一块每次重启都固定的基址(比如某个模块加载地址 + 固定偏移),下次重启用同一套偏移链就能重新定位。
说到底,内存读写最难的从来不是「读」或「写」这两个动作本身——Windows 下就是 ReadProcessMemory / WriteProcessMemory 两个 API 的事。难的是在浩如烟海的内存里,凭有限线索把目标揪出来。工具只是帮你缩小范围,真正的功夫在脑子里那张「这个数值可能怎么存、怎么变、从哪追溯」的假设网。练熟了,你看任何程序都会下意识想:它的状态,此刻正躺在哪片内存里。
