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

口袋妖怪绿宝石——数据提取与代码分析(B-以漆黑的魅影为例)

2022-12-26 22:32 作者:围巾胖头鱼  | 我要投稿

说在前面:

    在本系列专栏第9期的末尾,作者曾经提到要“拿一个真正的改版绿宝石游戏‘开刀’”,想了又想,最后还是选中了《漆黑的魅影》,这款国内绿宝石改版的业界标杆。

    这一期是之前所有专栏知识的综合运用,包括本系列专栏已有的11篇和另一个系列专栏“究极绿宝石5.3——科普向,什么是金手指”的10篇内容。对于读者来说,巩固专栏知识的最佳方法莫过于“真刀真枪”地把它们应用一回,这也是作者计划一定要拿一个改版来看看的原因;对于作者自己来说,像是一个已经挖开的大坑,终于要在这篇专栏中填平。

    出于对《漆黑的魅影》这款游戏的喜爱,作者在本期专栏的最后会给出一个彩蛋,即使是暂时还不能掌握这篇专栏涉及内容的读者,看了这篇专栏后也不会空手而归!

一代传奇,漆黑的魅影

    《口袋妖怪——漆黑的魅影》是一款基于原版宝可梦绿宝石的改版作品,先来看一下百度上给出的介绍:

《宝可梦漆黑的魅影》是宝可梦系列游戏的改版作品,以角色扮演(RPG)为主,辅以战略和动作游戏。玩家作为一位宝可梦训练家到各地旅行,不断提升自己的战斗能力,最终打败8个道馆馆主和四天王,开启二周目。二周目新增属性道馆多个,游戏性上升。游戏共有三个周目,每周目都有不同的挑战。在原基础上替换了冷门宝可梦为新宝可梦,也新增了一些宝可梦。

    这段介绍平平无奇,不足以反映这款改版在国内宝可梦改版历史上的地位。再引用一个知乎网友芒果冰OL的一段点评,出自“口袋妖怪有哪些优秀的改版?”,网址位于https://www.zhihu.com/question/35373003/answer/1553177114:

……许多初窥改版魅力的年轻人走上了做改版的道路,其中就有后来国内影响力最大的两个改版《漆黑的魅影》的作者——E大(Ebonyphantom)和《永恒之焱》的作者暗夜,当时他们都还只是初中生……

……《漆黑的魅影》整体给人的印象,就是两个字“扎实”。作为国内玩家数量最多的宝可梦改版作品,它在创新与情怀间做了很微妙的平衡。比如游戏的一周目流程与宝石原版有8分相似,却又时不时插叙另一个“世界”的故事,用悬疑的手法牢牢抓住玩家的兴趣,为2周目的全原创地区的后续冒险的超展开做好了铺垫,并且在原创的剧情中也严格遵循着原有世界观的角色与精灵设定。甚至还通过平行世界的设定打通了游戏、漫画和动画的世界观……

……又比如游戏一改386版本神兽遍地走的状态,为每一个神兽都安排了恰当的出现场景和登场演出,很多场景与TV版和剧场版的剧情相吻合,丝毫没有敷衍之感。至于什么引入四、五世代精灵,加入历代登场过的熟悉NPC及相关剧情,新技能,新特性,改善PVP体验和养成节奏的贴心调整更是属于基本操作。从游戏设计的角度去看,《漆黑的魅影》是一个非常精准地把握住玩家“心流”的典范。整体的叙事节奏、角色成长节奏都安排得恰到好处……

……我们无法确切得知《漆黑的魅影》的玩家数量到底有多少,结合贴吧关注数和我自己身边玩过这款游戏的人数,我的估算是绝不少于100万。这对于一个同人游戏来说,已经是堪称奇迹了……

    接触过《漆黑的魅影》的读者对它都有一个内心的评判。在引用了这么多评价之后,相信之前没接触过它的读者也对它有了一个基本的认识。前人之述备矣,作者在这里就不再做更多的评价。

    目前,《漆黑的魅影》最新版本号是5.0EX+,这个版本号下有两个版本,一个是5.0EX+BW,另一个是5.0EX+DP,两个版本只有在初始御三家精灵的选择上有不同,其他内容完全相同。下载地址在百度贴吧的“漆黑的魅影”吧,网址https://tieba.baidu.com/p/7842128959。

    根据百度贴吧的帖子,这个最新版发布于2016年12月24日。时隔这么多年,在“漆黑的魅影”吧中仍然活跃着诸多玩家的身影,但游戏本身应该不会有任何进一步的更新了。因此,可以认为这款改版游戏已经完结,这也是作者选择这款改版的原因之一。

    本期专栏以漆黑的魅影5.0EX+BW为例,运用专栏中的知识,对这个改版游戏进行一次“数据提取与代码分析”。

我们知道什么?我们不知道什么?

    面对一个改版游戏,在进行“数据提取和代码分析”之前,先来问问自己,我们知道什么?我们不知道什么?

    我们知道《漆黑的魅影》是基于原版绿宝石的改版游戏,因此《漆黑的魅影》中应该复用了原版绿宝石大量的函数。想象一下,应该是有些函数和原版一模一样,有些函数做了很小的改动,还有些函数改动较大,或者说根本没有复用,而是重写了。这些和原版一模一样或者改动很小的函数,给我们进行分析提供了机会,这种现象被称作函数相似性

    《漆黑的魅影》中每个函数的地址,和原版还一样吗?这一点我们是不知道的,可能一样,也可能不一样,取决于改版和原版相比的改动究竟有多大。这也就是说,在前面的专栏中,那个无比好用的符号表现在派不上用场了。在之前,我们可以根据一个函数的名字直接定位到它在原版中的地址,或者反过来,根据一个地址直接在符号表中找到它对应的是哪个函数;而现在面对改版,我们只能从函数相似性上入手,再也不能断言哪个地址就一定对应哪个函数了。

    再次强调,面对改版的绿宝石,我们不能完全相信符号表中地址和变量/函数的对应关系!

关键变量与关键函数

    “数据提取与代码分析”是比较抽象的说法,将这个任务具体一些,我们其实是想找到游戏中的一些关键变量和关键函数,“找到”的含义是确切地知道它们的地址。作者总结了一个关键变量和关键函数的表格如下,这些变量或者函数在之前的专栏中都出现过,这里再做个总结说明它们如何“关键”:

    上面这个表格能涵盖大部分我们感兴趣的游戏数据以及各类金手指涉及的地址,接下来作者就要逐一去在改版游戏中找到它们的具体地址。在原版中这个过程毫无难度,只需对照符号表去查找即可。但是,面对改版我们不得不扔掉“符号表”这根拐棍,在变量和函数的汪洋大海中去做“大海捞针”一样的事。

    “大海捞针”也并非毫无章法地乱找,而是基于函数相似性,从最简单、最容易找到的函数或者变量开始,一点一点地扩大战果,直到找到目标。

静态分析:从主函数开始

    在本系列第5期专栏口袋妖怪绿宝石——数据提取与代码分析(5-THUMB汇编指令基础)中的“改版ROM的主程序在哪儿”一节中,提到ROM的起始地址08000000一定对应着一条ARM模式的跳转语句,跳转之后的函数会在其中的一行进入主函数,现在来看看《漆黑的魅影》是否也是如此。

    使用VBA模拟器的“工具——反汇编”功能,在加载ROM后进入反汇编的界面:

找到主函数

    此时还需要用ARM模式进行查看,果然找到了主函数AgbMain的地址080003a5,跳转到此处:

    

AgbMain的反汇编代码

    进入主函数后,就需要切换到THUMB模式了,并且以后所有查看的汇编代码都是THUMB模式。和源代码项目中的主函数对比,相似度非常高,甚至可以说就是一模一样的,在这里需要找到C代码中的InitMainCallbacks函数,它位于源代码项目的第104行。注意,从源代码的第97行开始了连续的函数调用,对应到汇编代码中,则是连续的bl指令,这就是函数相似性最直观的体现。

    连续的函数调用到源代码的第112行停止,因为InitHeap是一个需要参数的函数,为了准备好参数,汇编代码中就不能再使用bl指令了(080003f6处终于暂停了长达12行的连续bl函数调用)。两相对比,可以找到InitMainCallbacks函数的地址,是图中标出的080004d8。

    接下来跳转到这个函数查看:

InitMainCallbacks函数

    第一个关键变量——gSaveBlock2Ptr——被找到了(它在03005D90)!根据第六期专栏口袋妖怪绿宝石——数据提取与代码分析(6-反作弊机制:跳动的指针),它保存了一个会随着时间变化的指针地址gSaveBlock2,这是由反作弊机制决定的。gSaveBlock2中包含了主角的名字和ID信息,如果有读者想通过专栏究极绿宝石5.3——科普向,什么是金手指(三)提到的方法,利用VBA模拟器的“查找金手指”功能去搜索主角的ID,会发现在《漆黑的魅影》中搜不到结果,这是因为“跳动的指针”反作弊机制会让主角ID的地址不断地变化。

    现在有了gSaveBlock2Ptr的地址,如果想要修改主角的名字或者ID,就有了制作金手指的基础。思路有两种,一种是继续寻找,找到“跳动的指针”反作弊机制对应的函数,用修改ROM的金手指破坏反作弊机制;另一种思路是下面提到的,不需要修改ROM。

利用gSaveBlock2Ptr修改主角的名字——中文字库

    《漆黑的魅影》是一个汉化的改版,使用的语言是简体中文,这和原版绿宝石有很大的区别。在本系列专栏第1期口袋妖怪绿宝石——数据提取与代码分析(1-字符集与文本信息的提取)提到了字符集的概念,是原版绿宝石对用数字(0/1序列)来编码字符的映射表。汉化的改版需要对中文字符进行编码,这种编码的对应关系和中文字符对应的图片数据合起来称为中文字库

    导入中文字库是对原版游戏或者对国外改版游戏进行汉化的第一步。本系列专栏对如何导入中文字库不做讨论,但是会关心中文字符的编码。由于导入中文字库的工具和中文字库本身相对固定,因此可以说几乎所有的精灵宝可梦GBA汉化版本用的都是同样的中文编码,这一点《漆黑的魅影》并不例外。

    原版绿宝石的字符编码位于源代码项目的charmap.txt文件中,这一点在第1期专栏也提到过。但是中文编码需要到网上去找,为了方便读者,作者把这个中文编码的文件放在网盘上:

https://pan.baidu.com/s/1_kp5X6lGk8kNxQM1YeuIZw

    密码是ANSI。这里面是一个charANSI.txt的文件,内容片段如下:

中文编码

    文件中包含了中文字库中所有中文字符的编码(中文字库中的汉字为常用汉字,约有7000个),例如上图中的“啊”在《漆黑的魅影》中会被编码到01 00共2个字节的数据(按十六进制来看)。如果按照小端序进行拼接,“啊”的编码就是0001。

    回到《漆黑的魅影》,这款改版尽管是汉化的版本,却没有任何地方支持输入中文。在开始新游戏时,主角会被问到“你叫什么名字”,但是可供输入的字符和原版一样,还是只有大小写字母、数字和英文标点符号:

起名字界面

    有的改版会在某个界面支持L/R键翻页,翻页后会出现输入汉字的选项,但遗憾的是《漆黑的魅影》并没有实现这个功能。此时,要想起一个中文名字,只能在输入名字的界面不输入任何字符而直接选“OK”,系统会为主角从若干个中文名中随机选出一个,例如“龙”“玉”“卡莲”等等。那么怎样才能自己起一个中文名呢?

    利用刚刚找到的gSaveBlock2Ptr变量,就可以实现一个改名字的金手指!

    如果是某种原始代码格式的金手指,那么格式应该是“变量地址:变量取值”的样子,但是由于“跳动的指针”反作弊机制,这里的变量地址是不断变化的,我们只知道这个不断变化的地址它的存储地址是固定的,也就是gSaveBlock2Ptr变量的地址03005D90。既然原始代码实现不了这个功能,那就去找功能更强大的GS V3格式码。

    查看GS V3格式码的文档,可以找到Type 21这个代码类型:

GS V3代码,Type 21代码类型

    按照这种代码的说明,它是将半字ZZZZ写入这样一个地址,这个地址由两部分相加得到,第一个部分是XXXXXXXX地址中保存的数值,第二个部分是YYYY乘2的结果。

    这里面,“XXXXXXXX地址中保存的数值”恰好就是“取出gSaveBlock2Ptr变量的内容”这个操作。换句话说,gSaveBlock2Ptr变量的内容是随时间变化的,但是Type 21这个类型的金手指并不关心它的内容如何变化,只要给出它存储的地址就够了。

    我们想要改的主角名字在gSaveBlock2Ptr的什么位置呢?可以看一下源代码项目中gSaveBlock2的类型Saveblock2的定义:

SaveBlock2

    如果《漆黑的魅影》没有修改这个结构体的定义的话,那么主角名字playerName应该位于gSaveBlock2相对地址0x00处,也就是最开始的地方。下面就要根据究极绿宝石5.3——科普向,什么是金手指(九)中对GS V3格式码的介绍,来组装修改名字的金手指了。

    注:GS V3格式码的文档中的Type 21类型介绍里,原始代码向中间代码转换的规则有印刷错误(图中的02024EA4 -> 4224EA4应为02024EA4 -> 42224EA4),有一定的误导性,但是在Type 20类型中并没有错误,可以作为参考进行修正。

    比如说我们想要把主角的名字改为“冒险家”,先查阅中文字库找到“冒险家”这三个字的编码,分别为:

    把主角名字的第一个字设置为“冒”,对应的中间代码应该是:

    这里,42305D90是将03005D90(gSaveBlock2Ptr的地址)按照Type 21的转换规则进行转换的结果,由于主角名字正好位于gSaveBlock2的开头,因此YYYY的部分对应的就是0000,而ZZZZ的部分需要小端序拼接。

    把主角名字的第二个字设置为“险”,对应的中间代码应该是:

    和第一条相比,YYYY变成了0001,这是为了它在乘2之后变为0002,这正好是第二个字相对于gSaveBlock2开头的地址(注意一个汉字是2个字节)。第三条以此类推。最后用AR Crypt工具进行加密,得到GS V3格式码:

加密

    将代码输入到VBA模拟器中,就可以修改名称了:

修改名称之后的训练师卡片

静态分析——函数调用链

    总结一下刚才找到gSaveBlock2Ptr的过程,可以用如下方式表示:

    含义是最开始从AgbMain出发,根据函数调用逐层深入,“->”箭头表示上一行的函数调用了这一行的函数,或者使用了这一行的变量。在汇编代码和源代码项目的对比之中,就可以找到关键函数或者关键变量的地址,这一系列通过函数调用关系联系起来的函数或者变量被称作函数调用链

    下面再给出一个函数调用链,然后对照着去看汇编代码,理解一下利用函数调用链查找关键函数或者关键变量的方法。

    从“静态分析:从主函数开始”这一小节中,我们已经分析到了InitMainCallbacks函数,并找到了CB2_InitCopyrightScreenAfterBootup函数的地址0816CEAD,所以分析从这一步继续:

CB2_InitCopyrightScreenAfterBootup函数

    仍然需要利用函数相似性迅速定位关键函数的地址,这个过程正如本系列第5期专栏提到的那样,考验的是读者对汇编代码的熟练程度。现在我们找到了函数调用链的下一个函数SetSaveBlocksPointers:

SetSaveBlocksPointers函数

    这样就来到了函数调用链的最后一环,找到了关键变量gSaveblock1Ptr。除此之外,这个函数中又出现了gSaveblock2Ptr,可以和之前找到的进行对比,两下验证二者一样,我们找到的结果就更加可信了。

    上面给出的两个函数调用链都是从AgbMain这个主函数开始的,其实当知道了其他函数之后,函数调用链也可以从某个已知的函数出发。我们像是在一片未知丛林中探索地图的探险家,最开始只有“主函数”这一个“据点”,但随着函数调用链的延伸,我们知道的函数越来越多,找到的关键变量、关键函数也越来越多,渐渐地整个“地图”就会被探索出来。

    接下来再给出几个关键变量的函数调用链,具体的分析过程就交给读者练习了:

    gPlayerParty的函数调用链:

    gBagPockets的函数调用链:

动态分析——伞兵天降

    查看VBA的反汇编窗口,和源代码项目进行比对,这个过程是静态分析的过程。从函数调用链的视角来看,我们只能从最开始的AgbMain函数开始逐渐地扩大探索范围。如果说,这种方式就像步兵一点一点地摸排过去,把已知的据点不断扩张出去,那么另一种方式——动态分析,则像是伞兵一样直接降落在某片未知区域,并以此为据点向周围扩展探索范围。

    动态分析需要的是No$GBA提供的调试功能,部分功能已经在专栏究极绿宝石5.3——科普向,什么是金手指(十)介绍过,下面介绍它在探索关键变量和关键函数中的作用。

    打开No$GBA模拟器,加载《漆黑的魅影》ROM,我们设置这样一个断点:

Debug->Define Break/Condition功能

    这个断点的含义是当gSaveBlock2Ptr的内容被写入时触发断点。我们知道根据“跳动的指针”反作弊机制,gSaveBlock2Ptr中存储的内容会随时间不断变化,那么它的内容被写入时说明了什么?说明触发断点时恰好就是这个反作弊机制正在执行中!

    回到游戏,让主角进入一个房间,此时触发了断点:

    

断点被触发

    断点被触发时,No$GBA程序会从游戏界面自动切换回代码界面并暂停游戏。代码界面中,被选中的一行是将要执行但还未执行的代码,它的上一行则是断点触发时执行的最后一条代码,也正是这条08076BF6处的代码str r0, [r1]执行了对gSaveBlock2Ptr的写入。仔细观察一下现在代码所处的位置,它恰好位于SetSaveBlocksPointers函数内。根据第6期专栏的分析,这个函数就是执行“跳动的指针”反作弊机制的函数,这个断点的触发符合预期。

    但是SetSaveBlocksPointers这个函数在静态分析时我们就已经找到了,动态分析能找到新的内容吗?不要着急,接下来才是动态分析大显身手的地方。

    按一下F8,或者点击菜单栏里的Run->Run to sub-return,它的含义是跳出当前函数,回到这个函数的调用函数中,现在来看代码的位置:

    

跳出

    可以看到,此时代码停在了08076C90这个位置,刚刚执行完的上一条代码是bl Lxx_8076BDC,恰好就是调用SetSaveBlocksPointers函数的代码。也就是说,F8这个功能将SetSaveBlocksPointers函数剩余的部分执行完,跳出了这个函数,回到了它的“外面”,也就是调用它的地方。那是谁调用了它呢?

    根据同样在原版绿宝石中的实验,此时应该是位于MoveSaveBlocks_ResetHeap函数内。和源代码项目进行比对,这样我们就找到了一个新的关键函数。

    这个过程可以用下面这个函数调用链表示:

    相对于静态分析的函数调用链,动态分析的函数调用链需要说明更多的条件:

  1. 需要说明触发断点之前要做什么样的准备,例如这里需要游戏中主角位于房间的外面

  2. 需要说明什么样的操作能触发断点,例如这里需要游戏中主角走入一个房间

  3. 需要说明打什么样的断点,例如这里用了一个变量的写入断点

  4. 注意箭头的方向,静态分析中箭头->指的是调用,而这里的<-指的是被调用

    下面再给出一个例子:

    这个例子是要找到对队伍首位精灵进行数据加密、数据打乱、校验和等反作弊机制的关键函数,触发断点前需要游戏中进行到选择初始御三家的界面,在确认选择某个御三家时触发断点,而断点设计为向队伍首位精灵所在的地址进行写入操作。

断点的准备工作

    触发断点后,按照函数调用链,此时应该位于memset函数内,按一次F8跳出到CopyMon函数,再按一次F8跳出到GiveMonToPlayer函数。注意,下面的一行,箭头方向发生了变化,意味着从GiveMonToPlayer开始进入静态分析的流程,去比对源代码工程找到GiveMonToPlayer调用的函数SetMonData,并一直向下找到反作弊机制的几个关键函数。

    最后,把几个关键变量和关键函数的函数调用链放在下面。

    gSpeciesNames的函数调用链:

    sGlobalScriptContext的函数调用链

    PlayerNotOnBikeMoving的函数调用链

    StringCopy的函数调用链

   gMoveNames的函数调用链

    gBattleMoves的函数调用链

    gItems的函数调用链

    gSpeciesInfo的函数调用链

    至此,所有关键变量和关键函数的函数调用链都给出了。

数据提取

    提取出来这些关键变量和关键函数,就可以按照专栏第2期口袋妖怪绿宝石——数据提取与代码分析(2-基于名称列表的详情信息提取)的方法,提取一些详情信息,例如可以用gSpeciesNames和gSpeciesInfo做出下面这张表:

部分的精灵详情列表

    同理还有技能详情列表、道具详情列表等等。

    上面的“曾用名”这一列是《漆黑的魅影》里使用的精灵名称,而“名称”这一列是神奇宝贝百科上的官译名称。由于《漆黑的魅影》开发的时间较早,许多名称在当时还没有统一的官方译名,这个历史遗留问题被一直留到了今天,因此在“漆黑的魅影”吧里讨论的吧友通常都执行两套“命名系统”,一套是《漆黑的魅影》游戏中的命名,另一套是神奇宝贝百科的名称,而本期专栏要给出的彩蛋就和这件事有关。

彩蛋——官译名称修正的《漆黑的魅影5.0EX+》

    由于有了上面的“曾用名”和“名称”列,作者就有能力对《漆黑的魅影》里出现的精灵名称、技能名称、特性名称等进行修改,将它们和神奇宝贝百科的名字进行统一。当然,《漆黑的魅影》中有一些自创的技能,它们的名称在神奇宝贝百科中没有对应,也就没有修改。

    除了修改名称之外,作者没有修改其他任何地方!因此这只能算是一个重新汉化的版本,也可以称作官译修正版。效果如下:

注意一些名称上的变化

    链接如下,BW和DP各有一份!

https://pan.baidu.com/s/15z0kZO31uk6uGuk6nuymOQ

    密码是“漆黑魅影”4个字的拼音首字母,全部大写。

说在后面:

    本期专栏使用静态分析和动态分析方法,对一个改版的绿宝石游戏——《漆黑的魅影》——进行了一次探索。至此,本系列专栏的内容应该算是全部结束了。

    终于赶在2023年之前完成了这个系列专栏的内容,结束语在第9期曾经说过,这里就不再重复,有什么问题还是评论区见吧!

口袋妖怪绿宝石——数据提取与代码分析(B-以漆黑的魅影为例)的评论 (共 条)

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