お手すきの際には、SethBling 氏のチャートを紹介していただけると幸いです。
https://docs.google.com/document/d/1moRP86CDTViNqOZJSAPyRFmGOJgUT7-DRBHIrPfHv8Y/edit
こちらの台本も公開いただいて構いません。
https://gist.github.com/absindx/8753caba78fe8ee7b4af9a27162305c3
もし終了時に私の紹介をいただける場合は、下記URLをお願いいたします。(基本的には貼らなくて大丈夫です)
https://absindx.github.io/ZpIndIndY/
皆様おはようございます、スーパーマリオコレクション+スーパーマリオワールドの解説をさせていただくabsindxと申します。
普段ファミコンのプログラミングや解析をしており、SMWへもちょっとの知見があるだけですが、枠が空いていたので申し込ませていただきました。
走者ではないため至らない点も多々あると思いますが、20分弱お付き合いいただければと思います。
スーパーファミコンの言わずと知れたスーパーマリオワールドやスーパーマリオコレクションですが、
海外限定で、コレクションにSMWが追加されたものが1994年に発売されています。
ファミコンから移植されたSMB1, SMB2(The Lost Levels), USA, SMB3 に加えてスーパーファミコンのSMWの計5本が1つにまとまって超お得!
今回は「All Five Games w/ACE」ということでこの5本すべてのゲームをクリアするのですが、EST 18分、「w/ACE」ということで不穏な雰囲気ですね。
「Arbitrary Code Execution」というのはゲームのバグを突いて、ゲーム内には存在しないプログラムを作り出して実行する行為です。
(オフラインのゲーム機だから精々ゲームや本体が壊れるだけだけど、PCでやるとシステムを破壊したり、情報を盗み出すクラッキングの類になってしまうので気を付けてね!)
この部分を掘り下げると「プログラムはどうやって動いているのか?」という重い話になってしまうので、朝方という時間もありますのでゆるふわでいきたいですね。
大まかなチャートは走者の SethBling 氏が公開されておりますので、65816のアセンブリが読める人はこちらを見たほうが早いです。
All Five Games (SMAS+SMW) ACE
一部最適化や安定化のためのマイナーチェンジがあり、こちらのドキュメント通りとはならない可能性があるためご了承ください。
SethBling 氏は以前からSMWの任意コード実行を利用して Credits Warpを人力で最初に成功 させたり、
Flappy Birdを作成したりとよくニュースになっている方です。
IsoFrieze 氏は様々な活動名義があるのですが、 Dotsarecool 氏, Retro Game Mechanics Explained の人と言うとわかる方にはわかるでしょうか。
(上記のチャンネルで昨日のTriforce%の解説動画を上げていらっしゃいます!早速見ましたがかなり興味深かったです。)
両名ともにその道のすごい方です。
Super Mario All-Stars - speedrun.com
WRはSethBling氏の8:58。
時間の都合上、かなり慌ただしい解説になることが想定されます。
リハーサルでは一人分の説明で精一杯だったため、 SethBling を中心に見ていく予定です。
SFCのプログラムはカートリッジ内のROMから機械語を読み取り、それを元に実行する処理を決めています。
この処理をどんどん繰り返し進めていくことで一連のアルゴリズムを形成できるのですが、SFCのCPUは読み取り先にROMやRAMを区別していません。
任意のデータ列をとることができるRAM領域にプログラムカウンタがジャンプできるバグがあれば、プレイヤーが任意のコードを実行できるということになります。
// 現代のCPUはアクセス権限があるため実行属性がないと保護違反となって実行停止してしまいますが、それでもROPとかいろいろありまして…。
今回5本のゲームが1つのカートリッジにまとまっているため、プレイ中のゲーム以外のプログラムやセーブデータも生きています。
普通はそれぞれ干渉しないように作られていますが、今回は普通ではないことができるため他のゲームにちょっかいが出せます。
例えば、SMWからSMB3のセーブデータを書き換えたり、そのSMB3から他のゲームを起動したり…。
そんなことが、今回起こるかもしれませんね…?
マルチタップというコントローラーのポート1つから4本タコ足接続できる周辺機器がありまして、これで1P, 2Pポート両方を拡張してコントローラーを4本接続しています。
ACE実行時にはコントローラーの入力状態をプログラムとして実行するため、あらかじめ必要なボタンをクリップで固定した状態にしています。
マリオ3のFILE Cを選択して「1 PLAYER GAME」を選択したらタイマースタートです。
即SAVE&QUITでセーブデータを作成したらリセットしてSMWに切り替えます。
ヨッシー回収(次ステージスロット調整のため)、中間ゲート通過によるスーパー状態の作成をします。
それ以外は普通。
ここも普通にクリアします。
一度クリアしたステージはポーズ中にSelectボタンを押すと脱出できるようになります。
ファイアを回収します。
// 記録狙いの時はそのままステータスインクリメントに移行する場合あり
ACEの準備を開始します。
まずヨッシーに無を取得させます。
ブロック増殖でヨッシーを2体出現させ、甲羅を口に含ませたヨッシーを谷底に落とすと
もう一体のスロット4の透明ヨッシーが口に無を含んだ状態で実体化します。
回転リフト上で無を吐きだすとX座標依存で様々な効果が得られる中で、今回はマリオのパワーアップステートのインクリメントを引きます。
ピクセルパーフェクトの技ですが、今回のセットアップでは猶予7F程度になっているそうです。
ステージを入りなおしてもう一回。
SMWにはチビ(0)、スーパー(1)、マント(2)、ファイア(3)と状態がありますが、バグでさらにパワーアップするとこんなカビたマリオになります。
回転リフトバグは高確率(ほとんどの座標)でゲームがフリーズします。
敵キャラによるメモリの調整をしていきます。
まずは $03D8
と $02F0
のOAM手続用メモリです。
画面に表示されるスプライトタイルの手続き領域です。
画面左側で甲羅を飛ばすエフェクトを発生させます。
ヨッシーに乗り甲羅の描画X座標が特定の値になるようにしてステージを脱出します。
木の実の食べキャンによりスプライトスロットを埋めつつ、ヨッシーの卵の殻スプライトが消滅するタイミングでのY速度を調整をします。
ヨッシーの表示X座標を調整して1UPキノコを取得することでパワーアップステート範囲外のアイテム取得処理へジャンプしてプログラムが暴走しますが、
事前の調整により任意コードが実行され、パワーアップステート = $15 になります。
// MEMO - ACE #1
// チャートではステージ脱出をしていたが練習配信の時はそのまま続行
甲羅を吐きだすことで敵キャラのX座標リスト領域にプログラムを作成していきます。
新しく出現した敵キャラにそのメモリを上書きされるまで以前の座標値が残っているため、上書きされないようにスプライトスロットの奥から調整します。
コントローラーを持ちかえたら準備完了です。
ストックのキノコを取得した瞬間にマリオ3のセーブデータを上書きするコードが実行されるため、2Pコンの入力から細工したセーブデータを送り込みます。
// MEMO - ACE #2
- ACE用キノコ達はスロット番号9である必要があるため、木の実の食べキャンやストックを降らせてスロット調整をしました。
- パワーアップステート = $15 のときは再度ストックにキノコが入るため連続で実行できるようになります。
- コントローラーポートの入力値が
LDY #imm
命令で任意の値をロードし、敵キャラX座標のSRAM書き込みルーチンを呼び出すプログラムになっています。(十字キーのミスが多発) - コントローラーの上下・左右同時押しや未割当ビットにより記述可能なコードには大きな制限があります。
- セーブデータの書き込み先はオペランドを直接インクリメントしています。(
STA long
繰り上がり未考慮 )
// チャートではステージ脱出をしていたが練習配信の時はそのまま続行
コントローラーをつなぎかえてプログラムを入れ替えたら、 $0100
が書き換わりエンディングを呼び出せるためSMWクリアです。
// MEMO - ACE #3
コントローラー入力 : AXLR0000 BYETUDLR
(E
=sElect, T
=sTart)
$19 PowerupState = $05 or $15
で $3103D0
を実行
$05
の1UPキノコと $15
のスーパーキノコは同等の効果を持つ
$03D0 : 05 xx 06 2A ; OAM ヨッシー頭のタイル (skip)
$03D4 : 0F xx xx xx ; OAM ヨッシー胴体のタイル (skip)
$03D8 : 4C F0 8C xx JMP $8CF0 ; OAM ノコノコ甲羅 (YI2(2)のセットアップから継承)
;$31:8CF0 : 20 F0 02 JSR $02F0 ; AND #$20 : BEQ $02 の途中
$02F0 : 10 F0 7C 20 / 18 F0 xx xx JMP ($1820, X) ; OAM 白い破裂エフェクト, X=9 => $1829
$1829 : 18 42 ; Minor extended sprite => $4218 : JOY1L
$4218 : 30 00 BMI $00 ; (skip) P1C1 ..LR.... ........
$421A : E0 00 CPX #$00 ; (skip) P2C1 AXL..... ........
$421C : 20 E4 JSR $00E4 ; P1C2 ..L..... BYE..D..
$421E : 00 60 RTS ; P2P2 ........ .YE.....
$00E4 : 84 19 60 STY $19 : RTS ; Sprite X YI2(3), パワーアップ = $15
本番時はシェルコードなし版の可能性高
$4218 : 20 04 JSR $A004 ; (skip) P1C1 ..L..... .....D..
$421A : A0 94 STY $10,X ; X=9 => $19 P2C1 A.L..... B..T.D..
$421C : 10 E4 CPX $00 ; (skip) P1C2 ...R.... BYE..D..
$421E : 00 60 RTS ; P2P2 ........ .YE.....
; オペランドの途中に飛び込み命令の解釈をずらして無害なルーチンとして抜けている
$31A004 : 96 20 STX $20, Y ; JSR $9624 の途中
$31A006 : 0D A4 AD ORA $ADA4 ; JSR $A40D
$31A009 : 19 14 D0 ORA $D014, Y ; LDA $1419
$31A00C : 04 24 TSB $24 ; BNE $04
$31A00E : 15 50 ORA $50, X ; BIT $15
$31A010 : 04 20 TSB $20 ; BVC $04
$31A012 : B1 A0 LDA ($A0), Y ; JSR $A0B1
$31A014 : 60 RTS ; RTS
SMB3 SRAM Injection
P2C1 の入力状態が1バイトずつSRAMに書き込まれる
$4218 : 80 00 BRA $00 ; (skip) P1C1 A....... ........
;$4218 : C0 00 CPY #$00 ; (skip) P1C1 AX...... ........
$421A : A0 xx LDY #$xx ; P2C1 A.L..... xxxxxxxx
$421C : 20 E4 JSR $00E4 ; P1C2 ..L..... BYE..D..
$421E : 00 60 RTS ; P2P2 ........ .YE.....
$00E4 : 98 8F 26 43 Fx E6 E6 60 TYA
STA $Fx4326 ; $700326 とかでも可 (LoROM SRAM)
INC $E6 ; dst++ STA $700327
RTS
// 友人情報
日本語版ではジャンプ先 ($700362
) JSL $0081CA
→ JSL $0081C4
,
チェックサムのバイト ($70036E
) を $F000
→ $F006
(LE) に変更すると概ね動作します。
SMWが付属しないためSRAMを直接書き換えてください。
USAの開始マップ(7-6)が崩壊しているため北米版と同じルートは通れませんでした。
SMW Credits Warp
$4218 : 80 00 BRA $00 ; (skip) P1C1 A....... ........
$421A : A0 20 LDY #$20 ; P2C1 A.L..... ..E.....
$421C : 20 E4 JSR $00E4 ; P1C2 ..L..... BYE..D..
$421E : 00 60 RTS ; P2P2 ........ .YE.....
$00E4 : 98 92 3D 60 TYA ; A = $20
STA ($3D) ; STA $0100
RTS
$3D
: モード7 レイヤー1Y上位座標 => $00
$3E
: BGモード (PPU $2105
) => $01
$0100
: ゲームモード ( $20
=> エンディング開始)
本番時はシェルコードなし版の可能性高
$4218 : 20 28 JSR $9028 ; LDA #$20 P1C1 ..L..... ..E.U...
$421A : 90 A0 LDY #$40 ; P2C1 A..R.... B.E.....
$421C : 40 91 STA ($20),Y ; $0100 = #$20 P1C2 .X...... B..T...R
$421E : 20 60 RTS ; P2P2 ..L..... .YE.....
$20
: レイヤー2 Y座標 => $00C0
SMB3で追加されたバグアイテムを選択
P1C1 A, P2C1 下 でSMB1起動(8-4開始)
セレクト : 壁無視
A : パワーアップ
Y座標がステータス領域にいるとマップのループ判定が出ずそのまま進むとクッパに到達するようです。
バグに抱擁されるマリオです。
SMB3で追加されたバグアイテムを選択
P1C1 右+A, P2C1 右+上 でSMB2起動(D-4開始)
セレクト : 壁無視
A : パワーアップ
もうお察しかもしれませんが、残りのゲームはバグアイテムから各ゲームを起動して、ずるいクリアをしていきます。
1P, 2Pの入力状態に寄って起動するゲームが変化します。
SMB3で追加されたバグアイテムを選択
P1C1 左+A, P2C1 右 でSMUSA起動(7-6開始)
USAの最終ステージは7-2なのですが、7-6から始まります。
内部的に7-2となるマップで死んで、7-2で再開させます。
A : 壁無視
X : 無敵
ジャンプボタンが壁無視に割り当てられてしまっているためジャンプできません。
壁無視でうまくキノコブロックの上に着地させて掴み、マスクゲートを倒します。
ゲートが上に飛んでしまうとなかなか戻ってこないので、無敵で当たらないように注意します。
曲ロードの関係でマムーを倒す前に無敵になっておく必要があるようです。
SMB3で追加されたバグアイテムを選択
Bでアイテム選択をするとSMB3続行
1-1 に入るとエンディングです。
- SMWの任意コード実行を利用して
- SMB3にバグアイテムを送り込み
- ズルいクリアをした
一見フリーズしてしまうような歪なバグのピース達をきれいに組み上げて任意コード実行に繋げた機構は圧巻の一言です。
ファミコンのメインメモリエディタとかQuineとかよくわからないの作ってます。
最近数万円かけてはんだごて環境を整えたりスーファミの勉強をし直したりしているところなので、近いうちにお披露目できたらと思います。
自サイト : ZpIndIndY
今回解説にあたり、 SethBling 氏の公開チャートが非常に参考になりました。ありがとうございます。
ACE発見やルート構築にあたった多くの方々の尽力にも敬意を。
また、調査に協力してくれた友人にも感謝。
SGDQ2022もあと少し、フィナーレまで盛り上げていきましょう!
それでは拙い解説ではありましたが、ご清聴いただきありがとうございました。