セーブデータに保存されるデータを利用して、ゲーム開始時に任意コードを実行できる方法を発見したので解説します。
この記事で紹介する方法ではゲーム開始時に自動的に実行される任意コードからバイナリエディタ起動準備を行い、L+Rの同時押しでいつでもバイナリエディタが起動できるようになります。
↓動画
記事用 pic.twitter.com/yVpItMxbJv
— ぼんじり( ꒪⌓꒪) (@_3z8) September 22, 2019
また、システムハックの導入にはバイナリエディタの起動環境を整えていること、16進数を理解していることを前提条件とします。
導入手順解説の前に、具体的に何を行ったかについて解説します。
従来の手法では、技アニメスクリプトの不正アドレス参照をトリガーにしてボックス上に記述した任意コードを実行する、といった方式でチューリング完全の処理を実行させることができていました。
実行できる任意コードはGBAのハードウェアを完全に制御することが可能なので、割り込み処理等を変更することでシステムハック自体は簡単に行えます。
(この記事で行っていることも大体同じ原理です)
bzl.hatenablog.com
割り込み処理変更によるシステムハックを実行すれば、技アニメ再生後は任意のタイミングで任意コードを実行することができます。
しかし、割り込み処理のポインタを含む静的RAMの変更内容は基本的にゲーム終了と同時にリセットされてしまうため、システムハックを行ってもその効果をゲーム終了後も持続させることはできませんでした。
そこでゲーム開始時にメモリを改竄するため、ゲーム開始時の処理をトリガーに任意コードを実行する方法がないか調査した結果、主人公スプライトに紐付けられたオブジェクトアクションID(デフォルト0x0B)がセーブデータ上に保存され、開始時にセーブデータ依存でRAM展開されることに気付きました。
(オブジェクトアクションIDについての資料はこちら)
オブジェクトアクションIDに紐付けられたプログラムはNPCスプライトが描画されている限り実行され続けるので、ここで不正プログラムを参照させれば主人公スプライトが描画されるゲーム開始時に任意コードを実行させることができます。
紐付けられたプログラムの参照テーブル付近のデータを調査した結果、0x084DDA40にボックスRAM0x02030401(0x02030400に記述されたプログラムをTHUMBステートで参照する)へのポインタとなるデータが配置されていたので、ここを参照できるオブジェクトアクション0x6Eを任意コード実行のトリガーとして使用することにしました。
ゲーム開始時に行われる処理の流れは以下の通りです。
- 主人公スプライト描画
- オブジェクトアクション0x6Eに紐付けられたプログラム(0x02030400)の実行
- 静的RAM(0x0203D000)へのスクリプト展開
- ボックス1の2匹目から記述されているプログラムを静的RAM(0x0203D020)にコピー
- 静的RAM(0x0203CE00)への割り込み処理展開
- 割り込み処理参照ポインタ(0x03007FFC)の書き換え
- システムハックの完了
導入手順
1)バイナリエディタの導入
記事冒頭で前提条件として挙げた通り、データの打ち込みにはバイナリエディタを使用します。
(理論上はアニメスクリプトでも書き込めるが、使用できるバイナリデータに限りがあるのとボックス埋め込み用にコード組むのがめんどくさい)
バイナリエディタ未導入の場合、以下の記事を参考に導入してください。
http://bzl.hatenablog.com/entry/2019/07/16/075233bzl.hatenablog.com
2)データ書き込みアドレスの始点計算
バイナリエディタが導入できたら、バグ技0x2B5C、又はバグポケ0x085Fを使用してバイナリエディタを起動します。
今回データを書き込む領域はボックスRAM(動的RAM)になるので、乱数によって対応するメモリアドレスが変化してしまいます。
適当な領域に書き込むとダメタマゴフラグの影響でプログラムが破損するので、正確な位置にデータを書き込む必要があります。
まず、バイナリエディタで0x03005AF4、0x03005AF5、0x03005AF6、0x03005AF7のデータを読み込み、この時読み込んだデータを前から①、②、③、④として扱います。

画像で読み込まれたデータは①AC、②94、③02、④02となっています。
このデータを④③②①の順に並び替えると0x020294ACというアドレスになると思います。
これがボックスRAMの始点です。
ボックスRAMの始点に0x6ED8を加算するとデータ書き込み先のアドレスが算出できます。
この画像のケースではデータ書き込み先のアドレスは0x02030384になります。
この手順で開いたバイナリエディタは閉じずにそのまま手順3に移行してください。
もし閉じてしまった場合は、再度手順2の頭からやり直しです。(動的RAMの先頭アドレスは画面切り替え毎に変動するため)
3)データ書き込み
手順2で算出したアドレスをこれ以降pと表記します。
任意コード実行にバグポケ0x085Fを使用している場合p+0x00~p+0xBBまでの改変は必要ありません。
その代わりにp-0x34の位置を始点に以下のデータを書き込んでください。
| 書き込み位置 | データ |
|---|---|
| p-0x34 | 00 |
| p-0x33 | 48 |
| p-0x32 | 00 |
| p-0x31 | 47 |
| p-0x30 | F1 |
| p-0x2F | 17 |
| p-0x2E | 03 |
| p-0x2D | 02 |
p+0x00~p+0x7Fまでのメモリ領域を0x1A,0x00,0x1A,0x00...と埋めていきます。
バイナリエディタの導入時点で既にここのメモリは書き換えられているはずなので実際に書き込む手間はそこまで多くありません。
プログラムとして参照しても問題ないようにした上で0x2B5Cを引き続き使用できるようにする為にp+0x80の位置に以下のスクリプトを記述します。
| 書き込み位置 | データ |
|---|---|
| p+0x80 | 03 |
| p+0x81 | DD |
| p+0x82 | 17 |
| p+0x83 | 33 |
| p+0x84 | 02 |
| p+0x85 | 99 |
| p+0x86 | 00 |
| p+0x87 | 08 |
0x023317DDをポインタとして扱って大丈夫なのか?と思う方がいるかもしれませんが問題なく参照可能です。(実機、VBA1.7.2で確認済み)
p+0x88~p+0xBBまでの領域を0x00で埋めます。
p+0xBCを始点に以下のプログラムデータを書き込みます。
(長いのでスポイラーにしています)
内容は0x02036FF6に0x6Eを代入するコードです。
ボックス名1の領域(p+0x146C)を始点に以下のデータを書き込みます。
| 書き込み位置 | データ |
|---|---|
| p+0x146C | 02 |
| p+0x146D | 48 |
| p+0x146E | 6E |
| p+0x146F | 21 |
| p+0x1470 | 81 |
| p+0x1471 | 75 |
| p+0x1472 | 70 |
| p+0x1473 | BC |
| p+0x1474 | FF |
| p+0x1475 | 51 |
| p+0x1476 | 80 |
| p+0x1477 | BD |
| p+0x1478 | E0 |
| p+0x1479 | 6F |
| p+0x147A | 03 |
| p+0x147B | 02 |
ボックス名に直した場合の文字列データは以下の通りです。
| ボックス | ボックス名 |
|---|---|
| ボックス1 | いぶホむゥユミB |
| ボックス2 | アィClマうい |
最後に、以下のメモリ改変を行います。
| メモリアドレス | データ |
|---|---|
| 0x0203CFFF | 0x?? → 0x00 |
| 0x02036FF6 | 0x0B → 0x6E |
以上でRAM改竄は完了です。
!注意
RAM改竄終了後は手順4の内容を終えるまで絶対にレポートを書かないこと。
もし入力ミスがあった場合ゲーム起動時にクラッシュするようになるので実質的なセーブ破損状態になる。
この状態になった場合『さいしょから はじめる』を選択してニューゲームするか外部ツールでセーブデータを改造する以外に回避する手段がない。
4)処理の実行
ポケモン選択画面やバッグ等を開いてから再度スタートメニュー画面に戻ります。
マップが再描画されオブジェクトアクションに紐付けられたプログラムが参照し直されると、前述の手順で書き込んだプログラムが実行され、割り込み処理の差し替えとバイナリエディタの静的RAMコピーが行われます。
フリーズせず正常に動作することを確認できたら、レポートを書いて終了してください。
以上の操作を完了させることにより、無改造のエメラルドをほぼ完全にハックすることができます。
ゲーム性は完全に崩壊しますが、メモリ弄りが大好きな方は是非試してみては如何でしょうか。
因みに、FRLGでも同じ手法でハックすることが可能です。