十七、新的文本读取程序
前面我们在查找文本指针的时候并没有关注文本的Bank号,如果后续还是在原来文本的位置修改,那可以不用管。但现在我们修改后的游戏中,码表会采用双字节,原来的位置肯定是放不下这些文本的,我们需要想办法把文本放到新的地方去,所以还需要看一下游戏里文本的Bank号是如何读取出来的。
前面在分析文本输出程序时,我们已经知道游戏是在0:34A1这里调用程序0:3786来实现文字输出,在这里设置断点

可以看到在调用这个程序时,Bank号已经是D,所以要往前去找Bank D是从哪里读出来的。
找到0:3CD0这里,从D86A读出的0D

接下来看什么时候往D86A写入0D,找到了程序3CB4将0D写入D86A

它根据D8B6、D8B7保存的文本指针(第14节分析过),判断该文本属于哪一个Bank。所以如果我们把文本放到了其他的Bank,需要对D86A的值进行修改以保证读取的是正确的文本。
接下来我们就开始修改程序。这里尽量把程序拆分成一个一个子程序,这样后面汉化其他游戏时,只要单独去修改个别的程序就好。
修改后的游戏中,码表采用双字节,第一个字节表示Bank号信息,第二个字节表示在该Bank中的位置,新的字库放在Bank 11-17,对应的第一个字节编码分别是31-37。(注意一下因为我还想保留原来的日文输出程序,所以把判断的编码改成了40以下,也就是原来对应短语的那部分编码,反正也不需要输出短语了。)
新的文本会放在Bank 18-19,对应关系为原Bank D的文本放到Bank 18,原Bank E的文本放到 Bank 19。Bank 10用于放新增加的程序。
首先把下面这两处做如下修改
0:3364 cp a,21 0:378A cp a,21 |
原程序为与40作比较,若小于40就跳转到输出短语的程序。这里修改后,对于21-40之间编码就不会跳转,会继续向下运行到读取文本的程序。
与之对应的,原来Bank 0上原来用于存放短语的0:3F72-0:3FFF这一部分也不需要了,把这里的内容删除,后续可以在这里写新增加的程序。
将原来对浊音进行判断的程序做如下修改,当读到21-40之间的值时会跳到WriteCH这里去运行新的程序
0:37B0 cp a,40 jp c, WriteCH |
注意在实际修改时,要把WriteCH换成该程序的地址(这里如下方所示,应该是3F72)。
当前的程序是在Bank 0上的,所以我们在Bank 0上找一个空的地方增加一段程序WriteCH。大部分时候Bank 0上可用的空间都非常少,这里我们也是删除了一部分不用的内容才腾出了一些空间。所以这里我们只放跳转的程序,当读取到对应的代码后就跳转到Bank 10中去执行大字文本读取输出的程序
0:3F72 WriteCH: push af push hl ld a,10 call 2A21 pop hl pop af call JudgeCode push af push hl call 2A30 pop hl pop af jp 37BF |
这里切换Bank利用了原来程序的0:2A21(切换到新Bank)和0:2A30(切换回原Bank)。注意切换到新的Bank要先指定Bank号,所以这里先给a赋值10。切换回原来的Bank则不需要,程序会把之前保存Bank号取回来。完成大字输出后,跳回到0:37BF,也就是之前我们分析出来的原程序写完小字的位置。
接下来我们就在Bank 10上来写新的程序JudgeCode。
因为有一些参数需要暂时保存,我们在WRAM中找一块没有被使用的区域,用于保存这些参数,这里选择DA00开始的这一段。
在Bank 10的4000这里增加一段程序JudgeCode,这个程序的目的主要是通过判断当前读到的代码或者其它参数,来决定后续采用哪一种文本输出方式,可以看作是前面的程序WriteCH的补充,也为后面增加其它的文本输出方式留下冗余。
这里我们引入了两个控制符38和39来解决原来文本长度不够放新的文本的问题。38是跳转到新的地址去读写(JumpOut),39是跳回原来的地址去读写(JumpIn)。如果都不是的话,就是正常的文本,则把当前读到的第一个代码(字库Bank号)保存在DA01,然后读出下一个代码,再调用正式的文本读写程序。
10:4000 JudgeCode: cp a,38 jr z,JumpOut cp a,39 jr z,JumpIn ld (DA01),a call ReadText call WriteLarge ret |
下面这一段程序是用来实现文本跳转的。翻译文本时,把原来文本的前3个字节修改为“38”+“双字节地址”,这里双字节地址对应新的Bank中文本的初始地址。当读到38跳转到这里时,先调用程序GetAddr把紧接在38后面的地址读出来放到hl,然后把切换前的Bank号(保存在C0C2)暂存在DA00,并计算出新的文本的Bank号(这里是加0B)保存在D86A和C0C2,这样后面就切换到新的文本Bank去读文本。在新的文本的结尾增加3个字节,“39”+“双字节地址”,这里双字节地址对应原来这一段文本结尾的控制代码地址。当读到39跳转到这里时,也是先调用程序GetAddr把紧接在39后面的地址读出来放到hl,然后从DA00取回原来的Bank号,放到D86A和C0C2,使得再切换Bank时会回到原先的Bank。由于这两步操作实际上都没有写文本,所以要提前给b加1,给e减1,以抵消后面程序对b和e的操作
10:4400 JumpOut: call GetAddr ld a,(C0C2) ld (DA00),a add a,0B jr JudgeEnd JumpIn: call GetAddr ld a,(DA00) JudgeEnd: ld (D86A),a ld (C0C2),a inc b dec e ret |
GetAddr这个程序是用来读取38或者39后面接的地址。调用程序ReadText读出这两个字节保存到hl中
10:40A0 GetAddr: call ReadText push af call ReadText ld l,a pop af ld h,a ret |
ReadText这个程序是新增加的用来读取文本的程序,因为这里涉及到切换Bank的操作,最好放在Bank 0上,从C0C2读取需要切换的Bank号,调用0:2A21切换Bank,读出hl上的编码,给hl加1,再切换回去
0:3F88 ReadText: ld a,(C0C2) push hl call 2A21 pop hl ldi a,(hl) push af push hl call 2A30 pop hl pop af ret |
这样我们就完成了新的文本读取的工作。
要注意到原先的主程序0:3786读出来的始终是Bank号(31-37)或者控制符(38、39),这样原来的文本编码或控制符是不会进入我们的文本读写程序的。并且我们只在读取文本时跳转到新的Bank,对于控制符还是在原来的Bank上操作,也减少了出错的可能。
在JudgeCode这个程序中还可以引入更多的判断语句,来实现不同的文本输出方式。比如对于道具武器的处理,以及命名界面小字的处理。
这种方式也有一个弊端,那就是每段文本的长度至少得有3个字节。对于少于3个字节的就要特殊处理。