Zigでゲームボーイエミュレーターをつくる

カテゴリー: PC
投稿日:
更新日:
書いた人: 山椒ねこまんま

先日のChip-8 に続き、Zigでゲームボーイのエミュレーターを作ってみました。

スプライトやAPUのないChip-8に比べるとはるかに難しくていろいろつまづいた結果、完成まで2週間ちょっとかかってしまいました。 なかなか解決策が見つからなかったものもあるので、後続のためにもどうやって解決したかを書いていこうと思います。

ソースコードは毎度のごとく GitHub で公開しています。

参考にした情報とか

The Ultimate Game Boy Talk

ゲームボーイの仕様について1時間でざっとまとめられています。 最初に見て概観をつかむのにおすすめです。

Game Boy: Complete Technical Reference

PDFへのリンク

Pan Docs に「エミュレーター開発者はこっちもおすすめだよ」って書いてあったので、CPUの実装まではここを参考にしていました。 ただ、間違いや欠けている部分がちょくちょくあるのでそのあたりは RGBDS で確認が必要でした。

gb-docs-ja

GitHubのリポジトリへのリンク

貴重な日本語のドキュメントです。

IOレジスタの仕様や各チップの動作を確認するのにぴったりだと思います。

Game Boy Sound Emulation | NightShade’s blog

ブログへのリンク

APUの細かい挙動が書かれたブログです。

APUは実装が難しいのか、サウンドが未実装のエミュレーターは多いみたいです。 私もグラフィック周りが完成した時はもう終わりでいいかなと思いかけました。
が、昔のゲーム機ならではのピコピコ音が鳴った瞬間はかなりじ〜んと来ますので、ぜひとも実装することをおすすめしますよ!

テストロムたち

BlarggのテストROMdmg-acid2 で動作を確認することで実装があっているか自信をもって進められました。

Blarggのcpu_instrsテストは Gameboy Doctor というツールを使うことで非常に簡単にデバックできるのでおすすめ。

つまったところ

DAA命令の仕様がZ80とは違う

以前Z80について色々調べていた こともあり、「ゲームボーイのCPUはカスタムZ80って聞いたことあるし同じだろう」と思って実装したら全然違いました。

flag0x06 = flagH or ((A & 0xF) > 9);
flag0x60 = flagC or (A > 0x99);

if (!flagN) {
    // 加算
    if (flag0x06) A +%= 0x06;
    if (flag0x60) A +%= 0x60;
    flagC = flag0x60;
} else {
    // 減算
    if (flagH) A -%= 0x06;
    if (flagC) A -%= 0x60;
    flagC = flagC;
}

flagH = 0;
flagZ = (A == 0);

コードっぽく書くとこんな感じ。 ハーフキャリーと、減算時のキャリーの挙動が違います。

ちなみに ゲームボーイのCPUとZ80は普通に別物 です。 レジスタから命令の種類から必要クロック数に至るまで全然違います。

Blarggのcpu_instrsでテスト実行が完了しない

個別のテストは全部通るにも関わらず、全体テストだとテスト3のところで止まってしまうという現象に遭遇しました。

Reddit(リンクは保存し忘れちゃった)に「STOP命令をちゃんと実装した場合は0xFF4Dを読まれた時に0xFFを返すようにしておかないと待機状態になっちゃうよ」とあったので試してみたら最後まで通るようになりました!

エミュレーターではSTOPはNOPにしちゃうのがまるそうです。

これでcpu_instrsクリア!!

instr_timingも通っていました。

dmg-acid2で、ウィンドウを実装したのに顎の代わりに目が表示される

PPUの内部カウンタの仕様を見落としており、失敗例に書かれている「Window internal line counter」を見ても全くピンとこず、時間を取られてしまいました。

ウィンドウが画面に描画された時にだけカウントアップし、V-Blankのときにリセットされる内部カウンタがあるそうです。

dmg-acid2で、表示するスプライトを正しい順番で選ぶようにしたのにホクロが表示される

x座標とメモリアドレスでソートした後に選んだ順で描画していたのが原因でした。 LIFOで描画しないといけないようです。

そんなこんなでdmg-acid2をクリアしました!

波形情報をPythonに送って音を出すようにしていたら、ブツブツ途切れるような感じになった

ゲームROMを動かしながらAPUを作っていたのですが、Chip-8の記事 で紹介した波形を構成する情報を送ってPython側でサンプリングして再生する方法では、どうやっても音が途切れてしまいました。

そこで色々調べた結果、「sox」なるアプリケーションに波形のbitデータを渡すとそのまま再生してくれるとのことだったので音の再生方式をまるっと変えました。

おかげできれいな音がなるようにできました!
前回紹介したテンプレート もこの方式に更新してあります〜(音の高低を変えるデモは面倒だったのでオミットしたケド)

ゲームを動かしている様子

完成したエミュレーターでゲームを動かしてみました!

動かしているゲームROMは Tobu Tobu Girl、ブートROMはGAME BOY Free Boot ROM です。

ゲームボーイのゲーム&吸い出し機は持っていないのでフリーのROMを探してきました。

音もグラフィックもゲームボーイ感が感じられていい感じ
アクションが苦手なこともあって全然クリアできませんでしたが…



というわけでゲームボーイエミュレーターを作った話でした。

作る前は「フリーのROMがあるからゲームを買わなくて済んで経済的!」とか思っていたのですが、いざ完成したエミュレーターでゲームが動いているのを見ると、カービィなど有名どころのゲームが動いているのが見たくなってきました。

ただ、ROMの吸い出し機は結構いいお値段がする上、他のゲームのエミュレーターを作った時もいちいち買っているとお金がかかって仕方ないので、今後作る可能性があるゲームをまとめて吸い出せる基板を作っちゃおうと思います。

既に OSSの吸い出し機 は存在するのですが、コアがArduino Megaだったり対応ゲームが幅広すぎたりと作るのにお金がかかりそうです。 なのでソフトウェアだけ拝借して、家に転がってるArduino Nanoを2個使って2,3千円で作れないかなー と考えています。


アプリなどを作ったりしています! よかったらみていってください→ つくったもの
今のイチオシ↓

山椒ねこまんま
山椒ねこまんま

個人アプリ開発者、ブロガー。

UKキーボードのためだけにMacを選んでいる。