flowchart TD A["静的解析で終了処理を特定"] --> B["状態6の入口 $240A で停止"] B --> C["次の状態 $242A には到達しない"] C --> D["区間内の2回のバンク呼び出しを確認"] D --> E["1回目の復帰後に E が C1 へ変化"] E --> F["E=00 に直して再検証"] F --> G["バンク05の $4000 はデータ"] G --> H["バンク06も不一致"] H --> I["セレクター14で有効なコードを確認"] I --> J["2回目を DE=1400 に修正"] J --> K["ヘッダーを維持してパッチを生成"] K --> L["ステージ1から2への遷移に成功"]
ステージ遷移クラッシュの調査と修正
mGBAでの再現、原因の切り分け、ROMパッチ作成まで
ステージ終了時には、別のROMバンクにある処理が2回呼ばれます。2回目の呼び出しに渡す値を DE=1400 に修正したところ、mGBA 0.10.3でステージ1から2へ正常に遷移しました。
また、mGBAがこのROMを正しい特殊カートリッジとして認識できるように、ROMヘッダーは変更せず維持しました。
このレポートについて
Pokémon Jade (Special Pikachu Edition) をmGBA 0.10.3で実行すると、ステージのゴール後に画面が崩れて停止します。このレポートでは、次の順に原因を切り分け、修正版のIPSパッチを作成するまでを説明します。
- ROMを静的に解析し、ステージ終了処理の位置を特定します。
- mGBAのデバッガーで、特定した処理を実際に追跡します。
- 2回目のバンク呼び出しに渡される値を検証します。
- ROM内の空き領域を使って修正処理を追加します。
- mGBAによるROMの識別方法を保ったまま、IPSパッチを生成します。
完成した修正だけでなく、E=00 やバンク 06 を試した結果も残しています。これらは最終的な修正ではありませんが、原因を絞り込むうえで必要な検証でした。
対象と安全上の方針
対象ROM
| 項目 | 値 |
|---|---|
| サイズ | 524,288 bytes(512 KiB) |
| 元ROM SHA-256 | 0E90D7659339A1F52C733BDC2B502D033E1C4C4004F761B6C539B236FA7FDD45 |
| mGBA | 0.10.3 |
| mGBAが使うヘッダーCRC32 | 8628A287 |
| mGBA上のマッパー | GB_UNL_NT_NEW |
SHA-256はファイル全体から計算する識別値です。内容が1 byteでも変わると通常は別の値になるため、同名の別ROMを誤って修正する事故を防げます。
Get-FileHash -Algorithm SHA256 pokemon_jade_pikachu.gb
node tools/verify-analysis.mjs pokemon_jade_pikachu.gbGet-FileHash は、元ROMのSHA-256を計算するPowerShellコマンドです。続く node tools/verify-analysis.mjs は、このレポート用の検証スクリプトを実行します。このスクリプトはハッシュに加えて、解析対象の命令列が期待した位置にあることも確認します。
管理方針
- 元ROMは上書きしません。
- ROM本体、セーブ、ステートはGitに含めません。
- 生成した修正版ROMとIPSパッチはローカル検証専用とし、このリポジトリでは配布しません。
- 修正スクリプトは、対象ROMのハッシュが一致しない場合に処理を中止します。
公開版では、再現条件と表示異常を論評するために不可欠なゲーム画面2枚だけを、出所と引用目的を明示して掲載します。確認動画、セーブステート、抽出素材、広範な逆アセンブルは掲載しません。実行結果はレジスター値、ハッシュ値、状態遷移などの事実情報でも記録します。全体方針は公開方針と権利表示をご覧ください。
最低限の用語
| 用語 | この調査での意味 |
|---|---|
| ROM | ゲームのプログラムとデータが入った読み取り専用イメージです。 |
| バンク | 大きなROMを16 KiB単位に分けた区画です。CPUから見える区画を必要に応じて切り替えます。 |
| マッパー | CPUから見えるROMバンクを切り替える、カートリッジ側の仕組みです。 |
| レジスター | CPU内部の小さな作業領域です。このレポートでは A、D、E などを扱います。 |
DE |
8-bitレジスター D と E を組み合わせた16-bit値です。対象処理では D がバンク選択、E が処理番号として使われます。 |
PC |
Program Counterです。次に実行する命令のアドレスを示します。 |
SP |
Stack Pointerです。関数の戻り先などを記録するスタックの位置を示します。 |
| ブレークポイント | 指定したアドレスを実行する直前に、ゲームを自動で一時停止する設定です。 |
| 不正命令 | CPUが命令として扱えないbyteを実行しようとした状態です。処理が本来のコードから逸れた手掛かりになります。 |
$240A |
16進数で表したアドレスです。デバッガーへの入力では同じ値を 0x240A と書きます。 |
CPUアドレスとROM内の位置
Game BoyのCPUが見るアドレスと、ROMファイル内の位置は常に同じではありません。固定バンク0の $0000-$3FFF は一致しますが、$4000-$7FFF に見える内容は、現在選択されているROMバンクによって変わります。
CPU $0000-$3FFF -> 常に固定バンク0
CPU $4000-$7FFF -> 現在選択されているROMバンク
そのため、同じCPUアドレス $4000 でも、選択中のバンクが 04 と 14 では別のbyteが見えます。
調査の全体像
1. 静的解析で停止位置を決める
最初にROMの命令を読み、ステージ終了時に実行される処理を探しました。ここで処理の位置を先に特定したため、デバッガーで無作為に停止位置を探す必要はありませんでした。
状態番号から処理アドレスを特定する
$2366 から始まる処理は、メモリ C21C の値を状態番号として読み、対応するアドレスへ移動します。分岐先の一覧は $2563 にあります。
2366: FA 1C C2 ld a,[$C21C] ; 現在の状態番号を読む
236D: 21 63 25 ld hl,$2563 ; 分岐先一覧の先頭
2370: C3 1E 38 jp $381E ; 状態番号に対応する処理へ移る
256F: 0A 24 dw $240A ; 状態6
2571: 2A 24 dw $242A ; 状態7この表から、C21C=06 のときは $240A、次の C21C=07 では $242A が実行されると分かります。
$240A と $242A が何をするか
$240A からの状態6は、終了演出中に使われる値 C213 を確認します。この値が0になるまでは演出の更新を続け、0になると C21C を7へ進めます。
240A: FA 13 C2 ld a,[$C213] ; 終了演出中に使われる値を読む
240D: B7 or a
240E: 28 14 jr z,$2424 ; 0なら状態7へ進める
...
2424: 3E 07 ld a,$07
2426: EA 1C C2 ld [$C21C],a
2429: C9 ret続く $242A からの状態7は、論理レベル番号 C27D を1増やし、次の画面遷移を開始します。
242A: FA 7D C2 ld a,[$C27D]
242D: 3C inc a
242E: EA 7D C2 ld [$C27D],a
...
2439: 3E 05 ld a,$05
243B: EA 19 C2 ld [$C219],a ; 次の画面遷移を開始したがって、次の2か所を観測すれば、問題の範囲を切り分けられます。
$240Aに到達する場合、ステージ終了処理の入口までは正常です。- その後に
$242Aへ到達しない場合、状態6の処理$240A-$2429に問題があります。
これが、最初に $240A で停止した理由です。再検証に必要な範囲の命令例はステージ遷移の静的解析と本レポート内にまとめています。
2. $240A で実行を止める
毎回ステージを最初から進めずに済むように、ゴールへ触れる直前でmGBAのステートをローカルに保存しました。図1は、再現起点となる看板とプレイヤーの位置関係を文章だけでは特定しにくいため、その条件を示す目的で引用しています。ステートファイル自体は非公開です。

引用図1:ステージ1のゴール直前
出典:『Pokémon Jade (Special Pikachu Edition)』ゲーム画面。投稿者がmGBA 0.10.3上で取得。看板への接触後に始まる終了処理の再現条件を特定するため、画面全体を掲載しています。対象は非公認作品であり、制作・販売主体および個々の素材の権利帰属は未確認です。
その後、mGBAの ツール → デバッガーコンソールを開く から次のコマンドを入力しました。
break 0x240A
continuebreak 0x240A は、CPUが $240A を実行する直前で停止するブレークポイントを設定します。continue は、停止中のゲームを再開するコマンドです。
ゴールへ触れると、次のように停止しました。
Hit breakpoint 1 at 0x0000240A
PC: 240A SP: C1FD
ROM: 02
00:240A: FA13C2 ld a, [$C213]
これはクラッシュではありません。Hit breakpoint は、設定したブレークポイントへ正常に到達して停止したことを示します。この結果から、ゴールとの接触判定から状態6の入口までは実行できていると分かります。
停止中に状態値も確認しました。
x/1 0xC219 4x/1 0xC219 4 は、アドレス $C219 から1 byte単位で4個の値を表示するコマンドです。
0x0000C219: 00 02 01 06
4 bytesは順に C219=00、C21A=02、C21B=01、C21C=06 を表します。最後の C21C=06 は、静的解析で確認した状態6と一致します。
3. 次の状態へ進む前にクラッシュすることを確認する
次に、状態7の入口 $242A へブレークポイントを置きました。
break 0x242A
continuebreak 0x242A は、状態7の入口 $242A にブレークポイントを設定します。continue で実行を再開し、この位置へ到達するかを確認します。
しかし、$242A のブレークポイントには到達せず、CPU状態が次のように壊れました。
PC: 0000 SP: 0002
HL: 0000
00:0000: E9 jp hl
PC=0000、SP=0002、HL=0000 は通常の関数実行では考えにくい組み合わせです。ここでは、ブレークポイントによる意図的な停止ではなく、処理が本来の経路から逸れたクラッシュだと判断しました。
以上から、調べる範囲を状態6の $240A-$2429 に絞れます。
listb
delete 1listb は登録済みのブレークポイントを一覧表示し、delete 1 は番号1のブレークポイントを削除します。mGBA 0.10.3では disable 1 が認識されないため、不要なブレークポイントは delete で削除します。
4. 状態6にある2回の「別バンク処理」を確認する
問題の範囲 $240A-$2429 を読むと、共通ルーチン $FF90 が2回呼ばれています。
2413: 16 04 ld d,$04
2415: 1E 00 ld e,$00
2417: CD 90 FF call $FF90
241A: 16 05 ld d,$05
241C: CD 90 FF call $FF90ここでの $FF90 は、D で指定されたROMバンクへ切り替え、そのバンクの $4000 にある処理を呼び出すための共通ルーチンです。
- 1回目は
D=04とE=00の両方を設定してから呼びます。 - 2回目は
D=05だけを設定し、Eは設定し直していません。
$FF90 の内容は次のとおりです。
FF90: FA 0A C2 ld a,[$C20A] ; 現在のバンク値を読む
FF93: F5 push af ; 元の値を一時保存する
FF94: 7A ld a,d
FF95: EA 0A C2 ld [$C20A],a
FF98: EA 00 20 ld [$2000],a ; Dで指定されたバンクへ切り替える
FF9B: CD 00 40 call $4000 ; 選択したバンクの処理を呼ぶ
FF9E: F1 pop af
FF9F: EA 0A C2 ld [$C20A],a
FFA2: EA 00 20 ld [$2000],a ; 元のバンクへ戻す
FFA5: C9 ret動作をまとめると、次のようになります。
- 現在選択されているバンク番号を保存します。
Dの値を使って別のROMバンクへ切り替えます。- 切り替えたバンクの
$4000を呼びます。 - 呼び出しが終わったら、元のROMバンクへ戻します。
このルーチンは AF を保存しますが、DE は保存しません。そのため、$4000 から始まる処理が E を変更すると、呼び出し元へ戻った後も変更後の値が残ります。
5. 1回目のバンク呼び出しを追跡する
1回目の呼び出し直前 $2417 で停止し、trace で命令を1つずつ実行しました。
break 0x2417
continue
trace 8
statustrace 8 は次の8命令を1命令ずつ記録しながら実行します。status は、現在のレジスター、実行位置、選択中のROMバンクなどを表示します。break と continue は前述と同じです。
| 段階 | 実行位置 | 起きたこと |
|---|---|---|
| 呼び出し前 | $2417 |
DE=0400 の状態で $FF90 を呼びます。 |
| バンク切替 | $FF94-$FF98 |
D=04 をバンク選択レジスターへ書きます。 |
| バンク内呼び出し | $FF9B |
選択したバンクの $4000 を呼びます。 |
| 呼び出し先 | バンク 04 の $4000 |
E を処理番号として分岐します。今回は E=00 です。 |
| 復帰 | $FF9E |
バンク内処理が終わると、元のバンクを復元します。 |
トレースでも、$FF9B の次にバンク 04 の $4000 へ移ったことを確認できました。
PC: 00:FF9B | call $4000
PC: 04:4000 | ld a,e
ROM: 04
バンク 04 の $4000 は、E の値に応じて処理を選ぶ入口です。今回は E=00 なので、比較が一致して $4406 へ分岐します。
4000: 7B ld a,e
4001: FE 00 cp $00
4003: CA 06 44 jp z,$4406なお、mGBAの status 末尾には、実メモリと異なる命令が表示される場合がありました。そのため、バンク切替付近では次の3つを照合しました。
traceが実行した命令x/1で読み出した生のbytedisassembleで逆アセンブルした結果
6. 1回目の復帰後に E が変わる
バンク内処理から戻った直後の $FF9E で停止しました。
break 0xFF9E
continueここでは、バンク内処理から戻った直後の $FF9E にブレークポイントを設定し、そこへ正常に戻るかを確認します。
1回目は正常に戻りましたが、レジスターは次の値になっていました。
D: 9B E: C1 (DE: 9BC1)
PC: FF9E
ROM: 04
呼び出し前は E=00 でしたが、復帰後は E=C1 です。続くコードは D=05 だけを設定するため、2回目の $FF90 には DE=05C1 が渡されます。
その後、2回目の $FF9E へは戻らず、次の不正命令で停止しました。
Hit illegal opcode at 0x0000CFFB: 0x000000FC
PC: CFFA SP: EFFB
ROM: 08
画面表示も図2のように崩れました。この表示は、単なるブレークポイント停止ではなく、処理がコード領域を外れて表示データも破損したことを視覚的に示します。その点を上記の停止位置とレジスター値と合わせて論評するため、異常発生時の画面を引用しています。

引用図2:不正命令へ逸れた際の表示異常
出典:『Pokémon Jade (Special Pikachu Edition)』ゲーム画面。投稿者がmGBA 0.10.3上で取得。不正命令停止時の表示異常を、停止位置 PC=CFFA および ROM=08 と対応付けて分析するため掲載しています。対象は非公認作品であり、制作・販売主体および個々の素材の権利帰属は未確認です。
この結果から、2回目の呼び出しで E を設定し直していないことが、最初の原因候補になりました。
7. E=00 だけでは直らない
$241C で停止し、2回目の呼び出し前に E=00 を手動で設定しました。
w/r e 0
continuew/r e 0 は、レジスター E に 00 を書き込むコマンドです。書き換え後、continue で処理を再開します。
これで2回目の呼び出しは DE=0500 になります。しかし、今度は別の場所で不正命令が発生しました。
Hit illegal opcode at 0x0000CD1B: 0x000000E3
PC: CD1A SP: 0841
E の値には問題がありましたが、E=00 に直すだけでは正常な処理を呼べません。そこで、もう一方の値であるバンク選択 D=05 も調べました。
8. 05 と 06 の $4000 は目的のコードではない
まず DE=0500 にした状態で、バンク 05 の $4000 を読みました。
x/1 0x4000 64
disassemble 0x4000 40x/1 0x4000 64 は、現在CPUから見えている $4000 から64 bytesを表示します。disassemble 0x4000 40 は、同じ位置から40命令を逆アセンブルして、人が読めるCPU命令として表示します。
先頭のbyte列は次のとおりです。
10 60 90 65 8E 6D C4 75 5F 7C 5F 7C ...
これをCPU命令として解釈すると、関数の入口としては不自然な並びになります。
4000: 10 stop
4001: 60 ld h,b
4002: 90 sub b
4003: 65 ld h,lどのようなbyte列でも、逆アセンブラーは何らかの命令として表示します。しかし、この並びには通常の関数入口や一貫した分岐が見られません。画像などのデータを命令として読んでいる可能性が高く、目的のコードではないと判断しました。
単純な1バンクずれも考え、セレクターを 06 に変更しました。
w/1 0xC20A 6
w/1 0x2000 6
x/1 0x4000 16w/1 ADDRESS VALUE は、指定アドレスへ1 byteを書き込むコマンドです。ここでは管理用のバンク値 $C20A と、マッパーが受け取るバンク選択アドレス $2000 の両方へ 06 を書きます。最後に x/1 で、切り替え後の $4000 を確認します。
06 でも $4000 には同じデータが見えました。そのため、単純に次の番号を選ぶ方法では解決しません。
9. セレクター 14 で目的の処理を確認する
なぜ通常のバンク番号として考えられないか
ROMヘッダー上のカートリッジ種別はMBC1ですが、mGBA 0.10.3はこのROMをヘッダーCRC32 8628A287 で個別に識別し、非公認カートリッジ用の GB_UNL_NT_NEW マッパーを適用します。
つまり、この調査では一般的なMBC1のバンク番号を前提にせず、mGBA上で各セレクターを書き込んだときに $4000 へ実際に何が見えるかを確認する必要があります。mGBAがNT-newマッパーを選ぶ登録値は、公式ソースの src/gb/overrides.c で確認できます。
この説明は実機カートリッジの電気的な挙動を断定するものではありません。ここではmGBA 0.10.3上で観測した挙動を対象にしています。
14 の $4000 は処理の入口になっている
セレクター 14 と処理番号 00 を設定し、$4000 を確認しました。
w/1 0xC20A 0x14
w/1 0x2000 0x14
x/1 0x4000 16
disassemble 0x4000 12前節と同じ書き込みコマンドを使い、今度はセレクター 14 を指定します。その後、切り替え先のbyte列と逆アセンブル結果を確認します。
今度は、E を使ってジャンプ先を選ぶ一貫した処理が現れました。
4000: 16 00 ld d,$00
4002: 21 D0 59 ld hl,$59D0
4005: 19 add hl,de
4006: 2A ld a,[hl+]
4007: 66 ld h,[hl]
4008: 6F ld l,a
4009: E9 jp hlこの処理は、$59D0 付近のジャンプ表から E に対応するアドレスを読み、その処理へ移ります。E=00 を渡すと表の先頭項目が選ばれます。
さらに、バンク内処理から戻った直後の $FF9E に正常に到達しました。
Hit breakpoint 2 at 0x0000FF9E
D: 00 E: 00 (DE: 0000)
PC: FF9E
ROM: 14
以上の結果から、mGBA 0.10.3では2回目の $FF90 を次の値で呼ぶ必要があると判断しました。
D=14 : セレクター14を選ぶ
E=00 : ジャンプ表の先頭処理を選ぶ
DE=1400
10. 5 bytesの場所から6 bytesの処理を呼ぶ
必要な命令は、合計6 bytesです。
ld de,$1400 ; 3 bytes
call $FF90 ; 3 bytesしかし、置換できる元の領域 $241A-$241E は5 bytesしかありません。
241A: 16 05 ld d,$05 ; 2 bytes
241C: CD 90 FF call $FF90 ; 3 bytesそこで、固定バンク内の未使用領域に小さな補助ルーチンを置き、元の場所から呼ぶ方法を採用しました。このように既存プログラムの空き領域へ追加処理を置く手法を「コードケーブ」と呼びます。
静的検索により、$2799-$37FF が FF で埋まった未使用領域であることを確認しました。その末尾7 bytesへ次の処理を配置します。
37F9: 11 00 14 ld de,$1400
37FC: CD 90 FF call $FF90
37FF: C9 ret元の5 bytesは、補助ルーチンの呼び出しと2つの NOP に置き換えます。
241A: CD F9 37 call $37F9
241D: 00 nop
241E: 00 nopNOP は何もせずに次へ進む1-byte命令です。これにより、後続のアドレスをずらさず、$241F からの処理もそのまま残せます。
| ファイル位置 | 変更前 | 変更後 | 目的 |
|---|---|---|---|
0x0241A |
16 05 CD 90 FF |
CD F9 37 00 00 |
補助ルーチンを呼びます。 |
0x037F9 |
FF FF FF FF FF FF FF |
11 00 14 CD 90 FF C9 |
DE=1400 で $FF90 を呼びます。 |
11. 初版の修正版ROMが開かなかった理由
最初に作成した修正版では、ROM全体のグローバルチェックサムも変更後の内容に合わせて再計算しました。しかし、このROMはmGBAで正常に開けませんでした。
原因は、mGBAによる特殊マッパーの自動判定です。mGBAはROMヘッダー $0100-$014F のCRC32を計算し、値が 8628A287 の場合にNT-newマッパーを適用します。一方、グローバルチェックサムの保存位置 $014E-$014F も、このCRC32の計算範囲に含まれます。
グローバルチェックサムを更新する
↓
ヘッダーCRC32が変わる
↓
mGBAの個別登録と一致しなくなる
↓
NT-newマッパーが選ばれない
↓
ROMを正常に実行できない
mGBAがヘッダーCRC32を使って個別設定を探す処理は、0.10.3公式ソースの GBOverrideApplyDefaults で確認できます。
最終版では、グローバルチェックサムを含む $0100-$014F を元ROMのまま維持しました。そのため、ROM内に記録されたグローバルチェックサムはコード変更後の内容とは一致しません。一方、ヘッダーCRC32は 8628A287 のままなので、mGBAは修正版にもNT-newマッパーを適用できます。
12. 再現可能なパッチを生成する
修正版ROMとIPSパッチは、tools/build-stage-transition-patch.mjs で生成します。
node tools/build-stage-transition-patch.mjs `
pokemon_jade_pikachu.gb `
build/pokemon_jade_pikachu_stage_transition_fixed_mgba.gb `
build/pokemon_jade_pikachu_stage_transition_fix_mgba.ipsnode tools/build-stage-transition-patch.mjs は、パッチ生成スクリプトを実行するコマンドです。後ろの3つの引数で、元ROM、生成する修正版ROM、生成するIPSパッチの順にファイルを指定します。行末のバッククォート ` は、PowerShellでコマンドを次の行へ続ける記号です。
スクリプトは、書き込み前後に次の項目を検査します。
- 元ROMが512 KiBであることを確認します。
- SHA-256が解析対象と一致することを確認します。
$241Aの置換元命令が期待値どおりであることを確認します。$37F9-$37FFが未使用のFF7 bytesであることを確認します。- ヘッダーCRC32が
8628A287であることを確認します。 - 修正後もヘッダー
$0100-$014Fが元ROMと同一であることを確認します。 - 生成したIPSを元ROMへ適用すると、修正版ROMとbyte単位で一致することを確認します。
成功時には次のような要約が表示されます。
PASS source SHA-256 0E90D765...
PASS preserved header CRC32 8628A287
PASS source bytes at $241A and $37F9
PASS IPS round-trip reproduces patched ROM
IPSパッチとは
IPSは、元ROMと修正版ROMの差分だけを記録するパッチ形式です。今回のIPSにはROM本体は含まれず、変更した12 bytes分のレコードと、IPS形式の管理情報だけが入ります。
この調査では、適法に利用できる元ROMへローカルでIPSを適用し、修正版を検証しました。生成したROMとIPSファイルは公開・配布せず、修正内容は位置、長さ、byte列および生成スクリプトとして記録します。
13. 最終検証
静的検証
生成後のROMを逆アセンブルし、次の命令になっていることを確認しました。
241A: CD F9 37 call $37F9
241D: 00 nop
241E: 00 nop
37F9: 11 00 14 ld de,$1400
37FC: CD 90 FF call $FF90
37FF: C9 ret成果物の識別値は次のとおりです。
| 成果物 | SHA-256 |
|---|---|
| 元ROM | 0E90D7659339A1F52C733BDC2B502D033E1C4C4004F761B6C539B236FA7FDD45 |
| mGBA互換修正版ROM | 7463D30924B2827CFB824F986A5571B079B7F4952E1912023C5705B91E39CFCA |
動的検証
mGBAを完全に閉じ、ヘッダーを維持した最終版ROMを開き直しました。ゴール直前から進めた結果、画面が崩れず、ステージ1から2へ遷移しました。
確認時には13秒の画面記録を作成しましたが、公開版には含めていません。公開レポートでは、修正版ROMのSHA-256、到達した状態、使用したエミュレーターの版を検証記録とします。
| 検証項目 | 結果 |
|---|---|
| エミュレーター | mGBA 0.10.3 |
| 修正版ROM SHA-256 | 7463D30924B2827CFB824F986A5571B079B7F4952E1912023C5705B91E39CFCA |
| 開始条件 | ステージ1のゴール直前 |
| 観測結果 | 画面が崩れず、ステージ2へ遷移 |
14. 調査結果から分かること
ブレークポイントによる停止とクラッシュは異なります
Hit breakpoint と表示された場合は、指定位置に正常に到達した結果です。一方、今回の PC=0000 や Hit illegal opcode は、処理が本来の経路から逸れたことを示します。停止時の表示、PC、現在命令を合わせて判断する必要があります。
E とバンク選択の両方に問題がありました
1回目の呼び出し後に E が変化し、2回目には E=C1 が渡されていました。ただし、E=00 に直すだけではクラッシュを解消できませんでした。バンク選択も 05 から 14 へ変更し、DE=1400 とする必要がありました。
逆アセンブルできることと、コードであることは同じではありません
バンク 05 の $4000 も命令として表示できましたが、実際には関数として不自然な並びでした。関数入口、分岐、戻り先などに一貫性があるかを確認する必要があります。
ROMヘッダーはmGBAのマッパー選択にも使われます
コード部分だけを正しく修正しても、ヘッダーCRC32が変わると、mGBAが別のカートリッジとして扱う場合があります。今回の修正版では、mGBAによるNT-newマッパーの選択を維持するためにヘッダーを変更していません。
15. 今後の確認候補
今回確認したのは、ステージ1から2への遷移です。追加で次の項目を確認できます。
- ステージ2以降の各ゴールでも、同じ終了処理が正常に動くか確認します。
- 最終ステージの別分岐
C219=08に影響がないか確認します。 - 通常セーブから起動した場合と、旧ROMのステートを読み込んだ場合の差を確認します。
- mGBAの新しい版やhhugboyでの互換性を確認します。
- IPS適用ツールを変えても、同じSHA-256の修正版が得られるか確認します。
付録A: mGBAデバッガーコマンド早見表
| コマンド | 説明 | 例 |
|---|---|---|
break ADDRESS |
実行ブレークポイントを追加します。 | break 0x241C |
continue |
実行を再開します。 | continue |
status |
CPUとバンクの状態を表示します。 | status |
trace N |
N命令を記録しながら実行します。 | trace 8 |
disassemble ADDRESS N |
指定位置からN命令を逆アセンブルします。 | disassemble 0xFF90 20 |
r/1 ADDRESS |
指定位置から1 byteを読みます。 | r/1 0xC213 |
x/1 ADDRESS N |
指定位置から1 byte単位でN個表示します。 | x/1 0x4000 16 |
w/1 ADDRESS VALUE |
メモリへ1 byteを書き込みます。 | w/1 0x2000 0x14 |
w/r REGISTER VALUE |
レジスターの値を書き換えます。 | w/r e 0 |
listb |
ブレークポイントの一覧を表示します。 | listb |
delete ID |
指定番号のブレークポイントを削除します。 | delete 1 |
デバッガーコマンドはmGBAの版によって異なります。0.10.3では disable を使用できなかったため、delete を使用しました。
付録B: 関連ファイル
変更履歴
| 日付 | 内容 |
|---|---|
| 2026-08-13 | 初版を作成しました。 |
| 2026-08-13 | 敬体への統一、静的解析の前置、バンク呼び出しとNT-newマッパーの説明を改善しました。 |