重装机兵吧 关注:154,597贴子:2,634,788

分享一篇从mm1游戏rom中导出NSF音乐文件的教程

只看楼主收藏回复

以mm1举例,说明从fc游戏rom中提取nsf音乐的方法,此方法也可应用到别的fc游戏音乐提取。
虽然每个游戏多少有点差别。


1楼2015-02-03 09:05回复
    正文开始
    名词解释:
    nsf:是一种从NES游戏rom中提取出来的声音格式文件。
    需要说明的是nsf文件并不纯粹是声音数据,它还包含一段用于播放这些声音数据的6502汇编代码,必须数据与代码一同提取出来才能够正常播放,并且不同的游戏的播放代码都是不一样的。
    nsf文件格式
    既然要导出NSF,必须要先了解nsf文件格式,其格式如下:
    offset # of bytes Function----------------------------
    $000 5 STRING 'N','E','S','M',$1A (denotes an NES sound format file)
    $005 1 BYTE Version number (currently $01)
    $006 1 BYTE Total songs (1=1 song, 2=2 songs, etc)
    $007 1 BYTE Starting song (1=1st song, 2=2nd song, etc)
    $008 2 WORD (lo, hi) load address of data ($8000-FFFF)
    $00A 2 WORD (lo, hi) init address of data ($8000-FFFF)
    $00C 2 WORD (lo, hi) play address of data ($8000-FFFF)
    $00E 32 STRING The name of the song, null terminated
    $02E 32 STRING The artist, if known, null terminated
    $04E 32 STRING The copyright holder, null terminated
    $06E 2 WORD (lo, hi) Play speed, in 1/1000000th sec ticks, NTSC (see text)
    $070 8 BYTE Bankswitch Init Values (see text, and FDS section)
    $078 2 WORD (lo, hi) Play speed, in 1/1000000th sec ticks, PAL (see text)
    $07A 1 BYTE PAL/NTSC bits
    $07B 1 BYTE Extra Sound Chip Support
    $07C 4 ---- 4 extra bytes for expansion (must be $00)
    $080 nnn ---- The music program/data follows until end of file


    2楼2015-02-03 09:23
    回复
      2025-12-15 22:07:44
      广告
      不感兴趣
      开通SVIP免广告
      纯支持~在挨踢界混十年的表示看不懂


      IP属地:北京来自Android客户端3楼2015-02-03 09:27
      收起回复
        如果想更详细的了解nsf文件格式可以访问以下链接:
        http://wiki.nesdev.com/w/index.php/NSF
        我只说重要的。
        这里面重要的是3个地址:
        ■ $008 load address 载入地址
        ■ $00A init address 初始化地址
        ■ $00C play address 播放地址
        1. 载入地址 :
        指的是 nsf 中的数据/程序($080开始直到文件结束)被载入到的地址,例如$8000,数据/程序将被载入到$8000开始的地址。
        但是对于需要bank切换的nsf(因为从$8000~$ffff一共只有32k,如果文件大于32k就必须切换bank才能够访问的到),不光要看载入地址,还需看$070位置的8个字节,具体后面再讲。


        4楼2015-02-03 09:28
        回复
          初始化地址:
          顾名思义,初始化声音需要访问的地址,在游戏中一般是在reset(游戏复位)的时候会访问一次,用于初始化nes声音相关的端口,一般初始化过后不需要再次访问。
          不好意思,上述说明是针对在游戏中的音乐初始化地址,但是在nsf此处的地址是指换歌和播放歌曲时会调用的地址,即每换一首歌cpu会直接跳到此地址。
          那此地址跟游戏中的初始化地址有没有关系呢?是这样的,一般进入此地址需要首先调用游戏中的初始化地址,然后调用游戏中的播歌地址即可,这样完成了停止当前歌曲,播放指定歌曲的功能。


          5楼2015-02-03 09:38
          回复
            播放地址:
            播放歌曲访问的地址。此地址并不是一次访问,而是频繁的在访问,大概是每秒访问60次,所以此地址一般是放在nmi例程中的,且每次都会访问,不会作判断。
            所以播放地址是在rom中寻找音乐数据的突破口,因为它位置比较固定,容易找,找到它的位置基本就是音乐数据的位置了。
            这里需要解释一下nmi
            NMI的意思是 Non-Maskable Interrupt (不可屏蔽中断),发生在每次屏幕刷新时
            (VBlank). 这些刷新的间隔依赖于所用的系统 (PAL/NTSC).
            NMI在NTSC控制下刷新次数为 60次/秒,即游戏画面每秒刷新60次。


            6楼2015-02-03 09:43
            回复
              我分析后发现其中JSR $D362就是播放声音的过程,也就是前面说的播放地址。 跟踪D362代码如下
              LDX #$87
              LDA #$1D
              STX $8000
              STA $8001
              CLI
              JSR $A006
              LDX #$86
              LDA $20
              SEI
              STX $8000
              STA $8001
              INX
              LDA $21
              STX $8000
              STA $8001
              CLI
              RTS
              到这里我似乎没有讲怎么确定播放声音的程序,稍微讲一下,因为播放声音最终是对声音端口的写入操作,所以只要用模拟器跟踪声音端口的写入即可。
              端口的范围是:4000~4017,只要跟踪这些地址的写入,然后利用堆栈地址反推就可以找到调用的地址了,模拟器建议用fceux,这里就不仔细讲了。
              其中 JSR $A006 是真正跳到了音乐播放的代码,而它前面的代码是mmc3(重装机兵所用mapper)的切换bank的代码,由此我们知道了代码所在的bank号是#$1D,(注意代码 LDA #$1D)。


              8楼2015-02-03 10:14
              回复
                ----
                前面已经找到了音乐代码的bank在#$1D,接下来我们把整个bank都反汇编出来,不过这次反汇编并不是为了看懂代码了。
                A000:
                JMP aA038
                A003:
                JMP aA14A
                A006:
                JMP aA260
                eA009:
                .DB $0C
                .DB $1A
                .DB $6F
                .DB $FF
                .DB $FA
                RTS
                .DB $FF
                ......
                到这里似乎胜利就在眼前了,这里正好3个地址,nsf文件格式里也是3个地址,是不是这3个就是对应那3个地址呢,可以说是,但是也不完全是。
                目测代码发现除了前3行是跳转指令外,后面接着的基本都是数据了。
                首先我们确定了这个a006就是我们需要的播放地址 ,是一个好的开始。至于a006它内部是怎么完成的,我们完全不需要管了,因为我们导出nsf的时候会将整个bank,包括代码和数据全部提取,我只要记好这个起始地址就好了。


                9楼2015-02-03 10:17
                回复
                  2025-12-15 22:01:44
                  广告
                  不感兴趣
                  开通SVIP免广告
                  2. 找初始地址
                  是发挥想象力的时候了,3个地址有一个是播放地址,那我们就跟踪一下另外的,我们将断点设在a038,就是第一个跳转的地址,从游戏一开始开始跟踪,马上就断了下来,根据堆栈反推找到地址是 d31c。
                  eD31C: ;
                  LDX #$1D
                  JSR $D414
                  JMP $A000
                  JSR $D350
                  JSR $D01D
                  eD32A:
                  LDA $05FE
                  BNE $D32A
                  LDA $645C
                  AND #$20
                  eD334:
                  BNE $D33F
                  LDA $8F
                  JMP $D350
                  在反复跟踪过程中,d31c这个地址不会被多次调用,所以我猜测这基本就是初始化的地址,同样地这个地址内部怎么实现我也无需去费心了。
                  至此我又奇迹般的找到了初始地址 ,a000。


                  10楼2015-02-03 10:18
                  回复
                    3.现在还差一个地址,载入地址,但其实这个地址无需找,我们已经知道音乐程序的代码在bank 1d,且起始地址是a000,我们只需将此bank载入到a000地址就好。
                    重要是另一个地址,即播放不同歌曲时的调用地址,此地址在游戏中需要开始播放一首歌时会被调用,如果对游戏研究不久的话,这个地址找起来有点难度。
                    不过我们可以继续发挥想象力,上面的3个地址,我们已经知道2个的作用了,剩下一个是不是就是这个播歌的地址呢。
                    经过我的测试确实就是这个地址a003。且播放不同歌曲的调用方法也从以下的代码可以看出
                    eA14A:
                    SEC
                    CMP #$F0
                    BCC eA160
                    AND #$0F
                    STA a06FB
                    STA a06FC
                    LDA #$00
                    STA a06FD
                    STA a06FE
                    RTS
                    eA160:
                    A003 地址直接跳转到a14a,a14a一开始就是根据A的值作判断,转到不同代码执行,可以猜想调用前将歌曲代码放到A即可播放不同的歌曲。
                    跟踪代码后发现确实这样,好吧!地址找完了。


                    11楼2015-02-03 10:20
                    回复
                      ----
                      找音乐数据
                      接下来的任务是找出所有的音乐数据,因为目前只找到了音乐代码所在bank 1d,却没有找出所有的音乐数据位置。
                      音乐代码所在bank 1d,肯定是包含了一部分音乐数据的,但是却不完全,一定在另外的bank中也有数据,这样的话,在音乐代码中一定会有bank切换的代码,将另外的音乐数据的bank切换过来才能访问。
                      所以又有办法了,在bank 1d 中搜索切换bank的代码,因为mmc3 切换bank一定 要用到8001端口,我们就搜索8001。结果太好了,就只发现一处使用。
                      eAA4F:
                      TXA
                      PHA
                      LDY a06F8
                      LDA a06F9,Y
                      TAY
                      LDA aAA6F,Y
                      CMP #$FF
                      BEQ eAA6C
                      LDX #$86
                      LDA aAA6F,Y
                      SEI
                      STX a8000
                      STA a8001
                      CLI
                      eAA6C:
                      PLA
                      TAX
                      RTS
                      eAA6F:
                      .db $0d, $0e, $0f
                      .DB $FF
                      此处代码,将bank切换到8000,bank号数据在AA6F开始的地址,由观察大法可得 就 d e f 3个bank(后面是ff,已经超过)。如此轻易的就找到了音乐数据在d e f 3个bank。
                      接下来确认是否这3个bank确实是音乐数据,如何确认? 仍然是观察大法,将3个bank都反汇编,观察得全都不是代码,这样至少说明,不会有游戏代码混在这里面,有很大可能就是音乐数据,即使不全是音乐数据,也无影响(如果想全部确认,需要跟踪音乐代码,太过麻烦)。


                      12楼2015-02-03 10:24
                      回复
                        技术贴,前排支持


                        IP属地:浙江来自Android客户端13楼2015-02-03 10:25
                        回复
                          到这里大家是不是以为所有需要的数据都找到了,我开始也这么以为,但是这里漏了一个dpcm数据,因为dpcm数据地址只能在c000~ffff,这一段是固定无需切换的,所以也没有切换代码。
                          根据经验来说,无需找了,一般就是在c000~xxxx,xxxx一般不超过d000,但是为了保险还是跟踪一下。
                          跟踪dpcm数据需跟踪4012端口,因为此端口是用来写dpcm地址的,设此断点后,找到了数据确实在c000~d000。
                          至此,大功告成!
                          慢着,只是数据都找到了,nsf还没生成呢。


                          14楼2015-02-03 10:25
                          收起回复
                            -----------
                            2.接下来我们需要修改nsf文件头中的那几个地址。
                            载入地址 ,需要说明一下,因为需要bank切换,此载入地址只要保证最后是3个0就可以了,例如8000,或9000都可以。
                            重要的是设置$70~$77的值,此处的地址对应如下:
                            NSF Address Register
                            ==== ========== ========
                            $070 $8000-8FFF $5FF8
                            $071 $9000-9FFF $5FF9
                            $072 $A000-AFFF $5FFA
                            $073 $B000-BFFF $5FFB
                            $074 $C000-CFFF $5FFC
                            $075 $D000-DFFF $5FFD
                            $076 $E000-EFFF $5FFE
                            $077 $F000-FFFF $5FFF
                            举例 :如果$70设置成0,则$8000-8FFF地址会载入bank 0,即文件的 $80~$1080.
                            注意:nsf中的bank以4K作为一个单位,跟mmc3 8K 一个bank是不同的。
                            再举例,如果$074 设置成1,则$C000-CFFF会载入bank 1 ,即文件的$1080~$2080。


                            16楼2015-02-03 11:34
                            回复
                              2025-12-15 21:55:44
                              广告
                              不感兴趣
                              开通SVIP免广告
                              所以我根据本游戏的情况,做如下设置:
                              music.prg 需载入到a000,所以$072设置成6,$073设置成7.
                              dpcm.bin 需载入到c000,所以$074设置成8.
                              另外的可以不管都是0.
                              记住,这几个值的设置是初始值,bank可以在后面被动态的切换,切换的方法就是上表的Register,
                              例如 向$5FF8写入1,会将bank 1切换到$8000-8FFF,明白了吧。
                              ----


                              17楼2015-02-03 11:38
                              回复