ファミコンディスクシステム用ソフトの作成2025年05月04日 05:07
しばらく前のことだが、ふとファミコンディスクシステムの互換BIOSを自作したいなと思い、そのためにどうも仕様が分からないところを実物を(エミュレータ上で)叩いて調べるにあたって、ディスクシステム上で動く最低限のソフトを作ろうとしたら案外簡単だったのでメモを残しておく。
バンク切り替え無しのマッパー0のソフトをベースに、ディスクシステムの機能は何も使わない、ただディスクシステムで動くだけの移植を行う。
アセンブラはNESASMをずっと使っているのだが、これには.fdsファイルの出力機能はどうやら無い。ただ「-raw」指定でINESヘッダ無しのファイルを出力することはできるのでこれを使う。
作業の流れとしては、少々変更したソースをアセンブルし、バイナリエディタなどで少々の切り貼りを行うことになる。
コード側で変更が必要なのは、BIOSが使うメモリ領域と、コードを置くアドレスの変更に伴うもの、そしてミラーリング設定の3つである。
・BIOSが使うメモリについて。
参考: https://www.nesdev.org/wiki/FDS_BIOS
$100~$103は、割込み時の処理を決定するために使われるので適切に設定する。といっても今回の用途ではデフォルトのまま触らないようにするだけでよいはず。ここを別の用途に使っているなら空ける必要がある。
この領域はスタックの末端であり、普通はまず使われない場所である。(まず使われない場所であることを理由に使っている場合はあるだろう)
他に$00あたりと$FFあたりにもBIOSで使われるメモリ領域があるが、説明によれば、――このブログサービスの禁止単語のようなので伏せ字にするが――「temp○rary variables」と「Used by controller reading routines」であり、おそらくディスクシステムの機能を使わない(BIOSコールをしない)限り勝手に使われることは無く、たぶんここは普通に使って問題ない。
(このブログサービスの禁止単語については色々と言いたいことがあるが、ここに愚痴を書いても邪魔なので別の記事にしよう…)
・ミラーリングについて。
ディスクシステムは縦ミラーと横ミラーの切り替えがソフト的に可能だ。
ハード的に決めているマッパー0からの移植では忘れずに設定しなければいけない。(後述するが、忘れていた)
これは$4025への書き込みで行う。
https://www.nesdev.org/wiki/Family_Computer_Disk_System#FDS_Control_($4025)
このレジスタには他の設定項目が含まれているので、普通はread-modify-writeをするのが適切なようだ。
でも他を使わないのにわざわざRMWをするのも無駄な気もして、決め打ちの値を書いた。
・アドレスについて。
の前にまずそもそもディスクシステムのファイル構造についてだが、
ディスクシステムのディスク上のファイルは変わった形式で、データだけでなくそれをどのアドレスに読み込むかまでファイルの属性として指定されている。
そしてファイルとは別にディスク先頭のブロックに「起動時に最初に読み込むファイル」が定義されている。
なのでマッパー0移植であれば、最初に全て読み込ませてやるだけで後はコード側ではファイル読み込みを明示的に行うこと無く素のファミコンソフトのつもりで実行できる。
アドレスは、素のファミコンで(マッパー0で)使えるアドレスは$8000~$FFFFであるのに対し、
ディスクシステムではこのうちの$E000~$FFFFにBIOSが置かれておりここはユーザープログラムに使えない。
その代わり、$6000~$7FFFにもRAMが用意されており、都合プログラムを置けるRAM領域は$6000~$DFFFと、マッパー0で見えるのと同量が使える。
割り込みベクタの$FFFA~FFFFはBIOSに取られるので、代わりに(デフォルトでは)$DFFA~DFFFが仮想的な割り込みベクタとして用意されている。
なのでアドレスはこれに合わせてマッパー0で$8000~$FFFFを使っていたものを$6000~$DFFFに$2000だけ平行移動させるのがシンプルでやりやすいと思う。
あるいは、$8000~DFFAをそのまま、$E000~FFFAを$6000~7FFFに移し、割り込みベクタの$FFFA~FFFFをDFFA~DFFFに移動するのも、書き換えるアドレスが少なくてよいかもしれない。
アドレスの扱いに関して、NESASMの.bank指定と.org指定について今までずっと理解が曖昧だったのだが今回調べてやっと理解した(気がする)ので自分なりの理解を備忘録的に書いておく。
.bankは、出力されるROM(イメージファイル)内での、$2000バイト単位の位置を指定する。.bank NはN*$2000バイト目からの$2000バイトに相当する。ここでPRG-ROM・CHR-ROMの区別は無い。
この$2000バイト単位をバンクと呼びたくなるが、マッパーによるPRGバンク切り替えは$4000単位のものが多く、この2つの「バンク」の扱いが紛らわしいので注意が必要だ。
$2000バイト内の位置合わせは.orgによる。
コードが$2000バイト区切りの境界をまたぐとエラー。
.orgは、CPUから見えるアドレス(16bit)を決定する。これには2つの意味があって、
.orgで決定されたコードのアドレスは、その下位13bitがROM上でバンク内の位置を指定する。上位3bitはここでは使われない。
.orgで決定されたラベルのアドレスは、プログラム中でその16bitのアドレスの数値として扱われる
例えば、
どこか別の場所からそれを呼び出すために
一方、その「func」のコードの実体はROM内で先頭から$2000×4=$8000に$A123の下位13bitの$1123を足した$9123バイトの位置にあるので、それを想定された位置に置くコード、例えばバンク4,5を8000~BFFFの位置に置くのだとプログラマーが考えてそのようなバンク切り替えのコードを書くことになる。
プログラム中でアドレスを何らかの計算で求めていたりしなければ、.orgで指定された数値やアドレスとして使う数値を一貫して-$2000するだけでディスクシステム用のコードになるはずである。
マッパー0のソースはバンクとorgの対応は次のようになっているはずだ。
.bank 0 → .org $8000~$9FFF
.bank 1 → .org $A000~$BFFF
.bank 2 → .org $C000~$DFFF
.bank 3 → .org $E000~$FFFF
.bank 4 → CHR-ROM
これに対し、ディスクシステム用に作るべきファイルは次のように$2000だけずらしたものにするのが素直だ。
.bank 0 → .org $6000~$7FFF
.bank 1 → .org $8000~$9FFF
.bank 2 → .org $A000~$BFFF
.bank 3 → .org $C000~$DFFF
.bank 4 → (一応CHR-ROM)
CHR-ROMについては、別の1つのバイナリファイルで扱っていてソースでは.incbinしているだけなら、わざわざ一旦1つのファイルにまとめる必要はない。
もしソース上で組み立てているならそのままbank4に置いておいて後で分割するのがよいだろう。
これを「-raw」指定でアセンブルし、ヘッダなしPRGのみ32kB、またはCHRありなら40kBのファイルができる。
分割するならして、PRG部32k($8000)バイトとCHR部8k($2000)バイトの2ファイルが用意できたら、FDSファイルのフォーマットに合わせ適宜必要な部分を追加する。
FDSファイルのフォーマットはこちら、 https://www.nesdev.org/wiki/FDS_file_format
そしてディスクのフォーマットはこちらにある。 https://www.nesdev.org/wiki/FDS_disk_format
ディスクの物理的なフォーマットも面白いが、エミュレータで動かすファイルを作るだけなら理解しなくてよいので、FDSファイルを作るのに必要な(論理的な?)部分だけ説明する。
今回作ったファイルは次のようになっている。
各部を説明する。
ディスク内容は「ブロック」という単位に分かれている。
ブロックごとにのみ読み書きが可能であり、それのためにブロックの頭と末尾に何かあったりブロック間は隙間があったりするが、FDSイメージファイル上ではその部分は再現されておらず単にブロックのデータ部分のみを隙間なく並べている。
ここで、今回作ろうとしているマッパー0ベースのソフトはファイルサイズは固定なので、上で示したPRG・CHR以外の部分についてソフトごとに変更が必要な箇所は無い。(ゲーム名や日付など書き換えた方がよい部分はあるが)
なので、「先頭からPRG部の前まで」と「PRG部の次からCHR部の前まで」をファイルにしておけば、作ったPRG部・CHR部とファイルとして結合するだけでFDSファイルが完成する。
また、CHR-RAMを使うソフトの場合、32kBのPRG部のファイルだけで済むので扱いが楽だ。「先頭からPRG部の前まで」を用意するだけでよい。(上記から最初に読み込むファイルの指定とファイル個数を変える)
実際に試してみよう。
以前書いた般若心経を変換する。動いた。

CHR-RAMのものも試す。以前書いた全画面表示プログラム。動いた。

(なお実は、「A面B面切り替え」が出ていることから分かるが、誤ってFDSファイルヘッダでの面数の指定を2面にしていた。それでも動いた)
自分で書いたコード(しかもサイズも小さい)を自分で書き換えてもあまり面白みがない。何か公開されているコードは無いか。
40kBのNESゲームで、NESASM向けのソースが手に入り、少なくとも個人的に書き換えて実行することが許可されているもの。
…と探すとよさそうなものが見つかった。
「Lan Master」。Shiru氏によるシンプルなパズルゲーム。
「The game and its source code are released into Public Domain」とあり、心置きなく改変が可能だ。
http://shiru.untergrund.net/software.shtml#nes
ソースを見るとファイルが多くてちょっと尻込みしたが、orgの指定されている箇所はメインのgame.asmのみ。
8000,a000,c000,e000とfffa(とCHR側)があるので、それぞれ-2000する。
CHR部分は1ファイルにまとまったものを.incbinしている。これは不要だ。コメントアウトする。
変数定義を見ると100~103は使っていないようだ。
しかし初期化の部分でゼロクリアしている。
$0100~$01FFの部分のクリアをやめる。
一応104~1FFはゼロクリアしておいた方がいいかとも思ったがまあただのスタックだろうし不要だろうと思いそのまま。
動いた。

せっかくなのでクリアまで遊んだ。なかなか面白いゲームだ。
するとエンディングの文字がどうもおかしい。PPUビューアで見るとミラーリングが間違っているせいで横スクロールなのに横1画面分に書いてしまっており書き換えが画面内に見えている。

ここで初めてミラーリング設定をしていなかったことに気づく。ゲーム画面は1画面だから正常に見えていたんだな。危なかった。
というわけで先述のミラーリング設定をすると、

今度は正常。
最終的なdiffはこんな感じ。
バンク切り替え無しのマッパー0のソフトをベースに、ディスクシステムの機能は何も使わない、ただディスクシステムで動くだけの移植を行う。
アセンブラはNESASMをずっと使っているのだが、これには.fdsファイルの出力機能はどうやら無い。ただ「-raw」指定でINESヘッダ無しのファイルを出力することはできるのでこれを使う。
作業の流れとしては、少々変更したソースをアセンブルし、バイナリエディタなどで少々の切り貼りを行うことになる。
コード側で変更が必要なのは、BIOSが使うメモリ領域と、コードを置くアドレスの変更に伴うもの、そしてミラーリング設定の3つである。
・BIOSが使うメモリについて。
参考: https://www.nesdev.org/wiki/FDS_BIOS
$100~$103は、割込み時の処理を決定するために使われるので適切に設定する。といっても今回の用途ではデフォルトのまま触らないようにするだけでよいはず。ここを別の用途に使っているなら空ける必要がある。
この領域はスタックの末端であり、普通はまず使われない場所である。(まず使われない場所であることを理由に使っている場合はあるだろう)
$0100 | NMIの有効無効と割り込みベクタの選択。BIOSを抜けてユーザーコードに来たときのデフォルトはC0であり、この場合[DFFA]に飛ぶ。DFFAというのは素のファミコンでのFFFAに相当するアドレスなので、普通はそのままでよい。他にDFF8とDFF6が選べる。 |
$0101 | ディスクシステムのタイマーIRQ時の処理を決める。デフォルトの80のとき割込み無効(BIOSが握りつぶす?)、使うならC0にする。ディスクシステムの機能は使わない想定なのでデフォルトでよい。 |
$0102:$0103 | リセット/BRK時の処理に関係するようだがあまり説明が理解できていない。起動時のデフォルトはAC35になっており、ソフトリセットすると5335になる、リセット要因を区別するために使うもの? それとも積極的に書き換えることで何かできる? |
(このブログサービスの禁止単語については色々と言いたいことがあるが、ここに愚痴を書いても邪魔なので別の記事にしよう…)
・ミラーリングについて。
ディスクシステムは縦ミラーと横ミラーの切り替えがソフト的に可能だ。
ハード的に決めているマッパー0からの移植では忘れずに設定しなければいけない。(後述するが、忘れていた)
これは$4025への書き込みで行う。
https://www.nesdev.org/wiki/Family_Computer_Disk_System#FDS_Control_($4025)
このレジスタには他の設定項目が含まれているので、普通はread-modify-writeをするのが適切なようだ。
でも他を使わないのにわざわざRMWをするのも無駄な気もして、決め打ちの値を書いた。
lda #$22 ;V mirror
sta $4025
これを設定していない場合の挙動はよく分からない。Mesenでは横ミラーとして動いているようだった一方、思えば別のプログラムをNintendulatorで動かしたときはネームテーブルへの書き込みが正常に行えなかった。NintendulatorよりMesenの方が挙動は正しそうなので横ミラーがデフォルトだろうか。sta $4025
・アドレスについて。
の前にまずそもそもディスクシステムのファイル構造についてだが、
ディスクシステムのディスク上のファイルは変わった形式で、データだけでなくそれをどのアドレスに読み込むかまでファイルの属性として指定されている。
そしてファイルとは別にディスク先頭のブロックに「起動時に最初に読み込むファイル」が定義されている。
なのでマッパー0移植であれば、最初に全て読み込ませてやるだけで後はコード側ではファイル読み込みを明示的に行うこと無く素のファミコンソフトのつもりで実行できる。
アドレスは、素のファミコンで(マッパー0で)使えるアドレスは$8000~$FFFFであるのに対し、
ディスクシステムではこのうちの$E000~$FFFFにBIOSが置かれておりここはユーザープログラムに使えない。
その代わり、$6000~$7FFFにもRAMが用意されており、都合プログラムを置けるRAM領域は$6000~$DFFFと、マッパー0で見えるのと同量が使える。
割り込みベクタの$FFFA~FFFFはBIOSに取られるので、代わりに(デフォルトでは)$DFFA~DFFFが仮想的な割り込みベクタとして用意されている。
なのでアドレスはこれに合わせてマッパー0で$8000~$FFFFを使っていたものを$6000~$DFFFに$2000だけ平行移動させるのがシンプルでやりやすいと思う。
あるいは、$8000~DFFAをそのまま、$E000~FFFAを$6000~7FFFに移し、割り込みベクタの$FFFA~FFFFをDFFA~DFFFに移動するのも、書き換えるアドレスが少なくてよいかもしれない。
アドレスの扱いに関して、NESASMの.bank指定と.org指定について今までずっと理解が曖昧だったのだが今回調べてやっと理解した(気がする)ので自分なりの理解を備忘録的に書いておく。
.bankは、出力されるROM(イメージファイル)内での、$2000バイト単位の位置を指定する。.bank NはN*$2000バイト目からの$2000バイトに相当する。ここでPRG-ROM・CHR-ROMの区別は無い。
この$2000バイト単位をバンクと呼びたくなるが、マッパーによるPRGバンク切り替えは$4000単位のものが多く、この2つの「バンク」の扱いが紛らわしいので注意が必要だ。
$2000バイト内の位置合わせは.orgによる。
コードが$2000バイト区切りの境界をまたぐとエラー。
.orgは、CPUから見えるアドレス(16bit)を決定する。これには2つの意味があって、
.orgで決定されたコードのアドレスは、その下位13bitがROM上でバンク内の位置を指定する。上位3bitはここでは使われない。
.orgで決定されたラベルのアドレスは、プログラム中でその16bitのアドレスの数値として扱われる
例えば、
.bank 4
.org $A123
func
...
とあった場合、.org $A123
func
...
どこか別の場所からそれを呼び出すために
JSR func
と書いたなら、JSR $A123
として解釈されそのような機械語が出力される。一方、その「func」のコードの実体はROM内で先頭から$2000×4=$8000に$A123の下位13bitの$1123を足した$9123バイトの位置にあるので、それを想定された位置に置くコード、例えばバンク4,5を8000~BFFFの位置に置くのだとプログラマーが考えてそのようなバンク切り替えのコードを書くことになる。
プログラム中でアドレスを何らかの計算で求めていたりしなければ、.orgで指定された数値やアドレスとして使う数値を一貫して-$2000するだけでディスクシステム用のコードになるはずである。
マッパー0のソースはバンクとorgの対応は次のようになっているはずだ。
.bank 0 → .org $8000~$9FFF
.bank 1 → .org $A000~$BFFF
.bank 2 → .org $C000~$DFFF
.bank 3 → .org $E000~$FFFF
.bank 4 → CHR-ROM
これに対し、ディスクシステム用に作るべきファイルは次のように$2000だけずらしたものにするのが素直だ。
.bank 0 → .org $6000~$7FFF
.bank 1 → .org $8000~$9FFF
.bank 2 → .org $A000~$BFFF
.bank 3 → .org $C000~$DFFF
.bank 4 → (一応CHR-ROM)
CHR-ROMについては、別の1つのバイナリファイルで扱っていてソースでは.incbinしているだけなら、わざわざ一旦1つのファイルにまとめる必要はない。
もしソース上で組み立てているならそのままbank4に置いておいて後で分割するのがよいだろう。
これを「-raw」指定でアセンブルし、ヘッダなしPRGのみ32kB、またはCHRありなら40kBのファイルができる。
分割するならして、PRG部32k($8000)バイトとCHR部8k($2000)バイトの2ファイルが用意できたら、FDSファイルのフォーマットに合わせ適宜必要な部分を追加する。
FDSファイルのフォーマットはこちら、 https://www.nesdev.org/wiki/FDS_file_format
そしてディスクのフォーマットはこちらにある。 https://www.nesdev.org/wiki/FDS_disk_format
ディスクの物理的なフォーマットも面白いが、エミュレータで動かすファイルを作るだけなら理解しなくてよいので、FDSファイルを作るのに必要な(論理的な?)部分だけ説明する。
今回作ったファイルは次のようになっている。
0000 46 44 53 1A 02 00 00 00 00 00 00 00 00 00 00 00
0010 01 2A 4E 49 4E 54 45 4E 44 4F 2D 48 56 43 2A 00
0020 4E 59 41 20 00 00 00 00 00 02 FF FF FF FF FF A0
0030 02 09 49 61 00 00 02 00 FF FF FF 00 FF FF FF FF
0040 FF FF FF FF 00 00 00 00 02 03 03 00 00 4B 59 4F
0050 44 41 4B 55 2D 00 28 E0 00 02 04 24 24 24 24 24
0060 24 24 24 24 24 24 17 12 17 1D 0E 17 0D 18 24 28
0070 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24
0080 24 24 0F 0A 16 12 15 22 24 0C 18 16 19 1E 1D 0E
0090 1B 24 1D 16 24 24 24 24 24 24 24 24 24 24 24 24
00A0 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24
00B0 24 24 24 24 24 24 24 24 24 24 24 24 24 1D 11 12
00C0 1C 24 19 1B 18 0D 1E 0C 1D 24 12 1C 24 16 0A 17
00D0 1E 0F 0A 0C 1D 1E 1B 0E 0D 24 24 24 24 0A 17 0D
00E0 24 1C 18 15 0D 24 0B 22 24 17 12 17 1D 0E 17 0D
00F0 18 24 0C 18 27 15 1D 0D 26 24 24 24 24 18 1B 24
0100 0B 22 24 18 1D 11 0E 1B 24 0C 18 16 19 0A 17 22
0110 24 1E 17 0D 0E 1B 24 24 24 24 24 24 24 15 12 0C
0120 0E 17 1C 0E 24 18 0F 24 17 12 17 1D 0E 17 0D 18
0130 24 0C 18 27 15 1D 0D 26 26 24 24 03 01 01 FF FF
0140 FF FF FF FF FF FF 00 60 00 80 00 04(78 A2 40 8E
… $8000バイトPRG部 …
8140 FF FF FF FF FF FF FA 00 00 60 00 00)03 02 02 FF
8150 FF FF FF FF FF FF FF 00 00 00 20 01 04(00 00 00
… $2000バイトCHR部 …
A150 00 00 00 FF FF 00 00 00 00 00 00 00 00)
0010 01 2A 4E 49 4E 54 45 4E 44 4F 2D 48 56 43 2A 00
0020 4E 59 41 20 00 00 00 00 00 02 FF FF FF FF FF A0
0030 02 09 49 61 00 00 02 00 FF FF FF 00 FF FF FF FF
0040 FF FF FF FF 00 00 00 00 02 03 03 00 00 4B 59 4F
0050 44 41 4B 55 2D 00 28 E0 00 02 04 24 24 24 24 24
0060 24 24 24 24 24 24 17 12 17 1D 0E 17 0D 18 24 28
0070 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24
0080 24 24 0F 0A 16 12 15 22 24 0C 18 16 19 1E 1D 0E
0090 1B 24 1D 16 24 24 24 24 24 24 24 24 24 24 24 24
00A0 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24
00B0 24 24 24 24 24 24 24 24 24 24 24 24 24 1D 11 12
00C0 1C 24 19 1B 18 0D 1E 0C 1D 24 12 1C 24 16 0A 17
00D0 1E 0F 0A 0C 1D 1E 1B 0E 0D 24 24 24 24 0A 17 0D
00E0 24 1C 18 15 0D 24 0B 22 24 17 12 17 1D 0E 17 0D
00F0 18 24 0C 18 27 15 1D 0D 26 24 24 24 24 18 1B 24
0100 0B 22 24 18 1D 11 0E 1B 24 0C 18 16 19 0A 17 22
0110 24 1E 17 0D 0E 1B 24 24 24 24 24 24 24 15 12 0C
0120 0E 17 1C 0E 24 18 0F 24 17 12 17 1D 0E 17 0D 18
0130 24 0C 18 27 15 1D 0D 26 26 24 24 03 01 01 FF FF
0140 FF FF FF FF FF FF 00 60 00 80 00 04(78 A2 40 8E
… $8000バイトPRG部 …
8140 FF FF FF FF FF FF FA 00 00 60 00 00)03 02 02 FF
8150 FF FF FF FF FF FF FF 00 00 00 20 01 04(00 00 00
… $2000バイトCHR部 …
A150 00 00 00 FF FF 00 00 00 00 00 00 00 00)
各部を説明する。
ディスク内容は「ブロック」という単位に分かれている。
ブロックごとにのみ読み書きが可能であり、それのためにブロックの頭と末尾に何かあったりブロック間は隙間があったりするが、FDSイメージファイル上ではその部分は再現されておらず単にブロックのデータ部分のみを隙間なく並べている。
・FDSファイルヘッダ (物理ディスクには存在しない、イメージファイルのヘッダ)。 | |||||||||||||||||
0000 | 46 | 44 | 53 | 1A | シグネチャ、"FDS",<EOF>。 | ||||||||||||
01 | ディスクの面数。A面だけなのでここでは01。間違っても動きはする。 | ||||||||||||||||
00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | ゼロパディング。 | ||||||
・ディスク内の最初のブロック | |||||||||||||||||
0010 | 01 | Block Code。01はDisk Info Blockを示す。 | |||||||||||||||
2A | 4E | 49 | 4E | 54 | 45 | 4E | 44 | 4F | 2D | 48 | 56 | 43 | 2A | 固定値"*NINTENDO-HVC*"。 | |||
00 | ライセンシーコード。00が未割り当てなのでこれを使った。 | ||||||||||||||||
0020 | 58 | 58 | 58 | ゲーム名。ASCII3文字でいい感じの名前が思いつかなかったので"XXX"とした。 | |||||||||||||
20 | 普通のディスクは20 | ||||||||||||||||
00 | バージョン。0からインクリメント。 | ||||||||||||||||
00 | 面番号。1面目、というか片面しか使わないので00。 | ||||||||||||||||
00 | ディスク番号。1枚目が00。 | ||||||||||||||||
00 | ディスクタイプ(FMC)。FMCの青ディスクとは何かわからないがまあ普通のは00。 | ||||||||||||||||
00 | 不明 | ||||||||||||||||
02 | 最初に読み込むファイルを指定する。02でIDが0から2のファイルを読み込む。CHR無しならここは01。 | ||||||||||||||||
FF | FF | FF | FF | FF | 不明 | ||||||||||||
A0 | 製造年月日。各1バイトのBCDで年は昭和年。2025年は昭和100年なので困る。とりあえずA0年とした。 | ||||||||||||||||
0030 | 02 | 09 | |||||||||||||||
49 | 国コード。日本は49。 | ||||||||||||||||
61 | 不明 | ||||||||||||||||
00 | 不明 | ||||||||||||||||
00 | 02 | 不明 | |||||||||||||||
00 | FF | FF | FF | 00 | 不明 | ||||||||||||
A0 | 02 | 09 | 書き換え年月日。 | ||||||||||||||
FF | 不明 | ||||||||||||||||
0040 | FF | 不明 | |||||||||||||||
FF | FF | ディスクライターのシリアル番号らしい。 | |||||||||||||||
FF | 不明 | ||||||||||||||||
00 | ディスク書き換え回数 | ||||||||||||||||
00 | Actual disk side。よく分からないが上の面番号は両面0になるがこちらは実際のA面B面を示す? | ||||||||||||||||
00 | ディスクタイプ(他)。こちらのディスクタイプも普通のは00 | ||||||||||||||||
00 | ディスクバージョン。不明らしいがとりあえず00で。 | ||||||||||||||||
・2つ目のブロック | |||||||||||||||||
0040 | 02 | Block Code。02はFile amount blockを示す。 | |||||||||||||||
03 | ファイル個数。PRGとCHRに加えて必須の許諾ファイルがあり3個。CHR無しならここは02。 | ||||||||||||||||
・3つ目のブロック | |||||||||||||||||
0040 | 03 | Block Code。03はFile header blockを示す。最初のファイルのヘッダ。 | |||||||||||||||
00 | ファイル番号。0から順に割り当てる。 | ||||||||||||||||
00 | ファイルID。割当ては自由。このIDを指定して読み出す。今回は単純に同じく0にする。 | ||||||||||||||||
4B | 59 | 4F | ファイル名。このファイルは必須の"KYODAKU-"ファイル。 | ||||||||||||||
0050 | 44 | 41 | 4B | 55 | 2D | ||||||||||||
00 | 28 | ファイルの読み込み先アドレス。(PPUの)$2800~、つまりネームテーブルに読み込む指定。 | |||||||||||||||
E0 | 00 | ファイルサイズ。$00E0バイト。 | |||||||||||||||
02 | ファイルタイプ(ファイルの読み込み先の指定)。02はネームテーブル。 | ||||||||||||||||
・4つ目のブロック | |||||||||||||||||
0050 | 04 | Block Code。03はFile data blockを示す。最初のファイルのデータ。 | |||||||||||||||
24 | 24 | 24 | 24 | 24 | ファイル内容。上のファイルヘッダで示された通り、$E0バイトある。 「KYODAKU-」ファイルの内容は読み込み時に表示される許諾メッセージ。 文字コードはゼルダなどに使われているのと同じ、0~9のあとにA~Zが続き以下少々の記号というもの。 | ||||||||||||
0060 | 24 | 24 | 24 | 24 | 24 | 24 | 17 | 12 | 17 | 1D | 0E | 17 | 0D | 18 | 24 | 28 | |
0070 | 24 | 24 | 24 | 24 | 24 | 24 | 24 | 24 | 24 | 24 | 24 | 24 | 24 | 24 | 24 | 24 | |
0080 | 24 | 24 | 0F | 0A | 16 | 12 | 15 | 22 | 24 | 0C | 18 | 16 | 19 | 1E | 1D | 0E | |
0090 | 1B | 24 | 1D | 16 | 24 | 24 | 24 | 24 | 24 | 24 | 24 | 24 | 24 | 24 | 24 | 24 | |
00A0 | 24 | 24 | 24 | 24 | 24 | 24 | 24 | 24 | 24 | 24 | 24 | 24 | 24 | 24 | 24 | 24 | |
00B0 | 24 | 24 | 24 | 24 | 24 | 24 | 24 | 24 | 24 | 24 | 24 | 24 | 24 | 1D | 11 | 12 | |
00C0 | 1C | 24 | 19 | 1B | 18 | 0D | 1E | 0C | 1D | 24 | 12 | 1C | 24 | 16 | 0A | 17 | |
00D0 | 1E | 0F | 0A | 0C | 1D | 1E | 1B | 0E | 0D | 24 | 24 | 24 | 24 | 0A | 17 | 0D | |
00E0 | 24 | 1C | 18 | 15 | 0D | 24 | 0B | 22 | 24 | 17 | 12 | 17 | 1D | 0E | 17 | 0D | |
00F0 | 18 | 24 | 0C | 18 | 27 | 15 | 1D | 0D | 26 | 24 | 24 | 24 | 24 | 18 | 1B | 24 | |
0100 | 0B | 22 | 24 | 18 | 1D | 11 | 0E | 1B | 24 | 0C | 18 | 16 | 19 | 0A | 17 | 22 | |
0110 | 24 | 1E | 17 | 0D | 0E | 1B | 24 | 24 | 24 | 24 | 24 | 24 | 24 | 15 | 12 | 0C | |
0120 | 0E | 17 | 1C | 0E | 24 | 18 | 0F | 24 | 17 | 12 | 17 | 1D | 0E | 17 | 0D | 18 | |
0130 | 24 | 0C | 18 | 27 | 15 | 1D | 0D | 26 | 26 | 24 | 24 | ||||||
・5つ目のブロック | |||||||||||||||||
0130 | 03 | 2つ目のファイルのファイルヘッダブロック。 | |||||||||||||||
01 | ファイル番号と、 | ||||||||||||||||
01 | ファイルIDは01。 | ||||||||||||||||
FF | FF | ファイル名は不要なのでFFで。 | |||||||||||||||
0140 | FF | FF | FF | FF | FF | FF | |||||||||||
00 | 60 | 読み込み先アドレスは(CPUの)$6000から | |||||||||||||||
00 | 80 | ファイルサイズは$8000バイト。 | |||||||||||||||
00 | 読み込み先はプログラムRAM。 | ||||||||||||||||
・6つ目のブロック | |||||||||||||||||
0140 | 04 | 2つ目のファイルのファイルデータブロック。 | |||||||||||||||
XX | XX | XX | XX | $8000バイトのPRG部分。 | |||||||||||||
… | |||||||||||||||||
8140 | XX | XX | XX | XX | XX | XX | XX | XX | XX | XX | XX | XX | |||||
・7つ目のブロック | |||||||||||||||||
8140 | 03 | 3つ目のファイルのファイルヘッダブロック。 | |||||||||||||||
02 | ファイル番号と、 | ||||||||||||||||
02 | ファイルIDは02。 | ||||||||||||||||
FF | ファイル名は同じくFFで。 | ||||||||||||||||
8150 | FF | FF | FF | FF | FF | FF | FF | ||||||||||
00 | 00 | 読み込み先アドレスは(PPUの)$0000から | |||||||||||||||
00 | 20 | ファイルサイズは$2000バイト | |||||||||||||||
01 | 読み込み先はキャラクタRAM | ||||||||||||||||
・8つ目のブロック | |||||||||||||||||
8150 | 04 | 3つ目のファイルのファイルデータブロック | |||||||||||||||
XX | XX | XX | $2000バイトのCHR部分。 | ||||||||||||||
… | |||||||||||||||||
A150 | XX | XX | XX | XX | XX | XX | XX | XX | XX | XX | XX | XX | XX |
なので、「先頭からPRG部の前まで」と「PRG部の次からCHR部の前まで」をファイルにしておけば、作ったPRG部・CHR部とファイルとして結合するだけでFDSファイルが完成する。
また、CHR-RAMを使うソフトの場合、32kBのPRG部のファイルだけで済むので扱いが楽だ。「先頭からPRG部の前まで」を用意するだけでよい。(上記から最初に読み込むファイルの指定とファイル個数を変える)
実際に試してみよう。
以前書いた般若心経を変換する。動いた。

CHR-RAMのものも試す。以前書いた全画面表示プログラム。動いた。

(なお実は、「A面B面切り替え」が出ていることから分かるが、誤ってFDSファイルヘッダでの面数の指定を2面にしていた。それでも動いた)
自分で書いたコード(しかもサイズも小さい)を自分で書き換えてもあまり面白みがない。何か公開されているコードは無いか。
40kBのNESゲームで、NESASM向けのソースが手に入り、少なくとも個人的に書き換えて実行することが許可されているもの。
…と探すとよさそうなものが見つかった。
「Lan Master」。Shiru氏によるシンプルなパズルゲーム。
「The game and its source code are released into Public Domain」とあり、心置きなく改変が可能だ。
http://shiru.untergrund.net/software.shtml#nes
ソースを見るとファイルが多くてちょっと尻込みしたが、orgの指定されている箇所はメインのgame.asmのみ。
8000,a000,c000,e000とfffa(とCHR側)があるので、それぞれ-2000する。
CHR部分は1ファイルにまとまったものを.incbinしている。これは不要だ。コメントアウトする。
変数定義を見ると100~103は使っていないようだ。
しかし初期化の部分でゼロクリアしている。
$0100~$01FFの部分のクリアをやめる。
一応104~1FFはゼロクリアしておいた方がいいかとも思ったがまあただのスタックだろうし不要だろうと思いそのまま。
動いた。

せっかくなのでクリアまで遊んだ。なかなか面白いゲームだ。
するとエンディングの文字がどうもおかしい。PPUビューアで見るとミラーリングが間違っているせいで横スクロールなのに横1画面分に書いてしまっており書き換えが画面内に見えている。

ここで初めてミラーリング設定をしていなかったことに気づく。ゲーム画面は1画面だから正常に見えていたんだな。危なかった。
というわけで先述のミラーリング設定をすると、

今度は正常。
最終的なdiffはこんな感じ。
12c12
< .org $8000
---
> .org $6000 ;$8000
166c166
< sta $100,x
---
> ;sta $100,x
174a175,177
>
> lda #$22 ;V mirror
> sta $4025
1942c1945
< .org $a000
---
> .org $8000 ;$a000
1949c1952
< .org $c000
---
> .org $a000; $c000
1955c1958
< .org $e000
---
> .org $c000; $e000
2086c2089
< .org $fffa
---
> .org $dffa ;$fffa
2092,2094c2095,2110
< .bank 4
< .org $0000
< .incbin "patterns.chr"
---
> ; .bank 4
> ; .org $0000
> ; .incbin "patterns.chr"
< .org $8000
---
> .org $6000 ;$8000
166c166
< sta $100,x
---
> ;sta $100,x
174a175,177
>
> lda #$22 ;V mirror
> sta $4025
1942c1945
< .org $a000
---
> .org $8000 ;$a000
1949c1952
< .org $c000
---
> .org $a000; $c000
1955c1958
< .org $e000
---
> .org $c000; $e000
2086c2089
< .org $fffa
---
> .org $dffa ;$fffa
2092,2094c2095,2110
< .bank 4
< .org $0000
< .incbin "patterns.chr"
---
> ; .bank 4
> ; .org $0000
> ; .incbin "patterns.chr"
CDのピットの数
6段のカレンダーが好きだ
PIC16のDhrystone MIPSを測ろうとしてみた
Twitterの画像の扱いがやっとまともになって嬉しい
ファミコンで9×9ドット文字表示(ほか)
謎の色名「honeydewtab」とlegacy color valueパース手順
6段のカレンダーが好きだ
PIC16のDhrystone MIPSを測ろうとしてみた
Twitterの画像の扱いがやっとまともになって嬉しい
ファミコンで9×9ドット文字表示(ほか)
謎の色名「honeydewtab」とlegacy color valueパース手順