关于《火焰纹章:晓之女神》的乱数生成规律的初步研究 – 作者:minjiang2011

0x00缘由

本人是火焰纹章、英雄无敌等战棋类游戏的业余玩家,虽然技术一般,但是乐在其中,玩过GBA三作,但是后来由于工作繁忙,一直没有时间体验最新作品,闲暇之余准备把一些经典拿出来体验一下,于是就开始了苍炎和晓女之行(当然是模拟器玩家),玩火纹这种战棋类游戏免不了使用S/L大法来避免全军覆没或者练出个奇葩,但是运气差的时候升级有可能一个点都没有,运气好的时候点数又会全满,不断读档凸点随机性太大而且很耗费时间,强迫症犯了就想如何能不用修改器让升级点数自然最大化(奇怪的症结)。当我体验了苍炎之后,发现同一个即时存档升级的时候点数总是一定的,因此也萌生了找到苍/晓的升级算法,并写一款可以预测升级点数工具的想法。

PS:本文仅用于技术讨论与思路拓展,我们尊重原作者的知识产权,如果您喜欢《火焰纹章》系列,请购买正版。

经过网上一番折腾,发现目前有这么些资源/提示:1.属性修改器很多,苍炎本身也自带金手指作弊码,但晓女没有。2.大神指出火焰纹章系列游戏的如必杀、升级、双击等随机事件主要由其中的乱数机制控制,GBA的乱数也有大神已经研究出是通过乱数表查询来获取。改变乱数的方法在各作都不一样,从论坛上以往公布的方法有的是通过查看属性页,有的是通过看路径,有的是通过斜向瞄准等等。

为达到目的我们主要解决三个问题:1.逆向乱数生成算法。2.升级点数计算方法。3.编写自动化预测升级点数。

主要工具:

1.Dolphin5.0-6304(主要是自己电脑上就是这个版本,新的应该也可以)

2.IDA/GHIDRA(静态反汇编工具,推荐GHIDRA,因为可以反编译POWERPC指令,有IDA的POWERPC插件的当我没说)

3.cheat engine 6.7 (最新的我也没用过,应该没啥区别)

4.带HEX功能的文本编辑器(UE、010都可以)

0x01前置知识

WII是任天堂2006年推出的家用游戏机,其CPU是POWERPC,因此其游戏的系统指令集也是基于POWERPC的。晓女是2007年任天堂在WII上开发的一款游戏,是火焰纹章的系列作,可以构卖WII主机和游戏进行体验,也可以在网上下载晓女的镜像,通过跨平台模拟器dolphin完美模拟,PC版模拟器可以对游戏进行实时调试,这就给我们分析这款游戏带来了方便。

0x02 内存定位

DOLPHIN在载入游戏镜像后,会申请一块内存用于模拟游戏内存,此内存有游戏的主要代码,其地址在每次游戏启动的时候随机申请,然后通过模拟机制模拟WII的系统运行,游戏内存运行的地址通过dolphin调试模式可以看到其模拟的地址始终从0x80000000开始。可以通过模拟器调试模式的内存->转储主内存把当前的主要内存保存为文件,位置在:C:\Users\你的账户名\Documents\Dolphin Emulator\Dump\ram.raw

图片1.png旧版的32位dolphin申请的模拟器地址基本是在7FFFFFFF-80000000区间,而新版取消了以往的直接申请模式,地址随机变化,因此我们首先第一步就是要把游戏的模拟内存找到,这样便于动静态分析。目前网上没有一个统一的办法可以直接找到,这里我通过多次尝试找到一个比较稳定的查找映射内存地址的办法,就是用CE的内存区域查看,寻找dolphin.exe的内存结束地址,往下的第一个大小为0x2000000的MAPEPED内存地址就是目标内存地址。

图片2.png从截图可以看到目标内存地址起始为RFEJ01正是模拟器的内存起始内容。这样我们就找到了模拟器ROM在内存中的地址。

图片3.png第二步是找到合适的调试和监控工具,dolphin用于动态跟踪代码很有用,但是有个缺点就是功能单一,断点只有位置和内存断点,可以查看内存,但是dolphin的调试界面很不友好,在游戏的时候就没法监视,我们这里使用CE来定位和监控内存变化。但是CE中的内存地址空间和dolphin不同,在查找代码的时候很不方便,这里我找了一个网上大神的插件,可以修改CE在内存中部分内存的映射地址,也就是可以把CE监控的目标程序的内存地址空间映射到指定起始内存地址。 图片4.png我们使用这个工具将前面找到的ROM内存地址重新定位为0x80000000,这样CE中的内存地址就和实际地址一致了。

图片5.png

0x03 寻找乱数地址

火焰纹章游戏里的各类操作如攻击、升级、必杀、双倍攻击等特效的触发都是由一个随机数来判定的,但是这个随机不是真随机,而是自己定义的一套随机算法,以往火纹的随机数是通过查询一张或几张乱数表,通过消耗乱数的方式来达成随机效果,网上也大概查了一下,苍炎可以通过查看人物属性改变乱数,晓女通过斜向瞄准修改。为了验证这个乱数的随机性,我通过使用Dolphin的即时存档来测试:先保存一个即时存档通过前面的乱数变化的方式,如查看几次属性页,斜向瞄准几次,通过一次攻击击杀目标获得经验值升级,检测升级加的属性点数和类型来判断这个随机数的随机性。通过多次试验,我发现同一个即时存档在执行相同操作的时候,如查看几次属性页,瞄准几次之后,升级点数和类型都是一定的,甚至必杀和双倍攻击等随机事件都是完全按照顺序发生,因此我猜测火纹的随机与时间无关,只与指定操作有关,这也为我们可以预测升级点数提供了可能。

下面就开始查找这个随机数的地址。这里我们利用CE的变化值和非变化值的监控来达到目的,首先使用CE监控dolphin进程,并按照上述方法将ROM内存找到并重定位,利用未知初始值->变化的值->不变的值->变化的值…反复查找,最后找到有如下地址是可疑的

图片6.png我们把内存定位到804A7F4C,然后切换到游戏中,用远程攻击斜向瞄准目标,可以看到从804a7f50开始的6个连续字节是固定变化的。

图片7.png

通过多次测试(瞄准、升级、攻击等可以改变乱数的操作)查看字节变化,可以确定这六个字节就是关键乱数字节。

0x04 随机字节规律分析

现在已经找到乱数字节位置,接下来我们分析下这6个字节的变化规律,通过多次瞄准打乱随机值,可以看到其变化是有规律的,有2个字节总是会在一次打乱随机值后从6个字节序列末转移到序列头,因此,可以假设这六个字节是通过计算得出。

图片8.png为验证这个想法,我把6个字节手动修改为0,再通过游戏中的操作来改变,可以看到在本关卡的同一地图中,无论之后进行任何操作,六个字节始终为0。在游戏中体现为:1.升级点数全满,2.部分人物必杀率大大提高。我修改了之后随便测了两个人物升级的情况。

图片9.png图片10.png

因此,可以判断这六个字节就是关键乱数字节,其计算方式不是通过查表,而是通过相互计算得出的!这就给我们预测乱数地址提供了可能,只要把其计算方法分析出,就可以计算出N次循环后的乱数值。由于这六个字节影响游戏中的一切随机行为(升级、攻击必杀等)。因此,苍炎/晓女只要知道了某个时刻的乱数值,可以通过算法将后面行动中所有的出现情况预测出来。当然,如果只想每个人物每次升级都全满,研究到这里就可以结束了,但我们的目标是研究正常情况下如何预测其升级点数,所以需要进一步挖掘这六个字节的生成规律。

0x05 算法分析

为找到这六个字节的算法,我们就要找到这六个字节是如何生成的,还好Dolphin提供了内存断点功能,我们把这六个字节下内存写入断点,然后根据堆栈查看调用,就很容易找到调用函数。

图片11.png

这里可以看到内存窗口中地址和CE的地址内容是一致的,但是不能在CE的内存中直接下断点,因为CE里的代码都是基于INTELCPU的汇编,而模拟器里面可以直接显示POWERPC汇编,在模拟器的内存中下断点可以直接定位到powerpc指令。

通过跟踪,可以发现乱数是以2个字节为单位,6个字节是3个单位,先通过当前乱数计算新的1单位乱数,然后将当前乱数右移1个单位,用新的乱数单位替换空缺,完成1次循环。乱数循环均与参数无关,只与当前3个单位乱数值有关。

下面就是我找到的一些功能函数,当然,我只想预测升级时候的点数,因此其他很多计算必杀相关、双击等随机事件的函数我都没去找,有兴趣的可以进一步分析。因为升级的时候大部分是在战场上,最多的时候就是击杀敌人获得经验升级。我主要找的就是这方面功能的函数,可以先下内存断点,再在游戏中执行操作,会自动定位到功能函数。

进攻的时候会出现乱数计算,有必要将进攻类型全面分析。

80084e68  攻击一次乱数增加计算函数,乱数前进2次

80084de8 平时的计算函数,乱数前进1次

80084d08 用斜线瞄准计算乱数的函数

其中为实现自动化分析,需要将乱数计算的算法逆向,可以使用GHIDRA辅助理解。这是80084de8的POWERPC代码以及伪代码。

图片12.png

根据你想用的编程语言来确定代码,为便于在下步使用CE生成辅助工具,我这里是用LUA来复现这个函数,核心代码如下:

r5 = bShr(orgr0 , 5)

r4 = bAnd(bShl(orgr4 , 1) , 0xffff)

r0 = bAnd(orgr6, 0x8000)

r6 = bShl(bAnd(orgr6,0x1ffff),0xb)

r5 = r6 + r5

r6 = bAnd(r5 , 0x0000ffff)

r5 = orgr6

if bAnd(r5 , 0x8000) == 0x8000 then

 r0 = r4 +1

 else

 r0 = r4

end

r6 = bXor(r6,r0)

NextNum = bAnd(bShr(CalcNumV,16) ,0xffffffff) + bAnd(bShl(ReverseNum(r6),32),0xffff00000000)

r0 = bAnd(r6,0xffff) * 0x64

r0 = bShr(r0 , 0x10)

r0 = bXor(r0 , PersonData[index])

--print (string.format("r0=0x%x,r6=0x%x",r0,r6))

r4 = bShr(r0 , 1)

r0 = bAnd(r0 , PersonData[index])

CalcNumV为执行函数当前的乱数值,index为当前人物升级属性索引,8个属性,升级第几个index就是几。NextNum就是经过此次循环之后的3个单位乱数值。这样就可以随时计算经过多少次循环后乱数值是多少。可以看到,苍晓的乱数的生成算法还是比较简单的,主要是通过位运算来计算的。

0x06 点数计算

解决了乱数生成的问题,最后需要解决的就是在乱数升级函数执行前,攻击行为对乱数的影响。晓女的进攻时乱数的计算与进攻对象种类、攻击方式、能否反击等因素有关,经过多次测试,攻击对象主要分为:近战、法师、弓箭手、医疗类;攻击模式主要分为以下几种类型:直线和斜线,加上对双击的判定,将这些因素进行组合,就可以覆盖火纹中大部分的攻击模式。

如果此次进攻后经验满足升级条件,就会进入升级点数计算函数,此函数调用8次80084DE8,每次调用的时候,该人物的属性成长值就作为参数进行计算,根据返回值判断是否升级此属性。

0x07 自动化计算辅助工具

理想中的完全自动化是直接HOOK Dolphin的OPENGL显示,动态显示当前的乱数值,以及根据当前选择的人物瞄准攻击的目标,然后自动计算出改变乱数循环多少次升级属性的点数,由于对Dolphin代码研究不多加上自己懒,就只通过CE的Trainer实现了一个简化版的,通过确定此次攻击之后某个人物要升级,工具预测乱数值循环次数对升级属性的影响。

其主要流程为:获取当前乱数值->选择当前攻击模式(近战1X1,还是法师远程,还是弓箭手,以及是否反击和双击)->计算N次乱数循环后的升级值。

为便于使用,我采用斜向瞄准/取消瞄准乱数循环的方法,也就是通过远程攻击斜向瞄准的方式来改变乱数,一次斜向瞄准为1次计算,也就是在辅助工具中表现为一条计算结果。有以下几点注意的地方:1.标准情况的1次计算是射程为2的兵种(法师,弓箭手、装备攻击距离为2的近战兵种都可以)以斜向的方式瞄准一次敌方不能造成反击(射程小于等于1)的目标。如下左图这种情况。如果是能够反击(射程大于等于2)的敌方目标,计算次数翻倍。如法师瞄准手斧(下右图)。X2是双倍攻击的意思,不影响斜向瞄准乱数次数。注意:如果斜向瞄准的攻击路径上有障碍物,那此斜向瞄准不会修改乱数。

图片13.png图片14.png

使用方法:先启动Dolphin正常开始模拟游戏,然后打开辅助工具,等“初始化”按钮可以点击后,点击初始化,状态变为绿色,人物和计算按钮也都可以按了,这样就可以开始使用了。

如果某个人物即将在下次攻击之后升级,先保存下状态,之后使用这个人物去攻击,记住攻击者和目标反击的状态,比如我这个例子萨扎是两次攻击,目标重甲士兵一次反击但是MISS了,就可以在辅助工具中先选择人物,再依次选择近战、能反击、直线、双击、miss5个选项,下面自动计算出需要8次循环,点击“计算”,左边的文本框就会出现循环次数与升级点数的关联,为方便使用,我以远程一次斜向瞄准为1次计算,列出了60次瞄准每次的升级情况,每个属性能升级为1,反之为0。

图片15.png可以看到如果不瞄准,应该是第4第5也就是技、速升级。斜向瞄准10次之后可以达到较大的升级点数6个,与实际符合,因此,只需要选择你想要升级的属性点情况对应的瞄准次数,用一个远程兵去瞄准/取消瞄准一个不能反击的单位多少次就可以了。我内置计算60次,如果次数较大,可以瞄准远程单位来双倍计算次数。(瞄准一次远程单位循环次数为2)。如果远程兵都行动完了无法远程瞄准,正好有个要升级怎么办?我在网上找到有人发现通过切换“指南”,观看教程的方法可以改变乱数,也可以使用这个办法,但是这种方法有个缺点就是乱数一直在变,很难精确控制,只能看一下,暂停,用工具计算当前乱数对升级的影响也就是看瞄准为0的升级点,找到一个你满意的就退出,开始攻击升级,否则继续观看。

图片16.png图片17.png再举个远程攻击的例子,吉尔用手斧远程攻击一个目标是1次攻击,因此选择近战、不能反击、斜线、不双击、不miss,单次攻击循环次数就是8,计算情况如下:

图片18.png直接攻击升级就是5个点,如果瞄准6次就可以升级7个点!

图片19.png图片20.png

升级点数以及属性与计算的一致,这样就验证了我们的算法是没有问题的。这个工具只是一个简单的辅助,对很多情况(如,攻击时是否miss的判定函数、超远程距离攻击我都没有去跟踪)并没有深入研究,有兴趣的可以继续完善。

一点小问题:用Dolphin调试的时候有个问题一直没有解决,就是没有找到WII/NGC匹配的符号,即使是简单的内存函数的符号也不知道在哪,很多基本的功能函数无法解析,这就给我们看汇编代码带来很大麻烦,希望知道的大神可以介绍一下。当然,即使这样,也可以完成算法的分析,只是要稍微耗时间一点。

结语:这只是我对火焰纹章的初步研究,其中也有对Dolphin模拟器在模拟时候的一些使用心得,有兴趣的可以相互交流。

*本文作者:minjiang2011,本文属 FreeBuf 原创奖励计划,未经许可禁止转载

来源:freebuf.com 2019-08-08 10:40:21 by: minjiang2011

© 版权声明
THE END
喜欢就支持一下吧
点赞0
分享
评论 抢沙发

请登录后发表评论