NSFripperへの道

まず初めに...
前のまんまである。よってLINKは間違ってる。でもコレでいいのだ。

基本(そんなモンがあるのかどーかは知らんが)は以下のとーり(三つ目は違う) というわけで、まずは既にリップされてるデータを参考にして解析をしていこう。
それと同時にファミコンに関する資料も載っけてみる。

※内容に関しては「オレ調べ」なので、間違い等見つけたら指摘をしてもらえると助かる

データを作成できたら情報よろしく
レベル別  Level 0  Level 1  Level 2   Level 3  Level 4  Level 5(by ふくい)   Level 12  Level 17  Level 20
NSF用資料ファミコンについて  ロムカートリッジ  割り込み  レジスタ  OPコード


LEVEL0 前準備

ツールを揃えるべし!
解析を開始する前に必ずする事
ヘッダーファイルを用意しておく
ヘッダーのみ(128byte)のファイルがあると後でらくちん。
nesイメージのヘッダは取り除いておく
始めの16バイトは各種エミュレータで取り扱うデータなので実カセットにこの部分はない。よって取り除いておかないとこの後のデータが16バイトずつずれる。

・これらは[NES2NSF]を使うとチョー簡単ッ!
ヘッダーについて
これを知らないと始まらないので解説
ただし、独自の規格も含まれている。
offset # of bytes   Function
----------------------------

0000  5   STRING  "NESM",01Ah  ; NSFの宣言(固定)
0005  1   BYTE    NSFのバージョン (現在は1)
0006  1   BYTE    総曲数
0007  1   BYTE    何曲目からプレイさせるか
0008  2   WORD   load(lo/hi) データロード開始アドレス      (8000-FFFF)
000A  2   WORD   init(lo/hi) 初期化ルーチン開始アドレス     (8000-FFFF)
000C  2   WORD   play(lo/hi) 演奏データ(ドライバ?)のアドレス (8000-FFFF)
000E  32  STRING  ゲーム名   (31文字+null1文字)
002E  32  STRING  アーティスト名(31文字+null1文字)
004E  32  STRING  メーカー名  (31文字+null1文字)
006E  2   WORD    (lo/hi) NTSCの再生レート(通常は411Aで良い)
0070  8   BYTE    バンク切り替え(詳しくは後述)
0078  2   WORD    (lo/hi) PALの再生レート
007A  1   BYTE    PAL/NTSC 切り替え?
                 bit 0: if clear, this is an NTSC tune
                 bit 0: if set, this is a PAL tune
                 bit 1: if set, this is a dual PAL/NTSC tune
                 bits 2-7: 未使用
007B  1   BYTE    どの拡張音源を使用するか
                 bit 0: 1 = Konami   VRC6
                 bit 1: 1 = Konami   VRC7
                 bit 2: 1 = Nintendo FDS
                 bit 3: 1 = Nintendo MMC5
                 bit 4: 1 = Namcot   106
                 bit 5: 1 = Sunsoft  FME7
                 bits 6-7: 拡張用未使用分
007C    4   ----    4 extra bytes for expansion (must be 00h)
0080  nnn ----    イメージのデータはここから
-----------------------

LEVEL1 CAPCOM(1943)

NES Music Ripping Guideを読むと解るのが、
カプコンやサン電子は例の6バイトが8000/8003/8000が多いと言うこと。
で、とりあえず1943辺りが簡単。

さて、いきなりイメージを16KB(16,384バイト)毎に分割してみる。
1943.000〜007が出来上がったと思う。

それぞれに8000(load)/8003(init)/8000(play)のヘッダーをくっつけてプレイヤーで聞いてみる。
1943.005の所で曲が鳴り出すはず。

これで貴方もNSFリッパーの仲間入り!?
LEVEL1はここまで。
要はこんな感じで進んで行くんだなと思ってもらえれば良し(実際はだいぶ違うと思うが)。

LEVEL2 サン電子(上海II DPCM無し)
ここではもう少し解析を交えつつ進めていく

まずバイナリエディタで”8D1540”を検索する

なぜ”8D1540”か?
それはこの命令がサウンドの制御命令に他ならないからだ
具体的に言うと

8D → OPコード一覧表を参考にすると「STA(Aレジスタをストアする)」の
    「abs(絶対番地。指定された16bitアドレスをアクセスする)」ということ

1540 → 4015番地。これは特別な意味を持つ場所で、
     レジスタの項を参照するとサウンド制御レジスタとなっている

というわけでサウンドを制御している場所だからデータもある可能性が高いと言うことになる。

ついでに説明すると各ビットON/OFFというのはAレジスタに格納された数字によって決まる
今回の上海2だと
A91F8D1540となっていて
A9 1F → 1FをAレジスタに格納。1Fは2進数にすると00011111
8D 15 40 → Aレジスタをストア 
4015の各ビットは ---abcdeなので1F(00011111)と併せてみるとチャンネル5までonになる

ちなみに
チャンネル1(e)→矩形波1
     2(d)→矩形波2
     3(c)→三角波
     4(b)→ノイズ
     5(a)→DPCM
かな
チャネル5はDPCMなので通常Aレジスタに渡す値は0F(00001111)になっていると思う
2,16進数分からない人はWINDOWSのアクセサリに電卓があるのでそれで確認してみて

本題に戻す
”8D1540”が連続で見つかると思う。ということはサウンド部である可能性が高い
場所は8000〜(ROMイメージ内の番地)
早速ここから16KB切り出してみる
  (ファイル分割ツールを使えば16KB毎に分割してファイル名.002になるかな)
ヘッダーファイル(8000/8003/8000)と結合して聞いてみる。

ならない

で、逆アセ。
yd6502.com ファイル名.002 1.txt   注)ヘッダーと結合する前のファイルを使用すること

で1.txtを開いてみる
すると
----------------------------------------------------------------
C000 : 02		db	$02		;無効命令
C001 : 4C 6E 81		jmp	$816E		;
C004 : 4C A6 80		jmp	$80A6		;
C007 : 4C 0A 80		jmp	$800A		;
C00A : A9 1F		lda	#$1F		;
C00C : 8D 15 40		sta	$4015		;サウンド制御
C00F : A9 00		lda	#$00		;
C011 : 8D 10 40		sta	$4010		;PCM制御 REG.
C014 : A2 00		ldx	#$00		;
以下続く
--------------------------------ーーーーーーーーーーーーーーーーーーーーーーー---------
こんな風になってるはず

その16KBの固まりが”8000”から始まっているのか”C000”から始まっているのかは
その近くのjmp命令を見てみればだいたい分かります
この場合 4C 6E 81 となっているので、loadアドレスは8000とあたりを付けます

始めに1byte余計な命令が入ってるのが分かります           ・・
よってinit/playを1バイトずつずらし8004/8001とします。これは単なるカンです
                                 ↑現在の主戦力
なったでしょ

本当はDPCMがあるので(音の厚みが全然違う)これで完璧ではないのですが
まだ解らないので...

要は、ROMイメージの中を検索してサウンド部を特定
   で切り出したイメージからload/init/playを割り出すと

ま、バイナリデータに触れてみるって事で
それっぽくなってきたでしょ

しかし8D1540とは全く違う場所にデータがあったり
   8D1540が全く無いデータ(魔界村)があったり
   8D1540を自分で埋め込んでやらないといけなかったり(LEVEL3参照)
そんなんではサウンドデータ部の特定すら難しい

道は険しそうです...

LEVEL3 テクノスジャパン(ダブルドラゴン2)
早速8D1540を検索
14000〜辺りで複数見つかったと思う。で16K切り出し。分割ならfilename.005ですね。
んで例の6バイトと結合。鳴らない。

逆アセ。が、見ても分からず...
ならば!「RiverCityRansom.nsf(ダウンタウン熱血物語)」と見比べてみる。←これ重要
すると...
-------------------------------------------------
C000 : 4C 26 80		jmp	$8026		;
C003 : 4C 96 81		jmp	$8196		;
C006 : 00		brk			;

途中省略

C01F : 00		brk			;
C020 : 4C 26 80		jmp	$8026		;
C023 : 4C 96 81		jmp	$8196		;
-------------------------------------------------
とダブルドラゴン2では801Fまで00が続くのに対し
-------------------------------------------------
C820 : 4C 46 81		jmp	$8146		;
C823 : 48		pha			;
C824 : A9 00		lda	#$00		;
C826 : 8D FF 07		sta	$07FF		;
C829 : 20 20 80		jsr	$8020		;
C82C : 68		pla			;
C82D : 8D FF 07		sta	$07FF		;
C830 : EE FF 07		inc	$07FF		;
C833 : A9 0F		lda	#$0F		;
C835 : 8D 15 40		sta	$4015		;サウンド制御
C838 : 4C 20 80		jmp	$8020		;
C83B : 00		brk			;
C83C : 00		brk			;
C83D : 00		brk			;
C83E : 00		brk			;
C83F : 00		brk			;
C840 : 8A		txa			;
-------------------------------------------------
ダウンタウンはこんな感じ。データ改変の可能性があります。
ならこの部分をダブルドラゴン2にも移植してみます。

4C 96 81 48 A9 00 8D FF 07 20 20 80 68 8D FF 07
EE FF 07 A9 0F 8D 15 40 4C 26 80 00 00 00 00 00

初めの32バイトが↑となります。
注意点はjmp命令の飛び先を、ダブルドラゴン2用に変更することです。

8000 4C 46 81 → 4C 96 81
8018 4C 20 80 → 4C 26 80

なったでしょ

じゃあこの部分は何をしているのかというと、どうやら初期化ルーチンらしいです。
見ていくと...
-------------------------------------------------
8000 : 4C 96 81		jmp	$8196		;8196にジャンプ(play)
8003 : 48		pha			;Aをプッシュ
8004 : A9 00		lda	#$00		;A←0
8006 : 8D FF 07		sta	$07FF		;Aを07FFでストア?
8009 : 20 20 80		jsr	$8020		;8020サブルーチンへ(ここも編集すべきかな?)
800C : 68		pla			;Aをポップする
800D : 8D FF 07		sta	$07FF		;Aを07FFでストア?
8010 : EE FF 07		inc	$07FF		;07FFに1を加算
8013 : A9 0F		lda	#$0F		;A←0F
8015 : 8D 15 40		sta	$4015		;Aを4015でストア(サウンド制御)
8018 : 4C 26 80		jmp	$8026		;8026にジャンプ
-------------------------------------------------
いやーよく分かりません。
初期化をしているのは確かなんですが。

ここで重要なことは

・スデにリップされたデータをオリジナルと比べ、どこに手を加えられたか見抜く
・NSF用に改変された箇所を移植してみる
・元データに手を加えるので、例の6バイトのアドレスは
              探すのではなく自分で作ると言うこと
              ・・・・・・・・・・・・
ちなみにこのやり方でテクノス中期は結構いけます。
初期はダメ(ダブドラ1、くにお、ドッヂボールどれも好きな曲なのに)

この辺からバイナリエディタやディスアセンブラをガンガン使い出すので慣れといて下さい。

LEVEL4 ハドソン(ヘクター87)
例によって8D1540を検索
14000〜辺りで2個見つかったと思う。で16K切り出し。分割ならfilename.005っすね。
んで例の6バイトと結合。鳴らない。

逆アセ。が、見ても分からず...
さあ、何を参考にしようか。販売時期や音色が似ているボンバーキングに決定。
早速見てみると...
例の6バイトが「8000/AEFC/800D」となっている
妙にinitとplayが離れている。あやしいな
ということでボンバーキングのAEFC付近を見てみる
-------------------------------------------------
EEFC : AA		tax			;
EEFD : BD 10 AF		lda	$AF10,x		;
EF00 : 85 FA		sta	$FA		;
EF02 : A9 00		lda	#$00		;
EF04 : 8D 50 01		sta	$0150		;
EF07 : 4C 7D 80		jmp	$807D		;
EF0A : FF		db	$FF		;無効命令
EF0B : FF		db	$FF		;無効命令
EF0C : FF		db	$FF		;無効命令
EF0D : FF		db	$FF		;無効命令
EF0E : FF		db	$FF		;無効命令
EF0F : FF		db	$FF		;無効命令
EF10 : 0F		db	$0F		;無効命令
EF11 : 01 02		ora	($02,x)		;
EF13 : 03		db	$03		;無効命令
EF14 : 04		db	$04		;無効命令
EF15 : 05 06		ora	$06		;
EF17 : 07		db	$07		;無効命令
EF18 : 08		php			;
EF19 : 09 0B		ora	#$0B		;
EF1B : 0C		db	$0C		;無効命令
EF1C : 0D 0E FF		ora	$FF0E		;
-------------------------------------------------
FFの固まりの中にひっそりとこんなデータが見受けられたと思う。思いっきり怪しい
移植してみる。
LEVEL3と違って場所は任意で決めて貰って構わない。
ただし、同じようにFFの固まりの中の方が無難だと思う。
で、オレはAEA0〜に移してみた。

問題は飛び先の修正だ。ダブルドラゴン2ほど簡単では無さそうだ。
そこで、ボンバーキングの飛び先に似た場所をヘクターから探してみる。

するとデータの並びが似ている所が85F2辺りにあると思う。
で、飛び先を 

4C 7D 80 → 4C F2 85

に修正
さらにAEBO〜のデータだが、これは命令では無さそうだ。

01 02 03 04 05 06 07 08 09 0B 0C 0D 0E

となっているがどうやら曲の再生順を決めるデータらしい。
なので01から順に並べてみる。すると...

AA BD B0 AE 85 FA A9 00 8D 50 01 4C F2 85 FF FF
01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F 10
               ↑ヘクターなら0Bまでしか曲がないんだけど一応ね
この32バイトになる。
loadはジャンプ先を見ても分かるように8000〜だ。
playだが...これもボンバーキングのplayに似たところを探し出す。8585に発見
というわけでオレの場合 例の6バイトは8000/AEA0/8585に決定。

見事鳴り出す。

しかし、「怪しい」とか「似ている」とかばかりで申し訳ない。
ゼロから解析できないので...

この方法で桃太郎伝説もいけそうなんだがダメ。ん〜なんだろ?※修正。いけました。

ここで重要なのは
・曲順を決めるルーチンを自作っている

ということ。
形は違うが、これと同じ事しているリップデータは結構ある(レッキングクルーとか)。
この仕組みがハッキリと解れば他で試せそうなんだが...

LEVEL3のもそうなんだが、どうやらNSFプレイヤで演奏するのに
足りないことを追加しているらしい(変数とか初期化とか)。

まあ、オレが解るのはここまで。
あんまり解析らしくなかったかな?
解析は今後も続けるが、これ以上はオレの手に負えないかな。

皆さんが何かデータを作成できたら、掲示板にでも情報願う!!
それ以外でも何か解ったらよろしく!!

LEVEL5 任天堂(レッキングクルー)
素晴らしいッ!おおッ!ブラボーッ!
ripperふくいによるlevel5だッ!
今までのおさらい&更なる飛躍だ。頑張れripper予備軍!
そして、必殺そのまま転載。
--------------------------------------------------------------------------
ripされてはいるが2曲しか鳴らないレッキングクルー

とりあえずdisってinit部分(ソースの末尾)をながめる
FFF3 : 8D DB 07		sta	$07DB		;	aを$07DBに書き込み

$07DBは曲の番号を示す変数(この場合RAM上の値)だと思い, disったソースから
$07DBを検索して付近をいろいろ見てみる
F87E : AD DB 07		lda	$07DB		;	$07DBをaに読み
F881 : 4A		lsr	a		;	1/2にする
F882 : B0 30		bcs	$F8B4		;	分岐(0でないなら)
F884 : 4A		lsr	a		;	1/2
F885 : B0 22		bcs	$F8A9		;	分岐
F887 : A5 F1		lda	$F1		;	$F1をaに読み
F889 : 4A		lsr	a		;
F88A : 4A		lsr	a		;
F88B : B0 EA		bcs	$F877		;	1/4で分岐
F88D : 4A		lsr	a		;
F88E : B0 12		bcs	$F8A2		;
F890 : 4A		lsr	a		;
F891 : B0 5A		bcs	$F8ED		;
F893 : 4A		lsr	a		;
F894 : B0 3C		bcs	$F8D2		;
F896 : 4A		lsr	a		;
F897 : B0 27		bcs	$F8C0		;
$F1と$07DBが似ている
$F1が曲の番号を示す変数,って線で攻めることにし,カン(主戦力)に任せてルーチン作ってみる
(6502.txt見て手作業アセンブルしてバイナリエディタで書き込み)

//$F1があやしいのでinitを改造して調査----------------------
AA		tax		;aをxに入れる
4A		lsr a		;aを1/2
4A		lsr a		;aを1/2
8d f1 00	sta	$f1	;$f1(RAM)に書き込む(aの1/4の値(追加部分))
E8		inx		;xを1増やす
8A		txa		;xをaに入れる
8D DB 07	sta	$07DB	;$07DB(RAM)に書き込む(a+1の値(最初のルーチン))
60		rts		;
曲数を255に増やし調査, 今まで鳴らなかった曲が鳴った(感動)
$F1の値が1,2,4,8,16,32のときに$07DBの値との組み合わせで鳴るようだ
ってわけで6曲ほど発掘
NESaudioripping.txtに書いてあったようにかっこいい曲順で鳴らすべく曲番号を整頓してみる
level4を参考にinitから来たときにaレジスタに入ってた値を変換してみる

//aを変換して$F1と$07DBに書き込むinitルーチンを作成--------
0A		asl a			;aを2倍
AA		tax			;aをxに入れる
BD D0 FF	lda	$FFD0,x		;$FFD0+x(ROM:曲番号変換table)からaに入れる
8D F1 00	sta	$00F1		;aから$F1(RAM)に書き込む
BD D1 FF	lda	$FFD1,x		;$FFD1+xからaに
8D DB 07	sta	$07DB		;書き込む
A9 00		lda	#$0		;0をaに入れる
8D 0C 40	sta	$400C		;ここから4行あまり効果なし
8D 0D 40	sta	$400D		;1曲目頭のノイズを消そうと思ったんだがよくわからん
8D 0E 40	sta	$400E		;
8D 0F 40	sta	$400F		;
60		rts
($FFD0からtableを作っておく)
--------------------------------------------------------------------------
さて、ここからはオレなりの解釈だ。
ripperふくいはホントに素晴らしい点に注目している。
鳴らす曲を決める「変数の発見」と言うところだ。

NSFが6502エミュレータと言うことを再認識させてくれる。
それは、0000〜7FFFまでもちゃんと機能している(当たり前だが)と言うことに他ならない。

initでこの変数に鳴らしたい曲の値を入れてやり、playに渡せば曲は鳴り出す...と。
変数を見つけるにはプログラムを解析しても良いが、簡単な方法にメモリ総当たりがある。
fwnesのメモリエディタで00〜FF(07FF)まで適当に値を入れていき
曲が変わればそれが変数だ。

初期任天堂はF0〜FF辺りが良く使われていた。
ここで注意しておきたいことが、変数を使わないプログラムもあるということ。
そういうゲームはこの方法でやっても無駄無駄無駄無駄無駄無駄無駄無駄ァッ!
見分け方はないけどね...

変数が分かってもplayが分からなければ話にならない。
playの探し方は、これはもうトレースして行くしかない(これが一番ムズイ)と思うが
一応法則もある。

「8D1740」

これだ。
本当は2コンの制御レジスタなのだが、1play専用のゲームでもこの命令があったりする。
今までのripデータでも、この部分からplayとしている物も多い。
ならば試す価値はあるだろう。
initには曲の変数を入れてやるだけでよい。

じゃ、実際にHUDSONのチャレンジャーで見ていこう。
fwnesを使って曲変数を割り出す(総当たり方式)。
”D8”に値を入れると曲が変わるのが発見できる。

次に8D1740を検索。

C9C4 : 99 00 40		sta	$4000,y		;矩形波CTRL.REG.#1
C9C7 : E8		inx			;
C9C8 : C8		iny			;
C9C9 : C6 5D		dec	$5D		;
C9CB : D0 F4		bne	$C9C1		;
C9CD : 60		rts			;
C9CE : A9 C0		lda	#$C0		;
C9D0 : 8D 17 40		sta	$4017		;ゲームパッド #2
C9D3 : 20 D9 C9		jsr	$C9D9		;

とゆーわけでplayはC9CEに決定。
んでルーチンは

BFF0 : AA		tax			;AをXに入れる
BFF1 : BD F7 BF		lda	$BFF7,x		;BFF7からデータをAに入れる
BFF4 : 85 D8		sta	$D8		;D8に書き込み
BFF6 : 60		rts			;おしまい
BFF7 : 01 03 05 07 08 09 06 02 04		:曲順データ

だけでOK!

AA BD F7 BF 85 D8 60 01 03 05 07 08 09 06 02 04

このたった16バイトを追加するだけでNSF作成可能!
この16バイトをBFF0〜にに加えて、load/initをBFF0に決定(別に中に組み込んでも良いけど)。

なるなるッ!


LEVEL12 任天堂(3Dホットラリー)

さて、いきなりレベル10越えだ。
ファミコンディスクモノが作成できたので、その過程を解説してみたいと思う。

まずディスクの中身だが、ディスクと言うだけ有ってファイル毎の管理となっている。
細かい説明は省くが...
ブロックID 01→許諾
      02→ファイル数(実際にはこれ以上有ったりするらしい)
      03→データのヘッダ(ファイル名、データサイズ、データロードアドレス、データの種類)
      04→データ(データサイズはブロックIDを除く)

となっており、nsfを作成するのならば「03」「04」が重要になってくる。
しかしファイル毎の切り出しが非常に面倒。
んで例によってわいわいさん作成の「NESplit」が最高の使い心地だ。log出力が助かりまくり!
コレがなかったら...

No.  ADDRESS SIZE  LOAD-ADDRESS TYPE NAME
---------------------------------------------
000  00004B  00E0  2800 - 28DF  NTB  KYODAKU-
002  0002CD  2000  0000 - 1FFF  CHR  TITLECHR
003  0022DE  6800  7800 - DFFF  PRG  TITLEPRG
004  008AEF  0200  DE00 - DFFF  PRG  TDRE-INJ

こんな出力になっていると思う。E000 - FFFFはDisksystem(BIOS)が使用するので、その下がPRG領域。
中身を「8d1540」で検索すると「TITLEPRG」の中にあることが判る。
んで逆アセ。7800からね
ディスクの場合、DFFA - DFFFが割り込みアドレスらしい。じゃあ早速NMIから見ていきましょうかっ...ン!?

DFF6 : FF		db	#$FF		;
DFF7 : 4C 1F C1		jmp	$C11F		;おお!こんなところにジャンプ命令が!
DFFA : 57		db	#$57		;
DFFB : A8		tay			;NMI  A857
DFFC : 00		brk			;RESET 8000
DFFD : 80		db	#$80		;IRQ  A820
DFFE : 20 A8 FF←これ	jsr	$FFA8		;最後にゴミデータが付くのはバグか?

そう、任天堂初期モノに付着していた、プレイアドレスを教えてくれるジャンプ命令がココにもッ!
playは確認もしないでC11Fに決定。
しかしinitが解らず...どうすればッ!一個一個見て行くしかないのか...←コレがナカナカできない人

はっ!そうだ、ふくいさんがコレはHAL研製では?との発言をしていたな。
試してみる価値はありそうだ。
というわけでアドベンチャーズオブロロと比較。←得意
成る程似ているッ!
initはplayのチョット上か...<作業中>...よっしゃ!鳴ったぁ!
軽快なオープニング(FMナシ)が鳴り出す。
勢いに乗ってB面も攻略だッ!
Bメンはロードアドレスが重なっているファイルが沢山...

No.  ADDRESS SIZE  LOAD-ADDRESS TYPE NAME
---------------------------------------------
100  010027  5740  88C0 - DFFF  PRG  3DHOTPRG
101  015778  13C0  68C0 - 7C7F  PRG  KATTOBI
102  016B49  0C00  7CC0 - 88BF  PRG  COURSE1
103  01775A  1400  68C0 - 7CBF  PRG  YONQUE
104  018B6B  0C00  7CC0 - 88BF  PRG  COURSE2
105  01977C  1080  68C0 - 793F  PRG  MONSTER
106  01A80D  0B00  7CC0 - 87BF  PRG  COURSE3
107  01B31E  2000  0000 - 1FFF  CHR  3DHOTCHR

ファイル名から察するに、車毎やコース毎でファイル化していることが容易に想像される。
ということは、
「3DHOTPRG」にサウンドドライバが入っていて、各車のファイルに曲データが納められているであろうコトが予測できる。
いよいよディスクモノのripらしくなってきた。

さっそく「YONQUE」+「COURSE1」+「3DHOTPRG」で結合。
例の6byteはA面と似てるトコを探す。←得意げ
あった。
鳴った。
カンタンカンタン。

早速「YONQUE」を「KATTOBI」で上書きしてトライ。
鳴る鳴る!
しかしこれではデータがデカイ上、ファイルも4個もある。削り&バンク切替は今度の機会に...

と終わったと思っていたら、なんとFM部が鳴ってないことが判明!
Mamiyaさん情報によると、
どうやら「4040 - 407F」にデータが書き込まれていない模様。拡張音源用の音色レジスタである。
んで探す。
どうせ4040から407Fまでの音色書き込みルーチンがあるだろ、と思い「4040」で検索...

ナイ

あれー?
おかしいな。B面には入ってないのかな?で、A面を探す。

C6ED : 00 00 04 07 0C 0F 14 19 1E 24 2A 30 33 39 35 38(407Fからのデータが置かれている)
C6FD : 36 3B 36 38 3C 3C 3D 38 3F 38 3F 00 30 00 3F 00
C70D : 30 2A 25 2A 25 2A 28 2D 32 38 3F 3A 3D 3D 3B 37
C71D : 35 3F 28 00 0E 19 22 29 2F 33 36 39 3B 3D 3E 3F
C72D : A9 80		lda	#$80		;はじまり
C72F : 8D 89 40		sta	$4089		;音量設定?
C732 : A2 3F		ldx	#$3F		;データの総数設定
C734 : BD ED C6		lda	$C6ED,x		;データ読み込み
C737 : 9D 40 40		sta	$4040,x		;データ書き込み
C73A : CA		dex			;アドレスを1減らして
C73B : 10 F7		bpl	$C734		;繰り返し
C73D : A9 00		lda	#$00		;
C73F : 8D 89 40		sta	$4089		;この辺ナゾ
C742 : 60		rts			;おわり

あった。どうやらココだけ見たい。まあ音色一個だからそんなもんか。
initのルーチンに組み込んで完了!Bメンはinitに直接コレを置いた。データ結合時は考えなくてはな...
よっしゃぁ!軽快なベースが鳴る〜!

ディスクは簡単なバンク切替と考えることが出来よう
どのファイルがどーゆー内容で、どーゆーふーに繋がっているか知るのがポイントと見た
あとは普通のripと同じ

LEVEL17 任天堂(新鬼ヶ島)

さあ、ディスクと言えばコレを抜きには語れない
永遠の名作「鬼ヶ島」を見ていこう
早速分割

No.  ADDRESS SIZE  LOAD-ADDRESS TYPE NAME
---------------------------------------------
000  00004B  00E0  2800 - 28DF  NTB  KYODAKU-
001  00013C  0840  D7C0 - DFFF  PRG  MOMOV
002  00098D  3900  6000 - 98FF  PRG  MOMO0
003  00429E  3CE0  9900 - D5DF  PRG  MOMOT
004  007F8F  3D3C  9900 - D63B  PRG  TABLE-04
005  00BCDC  1760  02A0 - 19FF  CHR  CHRA-004
006  00D44D  0050  0200 - 024F  PRG  

x4サイド分

いやーサスガ二枚組。たくさん出てくるなぁ
まずは「NMI」である「DFFF」の中身を確認するため「MOMOV」を逆アセ

DFFA : 2D 60 00		and	$0060		;NMI  602D
DFFD : 60		rts			;RESET 6000
DFFE : E2		db	#$E2		;IRQ   B5E2
DFFF : B5 FF		lda	$FF,x		;FFはゴミ

「602D」からか...じゃぁ「6000」から始まっている「MOMOO」を逆アセ
「6000]から始まっているPRGは「MOMOO」だけなのでコレがメインプログラムかと思われる(NMIの飛び先でもあるし)

602D : 48		pha			;
602E : A5 DF		lda	$DF		:
中略
6073 : 20 82 85		jsr	$8582		;他が「6xxx」や「8xxx」、「9xxx」が飛び先なのに
6076 : 20 52 93		jsr	$9352		;
中略
609A : 20 00 D8		jsr	$D800		;この辺だけミョーに違う
609D : 4C A3 60		jmp	$60A3		;
60A0 : 20 38 CC		jsr	$CC38		;あやしい
60A3 : 20 70 61		jsr	$6170		;
中略
610D : 68		pla			;
610E : 40		rti			;

「D800(MOMOV)」「CC38(MOMOT)」を確認。「MOMOV」はサウンドドライバだけっぽい
成る程。共にドライバっぽい。play候補に決定
initはどうしようか?
「MOMOV(8D1540)」付近を調べる。

D7FF : 7D A9 FF		adc	$FFA9,x		;A9 FF ですな
D802 : 8D 17 40		sta	$4017		;Joy-Pad #2 制御Reg.
D805 : A9 0F		lda	#$0F		;
D807 : 8D 15 40		sta	$4015		;SoundChannel 制御Reg.
D80A : 20 BE DA		jsr	$DABE		;
D80D : 20 BB D9		jsr	$D9BB		;
D810 : 20 1F D8		jsr	$D81F		;
D813 : A9 00		lda	#$00		;
D815 : 8D FF 07		sta	$07FF		;おぉ、何か初期化してるなぁ
D818 : 8D FD 07		sta	$07FD		;
D81B : 8D FE 07		sta	$07FE		;
D81E : 60		rts			;

「07FD - 07FF」があやしい
initで「07FD - 07FF」それぞれに適当な値を入れ、playをCC38・D800で試す
結合は「MOMO0」+「MOMOT」+(足りない分は適当な値で埋める)+「MOMOV」でloadは「6000」
やった!鳴る!(CC38にて)

と言うわけで
07FF ー 曲
07FE − 効果音1
07fD − 効果音2
と確認
曲番号は初期任天堂にありがちな各bit毎になっている模様。
なーんだコレもカンタンじゃん!
しかも何も参考にしてないから、初のゼロrip!そして鬼ヶ島!ウレシー!!

じゃあ、各面の曲は「TABLE-0x」に入ってるな!一気に終わらしたるわ〜!!


















挫折...





















鳴らない。鳴らないのだ。
「9900」から始まっている「MOMOT」を「TABLE-04」で上書きしたのだが、どうしても鳴らない
それもそのはず、「TABLE-04」の「CC38」にはサウンドドライバなぞ入っていないのである
じゃあ、「D800」かというとコレも鳴らない。ウガーッ!

ここでの挫折期間がそーとー長かった...

術が無くなった。あー困った。同じゲームである以上大差ないことをしているハズなんだが
んー困った。おー困った。
そうだ!
「困ったときのFwNES頼み!」
なる格言(絶対)を思い出す
と言うわけで、4章にてFwNESで0000-DFFFをダンプ(デバッグ画面で 0000.dfff w oni.prg
してそのデータで試す(0000/E000/D800 E000からは07FFに値を入れるだけのルーチンを組む)
あ〜鳴るじゃん!

ということは...0000 - 5FFF の部分が何かしら作用しているに違いない
まあ「0000-07FF」と見ていいだろうな
じゃあやることは一つ。そう、総当たりだ!
0000から16byteずつ削っていく...D0 - DFを削ったときに音が鳴らなくなった
更に絞る...結果DC,DD(値はCA,D0) に絞り込まれる。jump先か?しかし見てもよくわからん...
とりあえず自分のデータにDC,DDにも書き込むルーチンを組み込みplay!
おっしゃ!
鳴ったぜ!

んで他の「TABLE-0x」でも試す
が、ダメ

しかし頑張ってさらに頭を使う
「DC,DD」の値が面毎に違うのか...ならばそれを書き込んでいるルーチンがどこかにあるはずだ!
「85 DC」で検索...有った!(MOMOOにて)
;
6ECA : AD 28 99		lda	$9928		;成る程。各面データの「9928」を読み込んでいる
6ECD : 85 DC		sta	$DC		;DCに書き込み
6ECF : AD 29 99		lda	$9929		;同様「9929」
6ED2 : 85 DD		sta	$DD		;DDに書き込み

よしッ!裏付けが取れた!メインプログラム内にあるし間違いない!
各「TABLE-0x」の「9928,9928」にその値はある!
んじゃそのルーチンをinitに組み込んで、どうじゃ!

よっしゃーーーーーーー!
鳴る〜!

1章から9章までオッケイだ!!
(この辺で「MOMOO」は実NSFファイルには要らないことが解る)

しかしエンディングでつまずく

No.  ADDRESS SIZE  LOAD-ADDRESS TYPE NAME
---------------------------------------------
202  02051D  3500  A200 - D6FF  PRG  TABLE-EN
203  023A2E  3E95  9900 - D794  PRG  TABLE-08

エンディングデータは「A200から」なので「9928番地」は存在しないのである
あんぎゃーーす!

しかし、ココまで来たんだ。挫けずに解析だ!
オープニングのサウンドドライバがそのPRGの中にあったんだから、エンディングだってその中にあるんじゃ?

ナイ

ならば 「85 DC」で検索...

ナイ

ヌヌ、しからば「85 DD」で検索...ををッ!有った!

A2D6 : A9 D3		lda	#$D3		;
A2D8 : 85 DD		sta	$DD		;

メインプログラムとは別の所で設定していたのか
これをinitに組み込む...ン!なんか変だな。妙に遅い
「DC」の値が無いのが気になる
D300付近を見てみる

D2FA : A9 00		lda	#$00		;
D2FC : 85 16		sta	$16		;
D2FE : 85 17		sta	$17		;
D300 : 85 18		sta	$18		;ここなんだが...
D302 : 85 19		sta	$19		;
D304 : 60		rts			;

なんだかルーチンのど真ん中だな。もうちょっと見てみるか

D341 : A9 00		lda	#$00		;
D343 : 85 45		sta	$45		;
D345 : 60		rts			;ここまでがプログラムで
D346 : 26 26		rol	$26		;ここからがデータみたいだ
D348 : 2E 36 2E		rol	$2E36		;
D34B : 36 26		rol	$26,x		;

ニヤリ
「DC」に「46」を書き込み再びtry!

鳴っちゃうーーー!

後半は解析と呼べそうにない代物だが、いやいやなんのォ!

行動を起こすということが大切なのだよ!
何でもやらなきゃ始まらない
女だって、行動を起こしてるヤツはいつか出来るのさ
黙ってて女が出来るヤツは希だ!己を知れッ!
自分なりに出来るアプローチの仕方があるはずだッ!
あとは情熱だ!
そしてカン(主戦力)でゴーッ!
頑張れッ!ripper予備軍!(アレ?何か違うなぁ...

これにて各面毎の?.nsfが完成&「ripper」の称号を手に入れる
本当はこれら以外にも馬鹿なことでつまずいたりして結構時間を食ったが

しかしファイル10個もあるでよ
全部足したら80Kbyteあるし...バンク切替か...ふぅ


LEVEL20 NSFのバンク切替(新鬼ヶ島)

今まであえて避けてきたこの道
通ることを余儀なくされてしまった
さあ、行くぜッ!

バンク切替とは?

6502は素の状態だと、0000 - FFFF までのアドレスしかアクセスできない
その内0000-7FFFには、んー何と言えばよいのか、簡単に言うとROMのプログラムを置くことが出来ない
8000-FFFFは容量にして32KBだ

それ以上のデータはどうするかというと、データの場所を入れ替えてアクセスしている

$0000   +--------------------+
        |DATA 0-6            |
$F000   +--------------------+
        |DATA 7              |
$10000  +--------------------+
        |DATA 8              |←ココのデータはどうやってアクセスしているか?
        +--------------------+

$0000   +--------------------+
        |DATA 0-6            |
$F000   +--------------------+
        |DATA 8              |←こうやって入れ替えてやる。よってDATA 8をF000からアクセス可能
$10000  +--------------------+  |
        |DATA 7              |←
        +--------------------+

カンタンな例だが、理屈は簡単でしょ
じゃあ、実際はどうやっているのかというと
それぞれのマッパーに対応した各レジスタ(アドレス)に値を書き込むことで実現している

NSFでの話をしよう

NSFでは1バンクを4Kbyte毎に区切っている(マッパーによって違う
したがって4Kbyteごとの入れ替えとなる
で、その各バンクには番号が割り振られる
データは8000からしか置けない(NSFの仕様)

ADDR  BankNo.     BankNo.     BankNo.
$8000   +-------+   +-------+   +-------
       0|       |  8|       | 10|
$9000   +-------+   +-------+   +-------
       1|       |  9|       | 11|
$A000   +-------+   +-------+   +-------
       2|       |  A|       | 12|
$B000   +-------+   +-------+   +-------
       3|       |  B|       | 13|
$C000   +-------+   +-------+   +-------
       4|       |  C|       | 14|
$D000   +-------+   +-------+   +-------
       5|       |  D|       | 15|
$E000   +-------+   +-------+   +-------
       6|       |  E|       | 16|
$F000   +-------+   +-------+   +-------
       7|       |  F|       | 17|
        +-------+   +-------+   +-------

こんな感じに

バンクを切り替えるときは「5FF8 - 5FFF」が「8000 - F000」に対応していて
5FF8に08を書き込むと、

ADDR  BankNo.     BankNo.     BankNo.
$8000   +-------+   +-------+   +-------
       0|       |  8|       | 10|
$9000   +-------+   +-------+   +-------

         ↓
----------------------------------------
xxxx A9 08     LDA 08    :8を代入
xxxx 8D F8 5F  STA 5FF8  :5FF8に書き込み
----------------------------------------
         ↓

$8000   +-------+   +-------+   +-------
       8|       |  0|       | 10|
$9000   +-------+   +-------+   +-------

となる
つまり入れ替わるわけだ

NSFの場合、ヘッダーで初期配置を設定してやる必要があるので

      0  1  2  3  4  5  6  7
0070 00 00 00 00 00 01 00 00

0070が8000、0071が9000からと言うふうに対応しているので
そこに各バンク番号を入れてやればよい

      0  1  2  3  4  5  6  7
0070 10 08 01 00 0F 17 05 00

とすれば

ADDR  BankNo.
$8000   +-------+
      10|       |
$9000   +-------+
       8|       |
$A000   +-------+
       1|       |
$B000   +-------+
       0|       |
$C000   +-------+
       F|       |
$D000   +-------+
      17|       |
$E000   +-------+
       5|       |
$F000   +-------+
       0|       |
        +-------+

という並びになる
あくまでも初期配置ね
必要に応じて「5FFx」で切り替えてやる

何だ簡単じゃないか
案ずるより産むが易しだったなー

じゃあ、早速鬼ヶ島にトライ!
しかしデータサイズがマチマチ何だよな
B200からのも有れば、CD00からのもある
バンクまたがってるよなぁ

2バンクの切替となると...
4K(Bank) x 2(データが跨ってるので) x 10(ファイル数)で80KBか...結構でかいな

しばし思考中...

ん!DC,DDの値(LEVEL17参照)はどうみてもデータの場所を示すっぽかった
その場所を見てみる
んーデータの羅列だなぁ

よしッ!アドレス指定ナゾは無いモノとして進めてみよう
初めての移植作業ナリ

まずはそのデータを抽出
9928,9929の値を参考にデータを削る(指定されていたデータが必ず先頭に来るようにする)
後ろの方はプログラムっぽいので、4K毎という制限用に1つのデータを2Kまで後ろから削る
んで連結

SIZE
$0000   +-------+
        |DATA 1 |1章のデータ
$0800   +       +
        |DATA 2 |2章のデータ
$1000   +-------+
        |DATA 3 |3章のデータ
$1800   +       +
        |DATA 4 |4章のデータ
$2000   +-------+
        |DATA 5 |5章のデータ
$2800   +       +
        |DATA 6 |6章のデータ
$3000   +-------+
        |DATA 7 |7章のデータ
$3800   +       +
        |DATA 8 |8章のデータ
$4000   +-------+
        |DATA 9 |9章のデータ
$4800   +       +
        |DATA 10|エンディングのデータ
        +-------+

これをオープニングのNSFデータに連結

ADDR  BankNo.
$CC30   +-------+
       0|DATA 0 |オープニングのデータ
$D000   +       +&サウンドドライバ
       1|       |
$E000   +-------+
       2|DATA 1 |1章のデータ
$ 800   +       +
        |DATA 2 |2章のデータ
$F000   +-------+
       3|DATA 3 |3章のデータ
$ 800   +       +
        |DATA 4 |4章のデータ
$10000  +-------+
       4|DATA 5 |5章のデータ
$  800  +       +
        |DATA 6 |6章のデータ
$11000  +-------+
       5|DATA 7 |7章のデータ
$  800  +       +
        |DATA 8 |8章のデータ
$12000  +-------+
       6|DATA 9 |9章のデータ
$  800  +       +
        |DATA 10|エンディングのデータ
        +-------+

バンク番号は実際にデータを置いているところから数えるので、
今回のようにCC30からデータを置いていると
C000-CFFFがバンク番号0(ゼロ)となる
(コレに気付かず、8000からバンク番号を数えていてチョットハマッタ...)

で実際の動作は
データを8000から置くことにして
4K毎の切替とDD,DCの値を8000と8800に切り替えて鳴らすようにする

試しに上記の配置で7章のデータを鳴らす

xxxx A9 05     LDA 05    :バンク番号「5」を
xxxx 8D F8 5F  STA 5FF8  :8000にロードする
xxxx A9 00     LDA 00    :曲データ開始位置の指定
xxxx 85 DC     STA DC    :
xxxx A9 80     LDA 80    :
xxxx 85 DD     STA DD    :曲データが8000から始まることを書き込む
xxxx A9 01     LDA 01    :曲番号「1」を
xxxx 8D FF 07  STA 07FF  :書き込む
xxxx 60        RTS       :おしまい

鳴ったぜー!
よっしゃぁ!
カン(主戦力)が冴えわたるぅッ!

あとは順番に鳴らすルーチンを作るだけ!
こ、これが上手く思いつかなかった...この辺がフツーの人(?)とプログラマの違いなんだなぁ

よってベタなルーチンを作成

CBC0 : AA		tax			:
CBC1 : 48		pha			;初期化
CBC2 : BD 60 CB		lda	$CB60,x		;
CBC5 : 8D F8 5F		sta	$5FF8		;バンク切替
CBC8 : D0 0B		bne	$CBD5		;オープニングはプレイアドレスが違うので、バンク番号がゼロ以外なら分岐
CBCA : A9 38		lda	#$38		;オープニングのプレイアドレス指定
CBCC : 85 00		sta	$00		;
CBCE : A9 CC		lda	#$CC		;
CBD0 : 85 01		sta	$01		;
CBD2 : 4C DD CB		jmp	$CBDD		;
CBD5 : A9 00		lda	#$00		;オープニング以外のプレイアドレス指定
CBD7 : 85 00		sta	$00		;
CBD9 : A9 D8		lda	#$D8		;
CBDB : 85 01		sta	$01		;
CBDD : A9 00		lda	#$00		;サウンドデータのアドレス指定
CBDF : 85 DC		sta	$DC		;
CBE1 : BD 80 CB		lda	$CB80,x		;
CBE4 : 85 DD		sta	$DD		;
CBE6 : BD A0 CB		lda	$CBA0,x		;曲順データ読み込み
CBE9 : 8D FF 07		sta	$07FF		;
CBEC : 68		pla			;
CBED : 60		rts			;おしまい!

わかりやすッ!
そう、一曲毎にバンクとデータアドレスと曲順データを必要とする、データが大きくなるベタなルーチンだ!
だって考えててこんがらがってきちゃって...
でもコレなら曲を直接指定るので、重複曲の多い鬼ヶ島にはうってつけなんだよぅ(8章のデータはまるまる要らないし)

それに聴けりゃいいだろッ!
(何回このセリフを吐いたか...)

ちなみにplayは

CBF0 : 6C 00 00		jmp	($0000)		;

initで書き込んだ先にジャンプするようになっているって説明するまでもないか...
まあコレで完成!
データサイズも24KBと我ながら上出来だ!!

レベルが上がっていくぜッ!!!

あ、効果音入れてねーや
いいや
また今度

しかし険しい道だ...

注)minachunさんのドラスレ4解析結果をふんだんに流用(パクリ)してます
  そっちを見た方が分かり易い...って意味ネーじゃん!

ファミコンについて(NSF用)

ファミコンの基本

ファミコンはカスタム6502(CPU)と、PPU(ピクチャ処理ユニット)というカスタムビデオコントローラから成り立つ。

以下カスタム6502CPUのメモリーマップを示す
アドレス説明
0000〜07FFRAM
0800〜0FFFRAM (0000のミラー)
1000〜17FFRAM (0000のミラー)
1800〜1FFFRAM (0000のミラー)
2000〜4FFFレジスタ
5000〜5FFF拡張モジュール
6000〜6FFFSRAM
7000〜7FFFSRAM または Trainer (RAM)
8000〜BFFFPRG-ROM (低位)
C000〜FFFFPRG-ROM (高位)
PPUメモリマップやスプライト、BGスクロール・ジョイスティックなどは省略。
NSFには関係なさそう(?)なので。

何の事だか解らない人もいるだろうが、8000〜が重要と覚えて欲しい。

ロムカートリッジ

初期のカセットにはROMが2つ載っている。
プログラムロム(以下PRG)とキャラクタロム(以下CHR)だ。
その名の通りプログラムはPRGに、キャラクタデータはCHRに格納されている。
サウンドデータプログラムなのでPRGの中に入っている

一般的なnesイメージは簡単に言うと、HEADER+PRG+CHRとなっている。
BioNESとかでゲーム起動時に画面左下に表示されるアレです。Nesticleならイメージ選択時だね。

PRGには15bitのアドレスバス、8bitのデータバス
CHRには12bitのアドレスバス、8bitのデータバス

プログラムロム      キャラクターロム
   2^15*8=32KB       2^12*8=8KB

つまり素のROMは合計40KBまでしかアクセスできない。
初期のゲームのイメージを見てみると40KB(マッパー0)が殆ど。実際にはヘッダー(16byte)+という形になってると思うけど。

じゃあ32KBの制限を越えたPRGをアドレスし、8KB以上のCHRを同時にアクセスするためにはゲームはどうしているのかというと、バンク切替をしている
バンク切替とはコネクタから出ていないアドレス信号をむりやり作る(MMCを使う)事をいう、らしい。この辺はまだ解らないです。
しかしサウンドでバンク切替をするほどのゲームはそんなに無い(と思う)のでまだ気にしなくて良いかな?
まあ、RPGやSLGで曲数の多い物なんかはしていると思うが。

実際にロムカートリッジのプログラム部(PRG)は8000〜FFFFの場所に読み込まれる
C000から読み込まれるゲームはBattle City, Mario Brothers,Millipede, Nuts & Milk, Tennis等がある。昔の容量の少ないゲームだね
PRG-ROM空間全体を使う最初のゲームは、スーパーマリオブラザーズ(らしい)。

重要なのはこの中(8000-FFFF)にサウンドデータが読み込まれているって事です。
その部分をカセットの中から探し出すのが、まず第一歩というわけです。
このバンク切替、最小単位が16KBらしいのでイメージを分割する際16KB(32K)毎に分割するです。

割り込みまんま転載

6502にはIRQ/BRK、NMI、RESETの3つの割り込みがある。

各割り込みは自分のベクタを持ち、通常、ある実行されるコードを指す。
ベクタとは、割り込みが起きる時に「ジャンプする」位置を指定する16bitのアドレスである。
IRQ/BRK
6502がBRK命令を実行する時、IRQ/BRKが起きる。 BRKは多くのことに使用できるが、一般的にマシンが不幸な状態になるので、めったに使われない。 しかし、エミュレータ著者のために、いくつかのゲームがIRQ/BRKベクタにジャンプを強制するためにBRKを使うので、BRKを必ず正確にエミュレートする。
NMI
マスク不能割り込みを表わし、各リフレッシュ(VBlank/VBL)で生成され、 使用されたシステムに依存して異なる間隔で起こる。
RESET
電源投入時に起きる。 ROMがメモリにロードされ、6502はRESETベクタで指定されたアドレスへジャンプする。レジスタは変更されず、メモリはクリアされない。これらは電源投入時だけに起こる。
NMIに戻る。
2000のビット7が1にセットされる場合、NMIは50回/秒だけ実行される。 これは、リフレッシュが起こる度に割り込みを生成する(より明確にはNMIを実行する)ようにNESに命じる。 6502の割り込み遅延は7サイクルである。これは、割り込みに入ると出るのに7サイクルかかることを意味する。
VBlank割り込みが生じる場合、CPUはリターンアドレスとステータスレジスタをスタックに積んで、位置$FFFA(NESのROM)で格納されたアドレスへジャンプする。

ほとんどの割り込みはRTI命令を使用してリターンするべきである。 ファイナルファンタジー1のようないくつかのNESカートリッジは、この方法を使用しない。 これらのカートリッジは非常に奇妙な方法で割り込みからリターンする。手でスタックを操作し、次にRTSでリターンする。 これは技術的に有効であるが、道義的に「悪い」。あなたがエミュレータ著者ならば、必ずすべてのオペコードを正しく実装する。 そうすれば、素晴らしいはずである。ゲームコードは残りを世話する。

優先度順
PriorityInterrupt
HighestRESET
 NMI
LowestIRQ/BRK

ベクタポイント
VectorInterrupt
FFFANMI
FFFCRESET
FFFEIRQ/BRK


*************************** Hit/VBlankビット ****************************
読み出し専用の位置$2002の第7ビットにVBlankフラグを含んでいる。それは、PPU
がスクリーンを走査しているか、垂直帰線信号を生成しているかを示す。各フレー
ムの最後(スキャンライン232)でセットし、スキャンライン8からの次のスクリーン
リフレッシュ開始まで残る。プログラムは、$2002から読むことによりこのビットを
早々にリセットできる。
読み出し専用の位置$2002の第6ビットにHitフラグを含んでいる。スプライト#0が位
置する場所で、PPUが最初のスキャンラインをリフレッシュし始める時、1になる。
例えば、スプライト#0のY座標が34である場合、スキャンライン34でHitフラグをセッ
トする。垂直帰線信号が開始する時、Hitフラグをリセットする。プログラムは、
$2002から読むことによりこのビットを早々にリセットできる。
+------------------------------------------------------------------------+
VBlankフラグは、読み出し専用の位置$2002の第7ビットに含まれている。 PPUがス
クリーンを走査しているか垂直帰線信号を生成しているかを示す。 各フレームの最
後(スキャンライン232)でセットされ、スキャンライン8からの次のスクリーンリフ
レッシュ開始まで残る。 プログラムは、$2002を読むことによりこのビットを早々
にリセットできる。
Hitフラグは、読み出し専用の位置$2002の第6ビットに含まれている。 スプライト
#0が位置する場所で、PPUが第1のスキャンラインをリフレッシュし始める時、1にな
る。 例えば、スプライト#0のY座標が34の場合、Hitフラグはスキャンライン34でセ
ットされる。 垂直帰線信号が始まる時、Hitフラグがリセットされる。 プログラム
は、$2002から読むことによりこのビットを早々にリセットできる。
********************************************************************************

レジスタまんま転載

アドレス:(ROMの中の)レジスタの16ibtのアドレス
統計:各レジスタはそれに対して異なる統計と「様相」がある
これらの統計は次のように定義される
R = レジスタは読み込み可能
W = レジスタは書き込み可能
2 = 「二度書き」レジスタ
? = 統計は未知か、または恐らく間違っている
ビット:ほとんどのレジスタは、トグルでon/offできるビットを持っており、NESに様々なことを行う。
「-」 = 未使用
「?」 = 未知
[ラベル]:(プログラマのために)各レジスタに割り当てたラベル
|
アドレス統計ビット説明[ラベル]
2000WvhzcpwNNPPU制御レジスタ #1[PPUCNT0]
v = VBlank時にNMIを実行
0 = 無効
1 = 有効
h = スプライトHit時にNMIを実行
0 = 無効
1 = 有効
z = スプライトサイズ
0 = 8x8
1 = 8x16
c = スクリーンパターンテーブルアドレス
0 = $0000 (VRAM)
1 = $1000 (VRAM)
p = スプライトパターンテーブルアドレス
1 = $1000 (VRAM)
0 = 1ずつ増加
1 = 32ずつ増加
N = ネームテーブル選択
00 = $2000 (VRAM)
01 = $2400 (VRAM)
10 = $2800 (VRAM)
11 = $2C00 (VRAM)
2001WfffpcSItPPU制御レジスタ #2 [PPUCNT1]
f = Full Background カラー
000 = None \
001 = Red \一つだけ選択
010 = Green /
100 = Blue /
p = スプライト表示
0 = Hide sprites
1 = Show sprites
c = スクリーン表示
0 = Off (screen off)
1 = On (screen on)
S = スプライトクリップ
0 = Don't show sprites in the left
   8-pixel column
1 = Show sprites everywhere
I = イメージクリップ
0 = Don't show the left 8 pixels of
   the screen
1 = Show the left 8 pixels
t = カラー表示
0 = モノクロ表示
1 = カラー表示
2002RvhsW----PPUステータスレジスタ [PPUSTAT]
v = VBlank発生フラグ
0 = No VBlank
1 = VBlank
h = Hit発生フラグ
0 = No hit
1 = Refresh has hit スプライト#0
s = スプライトCount Max
0 = 現在のスキャンラインに8未満
1 = 現在のスキャンラインに8以上
W = PPU書き込みステータスフラグ
1 = VRAMへの書き込みは有効
1 = VRAMへの書き込みは無視
注:ビット#6は次のリフレッシュの初めに0にリセットされる。
注:最初の実際のピクセル(つまり透明でない)が描画されるまでビット#6はセットされない。したがって、最初の4ピクセルが透明で、5番めが不透明な値のスプライト(8x8)があれば、5番めのピクセルが見つかって描画された後、ビット6がセットされる。
2003Waaaaaaaaスプライトメモリアドレス [SPRADDR]
2004でアクセスするスプライトRAMのアドレスを指定する。
2004WddddddddスプライトI/Oレジスタ [SPRIO]
アドレス$2003で指定されたスプライトRAMの読み書きに使用する。
2005W2ddddddddBGスクロールレジスタ [BGSCROL]
スクリーンの垂直と水平スクロールに使う。
これは二度書きレジスタである。
バイト1:水平スクロール
バイト2:垂直スクロール
スクロールされたデータは複数のネームテー
ブルにまたがる。配置は次のとおりである
・#2 ($2800) ・#3 ($2C00)
・#0 ($2000) ・#1 ($2400)
注:垂直スクロール値が>239の場合は無視される。値が>239の場合、いくつかのエミュレータは垂直スクロールに0を書く。
注:2つのネームテーブルだけに十分なVRAMがあることを思い出す。
注:VBLが生じた後、次の書き込みは水平スクロールを制御する。
2006W2aaaaaaaaPPUメモリアドレス [PPUADDR]
データを読み書きする際のVRAMのアドレスを指定する。これは二度書きレジスタである。
16ビットアドレスの高位バイトを最初に、低位バイトを後に書く。
2007RWddddddddPPU I/Oレジスタ [PPUIO]
$2006で指定されたアドレスのVRAMの読み書きに使用する。
4000RWCChessss矩形波制御レジスタ #1
C = Duty Cycle (Positive vs. Negative)
00 = 87.5%
01 = 75.0%
10 = 58.0%
11 = 25.0%
h = Hold Note
0 = Don't hold note
1 = Hold note
e = エンベローブ選択
0 = エンベローブ可変
1 = エンベローブ固定
s = 再生レート
4001RWfsssHrrr矩形波制御レジスタ #2
f = 周波数固定/可変選択
0 = 固定 (ビット0-6無効)
1 = 可変 (ビット0-6有効)
s = 周波数変更速度
H = Low/High 周波数選択
0 = Low -> High
1 = High -> Low
r = 周波数レンジ(0=最小, 7=最大)
4002RWdddddddd矩形波周波数値レジスタ #1
d = 周波数値データ (下位8ビット)
4003RWtttttddd矩形波周波数値レジスタ #2
d = 周波数値データ (上位3ビット)
t = Active Time Length
注:周波数値は全部で11ビットのサイズであり、4003に上位3ビットを書く必要があることに注意する。
4004RWCChessss矩形波制御レジスタ #1
C = Duty Cycle (Positive vs. Negative)
00 = 87.5%
01 = 75.0%
10 = 58.0%
11 = 25.0%
h = Hold Note
0 = Don't hold note
1 = Hold note
e = エンベローブ選択
0 = エンベローブ可変
1 = エンベローブ固定
s = 再生レート
4005RWfsssHrrr矩形波制御レジスタ #2
f = 周波数固定/
0 = 固定 (ビット0-6無効)
1 = 可変 (ビット0-6有効)
s = 周波数変更速度
H = Low/High 周波数選択
0 = Low -> High
1 = High -> Low
r = 周波数レンジ(0=最小, 7=最大)
4006RWdddddddd矩形波周波数値レジスタ #1
d = 周波数値データ (下位8ビット)
4007RWtttttddd矩形波周波数値レジスタ #2
d = 周波数値データ (上位3ビット)
t = Active Time Length
注:周波数値は全部で11ビットのサイズであり、$4007に上位3ビットを書く必要があることに注意する。
4008RWCChessss三角波制御レジスタ #1
C = Duty Cycle (Positive vs. Negative)
00 = 87.5%
01 = 75.0%
10 = 58.0%
11 = 25.0%
h = Hold Note
0 = Don't hold note
1 = Hold note
e = エンベローブ選択
0 = エンベローブ可変
1 = エンベローブ固定
s = 再生レート
4009RWfsssHrrr三角波制御レジスタ #2
f = 周波数固定/可変選択
0 = 固定 (ビット0-6無効)
1 = 可変 (ビット0-6有効)
s = 周波数変更速度
H = Low/High 周波数選択
0 = Low -> High
1 = High -> Low
r = 周波数レンジ(0=最小, 7=最大)
400ARWdddddddd三角波周波数値レジスタ #1
d = 周波数値データ (下位8ビット)
400BRWtttttddd三角波周波数値レジスタ #2
d = 周波数値データ (上位3ビット) |
t = Active Time Length
注:周波数値は全部で11ビットのサイズであり、$400Bに上位3ビットを書く必要があることに注意する。
400CRWCChessssノイズ制御レジスタ #1
C = Duty Cycle (Positive vs. Negative)
00 = 87.5%
01 = 75.0%
10 = 58.0%
11 = 25.0%
h = Hold Note
0 = Don't hold note
1 = Hold note
e = エンベローブ選択
0 = エンベローブ可変
1 = エンベローブ固定
s = 再生レート
400DRWfsssHrrrノイズ制御レジスタ #2
f = 周波数固定/可変選択
0 = 固定 (ビット0-6無効)
1 = 可変 (ビット0-6有効)
s = 周波数変更速度
H = Low/High 周波数選択
0 = Low -> High
1 = High -> Low
r = 周波数レンジ(0=最小, 7=最大)
400ERWdddddddd周波数値レジスタ #1
d = 周波数値データ (下位8ビット)
400FRWtttttddd周波数値レジスタ #2
d = 周波数値データ (上位3ビット)
t = Active Time Length |
注:周波数値は全部で11ビットのサイズであり、$400Fに上位3ビットを書く必要があることに注意する。
4010RWCChessssPCM制御レジスタ #1
C = Duty Cycle (Positive vs. Negative)
00 = 87.5%
01 = 75.0%
10 = 58.0%
11 = 25.0%
h = Hold Note
0 = Don't hold note
1 = Hold note
e = エンベローブ選択
0 = エンベローブ可変
1 = エンベローブ固定
s = 再生レート
4011RWvvvvvvvvPCM音量制御レジスタ
v = 音量
4012RWaaaaaaaaPCMアドレスレジスタ
a = アドレス
4013RWLLLLLLLLPCMデータ長レジスタ
L =データサイズ/長さ
4014W スプライトDMA         [SPRDMA]
このレジスタに書かれた値がNとすると、アドレス$100*NのメモリをスプライトRAMへ256バイト転送する。
4015RW---abcdeサウンド制御レジスタ [SNDCNT]
e = チャンネル1 (0=無効, 1=有効)
d = チャンネル2 (0=無効, 1=有効)
c = チャンネル3 (0=無効, 1=有効)
b = チャンネル4 (0=無効, 1=有効)
a = チャンネル5 (0=無効, 1=有効)
4016RW???STeedジョイパッド #1 [SPECIO1]
[READING]
S = Zapper sprite detection
0 = スプライトnot detected
1 = スプライトdetected in front of crosshair
T = Zapper trigger
0 = Pressed
1 = Not pressed
e = 拡張ポートデータ
d = ジョイパッドデータ
  ?????eej[WRITING]
j = ジョイパッドストローブ
0 = Clear joypad strobe
1 = Reset joypad strobe
e = 拡張ポートデータ
4017R???STeedジョイパッド #2 [SPECIO2]
S = Zapper sprite detection
0 = スプライトnot detected
1 = スプライトdetected in front of crosshair
T = Zapper trigger
0 = Pressed
1 = Not pressed
e = 拡張ポートデータ
d = ジョイパッドデータ

サウンド(まんま転載)

NESの中で最も面白い面の1つはサウンドサポートであり、PCMレジスタ以外は真のアナログである。
完全なサウンド定数は、次のように思える

111860.78 (e.g. 3579545/32)

これはセガジェネシス内のTI 76486 PSGサウンドプロセッサ中で使
われるサウンド定数から得られた。

矩形波と三角波のチャンネルのために、1つの公式をNESのサウンドの正確な再生を
提供するために使用できる

P = 111860.78 / (CHx + 1)

「P」が実際の演奏データであり、CHxが演奏チャンネルとする。 チャンネル公式は下記である

CH1 = $4002 + ($4003 & 7) * 256 (矩形波 #1)
CH2 = $4006 + ($4007 & 7) * 256 (矩形波 #2)
CH3 = $400A + ($400B & 7) * 256 (三角波)

ここで$400x値はそのレジスタに書かれた実際の値である。
PCMチャンネルのために、実装の2つの方法がある:DMAとPCMボリュームポート($4011)によって。

サンプルは$4011へ1バイトずつ送られ、結果として全く聞こえる。 しかし、大部分
がDMA転送アプローチを使う一方、少数のだけのゲームはこの方法を使うように思える。

OPコード(まんま転載)
ファミコンは下記の命令に従って動く
A,X,Y,Sレジスタっていうのは、それぞれに用途の決まった変数のことだと思えばいいのかな?
っていうかストアとかプッシュって何?

命令表

LDA  ... Aレジスタにロードする
LDX  ... Xレジスタにロードする
LDY  ... Yレジスタにロードする
STA  ... Aレジスタをストアする
STX  ... Xレジスタをストアする
STY  ... Yレジスタをストアする

TXA  ... XレジスタをAレジスタにコピーする
TYA  ... YレジスタをAレジスタにコピーする
TXS  ... XレジスタをSレジスタにコピーする
TAY  ... AレジスタをYレジスタにコピーする
TAX  ... AレジスタをXレジスタにコピーする
TSX  ... SレジスタをXレジスタにコピーする

PHP  ... フラグをプッシュする
PLP  ... フラグをポップする
PHA  ... Aレジスタをプッシュする
PLA  ... Aレジスタをポップする

ADC  ... Aレジスタに加算する
SBC  ... Aレジスタから減算する
CMP  ... Aレジスタと比較する
CPX  ... Xレジスタと比較する
CPY  ... Yレジスタと比較する

AND  ... AレジスタとAND演算をする
EOR  ... AレジスタとEX-OR演算をする
ORA  ... AレジスタとOR演算をする
BIT  ... AレジスタとAND比較をする

ASL  ... 左シフト
LSR  ... 右シフト
ROL  ... 左ローテイト
ROR  ... 右ローテイト

INC  ... 1を加算する
INX  ... Xレジスタに1を加算する
INY  ... Yレジスタに1を加算する
DEC  ... 1を減算する
DEX  ... Xレジスタから1を減算する
DEY  ... Yレジスタから1を減算する

CLC  ... Cフラグをクリア
CLI  ... Iフラグをクリア
CLV  ... Vフラグをクリア
CLD  ... Dフラグをクリア
SEC  ... Cフラグをセット
SEI  ... Iフラグをセット
SED  ... Dフラグをセット

NOP  ... 何もしない
BRK  ... ソフトウエア割り込み

JMP  ... ジャンプ
JSR  ... サブルーチン呼び出し
RTS  ... サブルーチンから復帰
RTI  ... 割り込み処理から復帰


アドレッシングモード

上記の命令表との組み合わせになる
none ...
なし。命令単体。
例) TXA
A ← X
imm ...
直値。指定された値をそのまま使用。
例) LDA #20h
  A ← 20h
zero ...
ゼロページ。指定された8bitアドレスを用いて0000h〜00FFhをアクセスする。absより高速だが範囲が狭い。
例) LDA $20h
  A ← (0020h)
abs ...
絶対番地。指定された16bitアドレスをアクセスする。
例) LDA 8123h
  A ← (8123h)
zerox ...
ゼロページX。指定された8bitアドレスにXレジスタの内容を加算したアドレスにアクセスする。
例) LDA $20h,X
  A ← (0020h + X)
zeroy ...
ゼロページY。指定された8bitアドレスにYレジスタの内容を加算したアドレスにアクセスする。
例) LDX $20h,Y
  X ← (0020h + Y)
absx ...
絶対番地X。指定された16bitアドレスにXレジスタの内容を加算したアドレスにアクセスする。
例) LDA 8123h,X
  A ← (8123h + X)
absy ...
絶対番地X。指定された16bitアドレスにYレジスタの内容を加算したアドレスにアクセスする。
例) LDA 8123h,Y
  A ← (8123h + Y)
indx ...
インダイレクトX。指定された8bitアドレスにXレジスタの内容を加算したアドレスから2バイトを読み出し、その値を16bitアドレスとしてアクセスする。
例) LDA (20h,X)
  A ← ( (0020h + X) )
indy ...
インダイレクトY。指定された8bitのアドレスから2バイトを読み出し、その値にYレジスタの内容を加算して16bitアドレスとしてアクセスする。
例) LDA (20h),Y
  A ← ( (0020h) + Y )

コード表

  noneimmzeroabszeroxzeroyabsxabsyindxindy
LDA___ A9 A5 AD B5___ BD B9 A1 B1
LDX___ A2 A6 AE___ B6___ BE______
LDY___ A0 A4 AC B4___ BC_________
STA______ 85 8D 95___ 9D 99 81 91
STX______ 86 8E___ 96____________
STY______ 84 8C 94_______________
TXA 8A___________________________
TYA 98___________________________
TXS 9A___________________________
TAY A8___________________________
TAX AA___________________________
TSX BA___________________________
PHP 08___________________________
PLP 28___________________________
PHA 48___________________________
PLA 68___________________________
ADC___ 69 65 6D 75___ 7D 79 61 71
SBC___ E9 E5 ED F5___ FD F9 E1 F1
CPX___ E0 E4 EC__________________
CPY___ C0 C4 CC__________________
CMP___ C9 C5 CD D5___ DD D9 C1 D1
AND___ 29 25 2D 35___ 3D 39 21 31
EOR___ 49 45 4D 55___ 5D 59 41 51
ORA___ 09 05 0D 15___ 1D 19 01 11
BIT______ 24 2C__________________
  noneimmzeroabszeroxzeroyabsxabsyindxindy
ASL 0A___ 06 0E 16___ 1E_________
LSR 4A___ 46 4E 56___ 5E_________
ROL 2A___ 26 2E 36___ 3E_________
ROR 6A___ 66 6E 76___ 7E_________
INX E8___________________________
INY C8___________________________
INC______ E6 EE F6___ FE_________
DEX CA___________________________
DEY 88___________________________
DEC______ C6 CE D6___ DE_________
CLC 18___________________________
CLI 58___________________________
CLV B8___________________________
CLD D8___________________________
SEC 38___________________________
SEI 78___________________________
SED F8___________________________
NOP EA___________________________
BRK 00___________________________
JSR_________ 20__________________
JMP_________ 4C__________________
RTI 40___________________________
RTS 60___________________________
  noneimmzeroabszeroxzeroyabsxabsyindxindy