MAME

世界的に有名なアーケードマシンエミュレータ「MAME」。最新バージョンではとてつもないサイズになっており、ソース解析は大変です。ということで、オフィシャルページで公開されている、一番最初のバージョン「Ver0.1」のソースコードを読んでみることにしました。最新と比べるとサイズがかなり違いますが、基本的な考え方は大差ない…といいのですが。では、見ていきましょう。

MAMEで重要なのは「ドライバ」と「マシン処理」のように思えます。
「ドライバ」には、ROMイメージの格納アドレスや専用チップ処理の関数アドレスやその他設定値などが構造体としてまとめられています。
「マシン処理」では、専用チップ処理(サウンド再生やメモリアクセス等)の実態があります。

上記ドライバ構造体のアドレスを一つの配列にまとめているのが「ドライバテーブル」です。図にすると以下のようになります。

ドライバテーブル

起動時に引数としてゲーム名が付けられるので、その名前を元にドライバテーブルからドライバを探します。
ドライバが見つかると、初期化をして開始準備を行います。そしてCPU開始。開始後は以下の図のようになります。

メイン処理

CPU開始後は、メインループへ突入してCPUが動き続けます。CPU処理から、メモリ読み書き、ポート入出力、割り込みの各関数が必要に応じて呼ばれます。ここから先の処理はゲームによって動作が異なりますが、そこで最初に選択したドライバ情報が活用されるわけです。最初にドライバを探してポインタを設定している為、この部分ではポインタの先の関数を呼ぶだけになっています。

割り込み処理では、MAMEが動作するプラットフォームに依存した処理が行われます。この部分を書き換えれば他のOSへの移植もほとんど完了という状態になっているのでしょう。この部分をWindows用に実装すれば自分なりのMAME32が作れる!?

対応ROMの追加も、ドライバとマシン処理を書き、配列へアドレスを追加するだけで完了するという実にわかりやすい作りになっています。最初のバージョンではCPUは「Z80」のみだったのですが、現在は多数のCPUに対応されているということで、CPU切り替えの処理が入っているはずです。そのあたりは見ていないのでまだわかりませんが、きっと追加しやすい作りになっているのでしょう。

上図右端の「ROM・RAM」は、一次元配列として確保されています。CPU処理からこのメモリを参照して命令を実行しているようです。ROMイメージの読み込みもこのメモリに行われます。

簡単ではありますが、MAMEの内部をまとめてみました。細かい部分はわかりませんが、大まかな動作イメージが理解できたと思います。「こんなんじゃないよ!」という点がありましたらお知らせいただけれると助かります。

今までエミュレーターを作ろうとしてもCPUからポートの呼び出しはどうやって実装したらいいのかわからなかったのですが、内部から関数呼び出しをすればいいというのがわかっただけでもソースを呼んでみた甲斐がありました。最初のバージョンからこれほど拡張性がある作りになっているなんてさすがですよね。