🎮

Rustでゲームボーイエミュレーターを書いた

2022/03/06に公開

RustでTGB-Rというゲームボーイエミュレーターを書きました。

https://github.com/tanakh/tgbr

とりあえずWindowsとLinuxで動作確認をしていて、エミュレーションの精度もそれなりに出ているはずです(以下は非公式ROMによるスクリーンショット)。


GameBoy WORDLE


2048gb


Hi-Colour Demo


Bad Apple!!

名前は、以前私が書いていたゲームボーイエミュレーターの精神的後継だったり、Rustで書いていたりとかでこうなりました。

以前書いたエミュレーターもオープンソースで公開していたのですが、ふと検索してみたら、GitHubにプロジェクトができていて、メンテナンスが続けられていました。

https://github.com/libretro/tgbdual-libretro

私がこれを書いていた時期はGitHubどころかgit自体が存在しないような時代で、サーバーを借りてそこに手作りのアーカイブをしこしこアップロードしていたような、あまりに素朴な開発でしたが、そんな時代のソフトでも、オープンソースにしておいたことで気に入ってくれた人がメンテしてくださって、最新のスマホでも動くようになっていてなんだか感慨深いものがありました。

Rustでエミュレーターを書く

とまあ、それはそれとして、今回の話です。

実はRustを勉強しはじめた頃も一度エミュレーターを書こうとしたことがありました。Rustは速いコードが書けるし、バグを防ぐ機能が豊富で、型の表現力も比較的高いので、エミュレーターのような、速度が必要で、デバッグが大変なソフトを書くのには適した言語だと思ったわけです。しかし当時はRustへの理解度が低く、Rust特有のプログラミング上の制限を回避するすべがいまいち良く分からず頓挫していました。それから数年経って、さすがにもう大丈夫だろうと思ったので改めて書いてみたら、今度は問題なく書けたという次第です。

エミュレーターというのは、基本的には実機の各コンポーネントを模するコードを書くことになるんですが、それらが相互に通信して同期的に動くので、結構Rustで素直に表現できる構造に落とし込むのが難しかったりします。特に、もともとC++で書いていたソフトをたたき台にして作ろうとしていたので、それのオブジェクトの参照関係をストレートにはうまく表現できず、綺麗にボローチェッカーを通せるコードが書けずにスタックしていました。

しかし今回はRustのノウハウも溜まってきて、データを所有するのではなく管理する人に必要なものを要求するというような、Rustでうまく表現するためのパターンやらなんやらもいろいろ引き出しから取り出せるようになっていて、割と苦労もなく書けてしまいました。ここら辺のノウハウやらのケーススタディーは、次の機会にでも解説したいと思います。

最近のエミュレーター開発シーン

ゲーム機のエミュレーターを書くのもだいぶ久しぶりだったのですが、その間にGitHubが出来たりいろいろあったので、ずいぶん環境は変わっていました。古のコードや資料がサルベージされてgitで管理されてメンテされていたりして、情報がずいぶん見つけやすくなっていました。実機ハードの個別の機能のコーナーケースをチェックするためのテストROMが充実していて、これを通すだけで割と簡単に高精度のエミュレーターができてしまいました。ゲームボーイに限った話だと、ゲームボーイ・ゲームボーイカラーには、本体に256バイトもしくは2300バイト程度のブートROMが内蔵されているのですが、オープンソースのブートROMが開発されたりしていて、特にゲームボーイカラーのゲームボーイ互換モードのパレット設定などをエミュレーター側が実装しなくてもよくなっていたりして、そこらへんも楽ができるようになっていました。また、なぜかゲームボーイのソフトを開発するための環境がやたら充実していて、多くのデモプログラムやゲームも公開されてたりしました。

昔と比べてCPU性能がずいぶん向上したこともあり、かつては各コンポーネントの状態が変わるまでのクロックを静的ないし動的にスケジュールして、まとまったクロック数まとめて動かして処理のオーバーヘッドを減らしたりしていましたが、今や完全に1クロックごとに各コンポーネントのエミュレーションをインターリーブさせても充分に速度が出るようになっていました。例えばタイマー割り込みで定期的に音声チャンネルのボリュームを変更することでPCMを再生しているソフトががあったりするのですが、これを波形をまとめて生成したりすると正確な波形にならず綺麗に聞こえなかったりするので、レジスタへの書き込みログを残してタイミングを補正してやるみたいな処理が必要になりますが、1クロックごとにやれば何も考えずとも勝手に正確な波形が出せます。要するに、昔よりシンプルなコードで、昔より精度のいいエミュレーションができるようになりました。エミュレーション対象のハードとの性能マージンが大きくなれば、その分エミュレーションは楽になるというものです。

実装した機能など

一通りゲームボーイとゲームボーイカラーのエミュレーション機能は全部実装してあって、珍しいMBC(メモリバンク切り替えとか拡張デバイス載せるためのにカートリッジに入っているチップ)以外は実装してあります。サポートされているMBCのソフトで、動かないものがあればバグなので、報告していただけるとありがたいです。UI面では、キーコンフィグとゲームパッドのサポート、ホットキーの再割り当て、ステートセーブと倍速機能、それから最近のエミュレーターに実装されている、巻き戻し機能を付けてみました。実はこれを見て、自分もこれ実装してみたいなあという気持ちが今回書いた動機の一つでもあります。

で、それらの機能を実現するために頑張ってGUIを書きました。

RustでGUIアプリを書くというのも結構いい経験になりました。Rustには結構頑張っているものからそうでないものまで、GUIを書くためのライブラリは山のようにあるのですが、せっかくRustで書くので、主要なプラットフォームでは動くようにしたいと思ったわけです。

ライブラリの候補はたくさんあるとは言え、GUIでクロスプラットフォーム、さらにそのままwasmにコンパイルできるもの、といったところまで要件を広げると、使えるものはかなり限られてきます。今回の場合だと、ゲームエンジン系とGUIツールキットの両方の機能が使えるもので、かつメンテナンスが放棄されていないようなもので将来性もありそうなものとなると、本当に数えるほどしかなくて、結局bevy+eguiという結論になりました。

それなりに開発されてそうなライブラリは一通り全部試して、いけそう、やっぱダメそう、ってのを延々繰り返してサーベイしていたんですけど、実際に自分の用途に使えそうなものはあんまりなくて、だんだんめげそうになって、エミュレーションのコード書くよりそっちの方がはるかにしんどかったです。実際これでも機能が満足だったわけではなくて、本当はネイティブウインドウのメニューバーとかを出したかったんですけど、クロスプラットフォームでメニューバーを出すというのはどうやら自明ではないらしく、仕方がないので諦めました。とはいえ、スマホやWebを考えるとメニューバーというのは相性が良くないのかな?という気もしますし、最近のGUIアプリのデザインとかも勉強しないといけないのかなあという気もしています。

今後の予定

まだ実装していないけど、いずれ実装しておきたいような気がする機能としては、

  • wasm版を作る(そのままコンパイルできるように想定しているが、実際にはまだやっていないので)
  • 私が昔作っていたゲームボーイエミュレーターの特徴は通信機能だったけど、これも実装できるようにコア部分は書いたけどアプリ部分を書いてないので、それを実際に作る
  • リッチなGUIが使えること前提にライブラリを選んだので、リッチなGUIを持つデバッガーを実装したい

などがあります。他にも、こういう機能があるといいのになあ、というご意見があれば、ぜひIssueの方に頂けるとうれしいです。

Discussion