确定游戏数据是否存储在 std::list 中¶
识别存储在 std::list 类中的对象可能很棘手,但有一些迹象值得注意。首先,std::list 中的项目不能有静态地址,所以如果你要找的数据有静态地址,那就没问题。明显是集合一部分的项目,如果它们在内存中不连续,则可能是 std::list 的一部分。
还要考虑 std::list 中的对象可以有无限长的指针链(想想 it->prev->next->prev->next->prev……),在 Cheat Engine 中关闭「无循环指针」时,对它们进行指针扫描会显示更多结果。
你也可以用脚本检测值何时存储在链表中。清单 5-8 展示了一个正是这样做的 Cheat Engine 脚本。
function _verifyLinkedList(address)
local nextItem = readInteger(address) or 0
local previousItem = readInteger(address + 4)
or 0
local nextItemBack = readInteger(nextItem + 4)
local previousItemForward =
return (address == nextItemBack
and address == previousItemForward)
end
function isValueInLinkedList(valueAddress)
for address = valueAddress - 8, valueAddress -
48, -4 do
if (_verifyLinkedList(address)) then
return address
end
end
return 0
end
local node = isValueInLinkedList(addressOfSomeValue)
if (node > 0) then
print(string.format("Value in LL, top of node
at 0x0%x", node))
end
清单 5-8:用 Cheat Engine Lua 脚本确定数据是否在 std::list 中
这里有很多代码,但它做的事情其实很简单。isValueInLinkedList() 函数接受某个值的地址,然后从地址上方 8 字节(必须存在两个指针,每个 4 字节)开始向后查找最多 40 字节(10 个整数对象,以防值在更大的结构中)。由于内存对齐,这个循环以 4 字节为步长迭代。
每次迭代中,地址被传给 _verifyLinkedList() 函数,神奇就发生在这里。如果我们按本章定义的链表结构来看,这个函数只是这样做:
也就是说,函数基本上假设它得到的地址指向一个链表,并确保假设的节点有有效的 next 和 prev 节点。如果节点有效,假设就是正确的,该地址就是链表节点的地址。如果节点不存在或没有指向正确位置,假设就是错误的,该地址不是链表的一部分。
请记住,这个脚本不会给你列表根节点的地址,只给你包含你给定值的节点的地址。要正确遍历链表,你需要扫描到根节点的有效指针路径,所以你需要它的地址。
找到那个地址可能需要搜索内存转储、大量试错和无数抓耳挠腮,但绝对有可能。最好的开始方式是沿着 prev 和 next 节点链,直到找到一个数据为空、无意义或填充了值 0xBAADF00D 的节点(一些(但不是全部)标准库实现用这个值标记根节点)。
如果你确切知道列表中有多少节点,这个调查也会更容易。即使没有列表头,你也可以通过不断跟随 next 指针直到回到起始节点来确定节点数量,如清单 5-9 所示。
function countLinkedListNodes(nodeAddress)
local counter = 0
local next = readInteger(nodeAddress)
while (next ~= nodeAddress) do
counter = counter + 1
next = readInteger(next)
end
return counter
end
清单 5-9:用 Cheat Engine Lua 脚本确定任意 std::list 的大小
首先,这个函数创建一个计数器存储节点数量,以及一个变量存储下一个节点的地址。然后 while 循环遍历节点,直到回到初始节点。最后,它返回计数器变量,该变量在每次循环迭代时递增。