メインコンテンツへスキップ
見出し画像

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が起動しているときの構成例です。機種や動作状態によって、実際の割り当ては変わります。

    ページアドレス一般的なMSX1での中身00000h–3FFFh本体ROM(BIOS)14000h–7FFFh本体ROM(BASICインタープリタ)28000h–BFFFhRAM3C000h–FFFFhRAM(ワークエリアやスタックもここ)\begin{array}{|c|c|c|} \\[-1.15em]\hline \textbf{\textsf{\small{ページ}}} & \textbf{\textsf{\small{アドレス}}} & \textbf{\textsf{\small{一般的なMSX1での中身}}} \\ \hline \textbf{\textsf{\small{0}}} & \textsf{\small{0000h–3FFFh}} & \textsf{\small{本体ROM(BIOS)}} \\ \hline \textbf{\textsf{\small{1}}} & \textsf{\small{4000h–7FFFh}} & \textsf{\small{本体ROM(BASICインタープリタ)}} \\ \hline \textbf{\textsf{\small{2}}} & \textsf{\small{8000h–BFFFh}} & \textsf{\small{RAM}} \\ \hline \textbf{\textsf{\small{3}}} & \textsf{\small{C000h–FFFFh}} & \textsf{\small{RAM(ワークエリアやスタックもここ)}} \\ \hline \end{array}

    MSXの面白いところは、この四つのページに、異なるスロットのメモリを割り当てられるところです。

    たとえば、BASICを使っているとき、ページ1にはBASIC ROMが居座っています。BASICを使わないマシン語ゲームなら、このページをカートリッジROMに切り替えて、ゲームのプログラムを実行できます。

    基本スロットの選択は、I/OポートA8hに書き込む値で設定できます。ページごとに2ビットを使って、0〜3のスロット番号を指定します。

    ビット対象1-0ページ 03-2ページ 15-4ページ 27-6ページ 3\begin{array}{|c|c|} \\[-1.15em]\hline \textbf{\textsf{\small{ビット}}} & \textbf{\textsf{\small{対象}}} \\ \hline \textbf{\textsf{\small{1-0}}} & \textsf{\small{ページ 0}} \\ \hline \textbf{\textsf{\small{3-2}}} & \textsf{\small{ページ 1}} \\ \hline \textbf{\textsf{\small{5-4}}} & \textsf{\small{ページ 2}} \\ \hline \textbf{\textsf{\small{7-6}}} & \textsf{\small{ページ 3}} \\ \hline \end{array}

    後述する拡張スロットもあるため、通常は次のBIOSルーチンを利用します。

    • ENASLT(0024h):指定したページを指定したスロットに切り替える

    • RSLREG(0138h):現在のスロット選択を読む



    スロットはどうやって区別されるのか

    スロット番号は、ROMに書かれた製品情報などで決まるのではなく、本体内部の接続や、カートリッジを挿した接続口によって決まります。

    たとえば、本体ROMをスロット0、カートリッジ用の接続口をスロット1・2、RAMをスロット3に割り当てた構成があります。実際の配置は機種によって異なります。

    各ページへのメモリアクセスには、そのページで選択されたスロットだけが応答します。

    そのため、同じ4000h〜7FFFhにBASIC ROMとカートリッジROMが存在していても、CPUから見えるのは、そのとき選んでいるほうだけです。

    拡張スロット(サブスロット)

    四つの基本スロットは、それぞれ、さらに四つの「サブスロット」に拡張できます。

    拡張スロットには、ページごとにサブスロットを選ぶためのレジスタがあります。このレジスタには、その基本スロットをページ3に選択した状態で、FFFFhを通してアクセスします。

    仕組みは少し複雑ですが、BIOSのENASLTなら、基本スロットとサブスロットをまとめて指定できます。

    BIOSで使うスロット指定の形式は、次のとおりです。

    ビット内容71ならサブスロットあり(拡張スロット)3-2サブスロット番号(0〜3)1-0基本スロット番号(0〜3)\begin{array}{|c|c|} \\[-1.15em]\hline \textbf{\textsf{\small{ビット}}} & \textbf{\textsf{\small{内容}}} \\ \hline \textbf{\textsf{\small{7}}} & \textsf{\small{1ならサブスロットあり(拡張スロット)}} \\ \hline \textbf{\textsf{\small{3-2}}} & \textsf{\small{サブスロット番号(0〜3)}} \\ \hline \textbf{\textsf{\small{1-0}}} & \textsf{\small{基本スロット番号(0〜3)}} \\ \hline \end{array}

    ENASLTを呼ぶときは、Aレジスタにこの形式のスロット番号を入れます。切り替えるページは、HLレジスタの上位2ビットで指定します。

    たとえばHレジスタを40hにすればページ1、80hにすればページ2が対象です。

    基本スロットが拡張されているかどうかは、ワークエリアのEXPTBL(FCC1h〜FCC4h)のビット7で確認できます。

    注意:今実行しているプログラムや、使用中のスタックがあるページを不用意に切り替えると、動作を続けられなくなることがあります。特にページ3にはBIOSのワークエリアやスタックがあるため、今回のゲームではRAMのまま使います。



    カートリッジROMが起動する仕組み

    カートリッジは、電源を入れたとき、本体のBIOSに「プログラム入りのROMがある」と見つけてもらうことで起動します。

    目印は、4000hまたは8000hに置かれたROMヘッダーの、先頭2バイトにある文字「AB」です。

    起動処理のうち、今回関係する部分を大まかにまとめると、次のようになります。

    1. 本体がRAMの確認やワークエリアの初期化を行う

    2. 各スロットの4000h・8000hにあるROMヘッダーを調べる

    3. INITが0でなければ、指定された初期化ルーチンを呼び出す

    4. BASICの起動処理では、TEXTが指定されたROM内のBASICプログラムを自動実行する

    ROMヘッダーの構成は、次のとおりです。位置はヘッダー先頭からのバイト数で、+10は十進数表記です。

    位置サイズ名前役割+02ID「AB」(41h 42h)+22INIT起動時に呼ばれるアドレス+42STATEMENT拡張命令用(今回は0)+62DEVICEデバイス用(今回は0)+82TEXTBASICプログラムの位置+106予約0で埋める\begin{array}{|c|c|l|l|} \\[-1.15em]\hline \textbf{\textsf{\small{位置}}} & \textbf{\textsf{\small{サイズ}}} & \textbf{\textsf{\small{名前}}} & \textbf{\textsf{\small{役割}}} \\ \hline \textbf{\textsf{\small{+0}}} & \textsf{\small{2}} & \textsf{\small{ID}} & \textsf{\small{「AB」(41h 42h)}} \\ \hline \textbf{\textsf{\small{+2}}} & \textsf{\small{2}} & \textsf{\small{INIT}} & \textsf{\small{起動時に呼ばれるアドレス}} \\ \hline \textbf{\textsf{\small{+4}}} & \textsf{\small{2}} & \textsf{\small{STATEMENT}} & \textsf{\small{拡張命令用(今回は0)}} \\ \hline \textbf{\textsf{\small{+6}}} & \textsf{\small{2}} & \textsf{\small{DEVICE}} & \textsf{\small{デバイス用(今回は0)}} \\ \hline \textbf{\textsf{\small{+8}}} & \textsf{\small{2}} & \textsf{\small{TEXT}} & \textsf{\small{BASICプログラムの位置}} \\ \hline \textbf{\textsf{\small{+10}}} & \textsf{\small{6}} & \textsf{\small{予約}} & \textsf{\small{0で埋める}} \\ \hline \end{array}

    注意したいこと

    4000hのヘッダーからINITが呼ばれる場合、その時点でページ2まで同じカートリッジに切り替わっているとは限りません。

    そのため、32KB ROMの後半、8000h〜BFFFhも使うプログラムでは、ページ2をカートリッジ側に切り替える処理が必要です。通常はBIOSのENASLTを使います。

    今回のフルマシン語版はページ1だけで収まるため、この処理は不要でした。



    カートリッジROMの種類

    初期のカートリッジROMには、8KB、16KB、32KBなどのものがありました。さらに大きなプログラムやデータを扱うものには、バンク切り替えの仕組みを持つ「メガROM」などがあります。

    ROMがどのアドレスに見えるかは、容量だけではなく、カートリッジの回路によって決まります。

    今回の説明に関係する配置例は、次のとおりです。

    容量置き場所備考8KB4000h〜5FFFh または 8000h〜9FFFh小さなソフト向け16KB4000h〜7FFFh または 8000h〜BFFFhマシン語ゲームの定番32KB4000h〜BFFFh前半がページ1、後半がページ2\begin{array}{|c|c|c|} \\[-1.15em]\hline \textbf{\textsf{\small{容量}}} & \textbf{\textsf{\small{置き場所}}} & \textbf{\textsf{\small{備考}}} \\ \hline \textbf{\textsf{\small{8KB}}} & \textsf{\small{4000h〜5FFFh または 8000h〜9FFFh}} & \textsf{\small{小さなソフト向け}} \\ \hline \textbf{\textsf{\small{16KB}}} & \textsf{\small{4000h〜7FFFh または 8000h〜BFFFh}} & \textsf{\small{マシン語ゲームの定番}} \\ \hline \textbf{\textsf{\small{32KB}}} & \textsf{\small{4000h〜BFFFh}} & \textsf{\small{前半がページ1、後半がページ2}} \\ \hline \end{array}



    フルマシン語版をROMにする

    今回作成したマシン語本体は3,723バイトです。これに16バイトのヘッダーを付けると、合計3,739バイトになります。

    これまではRAMのD000hからプログラムを置いていましたが、ROM版では4010hから置きます。書き換えが必要な変数やバッファは、RAM側で扱います。

    プログラム本体はページ1に収まるため、起動後にページ2をカートリッジへ切り替える必要はありません。

    アドレス中身4000h–4001h「AB」4002h–4003hINIT(4010h)4004h–400FhSTATEMENT・DEVICE・TEXT・予約(すべて0)4010h–ゲーム本体(3723バイト)\begin{array}{|c|c|} \\[-1.15em]\hline \textbf{\textsf{\small{アドレス}}} & \textbf{\textsf{\small{中身}}} \\ \hline \textbf{\textsf{\small{4000h–4001h}}} & \textsf{\small{「AB」}} \\ \hline \textbf{\textsf{\small{4002h–4003h}}} & \textsf{\small{INIT(4010h)}} \\ \hline \textbf{\textsf{\small{4004h–400Fh}}} & \textsf{\small{STATEMENT・DEVICE・TEXT・予約(すべて0)}} \\ \hline \textbf{\textsf{\small{4010h–}}} & \textsf{\small{ゲーム本体(3723バイト)}} \\ \hline \end{array}

    今回は、未使用部分をダミーデータで埋めて、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以内に収まりました。

    アドレス中身8000h–800Fhヘッダー(「AB」とTEXTポインタ)8010h〜BASICプログラム(約1.8KB、70行)A000h〜マシン語エンジン(2726バイト)\begin{array}{|c|c|} \\[-1.15em]\hline \textbf{\textsf{\small{アドレス}}} & \textbf{\textsf{\small{中身}}} \\ \hline \textbf{\textsf{\small{8000h–800Fh}}} & \textsf{\small{ヘッダー(「AB」とTEXTポインタ)}} \\ \hline \textbf{\textsf{\small{8010h〜}}} & \textsf{\small{BASICプログラム(約1.8KB、70行)}} \\ \hline \textbf{\textsf{\small{A000h〜}}} & \textsf{\small{マシン語エンジン(2726バイト)}} \\ \hline \end{array}

    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活用 #プログラミング

     
     
    AIで時間を生み出す実験をしています。 元PC系出版社編集者。 Claude・ChatGPT・Geminiを並行使用しながら、 実践した話だけを書いています。

    あなたへのおすすめ