卧看微尘 发表于 2023-9-10 03:39:37

浅谈火红叶绿汉化中的培育屋BUG

本帖最后由 卧看微尘 于 2023-9-10 03:42 编辑

前言
本文章简单阐述下关于火红叶绿汉化(或以火红叶绿为基础的改版汉化)中出现的培育屋bug的产生原因及解决方法。适合对游戏机制有兴趣及有意向进行改版汉化的朋友们进行阅读。

BUG介绍
培育屋汉化bug,指的是当GBA火红叶绿游戏里存入两只宝可梦后,在确认取出时,发生的卡死问题。该bug仅发生在存入两只宝可梦的情况下,且仅在汉化版中存在,日文、英文等原版中并不存在该问题。

这是一个由来已久的bug,早在d商汉化时期,以及口袋群星SP汉化组的2012年精灵宝可梦版汉化时期均有发生。口袋群星SP的汉化版中,后续的更新版里已经修复了该问题,在发布页的更新内容中可以看到这点。

在如今,会再次遇到这个bug,往往是玩家游玩d商版、早期未更新的12版汉化时会出现。同时在改版圈,改版汉化者引入国外的改版作品进行汉化时,也常常会遇到这种问题。部分早期汉化的改版作品未进行这个bug的修复,玩家游玩时也会遇到这个情况。

口袋群星SP的解决方式
在改版作者的汉化中,往往发现这个bug会在汉化了"Yes\nNo"和"Go back to the\nprevious menu."这两段文字后出现。前者用于“是\n否”选项框的文字,后者则是PC电脑选择道具寄存系统时的提示文字“返回至\n上一级菜单”。
可以看看口袋群星SP汉化组是如何解决这个问题的:


可以看到口袋群星SP的做法是保留了第二句的原句,将第二句的汉化文本修改指针移动到了其他空位处。
除了口袋群星SP的方法外,保留第二句在原位汉化,但是在句子前添加一个空格,又或是在“是\n否”的汉化文本中插入一个空格,同样可以解决这个bug。那么这个bug的本质原因,究竟是什么呢?还请接着往下看。

BUG的本质原因
在这里我们综合借助no&gba的debug功能,以及pret的反编译工程进行探索。
首先既然是已知读取到“返回至\n上一级菜单。”时会出现问题,那么便可以对该地址进行断点,一旦有程序读取这条文本所在的地址时便暂停程序。从上面截图可以看到地址是0x08416080,那么我们需要设置该地址被读取时即停止的断点代码格式为:“?”
按下ctrl+b键唤出断点窗口,输入这串代码。

设置好断点后,在培育屋内进行对话,当触发断点条件时,程序会停止在对应指令上。在这里可以看到是停在了0x081E5F9E的函数位置上。

在pokefirered反编译生成的map文件中,我们可以查找到这个地址的函数是strcpy。

但是很可惜这个函数是封装在库里的函数,并没有c语言样式的函数代码。那么我们接着回到no&gba里观察这个函数。
断点上一位的指令ldrb r0,,意味着将地址为寄存器r2里的值,将该地址的1字节的值,存入到寄存器r0里,可以看到r0里存入的值为0xC1,对应的便是"Go back to the\nprevious menu."的第一个字母“G”的编码。

而接下来的指令strb r0,,意味着将寄存器r0的低位1字节的值存入寄存器r3的值所代表的地址,即0x03007D6D。Trace运行一下指令,就可以在数据窗口内观察到该地址的确被写入了0xC1。

观察0x03007D6D周围的数值,可以发现前面的数值其实就是"Go back to the\nprevious menu."这句文本前面的其他文本内容,从0x03007D4C开始写入。


在反编译项目里可以看的更清楚,写入的是这些文本

继续运行程序,我们可以发现这个函数会进行一个判断,即读入的文本编码是否为0x00,也即空格所代表的编码,只有当读到0x00时,程序才会停止读入,否则会一直读取文本的内容。

那么到这里问题已经开始逐渐明了了,"Go back to the\nprevious menu."文本的第三个字符便是空格,也即0x00,可以终止程序的运行,而汉化后的文本由于中文语法习惯不常用空格,导致程序一直读取不到0x00的值,造成内存溢出,文本编码不断载入占用了其他内存的数据。
这也符合上述提到的几种解决方案都是在其中保留了0x00的编码,使得读入的编码没有造成内存溢出。
接着我们看一看究竟是哪一个程序调用了这个strcpy函数,在记录返回的程序地址的r14寄存器的位置上,可以看到是0x08046961,在反编译文件里查找,可以看到是函数DaycarePrintMonLvl所在的范围内。

那就让我们来看一看这个函数到底写了些什么。

可以看到strcpy((char *)lvlText, (const char *)gText_Lv);这句,意思是将文本gText_Lv的值拷贝到变量u8 lvlText的位置上。而这个gText_Lv,正是上面观察到的被载入内存的第一个文本。

这个函数的作用,便是在取回宝可梦时,显示宝可梦的等级Lv符号。而这个窗口,仅在存入两只宝可梦的时候才会显示。只有1只宝可梦时,并不会出现这个窗口,而是直接归还宝可梦。这也应证了前面提到的bug的触发条件必须是已经存入了两只宝可梦后才会出现。

在反编译函数里,还有一个细节,提到当版本不为1.0时,调用的是StringCopy(lvlText, gText_Lv);函数,而不是strcpy。我们可以看一下StringCopy函数的内容。

可以发现StringCopy同样也是拷贝文本编码,但区别在于停止的识别符是0xFF,而0xFF正是宝可梦3代游戏里,文本结束的标识符。而strcpy,还记得上面对指令的观察得到的结论吗,停止的识别符是0x00。这就是两个函数的最大区别。
至此,BUG产生的本质原因已经彻底明晰,问题在于strcpy函数的缺陷。程序为了显示两个宝可梦的信息窗口,本应只拷贝gText_Lv文本以显示lv符号,但却由于strcpy函数只有识别到0x00时停止,以至于将后面的文本也一同载入进了内存中,直到遇到了“Go ”里面的空格0x00才停了下来。而汉化版汉化之后0x00需要在非常后面才存在,致使strcpy读取数据超出原定大小造成内存溢出,从而引发卡死。

BUG的解决方法
在上述内容中,我们可以看到其实GF本身已经给出了一种解决办法,将strcpy函数替换为StringCopy函数。在火叶的v1.1版里已经更新为了这个函数。
而对于以火叶v1.0为基础的汉化,亦或改版汉化,可以采用如下方式进行修复。
通过观察gText_Lv到gText_GoBackToThePreviousMenu之间的文本,可以发现gUnknown_841623D[] = _("YES\nNO");这句文本最适合插入0x00的编码。
“Yes\nNo”对应的编码为 “D3 D9 E7 FE C8 E3 FF”总计7个字节
而汉字为双字节,翻译为“是\n否”时,正好空出1个字节的富余,对应编码为“0C 0C FE 03 F4 FF”总计6个字节。
那么多出来的那一个字节,就可以用来放置0x00,即“0C 0C FE 03 F4 FF”+“00”。
当然,也同样可以把空格塞入到文本内部中,如添加到“是”或“否”的后面,
“是 \n否”(“0C 0C 00 FE 03 F4 FF”)、“是\n否 ”(“0C 0C FE 03 F4 00 FF”)。
以上便是对于火红叶绿汉化中的培育屋BUG解析的全部内容,希望能对大家有所帮助。


卧看微尘 发表于 2023-9-12 08:24:59

K.SKT 发表于 2023-9-12 02:49
感谢阁下对于这个问题的进一步研究。另外有了些个人而言的一些稍稍有点价值的新发现……不过毕竟本人经 ...

地址这方面你我都是没问题的哈哈,偏差的原因是在于你用的是火红,我用的是叶绿做测试。

至于去掉ff后的00没有溢出,是因为内存里留给复制lv的变量是有一定空间的,你可以看到文本里在后面不远处也有00,所以还是会在允许范围内截断,就像美版一样一直到“Go ”才截断也能正常运行,你可以试着把后面一定范围内出现的00都删掉,让它超出内存预留给它的空间,那么就会溢出了。

卧看微尘 发表于 2023-9-12 08:46:07

K.SKT 发表于 2023-9-12 02:49
感谢阁下对于这个问题的进一步研究。另外有了些个人而言的一些稍稍有点价值的新发现……不过毕竟本人经 ...

啊,刚再仔细看了下你说的,我上面说的朋友说的时间,指的是2010年的时候发生的bug,那个时候还没有新汉化出来,所以判断是d商版出的问题。不是指朋友的bug是10年前发生的(笑哭

卧看微尘 发表于 2023-9-12 08:41:46

本帖最后由 卧看微尘 于 2023-9-12 08:51 编辑

K.SKT 发表于 2023-9-12 02:49
感谢阁下对于这个问题的进一步研究。另外有了些个人而言的一些稍稍有点价值的新发现……不过毕竟本人经 ...

大概从f905ff起,含f905的0x28个字节内,把里面出现的00全部替换掉,你就可以在日版或d商版触发这个bug了。

卧看微尘 发表于 2023-9-11 09:42:39

本帖最后由 卧看微尘 于 2023-9-11 09:44 编辑

K.SKT 发表于 2023-9-11 01:25
万分感谢阁下,这才是技术力爆表的本格的原创研究。
而技术力全无的本人表示遇到这类问题,也总是会去找一 ...
我想想,说d商版是因为当时在群里讨论时,有一位小伙伴提及10年的时候也遇到过卡死问题,那个时候新汉化版也是还没有出来的,所以我便默认是在d商版里也存在这个问题。
我确认了下,在日版火叶里,lv字符并不是调用的“是否”前面的lv字符,而是在其他地方的lv字符,地址是0x0821c292,这里在lv字符ff后立刻补了个00,所以不会有影响。所以说要么可能是那位小伙伴记错了不是这个版本,要么可能是当年对方玩的d商版并不是我们现在常见的那种,的确有造成了strcpy的溢出问题,或者干脆就是其他方面的问题导致的bug,而非strcpy相关的内容。
以及绿宝石里的StringCopy函数,从时间线来说,其实正是继承了火叶v1.1修复之后的StringCopy函数。红蓝宝石里有查看了下,这部分使用的是完全不同的函数,直接没有strcpy或stringcopy出现的机会。

海のLUGIA 发表于 2023-9-10 10:10:53

strcpy在1.1还有地方会用到?其他功能倒是有用00判断结尾的现象…

卧看微尘 发表于 2023-9-10 10:26:25

海のLUGIA 发表于 2023-9-10 10:10
strcpy在1.1还有地方会用到?其他功能倒是有用00判断结尾的现象…

反编译项目里提供了1.0和1.1的代码放在了一起,可以选择编译出1.0或1.1的rom。全局搜索strcpy的结果是除了培育屋lv的函数外,只有一个test函数有用到strcpy,也意味着v1.1修复了培育屋lv函数后,游戏里正常流程是不会再有遇到strcpy的情况了。所以v1.1是不用担心strcpy的问题的。

K.SKT 发表于 2023-9-11 01:25:55

本帖最后由 K.SKT 于 2023-9-11 02:44 编辑

万分感谢阁下,这才是技术力爆表的本格的原创研究。
而技术力全无的本人表示遇到这类问题,也总是会去找一些野路子,仅此而已……实在是有些东西所以简单说说……
倘若是基于反编译汉化的话,个人如同白痴般単純的解决思路无非就是换版本找答案,即使没有GF给出的友情解决方案,没有这个问题的PokeEmerald之中的函数名和功能也与PokeFirered大同小异,几乎连调整都不用就可以简单的复制粘贴就可以搬过来。
https://images.weserv.nl/?url=https://i.ibb.co/w4rCDBj/em-sodateya.png

(当然以上只是个人野路子,对其中的原理真的一无所知……
不过古早接触汉化时因为D商ROM的本身空位安排布局的和压缩文本空间的特殊性,所以养成了一律FF填充后改指针的习惯,估计乱搞规避掉这个BUG的可能性也不是没有……)
但是遗憾的是,个人在印象里的D商汉化貌似是没有这个问题的。为此还测试了一下,也暂时并未在其中发现同类问题……
(毕竟继承了衣钵,也算是稍稍地为师父正个名了)。
(不知道GIF能不能看,姑且就这样吧……)
https://link.jscdn.cn/1drv/aHR0cHM6Ly8xZHJ2Lm1zL2kvcyFBamRFTDVQQ0ROWS1nVDZJdS1Ha0NlS2NFTHowP2U9YUxkZ1U2.gif

配合文字转换工具和CT反查到D商的那串字符(「返回前面!」)位于083DD871这个地址,上面还一样附带了那个「是\n否」……
使用無料GBA单刀直入直接打开玩家电脑界面断点是成功的。但重复在培育屋的一系列对话之后始终没有触发……
https://images.weserv.nl/?url=https://i.ibb.co/CQHzCZL/image.png

https://images.weserv.nl/?url=https://i.ibb.co/YdvR9xt/moji.png

https://images.weserv.nl/?url=https://i.ibb.co/tzHwpwv/texthex.png

(怀疑是D商和POCKETSTARS SP一样修改了文本指针。阴差阳错地去掉了这个BUG……
原版日版倒是还没确认,等有时间确认一下……)
另外,顺手还看了下日版FRLG源码中的「sodateya.c」,虽然本身各类函数命名与排布同PRET项目相差甚远,不过也有幸找到了疑似同款函数(毕竟就是英文版「准确地来说可以被叫做英化版吧……」前身)。
https://images.weserv.nl/?url=https://i.ibb.co/9ZF4PjB/src-sodateya.png

构造貌似应该是与英文版1.0类似,理论上也应该是有这个问题的。但不知为何,基于日版汉化的D商版其实貌似是没有的,且中间也没有发现添加00空位的痕迹……
最后一句,个人所说的这些只是一些废话而已,过于深奥的技术与汇编层面几乎可以说是一窍不通。见笑还请见谅。
以上。

TC酱 发表于 2023-9-11 11:37:49

没想到现在还能在坛里看到这么硬核的内容
我自己三代时期没玩孵蛋没有遇到过
现在也算是了解冷知识了感谢大佬更新

K.SKT 发表于 2023-9-12 02:49:02

卧看微尘 发表于 2023-9-11 10:42
我想想,说d商版是因为当时在群里讨论时,有一位小伙伴提及10年的时候也遇到过卡死问题,那个时候新汉化 ...

感谢阁下对于这个问题的进一步研究。另外有了些个人而言的一些稍稍有点价值的新发现……不过毕竟本人经验技术力相对而言不足。如有错误还请见谅
冒昧跟阁下稍稍讲一下,个人查出的具体地址貌似是0821C2B2,与阁下的地址相比稍微差了两行……不过区域也大致在附近,证明阁下的发现是对的……
https://i.ibb.co/9T4tXV3/tekisuto2.png

当然可能也是本人出错了,技术不精,如有错误还请见谅。另外本人没有用断点的方法,而是直接查看的代码(因为前者对于汇编知识的要求,就技术力全无的本人而言实在是無理),前面应该有一串与之相并排的「やめる$」的(PRET项目里「$」就是FF了,不过原代码里是用「_EOM(End Of 文字【もじ】,相当于PRET中的EOS【End Of String】)」这样的一个名称表示)。
https://images.weserv.nl/?url=https://i.ibb.co/M13ZRcm/kigou.png

https://images.weserv.nl/?url=https://i.ibb.co/dLSqYrW/tekisuto.png

发现这两者外加一个换行键貌似被写在了一起……所以文字一转换就很容易地被查出来了……
把得到的地址反拿回到D商版,更令人驚奇的事情出现了,D商竟然漏翻了这个「やめる$」……这也就导致了两者在这一块的文本是一样的……

拜读完阁下的回复后,本人顺手做了个小实验(既然文字都一样,就用了D商版,因为毕竟串不了档),首先把后面补的这个00,也就是阁下认为的中止判断大胆改成了FF,其目的无非就是来测试ROM会不会出现显示上的BUG……
https://images.weserv.nl/?url=https://i.ibb.co/9yfVLcm/EDIT1.png

结果并没有发现ROM存在数值溢出问题。文字显示也很正常。
https://images.weserv.nl/?url=https://i.ibb.co/RGFKRW3/01.png

另外再将前面的FF改成了00,倒是发现先前的文本起了变化。说明在日版中,不出意外的话,起到中止作用的是FF,而不是00。
https://images.weserv.nl/?url=https://i.ibb.co/bJCBzms/EDIT2.png

https://images.weserv.nl/?url=https://i.ibb.co/ch8QG48/02.png

最后把两个数值都改成了00,虽然没有出现溢出问题,但文字显示还是不正常的……
https://images.weserv.nl/?url=https://i.ibb.co/pKQCTFR/EDIT3.png

https://images.weserv.nl/?url=https://i.ibb.co/ch8QG48/03-1.png

经过上面这一番简单的验证,个人猜测不会日版的strcopy不会就是和PM_TextPrint(也就是所谓的StringCopy)一样仅仅用FF判断中止?(虽然从常识来看纯仮名字里行间必须要空格隔开要不真的是全然),且本身英文版文字显示系统相较于日版进行了非常大的修改,如此涉及到ROM底层修改专业的问题,技术力全无的个人是无法解决和深究的。
再其次,就是版本的问题,这里没有质疑阁下的意思,不过十年前倒推的话,现在通行的新汉化FRLG,应该已经出来了说不定,毕竟POKESTAR的新汉化FRLG最早应该是2012年4月发布的,也就是2011年暑假绿宝石新汉化出来约大半年之后。现在算来,也已经有10年了。不过个人印象里火叶是没有和绿宝石一样以「口袋妖怪」为标题的汉化版本,实在是可惜了。
第三世代的FRLG的话,个人手里也属实是有收集好几个DUMP出来的其他版本,但归根到底,都是D商从日版为基础汉化出来的。本身就是同一版本,但由于应该是出自不同的卡带,大的不同虽然没有,小的不同应该是有的。袁叔叔的加密壳在GBA领域是出了名的黑科技。各位卡商魔改或是碰到了什么底层机制也不一定……要不然就没有诸如当年绿宝石有一部分卡带坐船死机,而有一些卡带却正常这样的事情了……
这些非常见的D商汉化版本,多数是OVERDUMP以及存在各种BUG的BADDUMP(有一个版本连精灵查看界面打不开……选择即黑屏,而印象里某个版本的叶绿性格是未汉化的乱码等等……),另外大约是一两年前在某论坛还找到了内销转出口的海外特供版D商英化叶绿和英化蓝宝石(之前看到过资源吧那边有人搬过D商英化绿宝石,也就是大名鼎鼎的Chinese Emerald),然而貌似都是32MB的OVERDUMP,且连档都存不了……
https://images.weserv.nl/?url=https://i.ibb.co/z4rCtF4/cleaf-1.png

等个人之后有时间的话,也许会具体尝试,至于其他的版本,恕本人见识真的不多,认知水平有限。是真的没有再见过了。
最后,个人也去看了一下PokeRuby,发现正如阁下所言,变动相对较大,但其实在分函数里貌似好像也是用到了StringCopy这个函数的……
https://images.weserv.nl/?url=https://i.ibb.co/FxgQtvf/Pokemon-Ruby-Version-U-V1-1-02.png

https://images.weserv.nl/?url=https://i.ibb.co/XbSVv8g/ruby1.png

https://images.weserv.nl/?url=https://i.ibb.co/pWwyGZK/ruby2.png

我个人没有具体测试这两个函数的効用,根据英文名称和结构推测应该是负责显示精灵列表中的昵称和等级,不过仅仅只是本人的猜测……至于「LV」这个字符是如何显示,个人理解是dest附着的数值之中,这一过程与FRLG和EM不同,其实就是从操作数据方面着手,直接输入代表LV这个字符与空格的数值。和日文版在数量词后面显示诸如ひき和こ之类的単位貌似是同一个道理。对照Charmap.txt查看一下,也许应该就相对明显了吧……
https://images.weserv.nl/?url=https://i.ibb.co/x2Tr5qY/char.png

最后,以上全部只是本人的猜测和个人略微的一些単純的思考,仅此而已。如有错误,真的还请见谅。

卧看微尘 发表于 2023-9-12 08:26:52

K.SKT 发表于 2023-9-12 02:49
感谢阁下对于这个问题的进一步研究。另外有了些个人而言的一些稍稍有点价值的新发现……不过毕竟本人经 ...
以及倒也不用担心质疑冒犯什么的,有能一起对细节提供不同视角进行讨论的同好我是很开心的,可以让结论得到更好地完善~
我的那位朋友所说的版本,由于年代过远,自己也找不到当年的记录和rom了,没法具体验证问题情况,只能说悬着无法确定到底什么是根本原因了,就我现在搜集的d商rom来看,我更倾向于是其他方面的问题而不是strcpy导致的bug。
红宝石我前文说的函数结构不同,其实是特指出问题的lv这个东西,正如你看到的,它是在dset里直接写入编码的,也就是0x34这个编码,而非火叶绿里单独对其strcpy或stringcopy。指的是这个意思。

EiMos 发表于 2023-10-17 20:33:08

大佬们聚集,太强大了

sdhuch 发表于 2023-10-18 15:39:45

这个分享很专业,都能自己去看内存了。
页: [1]
查看完整版本: 浅谈火红叶绿汉化中的培育屋BUG