FF2 の glitched TAS がついに公開された その2

昨日の続きです。

誤動作途中への MMC1 レジスタへの書き込み

$A028 の誤ったプログラムを実行した結果の UNDEFINED の逆アセンブルを修正します。

S:0E                   $9B84:60        RTS (jsr からの復帰ではないので from 表示がない)
(割り込み発生前のstatus register と PC 下位 byte が PC に入る)
S:10                 $A028:04        NOP $04 (illgal)
S:10                 $A02A:66 A9     ROR $00A9 = #$76
S:10                 $A02C:15 85     ORA $85,X @ $0088 = #$02
S:10                 $A02E:67 20     RRA $20 (illgal)
S:10                 $A030:2F 97 A9  RLA $A997 (illgal)
S:10                 $A033:05 85     ORA $0085 = #$11
S:10                 $A035:66 A9     ROR $00A9 = #$BB
S:10                 $A037:69 85     ADC #$85
S:10                 $A039:67 20     RRA $20  (illgal)
S:10                 $A03B:2F 97 A9  RLA $A997 (illgal)
S:10                 $A03E:06 85     ASL $0085 = #$11
S:10                 $A040:66 A9     ROR $00A9 = #$5D
S:10                 $A042:20 85 67  JSR $6785

問題は RLA $A997 で, CPU address $A997 に値が書き込まれてしまいます。この address への write は MMC1 のレジスタの書き込みとなってしまい、5回連続書き込むことによってレジスタの更新が出来ます。5回書く途中にCPU address $8000-$BFFF の可変バンクへアクセスするとこの領域の読み込みが不安定になってしまうようです。

5 回の書き込み回数を reset するには data bit 7 を 1 にして write します。

任意プログラム (試作)

FF2 の stack 制御でバグを起こすことによって、 jsr $6785 という命令を実行させることができました。 $6785 は本来はセーブデータその3の仲間3人目の名前4文字目の場所です。

よってゲームを開始するときに入力する名前が RAM にどの値が書かれるのかがわかれば、名前欄に入力した文字は 6502 のアセンブラプログラムとして動かすことが出来ます。

ただし名前欄で入力出来る文字は名前なので、1人あたり連続して6文字かつ使える文字が 0 から 0xff すべてが使えるわけではありません。この制限は無視してとりあえず $6785 に 6502 プログラムを FCUEX の hexedior から無理矢理書き込んで、エンディングに飛ばせるかを調査します。

2年程前に FF3 のエンディング呼び出しを応用して FF2 で似た形で呼べることを確認していますのでこの形に任意プログラムを書いてみました。

	org	$6785
	ldx	#0
	stx	$0017 ;scenario enable flag #0, must be set 0
	inx
	stx	$006c ;scenario enable flag #1, 1:execute
	ldx	#$8c
	stx	$0072 ;scenario pointer.l
	ldx	#$86
	stx	$0073 ;scenario pointer.h
;このPCにくる途中にMMC1のbankregisterがぐちゃぐちゃになってるので初期化し直す
	stx	$ffff ;reset shift count on MMC1, address $c000-$ffff, data bit7=1
;	lda	#$0e
;	jsr	$fe06 ;MMC1 bank mode set (いらんかも)
	ldx	#$fb ;stack initialize
	txs	;(とりあえず一番底に近い値にしたが戦闘終了後に止まらなければ $01fb でなくてもいい)
;	lda	#$0b
;	jsr	$FA7F ;set page register 0x0b for $8000-$bfff
	jmp	$9e79 ;battle scene exit

やっていることは下記です。

  • イベント(シナリオ)の進行をエンディングに無理矢理設定
  • MMC1 の書き込み回数を補正し不安定状態を解消
  • stack pointer を正常な動作をする場所へ無理矢理補正する
  • 戦闘シーンを無理矢理終わらせる

ということでこのコードを実行するとエンディングを呼び出せます。 また address $0072, $0073 に書かれるポインタを設定すると好きなイベントを勝手に起動することが出来ます。(ただし、無理矢理なので正しく動かすにはシナリオ上で場所の初期化の処理も混ぜる必要がある)

任意プログラム (最適化)

試作のコードは冗長で名前欄にも収まらないので参考資料として渡し、続きをピロ彦さんにお願いしたところ、戦闘終了もシナリオも無視してエンディング処理を実行させる jmp 命令をみつけてしまいました。さすがです。

これによりコードは下記のようになり、名前欄にもこのように収まりました。

	org	$6785
	jmp	$6442 ;jmp to save data #2, name #2

	org	$6442
	dec	$d0cf ;reset shift count on MMC1 
	jmp	$f34d ;ending scene start

各手続きを通らずに無理矢理エンディング処理を実行していますので動画の通り、曲は戦闘時のままです。曲をならす方法も探しましたが、命令が 4 byte 増えて名前欄の入力が複雑になってしまいます。 TAS では最速を目指しますので仕方ないですね。

エンディング曲の再生命令を混ぜる場合

名前欄は連続して6 byte なので末尾の 2byte か 3 byte に jmp/bne 命令をいれる必要があり、入力出来ない文字の制限もあるのでこのような形になると思います。

	org	$6782  ;3人目
	bne	$6742  ;キ ュ
	nop            ;x  
	jmp	$64c2  ;び じ ー
	
	org	$67c2  ;4人目
	sta	($8d),y;く え
	bne	$6782  ;キ ッ x x
	
	org	$6742  ;2人目
	ldx	#$5d   ;み デ
	bne	$6702  ;キ ッ x x
	
	org	$6702  ;1人目
	stx	<$e0   ;5  ヌ
	jmp	$f34d  ;び ぶ レ

補足

私は Lua script の使い方をよくしらないので手動で PC が $6785 になるか100回ぐらい闇雲に試しましたがうまくいきませんでした。RTA でやろうとすると相当大変だと思われます。