Advance)エミュレータのmagiaをGo言語で開発しました。(よかったらスターお願いします!)この記事では、それに伴って得られた知見やエミュレータ開発に関するノウハウについて書いていこうと思います!なぜGBAエミュレータを作るのか?GBAが好きだからまず第一にこれです。僕は子供のころGBCとGBAが大好きで親の制限下の中、ずっとやっていました。思い入れがないゲームのエミュレータでないと作ろうという気もあまり起きませんし、作り始めてもモチベーションが続かないと思います。低レイヤの知識のアウトプットになるあとで述べますが、エミュレータ開発はCPU、メモリなど低レイヤ周りの知識が求められます。成果物で遊べる低レイヤ周りの趣味開発の成果物というのは、ほとんどの場合、頑張って作っても、作って終わりになるものが多いです。(やる意味は大いにありますが。ゲームのエミュレータなら実際に成果物で思い出のゲームを遊べるので、開発を最後までやるためのモチベーションは保ちやすいかなと思います。エミュレータ開発に必要な知識エミュレータ開発には低レイヤ(コンピュータ)の知識が必要になってきます。ゲームというのは、究極的には、ソフトのプレイに特化した単なるコンピュータだからです。一般的なコンピュータ同様、CPUやメモリなどがあります。エミュレータというのはそのCPUやメモリなどの動作をソフトウェア上で再現することなので、まずこれらがどのようなものであり、どのように動いているのかを知る必要があります。今回はGBAについて取り上げます。GBミクロについては取り上げません。ありCPUはなんとARMのCPUを採用しています。(そのおかげでLLVMのターゲットに指定できる)ゲームボーイとの違いCPU最大の違いはCPUです。ゲームボーイ(以降、GB)ではZ80を独自カスタムしたLR35902というCPUを使っていますが、このCPUは8bitで動作するCPUです。命令のサイズが8bit(1byte)しかありません!しかも、LR35902は掛け算命令など現代のCPUでサポートしている基本的な命令をサポートしていません。このおかげでCPUの命令セットがさまざまな命令をサポートできるようになりました。CPUですが...)またクロック数もGBの4MHzから16MHzに伸びました。BIOSGBのBIOSはチェックサムによるソフトの読み込みチェック+エントリーポイントへのジャンプくらいの機能しかないですが、GBAははるかに高機能になっています!GBAのBIOSには、割り算や圧縮、メモリコピーなど汎用的な機能が書き込まれているので、GBAのソフト開発者はこれらの命令を自前で開発する必要はありません!これらはシステムコールとして呼び出すことができます。互換性GBAはGBと互換性を持っていて、GBAでGBのゲームをプレイすることが可能です。基本仕様の表では書いていませんが、実はGBとの互換を保つため、GBAにはGBのCPULR35902も搭載されています。GBAのゲームを遊ぶときはARMのCPUだけを、GBのゲームを遊ぶときはLR35902だけを使います。ピクセル単位のタイルデータの集合(タイルセット)をあらかじめ用意しておき、画面描画時には、タイルセットの中からタイルデータを指定して画面に並べていくという描画形式でした。(タイルモード)タイルセット↓GBAではこれに加えて、ビットマップモードというメモリ領域に格納された色データがそのまま対応する画面上のピクセルに反映されるという描画モードもサポートしています。タイルモードもGBと違って画面がレイヤーを持つことができるようになりGBでは表現できなかった画面も表現可能になりました!実装過程THUMBモードまずはエミュレータというかコンピュータの核となるCPUから作っていきました。ここでGBAのARM7TDMIのTHUMBモードについて説明する必要があります。GBAに搭載されているARM7TDMIというCPUには2つのモードがあります。ARMモードとTHUMBモードです。ARMモードは前述の通り、32bitで動作するCPUなのですが、THUMBモードは16bitで動作します。解釈する命令幅も16bitになります。これは先程のGBAとGBのCPUのように、どちらか片方選んで、動作中はずっと一方のみを使うというわけではなく、実行中に簡単に切り替え可能です。ARMモードで実行する32bit命令のほうが、命令が持てるbit数の分、表現の幅が広いです。スタックにpushする命令は、ARMモードならSP(スタックポインタ)をインクリメントするか、デクリメントするか、そのタイミングはスタックにpushする前か後か、などを命令で指定可能ですが、THUMBモードのpush命令は、pushした後にスタックをデクリメントするということしかできません。ARMモードでいいのでは?と疑問に思うかもしれませんが、ここで問題になってくるのが、カートリッジ(ゲームカセット)から命令を読み取って実行する場合です。カートリッジはバス幅が16bitしかありません。CPUが一度にカートリッジから取ってこれる命令の長さは16bitまでなのです。ARMモードの命令は32bitなのでカートリッジの中のARMモードの命令を実行しようとするとCPUは2回に分けてカートリッジにアクセスすることになってしまい、非常に低速です。当時はカートリッジの容量もカツカツなので、THUMBモードでARMモードの命令と同じ結果が得られるならTHUMBモードで書いたほうが当然容量の節約にもなります。GBAはほとんどTHUMBモードで動作します...(だから実質16bitCPU)ただ、BIOSの命令などはARMモードで動作するので、エミュレータを作る場合は、このARMモードとTHUMBモードの両方をエミュレートしてあげる必要があります。こんな感じで命令のデコードから作っていきました。パイプラインGBAのCPUにはパイプラインが実装されていて、CPUの実行中に次の命令のフェッチが行われています。このため、現在の実行中の命令のアドレスとPC(プログラムカウンタ)の値がパイプラインによるフェッチの分ずれています。worldそんなこんなで苦労しましたが、なんとかCPUの実装を終えました。次に取り組んだのが画面描画です。前述のようにGBAにはタイルモードとビットマップモードがありますが、タイルモードはGBとほとんど同じだったのでGBエミュ開発の経験から、割とあっさり実装できました。worldを表示するだけの一番単純なソフト)を動かす準備ができました。ROMではビットマップモードのほうは使わないのでタイルモードの実装だけで大丈夫です。)動かしてみたところ、、、無事動きました!ちなみに、画面描画(エミュレートのほうではなく実際にPCにウィンドウを表示する方)や60fpsを保つために、Hoshiさんのebitenを使っています。ebitenはいいぞ!その後、ビットマップモードのほうも実装しましたが、こちらはメモリと画面上のピクセルが1:1に対応しているというシンプルな構造だったため、こちらも簡単に実装できました。worldなどの単純なROM)の動作に成功しました!今度は通常のROM(今回は吸い出したロックマンエグゼ6グレイガ)を動かすことを目標に、DMA転送やタイマー、キー入力などを実装していきました。DMA転送は、GBの場合はVRAM以外を転送先にすることはできませんでしたが、GBAではVRAM以外にも転送することができ、デジタルサウンドの再生にも利用されています。Goの実装の話から言えば、タイマーは当初、GBAのCPUやメモリのあるgbaパッケージとは独立のtimerパッケージで実装していたのですが、timerからGBAのメモリを操作するDMA転送のトリガーを引きたい時が出てきて、その際にgbaのパッケージと循環参照になってしまうため同一パッケージにしたという苦労話があります。このように、必要な機能を実装していき、機能が一通り揃ったところでロックマンエグゼ6を動かしてみたところ、タイトル画面の描画に成功しました!バグ取りほとんどの機能はできたので、残すはROMを動かしていく過程で見つけたバグを取っていく作業になります。bitが1つ正しくないだけで致命的な結果になるのがエミュレータ開発です。実際の原因箇所と、現れる結果が乖離していることも多く、バグの特定は想像以上に厳しいです。下の画像は、シフト命令でキャリーの更新処理にバグがあった結果生まれたエミュレーションの不具合です。みんながそっぽを向いちゃいました。(背景が水色なのは別のバグです)正直、このバグの修正には苦労しましたが、シュールなバグだったので割と楽しめました。このようなシュールなバグもエミュ開発の醍醐味だと思います。このようにバグ修正は大変なので、エミュレータを作る際にはデバッグ機能(メモリのダンプやブレークポイント)を実装することを強くお勧めします。他に遭遇したエミュレータならではのバグは、実機のGBAでは方向キーの←と→が構造上同時に押せないようになっているのですが、エミュレータでもし←と→を同時に押してしまえるようにすると、実際に押したときにクラッシュしてしまうゲームがいくつかありました。今回はGo言語でエミュレータを実装しました。(Pythonと比べて。60fpsを保つという性質上速度がエミュレータでは求められます)また上でも述べましたが画面描画や60fpsの維持、さらにキーボードの入力のハンドリングにはebitenという2Dゲームエンジンを使いました。ebitenは開発が積極的に進められていて、APIも直感的で2Dゲームエンジンならこれが一強だと思います。ebitenはいいぞ!wasm今回のエミュレータはGo言語で実装されています。wasmにコンパイルしてWeb上で動かすことが可能です。これができればブラウザ上でGBAのゲームが遊べるようになります。今回実装したGBAエミュレータは最適化はまだ全然していません。実際に広く使われているエミュレータにはパフォーマンスのためのさまざまな最適化が施されています。GBAの場合、BIOSの命令(割り算、arctan、圧縮など)はBIOSの命令列をエミュレートしたCPUで実行することでエミュレートしますが、mGBAなどの有名なエミュレータでは、処理の結果を、返り値を格納するレジスタに直接書き込んでしまい、エミュレートしたことにしています。emulation)と言います。SwitchのエミュレータであるRyujinXは、ソフトのARMCPUの命令を、エミュレータを動かすCPU(x86)の命令に実行時に変換を行い、同じコードを実行する際には変換したx86のコードをそのまま動かすJITの導入によってパフォーマンスをあげています。(参考記事)終わりにサクサクっと書いたので、雑な記事になってしまいましたが、ここまで読んでくださって本当にありがとうございました。Proよくある質問LinksX(Twitter)GitHubメディアキットLegal利用規約プライバシーポリシ。