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;
让我们再回到sub_E310:



-------------------------------------------------------------------------------------------------
一看就知道,这是一个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