跳转至

确定游戏数据是否存储在 std::map 中

通常,在确定集合不是数组、std::vector 或 std::list 之前,我甚至不会考虑数据是否可能在 std::map 中。如果你排除了所有三种选项,那么与 std::list 一样,你可以查看值之前的三个整数值,检查它们是否指向可能是其他地图节点的内存。

同样,这可以在 Cheat Engine 中用 Lua 脚本完成。脚本与我为列表展示的类似,在内存中向后循环,看看在值之前是否找到有效的节点结构。不过与列表代码不同,验证节点的函数要棘手得多。看看清单 5-10 中的代码,然后我会剖析它。

function _verifyMap(address)
    local parentItem = readInteger(address + 4) or 
0
    local parentLeftItem = readInteger(parentItem 
+ 0) or 0
    local parentRightItem = readInteger(parentItem 
  local validParent =
      parentLeftItem == address
      or parentRightItem == address
    if (not validParent) then return false end
    local tries = 0
    local lastChecked = parentItem
    local parentsParent = readInteger(parentItem + 
4) or 0
  while (readInteger(parentsParent + 4) ~= 
lastChecked and tries < 200) do
      tries = tries + 1
      lastChecked = parentsParent
      parentsParent = readInteger(parentsParent + 
4) or 0
    end
    return readInteger(parentsParent + 4) == 
lastChecked
end

清单 5-10:用 Cheat Engine Lua 脚本确定数据是否在 std::map 中

给定 address,这个函数检查 address 是否在地图结构中。它首先检查是否存在有效的父节点,如果有,检查该父节点是否在任一侧指向 address①。但这个检查还不够。检查通过,函数还会沿着父节点链向上爬,直到到达一个是自己父节点的父节点的节点②,在放弃前尝试 200 次。如果爬升成功找到一个自己是自己祖父节点的节点,那么 address 肯定指向地图节点。这是有效的,因为正如我在第一百一十四页「可视化 std::map」中概述的,每个地图顶部都有一个根节点,其 parent 指向树中的第一个节点,而该节点的 parent 指回根节点。

 我打赌你在读一本游戏破解书时没想到会遇到时间旅行中的祖父悖论!

使用这个函数和清单 5-8 中略作修改的回溯循环,你可以自动检测值何时在地图内部:

function isValueInMap(valueAddress)
    for address = valueAddress - 12, valueAddress 
- 52, -4 do
            return address
        end
    end
    return 0
end
local node = isValueInMap(addressOfSomeValue)
if (node > 0) then
    print(string.format("Value in map, top of node 
at 0x0%x", node))
end

除了函数名,这段代码与清单 5-8 的唯一区别是它从值之前 12 字节开始循环,而不是 8 字节,因为地图有三个指针,而列表只有两个。地图结构的一个好处是很容易获得根节点。当_verifyMap 函数返回 true 时,parentsParent 变量将包含根节点的地址。通过一些简单修改,你可以把它返回给主调用,在一处拥有从 std::map 读取数据所需的一切。