大富翁 3 游戏文件分析(十二)

春节假的最后一天,抽出了一点时间,写一下之前分析一半弃坑的内容,大富翁 3 中的鼠标指针

MOUSE.MKF

鼠标指针的文件集合,同样符合 MKF 文件的打包规则,一共可以拆分出 14 组 smkf,smkf 又可以进一步拆分成多个文件,因为有些指针是有动画效果的

不过分析文件结构时遇到了些困难

从03开始是第一个鼠标指针,有了以前的经验,我们不难猜出 03 是平面宽度,1e 是平面高度,这样的话,鼠标的尺寸为 24 x 30,但是后面的就不太好猜了,我们基本可以看出,每 15 个字节为一组,那么这可能是 3 个 byte 的 mask 外加 12 个 byte 的四平面数据,鼠标的尺寸这么小,应该不会有像前景图片一样的压缩算法,而且数据排列很规则,应该是原始的图片,不过到底哪几个 byte 是 mask,哪几个数据是图像,这就很难猜了,不过还好,现在我们会用 dosbox debugger 看汇编了。使用 debugger 的方法这次我会讲得粗略一点,控制一下篇幅。

鼠标指针的存储位置

老样子,我们可以使用 bpint 21 3f,在读取文件时中断,然后使用 d ds:dx 来查看程序读取的文件内容,由于我们这次只能使用 rich3.exe 而不是 ss.exe,前者打开的文件更多,需要有些耐心,中间你会看到一个比较明显的文件特征是 ‘creative voice file’,这是音频文件的内容,鼠标还在这之后,注意 DS 值的变化,看到程序读取到 MOUSE.MKF 后,我们注意到文件内容被放到 DS = 9117 的位置。

放到 9117 后,这部分内容一直都没有变化,那么我们推测,基本上程序也是直接把这部分程序写入显存,接下来用断点看就比较累了,我们直接 dump 一段程序运行的 CPU 日志,来查找 9117 是如何写入 A000 的,还记得 A000 吗,对,显存的起始位置。

9117 到 A000

我们用 klogg 打开 logcpu.txt,直接搜索 DS:9117 ES:A000

首先我们看到

  • 126 行读取了 0003,放到 ax
  • 13B 行读取了 001e,放到 ax

我们知道,这是第一个鼠标指针的平面宽和平面高,那么程序就是从这里开始要把鼠标画上屏幕的。

这里面还有个细节,就是程序把宽和高存储到了 cs:0002cs:0004,这里 cs 应该是 381c,后面我们会用到,我们继续看下去。

cs:[0160~0162] 写入了 9117:[0010~0012] 的内容,我们可以知道写入的是 03FFFF

cs:[01b0~01b2] 写入了 9117:[0013~0015] 的内容,我们可以知道写入的是 000000

di 增加了 0x50cs:[0200~0202] 写入了 9117:[0016~0018] 的内容,我们可以知道写入的是 000000,同时注意到 0385 行的 loop,这个 loop 会执行四次,每次差别只有 di 增加 0x50,都是复制 3 个 字节,为什么增加 0x50,其实也很好理解,0x50 = 80,80 * 8 = 640,符合屏幕的宽度,正好是换了一行。

385 的 loop 后,很快我们看到了 程序执行了 38A 的函数,这个函数里我们也看到老朋友了:

  • 39A: out 03ce, 0004
  • 3A2: out 03c4, 0102

我们之前有谈到过这两个

简化指令

指令 说明
3ce xx05 写入模式xx
3c4 xx02 按位选择平面xx

out 03ce, 0004 虽然具体是什么我们还不清楚,不过我们知道接下来就要把数据复制到显存了,复制的位置在 3ed 行,我们看复制前的操作:

  1. 3e0 es:di (A000:2670) 的值复制到 al
  2. 3e3 alcs:0160 (3F) 执行与操作
  3. 3e8 alcs:01B0 (00) 执行或操作
  4. 3ed al 写入 es:di

简单总结的话,这个步骤相当于先取出原始位置的图像,与 3F 执行与操作,与 00 执行或操作,然后写回原位,通过上文我们可以知道 3F 和 00 是 哪里的数据,我们来标记一下它们的位置

这个 loop 执行了 3 次,差别在于 si 和 bx 自增 1,即

  1. A000:2671 取出,与 381C:0161,或 381C:01B1,写回 A000:2671
  2. A000:2672 取出,与 381C:0162,或 381C:01B2,写回 A000:2671

接下来程序又回到了 39A 和 3A2,有一点需要我们留意,3F3 行,bx 自增了 0x50

  • 39A: out 03ce, 0104
  • 3A2: out 03c4, 0202

我们也轻车熟路了,换平面。然后后续的操作对比之前也不难看出

  1. 3e0 es:di (A000:2670) 的值复制到 al
  2. 3e3 alcs:0160 (3F) 执行与操作
  3. 3e8 alcs:0200 (00) 执行或操作
  4. 3ed al 写入 es:di

与之前也是相同,差别就在于 bx 增加了 0x50,后续这个循环也会执行三次,来写入 26712672

结论

好了,到这里我们基本可以知道鼠标指针的基本结构了

长度 说明
2 平面宽度
2 平面高度
平面宽度 第一行 mask
平面宽度 第一行 第一平面数据
平面宽度 第一行 第二平面数据
平面宽度 第一行 第三平面数据
平面宽度 第一行 第四平面数据

后面就是每行的重复,我们来画个小天使庆祝一下吧