ピロ彦の何か置き場

FDSゼルダ1任意コード実行TASの更新部分のちょっとした解説


英語による解説文は http://tasvideos.org/4709S.html をご参照ください。
前回のTAS(sm26296564)からRAT926氏によって移動部分で1:12で10Fと2:36で40Fほど更新されています。
※参考URL http://tasvideos.org/forum/viewtopic.php?p=408522#408522

ゼルダにするためにファイル名にZELDAの文字が必要で、
  1. ZELDAEヤ6
  2. Wヤ6AG2シ2
  3. ユノ6ソク+
と3つのファイルを使って任意コードを書いています。
ZELDAの後ろに文字が入っててもゼルダになるので19文字分使えますが、
2マス空白にしているのでここでは17文字が任意コードに使われています。
ゼルダのファイル名は$0638・$0640・$0648の3つ分が連続して格納されています。

墓地にてギーニを沢山出して枠を埋めた後に笛を装備しながらABを押すと
本来スプライトのためではない部分が変更され、
バグAIとして$0602からプログラムが実行されます。
その近辺はBGMの制御に使われているのでタイミング調整で
ファイル名アドレス$0638部分までプログラムカウンタが進むようにしています。
  • $0638: 23 0E 15 0D 0A 0E 4B 06
  • $0640: 20 4B 06 0A 10 02 33 02
  • $0648: 4C 40 06 36 2F 64 24 24
ファイルに使われた値は上の様になってます。
はじめの5文字はZELDAに使われているので$063Dからがコードになります。
  • $063D: 0E 4B 06 ASL $064B

この ASL(Arithmetic Shift Left)」「算術左シフト」を表し、
$064B[36]の値を2倍[6C]へ書き換えてます。
ファイル名に使用可能な文字が[00-64]の範囲なので、
それを超える値を使うためにこのような手法をとっています。

ここからはリアルタイムキー入力メモリに書き込んで
$06C3から新しいコードを書くためのループ文となっています。
このコードの原型はsockfolder氏によって書かれました。
※参考URL http://tasvideos.org/forum/viewtopic.php?p=408475#408475

  • $0640: 20 4B 06 JSR $064B
  • $0643: 0A ASL
  • $0644: 10 02 BPL $0648
  • $0646: 33 02 RLA ($02),Y
  • $0648: 4C 40 06 JMP $0640
  • $064B: 6C 2F 64 JMP ($642F)

下流

  • $0640: 20 4B 06 JSR $064B
JSR=ジャンプ・サブルーチン命令によって$064Bへ飛びます。
  • $064B: 6C 2F 64 JMP ($642F)
JMP=ジャンプ命令によってキー入力更新ルーチンの$EA1Fへ飛びます。
EA1Fが文字で表現できないので、($642F)に書かれているEA1Fを拾って間接ジャンプ。
このルーチンが終わると先程のJSRの次の$0643に戻ってきます。
この時、CPUのAレジスタYレジスタには前フレーム差分と現フレームのキーが入ってます。
  • $0643: 0A ASL
ASL命令によってAレジスタ2倍にされます。
キーはそれぞれ A[80],B[40],Sel[20],sT[10],↑[08],↓[04],←[02],→[01]
が合わさったものが入ります。
  • $0644: 10 02 BPL $0648
ASL2倍にされたAレジスタ[FF]を超えた場合、
つまりAが押されていた場合はキャリー・フラグが発生します。
ASL2倍にされたAレジスタ[80]以上だった場合、
つまりBが押されていた場合にはここでネガティブ・フラグが立ち、
Bが押されていなかった場合にはネガティブ・フラグクリアになっています。
BPL命令によってネガティブ・フラグクリアならば$0648相対ジャンプします。
つまりBが押されたフレームは$0646が実行され、そうでない場合は次の命令へ飛びます。
  • $0646: 33 02 RLA ($02),Y
RLAファミコンCPU6502の未定義命令なのでVirtuaNESなどでは実行されません。
($02)にはこの時$0602という値が入っているので、$0602+Yレジスタメモリ
キャリーつき左ローテートされ、Aレジスタメモリとの論理積になります。
左ローテート左シフトの後にキャリーフラグがあった場合に+1されます。
つまりAB右が新規に押された場合はキャリーフラグとネガティブフラグが立ち、
$0602+Y[C1]= $06C3メモリ2倍+1になり、
前フレームにAが押され、次フレームにAB左が押された場合はネガティブフラグのみで、
$0602+Y[C2]= $06C4 のメモリは単に2倍になります。
  • $0648: 4C 40 06 JMP $0640
JMP命令によって$0640に戻り1フレームの間に何度もループします。
Bが新規に押されたフレームのみ$0646が一度だけ実行されるので、
実質的に新規コードを書き込む速度は2フレームに1bitとなります。
2フレームに1バイトではなくとてもゆっくりです(;´Д`)

入力の終わりに$064Bを2倍に書き換えてループを解きます。
$06C3から書かれた新規コード5秒弱24バイト
29 02 20 4C 84 09 09 85 10 6E 00 20 A5 3C D0 FC 09 13 20 4C 84 20 7B 63
となっています。
エンディングはLevel9ダンジョンじゃないと読み込まれないのではないかと思います。
このコードの詳細はまだMasterjun氏が解説してないので略しますが、
  1. ゲームモードを読み込み状態に書き換え
  2. マップIDをLevel9のダンジョンに書き換え
  3. ディスクを読み込み
  4. 笛で止まった時間のタイマーが0になるまでループすることで読み込み終了を検知し
  5. ゲームモードをイベント状態に書き換えてイベントを暴走させ
  6. メインループのNMI待ちの箇所へジャンプしてエンディングを待つ

多分こんな感じだと思います。