欢迎光临散文网 会员登陆 & 注册

口袋妖怪绿宝石——数据提取与代码分析(1-字符集与文本信息的提取)

2022-08-17 10:09 作者:围巾胖头鱼  | 我要投稿

说在前面:

    口袋妖怪绿宝石的ROM中包含了游戏的所有信息,许多信息是以列表的形式呈现的,例如精灵列表、道具列表、招式列表等。作为二进制文件,直接从ROM中提取出来的0/1序列本身没有任何可读性,只有在了解这些数据的结构和含义之后,才能提取出有用的信息。

    ROM中的0/1序列有的代表数据,有的代表代码。数据又分为几个类型,包括文本、数字、位图、音乐音效等。本期专栏作者打算介绍ROM中文本信息(呈现为列表的文本信息)的提取。有了文本信息的列表,许多后续的数据处理都会变得容易理解。

字符集

    字符集是一个用数字(0/1序列)来编码字符的映射表。绝大多数编程语言采用ASCII码来对字符编码,对C、Java、Python等编程语言熟悉的读者们可能会记得数字48对应到字符'0',65对应到字符'A'等等。ASCII码就是一种字符集。

    绿宝石ROM有自己的字符集,和ASCII码并不相同。在原版绿宝石的源代码项目文件夹内,字符集存储在charmap.txt这个文件内,可以用VS Code打开看一下:

原版绿宝石的字符集

    使用VS Code的“文件——打开文件夹”,可以将整个绿宝石源代码项目打开。charmap.txt位于项目的根目录中。在这个文件中可以看到,第95行表示字符'A'被编码到了数字BB,这个BB是十六进制数,对十六进制数还不太了解的读者可以参考之前的专栏究极绿宝石5.3——科普向,什么是金手指(二)

    charmap.txt本身有上千行,本期专栏只需要用到它的前156行,第一行是

    含义是“空格”这个字符编码到00,最后一行是

    这里的'$'并不是美元符号的意思,而是指一个字符串结尾的标记。熟悉C语言的读者可以将它看做是C语言字符串结尾的那个'\0'字符。在绿宝石ROM中,每个字符串都会以FF这个字节结尾。

    举个例子,“Hello world!”这个字符串(不包含引号)会被编码到如下数据:

    每个字符恰好占用一个字节的空间。

    一个字节有8位二进制数,也就是256种可能。对于英文字符、数字、一些特殊符号(英文标点、键盘上的数学运算符号等)来说,256个选择已经够用了,但是对于汉字来说却远远不够。在汉化的绿宝石改版游戏中,一个汉字需要用2个字节来编码,汉化版绿宝石游戏的字符集要比原版大得多。

    我们希望通过字符集将ROM中包含的文本信息列表提取出来,比如说上面那一串数字是怎么转换到“Hello world!”这个字符串的。有一个方法是利用Excel表格的功能,将会在本期专栏介绍,在此之前,还需要对charmap.txt里的字符集进行一些处理。

字符集的处理

    为了使用Excel表格的功能,字符集需要处理成一个n行2列的格式,每行表示一个映射关系(编码规则),第一列是字符,第二列是编码。charmap.txt里面每个字符都被单引号括起来,同时字符和编码之间还有个等号,这都是需要去掉的冗余信息。

    在将文本文件的内容复制到Excel表格时,制表符(按键盘上的tab键打印出来的字符)可以将同一行的文本分隔成不同的列,所以在处理后的字符集文件的每一行应该是“字符——制表符——编码”这种格式。

    VS code中默认tab键输入4个空格,而不是制表符,为此需要在“文件——首选项——设置”中进行修改:

    

把该选项的对勾去掉

    打开设置后可以搜索“insert spaces”,在“常用设置”里面把框的对勾取消,就可以按tab键正常输入制表符了。

    还可以注意到,有的符号需要不止一个字节来编码。例如第52行:

    其实在游戏中可以看到这里的POKEBLOCK是一种特殊字体下的字符串,通常情况下它是不会出现在精灵列表、招式列表这种文本信息列表中的。这种不是单个字节的编码不能放在一列中,直接把它删掉就好了。还有一些特殊符号不是用单引号括起来的,为了方便处理也把它们删掉了。

    以上就是需要对字符集进行的处理操作:删除单引号、删除等号并换成制表符、删除编码有多个字节的行。如果一行一行地处理,费时费力,我们需要利用VS code的功能来简化这个操作过程。

    先将charmap.txt的前156行复制到一个新的文件,然后按Ctrl+F出现查找框,开启正则表达式选项:

启用正则表达式

    在搜索框内输入

    这是一段正则表达式,不太了解的读者可以参考由deerchao编写的正则表达式教程:https://deerchao.cn/tutorials/regex/regex.htm

    它的含义是:从左到右依次匹配:单引号(')、单个字符(.)、单引号(')、若干个空格(\s+)、等号(=)、单个空格(\s)、两个数字或字母(\w\w)。

    这样就找到了由单引号括起来、仅编码到一个字节的编码规则,下面是关键的一步,按下Alt+Enter组合键,进入多行编辑模式

多行编辑模式

    这种模式下,每个被选中行的末尾都会有一个闪烁的光标。把选中的内容剪切下来,然后覆盖到这个文件内(最方便的做法是3个快捷键Ctrl+X, Ctrl+A, Ctrl+V),这样每一行就都是合适的编码规则了。

    此时查找对话栏还没有关闭,再按一次Alt+Enter进入多行编辑模式,此时按下键盘上的左右键,会发现光标会在所有行同时左移或者右移,此时编辑一行就等价于对所有行进行编辑。我们可以把单引号删掉、把等号和多余的空格换成制表符:

多行编辑

    多行编辑是提高文件编辑效率的重要工具,尤其是当文件是由格式完全相同的行组成的时候,编辑一行就相当于编辑整个文件。

    现在可以把这些内容复制到Excel表格中了,最好复制到表格的第二行,第一行往往需要填写列的说明(也被称作表格的列头):

复制到Excel表格中

    复制内容到Excel表格中一般都会出问题,上图中本来空格应该对应到00,但复制过来后就变成了0,原因是Excel将它识别为数字,但我们需要把它当成纯文本来对待:

将内容转换为纯文本

    即使转换之后,B2单元格的0也没有变成00,这时只需要再将字符集复制过来就可以正常显示了,并且可以加上每一列的说明:

    

Excel表格中的字符集

    当前工作表可以重命名为“字符集”,以备后用。

    

将当前工作表重命名

    注:如果读者分析的是汉化版的绿宝石游戏,这里的字符集可能会有上千行,并且每个汉字对应到的是两个字节。

文本和编码的相互转换:文本到编码

    接下来我们利用Excel表格的公式功能来实现文本和编码的相互转换。

    新建一个工作表作为实验区域。我们先来看文本怎么转换到编码。

    口袋妖怪系列,全国图鉴的第一只精灵是妙蛙种子,英文名叫Bulbasaur,这个信息可以直接在神奇宝贝百科的网站(https://wiki.52poke.com/wiki/%E4%B8%BB%E9%A1%B5)上复制到。在原版绿宝石游戏中,所有精灵名称的字母都是大写的。如果我们想在游戏的ROM中找到“妙蛙种子”在哪儿,就需要搜索它的大写英文名。

    在Excel表格中,转换英文字符串到大写是很方便的,就是使用UPPER函数:

使用upper函数


    把Bulbasaur输入到A1单元格,在A2单元格输入

    这里的A1可以不用键盘输入,而是在输入公式的时候用左键点击A1单元格,单元格的名称会自动填充到这里。输入公式完成后,再按回车,B1单元格就可以显示大写后的字符串了。

    下一步是将每个大写字母对应到一个字节的编码,这里要提到一个非常强大的Excel公式组合:INDEX+MATCH。

    INDEX函数的输入是一列数据和一个下标值,输出是该列数据在该下标处的取值,类比到编程语言中,可以看做是数组取下标的操作。

    MATCH函数的输入是一个待查找数值和一列数据(还有第三个参数是匹配类型,通常选择0表示精确匹配),输出是该待查找数值在该列数据中的下标值。看上去,它的功能似乎和INDEX正好相反,一个是给下标找数值,一个是给数值找下标。

    将字符串转换到编码的过程,其实可以看做是这样一系列步骤:

  • 取出字符串中的一个字符

  • 找到该字符在字符集中的位置

  • 将对应位置处的编码拼接到结果上

    而这一过程可以用下面这个Excel公式来表达:

    仔细看一下这个公式,从大的结构上来说,是INDEX函数内部嵌套一个MATCH函数,从内到外,依次实现上述的三个步骤:

  • 取出字符串中的一个字符:使用MID函数来截取字符串,它的第一个参数是字符串所在的单元格,$B$1的写法是为了在后面使用“自动填充”功能时,这个单元格保持不动(在行号B前面加上一个美元符号$,表示控制行不变;在列号1前面加上的美元符号表示控制列不变,行号和列号前面都加上美元符号说明控制这个单元格不变);第二个参数是字符串从什么位置开始截取,ROW函数返回一个单元格所在的行,ROW(A1)就是1,之所以写成这样是为了自动填充时A1可以自动变成A2,A3……。MID函数的第三个参数是截取的长度,单个字符就是1。

  • 找到该字符在字符集中的位置:这就是MATCH函数的功能,在输入第二个参数时,可以直接用鼠标点击“字符集”这个工作表的A列。

  • 将对应位置处的编码拼接到结果上:“对应位置处的编码”就是INDEX函数的功能,现在MATCH函数给我们提供了一个下标值,根据下标值可以很容易地找到对应的编码。

字符对应的编码

    接下来就是利用Excel的“自动填充”功能,将鼠标选中B2单元格右下角的绿色小方块,当指针变成黑色十字后向下拖动,字符串对应的编码就显示出来了:

    

字符串编码

    下面出现"#N/A"的地方是因为字符串起始位置超出了字符串的长度,所以也就没有编码结果了。这样就实现了从文本到编码的转换。

    为了使用十六进制编辑器中的“字节序列查找”功能,我们可以先用VS code简单处理一下从B2到B10的信息。

    从Excel表格中选中从B2到B10,将它复制到VS code的一个文本文件内,将所有的换行符替换为空格。在正则表达式中,换行符是“\n”(不包括引号):

将换行符替换为空格

    选择全部替换(或者按下Ctrl+Alt+Enter快捷键),这就是空格隔开的字节序列。别忘了在绿宝石ROM中,字符串以FF结尾,因此再添加一个FF字节,就可以复制到HxD内进行查找了。    

    使用HxD打开ROM文件,按下Ctrl+F进行查找,选择第二个选项卡“字节序列”,查找方式为“从头”,点击“全部列出”:

字节序列查找

    搜索结果只有一个:

唯一的搜索结果

    这就是“妙蛙种子”的名称所在的地址了。

    了解专栏究极绿宝石5.3——科普向,什么是金手指(五)的读者们知道,这里的地址003185D3实际上是083185D3,这是因为ROM文件加载到模拟器之后,它所在的内存区域起始地址是08000000,这是一个固定的偏移。

    此时,源代码项目中的符号表文件就起到作用了。打开源代码的符号表文件(pokeemerald.sym.txt),可以看到083185D3位于gSpeciesNames这个名称所在的范围内:

精灵名称地址

    而变量名“gSpeciesNames”表示的含义正好是“种族名称”,它的地址从083185c8开始,到0831977c结束,长度是0x11b4个字节,包含了所有精灵种族的名称。接下来就是在这四千多个字节中(0x11b4的十进制是4532)提取出所有的精灵名称。

文本和编码的相互转换:编码到文本

    这一小节和上一小节做的事正好反过来:给定一个编码序列,找到它对应的文本。还是先从Excel的公式开始。

    格式是这样的:在一行内,每个字符对应的编码从左到右排列,对应一个字符串。

    作为示例,将刚才转换后的“妙蛙种子”英文名对应编码放在第一行:

“妙蛙种子”英文名对应编码

    现在第二行生成每个编码对应的字符,这个过程利用的公式和上一小节非常类似:

    

    这里取出“字符集”这个工作表中的A列和B列时,在列号的前面加上了$符号,这是因为在此处使用“自动填充”功能时,是沿着一行去填充的,不加$符号公式会有错误(具体是什么错误读者可以去尝试一下)。$符号起到的是“固定”作用。

    按行“自动填充”后,结果是每个字符都以单独的方式出现在单元格内:

    

编码对应的字符

    本来最后一步是将这些字符拼接到一起,但是Excel的一些较老的版本并没有方便使用的函数(TEXTJOIN函数在2016版之后才有)。因此,可以把生成的这一行复制到VS Code中去处理。

提取精灵名称列表

    有了上面的功能,我们就可以开始着手提取文本信息列表了。本期专栏以精灵名称列表为例。

    在上面的“文本和编码的相互转换:文本到编码”小节的最后,我们在符号表中找到了“gSpeciesNames”这个表示精灵名称列表的符号,它的地址从083185c8开始,到0831977c结束,长度是0x11b4个字节,在HxD中,可以利用这个信息很方便地将这些字节复制出来。

    在HxD中,打开“编辑——选择范围”,或者按Ctrl+E,出现“选择范围”对话框,将“gSpeciesNames”的起始位置和终止位置(或者长度)填写进去(注意地址00xxxxxx和08xxxxxx之间的转换):

选择范围

    点击“确定”后,就可以复制到VS Code中处理了。

    处理方式分三步:把所有的FF替换为换行符(让每行表示一个精灵名称);把所有的00删除(因为它代表空格);把所有空格替换为制表符,这是为了复制到Excel中处理。这三个操作利用VS Code的查找/替换功能很方便实现。

    这里的替换也有个小技巧。注意到复制过去的数据是由空格分隔开的字节构成的,为了让替换后的数据不包含多余的空格,可以利用正则表达式,具体来说:

    这里使用\s*表示0个或多个空格,这样在替换的时候会将多余的空格一并替换掉。经过替换的效果如下:

替换后的结果

    这样就可以直接复制到Excel表格中处理了。为了留出填写额外信息的空间,我们选择把数据复制到左上角是B2的单元格内,这些数据应该是以“文本”的格式复制到Excel表格中的。

复制到Excel表格中的数据

    从表格范围来看,最右侧的单元格不会超过K列,也就是说精灵名字的长度最多10个字符,这样我们可以将公式放在第L列到第U列,然后利用自动填充功能获得所有的编码结果:

编码结果

    再将编码后的结果复制到VS Code中进行处理,去掉所有的#N/A和制表符,也就是

    精灵名称列表就呈现出来了:

精灵名称列表

精灵名称列表和修改种族金手指的关系

    从网上很多发布金手指的网站来看,修改精灵种族的金手指都需要“精灵代码”。比如想用金手指修改首位精灵为“超梦”,就需要知道“超梦”对应的精灵代码是什么。现在我们有了精灵名称列表,就可以利用Excel生成精灵代码。

    将精灵名称列表复制到Excel表格中,复制时选择左上角为C2单元格,上面留出一行,左边留出两列,添加好对应的说明:

复制精灵名称列表、生成序号

    其中第一列命名为“序号”,序号从0开始,对应的名字是一串问号,也就是0号精灵(游戏中并不存在)。利用自动填充很容易将第一列填写成0,1,2……序列。

    第二列命名为“精灵代码”,精灵代码是长度为4的十六进制数,利用DEC2HEX函数将第一列直接转换过来即可,该函数的第2个参数指定了生成十六进制数的长度,不足的用0补齐:

生成精灵代码

    有个小细节在这里提醒一下,如果在B列写完公式,按下回车后发现并没有出现公式的结果,而是公式本身出现在单元格中,说明这个单元格目前是“文本”格式,在上图的右上角把它改为“常规”格式即可。

    这样,用于金手指的精灵代码就生成完毕了。

关于汉化版本的文本转换

    上面举的例子都是原版绿宝石中的英文文本。如果是汉化版的绿宝石,就需要处理编码到2个字节的汉字。这里最大的问题就是中英文夹杂的文本,给定一串字节,怎么判断哪些字节是单个字节编码到的英文字符,哪些字节是需要两个合并在一起表示一个汉字呢?

    另外,上面的一系列操作虽然已经在很大程度上减少了重复的劳动,但看上去仍然很繁琐,有没有更简单的方法来处理文本呢?

    答案是有的,就是写一个程序去处理文本中可能出现的各种情况,然后按照指定格式进行输出。按照专业的说法,这种程序称做parser,中文翻译可以是“分析程序”“解析器”等等。然而,“怎么编程”不是本系列专栏能够涉及到的内容,感兴趣的读者可以在讨论区进一步交流。

其他的文本

    有了精灵名称列表,其他的文本也可以通过类似的方式找到:

  • 找到一段游戏中出现的文本,进行编码后在ROM中查找,看看附近是不是也是文本格式的数据,将一大块字节序列解码到对应的文本。例如,道具列表、招式列表、特性列表等都可以通过这种方式来生成。

  • 利用符号表,找到文本所在的位置对应的符号名称,这样就可以精确地获取文本列表所在的地址范围。

    还有一类文本是对话类文本,也就是在游戏中和各种NPC对话时出现的文本。它们不是以列表的形式出现,往往散落在ROM中的各处,这些文本的提取需要等到后续专栏,说到“脚本”这个题目的时候再做说明。

    本期专栏涉及到的知识比较杂乱,既有文本编辑器中的多行编辑、正则表达式查找,又有Excel表格里的公式运用、自动填充,还有ROM文件的字节序列查找、范围选择。方法虽然杂乱,目的只有一个:高效地提取ROM信息。对于不希望使用编程(写一个parser)来实现信息提取的读者,作者在本专栏给出一些采用其他工具的方式作为替代,虽然从某种程度上来说,Excel的公式和查找用的正则表达式和编程也相去不远了。

    手机上有个应用(APP)叫做“口袋改版工具”,让它读取ROM文件之后就可以直接获得各种游戏信息。这是一个对用户十分友好的应用程序,但是它屏蔽了很多细节,对改版程度比较大的ROM也无能为力。本系列专栏还是希望尽可能从接近原理的方面来解释这些信息从何而来。

    听作者啰嗦不易,感谢众位读者的支持!


口袋妖怪绿宝石——数据提取与代码分析(1-字符集与文本信息的提取)的评论 (共 条)

分享到微博请遵守国家法律