reiju's diary

オタクが文字を入力しておくだけ

ポケモンDPの技術的解説(翻訳)

Cyro氏による、このトリックについての翻訳済みの文章および解説は公開を許諾されています。詳細はこちらへ

追記: 2017-02-05 13時に編集を行いました。

 

BACKGROUND

Pokemon D/Pは動的アドレスを扱うので、別言語のROM間で、ベースアドレスと最もこのトリックに関係したメモリの相対位置は異なります。説明の一貫性のため、USバージョンのD/Pのアドレスや相対位置を扱って説明します。

ゲーム内のすべてのマップは4つの主要なプロパティを持ちます。それらは家具(像、植物など)、オブジェクト(スプライト)、ワープ(ドア、洞窟の出入り口など)、そしてトリガー(自動スクリプトの床)です。下にあるのはベースアドレスと、のちに扱われるメモリの相対位置です。

Base Address      = [0x02106FC0]
ベースアドレス

Map Matrix Height = Base + 0x22AD8
マップの高さ
Map Matrix Width  = Base + 0x22AD9
マップの幅
Map Matrix Layout = Base + 0x22ADA
マップのレイアウト
Map Data          = Base + 0x23C6E
マップデータ

Furniture Count   = Map Data + 0x20
家具の数
Object Count      = Map Data + 0x24
オブジェクトの数
Warp Count        = Map Data + 0x28
ワープの数
Trigger Count     = Map Data + 0x2C
トリガーの数

Furniture Address = Map Data + 0x30
家具のアドレス
Object Address    = Map Data + 0x34
オブジェクトのアドレス
Warp Address      = Map Data + 0x38
ワープのアドレス
Trigger Address   = Map Data + 0x3C
トリガーのアドレス

Furniture Data    = [Furniture Address]
家具のデータ
Object Data       = [Object Address]
オブジェクトのデータ
Warp Data         = [Warp Address]
ワープのデータ
Trigger Data      = [Trigger Address]
トリガーのデータ

マップの配置

ベースアドレスからの相対位置0x22ADAのデータにより、マップのレイアウトを取得できます。これは1800バイトのデータで、ある歩数だけ歩くと、あのマップに訪れる、ということを決めています。シンオウ地方では1800バイトの空間を費やします。これは屋内にあたる場所では移動が何においても難しくなります。

1800バイトの長さであることの背景は、マトリックスの高さと幅の可能な最大値—30x30であることからきています。30x30のマップデータのあるシンオウ地方を例としましょう。この例のデータに含まれるMap IDsは30 Map ID(=60バイト)ごとに一周します。Map IDsの数の流れをただ見るだけでは目で見てもよくわからないので、1800バイトのマップの配列のレイアウトをタウンマップ上に配置しました。これがどのようにフィットし合うのか見れるでしょう。

0の値でパディングされた16進数のマップ

 

f:id:xreiju:20171226165555p:plain

0の値でパディングされていない十進数のマップ

f:id:xreiju:20171226165659p:plain

視覚的に、コトブキシティから202ばんどうろに下へ移動するのは、ただMap ID 3からMap ID 343に移動しているだけのことです。これは正しいのですが、さらにもう一つ起こっていることがあり、それはあなたのマップのレイアウトでのメモリ上の位置が、先ほどいた位置と比べると、マップの幅にあたる30だけの差があるということです。これは、下に1マップだけ動くと30マップ分右に動いたこととなり、Map IDは2バイトで表現されるため、もっと正確にいうと1マップ(32歩)下に進むと、実際は60バイトだけメモリが加算されるということになります。

次のマップのマトリックスのレイアウトは900バイトの部分で、これはマップの高さのボーダーを定義します。次の1800バイトの部分は、実際のマップデータのインデックス(歩ける床かどうか、3Dモデルデータ、地形の情報など)を持ちます。このトリックでは今まで説明したすべての4500バイトの情報が関係します。

今までの4500バイトはすべてセーブデータから確定されます。ワープ処理(ドアに入ったり、シルベのとうだいのような自動ワープをすること)を実行させない限り、前述のデータには変更が起こりません。一方で、マップデータに関わるバイトは、訪れたマップによって変更が起こります。

相対位置0x23C80から始まるデータは現在のMap IDです。コトブキシティから202ばんどうろに降下するように、新しいマップに訪れた際には、前の4500バイトの部分は、マップデータの家具やオブジェクト、ワープ、トリガーにかかわる変更が起きても、そのままの状態を保ちます。

そしてさらに面白いことは、古いデータは新しいデータによってしか上書きされることがないということです。新しいエリアが読み込まれようと、前のマップがより多くのマップデータを新しいマップより含んでいたら、古いマップデータはそこに残ります。たとえば、コトブキシティから202ばんどうろに降下すると、コトブキシティは202ばんどうろより多くのスプライトを含んでいるので、メモリにマップデータが残り、多くのスプライトが発生します。

これは、求める場所(どこでも)を読み込むためのトリックを成しえるため、私たちが手を加えることになるデータとなります。

HOW IT WORKS

すべての必要な背景の情報を出した今、初めから終わりまでの間に本当は何が起きているのか説明しましょう。

ポケッチカンパニーのマップのマトリックスは1x1なので、下か右に進むのは2バイトだけメモリの値が増えます。左か上に進むのは2バイトだけメモリの値が減ります。そのために、Save/Resetをする前の5歩は126バイトだけメモリの値を減らし、32バイトだけメモリの値を増やします。

これはマップのマトリックスのレイアウトの初めから94バイト後ろにキャラを置きます。ここのMap IDは558より大きくなります。この数以上のMap IDはコトブキシティのデフォルトのプロパティを読み込むようになっていて、セーブを安全に行うことができます。しかし、そのプロパティたちは、その数の多さのために周辺のすべてのマップデータを上書きします。

Save/Resetが終わったのちのキャラの位置は、はるかに別のものになっています。94バイトだけマップのレイアウトの初めから後ろにあったのに対して、キャラは834バイトだけマップのレイアウトの初めから進んでいます。これはゲームがキャラのX・Y座標を使ってキャラのマップのレイアウト上の位置を再計算するためです。

ゲームをセーブするとき、キャラのいたMap IDと関連したマップのマトリックスのレイアウトがセーブデータに書き込まれます。ここでリセットすることで、キャラのメモリ上の位置は新しいマップの大きさ(30x30)に適応するために再計算されます。

この再計算は下記の簡易コードで説明されます:

x_offset = floor(x / 32) * 2
y_offset = floor(y / 32) * (2 * matrix_width)

current_offset = matrix_layout_start + x_offset + y_offset

384 Sの前後の32Eと32Wのステップは、Map ID 176〜188を含むマップを回避するために行われます。これらのマップはスクリーン全体を再描画するとき(バトル終了や、ずかんやかばんから戻るときなど)にクラッシュを引き起こします。

1792 Sのステップはマップのマトリックスのレイアウト全体を通過してキャラを実際のマップデータ上に配置します。1664 Sのステップで、ちかつうろ(Map ID 2)に訪れます。このときすべてのちかつうろのマップデータが読み込まれます。ちかつうろのSprite 1はX座標392に配置されており、このX座標の値は221ばんどうろのMap IDと等しいです。

もしSprite 1のX座標のデータにメモリ上で出会えたら、221ばんどうろのマップデータは読み込まれて、キャラの周りのすべてのマップのデータが一瞬で変わります。また、マップの読み込みやマップのリソースの読み込みをする新しい道が拓けます。これは効率的にかべのなかで求めるテレポートを行うことが可能になります。Step 2の最後の160W ステップはキャラをそのアドレスに置き、221ばんどうろのマップデータがキャラの周りで読み込まれます。

221ばんどうろは出口がパルパークのエントランス(Map ID 393)であるワープを含みます。前の手段を繰り返して、Step 3の初めの1 Nのステップはキャラをそのアドレスに導き、パルパークのエントランスのマップデータを読み込みます。

パルパークのエントランスは当然パルパーク自身(Map ID 251)へのワープを含みます。ここでも同じ方法を使ってワープの出口を含んだアドレスに歩き回り、Step3 の2回目の1 Nステップはパルパークのマップデータを読み込み、キャラをパルパークモードに突入させます。

Step 4は短いですが、66 Sのステップは2つの重要なことを行います。65 Sステップはクロガネシティ(Map ID 45)のマップデータを読み込みます。このデータ内のSprite 13はX座標316のデータを含みます。この座標はリッシこのMap IDと同じです。面白いことに、この出口はクロガネシティ(Map ID 45)に行くために今さっき読み込んだワープと全く同じ場所に読み込まれます。最後の66 Sステップはリッシこのマップデータを読み込みます。その後の1 Nのステップは、リッシこに行くための1 Sから正しい位置に戻るためにあります。

リッシこのSprite 0は510の値を持ちます。これははじまりのまのMap IDです。パティを探すために)

今になってもはじまりのまへアクセスするのは不可能ですが、一つ一つの移動はこの特定のアクセス不可のアドレスへと最後には導きます。この手段を確立させたのは、なぞのばしょが家具やオブジェクト、ワープと、トリガーの数が格納された初めの十数バイトにおいて0しか値を持たず、そのためにそれらの数をキレイにするためです。

Step 5の最後の226 Eステップは、このキレイになった空間を横切って、Sprite 0の値に到達し、結果としてはじまりのまのマップデータを読み込みます。

不幸にも、はじまりのま自身は一つもイベントプロパティをMap ID 510のデータで含まないので、つまり1つの床の上(キャラが今いるところ)でアルセウスエンカウントする必要があります。アルセウススクリプトがキャラを上に2歩動かすために、私たちはなぞのばしょでアルセウスと戦闘し、捕まえなければなりません。しかしそれは15分以内でアルセウスを捕まえるという初めての試みへの関心により作られた犠牲であります。

戦闘が終わると、同じ手段を最初の数ステップにおいて繰り返します。221ばんどうろからパルパークのエントランス、そして最終的にパルパーク。「リタイア」をパルパーク内で使うのは、文字通りかべのなかからの脱出とパルパークモードの無効化を同時に行う唯一の方法です。

他のあれこれ

私はUSバージョンのD/Pの一つ一つのマップにあるスクリプトとイベントプロパティすべてをダンプしたものを添付しました(主に、マップデータを読み込む目的で使えるマッププロパティを探すために)
[注: 添付ファイルはフォーラムからダウンロードしてください。]

また、はじまりのまでアルセウスを捕まえることは可能です。しかし、それには骨の折れる11k步もの降下ステップが含まれ、ID 510にある3Dモデル(壊れたはしら)を含むマップデータを読み込むためにかなりカオスな3Dモデルを読み込むことが利用されます。皮肉なことに、この3Dモデルを含むゲーム内での唯一のマップはやりのはしらです。私はこの方法の方がとても短く、どんなデータが読み込まれるか予測するのがとても簡単だったために、この方法を選びました。