十五、《圣剑传说》的原文本输出程序分析
接下来我们就好好分析一下游戏原来的文本输出程序,看可以如何去做修改。
之前已经记下了游戏在运行到0:378A时会读出文本,就在这里设置断点,先看一下游戏在读出文本后做了哪些操作。

这里可以开两个窗口,平行地看游戏对清音和浊音的输出有什么区别,这样们可以知道在哪里去做修改。这里我们先以读出D:46FB的4E(“ど”)为例。
读出文本的代码后,按F7一步一步向下走。这里是判断读取得到值是否是控制符(00-1F)或者短语(20-3F)。

cp a,40
jp c,3837
cp是比较,将当前a的值与40作比较,相当于做减法,根据结果的不同执行不同的操作。有两套判断标准,一个是c和nc,如果结果小于0,就标记为c,大于等于0就是nc。另一个是z和nz,结果是0就是z,不是0就是nz。可以在寄存器窗口最右侧看到标记情况。这里4E-40=E,是大于0的,标记为nc。
jp是跳转的指令,可以加判断也可以不加。这里表示,如果标记是c就跳转到3837,现在标记是nc,所以不会跳转,程序要继续向下运行。
这一段是比较D84A的值,如果这个值是下面做比较的这些值中的一个,就跳到37AF去运行,不是就继续运行。

push af表示把当前寄存器af的值推送到栈,与之对应的是pop af,从当前栈中取回值到寄存器af。类似的其他几个寄存器也有同样的命令。一般来说是后面的操作会用到当前这个寄存器,但现在寄存器的值待会还要用,就可以用这个命令暂时保存一下。这个命令使用时要注意推送后记得取回,如果同时推送多个寄存器的话要注意取回顺序。
jr也是跳转,但只能跳转比较近的距离(前后256个字节?),但这个命令本身只有2字节,相比jp(3字节)可以节省一些空间。
这里D84A的值是06,所以继续运行。将b的值减1,给a赋值7F,然后调用程序0:389D,再给d的值加1

这里call是调用程序,表示跳到后面这个地址去运行,运行完一般会有一个ret返回的命令,再返回到下一行继续运行。
这里0:389D这个程序非常重要,它完整的运行顺序是这样的。

它的作用是根据de的值计算出一个MAP上的地址,然后把a的值对应的Tile写到上述MAP地址,从而实现文本的显示。具体的程序就不解读了,大家可以自己去分析一下。这里因为调用程序之前给a赋值7F,对应的是空白Tile,同时给d的值减了1,所以最终的效果是将当前要写字的位置上方清空。
接下来,将寄存器a的值取回来,也就是之前读出的文本的代码,然后将这个值先与67比较,再与70比较。

看一下码表,40-66是浊音,67-6F是半浊音,而前面我们已经判断过一次,a里的值要大于40,综合起来我们可以判断出这里的两个cp指令正是来判断浊音和半浊音的,也就是我们预期可以用来跳转到中文输出程序的位置。
简单看一下0:37FA和0:381E这两个程序,他们分别给a赋值70和71,然后调用程序0:3897

而程序0:3897是不是很眼熟,和刚才0:37A8几乎一样,将d减1后调用程序0:389D,再给d加1。区别是没有给a赋值,这里a就对应将要写到MAP上的Tile的编号。

看一下VRAM,70对应的Tile是“゛”,71对应的Tile是“゜”,也就是浊音和半浊音的上标。通过0:389D程序,把对应的上标写到当前字符的上方,实现浊音、半浊音的输出。后面的程序就是再换算出对应的清音的码表编码。这里4E被计算为9D保存在a中返回。查看码表,9D对应的正是“と”。接下来将a的值减去80,对应到VRAM中的Tile编号,调用程序0:389D程序,把这个字符写到MAP上。

因为这里没有给d值减1,所以“と”是写在正常文本这一行。而如果之前a的值大于等于70,就会直接来到这里,输出单个的字符。
到这里,程序就完成了一个编码对应的文本输出。所以我们就可以把0:37B0这里修改为判断a的值后跳到我们的中文输出程序,输出中文后再跳到0:37BF。
后面的程序暂时还看不太明白,最终会来到一步ret退出程序,之前这些都是属于程序0:3786。我们先放一放,再往前面去看一看。
我们从读取文本的断点往前追踪,很快就看到,确实是调用了程序0:3786。

程序0:3786先调用程序0:375C,先判断D84A的值是否06,然后从D8B8-D8BB读出de、bc的值,接下来就到了我们刚才分析的文本读取输出。

文本的地址hl在进入程序之前就已经存在了,de和bc是程序后重新读出来的。并且之前已经发现de的值和文本在MAP上什么地方写Tile有关系,我们就再看一下它们是如何变化的。
点击游戏屏幕,观察游戏在每一次运行到0:378A时,寄存器中数值的变化。可以看到hl的地址每次增加1,这个可以理解,要按照顺序把文本一个一个读出来。另外一个变化时每读写一个字,b的值会减1,e的值会加1。这应该是程序0:3786中下面这两句运行的结果。

按照这个规律,如果输出4个Tile的大字,因为编码变成了2字节,同时字符多占了一个字的位置,可能要给b和e额外的加1减1。这个后面写程序的时候要注意。