(SFC) DQ5 TASの補足 [任意コード実行]
挙動自体については動画内でほぼ全て説明しきっているので、任意コードの詳細等も記載はしますが余談がメインです。
冗長ですので、スクロールしながら興味のある箇所だけご覧ください。
お品書き
- データ関係
- オープニングカットチャートの可能性
- 今回使用の任意コード
- Lsnes Edit movieの見方
- 主人公名前文字コード表(2020/5/2追記)
データ関係
SRAM(セーブデータ)
まず、初回起動時にSRAMが00で埋められます。なので、SFCDQ1,2のような悪さは出来ません。当たり前だよなあ?
以下、全て冒険の書1での説明。
冒険の書1の頭は$0012[冒険の書Lv表示]で、末尾がチェックサムです。
今回のTASで関わってくるのは、$002B[所持金下位]と$002F[PT-1]のみです。この2つの差し引きが±0になるよう、所持金の管理を行います。
今回のサブフレームリセット箇所は、$002F[PT-1]書き込み後、$0030書き込み前です。
データ消去
チェックサム不一致、$0012[冒険の書Lv表示]=0、の少なくとも2種類があります。また、手動・自動に関わらずデータ消去時は冒険の書内の全ての値が00に書き換わります。
WRAM <--> SRAM
MVNによる転送命令によりWRAM7E:2000($2000) -> SRAM70:0012($0012)へコピー、以降チェックサム領域までそのまま一連の流れでコピーされます。
ですので、SRAMとWRAMの番地の対応は±0x12をすれば概ねわかります。
SFCDQ1,2は1byteあたり数サブフレーム(rlowの値)を要していたのが、DQ5の場合は値が1進むごとに1byte書き込んでいました。おそらく上記画像どおりMVNで即書き込む形になっているおかげと思われます。疎いので説明に誤りがあったらすみません。
なので、人力リセットを狙うにはなかなか厳しいのではないかなと思います。(他のゲームがどのようなセーブ形式なのかは全く存じておりませんが。)もしくは猶予bytesを広く取る必要があるかもしれません。
オープニングカットチャートの可能性
サブフレームリセットによるオープニングカットは、可能です。新規データ作成時、チェックサムが0x0000になるようなキャラクター名を付け, $0045[冒険再開場所]が0になるようにそれ以前でサブフレリセをします。ですが…。
キャラクターデータはリセット時の書き込み範囲より後ろのため、全て00になってしまいます。
$0012[冒険の書Lv表示]は、初回開始時こそ1になっていますが、以降の教会セーブでは主人公Lvを書き込むためにもれなく0となり先述のデータ消去条件に当てはまってしまうため、セーブ後の起動時・リセット時はデータが消去されてしまいます。
となると、これ以降はセーブをせずにENDまで突っ走りたいところです。
起動時は中身が棺桶状態なので移動できませんが目の前の神父で蘇生できます。蘇生後動けるようになるので、ヘンリー誘拐イベントを進めて外に出ると…。
本来オープニングの下船後に起こる強制戦闘が始まり、その後パパスに連れられて移動が始まります。そして海岸線に引っかかり、メニューも開けないのでうみでつみです。なお、戦闘中のキメラの翼も効果がありませんし、敵を倒してもLvは上がりません。
また、どういうわけか世界地図を見ることでもスライム戦が始まります。
戦闘終了後に移動が始まるので、強制イベント突破か!?と思うのですが…。
外に出ると再び強制戦闘→強制移動で詰みます。ちなみにパパスが2人に増えるので無駄に強いですが、どのみち移動で詰むので意味はないです。
というわけで、オープニングカットへの道のりは険しいのでした。突破方法はあるのでしょうか。
今回使用の任意コード
言語は65C816(65816)です。ニーモック | オペコード | バイナリ | 摘要 |
48730f | |||
REP #$20 | C2 20 | 11000010 00100000 | ;A:16bit |
LDA #$BED1 | A9 D1 BE | 10101001 11010001 10111110 | ;A=0xBED1 |
WAI | CB | 11001011 | ;Wait for Interrupt |
48731f | |||
BRA F8 | 80 F8 | 10000000 11111000 | ;-8へ分岐 |
STA $07,s | 83 07 | 10000011 00000111 | ;S+0x07=A(=0xBE01) |
LDA #$02EC | A9 EC 02 | 10101001 11101100 00000010 | ;A=0x02EC |
WAI | CB | 11001011 | ;Wait for Interrupt |
48732f | |||
BRA F8 | 80 F8 | 10000000 11111000 | ;-8へ分岐 |
STA $7E21EA | 8F EA 21 7E | 10001111 11101010 00100001 01111110 | ;$7E21EA=A(=0x02EC) |
TCS/TAS | 1B | 00011011 | ;Stack pointer=A(=$02EC) |
WAI | CB | 11001011 | ;Wait for Interrupt |
48733f | |||
BRA F8 | 80 F8 | 10000000 11111000 | ;-8へ分岐 |
SEP #$20 | E2 20 | 11100010 00100000 | ;A:8bit |
STA $210C | 8D 0C 21 | 10001101 00001100 00100001 | ;$210C=A(=0xEC) |
WAI | CB | 11001011 | ;Wait for Interrupt |
48734f | |||
BRA F8 | 80 F8 | 10000000 11111000 | ;-8へ分岐 |
JML $02:FD07 | 5C 07 FD 02 | 01011100 00000111 11111101 00000010 | ;Jump to $02:FD07 |
4つのコントローラで2バイトずつ、1フレームあたり計8バイト分のコードを入力できます。
WAI / BRA F8
塊ごとに共通しているこれらは、実行プログラムをコントローラ領域で循環させるための魔法の言葉のようなものです。WAIで割り込み待ち、次のフレームに進みます。
BRAはフラグに関わらず常に分岐。今回、BRA F8は常に$421E,Fで実行させているため、F8つまり-8、$4218(コントローラ1つ目)へ実行アドレスが移動するようになっています。
REP #$20 / SEP #$20
A(アキュムレータ)の扱いを変更します。REP#$20は16bitに、SEP#$20は8bitの扱いです。LDA #$BED1 / LDA #$02EC
Aに値をロードします。STA $07,s
7つ先のスタックにAの値を書き込みます。命令時点のスタックポインタは$0989なので、7つ先の$0990へ、事前にLDAした0xBED1を書き込みます。
で、このスタックへの書き込みはなんぞやというお話ですが。
DQ5には(おそらく毎フレーム?)、WRAM上に命令を展開してそれを実行するルーチンが存在します。
このルーチンが何をしているのかは正直わからないのですが、$2bbf0c rtsの戻り先がおかしくなっていると、任意コードでエンディングムービーに飛ぼうとしてもうまく行かなかったために、修正をかけているわけです。
TCS/TAS
Transfer Accumulator to Stack pointer、つまりAの値をスタックポインタへ転送します。事前にLDAした0x02ECを転送し、スタックポインタ(スタックの現在位置)を$02ECへ変更しています。これは先程のWRAM展開ルーチンとも関わりがあります。
どうやらこのルーチンは特殊な扱いのようで、安定して実行するためにスタックポインタの位置を毎回$098Dに変更した上で実行しています。おそらくこの近辺のスタックは、このWRAM実行ルーチン周辺でしか使用していないのではないかと推測します。
ですが、STA $07,sの項目で説明したとおり、任意コード実行開始時点のスタックポインタが$0989、つまり本来はWRAM展開ルーチンでしか使わないはずのスタックへ既に侵入しているわけです。
ですので、通常使用するスタックの範囲へと戻してやるわけです。
…長々説明しましたが、実際問題としては「よくわからないけどここでスタックポインタを$02ECにしたらうまくエンディングに飛べた」レベルです。ごめんなさい。ENDに行かない原因を一つ一つ潰していった結果です。
STA $7E21EA
$7E21EAはイベントフラグです。エンディングムービーへ飛ぶには0x80以上が必須で、今回はスタックポインタ変更で使用した0xECが偶然条件を満たす形だったので流用しています。なお、Aを16bitのまま処理しているので隣の$7E21EBが0x02ECの0x02に汚染されていますが、影響が出なかったので放置しています。ちなみに、このイベントフラグを端折るとどうなるかと言いますと…。
こうなります。任意コードで初めてムービーに飛ばした時は、思わず笑ってしまいました。そうくるか。まさかムービーを共有していたとは。
STA $210C
$210CはIOポートのバックグラウンド設定領域です。$7E21EAのイベントフラグも設定し、いよいよENDへ、と思ったら思わぬ壁がそびえ立ちました。
が、 になってしまう。
いや、なんだよこれまじで…となりました。
DQ5はグラフィックにも圧縮がかかっているので、解凍処理がおかしいのか?などと色々な方向で探っていたのですが全く埒が明かず。
2週間ほど悩み続けてからno$snsというエミュレータを発見し、世界が変わりました。
これだ!!!!!ということで無事解決したのでした。
bit1,2が0、bit3が1なら作動しそうだったので、スタックポインタのための0x02EC(SEP #$20でAを8bitにしているのでここでは0xEC)が偶然使えました。(0xECはbit表記で11101100)
JML $02:FD07
エンディングムービーへジャンプする命令です。$7E21EAのイベントフラグがきちんと設定されていれば、ゴールドオーブは落ちてきません。やったぜ。Lsnes Edit movieの見方
色々な要因が重なり、任意コードで何を入力しているのかが読み取りにくくなっています。
- Lsnesのキー受付タイミング
- 任意コードとして反映される順番
- WAIとBRA F8とコントローラの順番とフレームの関係
Lsnesのキー受付タイミング
上記画像で、背景色が赤と青とで分かれていますが、青の部分つまりBYsS↑キーは表示よりも1フレーム遅く入力されます。(DQ1,2,5の仕様なのか、他のゲームも共通なのかは未調査。)雑に画像を直すと、実際は以下のようなフレームで入力がされています。
任意コードとして反映される順番
キー入力が命令に変換される順番は、エディターの左から右へ順番に…ではありません。このようにジグザグな感じで命令が反映されます。
例えば48730fの場合は、[11000010:C2][00100000:20][10101001:A9][11010001:D1][10111110:BE][11001011:CB]となるわけです。
WAIとBRA F8とコントローラの順番とフレームの関係
だいぶ上の方で解説した任意コード一覧の表だと、各フレームの先頭にBRA F8を記載していますが、上記画像では一番右、7番8番がBRA F8に相当します。これは、6番のWAI命令で次のフレームに進めてキー入力内容を更新した後、フレームの最初でBRA F8によりコントローラ1-1へ戻るところから始まっているためです。
フレーム単位で見ると、コントローラ2-2(BRA F8) → 1-1 → 2-1 → 1-2(WAI)の順になります。
最後に
もう一度未加工のエディター画像を表示してお別れしましょう。なんとなく分かりましたでしょうか。
少しでも理解の助けになれば幸いです。それでは。
主人公名前文字コード表
(2020/5/2追記)それでは、と言いつつの追記です。
なおゲーム中テキストは別に管理されているので、この文字コードでは解析できません。
主人公名前文字コード表 | ||||||||||
あ 10 | い 11 | う 12 | え 13 | お 14 | | ア 42 | イ 43 | ウ 44 | エ 45 | オ 46 |
か 15 | き 16 | く 17 | け 18 | こ 19 | カ 47 | キ 48 | ク 49 | ケ 4a | コ 4b | |
さ 1a | し 1b | す 1c | せ 1d | そ 1e | サ 4c | シ 4d | ス 4e | セ 4f | ソ 50 | |
た 1f | ち 20 | つ 21 | て 22 | と 23 | タ 51 | チ 52 | ツ 53 | テ 54 | ト 55 | |
な 24 | に 25 | ぬ 26 | ね 27 | の 28 | ナ 56 | ニ 57 | ヌ 58 | ネ 59 | ノ 5a | |
は 29 | ひ 2a | ふ 2b | へ 2c | ほ 2d | ハ 5b | ヒ 5c | フ 5d | ヘ 5e | ホ 5f | |
ま 2e | み 2f | む 30 | め 31 | も 32 | マ 60 | ミ 61 | ム 62 | メ 63 | モ 64 | |
や 33 | ゆ 34 | よ 35 | ヤ 65 | ユ 66 | ヨ 67 | |||||
ら 36 | り 37 | る 38 | れ 39 | ろ 3a | ラ 68 | リ 69 | ル 6a | レ 6b | ロ 6c | |
わ 3b | を 3c | ん 3d | ワ 6d | ヲ 6e | ン 6f | |||||
っ 3e | ゃ 3f | ゅ 40 | ょ 41 | ッ 70 | ャ 71 | ュ 72 | ョ 73 | |||
nil 01 | ※1 | ー 78 | ゜ 83 | ゛ 84 | ※2 |
※1
主人公の名前領域は8バイト(SRAM$13~1A)それに満たない名前の場合、残りは01で埋まります。
例:[あいうえ] → [10 11 12 13 01 01 01 01]
※2
半濁点゜濁点゛は、適応文字の直前に格納されます。例:[ぱばパバ] → [83 29 84 29 83 5b 84 5b]