今回から16進数を羅列していた部分をアセンブリ命令?に変えることにします。本当はJavaScriptとCSSで切り替えるようにできればいいんですが、はてなはJSは禁止らしいので、とりあえずペンディング。このブログ見ながら16進数をぽちぽち打ってた人には悪いのですが、まあ今までの表記が邪道過ぎたので。なお、表記はdis65816で逆汗した結果っぽい感じのものを使っています。暇があれば過去のエントリも同様の表記に変更していきます。
- SR: $0287B9 一撃必殺武器ヒット判定(ヒットc=on)
0287B9 | PHY | Push Y | |
---|---|---|---|
0287BA | PEA $2011 | Push $2011 | |
0287BD | PEA $0020 | Push $0020 | |
0287C0 | PEA $7E00 | Push $7E00 | |
0287C3 | JSL $09029E | SR: $09029E | イベント戦闘か調べる |
0287C7 | CLC | c=off | |
0287C8 | BNE #$67 | if(z==off) goto $028831 | |
0287CA | LDA $23F2 | A=$23F2 | |
0287CD | CLC | c=off | |
0287CE | BNE #$61 | if(z==off) goto $028831 | |
0287D0 | LDX $23EE | X=$23EE | |
0287D3 | JSL $02CC47 | SR: $02CC47 引数:1#$007A 引数:2#$0040 | 戦闘行動が一撃必殺が発生する行動か調べる |
0287DB | CLC | c=off | |
0287DC | BEQ #$53 | if(z==on) goto $028831 | |
0287DE | LDA $23E4 | A=$23E4 | |
0287E1 | STA $2428 | $2428=A | |
0287E4 | LDA #$0000 | A=#$0000 | アイテム種別:武器をセット |
0287E7 | JSL $02B8AA | SR: $02B8AA | 装備アイテムID取得 |
0287EB | LDA $242C | A=$242C | |
0287EE | CMP #$0009 | A==#$0009? | 毒針か |
0287F1 | BEQ #$06 | if(z==on) goto $0287F9 | |
0287F3 | CMP #$0031 | A==or>=#$0031? | アサシンダガーか |
0287F6 | CLC | c=off | |
0287F7 | BNE #$38 | if(z==off) goto $028831 | |
0287F9 | JSL $0012D1 | SR: $0012D1 | 乱数発生 $00-FF |
0287FD | AND #$000F | A&=#$000F | 15/255で一撃必殺発生 |
028800 | CLC | c=off | |
028801 | BNE #$2E | if(z==off) goto $028831 | |
028803 | LDA $23E8 | A=$23E8 | |
028806 | STA $2428 | $2428=A | |
028809 | LDA #$FFFF | A=#$FFFF | ダメージ65535をセット |
02880C | STA $00 | DP($00)=A | |
02880E | JSL $02BE8A | SR: $02BE8A 引数:1#$06 | HP減産処理 |
028813 | JSL $02B977 | SR: $02B977 | パーティウィンドウ再描画? |
028817 | LDA $23EE | A=$23EE | |
02881A | PHA | Push A | |
02881B | LDA #$0018 | A=#$0018 | |
02881E | STA $23EE | $23EE=A | |
028821 | JSL $02CFCE | SR: $02CFCE | 消滅処理? |
028825 | PLA | Pull A | |
028826 | STA $23EE | $23EE=A | |
028829 | JSR $8833SR: $028833 | 毒針ヒット時の処理 | |
02882C | JSL $02B054 | SR: $02B054 | 行動対象者を戦闘から離脱させる処理メイン |
028830 | SEC | c=on | |
028831 | PLY | Pull Y | |
028832 | RTS | return |
イベント戦闘(ボス戦)の場合は一撃必殺が発生しません。ゾーマが毒針一撃で倒されることもない、ということですね。また、毒針もアサシンダガーもどちらも一撃必殺の確率は同じです(約1/16)。一撃必殺時には65535をHPから引ことで確実に死ぬ、ということのようです。FC版のDQ2でもザラキはHP255マイナスとかそんな実装だったような。
さて、ようやく一番のメインディッシュの「戦闘行動実行」にたどり着きました。
- SR: $02885E 戦闘行動実行
02885E | PHY | Push Y | |
---|---|---|---|
02885F | SEP #$20 | m=on(A/M:8b) | |
028861 | LDA #$C2 | A=#$C2 | |
028863 | PHA | Push A | 1回目 |
028864 | REP #$20 | m=off(A/M:16b) | |
028866 | LDA #$888E | A=#$888E | |
028869 | DEC | A– | 戦闘行動を実行した後のプログラムカウンタを用意 |
02886A | PHA | Push A | 2回目 |
02886B | LDX $23EE | X=$23EE | |
02886E | SEP #$20 | m=on(A/M:8b) | |
028870 | JSL $0903EE | SR: $0903EE 引数:1#$00 引数:2#$001D 引数:3#$C20060 引数:4#$0008 | 戦闘行動アドレス上位1バイトを取得(m=offなので1バイトしかセットされない) |
02887C | PHA | Push A | 3回目 |
02887D | REP #$20 | m=off(A/M:16b) | |
02887F | JSL $0903EE | SR: $0903EE 引数:1#$00 引数:2#$001D 引数:3#$C20060 引数:4#$0006 | 戦闘行動アドレス下位2バイトを取得 |
02888B | DEC | A– | 3回目 戦闘行動開始用のプログラムカウンタを用意 |
02888C | PHA | Push A | 4回目 |
02888D | RTL | return | <-JSRでコールしたのにRTLでリターン |
02888E | BCC #$21 | if(c==off) goto $0288B1 | |
028890 | LDA #$0001 | A=#$0001 | |
028893 | PEA $23AD | Push $23AD | |
028896 | PEA $0001 | Push $0001 | |
028899 | PEA $7E00 | Push $7E00 | |
02889C | JSL $0902E9 | SR: $0902E9 | RAM上情報変更 |
0288A0 | PEA $23AE | Push $23AE | |
0288A3 | PEA $0002 | Push $0002 | |
0288A6 | PEA $7E00 | Push $7E00 | |
0288A9 | JSL $0902E9 | SR: $0902E9 | RAM上情報変更 |
0288AD | JSL $02CF00 | SR: $02CF00 | 描画系?表示エフェクトプログラム2関連 |
0288B1 | PLY | Pull Y | |
0288B2 | RTS | return | <-JSRでもともとコールされているのでRTSで帳尻あわせ? |
通常ではやったらバグ確実のコードですが、非常にうまくできています。SNES(65816?)では、通常はSR内でスタックにPUSHしたら必ず出る前にPULLするのがお約束で、これを守らないとすぐにバグります。スタックにはSRコール時にコールしたアドレスの情報もPUSHされるらしく、RTS,RTLするとこれがPULLされて1インクリメントされるので次に実行すべき命令の位置がわかる、というようになっているようです。この情報のことをプログラムカウンタ(PC)と呼びます*1。したがって、SR内でPUSHとPULLの数の対応が取れていない場合、予期しない値がプログラムカウンタとして扱われるためにとんでもない場所に実行位置が飛んでしまってバグが起きる、ということのようです。また、JSR(#$20),JSL(#$22)によってPUSHされるプログラムカウンタのサイズ?が異なるせいか、JSRでコールしてRTL(#$6B)でリターンしたり、JSLでコールしてRTS(#$60)でリターンすると、リターン後のプログラムカウンタが正しく取得できなくなってこれまた即バグります。改造する場合は、SRを別の場所に作って飛ばして戻す、という処理をよくやるわけですが、JSRとRTS、JSLとRTLの対応はきちんととるようにしましょう。そういう意味では、分岐してそのままリターンというようなコード(下記参照)は書かないほうがいいかもしれません。
- SR: $0DE6E1 光の鎧を入手しているか(該当c=on) <-よくない例?
0DE6E1 | LDA $3544 | A=$3544 | |
---|---|---|---|
0DE6E4 | AND #$0010 | A&=#$0010 | |
0DE6E7 | BEQ #$02 | if(z==on) goto $0DE6EB | |
0DE6E9 | SEC | c=on | |
0DE6EA | RTL | return | <-2箇所でリターンしている |
0DE6EB | CLC | c=off | |
0DE6EC | RTL | return | <-2箇所でリターンしている |
もうひとつ、DQ3やDQ6ではSR: $0903EEのような直後に引数をとるようなSRが数多く使用されていますが、これもおそらくプログラムカウンタを利用して、呼び出し元のアドレスを取得し、その後ろに設定されている値を取得する、という手法がとられているものと思われます。
さて、SR: $02885Eに話を戻すと、通常ではやってはいけないPUSHしっぱなしを都合4回やっています。SRに入った直後ではプログラムカウンタはスタックの一番上に置かれています(多分)。リターン時にはスタックの一番上のデータをプログラムカウンタとして取得し、1インクリメントしてプログラムの実行を続ける、というのは説明したとおりですが、このルールに当てはめると、$02888Dでリターンした直後、スタックの一番上は以下のようになっているはずです。
0 | 戦闘行動実行アドレス(下位2バイト)-1 | |
1 | 戦闘行動実行アドレス(#$00C2?) | |
2 | #$888D | |
3 | #$00C2 | |
4 | #$7DA7 | SR: $02885Eを呼び出した位置 |
JSLでリターンしたので、スタックを2つPULLして3バイトのプログラムカウンタとして扱い、1インクリメントして実行を続けます。これが戦闘行動の実体部分の実行です。各戦闘行動の実体部分の最後もRTLでリターンします。このときのスタックの状態は
0 | #$888D | |
1 | #$00C2 | |
2 | #$7DA7 | SR: $02885Eを呼び出した位置 |
となっているはずです。再度スタックが2つPULLされ、1インクリメントして$02888Eから実行を続け、戦闘行動に対応するエフェクトなどを実行します。$0288B2ではRTSでリターンします。このときのスタックの状態は
0 | #$7DA7 | SR: $02885Eを呼び出した位置 |
となっています。スタックを1つPULLして、これ以降はスタックは正常に戻ります。DQ6の戦闘行動実行部分の解析をやっていないのでわかりませんが、DQ6のエフェクト実行部分ではプログラム開始位置らしきものを指定する場合、あらかじめアドレスを-1しておくのがお作法でした。後発のDQ3では、同じエフェクト実行部分でプログラム開始位置らしきものを指定する場合は-1せず、実際の開始位置が指定されていました。根っこのSRで上記のように開始位置を-1してやることで、より直感的なアドレス指定が実現されている(=ひいては設定ミスによるバグがおきにくくなる)と思います。DQ3ではこうしたDQ6のエンジンをより洗練させて流用していると見受けられる部分が何箇所かあります。
結構な分量になったのでとりあえずここまでにして、次回は上記戦闘行動実行で呼び出される実体部分について、いくつか特徴的なものをピックアップして解説します。
*1:以前のSnes9x Debuggerではこの情報は見えなかったのですが、最新のSnes9x Debugger1.51ではレジスタの情報とともにこのプログラムカウンタの情報も見えるようになっており、気になる人は見てみるといいと思います
コメント