世界を動かす技術を、日本語で。

ドンキーコング カントリー2とオープンバス

2025年7月1日原文(jsgroth.dev)

概要

  • Donkey Kong Country 2 の特定ステージで、 ZSNESエミュレータ のバグにより回転バレルが正常に動作しない現象
  • バグの原因は open bus挙動の未実装 によるもの
  • 実機や他エミュレータ(Snes9x)では正しく動作し、 バレルの停止判定 にopen busの値を利用
  • 65816 CPU のアドレッシングやメモリバンク構造の説明
  • ゲーム内部の処理やアセンブリコード解析を通じたバグの詳細解説

Donkey Kong Country 2とZSNESのバレル回転バグ

  • Donkey Kong Country 2の一部ステージ(例: Barrel Bayou)で回転バレルの操作が異常になる現象
    • 本来:プレイヤーが 左/右ボタンを押している間だけ回転
    • ZSNESでは:一度押すと 永遠に回転 し続け、逆方向を押すと逆回転が止まらない
  • このバグは 難易度を大幅に上昇 させ、本来のゲーム性を損なうもの

原因:Open Bus挙動の未実装

  • SNES実機では 未マッピングアドレス へのアクセスで「open bus」挙動が発生
    • 直前にバスに載った値が再読込される仕様
  • ゲームは バレル停止判定 にこのopen bus値を利用
  • ZSNESではopen bus挙動が未実装のため、 常に0が返り、判定が正常に働かない
  • Snes9x等の他エミュレータでは バグが修正済み

65816 CPUとメモリアドレッシング

  • SNESのCPUは 65C816(65816)
    • 24ビットアドレスバス、8ビットバンク+16ビットオフセットで管理
    • プログラムバンク(PBR)データバンク(DBR) の概念
  • メモリバンク$B3の$2000/$2001アドレスは 未マッピング でopen busとなる
  • 16ビットアクセス時は 2回の8ビットリード を連続実行
  • ゲームコードはこの仕様を想定して記述

ゲーム内部処理の解析

  • バレル回転処理のアセンブリコード例
    • バレルの向き回転量一時変数 の管理
    • 回転停止判定は XOR+AND演算 で、open bus値(本来は0x2020)を利用
  • ZSNESではopen bus値が0となるため、 停止判定が常に失敗 し回転し続ける
  • 実機やSnes9xでは 0x2020 が返ることで、バレルが正しい方向で停止

まとめ

  • Donkey Kong Country 2の 回転バレルバグ はZSNESの open bus未対応 が原因
  • ゲームはopen bus挙動を前提に判定を組んでおり、エミュレータの再現性が重要
  • ZSNESは開発終了 (最終リリース2007年)、今後の修正は見込めない
  • バグ回避には 他エミュレータの利用 推奨

Hackerたちの意見

こういうの大好き!アセンブリコードの理解度が60%くらいしかないから、文章での説明がめっちゃ助かる。あと、「誰も理解してなかったバグ」みたいな話を聞くのも楽しいよね!

この時代のシステムで好きなところの一つは、今ではほとんどの組み込みシステムで当たり前とされている現代のチェックがなかったことだね(ネットワークに接続できるものには必要で、完全に孤立した組み込みアーキテクチャでも安価だから含まれている)。オリジナルのNESでは、たくさんの読み書きがどこかのラインの電圧を切り替えるだけで、その後何が起こるかはそのままだった。CRTのブランキング間隔の動作を示す信号と同期して、非常に制御された方法でその電圧を切り替えることで、欲しい効果が得られたんだ。スーパーマリオブラザーズ3の一部のアニメーションでは、複数のスプライトデータバンクから選択するためにRAMのマルチプレクサを切り替えて、グラフィックハードウェアがスプライトを引っ張るときに、見た目が少し異なる全く別のチップから引っ張るようになってた。テレビのタイミングが重要だったから、NTSCとPALのテレビがある地域では、異なるリフレッシュレートで動作していたので、異なるソフトウェアをリリースする必要があったんだ。あの時代は本当にワイルドだった。

6502アセンブリプログラマーとして言わせてもらうと、同じ問題を追いかけるのに無駄に何時間も費やしたことがあるよ。即値の前に#を付けるのを忘れて、メモリアクセスしちゃうってやつね。こういうケースって、時々はうまく動くこともあるから厄介なんだよね。この例よりも厄介なのは、初期化されていないRAMに依存する場合。DRAMによって一貫性があるから、自分のマシンやエミュレーターでは動くけど、違うDRAMチップのマシンでは動かないことが多い。デモパーティーでこれにハマると、パーティーのマシンで動かなくて、デモを発表する前に15分しか修正時間がないっていうのが定番だよね。

6502 CPUを使った動的メモリを持つアーキテクチャってあったのかな?私の(限られた?)経験では、そのプラットフォームは常に静的RAMだった。

6502は私の最初のアセンブリ言語で、「LDA #2」みたいな命令は「Aに2をロードする」って考えてたけど、LDA 2は(メモリの位置2にあるものを)Aにロードするって感じだった。

こういう状況では、コードをLLMに通すのが実際に役立つことがあるんだよね。こういう深刻な影響を与えるエラーやタイプミスを見つけるのが得意だから、目がスルーしがちなところを見逃さないんだ。

OTだけど、30年も経ったゲームなのに、DKカントリー2が今でもこんなに楽しめるのはすごいと思う。エミュレーターでプレイしてるけど、グラフィック、音、レベルデザイン、操作性が全て素晴らしい。子供たちはフォートナイトを楽しんでるけど、俺はいつでもDKCとクロノトリガーを選ぶよ!

クロノトリガーは今でも素晴らしい。あのゲームは名作だね。

あの時代のRareが開発したゲームは、ほとんどが本当に良くできてたよね。

3年前に初めてオリジナルのDKCトリロジーをプレイしたけど、1と2はあんまり好きじゃなかったな。操作がすごく「フローティー」に感じて、難易度も調整がイマイチだった。DKC 2の鳥のステージは特にイライラさせられた、どれか分かるでしょ。だけど、DKC 3はすごく楽しめた。どうやらハードコアなDKCファンにはあんまり人気がないみたいだけど、それはそれで。

エミュレーションでゲームをプレイしてて詰まると、「これエミュレーターのバグかな?」って思うことがある。この特定の問題は、ゲームがこういう風に設計されてるんだと思ってた。あんまり関係ないけど、ゲームがすごく難しいと「これはエミュレーションの遅延のせい?」って感じることもある。これについては深く調べて、ミスターFPGAを作ったよ!

クロノトリガーにもこんなのあった気がする。ネズミを捕まえるセクションがあって、その後に4つのキーを同時に入力しなきゃいけなかったんだ。でもUSB入力は一度に3つしか送れないから、4つを一気に押して、短い時間内に登録されるのを待つしかなかった。何度も挑戦して、すごくイライラしたよ。

Hacker Newsで議論の続きを見る