TimothyQiu's Blog

keep it simple stupid

写游戏修改器的流水账

分类:技术

昨天很郁闷的在玩《仙剑五》打七圣的时候被太武老爷子调戏了三四个小时,于是玩游戏一贯很讨厌用作弊器的俺打起了作弊的念头。因为懒得找专用修改器和《金山游侠》(软件名称应该打书名号,但总有点怪怪的)的下载,作为一个死程,自然是要自己动手写代码咯~

基本原理非常之简单:既然程序运行时所需数据都保存在内存中,那么如果要修改游戏里的某项数值的话,直接去保存数值的地方修改就好了。

首要问题在于:茫茫内存,我他喵的怎么知道 HP 存哪儿去了?

嘛~用过《金山游侠》的都知道这么一个流程,比如要修改 HP: 1. 记下当前的 HP,按「搜索」,下面一个列表里会显示一大堆「搜索结果」; 2. 如果结果不唯一,进游戏修改自己的 HP(比如嗑药)后重复第1步; 3. 此时剩下的结果显示的就是保存 HP 的地方了。

也就是说,要在该进程所占的所有内存地址中不断筛选所存值与 HP 值相同的地址。

打开进程需要先获得进程ID,最简单的方法可以通过 EnumWindows -> GetWindowThreadProcessId 的流程枚举获得各个窗口对应的进程ID,但我这么做以后运行时一直提示「无法定位程序输入点 GetWindowThreadProcessId」,各种搜索未果后就放弃了好吧,不久后我又发现这么做木有问题了;另一种方法是改按 CreateToolhelp32Snapshot -> Process32First -> Process32Next 的流程获得各进程ID。(=ω= 忽然很萌这种 API 接 API 的「流程」……)

获得进程ID后就可以用 OpenProcess 获得进程句柄,剩下的工作就是遍历该进程所占内存。

于是又来一个问题:从哪儿遍历到哪儿?即使是 32 位系统,要遍历 0x00000000~0xFFFFFFFF 的话你也伤不起啊!

幸好这个范围是可以缩小的:

首先,我们知道,虽然 Windows 下每个应用程序可以认为「独占」所有地址,但也只是「认为」而已。实际可访问地址的上下限可以通过 GetSystemInfo 获取。

然后,在这个上下限范围内,也并不是所有内存都已分配,更不是所有内存都可读写(「不可写」即该进程自己也不可写,那么是纯常量或者程序段,肯定存不了HP数据)。可以通过 VirtualQueryEx 函数获取各段内存的信息,从而筛选出我们最终需要分析的地址范围。

那么,在之前得到的内存片段里,就可以用 ReadProcessMemory 读取指定地址、指定大小的数据了。经历若干次筛选,地址确定后,可以用 WriteProcessMemory 改写指定地址、指定大小的数据,这样就实现了游戏的修改,最基本的内存修改器也就是这样的了。

ps. 悲催的是今天决定自己写修改器其实是因为懒得下载安装《金山游侠》,但结果写到最后,还是把《金山游侠》下了下来做测试用了……

游戏按键处理?

分类:技术

游戏中,主循环每一帧的一般流程概括起来是这样的:

处理输入 → 更新状态 → 绘制画面

「处理输入」就是获取键盘、鼠标、手柄等等输入设备的当前状态,然后根据这些输入设备的状态改变游戏中相应对象(不管是实体还是虚拟对象)的状态。

这里的处理按键和操作系统本身的按键消息不属于一个系统,有一个比较麻烦的就是键是可以一直按着的。假设游戏有甲乙两个画面,两个画面间可以通过按 Tab 键切换,如果在每个画面的处理输入时写:

input.tab = IsKeyDown(KEY_TAB); // input.tab 是布尔型变量

if (input.tab) {
    SwitchState();
}

那么如果你按着 Tab 键,游戏就会一直在两个画面间切换,which is not what we want(好吧,这个从句用中文说感觉不像中文,那就英文写好了)。

也就是说,事实上我们需要的是边沿触发而不是电平触发(这术语是模电还是数电的来着 = =),如果API只能取得指定键是否按下的话,有一种方法就是自己来:

if (IsKeyDown(KEY_TAB)) {
    input.tab++; // input.tab 是整型变量
} else {
    input.tab = 0;
}

if (input.tab == 1) {
    SwitchState();
}

这就是今天上网看到的一个比较顺眼的方法,虽然比较火星。以上。