真的是一波三折,卡壳卡了好几天,结果踏破铁鞋无觅处,得来全不分功夫。
本系列已完结,以下是各章节说明,17 之前是 dos 版相关,之后是 2001 版:
前面我们知道了,资源中有几个字体文件,这些文件中保存了文字的写法,所以我们基本可以肯定,程序会根据某些信息,来确认到底是要画出哪个字。
另一个我们基本可以肯定的事情是文字的编码,Big5 基本上也是没跑。
而最大的问题,是程序显示的文字从哪里来。
我们可以合理推测这个文本应该是出自 game.scr,看名字这应该是脚本文件,游戏大到一定程度应该是会有脚本,这样可以把一部分游戏逻辑的工作从程序那边分离出来,程序可以更专注功能的实现。
我最初的想法是硬解,game.scr 中的数据,如果能在字体文件中找到,那么就是文字, 找不到的,那可能是流程控制字符。那么怎么定义「找到」,字体文件的头部不是有所有文字的编码么, 每次读取两个字节,如果是一个字,则保存,然后移动两位继续读,如果不是字,那么把保存的字输出出来,移动一位继续读。
这个方法是可以输出不少找到的文字,但是经过粗略的筛查就可以知道,输出的文字都是连起来无意义的单字,所以这个方法不可行。
不过我们起码知道了一点,game.scr 并不是明文的数据,可能是加密后的脚本。
这条路走不通,我们还可以有两个选择:
我选了第一条路,结果事实证明是我最近卡壳的重要原因,确实我也找到了文字数据:
offset | data | comment |
---|---|---|
028F:50D0 | 043F | ?? |
028F:50D2 | 028F:50EA | |
028F:50D6 | 028F:51EA | |
028F:51EA | 1A | 长度 |
028F:51EB | C4 5F A4 47 B7 DD A1 42 C4 5F A4 47 B7 DD A1 41 A7 D6 B0 5F A8 D3 B0 DA A1 49 |
|
028F:50EA | 1A | 长度 |
028F:50EB | C4 5F A4 47 B7 DD A1 42 C4 5F A4 47 B7 DD A1 41 A7 D6 B0 5F A8 D3 B0 DA A1 49 |
这个就是开头的第一句话:
可是我就是看着这段数据从这里复制到那里,从那里再复制到另一个地方,有时候还会拷回原来的地方,这就叫人崩溃,到最后也没有摸出来源头。
而另一个让人崩溃的点是,ghidra 开始不给力了:
我们可以看到,相同的位置,明明是指令,却被 ghidra 识别成了资源,更让人难受的是,这种情况还不止一个,经常会想看一个 cpu log 中的函数,在 ghidra 中却找不到位置,理解函数和处理流程的效率大打折扣。我 ghidra 也刚用没多久,不晓得这种情况是否可以手动调整一下。对于这个情况,我觉得其实也不一定是 ghidra 的锅,程序里有脚本,那大概率有些脚本也是以资源的形式保存在程序中的,以我目前的程度,还不能完全掌控这种局面。
不过,看了这么久,也不是一点收获也没有,我们来看程序是如何把数据画成字的:
我之前卖了个关子,程序如何把 big5 编码的字画出来,因为两字节可以容纳 65536 个字,不过程序里只有 16366 个,所以实际占不了那么多空间,这种情况下如何确认是哪个字并画出来,其实这就是程序的算法,看着这三个每次除以二的数字,有点基础的朋友可能会会心一笑,二分法,写程序的老哥基础不错。那个时代用二分法确实可能比较简单,我印象中 c 引入 unordered_map
,也是很后面的事情,那个时候只有 map,这个地方用 map 也合适,因为我们可以控制字符编码的顺序一定是递增的,不过如果放到现在,我想更好的做法应该是用 hashmap
类似的容器,查找更快一点。这个逻辑细节我就不展开说了,因为我们重点不是这个,我们要找文字是哪里来的。
我就这么在内存数据、cpu log 以及 ghidra 中毫无实质进展地耗了好几天,直到一天晚上,我觉得算了,还是从读取文件下手。
喔,这几天我还学到了一个 dosbox 的小技巧,Save / Load State:
由于我的主要实验场景是开新游戏,所以在播完动画保存一下状态,之后每次再加载状态,就可以直接回到游戏主界面,不用重开游戏甚至 dosbox。
另外还有一个发现,程序在文字界面用来打印字符串的函数应该是 FUN_1048_0ab1
:
发现这个函数其实也很偶然,我其实没看懂这个函数的流程,不过看他传入的参数,是字符串的地址,所以我想应该是它没错。
你看,我是真的在不停研究这个东西,但是也是真的卡壳了。
前面也说了,由于我现在不太相信 ghidra 的反编结果,所以我主要仰仗的还是 cpu log, cpu log 和反编两者各有利弊,而且互相补充,cpu log 可以快速帮我们抓住逻辑主线, 而 ghidra 能帮我们掌握程序的结构。我们最终还原的程序流程, 有时候也需要从 cpu log 提取出的信息来验证。现在在我看来,ghidra 已经是靠不太住了,那就看看 cpu log 好了。
思路其实也很简单,bpint 21 3d
确定 GAME.SCR 是何时打开的,然后去看 bpint 21 3f
找到何时读取这个文件,然后打 log。
看 log 看了好几天,整个人都要麻木了,不过这个思路印出来的 log 倒是很容易发现端倪:
从原始的位置读出来,然后再写回原始的位置,这似乎是一个简单的类似 ROT13 的加密方式,那这个程序太好写了,立即写个程序还原游戏脚本:
SHOWEVENT 117,255,32
msg '神秘的玉門關,已經完全門戶大開,似乎正引誘著你長驅直入呢!'
脚本很长,而且内容我应该是不能贴的,看来这个游戏基本上对我没有什么秘密可言了。
我大致对了一下游戏内容,这个游戏脚本化的水平还是相当高的,我有印象的游戏内容, 都来自脚本,所以程序老哥的水平我觉得还是很高的,说出来你可能不信, 其实之前我觉得老哥水平是堪忧的,因为有些资源的处理有些毛草,这个我们以后再说好了。
后来我转念一想,既然脚本怎么玩我已经知道了,那其实我可以自己做游戏内容了。