NSFripperへの道

そーいやNSFってなんなのかの説明がなされてなかった。
迂闊だったな。

NSFに限らず、コンシューマ系サウンドエミュデーターってのは、
イメージの中からの解析吸い出しが必要になる。
アーケード系はサウンド関係が別ROMで独立してる事が多いので、
データに関してはわざわざ解析する必要がない(事が多い)。

話を戻して、
コンシューマのゲームカセットやディスクなどの中にゲームのプログラムが全てつまってる。
その中から、サウンドドライバ(プログラム)とサウンドデータ(データ)を抜き出し
各サウンドエミュの規格で聴けるように加工を施したモノ、それの規格の一つがNSFというワケだ。

ちなみにその規格を立ち上げたのが、NES界のサウンド関係では有名なKevin HortonとChris Covellです
こいつらには感謝の念でいっぱいであります。
初めてNSFを知ったときの衝撃は今でも忘れない...
誤解されてる人もいるのではないかと思うが、
コレは立派なエミュレーターです。サウンドの。あ、プレイヤーがね。

「成る程理屈は分かったよ。じゃぁどーやってデーター作んの?」

データを抜き出すには、データがどこか?ってのを解析しないとダメなんですな。
で、このRipperへの道というのは、

「これを読めばファミコンの構造を知らないド素人でもNSFを作成できるようになる(といーなぁ)」

という代物です。随分大きくでたなぁ...ま、大丈夫でしょ。
これを読んで、アナタも今日からRipperの仲間入りだ!!!

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

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

データを作成できたら情報よろしく
レベル別  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には関係なさそう(?)なので。

と書いたはイイが、どうやら曲データをPPU転送しているモノがあるらしく、これらも覚えないとダメそう...

何の事だか解らない人もいるだろうが、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を使う)事をいう。
実際のバンク切替の方法だが、マッパー事に決められたアドレスへのアクセスで実現している。 具体的な挙動はLEVEL20を参考にしていただきたい。マッパーによって切り替えるサイズ、指定するアドレスなどが変わるわけだ。 しかしサウンドでバンク切替をするほどのゲームはそんなに無い(と思う)のでまだ気にしなくて良いかな?
まあ、RPGやSLGで曲数の多い物なんかはしていると思うが。
と書いたが、実際にはありましたな。流石にサウンドドライバ内でバンク切り替えしてるヤツは分からんが 大概はサウンドデータアクセスのために切り替えてますな。
実際にロムカートリッジのプログラム部(PRG)は8000〜FFFFの場所に読み込まれる
C000から読み込まれるゲームはBattle City, Mario Brothers,Millipede, Nuts & Milk, Tennis等がある。昔の容量の少ないゲームだね
PRG-ROM空間全体を使う最初のゲームは、スーパーマリオブラザーズ(らしい)。

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

割り込み


当初なんの事だかサッパリだったが、なんとなく分かったので解説。
読んで字のごとく、プログラムを処理中何らかの条件を満たせば
現在のプログラムを中断し、割り込み処理用のプログラムを処理しはじめるということ。

6502CPUの割り込みには3種類ある。
それぞれ、IRQ/BRK,NMI(VBlank/Hit),RESETとなっている。
役割は...

IRQ/BRK
	6502がBRK命令を処理するときにココが呼ばれる。
	命令の中で呼び出す割り込みですな。
	なんだかマシンが不幸な状態になるのであまり使われないらしい。
	
NMI(VBlank/Hit)
	解析時に一番重要となるであろう割り込みです。
	VBlank/Hitとかなんのことかやらだが、NMIのアドレスにジャンプ(割り込み)するのに、
	VBlank(一定周期)かHit(スプライト衝突時)かの二種類方法があるということですな。
	割り込み時は、その時のリターンアドレスとステータスをスタックに保存してからジャンプ
	6502の割り込み遅延は7サイクルである。	これは、割り込みに入ると出るのに
	7サイクルかかることを意味する。(コレよく分からんまま)

	VBlank
	VBlank有効無効は、レジスタ2000のbit7で指定。(レジスタ参照)
	各リフレッシュで生成され、使用されたシステムに依存して異なる間隔で起こる。
	要は一定間隔で呼び出される処理のことだね。
	VBlank有効時、ココの割り込みは1秒間に50回呼び出される。(50回ってPALの事だよね?
	リフレッシュが起こるたびにNMIが実行される(だからNTSCでは60回のハズ...

	Hit
	Hit有効無効は、レジスタ2000のbit6で指定。(レジスタ参照)
	スプライト衝突時にNMI実行(だと思う。NSFではこの割り込みで解析したことがない...

	VBlank/Hitの状態を知るには、レジスタ2002のbit7と6でそれぞれを確認できる。
	以下そのまんま(ぐはぁ

	VBlankフラグ(2002のbit7)
	PPUがスクリーンを走査しているか垂直帰線信号を生成しているかを示す。
	各フレームの最後(スキャンライン232)でセットされ、スキャンライン8からの
	次のスクリーンリフレッシュ開始まで残る。
	プログラムは、$2002を読むことによりこのビットを早々にリセットできる。

	Hitフラグ(2002のbit6)
	スプライト#0が位置する場所で、PPUが第1のスキャンラインをリフレッシュし始める時、
	1になる。 例えば、スプライト#0のY座標が34の場合、Hitフラグはスキャンライン34で
	セットされる。 垂直帰線信号が始まる時、Hitフラグがリセットされる。
	プログラムは、$2002から読むことによりこのビットを早々にリセットできる。

RESET
	電源投入時に起こる。
	いっちゃん始めの処理ですな。
	レジスタは変更されず、メモリはクリアされない。これらは電源投入時だけに起こる。 

割り込み処理の飛び先だが、ROMカセットなら
MEM address
      0  1  2  3  4  5  6  7  8  9  A  B  C  D  E  F
FFF0 xx xx xx xx xx xx xx xx xx xx xx xx xx xx xx xx
                                   IRQ   NMI   RESET
とアドレスが決まっており(ベクタポイントというらしい)

      0  1  2  3  4  5  6  7  8  9  A  B  C  D  E  F
FFF0 xx xx xx xx xx xx xx xx xx xx 00 FF 01 A0 00 80
となっている場合は

 BRK命令で → $FF00
一定間隔で → $A001
電源投入時 → $8000

へそれぞれが飛ぶということ。
ちなみにディスクシステムの場合、ベクタポイントは$DFFAからの6バイトになる。
さらに言えばディスクの場合ココはRAM領域となるので、
プログラムにて随時書き換えが可能な点も注意しておきたい。
まーROMでもバンク切替で変更できるけどさ。

レジスタ

レジスタって何よ?

恐らく、勉強しはじめの時はこの辺の概念がサッパリだろう。
オレも何の事だか分からなかったモンさ。
んな偉そうに言えるほど理解してるワケじゃないが。
じゃ、いってみようか。

まず、
8bit系のCPUというのはアドレスが0000〜FFFF(65535)までの64kBしか扱えない。
さらにそのアドレス一個一個に00〜FF(255)までの値を出し入れしているに過ぎない。(制限も多いが
その65536個の中に、256個の数値が入り乱れているだけでしかないのだ。
じゃぁどーやって絵を描いたり動かしたり音を奏でているのかというと
ある特定のアドレスに意味を持たせているのだ。

例えば(あくまでも例えだよ)、$6000というアドレスにFFという値を書き込むと音が鳴る。
               $8000というアドレスに80という値を書き込むと絵が動く。
こんな感じに。
特殊なアドレスなんですな。ココにはプログラムは置けないの。
なんでそーなるかって言うとそーなるよーに設計されてるから(馬鹿っぽいなぁ...
命令の処理の仕方も含めて、その設計の仕方で同じ8bitでもいろんなCPUがあるワケですね。
で、ファミコン(6502)はどーかっていうと以下の表のようになる。

重要なのは$4000〜だろう。
簡単に言うと、音色を設定したり実際に発音したりしている重要なレジスタばかり。
この辺の命令が密集してると「もしや...サウンドドライバか?」とカン(?)が働くワケであります。
フラグ辺りの細かいことまで全部覚える必要はないだろう。ナニしてんのか概要だけ掴んでおけばOK!
で、例によってまんま転載と...

アドレス:(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 = ジョイパッドデータ

サウンド(まんま転載。この辺を理解していないのがRipperとして悲しいトコ。流石最下層組である)
ドライバまで解析してないのがバレバレだぁ。なんとなくは理解してるつもりなんだけど、文章には出来ない...

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コード
ハイ、正直に告白します。まだ挙動を全て把握していません。
特に、レジスタ関連(というかフラグ)分かってないです。
それでもNSFは作成できてしまうのですネー。勢いだけで...
では気を取り直してレッツゴー!

6502が00-FFまでの数値を出し入れしているというのは前述したとーり。
アドレスに特定の意味を持たせて(レジスタね)いるように、このデータにも意味を持たせたモノ
それが命令だ。

00-FFまでの数値には(全部じゃないが)意味がある。
ただ、時にはプログラムだったりデータだったりと見分け方が難しいかも。
この辺のプログラムの追い方が腕の見せ所なワケです。ええ、見せてませんけどね。

命令表

LDA  ... Aレジスタに指定した値を読み込む(ロード)
LDX  ... Xレジスタに指定した値を読み込む(ロード)
LDY  ... Yレジスタに指定した値を読み込む(ロード)
STA  ... Aレジスタを指定したアドレスに書き込む(ストア)
STX  ... Xレジスタを指定したアドレスに書き込む(ストア)
STY  ... Yレジスタを指定したアドレスに書き込む(ストア)

BMI  ... 直前の結果がマイナスなら分岐(ブランチ)
BEQ  ... 直前の結果がゼロなら分岐(ブランチ)
BNE  ... 直前の結果がゼロ以外なら分岐(ブランチ)
BPL  ... 直前の結果がプラスなら分岐(ブランチ)
BCC  ... (C) Carryフラグクリアなら分岐(ブランチ)
BCS  ... (C) Carryフラグセットなら分岐(ブランチ)
BVC  ... (V) Overflowフラグクリアなら分岐(ブランチ)
BVS  ... (V) Overflowフラグセットなら分岐(ブランチ)
BIT  ... Test Bits in Memory with Aレジスタ

※)分岐の仕方だが、条件を満たしていればその分岐命令直後の数値分先に進む(戻る)
具体的にこんな感じで並んでいたら

8000 : A9 00 F0 03 4C 00 80 8D 15 40
                    0  1  2  3

8000 : A9 00		lda	$#00	;Aレジに00を読み込む
8002 : F0 03		beq	$8000	;結果がゼロなので、その次のデータをゼロから数えて3バイト先に飛ぶ
8004 : 4C 00 80		jmp	$8000	;$8000へジャンプだが、今回は無視される。
        0  1  2
8007 : 8D 15 40		sta	$4015	;Aレジスタの値(今回はゼロ)をサウンドチャンネル
	3				;制御レジスタに書き込む(ココへ飛ぶ) 

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レジスタに加算する   with Carry
SBC  ... Aレジスタから減算する with Borrow
CMP  ... Aレジスタと比較する
CPX  ... Xレジスタと比較する
CPY  ... Yレジスタと比較する

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

ASL  ... 左シフト (Memory or Aレジスタ)
LSR  ... 右シフト (Memory or Aレジスタ)
ROL  ... 左ローテイト (Memory or Aレジスタ)
ROR  ... 右ローテイト (Memory or Aレジスタ)

注)シフトの場合

そのもの、値を左や右にずらすことを言う。

00000001 とあったら、左シフトなら順次左にずれる。 
00000010   bit0にゼロが挿入され、bit7はCフラグに格納される。簡単に言うと2倍になるわけだな。
逆に右シフトはbit7にゼロが挿入され、bit0がCフラグに格納される。簡単に言うと1/2倍になるわけだな(端数切り捨てか

以下転載。
                   +-+-+-+-+-+-+-+-+ 左シフト
  Operation:  C <- |7|6|5|4|3|2|1|0| <- 0
                   +-+-+-+-+-+-+-+-+                    N Z C I D V
                                                        / / / _ _ _
                   +-+-+-+-+-+-+-+-+ 右シフト
  Operation:  0 -> |7|6|5|4|3|2|1|0| -> C               N Z C I D V
                   +-+-+-+-+-+-+-+-+                    0 / / _ _ _
 8000 : A9 01 0A 0A 0A

8000 : A9 01	lda	#$01	;Aレジに01を読み込む
8002 : 0A	asl	a	;00000010 x2
8003 : 0A	asl	a	;00000100 x2
8004 : 0A	asl	a	;00001000 x2で、値は1の8倍で8です。

注)ローテイトの場合

シフトはずらしただけだが、ローテイトは循環する。考え方は同様。
以下転載
               +------------------------------+ 左ローテイト
               |         M or A               |
               |   +-+-+-+-+-+-+-+-+    +-+   |
  Operation:   +-< |7|6|5|4|3|2|1|0| <- |C| <-+         N Z C I D V
                   +-+-+-+-+-+-+-+-+    +-+             / / / _ _ _
               +------------------------------+ 右ローテイト
               |                              |
               |   +-+    +-+-+-+-+-+-+-+-+   |
  Operation:   +-> |C| -> |7|6|5|4|3|2|1|0| >-+         N Z C I D V
                   +-+    +-+-+-+-+-+-+-+-+             / / / _ _ _
申し訳ないことに、コレの挙動がよく分からんのです。
したがって具体例を挙げられません。(情けない...
(Cフラグの値は上書きされてしまうのか?或いは同時に動くのか?)

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

CLC  ... (C) Carry          フラグをクリア
CLI  ... (I) Interrupt Disable Statusフラグをクリア
CLV  ... (V) Overflow        フラグをクリア
CLD  ... (D) Decimal Mode      フラグをクリア
SEC  ... (C) Carry          フラグをセット
SEI  ... (I) Interrupt Disable Statusフラグをセット
SED  ... (D) Decimal Mode      フラグをセット

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)
ind ...
インダイレクト。指定された8bitアドレスから2バイトを読み出し、その値を16bitアドレスとしてアクセスする。6C(JMP)のみ使用
例) JMP (20h)
  JMP (0020hからの2バイトデータをジャンプ先アドレスとする)
indx ...
インダイレクトX。指定された8bitアドレスにXレジスタの内容を加算したアドレスから2バイトを読み出し、その値を16bitアドレスとしてアクセスする。
例) LDA (20h,X)
  A ← ( (0020h + X) )
indy ...
インダイレクトY。指定された8bitのアドレスから2バイトを読み出し、その値にYレジスタの内容を加算して16bitアドレスとしてアクセスする。
例) LDA (20h),Y
  A ← ( (0020h) + Y )

コード表

  noneimmzeroabszeroxzeroyabsxabsyindindxindy
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__________________
BMI 30______________________________
BEQ F0______________________________
BNE D0______________________________
BPL 10______________________________
BCC 90______________________________
BCS B0______________________________
BVC 50______________________________
BVS 70______________________________
BIT______ 24 2C_____________________
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_____________________
  noneimmzeroabszeroxzeroyabsxabsyindindxindy
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____________ 6C______
RTI 40______________________________
RTS 60______________________________
  noneimmzeroabszeroxzeroyabsxabsyindindxindy

別の早見表(例によってアレ)
        00 - BRK                
        01 - ORA - (Indirect,X) 
        02 - 
        03 - 
        04 - 
        05 - ORA - Zero Page    
        06 - ASL - Zero Page    
        07 - 
        08 - PHP                
        09 - ORA - Immediate    
        0A - ASL - Accumulator  
        0B - 
        0C - 
        0D - ORA - Absolute     
        0E - ASL - Absolute     
        0F - 
        10 - BPL                
        11 - ORA - (Indirect),Y 
        12 - 
        13 - 
        14 - 
        15 - ORA - Zero Page,X  
        16 - ASL - Zero Page,X  
        17 - 
        18 - CLC                
        19 - ORA - Absolute,Y   
        1A - 
        1B - 
        1C - 
        1D - ORA - Absolute,X   
        1E - ASL - Absolute,X   
        1F - 

        20 - JSR
        21 - AND - (Indirect,X)
        22 - 
        23 - 
        24 - BIT - Zero Page
        25 - AND - Zero Page
        26 - ROL - Zero Page
        27 - 
        28 - PLP
        29 - AND - Immediate
        2A - ROL - Accumulator
        2B - 
        2C - BIT - Absolute
        2D - AND - Absolute
        2E - ROL - Absolute
        2F - 
        30 - BMI
        31 - AND - (Indirect),Y
        32 - 
        33 - 
        34 - 
        35 - AND - Zero Page,X
        36 - ROL - Zero Page,X
        37 - 
        38 - SEC
        39 - AND - Absolute,Y
        3A - 
        3B - 
        3C - 
        3D - AND - Absolute,X
        3E - ROL - Absolute,X
        3F - 

        40 - RTI                
        41 - EOR - (Indirect,X) 
        42 - 
        43 - 
        44 - 
        45 - EOR - Zero Page    
        46 - LSR - Zero Page    
        47 - 
        48 - PHA                
        49 - EOR - Immediate    
        4A - LSR - Accumulator  
        4B - 
        4C - JMP - Absolute     
        4D - EOR - Absolute     
        4E - LSR - Absolute     
        4F - 
        50 - BVC                
        51 - EOR - (Indirect),Y 
        52 - 
        53 - 
        54 - 
        55 - EOR - Zero Page,X  
        56 - LSR - Zero Page,X  
        57 - 
        58 - CLI                
        59 - EOR - Absolute,Y   
        5A - 
        5B - 
        5C - 
        50 - EOR - Absolute,X   
        5E - LSR - Absolute,X   
        5F - 

        60 - RTS
        61 - ADC - (Indirect,X)
        62 - 
        63 - 
        64 - 
        65 - ADC - Zero Page
        66 - ROR - Zero Page
        67 - 
        68 - PLA
        69 - ADC - Immediate
        6A - ROR - Accumulator
        6B - 
        6C - JMP - Indirect
        6D - ADC - Absolute
        6E - ROR - Absolute
        6F - 
        70 - BVS
        71 - ADC - (Indirect),Y
        72 - 
        73 - 
        74 - 
        75 - ADC - Zero Page,X
        76 - ROR - Zero Page,X
        77 - 
        78 - SEI
        79 - ADC - Absolute,Y
        7A - 
        7B - 
        7C - 
        70 - ADC - Absolute,X
        7E - ROR - Absolute,X
        7F - 

        80 - 
        81 - STA - (Indirect,X) 
        82 - 
        83 - 
        84 - STY - Zero Page    
        85 - STA - Zero Page    
        86 - STX - Zero Page    
        87 - 
        88 - DEY                
        89 - 
        8A - TXA                
        8B - 
        8C - STY - Absolute     
        80 - STA - Absolute     
        8E - STX - Absolute     
        8F - 
        90 - BCC                
        91 - STA - (Indirect),Y 
        92 - 
        93 - 
        94 - STY - Zero Page,X  
        95 - STA - Zero Page,X  
        96 - STX - Zero Page,Y  
        97 - 
        98 - TYA                
        99 - STA - Absolute,Y   
        9A - TXS                
        9B - 
        9C - 
        90 - STA - Absolute,X   
        9E - 
        9F - 

        A0 - LDY - Immediate
        A1 - LDA - (Indirect,X)
        A2 - LDX - Immediate
        A3 - 
        A4 - LDY - Zero Page
        A5 - LDA - Zero Page
        A6 - LDX - Zero Page
        A7 - 
        A8 - TAY
        A9 - LDA - Immediate
        AA - TAX
        AB - 
        AC - LDY - Absolute
        AD - LDA - Absolute
        AE - LDX - Absolute
        AF - 
        B0 - BCS
        B1 - LDA - (Indirect),Y
        B2 - 
        B3 - 
        B4 - LDY - Zero Page,X
        BS - LDA - Zero Page,X
        B6 - LDX - Zero Page,Y
        B7 - 
        B8 - CLV
        B9 - LDA - Absolute,Y
        BA - TSX
        BB - 
        BC - LDY - Absolute,X
        BD - LDA - Absolute,X
        BE - LDX - Absolute,Y
        BF - 

        C0 - Cpy - Immediate    
        C1 - CMP - (Indirect,X) 
        C2 - 
        C3 - 
        C4 - CPY - Zero Page    
        C5 - CMP - Zero Page    
        C6 - DEC - Zero Page    
        C7 - 
        C8 - INY                
        C9 - CMP - Immediate    
        CA - DEX                
        CB - 
        CC - CPY - Absolute     
        CD - CMP - Absolute     
        CE - DEC - Absolute     
        CF - 
        D0 - BNE                
        D1 - CMP   (Indirect@,Y 
        D2 - 
        D3 - 
        D4 - 
        D5 - CMP - Zero Page,X  
        D6 - DEC - Zero Page,X  
        D7 - 
        D8 - CLD                
        D9 - CMP - Absolute,Y   
        DA - 
        DB - 
        DC - 
        DD - CMP - Absolute,X   
        DE - DEC - Absolute,X   
        DF - 

        E0 - CPX - Immediate
        E1 - SBC - (Indirect,X)
        E2 - 
        E3 - 
        E4 - CPX - Zero Page
        E5 - SBC - Zero Page
        E6 - INC - Zero Page
        E7 - 
        E8 - INX
        E9 - SBC - Immediate
        EA - NOP
        EB - 
        EC - CPX - Absolute
        ED - SBC - Absolute
        EE - INC - Absolute
        EF - 
        F0 - BEQ
        F1 - SBC - (Indirect),Y
        F2 - 
        F3 - 
        F4 - 
        F5 - SBC - Zero Page,X
        F6 - INC - Zero Page,X
        F7 - 
        F8 - SED
        F9 - SBC - Absolute,Y
        FA - 
        FB - 
        FC - 
        FD - SBC - Absolute,X
        FE - INC - Absolute,X
        FF - 
転載(&間違い?)だらけだけど、参考になってくれればウレシイナ。