• ドラクエ4 TAS の サブフレームリセットと任意コード実行の補足

    2019-02-13 19:003

    ※注意:TASの操作は大変危険なものですので決して真似しないでください。


     今回のTASは、TAS制作統合エミュレータであるBizhawkの開発版に実装されたSubNESHawkによってファミコンでもサブフレームリセットサブフレームインプットが可能になったのを知って急遽制作しました。
     ざっくりとチャートを説明すると、0人PTを作って並び替えをした時にバグってSRAMをプログラムとして呼び出すのでそれを利用して任意コード実行しよう、ということです。

     この画像はBizhawkでコアにSubNESHawkにしている時のTAStudioのタイムラインです。

     赤いラインは通常はラグフレームとして扱われますが、SubNESHawkではVBlankが発生した時に挿入され、プログラムがコントローラの入力処理を検知するたびに緑色のサブフレームインプットが発生します。
     ドラクエ4ではカーソル移動処理時に1フレームに2回×2コントローラの入力受付があり、サブ1で決定・キャンセルの判定をしサブ3でカーソル移動の判定をしています。
     そのおかげで通常のTASと違い、カーソルが毎フレーム連続移動出来るので名前入力時などのカーソル移動がとても速くなってます。
     左のカラムにReset Cycleと書かれているのはリセット入力時に待機するPPU Cycleを数値で入力するもので、『けこけこ』[13 14 13 14]が冒険の書3に書き込まれる瞬間のトレースログを見ると実際にPPU-Cy:30400を越えたところでリセット処理が発生していることが分かります。


     名前を入力して「おわり」を選択した瞬間に冒険の書に名前が書き込まれますが、メッセージ速度を決定するまではチェックサムが0000のままなので、その前にリセットをすると冒険の書が消えるメッセージを簡単に見ることが出来ることができます。

     動画3秒の地点でのエラー処理中にサブフレームリセットをすることで冒険の書3の名前の手前までを4Bで埋め尽くしています。
     これをしない場合は0人PTでの並び替え時に$68F1が実行された時に 00:BRK 命令が発生して目的地にたどり着けないので無害な命令である 4B:ALR #immで回避しています。
     この命令は未定義命令ですが A = A AND immediate, A = shift right A という処理が実行されるので通過し終わった頃にはCPUのAレジスタ00になってます。

     『けこけこ』[13 14 13 14]という名前は 13:ASO ($14),Y というプラグラムを2回実行するためにつけてます。

    000102030405060708090A0B0C0D0E0F
    00
    10
    20
    30
    40
    50
    60
    70Ex
     ASOという未定義命令は memory = shift left memory, A = A OR memory という処理が実行されるので、単に左シフトとして利用します。($14),Yの部分は$14$15の2バイト+Yの間接アドレス先のメモリを書き換えるというものです。
     未定義命令はエミュレータによっては無視されたりエラーを起こしたりしますが、現在のTAS環境においては未定義命令も実機と同等の動作をするようなので遠慮なく使います。
     $14はコントローラ1、$15はコントローラ2の入力が反映するメモリなので、好きなアドレスのメモリ値を2回左シフトすることになります。純粋な左シフトは間接アドレスが使えないのでこちらを採用しています。『け』は『こ』に近いので便利です。
     ちなみに一番最初に『けこけこ』の処理をする理由は冒険の書が多いほうが処理が多くなるからで、冒険の書が埋まったときのほうがウィンドウの処理自体は速いですが総合的には遅くなります。

     続いて冒険の書1に『にへ゜ 』[20 27 6A 00]という名前をつけてスタートしています。この名前は 20:JSR $6A27という命令で、実行されると冒険の書3の所持金のアドレスにサブルーチンとしてジャンプすると意味になります。
     純粋なジャンプ命令である4Cや相対ジャンプでループするための負数(0x80以上)は名前から得られませんが、エニックスが『゜』を6Aにしてくれたので冒険の書3の中でループを作る事が可能になっています。
     制作直後の冒険の書にはPTの加入情報は0なのですが、第一章が始まる時にライアンの加入処理が発生するので、0人PTを作るためにまず教会でセーブした状態を作ります。

     動画53秒地点で冒険の書1を2にコピーした直後に冒険の書2を消して、再び冒険の書1を2にコピーをしていますが、この作業でデータ2の所持金が[32 00 00]=50Gだったものが[4B 4B 4B]=4,934,475Gになります。



     $65DF・$65E0が冒険の書2のCRCっぽいチェックサムとなっていますが、16bitのCRCを調整するのは簡単ではありません……

     と思いきや、最終的な計算結果の比較処理にあるべき1行が抜けてる為に8bit分しか判定に貢献していません(;´Д`)

     こんな感じのFCEUX用のLuaスクリプトを書いて画面に表示して、HEXエディタで4Bを書き込んでいって偶然にも$65E0CRCの計算結果が一致するものを手動で探してました。BizhawkよりもFCUEXの方がHEXエディタが使いやすかったのでチャート構築や解析はこっちでしてました。
     このように4Bで埋める範囲を調整することでCRC調整出来ますが、$66A8から$66AFまでのライアンの道具欄も4Bで埋めてしまうと天空の兜(4B)だらけになって売り買いが出来なくなります(´・ω・`)
     $673C00にしてるのは、そこも4Bで埋めた場合は道具欄が埋まるまでにCRCが一致しなかったからです。冒険の書のメッセージ速度はデータの一番後ろにあるので、+1するごとに+0x10*nをXORしたCRCになるので5bit分だけ一致していれば調整可能でしたが、今回はなんとかその手間を掛けずにCRCを一致させることが出来ました。

     さて、名前のアドレスと近い位置にある所持金を増やすことに成功したので動画1分過ぎから冒険の書2で金額調整に入ります。
     ここで欲しい値は[D3 14]で、これはD3:DCM ($14),Y という命令になり$14-$15のコントローラ情報を利用して特定のアドレスの値を1つ減らすことが可能になります。
     DCMという命令も未定義命令で memory = memory - 1, compare A and memory という処理がされるので、比較部分以外はほぼDEC命令と同じですがDECは間接アドレスを扱うものがないのでこちらを使っています。

     しかし売り買いだけでその金額にしようとすると13944Gも買い物をしないといけないので、買っては捨てて買っては捨ててで時間がエラい掛かります。 そこで先程用意した冒険の書3の『けこけこ』[13 14 13 14]を用いた任意コード実行で金額を変更してしまおうという魂胆です。


     先に所持金を[D3 45 4B]に調整します、そのために必要な購入額は1400Gと分かりやすいので調整も楽にできます。 $67380x45を2回左シフトすると、0x45*4=0x114となり、8bit分の0x14が残ります。
     このコードを実行するために0人PTを作ります。上図の$674Aにある[86 00 00 00]がライアン・無人・無人・無人というパーティー構成を意味しているのでこのライアン00にしてしまえば良いのですが、4Bをつっこんでも存在しないキャラ扱いになって0人PTになるので冒険の書1を削除して$645Aライアン[86]まで4Bで埋めて、冒険の書2を1へコピーしてCRCを再度一致させます。


     このとき$645Aまでではなく、PTの2人目以降も4Bで埋めた場合は神父に呼ばれる名前が改行込みのちょっと長い空白になり、人数が多いほどロードも長くなるので今回のTASでは良い感じに調整できてますが駄目な場合は$63A5のライアンの生存フラグまで4Bで埋まってもCRCが一致しない可能性がありました。 なお名前も上書きされて『ソソソソ』[4B 4B 4B 4B]になってますが問題ありません。

     冒険の書1に0人PTを作れたので、早速1回目の任意コードを実行していきます。 0人だとバグが発生するのはRPGには良くあることで、人数分の処理を行う際に0からスタートすると判定前に-1して255になってしまうのが主な原因だと思われます。
     動画時間1分39秒で並び替えを実行した後の入力判定で1Pは上下スタート、2Pは下左セレクトBAを押すことで$14が0x6738となり、ソソソソ†が32人分ほど表示されたタイミングでバグが発生して冒険の書3の$68F1が呼び出され、4B4Bゾーンを通り抜けて$6A2D『けこけこ』[13 14 13 14]が実行されて$6738が晴れて4514になるわけです。


     ちなみ余談ですが上図でいうところの$6775$6777はそれぞれ船座標XYと気球座標XYで、教会でロードをすると街に対応した位置に配置されてて街を出る時にフラグチェックをして0000に戻されたりします。 船・気球フラグは冒険の書2でいうところの$686Eでこの画像よりも大分下の方にあり、船がある時は+01・気球がある時は+02という感じになってます。
     つまり4Bだと船も気球も手に入った状態になるということなので、一章が始まって教会する前にリセットした冒険の書を空っぽのデータにコピーし終わる前にリセットを押すと1/256の確率でCRCが一致し、上書き位置が悪くなければロード後に一章が全滅BGMと变化状態で始まり、教会で再開かキメラの翼を使うと気球と船が外に配置されるようになります。
     失敗例としてはリセットが早すぎて名前の2バイト前にある章番号が4Bになったせいで、4B章が始まってしまうケースです。 このデータでは主人公不在のバグった5章スタートで、並び替えバグをしても$62F1が実行されないので多分役に立ちません。
     ただし、成功例でもデータの終端にあるメッセージ速度が4Bになってしまい、48~4Fの範囲外に戻すことが出来なくなって戦闘が凄く遅くなります(´・ω・`)
     名前によって成功率が変わると思いますが、コピー決定の2~3フレーム後くらいに適当にリセットを押していれば実機でも現実的な試行回数で一章から気球に乗れるようになると思います。
    ※追記 戦闘中にセレクトを押して速度を3に変更するとメッセージが速くなるそうです!!

     話を戻すと、動画時間1分40秒で冒険の書2の所持金が[D3 45 4B]から[D3 14 4B]になったことで冒険の書2のCRCが一致しなくなり、データ消去処理が発生するので前述と同様に消去中のサブフレームリセットと冒険の書1から2へ上書き中のサブフレームリセットで冒険の書2を復活させます。

     この先は冒険の書1しか起動することはないので、冒険の書2は所持金と名前さえ無事ならどの範囲を使って調整しても大丈夫です。 じゃあ何のために冒険の書2を復活させたかというと、冒険の書3にコピーするためです。

     冒険の書3はコピーしたあとに00を埋めるために所持金手前まで4Bで埋めます。 消えたデータ扱いでいいのでCRCは気にしません。

     これによって欲しいプログラムの第一形態が完成しました。以下プログラムの中身↓
      $68F1: 4B 4B     ALR #$4B    Aレジスタと#$4Bの論理積を取った後に右シフト
      $68F3: 4B 4B     ALR #$4B  
      ---------------------------
      $6A27: D3 14     DCM ($14),Y  $14の16bitにYレジスタを足した間接アドレス先を1減らす
      $6A29: 4B 4B     ALR #$4B  
      $6A2B: 4B 00     ALR #$00    Aレジスタは0になる
      $6A2D: 20 27 6A  JSR $6A27   $6A27へサブルーチンとしてジャンプする
     $6A27から$6A2Dの間で無限ループして好きなアドレスを好きな値に書き換えることが可能になりましたが、無限ループ中に$14を変更することは出来ないので狙った値になるタイミングでリセットをかける必要があります。動画1:52では$6A260xA8に、1:59で$6A220xECに、2:07で$6A230xC8に、最後に2:13で$6A210x20にしています。
     A8:TAYはAレジスタをYレジスタにコピーする1バイトの命令で、3バイト命令を通った後にプログラム位置がずれるのを直すのとYレジスタを0にするために設置しています。
     [20 EC C8]JSR $C8EC で、コントローラ情報を取得して$14と$15を更新するサブルーチンになってます。
     これで晴れてループ中にコントローラ情報を更新できるようになったかと思いきや、まだループ外なので最後にちょっとした工夫をします。

     現在のループは$6A27へジャンプするものなので、$6A2Eの27を減らして$6A21へジャンプするようにするようにすれば良いのですが、1つずつ減らす間にループ位置が一つずつずれるので正しくループが出来なくなる可能性がありますが、そこはちゃんと考えての配置にしてます。 以下は一連の流れ。
      $6A21: 20 EC C8  JSR $C8EC   コントローラ情報を取得し$14-$15を更新するサブルーチン
      $6A24: 4B 4B     ALR #$4B     $C8ECの後、0x10になっているAレジスタが0x00になる
      $6A26: A8      TAY        YレジスタにAレジスタがコピーされる

      $6A27: D3 14     DCM ($14),Y   ($14)+Y=$6A2Eを1減らす

      $6A29: 4B 4B     ALR #$4B  
      $6A2B: 4B 00     ALR #$00  
      $6A2D: 20 26 6A  JSR $6A26    $6A26へジャンプ
      -----
      $6A26: A8      TAY        YレジスタにAレジスタがコピーされる
      $6A27: D3 14     DCM ($14),Y   ($14)+Y=$6A2Eを1減らす

      $6A29: 4B 4B     ALR #$4B  
      $6A2B: 4B 00     ALR #$00  
      $6A2D: 20 25 6A  JSR $6A25    $6A25へジャンプ
      -----
      $6A25: 4B A8    ALR #$A8
      $6A27: D3 14     DCM ($14),Y   ($14)+Y=$6A2Eを1減らす

      $6A29: 4B 4B     ALR #$4B  
      $6A2B: 4B 00     ALR #$00  
      $6A2D: 20 24 6A  JSR $6A24    $6A24へジャンプ
      -----
      $6A24: 4B 4B    ALR #$4B
      $6A26: A8      TAY        YレジスタにAレジスタがコピーされる
      $6A27: D3 14     DCM ($14),Y   ($14)+Y=$6A2Eを1減らす

      $6A29: 4B 4B     ALR #$4B  
      $6A2B: 4B 00     ALR #$00  
      $6A2D: 20 23 6A  JSR $6A23    $6A23へジャンプ
      -----
      $6A23: C8      INY         Yレジスタが+1される
      $6A24: 4B 4B    ALR #$4B    Aレジスタが0になる
      $6A26: A8      TAY        YレジスタにAレジスタがコピーされる
      $6A27: D3 14     DCM ($14),Y   ($14)+Y=$6A2Eを1減らす

      $6A29: 4B 4B     ALR #$4B  
      $6A2B: 4B 00     ALR #$00  
      $6A2D: 20 22 6A  JSR $6A22    $6A22へジャンプ
      -----
      $6A22: EC C8 4B  CPX $4BC8   $4BC8とXレジスタを比較
      $6A25: 4B A8    ALR #$A8
      $6A27: D3 14     DCM ($14),Y   ($14)+Y=$6A2Eを1減らす

      $6A29: 4B 4B     ALR #$4B  
      $6A2B: 4B 00     ALR #$00  
      $6A2D: 20 21 6A  JSR $6A23    $6A21へジャンプ
      -----
      $6A21: 20 EC C8  JSR $C8EC   コントローラ情報を取得し$14-$15を更新するサブルーチン
      $6A24: 4B 4B     ALR #$4B     $C8ECの後、0x10になっているAレジスタが0x00になる
      $6A26: A8      TAY        YレジスタにAレジスタがコピーされる
      $6A27: D3 14     DCM ($14),Y   ($14)+Y=$6A2Eを1減らす
      $6A29: 4B 4B     ALR #$4B  
      $6A2B: 4B 00     ALR #$00  
      $6A2D: 20 21 6A  JSR $6A21    $6A21へジャンプ
      -----
     このようにループ変更途中にC8:INYが実行されてしまうのでA8:TAYでYレジスタを0に戻してやっています。 また、JSR $C8EC のサブルーチンから戻って来た後はAレジスタが0x10になるので ALR #$4B によってAレジスタが0になるようにしてます。
     Yレジスタがずれたままだと$6A2Eではなく$6A2Fを減らすことになってループ先が$6923になってしまいます。 それ自体は4Bゾーンを通った長めのループになるだけなので問題なさそうですが、一回のループが長いとVBlankが発生した時にループが途切れる可能性が増えます。

     これでようやく任意コード実行の最終形態とも呼べるであろうトータル・コントロールが可能になったので、メモリをモリモリ減らしていきましょう。
     動画では10フレームしか表示されなかった部分を細かいサブフレームインプットまで表示するとこんな感じになります。
     $14の数値に対応したアドレスが1ずつ減らされていくのが見てわかると思います。$6921から書いてるコードはエンディングへ飛ぶためのもので、書き終わった後に$6A2Fの値を1つ減らしてジャンプ先を$6A21から$6921に変更して最後の操作を終了しています。
     その直前にはフラグ操作も行っていて、0人PT時の動けないフラグ$058F[10][0F]に変更し、エンディング途中でメッセージが発生すると入力が必要になるので、コーリンゲンで止まらないように$627E$62830xFFにしてます。しかし、実は$62A1[20]のエンディング待機フラグあればその2つは必要ないので1フレーム未満の差ですが本当はそっちの方が早いですが、通常のエンディングを見るよりも特殊なエンディングの方が価値が高いと思ったのでこちらを採用。

    エンディングに飛ぶプログラムについては以下の通り
      $6921: 49 1A     EOR #$1A    Aレジスタを排他的論理和で0x1Aにする
      $6923: 20 91 FF   JSR $FF91    $FF91のサブルーチンでバンクを0x1Aに切り替える
      $6926: 20 FC 9B   JSR $9BFC   エンディング処理の途中である$9BFCにジャンプ
     Aレジスタを変更する命令は49:EOR以外にも沢山ありますが、4Bから近いので採用。
     バンク切り替えというものはROMが読み込んでる範囲を変更するものです。 CDで言えばディスクチェンジを一瞬でしてるようなものだと思うと合ってるかもしれませんし間違ってるかもしれません。 グラフィックが読み込まれる範囲を動的に変更したりも出来るので、バンクが間違った状態だとグラフィックも崩れたりします。
     DQ4の場合は$8000~$BFFFと$C000~$FFFFの部分にプログラムが書かれてますが、前者は細かく切り替えられながらプログラムを実行しています。 その中でエンディング処理が書かれているのは 1A:9BFC でそのままだとバンクが違うのでJSR $9BFCを実行してもエンディングへは飛べないのでバンク切り替えを行ってからジャンプしています。


     最後に、スタックが溢れてないか気になってる人がいると思うのでGIFを貼っておきます。












     
     エンディング中はイベント処理を実行し続けてサブルーチンから戻ってこないので、
    スタックは溢れまくりましたがエンディングが見れたので問題ありません。

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

    2015-06-06 02:282

    英語による解説文は 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待ちの箇所へジャンプしてエンディングを待つ

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


  • 【TAS】 スーパーマリオワールド "game end glitch" 41秒98 について。

    2014-05-20 15:2616
    またしてもMasterjun氏によってクレジット呼び出しバグによるクリアTASが制作されたので、解説を少しだけ書こうと思います。足りないところは http://tasvideos.org/4315S.html を読むべし。
    ※kentora12氏は全く無関係なのでコメントする際には注意しましょう。

    ・任意コード実行まで

    炎で亀を燃やして発生したコインをヨッシーの舌で掴んでいる最中にマリオがそのコインを取ると、舌の上に無を取得し、そこでブルが画面内に現れると舌の上にワープしてそのまま食べてしまいます。
    マリオの状態や食べるブルの種類によってストックアイテムに様々な変化をもたらします。
    今回利用されたのは、ゴール玉をストックするのに使われたブルとは別の種類のブルです。

    sm21390603 で出現したジュゲムの雲ストックについて Masterjun氏 が調査されてた時、"任意コード実行" = ACE(Arbitrary Code Execution) の可能性にたどり着いてしまったようです。
    ちび状態でダッシュするブルを食べると、ジュゲムの雲ストックした後にマリオの状態が異常になり、本来は呼び出されることのないルーチンによってプログラムは$014A13へジャンプします。
    これらは、あんた氏が以前に予見していたことでもあります。
    そのジャンプ先は Open Bus とか SNES Bus とか呼ばれ不安定な部分なのでフリーズ待ったなしですが、なんとか$4218のコントローラレジスタ部へたどり着くことが出来たとのこと。

    ・任意コード実行

    コードは4つのコントローラで5フレームの間実行されています。
    これらのコードは65816というCPUのアセンブリ言語で書かれています。
    1. E0 0A FB 64 10 CB 00 00
    2. A9 18 D8 64 10 CB 80 F8
    3. 87 3D 0B 64 10 CB 80 F8
    4. A9 08 AB 64 10 CB 80 F8
    5. 8D C6 13 20 72 80 80 F8
    上記の操作によって、$0100(ゲームモード)を 0x18(Load Credits/Cutscene?) にセットし、$13C6(カットシーン)を 0x08(エンディング) にセットした後、ゲームを正常にスタートさせています。

    まずは共通して使用されているコードについて解説

     64 10 = STZ: $10
    これはアドレス$10のメモリの値に0をセットするという意味になります。
    このアドレスは1フレーム間に行われなければいけない処理が完了した時に0がセットされ、
    0以外の場合は無限ループする処理が発生するのでそれを回避するために書き換えています。

     CB = WAI
    これは1/60毎に発生するNMIという割り込み処理を待つという意味のコードです。
    これによって複数フレームのコードを実行できるようにしています。

     80 F8 = BRA: -8
    これは無条件分岐命令で、相対アドレスの F8(-8)先へジャンプするという意味です。
    これによってコントローラ部の命令を何度も実行できるようにしています。

    メモリの書き換え命令

     A9 18 = LDA: #$18
    これはCPUのAレジスタに0x18という数値を読み込むという意味です。

     87 3D = STA: [$3D]=$000100
    これはアドレス$3Dから3バイトのメモリ値をアドレスとして読み込んで、その先の$000100にAレジスタの値(0x18)を書き込むという意味です。
    アドレスを直接指定しようとすると3バイトも使ってしまうので、たまたま 00 01 00 と並んでいた$3Dを利用してコードを2バイトに圧縮してるようです。

     A9 08 = LDA: #$08
    同様にAレジスタに0x08を読み込み、

     8D C6 13 = STA: $13C6
    $13C6にAレジスタの0x08を書き込んでいます。

    その他の命令
     E0 0A = CPX: #$0A <- Xから0x0Aを減算して結果をN・Z・Cフラグへ返す
     0A = ASL <- Aレジスタを左にシフト($4218からじゃなくて$4219かもしれない?)
     FB = XCE <- CフラグとEフラグの値を入れ替え
     D8 = CLD <- Dフラグをクリア(D=0)
     0B = PHD <- DをSに格納
     AB = PLB <- DBをSから取出
     20 72 80 = JMP: $8072 <- バンク内のサブルーチン呼び出し
    一連の操作で正常にゲームのメインプログラムへジャンプ出来るように、各種フラグやDB(データバンクレジスタ)を調整してるのだと思います。

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