跳转至

激活跟踪

为了最终设置我的跟踪,我移除了之前的所有断点,并在疑似主循环顶部放置了一个断点。断点一被触发,我就移除它。然后我在 CPU 窗口中按 ctrl-T,弹出一个叫做「暂停运行跟踪条件」的对话框,如图 2-6 所示。在这个新对话框中,我启用「条件为真」选项,把表达式放在它旁边的框中,按「确定」。然后我回到 CPU 窗口,按 ctrl-F11 开始「跟踪步入」会话。

图 2-5 OllyDbg 的「找到的模块间调用」窗口

跟踪开始后,游戏运行得如此之慢,几乎无法玩。为了降低测试角色的生命值,我打开了游戏的第二个实例,登录另一个角色,攻击我的测试角色。当跟踪的执行赶上实时时,OllyDbg 看到我的生命值变化,在接下来的函数调用上触发了断点——正如预期的那样。

在这款游戏中,修改生命值的主要代码直接由网络代码调用。使用这个跟踪,我能够找到网络模块在网络数据包告诉游戏改变玩家生命值后直接调用的函数。以下是游戏正在做什么的伪代码:

void network::check() {
    while (this->hasPacket()) {
        packet = this->getPacket();
        if (packet.type == UPDATE_HEALTH) {
            oldHealth = player->health;
            player->health = packet.getInteger();
            observe(HEALTH_CHANGE, oldHealth, 
player->health);
        }
    }
}

我知道游戏有只需要在玩家生命值改变时执行的代码,我需要添加也能响应这种变化的代码。在不知道整体代码结构的

图 2-6 暂停运行跟踪条件对话框

情况下,我猜测依赖生命值的代码会在生命值更新后通过某个函数调用直接执行。我的跟踪条件断点证实了这个猜测,它直接在 observe() 函数①上中断。从那里,我能够在函数上放置钩子(钩子是一种拦截函数调用的方法,在第 153 页「用钩子重定向游戏执行」中描述),并在玩家生命值改变时执行我自己的代码。