在上一章中,我们搜索到了"恐龙新世纪"模拟游戏中1号机台玩家血量的地址,那么,这个地址在对该游戏的逆向工程中可以起到什么作用呢?我们可以考虑,玩家血量的变动有多少种情况:
1、被敌人攻击
- 得到敌人某个技能的攻击力,自身的防御力。
2、被队友攻击
- 得到队友的攻击力。
3、被道具击中
- 得到道具的攻击力。
4、使用扣血技能
- 得到使用技能扣除的血量值。
5、拾取回血道具
- 得到某个回血道具回复的血量值。
也许有更多的情况,这里只列出常见的几种情况。
那么,我们需要MAME的中断功能来得到这些我们想要得到的数据。
一、中断是什么
中断的意思是在某个条件下,中断程序的运行,这个条件可以是多种多样的,比如说最基本的。程序运行到某行时中断。但我们并不知道程序在哪一行改变玩家血量的值,所以,我们利用另一种中断来找到程序在哪一行对玩家血量的值进行改变。
二、MAME的中断指令
MAME的中断指令有两种:
1、断点指令
bpset
bp[set] <address>[:<CPU>][,<condition>[,<action>]]
在指定的<address>处设置新的执行断点。 <address> 后面可以选择跟随一个冒号和一个标记或调试器 CPU 编号,以便为特定 CPU 设置断点。 如果未指定 CPU,则将为调试器中当前可见的 CPU 设置断点。
可选的 <condition> 参数允许您指定每次命中断点地址时将计算的表达式。 如果表达式的结果为真(非零),断点将停止执行; 否则,执行将继续而不通知。 可选的 <action> 参数提供每当遇到断点并且 <condition> 为 true 时要执行的命令。 请注意,需要用大括号 { } 将操作括起来,以确保命令中的逗号和分号不会在 bpset 命令本身的上下文中进行解释。
设置的每个断点都分配有一个数字索引,可用于在其他断点命令中引用它。 断点索引在整个会话中是唯一的。
bpclear
bpclear [<bpnum>[,…]]
清除断点。 如果指定<bpnum>,则引用的断点将被清除。 如果未指定<bpnum>,则所有断点将被清除。
bpdisable
bpdisable [<bpnum>[,…]]
禁用断点。 如果指定了<bpnum>,则引用的断点将被禁用。 如果未指定<bpnum>,则所有断点将被禁用。
请注意,禁用断点并不会删除它,它只是暂时将断点标记为非活动状态。 禁用的断点不会导致执行停止,不会评估其关联的条件表达式,也不会执行其关联的命令。
bpenable
bpenable [<bpnum>[,…]]
启用断点。 如果指定了 <bpnum>,则将启用所引用的断点。 如果未指定<bpnum>,则将启用所有断点。
bplist
bplist [<CPU>]
列出当前断点及其索引以及任何相关条件或操作。 如果没有指定<CPU>,则会列出系统中所有CPU的断点; 如果指定了 <CPU>,则仅列出该 CPU 的断点。 <CPU> 可以通过标记或调试器 CPU 编号指定。
2、内存访问监视点
wpset
wp[{d|i|o}][set] <address>[:<space>],<length>,<type>[,<condition>[,<action>]]
设置从指定的<address>开始至<length>的新监视点。 观察点的范围是 <address> 到 <address>+<length>-1(含)。 <address> 后面可以选择跟随 CPU 和/或地址空间。 如果未指定地址空间,则命令后缀设置地址空间:
wpset 默认为当前的第一个地址空间,
wpdset 默认为索引 1 的空间(数据),
wpiset 默认为索引 2 的空间(I /O),
wposet 默认为索引为 3(操作码)的空间。
<type> 参数指定要捕获的访问类型 - 它可以是三个值之一:r 表示读访问,w 表示写访问,或 rw 表示读和写访问。
可选的 <condition> 参数允许您指定每次触发监视点时将计算的表达式。 如果表达式的结果为真(非零),监视点将停止执行; 否则,执行将继续而不通知。 可选的 <action> 参数提供每当触发监视点并且 <condition> 为 true 时要执行的命令。 请注意,您可能需要用大括号 { } 将操作括起来,以确保命令中的逗号和分号不会在 wpset 命令本身的上下文中解释。
设置的每个监视点都分配有一个数字索引,可用于在其他监视点命令中引用它。 监视点指数在整个会话中是唯一的。
为了使 <condition> 表达式更有用,可以使用两个变量:对于所有监视点,变量 wpaddr 设置为触发监视点的访问地址; 对于写入监视点,变量 wpdata 设置为正在写入的数据。
wpclear
wpclear [<wpnum>[,…]]
清除监视点。 如果指定了<wpnum>,则引用的监视点将被清除。 如果未指定<wpnum>,则所有监视点将被清除。
wpdisable
wpdisable [<wpnum>[,…]]
禁用监视点。 如果指定了<wpnum>,则引用的监视点将被禁用。 如果未指定<wpnum>,则所有监视点将被禁用。
请注意,禁用监视点并不会删除它,它只是暂时将监视点标记为非活动状态。 禁用的监视点不会导致执行停止,不会评估其关联的条件表达式,也不会执行其关联的命令。
wpenable
wpenable [<wpnum>[,…]]
启用监视点。 如果指定了<wpnum>,则将启用所引用的监视点。 如果未指定<wpnum>,则将启用所有监视点。
wplist
wplist [<CPU>]
列出当前监视点及其索引以及任何相关条件或操作。 如果没有指定<CPU>,则会列出系统中所有CPU的监视点; 如果指定了 <CPU>,则仅列出该 CPU 的监视点。 <CPU> 可以通过标记或调试器 CPU 编号指定
三、内存访问监视点指令的使用
我们运行游戏,回到上一章所保存的存档,在调试器的指令框输入:
wp ffb2e1,1,w
该命令的详细介绍:
wp - wpset的指令缩写
ffb2e1 - 监视以ffb2e1内存地址为开始的内存访问
1 - 监视的内存长度,也就是只监视(ffb2e1+1)-1长度,这里只监视1个字节的长度。
w -写监视,只监视当被监视内存有数据被写入时中断
设置好中断后,我们先观察到当前血量的值为0064:
我们先测试比较简单的一个测试,找到使用扣血技能扣血的程序,我们回到游戏,使用扣血技能,并击中敌人:
人们看到程序中断在地址:10b22处,我们观察中断位置的上一行代码,就是血量减少的代码:
sub.w D0,($6c,A2)
//sub - 减法指令
//.w - 指令作用域为双字节
//D0 - D0寄存器的值
//($6c,A2) - $6c + A2寄存器的值 所指向的内存地址
这句指令的作用为:把($6c + A2寄存器的值 所指向的内存地址的值) 以(双字节大小)与 (D0寄存器的值)相减。
我们可以计算出($6c + A2)所指向的地址,A2寄存器的值为:ffb274,可以用计算器计算出:
FFB2E0,正好是玩家血量对应的地址,我们可以查看到D0寄存器的值为0000000A,这句代码的作用是把血量值与D0寄存器的值相减,我们可以计算出:
我们查看内存,血量的值是否已改变为5A
那么,如果这句代码失去作用,当再次使用扣血技能时,应该就不会再扣除应扣的血量。我们把这句代码修改为68000的空指令nop,空指令没有任何作用,可以把他当做一个占位符。
我们观察这句代码:
这句代码的地址在:10b1e
源数据为:916A 006C ,占用四个字节
而nop指令占用两个字节,所以,我们需要两个nop指令来替换这句代码。
我们在指令框输入以下指令:
rw@10b1e=4e71
rw@10b20=4e71
该指令在版本较低的MAME中可能没有效果,如果你的MAME版本过低 ,可以考虑下载更高版本的MAME来进行测试。
我们再读取存档进行测试,当使用扣血技能时,可以观察到,不再扣除本应扣除的血量。