DQ2 (FC) パルプンテバグ (とてつもなく恐ろしいもの) の調査 (暫定)
FC版DQ2は、パルプンテで「とてつもなく恐ろしいもの」を呼び出した際に不具合が起こりうることが知られている。 知らない人は以下の資料などを参照 (YouTube にもいくつか動画がある):
少し調べたところ、これは配列外書き込みによりスタックが壊れるのが原因と思われる。以下に現時点で判明していることを記す (バグの挙動確認は Mesen 上で行った)。
敵が消滅する際、配列 $0162-
に 0xFF が代入されるが、このときインデックスとして $9F
の値が使われる (該当コード: PRG 4 $9C39
)。
しかし、「とてつもなく恐ろしいもの」の場合 $9F
が更新されない (特定の敵でなく「まもののむれは にげだした」になるためと思われる)。
この場合、$9F
は 2 * (正規乱数)
の乗算における一時変数の値となる (味方が気絶する際に謎の「ダメージ計算」が行われるため。該当コード: PRG 4 $AE31
)。
乗算の左辺が 2 である関係上、結果の $9F
の値は 4 の倍数となる。よって、アドレス 0x0162 + 4*n
に 0xFF が上書きされる、という動作になる。
通常エンカウントの場合、この対象アドレスが $01FE
または $01FA
になると使用中のスタック領域が壊れ、以下のような挙動を示す:
$01FE
: ルプガナイベントが始まる (最も有名なパターンと思われる)。$01FA
: なぜか戦闘が継続する。
対象アドレスがこれより低位だと未使用スタック領域、高位だとスプライトバッファとなるため破壊しても挙動はおそらく変化しない。
しかし、スタックの使用状況を変化させると破壊時の挙動も変化する。
たとえば、ローレシア城地下の地獄の使い戦ではスタック使用量が少し多くなり、$01FE
, $01FA
の他に $01F6
, $01F2
も使用中となる。スタック破壊時の挙動も以下のように変化する:
$01FE
: バンク切り替えが壊れ、CPU が暴走後クラッシュする。$01FA
: 戦闘から抜けた後さらにハーゴン戦が始まる。勝っても何もなし (イベント処理を経由していないため、シドー戦発生フラグが立たない ($44
の値が 0x64 のとき特定座標でシドー戦が発生))。$01F6
: 少しラグが発生した後、普通に戦闘から抜ける (詳細未確認)。$01F2
: プログラムカウンタが$2FC9
へ飛び、CPU が暴走/クラッシュする (PPU レジスタ領域を実行するため、ほぼ制御不能と思われる)。
今のところスピードラン的に有用そうな現象は見つかっていないが、セットアップ次第では何かしらの新展開に繋がるかもしれない。
なお、破壊対象となるアドレスは「パルプンテのエフェクトが出た直後」の乱数状態 (CRC) $32-$33
の値により決まる。
いくつかのスタック上アドレスについて破壊するための CRC 値の一例を記しておく (PT 3 人生存時。リトルエンディアンであることに注意: たとえば値が 0x1234 なら $32-$33
を [0x34, 0x12]
とする):
$01FE
: 0x008B$01FA
: 0x007B$01F6
: 0x0036$01F2
: 0x0023$01EE
: 0x0047
ただし、デバッガで試す場合は PRG 4 $9C39
にブレークを仕掛けて $9F
の値を直接書き換える方が簡単である。
2024-06-22 追記:
ローレシア地下の地獄の使い戦でパルプンテバグにより $01FE
を破壊した場合、実行されるコードをプレイヤーがある程度制御できるが、任意コード実行まで繋がるかどうかは疑わしい。一応この場合の処理の概略を示す:
- スタックポインタが 0xFF の状態で
rts
命令が実行される。よって、戻りアドレスは$0100-$0101
から取得される。 - このとき取得される戻りアドレスは 0x0502 で固定と思われる (
$0100-
はパルプンテを使う際にムーンの呪文リストで上書きされるため)。 よって、rts
命令によりプログラムカウンタは$0503
へ飛ぶ。 - ここで
$0500-$0519
は現在のパレットデータなので、実行されるコード自体は固定である。 ただし分岐命令の行き先が RAM および PPU レジスタの値に依存している。Mesen 上では$051D
へ飛ぶケースを確認している。 - ここで、
$051A-$0529
は開けた宝箱の座標の配列、$052A-$0539
は開けた扉の座標の配列である。 よって、$051D
以降で実行されるコードはプレイヤーがある程度制御可能である。
というわけで、$0503
以降に実行される分岐命令の行き先を制御した上で、宝箱座標や扉座標を使ってうまく 6502 コードを組み立てられるなら…
と言いたいところだが、宝箱座標や扉座標はフィールドに出るとクリアされてしまうため、書けるコードは著しく制限される。よってエンディング呼び出しや任意コード実行に繋がるかどうかは疑問。
一応 $053A-
がPTキャラ/NPCのシンボル構造体リストになっているが、これも制御の余地は少ない。
ただ、任意コード実行はできなくても、$D0
の bit7 が 1 ならばシドー撃破済と判定されるので、その方向で攻める手はあるかもしれない。
2024-06-26 追記:
ローレシア地下の地獄の使い戦で $01FE
を破壊した場合、$0503
へ飛んだ後 $0540
の kil 命令 (0x02) を実行してクラッシュするようだが、この 0x02 は主人公シンボルの向き(南)なので、他の固定戦闘を使えば回避できるかもしれない(例: 海底洞窟の地獄の使い*2戦)。
その場合、主人公/NPCシンボル領域 $053A-$05F1
を頑張って調整し、プログラムカウンタをインベントリ領域 $0600-
などに飛ばせれば任意コード実行に繋がるかもしれない。できるとしても調整がかなり面倒そうではあるが…。