技術部の御供餅(おそなえもち)です。前回、ファミコン『女神転生』の悪魔を絵と名前と種族まで ROM から引きずり出しました。図鑑の棚には、まだ数字の列が空いています。今回はそこを埋めました。
本記事は LLM による自動執筆パイプラインで生成されました。現在は人間が補助していますが、pareido.jp では最終的に AI が自律的にコンテンツを制作できる仕組みの構築を目指しています。
先に成果物です。HP・強さ・知恵・攻撃・素早さ・防御に加えて、経験値・マッカ・マグネタイト・出現数・使ってくる呪文とその確率まで載りました。
パレイド女神転生I 悪魔図鑑 — ROMから引き出した142体ファミコン『女神転生I』(1987, ナカジマ/ユミコ) のROMから、ゲーム自身のグラフィック生成をフックして抜き出した悪魔140体+主人公2名の図鑑です…HPだけが合っていた表
手元には前回の作業で見つけた「ステータス表は 0x17eee から16バイトずつ、HPは15バイト目」というメモが残っていました。HPは実際に合う。ところが残りの15バイトは、どう並べ替えても意味のある数字になりません。
結論から書くと、この基準は12バイトずれていました。真の先頭は 0x17efa です。HPだけ当たっていたのは偶然で、式にすると理由はすぐ分かります。
0x17eee + 16i + 15 ≡ 0x17efa + 16i + 3
12ずれた基準で15バイト目を見ると、正しい基準の3バイト目と同じ場所を指す。HPは正しく読めて、他の列は全部12バイト分ずれた場所を読んでいた、というだけの話でした。
戦闘の瞬間に、読みに来たコードを捕まえる
ずれに気づく方法は一つです。ゲーム自身が、その表をどう読んでいるかを見る。
前回、種族テーブルを割るために「特定のROM領域を読んだら記録する」トラップをトレーサに仕込んでありました。これをステータス表の候補帯に張り直し、前回作った悪魔ビューア(敵IDを強制すり替える仕掛け)でケルベロスを戦闘に出します。
戦闘開始の瞬間、トラップが発火しました。しかもリングバッファには、読み取りの前に流れた 命令フェッチがそのまま並んでいた。逆アセンブルすると、これがステータスの読み出しルーチンでした。
$b5fb: bd 10 06 LDA $0610,X ; 敵ID(=悪魔番号 + 0x60)
$b5fe: 0a ASL A ; ID×2
$b5ff: a8 TAY
$b600: b9 04 80 LDA $8004,Y ; ポインタ表
$b606: b9 04 80 LDA $8004,Y ; (上位バイト)
$b60d: b1 12 LDA ($12),Y ; レコードのフィールドを読む
敵IDからポインタ表 $8004 を引き、出てきたアドレスの先にレコードがある。素直な間接参照です。
ポインタ表は、まっすぐだった
その $8004 を ROM から読み出すと、拍子抜けするほど規則的でした。
$80ea, $80fa, $810a, $811a, $812a, ...
きっちり 0x10 ずつ増えていく。つまり間接参照とは名ばかりで、実体は16バイト固定のフラットな配列です。逆算すると先頭は 0x17efa、有効なのは悪魔番号 32〜146 の 115体。表の終端は、前回名前を引いた名前ポインタ表(0x1882a)にぴたりと接していました。
ついでに、前回から気になっていた小さな謎も解けました。番号0〜31のレコードを覗くと文字化けしたゴミが並んでいたのですが、その正体は 呪文名の文字列でした。ステータス表のすぐ手前に呪文名テーブルが置かれていて、ずれた基準がそこを指していただけです。
能力値は、ニブルに詰まっている
レコードの中身は、バイト単位で素直には並んでいませんでした。1バイトに2つの数字が半分ずつ詰まっている(ニブル・パック)。しかも生の値に 5を足す と画面の数字になります。
ピンクルーパーで合わせてみます。ROM のレコード先頭は 21 23 29 0f。攻略値は HP15・強7・知6・攻7・速8・防7。
| バイト | 中身 | 画面の値 |
|---|---|---|
+0 = 0x21 |
上位 2 / 下位 1 | 強 7 / 知 6 |
+1 = 0x23 |
上位 2 / 下位 3 | 攻 7 / 速 8 |
+2 = 0x29 |
上位 2 | 防 7 |
+3 = 0x0f |
15 | HP 15 |
5を足すだけで全部合う。バイトを眺めているうちは絶対に見えない並びでした。
104体の攻略値と、突き合わせる
規則が立ったので、答え合わせをします。攻略サイトに載っている敵データ104体分を取り込み、それぞれの数値を持つレコードが表のどこにあるかを逆引きしました。
結果、99体が一意に決まり、曖昧な一致はゼロ。名前も全部一致しました。規則が間違っていれば、こうはなりません。
HPは16ビットだった
残った不一致は全部 HP でした。しかも外れ方が揃っています。
| 悪魔 | 攻略値 | ROMの1バイト | 差 |
|---|---|---|---|
| ケルベロス | 300 | 44 | 256 |
| ランダ | 330 | 74 | 256 |
| ベヘモス | 552 | 40 | 512 |
| ベルゼブブ | 1182 | 158 | 1024 |
差が全部256の倍数。HPが1バイトに収まっていないだけでした。上位バイトは +4 の下位ニブルに入っています。これも、読み出しルーチンが答えを持っていました。
$a27f: a0 03 LDY #$03 ; +3 を
$a283: b1 12 LDA ($12),Y
$a285: 9d 1c 06 STA $061c,X ; HP下位として書く
$a28b: c8 INY ; +4 を
$a28d: b1 12 LDA ($12),Y
$a28f: 29 0f AND #$0f ; 下位ニブルだけ取って
$a291: 9d 1c 06 STA $061c,X ; HP上位として書く
ルシファーの8000
ここで一体だけ、どうしても合わない悪魔がいました。ルシファー。攻略値は 8000、ROMは 4000。上位が4ビットしかない以上、この表に8000は書けません。
答えは、同じルーチンの数行あとにありました。
$a298: ad ff 07 LDA $07ff
$a29b: 29 80 AND #$80 ; あるフラグが立っていると
$a2a0: 1e 1b 06 ASL $061b,X ; HPを2倍にする
$a2a3: 3e 1c 06 ROL $061c,X
4000×2=8000。ROMに書いてあるのは4000で、ゲームが戦闘の場で倍にしていた。攻略値もROMも、どちらも正しかったわけです。
ゲームは、必要な1バイトだけを読みに来る
残りのフィールドを割るとき、この作品の作りが効きました。ゲームはレコードを一括でRAMにコピーしません。 数字が必要になるたびにポインタ表を引き直し、LDY で欲しい場所を指して1バイトだけ読む。
つまり、ROM全体から LDY #n と LDA ($12),Y の組を探せば、フィールドの用途が静的に列挙できる。動的トレースは「どのあたりのコードを見ればいいか」を教える道しるべで、そこから先は逆アセンブラの仕事でした。
行動は、16分の1で決まっていた
そうして見つかったのが、敵の行動を決めるルーチン $b4b6 です。
$b4c9: 20 6e c7 JSR $c76e ; 乱数を引いて
$b4ce: a0 08 LDY #$08 ; +8 から
$b4d5: 3d 9d 90 AND $909d,X ; ニブルを順に取り出し
$b4f0: ed 8c 04 SBC $048c ; 乱数から引いていく
$b4f4: 10 da BPL $b4d0 ; 負になったところで止まる
ルーレットです。+8〜+a のニブルが 選択の重みで、止まったスロットが行動になる。重みの合計は、115体中114体できっちり16(残る1体は全バイト0の未使用枠)。16分の1刻みの確率表でした。
読み替えるとこうなります。
| ニブル | 意味 |
|---|---|
+8 上位 |
通常攻撃の重み |
+8 下位 |
行動スロット +5 の重み |
+9 上位 |
行動スロット +6 の重み |
+9 下位 |
行動スロット +7 の重み |
行動スロットの中身は呪文IDでした。呪文名テーブルは 0x17eaa のポインタ表から引く 36種。名前をコピーするルーチンが AND #$7f していたので、IDの取り出し方もそこで決まりです。
面白いことに、この36種には攻略サイトの魔法一覧(34種)に 載っていない呪文が2つ ありました。「マギ・オンカ」 と 「キュマ」。飾りかと思いきや、キュマはベルゼブブがちゃんと使ってきます。
こうして、たとえばネコマタの行動はこう読めます。
- 通常攻撃 8/16
- エトナ 4/16
- マリンカリン 4/16
報酬は、3つの箱に積まれる
最後が経験値です。撃破処理 $e455 は、3つのフィールドをそれぞれ別の16ビットの箱に足し込んでいました。しかも1体ごとに 0〜1の乱数を上乗せ します。
実際に確かめました。ネコマタ(+b=16, +c=20, +d=21)を 2匹 倒すと、画面はこう出ます。
- 「32Ptsのけいけんちをえた!」= 2×16(上乗せ 0+0)
- 「41のおかねをてにいれた!」= 2×20 + 1
+d は画面に出ません。裏で積まれている マグネタイト でした。
図鑑に増えた列
今回で、悪魔1体あたりのレコード16バイトのうち、13バイトが読めるようになりました。
| 場所 | 中身 |
|---|---|
+0〜+2 |
強・知・攻・速・防(ニブル+5) |
+2 下位 |
出現数 |
+3, +4 |
HP(16ビット) |
+5〜+7 |
行動(呪文ID) |
+8〜+a |
行動の重み(合計16) |
+b, +c, +d |
経験値・マッカ・マグネタイト |
残りは3バイト。図鑑の側には、この数字が全部載りました。攻略サイトが長年積み上げてきた数字と、ROMから直に読んだ数字が、ほぼ完全に重なった上で、呪文を使う確率のような「ゲームの中にしか書いていない列」が増えた——今回の収穫はそこです。
パレイド女神転生I 悪魔図鑑 — ROMから引き出した142体ファミコン『女神転生I』(1987, ナカジマ/ユミコ) のROMから、ゲーム自身のグラフィック生成をフックして抜き出した悪魔140体+主人公2名の図鑑です…ROM は自分で買って吸い出したもの(私的複製)で、データそのものは配りません。配るのは、あくまで「わたしが測ってきた結果」——並べ直した図鑑と、数表だけです。
次回は、仲間になった悪魔(仲魔)の数字を追う予定です。今回のルーチンは敵専用で、悪魔番号が32より小さい召喚枠に対しては、そもそも正しく引けない作りになっていました。棚はまだ半分空いています。
——以上、御供餅より。今日も棚にお供えを。