
MSXのマシン語ゲームをガッチャンコできるカートリッジROM化してみた
毎度、rorosukeです。
前回、MSX1のSCREEN 3で動くインベーダー風ゲームの起動時間を短縮するために、「起動ディスク」を作成しました。
その記事がこちらです。
とても快適で、私自身もご満悦なのですが、実はこの方法以外にも、もっと高速に起動する方法があるのです。
何かというと……それは!
カートリッジROM化です
カートリッジROMなら、MSX本体にガッチャンコして電源を入れるだけで、ゲームが起動します。ディスクからプログラムを読み込む時間すら不要になります。
これはやりたい!
すぐAIに聞いてみました。
👦
本ゲームを、カートリッジROMのイメージにすることはできますか?
回答きました。
「できます。」
AIの説明によると、今のゲームはメモリ上のD000hから動く前提なので、これをカートリッジROMの配置に合わせて作り直し、先頭に16バイトのROMヘッダーを付けるとのことです。
ただし、プログラムの配置アドレスを変えるだけでなく、変数やバッファなど、書き換えが必要なものはRAMがマッピングされているところに置く必要がありますので、それらの対応もしています。
こうしてできたのが、次のカートリッジROMイメージファイルです。クリックすると、ローカルディスクにダウンロードされます。
メモ:BASICのソースコードなら、MSXPenの入力枠を使ってURLでシェアできます。今回のROM版は、その方法では共有せず、ROMイメージファイルを配布します。
MSXPenでの実行方法
私が最近お気に入りのMSXエミュレータが、MSXPenです。オンラインでMSXのプログラムを実行できます。
MSXPenには、カートリッジROMのイメージファイルを読み込む機能があります。
カートリッジROMイメージを読み込む
次のメニューで「Load ROM Image」を選び、ダウンロードしたROMイメージファイルを指定します。

ゲームが起動する
すぐにゲームが起動して、スタート画面が表示されます。

ゲームをプレイする
スペースキーを押すと、ゲームスタートです。

カートリッジROMをセットするだけで起動……。
うきゃーっ!最高じゃないか!
いい時代ですねー。
MSXのメモリとスロット
メモリの仕組みを少し整理しておきます。
MSXのCPU(Z80)が直接扱えるアドレス空間は64KBです。これを16KBずつ、四つに区切ったものを「ページ」と呼びます。
次の表は、RAMを32KB以上搭載したMSX1で、BASICが起動しているときの構成例です。機種や動作状態によって、実際の割り当ては変わります。
MSXの面白いところは、この四つのページに、異なるスロットのメモリを割り当てられるところです。
たとえば、BASICを使っているとき、ページ1にはBASIC ROMが居座っています。BASICを使わないマシン語ゲームなら、このページをカートリッジROMに切り替えて、ゲームのプログラムを実行できます。
基本スロットの選択は、I/OポートA8hに書き込む値で設定できます。ページごとに2ビットを使って、0〜3のスロット番号を指定します。
後述する拡張スロットもあるため、通常は次のBIOSルーチンを利用します。
ENASLT(0024h):指定したページを指定したスロットに切り替える
RSLREG(0138h):現在のスロット選択を読む
スロットはどうやって区別されるのか
スロット番号は、ROMに書かれた製品情報などで決まるのではなく、本体内部の接続や、カートリッジを挿した接続口によって決まります。
たとえば、本体ROMをスロット0、カートリッジ用の接続口をスロット1・2、RAMをスロット3に割り当てた構成があります。実際の配置は機種によって異なります。
各ページへのメモリアクセスには、そのページで選択されたスロットだけが応答します。
そのため、同じ4000h〜7FFFhにBASIC ROMとカートリッジROMが存在していても、CPUから見えるのは、そのとき選んでいるほうだけです。
拡張スロット(サブスロット)
四つの基本スロットは、それぞれ、さらに四つの「サブスロット」に拡張できます。
拡張スロットには、ページごとにサブスロットを選ぶためのレジスタがあります。このレジスタには、その基本スロットをページ3に選択した状態で、FFFFhを通してアクセスします。
仕組みは少し複雑ですが、BIOSのENASLTなら、基本スロットとサブスロットをまとめて指定できます。
BIOSで使うスロット指定の形式は、次のとおりです。
ENASLTを呼ぶときは、Aレジスタにこの形式のスロット番号を入れます。切り替えるページは、HLレジスタの上位2ビットで指定します。
たとえばHレジスタを40hにすればページ1、80hにすればページ2が対象です。
基本スロットが拡張されているかどうかは、ワークエリアのEXPTBL(FCC1h〜FCC4h)のビット7で確認できます。
注意:今実行しているプログラムや、使用中のスタックがあるページを不用意に切り替えると、動作を続けられなくなることがあります。特にページ3にはBIOSのワークエリアやスタックがあるため、今回のゲームではRAMのまま使います。
カートリッジROMが起動する仕組み
カートリッジは、電源を入れたとき、本体のBIOSに「プログラム入りのROMがある」と見つけてもらうことで起動します。
目印は、4000hまたは8000hに置かれたROMヘッダーの、先頭2バイトにある文字「AB」です。
起動処理のうち、今回関係する部分を大まかにまとめると、次のようになります。
本体がRAMの確認やワークエリアの初期化を行う
各スロットの4000h・8000hにあるROMヘッダーを調べる
INITが0でなければ、指定された初期化ルーチンを呼び出す
BASICの起動処理では、TEXTが指定されたROM内のBASICプログラムを自動実行する
ROMヘッダーの構成は、次のとおりです。位置はヘッダー先頭からのバイト数で、+10は十進数表記です。
注意したいこと
4000hのヘッダーからINITが呼ばれる場合、その時点でページ2まで同じカートリッジに切り替わっているとは限りません。
そのため、32KB ROMの後半、8000h〜BFFFhも使うプログラムでは、ページ2をカートリッジ側に切り替える処理が必要です。通常はBIOSのENASLTを使います。
今回のフルマシン語版はページ1だけで収まるため、この処理は不要でした。
カートリッジROMの種類
初期のカートリッジROMには、8KB、16KB、32KBなどのものがありました。さらに大きなプログラムやデータを扱うものには、バンク切り替えの仕組みを持つ「メガROM」などがあります。
ROMがどのアドレスに見えるかは、容量だけではなく、カートリッジの回路によって決まります。
今回の説明に関係する配置例は、次のとおりです。
フルマシン語版をROMにする
今回作成したマシン語本体は3,723バイトです。これに16バイトのヘッダーを付けると、合計3,739バイトになります。
これまではRAMのD000hからプログラムを置いていましたが、ROM版では4010hから置きます。書き換えが必要な変数やバッファは、RAM側で扱います。
プログラム本体はページ1に収まるため、起動後にページ2をカートリッジへ切り替える必要はありません。
今回は、未使用部分をダミーデータで埋めて、32KB(32,768バイト)のROMイメージファイルにしました。
プログラムそのものに32KBが必要というわけではありません。ファイルの構成については、後ほどまとめます。
BASIC+マシン語版をROMにする
フルマシン語のゲームがカートリッジROM化できましたが、BASIC+マシン語のカートリッジも作れます。
そこで、以前作ったBASIC+マシン語版についても、カートリッジROM化できるか試してみました。
結果的には作成できましたので、こちらのROMイメージも公開します。
なお、以前の記事で説明したとおり、こちらはBASICの処理に時間がかかるため、フルマシン語版より動作がややゆっくりで、ゲームの難易度としては易しめです。
フルマシン語版との違いについて
フルマシン語版ではBASICを使わないため、ページ1のBASIC ROMをカートリッジROMに切り替えて、ゲームを動かせました。
しかし、BASIC+マシン語版では、ページ1にあるBASICインタープリタを使う必要があります。
そこで今回は、ページ1にはBASIC ROMを残し、ページ2の8000h〜BFFFhに、BASICプログラムとマシン語エンジンを配置しました。
BASICプログラムは、通常のソーステキストではなく、BASICが実行できる中間コード形式で格納します。
BASICとマシン語を合わせたデータ量は約4.5KBです。配置の途中に空き領域もありますが、それを含めてもページ2の16KB以内に収まりました。
TEXTポインタは、BASICプログラムの先頭を示す00hの位置を指すように設定します。
変数とスタックはページ3のRAM
今回の構成では、ページ2をROMが使うため、BASICの変数やスタックはページ3のRAM(C000h以降)を使います。
マシン語エンジンの作業用メモリも、ページ3に用意しました。
E000h–E5FFh:エンジンのバッファ
E600h–E69Eh:エンジンの変数
BASIC側では、この領域を守るために、先頭で次の命令を実行します。
CLEAR 300,&HDFFF文字列用の領域を300バイトとし、BASICが使うメモリの上限をDFFFhに設定する命令です。これにより、E000h以降をマシン語エンジン用に確保します。
マシン語の呼び出し先は、次のように登録します。
DEFUSR0=&HA000:DEFUSR1=&HA003これで、A000hとA003hにあるマシン語ルーチンを、それぞれUSR0・USR1で呼び出せます。
マシン語をRAMに展開するローダーがいらないのは、呼び出すコードが最初からROM上にあるからです。
ROMイメージが32KBになっている理由
今回作成したROMイメージは、フルマシン語版もBASIC+マシン語版も、どちらも32KBにしています。
ただし、二つの版では、使っている場所が異なります。
フルマシン語版
前半の4000h側にヘッダーを置き、4010hからゲーム本体を配置しています。
ゲーム本体はページ1に収まるため、後半の16KBは使っていません。
BASIC+マシン語版
後半の8000h側に、BASIC用のヘッダー、BASICプログラム、マシン語エンジンを配置しています。
前半の4000h側には補助的なヘッダーを置き、そのINITは0にしています。前半のヘッダーから、ゲームの起動処理を呼び出さない構成です。
エミュレータとの兼ね合い
どちらの版も、プログラムの配置に必要な範囲は16KB以内です。32KBすべてを使っているわけではありません。
MSX1が発売されていたころはROMも高価でしたから、製品として作るなら、容量を抑えたいところです。
ただし、実機のカートリッジでは基板の配線で決まるROMの配置を、エミュレータではROMイメージのサイズやヘッダーなどから判断する場合があります。
今回は、32KBのROMイメージとして、MSXPenで正常に起動できた構成を公開しています。
32KB化や補助ヘッダーがすべてのエミュレータで必須という意味ではありません。容量を切り詰めることよりも、今回動いた形を優先しています。
まとめ:没頭してもいいですか?
MSXのゲームカートリッジを作っていたわけでもない私にとっては、カートリッジROMを作るのは、ある種の夢の実現です。MSXPenであっても、カートリッジをセットしてゲームが動いたときには、大興奮!
調子に乗って、MSXのメモリの仕組みなども解説してみましたが、実際には、AIに向かって「カートリッジROM化してちょ」と伝えれば、必要なお膳立てをしてくれますので、案外大丈夫。
それにしても、こうしてAIを使って、MSXのマシン語プログラムを作り、さらにはカートリッジROMまで作成できました。
かつて、私もマシン語プログラムは作った経験がありますが、ゲームまるまる1本作るなんてとても無理でした。
なので、この今の状況はまさしく……。
俺の未来キター!
もう泣きそう。
以上!
追記:続きの記事を書きました
またまたフルマシン語ゲームを作ったりしています。
#MSX #MSX1 #レトロPC #レトロゲーム #ゲーム制作 #マシン語 #Z80 #MSXBASIC #ROM化 #カートリッジ #MSXPen #生成AI #AI活用 #プログラミング