flowchart LR A["正しい元ROM"] --> C["IPSを適用"] B["変更箇所だけを記録したIPS"] --> C C --> D["修正版ROM"]
IPSパッチはどのように作ったのか
ローカル検証用に12 bytesの修正を再現可能な形で記録する方法
今回のIPSパッチは、外部の差分作成ソフトに2つのROMを渡して作ったものではありません。専用のNode.jsスクリプトで、変更する位置と新しいbyteを明示し、IPS形式の30 bytesのファイルを直接組み立てました。
パッチに記録されている実際の変更は2か所、合計12 bytesです。生成後には、IPSを元ROMへ適用し直し、先に作った修正版ROMと1 byteも違わないことをスクリプトで確認しています。
IPSパッチとは
IPSパッチは、あるファイルの「どの位置を、どの内容へ変更するか」を記録した小さなファイルです。ゲームのROM全体をもう一度収めるものではありません。
たとえば、長い本に対する次のような訂正表を考えると分かりやすくなります。
24ページの10文字目から、5文字を書き換える
37ページの後半にある7文字を書き換える
IPSも考え方は同じです。「ファイル内の位置」「書き換える長さ」「新しい内容」を1組の記録として持ちます。この記録を元ROMへ適用すると、変更箇所だけが置き換わり、修正版ROMができます。
IPSには元ROMの大部分が入っていません。ただし、差分形式であることだけで公開・配布の適法性が保証されるわけではありません。このリポジトリでは、ROM本体、修正版ROM、生成したIPSを管理・配布しません。再検証は、適用される法令と利用条件を確認し、自身が適法に利用できる元ROMを使ってローカルで行ってください。
なぜIPSを使用したのか
今回IPSを選んだ主な理由は、次の3点です。
- 元ROMを上書きしないため 修正版は別ファイルとして作るので、元ROMをそのまま残せます。
- 修正内容を小さなファイルで表せるため 512 KiBのROM全体ではなく、変更した12 bytesと管理情報だけを記録できます。
- 変更範囲を明確に検証できるため IPSの記録を調べれば、意図した位置以外を変更していないことを確認できます。
今回はROMのどこを変更したのか
ステージ終了処理には、もともと5 bytesの命令がありました。
241A: 16 05 ld d,$05
241C: CD 90 FF call $FF90調査の結果、2回目のバンク呼び出しには DE=1400 を渡す必要があると分かりました。しかし、必要な処理は7 bytesになり、元の5 bytesの場所には収まりません。
そこで、元の場所からROM内の未使用領域 $37F9 を呼び出し、そちらで必要な処理を行ってから戻るようにしました。このような中継用の短い処理を、このレポートでは「中継処理」と呼びます。
変更1: 元の5 bytesを中継処理の呼び出しへ置き換える
ROMファイル内の位置 $241A に、次の5 bytesを書き込みます。
241A: CD F9 37 call $37F9
241D: 00 nop
241E: 00 nopcall $37F9 は中継処理へ移動する命令です。残りの2 bytesは、何もしない nop で埋めます。
変更2: 未使用領域へ7 bytesの中継処理を置く
ROMファイル内の位置 $37F9 に、次の7 bytesを書き込みます。
37F9: 11 00 14 ld de,$1400
37FC: CD 90 FF call $FF90
37FF: C9 retこの処理は、DE を必要な値 $1400 に設定してから元の呼び出し先 $FF90 を実行し、最後に ret で呼び出し元へ戻ります。
| 記録 | 書き込み位置 | 長さ | 新しいbyte |
|---|---|---|---|
| 1 | $241A |
5 bytes | CD F9 37 00 00 |
| 2 | $37F9 |
7 bytes | 11 00 14 CD 90 FF C9 |
| 合計 | 2か所 | 12 bytes | — |
どちらも固定バンク0の $0000-$3FFF 内にあるため、このROMではCPUアドレスとROMファイル内の位置が一致します。
IPSファイルの中身
今回生成するIPSは30 bytesです。全体を16進数で表すと、次のようになります。
50 41 54 43 48
00 24 1A 00 05 CD F9 37 00 00
00 37 F9 00 07 11 00 14 CD 90 FF C9
45 4F 46
これを役割ごとに分けると、次の構造になります。
| 部分 | 16進数 | 意味 |
|---|---|---|
| ヘッダー | 50 41 54 43 48 |
ASCII文字の PATCH |
| 記録1の位置 | 00 24 1A |
$241A |
| 記録1の長さ | 00 05 |
5 bytes |
| 記録1の内容 | CD F9 37 00 00 |
中継処理を呼び出す命令 |
| 記録2の位置 | 00 37 F9 |
$37F9 |
| 記録2の長さ | 00 07 |
7 bytes |
| 記録2の内容 | 11 00 14 CD 90 FF C9 |
DE=1400 で $FF90 を呼ぶ処理 |
| 終端 | 45 4F 46 |
ASCII文字の EOF |
位置は3 bytes、長さは2 bytesで、大きい桁から順に記録されます。たとえば 00 24 1A は16進数の $241A、00 05 は5 bytesを意味します。
ファイルサイズが12 bytesではなく30 bytesなのは、変更内容の12 bytesに加えて、PATCH、2件分の位置と長さ、EOF が必要だからです。
5 bytes : PATCH
10 bytes : 記録1(位置3 + 長さ2 + 内容5)
12 bytes : 記録2(位置3 + 長さ2 + 内容7)
3 bytes : EOF
----------------
30 bytes : 合計
今回のIPSでは、同じbyteの繰り返しを短く表すRLE機能は使用していません。
専用スクリプトで生成する理由
一般的なIPS作成ソフトでは、元ROMと修正版ROMを比較して差分を見つける方法がよく使われます。今回のbuild-stage-transition-patch.mjsは、それとは少し異なります。
スクリプト内に、変更位置と新しいbyteを明示しています。
const ipsRecords = [
{ offset: CALL_SITE, data: callPatch },
{ offset: TRAMPOLINE, data: trampolinePatch },
];この方法には、「意図した2か所以外を誤ってIPSへ含めにくい」「同じ入力から毎回同じ成果物を作れる」という利点があります。また、パッチを作るだけでなく、入力と出力の検査も同じスクリプトで行えます。
生成手順
1. 必要なものを用意する
- Node.js
- このリポジトリ
- 正しい元ROM
対象となる元ROMは次のものです。
| 項目 | 値 |
|---|---|
| ファイルサイズ | 524,288 bytes(512 KiB) |
| SHA-256 | 0E90D7659339A1F52C733BDC2B502D033E1C4C4004F761B6C539B236FA7FDD45 |
ヘッダー $0100-$014F のCRC32 |
8628A287 |
SHA-256はファイル全体から計算する識別値です。内容が1 byteでも違えば、通常は別の値になります。同じ名前のROMであっても、SHA-256が異なる場合は今回のパッチの対象ではありません。
2. スクリプトを実行する
リポジトリの先頭のフォルダーで、PowerShellから次のコマンドを実行します。
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.ips3つのファイル名は、順番に次の意味を持ちます。
- 入力する元ROM
- 出力する修正版ROM
- 出力するIPSパッチ
行末のバッククォート ` は、PowerShellで1つのコマンドを複数行に分ける記号です。1行で入力しても構いません。
3. 成功表示を確認する
正常に生成できると、次のような表示が出ます。
PASS source SHA-256 0E90D765...
PASS preserved header CRC32 8628A287
PASS source bytes at $241A and $37F9
PASS IPS round-trip reproduces patched ROM
OUTPUT ROM build/pokemon_jade_pikachu_stage_transition_fixed_mgba.gb
OUTPUT SHA-256 7463D309...
OUTPUT IPS build/pokemon_jade_pikachu_stage_transition_fix_mgba.ips
PASS は、その検査に合格したことを示します。途中で条件に合わないものが見つかった場合、スクリプトは FAIL を表示して終了し、成果物を書き出しません。
スクリプトが行う安全確認
スクリプトは、単にbyteを書き換えるだけではありません。次の順に検査します。
- 元ROMの大きさが512 KiBであることを確認します。
- 元ROMのSHA-256が解析対象と一致することを確認します。
- ヘッダーCRC32がmGBAの期待する
8628A287であることを確認します。 $241Aに置換前の5 bytesがあることを確認します。$37F9-$37FFが使用予定どおり7 bytesのFFであることを確認します。- 元ROMをメモリ上で複製し、その複製だけを書き換えます。
- 書き換え後の2か所が期待したbyteになったことを確認します。
- ROMヘッダー
$0100-$014Fが元ROMから変化していないことを確認します。 - 2件の変更記録からIPSを組み立てます。
- そのIPSを元ROMへ適用し直し、修正版ROMとbyte単位で一致することを確認します。
- すべてに合格した後で、修正版ROMとIPSを書き出します。
「適用し直して一致を確認する」とは
この確認は、生成処理の中でも特に重要です。
flowchart TD
A["元ROMを複製して12 bytesを直接修正"] --> B["修正版ROMの内容"]
C["同じ2件の記録からIPSを生成"] --> D["IPSを元ROMへ適用"]
D --> E["IPSから復元した内容"]
B --> F{"全bytesが一致するか"}
E --> F
F -->|一致| G["成果物を書き出す"]
F -->|不一致| H["FAILで中止"]
修正版ROMを作る処理とIPSを適用する処理は別々です。その2つの結果が完全に一致すれば、IPSに位置、長さ、内容を書き込む際の間違いを検出できます。この往復確認を「ラウンドトリップ検証」と呼びます。
ROMヘッダーを変更しない理由
通常、ROMを書き換えた後には、ROM内のグローバルチェックサムも更新したくなります。しかし今回のROMでは、mGBAがヘッダー $0100-$014F のCRC32を使って特殊なNT-newマッパーを選択します。グローバルチェックサムの保存位置 $014E-$014F も、その範囲に含まれます。
グローバルチェックサムを書き換える
↓
ヘッダーCRC32が変わる
↓
mGBAが対象ROMとして認識できなくなる
↓
必要なNT-newマッパーが選択されない
そのため、このパッチではヘッダーを元ROMのまま維持します。スクリプトも、修正後にヘッダーが1 byteも変化していないことを確認します。
IPSを利用するときの注意
以下はローカルでの再現確認を目的とした手順です。生成物の第三者への配布を推奨または許諾するものではありません。
IPS形式そのものには、「どの元ROM専用か」をSHA-256で記録する仕組みがありません。一般的なIPS適用ソフトは、異なるROMを選んでも処理を続ける可能性があります。そのため、適用前に元ROMのSHA-256を確認することが大切です。
PowerShellでは、次のコマンドで確認できます。
Get-FileHash -Algorithm SHA256 pokemon_jade_pikachu.gb表示された値が次と完全に一致することを確認します。
0E90D7659339A1F52C733BDC2B502D033E1C4C4004F761B6C539B236FA7FDD45
IPS対応ソフトで適用する場合も、元ROMを直接上書きせず、修正版を別のファイル名で保存してください。適用後の修正版ROMは、次のSHA-256になります。
7463D30924B2827CFB824F986A5571B079B7F4952E1912023C5705B91E39CFCA
生成される30 bytesのIPS自体のSHA-256は次のとおりです。
850EC96666011C792D62C4F587D0CDBAC2DCA87A3294A46D2651486D6BEF2584
何が確認済みで、何が未確認か
| 項目 | 状態 | 内容 |
|---|---|---|
| IPSの構造 | 確認済み | 2件、合計12 bytesの変更を持つ30 bytesのIPS |
| 元ROMの識別 | 確認済み | サイズ、SHA-256、ヘッダーCRC32を検査 |
| IPSのラウンドトリップ | 確認済み | 適用結果が修正版ROMとbyte単位で一致 |
| mGBAでのステージ1から2への遷移 | 確認済み | 画面が崩れず次のステージへ遷移 |
| 別のIPS適用ソフトでの再現 | 未確認 | 同じ修正版SHA-256になるか追加確認可能 |
| ステージ2以降 | 未確認 | 各ステージ終了時の動作確認が必要 |
関連資料
- ステージ遷移クラッシュの調査と修正 — 修正内容へ至った調査の全記録
- ステージ遷移の静的解析 — 状態遷移と主要ルーチンの解析
- エミュレーター確認手順 — mGBAデバッガーでの確認方法
- パッチ生成スクリプト — IPSを実際に生成するプログラム
変更履歴
| 日付 | 内容 |
|---|---|
| 2026-08-13 | 初版を作成 |