4年前做的,现在有空整理一下发出来混个邀请码….
FC游戏-炸弹人1代,相信很多80后都玩过吧,我小时候挺喜欢玩的,游戏玩的不怎么样,但是对那个续关码很感兴趣,曾经记了20多页纸试图找出其中的规律,只知道最后一位是校验位 -_-!
要想知道续关码的算法,首先要知道它的处理器,一搜才知道,原来NES用的是6502,这可是一款经典的CPU,Apple一代和二代都是用的它,还有国产的文曲星…说到文曲星,当年(2003年)表哥送我一台文曲星(具体什么型号忘了),我上网查到可以用数据线升级BASIC功能,就满泰安城找数据线去了,把跟我去的同学都逛烦了,最后也没有找到,不然也许能早入门个几年……:)
用到的资源:
炸弹人ROM 文件 Bomber Man (J).nes http://www.romnation.net/srv/roms/28...mberman-J.html
6502 指令集 http://www.laogu.com/laogubbs/shared...p?id=270&srv=1
FCEU0.98.12.win (带Debug功能的NES模拟器)http://mmm.onegreen.net/soft10/FCEU0.98.12.win.rar
NESpackage (Ida 载入nes格式的插件) http://bbs.pediy.com/showthread.php?t=125458
首先,我们把 NESpackage\nesdbg\bin\nes_stub.plw copy到ida的 \loaders文件夹里面,这样Ida才能识别NES格式的文件,用Ida载入Bomber Man (J).nes
我们可以看到ida已经识别出代码段和数据段了,下面要做的就是定位代码,
要定位代码,就要知道数据的存储地址,懒得去自己调试,最快的方法还是站在巨人的肩膀上…从网上搜了个炸弹人的金手指
; 炸弹人1代金手指 #1 0068-01-09 生命无限 #1 0073-02-09C0 最强的雷 #1 0075-01-01 速度加快 #1 0076-04-01010101 免疫功能 #1 007D-02-0101 其他功能 #1 007A-01-01 无敌 #1 0058-01-4A 最终关
要注意的是续关码只保存了一部分数据(例如,不保存剩余生命数),我知道0x75(加速鞋子)是保存在续关码里面的
用FCEU载入游戏,玩到最后一条命的时候,打开调试窗口

-------------------------------------------------------------------------------------------------
当主角死了但是还没有出现续关码的屏幕前,切换到调试窗口,点击单步,先让游戏暂停下来,在0x75设内存读取断点,然后点击运行

-------------------------------------------------------------------------------------------------
可以看到,程序中断在 e31b
切换到ida ,看看e31b在干什么?

-------------------------------------------------------------------------------------------------
E31b处的指令是 LDA ($34),Y
查询6502指令集

-------------------------------------------------------------------------------------------------
得知,这种寻址方式属于零页间接索引地址,内存34和35处放置着一个16位地址,把这个地址再加上Y寄存器的值作为有效地址,再把有效地址指向的数据送到A寄存器中
我们看前面那条指令
JSR sub_E327
Jsr是6502的call指令,我第一眼以为是像jmp一样不回来了呢
让我们到 子函数sub_E327 里面看看吧

-------------------------------------------------------------------------------------------------
原来地址在这里面,双击E334,来到这里

-------------------------------------------------------------------------------------------------
不好…ida把这里认作了代码….
只好手动undefine ,一共20个16位地址

-------------------------------------------------------------------------------------------------
BYTE offset[20]={ 0x67,0x77,0xDD,0x61,0x99,0x66,0xDC,0x64,0x79,0x9A, 0x74,0x63,0x75,0x62,0x9B,0x65,0x94,0xDE,0x76,0x95 };
typedef struct { BYTE score_1; //分数右1位 BYTE Remote_bomber; //遥控 BYTE stage_low; //关卡低4位 BYTE score_7; //分数右7位 BYTE byte_99; //未知 BYTE score_2; //分数右2位 BYTE power; //威力 由mem_73右移4位获得 BYTE score_4; //分数右4位 BYTE mianyi; //炸弹免疫 BYTE byte_9a; //未知 BYTE bomber_num; //炸弹数 BYTE score_5; //分数右5位 BYTE speed; //速度 BYTE score_6; //分数右6位 BYTE byte_9b; //未知 BYTE score_3; //分数右3位 BYTE show_obj; //显示物品 BYTE stage_high; //关卡高4位 BYTE chuanqiang; //穿墙 BYTE byte_95; //未知 }Address;

-------------------------------------------------------------------------------------------------
一看就知道,这是一个for循环,以 byte_20为循环变量,目的是计算连续4个地址的校验和,放到byte_24中,我们要到上层函数里面看看byte_24放哪里去了
来到函数sub_E291: 其实这个就是encode函数

-------------------------------------------------------------------------------------------------
我们可以看到 byte_24 被放到刚才的地址列表中去了,原来
0x99;
0x9A
0x9B
分别放的是 前面4位数据的校验和
下面我们对encode函数从头到尾分析一遍

-------------------------------------------------------------------------------------------------
通过调试,我们知道,byte_73里面放到是威力大小,但是是低4位和高4位反着放的,所以,最多能有15格的威力
Encode函数把byte_73的数据左移4位,得到威力数据,放到byte_dc中
下一步是 把byte_58(关卡数)中的数据分成高4位和低4位两部分
低4位放到 byte_dd 高4位放到byte_de
再往下就是初始化循环变量了

-------------------------------------------------------------------------------------------------
前面我们分析过,这是个 do while 循环 一共3次,把前3组的校验和算出来,分别放到0x99 0x9A 0x9B
然后下面还有一次,是计算最后一组校验和的,不过最后一组的校验和没有直接放到 0x95,而是加上前面3个校验和的左移一位之后的和,
下面还是个 do while 循环

-------------------------------------------------------------------------------------------------
一共循环了 0x28/2 = 20 次
程序设置了一个临时变量 byte_54
把内存列表对应的数据取出来,然后取数据的低4位,再减去byte_54 ,减去7,再取低4位,把结果放到 182(因为sub_e327中x加了2次)开始的内存地址。
用c语言表示
do { cache= (p[x]&0x0F)-Temp; Temp = ( cache-7 )&0x0F; psw[x++] = Temp; }while(x<20);

到了这里,只差最后一步了,

-------------------------------------------------------------------------------------------------
程序循环 (0x2a -2) /2次,把 0x182开始的数据查表后输出
转换表在 e35c

-------------------------------------------------------------------------------------------------
共16位
BYTE convert[]="AOFKCPGELBHMJDNI"; //转换表
转换之后就大功告成了……
炸弹人续关码编码和解码工具:
Project1.rar