FF3 の RAM 破壊の話

不正なジョブ番号のコマンド実行から、 $6000-$6fff 付近へ indirect jump して、名前欄に書いた 6502 プログラムを実行しようと思ったが、そんなジャンプ先はなかった。index register の範囲が 256 byte だったため。惜しい。

動画の RAM 破壊の解析をやって大まかな概要まではわかった。不正なジョブ番号をいれると、コマンド欄の文字列がおかしく表示されるのはすぐにわかるんだが、不正な文字列の展開が表示領域を通り越して広範囲なのでほかの動作にもいろいろ影響がでるらしい。

下記の順に処理をしているようだ。

  • ジョブ番号から、コマンド番号を4つ取得する。
  • 以下を4回行う。
    • コマンド番号から表示する文字列を取得する。(たたかうとかアイテムなど)
    • 取得した文字列を画面の枠などに収まるように計算して VRAM へ表示するバッファへ書き込む
  • VRAM バッファから VRAM 実体へ書き込む

コマンド番号から表示する文字列を取得する

計算が複雑でよくわからないのだが、コマンド番号が入力で、結果的に $7ad7,x へ文字列を書き込む。正常な場合は空欄,文字x4,終端で6byteらしい。先頭の空欄は矢印のために開けるようだ。

不正な値が入ると $7ad7,x の範囲は広大になり、 $7b00 を越して書いてしまうこともある。(ただし x の範囲は最大 0xff)

VRAM へ表示するバッファへ書き込む

前述の処理で $7ad7 から終端(0)があるまで順番に文字列を展開していく。
VRAM バッファのアドレスはループの前に $7200 (ポインタ$004e)に設定して、順番に進めていく。枠に収める関係で 6,7 byte は連続するがとびとびになるらしい。

文字列の1byteで単語を展開する処理が入っているようだし、文字列が長いと破壊領域は広大になり、参照元の $7ad7,x の部分も破壊するかもしれない。

前項の処理で $7ad7,x は $7b00 を越して書いてしまうが、終端文字が入っていないことが多い。$7b10 で書き終わった場合は、$7b11 以降を参照する。$7b00-$7b3f は町やダンジョンでのセリフのバッファに使っているので、会話をするとここは書き換えられる。$7b40 以降は移動画面での地形表示などに使用していて0 が入ってるので文字列展開は長くてもここら辺でとまる。

$7b00-$7b3f はエミュレータで RAM を見ながら動かしてもらった方が理解が早いと思うが、ピアノの演奏でもここは影響する。ただし、お店に入ると上書きされるのでピアノの演奏はやらなくてもいい気がする。

cheap 氏の日記に"フィールドでメニューを開いたかどうかが影響する"というのはメニューを開くと $7af0 が 0 になるので、文字列展開がこのアドレスで止まる可能性が高い。アイテム欄のカーソルに使っていて初期値は0ということらしい。

VRAM バッファから VRAM 実体へ書き込む

VRAM バッファとして区分けした領域だけに VRAM に書き込まれるので、表示がおかしいのはコマンド枠だけ、ということらしい。

RAM 破壊のパラメータ

  • ジョブ番号から展開されるコマンド番号
  • コマンド番号から展開される $7ad7 から 0x100 byte の data
  • コマンド番号から展開されないものの参照されてしまう $7axx-$7bd6 の data
    • ここらへんは戦闘中には使わないので移動中のデータが残っている
    • $7a00-$7aff はメニューでの変数(アイテム選択関連らしい)
    • $7b00-$7b3f はセリフのウインドウ
    • $7b40-$7bd6 は移動画面の地形の表示(?)

ジョブ番号から展開されるコマンド番号は一覧を記載する。ジョブ番号のbit5:0 が有効でbit7:6 は shift の都合で無視される。数値は全て16進数。

00:04,05,06,14 | 20:60,ad,eb,7c
01:04,05,06,14 | 21:f0,06,a9,00
02:04,05,06,14 | 22:8d,eb,7c,60
03:04,15,06,14 | 23:20,54,97,a9
04:04,15,06,14 | 24:77,8d,0a,72
05:04,15,06,14 | 25:a9,79,8d,0b
06:04,05,15,14 | 26:72,a5,52,48
07:04,05,06,14 | 27:a9,00,85,52
08:04,0e,07,14 | 28:a2,12,20,49
09:04,0c,0d,14 | 29:a5,a9,c7,8d
0a:04,0b,05,14 | 2a:e3,7a,20,41
0b:04,08,05,14 | 2b:a5,18,69,06
0c:04,05,06,14 | 2c:a8,a2,01,b1
0d:04,0f,05,14 | 2d:57,9d,d7,7a
0e:04,05,15,14 | 2e:c8,e8,e0,07
0f:04,15,06,14 | 2f:d0,f5,20,8d
10:10,11,12,14 | 30:9b,b1,5b,85
11:04,15,06,14 | 31:18,c8,b1,5b
12:04,15,06,14 | 32:85,19,20,e1
13:04,15,06,14 | 33:95,a5,1b,8d
14:04,15,06,14 | 34:df,7a,a5,1c
15:04,05,06,14 | 35:8d,e0,7a,a5
16:a9,18,d0,06 | 36:1d,8d,e1,7a
17:a9,05,d0,02 | 37:a5,1e,8d,e2
18:a9,06,85,ca | 38:7a,a9,00,85
19:e6,c9,60,18 | 39:24,20,1d,9d
1a:65,5f,a8,60 | 3a:ad,e8,7c,c9
1b:18,a9,03,65 | 3b:ff,f0,6f,a6
1c:5f,a8,60,18 | 3c:52,bd,cf,78
1d:a9,2f,65,5f | 3d:c9,ff,f0,34
1e:a8,60,18,a9 | 3e:c9,c8,90,1c
1f:2e,65,5f,a8 | 3f:38,e9,c8,85

重複をなくして並べ替えると下記。

00 01 02 03 04 05 06 07 08 0a 0b 0c 0d 0e 0f 10 
11 12 14 15 18 19 1b 1c 1d 1e 20 24 2e 2f 34 38 
41 48 49 52 54 57 5b 5f 60 65 69 6f 72 77 78 79 
7a 7c 85 8d 90 95 97 9b 9d a2 a5 a6 a8 a9 ad b1 
bd c7 c8 c9 ca cf d0 d7 df e0 e1 e2 e3 e6 e8 e9 
eb f0 f5 ff

cheap 氏の試行錯誤を見る感じ、ジョブ番号よりから生成される文字列よりかはそれを通り越した、移動中画面でのデータ領域がエンディング直行のパラメータに影響している感じがする。

追記分、ピアノの演奏について

ピアノを演奏すると script pointer $0073,$0072 に 0xbb9c が入る。この値は会話やイベントで更新されるので、店やメニューには影響しない。戦闘に入ると $74f3, $74f2 に入る。

動画のメモリ破壊で、$74f3 が破壊され 0xe4 が入り $74f2 は 0x9c のまま。ということでピアノ演奏は必要だった。ウルの町では猫ふんじゃったの演奏以外に 0x9c から 0xa3 の値が入るような会話やイベントがない。

TAS で動画の条件を越えて更新するとなると、ピアノ演奏を省くぐらいしかないようで、ウルの町で手に入るナイフ以外のジョブ番号から $74f2 と $74f3 を破壊して、data 0xdd を含むアドレスを書き込めるかが条件となるだろう。

メモリ破壊の不確定要素がいまだよくわからないので自分はもう限界だと思う。