改造者、GBAのクラッシュ音からゲームROMを復元
(arstechnica.com)- TheZZAZZGlitchは Game Boy Advanceのクラッシュ音 を録音して、カートリッジ内のゲームデータを識別し、最終的に同じROMを復元できることを示した
- 核心はクラッシュ後に出力されるオーディオを ROMデータ として解釈することだが、ソース形式ごとの調整が多く必要で、汎用ダンプツールとして使うのは難しい
- 4時間を超える録音のうち約 1時間50分付近 で特徴的な波形が現れ、その後はゲームの楽器音やオーディオサンプルが順に聞こえる
- Pythonスクリプトと整列補正により 99.76%の精度 に到達したが起動には失敗し、3回の録音を多数決アルゴリズムで統合すると99.979%まで改善した
- 7本の録音の統合と空白領域のフィルタリングの末に 100%一致 を達成し、クラッシュ音だけでGBA ROMを復元できるという実験的可能性を確認した
クラッシュ音からROMデータを読み出した実験
- TheZZAZZGlitchは、GBAがソフトウェアクラッシュ後に出す音にゲームデータが含まれうることを実演した
- クラッシュしたGBAはカートリッジ内部のデータに基づく音を出し続けることがあり、特殊なハードウェアとコードでこのオーディオを解析すれば、どのゲームかを識別できる
- ただしこの方法は、カートリッジデータを簡単にダンプする手段ではなく、すぐに使える解決策 でもない
- ソース形式ごとに多くのチューニングが必要になる
長時間録音で現れた波形
- GBAをクラッシュさせたあと 4時間以上 録音した結果、約1時間50分付近で特徴的な波形が現れた
- その後の区間では、ゲームに含まれる実際の 楽器音やオーディオサンプル が順番に聞こえる
- 残りのデータは13,100Hzの 8ビットデータ のように聞こえ、一部の区間は非常に奇妙に聞こえる
Pythonスクリプトと最初の復元の試み
- TheZZAZZGlitchは「2日間バグを修正したあと」、きれいなGBAクラッシュダンプ録音を読み取る Pythonスクリプト を用意した
- ROMデータには0バイトの領域が大きく存在し、この部分は無音のように見えるため、オーディオからの解析が難しい
- 元のROM内の位置を基準にセクションを再整列する別スクリプトを実行したあと、復元ROMは 99.76%の精度 に達した
- このROMは依然として起動せず、既知のROMデータを使って未知のデータを明らかにする方法だったため、技術的には「チート」に当たる
- 完全にブラインドで進めたとしても、適用できる仮定や推定は残っている
複数の録音を統合して精度を向上
- 次の段階では 録音品質の改善 に集中した
- 3回録音した結果を「多数決」アルゴリズムで統合すると、精度は 99.979% まで上がった
- この出力ROMは起動には成功したが、テキストが文字化けし、タイトル画面でクラッシュが発生した
- その後、7本の録音を結合して空白領域をフィルタリングすると、100%一致 を達成した
物理ハードウェアと追加実験
- 動画の後半では、この方法が 物理ハードウェア 上でどのように動作するかも確認している
- 他のゲームでも実験し、複製カートリッジ内のARMコードに関する謎もあわせて追跡した
- より良い録音を得る方法も試された
- そのひとつは、1チャンネルに粗くミックスダウンする「cursed adapter」を使う方法だった
1件のコメント
Hacker News のコメント
0x00 が長く続くときの問題は、**クロックリカバリ(clock recovery)に関係している
一部のデジタルデータストリーム、特にディスクドライブの磁気ヘッドからの生データや Ethernet のような高速シリアル通信は、別個のクロック信号なしで送信される
受信側はおおよその周波数基準からクロックを生成し、位相同期ループ(PLL)でデータストリームの遷移にクロック位相を合わせる
この方式が機能するには、PLL 発振器のドリフトを補正できるだけのデータ遷移が十分頻繁に現れる必要があり、遷移なしでどれだけ長く耐えられるかを最大連続同一数字(CID)**仕様と呼ぶ
https://en.wikipedia.org/wiki/Clock_recovery
その後は 64/66b のように大きなビットの塊に短いヘッダーを付けてクロック遷移を保証し、全体を疑似乱数スクランブラに通す方式へ移行した
クロックが完全に同期していても、長い区間ではほぼすべて 1 の信号とほぼすべて 0 の信号が最終的に同じになってしまう
オーディオはアナログで、ビットストリームとしてデコードするには DAC が必要であり、特別な同期なしに 44.1kHz や 48kHz のような固定周波数で送信される
https://en.wikipedia.org/wiki/Wireless_Set_Number_10
ほぼ20年前に、第4世代 iPod のファームウェアをピエゾスピーカーでダンプした元祖 iPodLinux ハックを思い出す
https://web.archive.org/web/20140810083116/http://www.newscientist.com/article/dn7085
偶然にもそのおかげで iPod 上で GBA ゲームも動かせるようになったが、クリックホイールで Doom を遊んだり白黒動画を見たりするだけでも十分満足だった
まだ iPod Classic を持っていて、バッテリーをアップグレードしたあと 256GB の MicroSD カードを4枚入れた
最近 Bluetooth を追加する改造も見かけたが、背面ケースを交換する必要があるので、そこまではやらなさそう
デザインにも、音楽ファイルのコピーを自分で所有しているという点にも、時が経っても色あせない感覚がある
ここでさらに注目されてうれしい
数日前にも投稿されていたが埋もれていた: https://news.ycombinator.com/item?id=39037104
元動画にはこの短い記事にはない内容が多く、ハッカーが DS から適切なオーディオ品質を得るために自分で切り貼りして作ったカスタムアダプターも含まれている
https://www.youtube.com/watch?v=0-7PSmYYHF0
Zzazz は毎年エイプリルフールのコンテスト/イベントを開いていて、通常はある程度のレトロハックやリバースエンジニアリングが入る
参加をおすすめする
過去のコンテストは GitHub にある
Nintendo のオーディオアーキテクチャはいつも興味深かった
もともと NES には任意波形を作れるサンプルジェネレータがあり、2つの方法で駆動できた
1つはメモリアドレスを渡すとビットを読み取り、非常に単純な波形として処理する方式で、1 は「値を1つ上げる」、0 は「値を1つ下げる」なので、平らな波形を作るには 10101010 のように繰り返す必要があった
もう1つは CPU から「初期値」を継続的に直接投入してチップを直接駆動する方式で、実際にはオーディオドライバが RAM からビットを読むより速かった
問題は、この方式が CPU サイクルをすべて食い尽くすため、ほかの処理をしていないときにしか使えなかったこと
Battletoads のようなゲームはこれを活用し、アクションが止まっている間により高音質のドラムヒットを再生し、タイトル画面、敵に最後の一撃を与えるときに一瞬すべてのアクションが止まる「パリッとした」効果、記憶に残るポーズ音楽に使っていた
ゲームが直接駆動モードと、チップがサンプルを読み取るモードを切り替えるデモがここにある。Retro Game Audio がエミュレータで修正した動画を投稿し、直接駆動サブルーチンに入った瞬間を示している: https://www.youtube.com/watch?v=JGT0FM3yh-w
そもそも、なぜこんなことが起きるのかが気になる
こういうゲームが状態をオーディオとしてダンプするのはよくあることなのか? ゲーム開発者向けの意図的なデバッグツールなのか?
基本的にGBAのサウンドはRAM内のバッファからオーディオをストリーミングし、割り込みがハードウェアにバッファの先頭から再度読み込むよう知らせる必要がある
しかし、ゲームがクラッシュした場合のように割り込みが発生しないと、オーディオストリームはバッファを越えてメモリの別領域を読むようになる
[1] https://www.youtube.com/watch?v=wSWNkpqjtQY&t=361s
理論上、GBAはゲームがクラッシュしたときにアーキテクチャを「停止」させることもできたし、定期的に実行されるべき状態更新をマスク不能割り込みに結び付け、その更新が止まったら再起動するウォッチドッグを置くこともできた
しかしそうした機能はコストがかかるため、GBAには搭載されていない
Nintendoは古いゲームカートリッジメーカーの伝統的なアプローチ、つまり「自分たちのゲームにバグがなければ、未定義のハードウェア状態の動作を心配する必要もない」を選んだわけだ
そのため、GBAゲームが割り込み無効の無限ループのようなクラッシュ状態に陥ると、オーディオチップはシステムがクラッシュしたことを知らず、RAMの連続したビットを読み取って音に変換するという単純な作業を続ける
通常動作中なら読み取り処理を管理していた後始末ルーチンがなくなるため、そのまま読み続け、最終的にはカートリッジROMの値を表すビットにまで到達する
本当にとんでもなく印象的だ
ここで使われている多数決アルゴリズムのような手法は、さまざまな産業でまだ十分に活用されていないのではないかと思う
磁気記憶媒体は基本的にこのハックと同じ原理で動いている
https://en.wikipedia.org/wiki/Partial-response_maximum-likelihood
ここにも同じ発想を適用できる。GBA ROMの内容には強い偏りがあるはずだからだ
多数決は多くの情報を無駄にしている
すべての写真を重ね合わせ、各ピクセルごとに中央値だけを残せばよい[1]
[1]: https://patdavid.net/2013/05/noise-removal-in-photos-with-median_6/
ディスクイメージを繰り返し読み取り、クォーラム判定を回し、チェックサムを通る結果が出たら正しく動くか確認する、という具合だ
ファイルシグネチャのようなより高レベルのチェックサムがあれば、追加検証にさらに役立つ
一部の航空宇宙用途でも使われている
pristineなマスターではなく、損傷している可能性のある劇場上映用プリントを扱うため、複数本を使って傷などを除去できるなら、ポストプロダクションでの手作業修正の時間を大幅に減らせるはずだ
0xFFが0x00に変わるのは、直流遮断コンデンサやハイパスフィルタリングが原因かもしれない
オーディオ回路は非可聴コンテンツにはあまり向いていない
運がよければ、チップのオーディオ出力にデジタルオシロスコープを直接つなぐことでキャプチャ精度が上がるかもしれないが、それでも起動可能なイメージを得られたのはかなり印象的だ
だから本格的なデータロギング用オシロスコープの代わりに、何時間もかけて繰り返しキャプチャし、誤差を平均化していたのだ
Manchester符号化のような単純な方式だけでも大きな助けになる
それで十分でなければ、NRZや、場合によっては畳み込み符号化も可能だ
また、正弦波を送るか、それが無理なら少なくとも矩形波の周波数を十分高くして、AC結合コンデンサに食われないようにする必要がある
この件全体で一番印象的なのは、個人的には海賊版業者が、ROM+揮発性保存メモリの代わりに、書き込み可能なフラッシュからゲームを実行するようコードを改変した方法だ
実のところ、こうしたパッチを作るのもそれほど難しくない
海賊版業者はゲームコードが保存する場所を少しリバースエンジニアリングし、ゲームごとのパッチを書いて保存内容を書き込み可能なフラッシュへフラッシュする
ただし公式のGBAゲームは常にNintendo SDKの関数を使って保存するため、その関数をフックすれば、バッテリーなしの複製カートリッジでもどんなGBAゲームでも保存できるようにする汎用パッチはかなり簡単になる
これを行うパッチャーを書いており、ここで見られる
https://github.com/metroid-maniac/gba-auto-batteryless-patcher
TheZZAZZGlitchのエミュレータが、ゲームが不正なアドレスにジャンプしようとしていると報告するとき、内部で何が起きているのか知っている人はいる?
GameBoy Advanceで使われているARM7プロセッサには詳しくないが、不正な値でジャンプ呼び出しを構成することがどう可能なのか、あまり想像がつかない
それと、TheZZAZZGlitchが誤って復元したROMの一つを実機のGameBoyで動かしたらどうなるのかも気になる
bx命令を使えば、レジスタに保存された任意のアドレスへジャンプできるそして誤って復元されたROMを実機のGameBoyで実行するとクラッシュし、最終的にはROMをスピーカーで再生し始めるだろう
それが動画の肝だ :)
実ハードウェアではどうなるか……誰にも分からない :)