找出在哪里暂停¶
作为起点,我用 Cheat Engine 在内存中找到 Player 1 名字的地址。得到地址后,我用 OllyDbg 在字符串的第一个字节上设置内存断点。每次这个断点被触发时,我检查汇编代码,确定它如何使用 Player 1 的名字。最终,我发现在我之前命名为 printText() 的函数的调用正上方,名字被直接访问。我找到了绘制名字的代码。
我移除了内存断点,并在对 printText() 的调用上替换为代码断点。但有一个问题:因为对 printText() 的调用位于遍历游戏中每个玩家的循环内部,我的新断点在每次绘制名字时都会被触发——这太频繁了。我需要修正它,让它只在特定玩家时触发。
检查我之前内存断点处的汇编代码告诉我,每个玩家的名字都是使用以下汇编代码访问的:
EAX 寄存器包含一个玩家数据数组的地址;我把它叫做 playerStruct。playerStruct 的大小是 0x90 字节,ECX 寄存器包含迭代索引(著名的变量 i),每个玩家的名字存储在其 playerStruct 起始位置之后 0x50 字节处。这意味着这条 PUSH 指令把 EAX[ECX].name(索引 i 处玩家的名字)放到栈上,作为参数传给 printText() 函数调用。那么,这个循环可以分解为类似下面的伪代码:
playerStruct EAX[MAX_PLAYERS]; // 在别处填充
for (int ECX = 0; ECX < MAX_PLAYERS; ECX++) {
char* name = EAX[ECX].name;
breakpoint(); // 我的代码断点基本上就在这里
printText(name);
}
纯粹通过分析,我确定 playerStruct() 函数包含所有玩家的数据,循环遍历玩家总数(用 ECX 递增计数①),获取每个索引的字符名②,并打印这个名字。