DQ3 プレイヤーキャラクター情報へのアクセス

以前バイナリスレで86氏に教えていただいたPC*1のステータス(HP、MP、力など)の取得/変更(SR: $043414 力取得など)は、大抵3,4個の引数を取りますが、なんとなく2番目が対象キャラクターを示し、3番目以降で変更(もしくは取得)する値の指定をしている程度の理解で実装をしていました。現在行っている作業の中でこれらのSRについて正しく理解する羽目になったのでエントリとして投下します。

そもそも各PCのステータス自体は$7E3925-から1PCにつき60バイトの領域を使用して連続して格納されています。$7E3925-は勇者の情報、$7E3961-はルイーダに最初から入る仲間の1人目の情報…のようになっており、この順番は基本固定で、キャラの順番を入れ替えても格納位置が変わることはありません。実際にパーティに参加しているキャラクターの情報等は以前のエントリ(https://retrogamehackers.net/dq3-pcinfo-management-001/)で説明したように$7E36E2-に各種人数、$7E36E8-に各キャラクターの開始アドレスが配列になって並んでいます。とどのつまりは「この開始アドレスをいかにして取得してくるか」が最終的な目的になります。PCのステータスにアクセスするSRは以下の2種類があります。

  1. Xレジスタに開始アドレスがセットされていることを前提としてAレジスタにデータを返す/Aレジスタのデータに変更する
  2. 1をラップしてより柔軟なデータのやり取りをできるようにする

1の実装は極めてシンプルです。

  • SR: $043442 力取得
043442LDA $000C,XA=$000C+X
043445AND #$00FFA&=#$00FF
043448RTLreturn
  • SR: $043442 力変更
043450CMP #$00FFA>=#$00FF?
043453BCC #$03if(c==off) goto $043458
043455LDA #$00FFA=#$00FF
043458SEP #$20m=on(A/M:8b)
04345ASTA $000C,X$000C+X=A
04345DREP #$20m=off(A/M:16b)
04345FRTLreturn

Xに各PCの開始アドレスがセットされていることを期待してAに値をセット/Aの値で変更を行っています。他のステータスについても同様の実装がされています。次に2の実装を見てみます。

  • SR: $043414 力取得
043414PHPPush P Flag
043415PHBPush DB
043416REP #$30m=off(A/M:16b) x=off(X/Y:16b)
043418PHAPush A
043419PHXPush X
04341APHYPush Y
04341BJSL $C42777SR: $042777 呼び出し元の後の3バイトを引数として取得する
04341FPEA #$7E7EPush #$7E7E
043422PLBPull DB
043423PLBPull DB
043424LDX #$40B5X=#$40B5
043427JSL $C4289ESR: $04289E対象キャラのフィールド上情報スタートアドレスを取得
04342BBCC #$03if(c==off) goto $043430
04342DLDA #$3925A=#$3925
043430TAXX=A
043431JSL $C43442SR: $043442力取得
043435LDX #$40B7X=#$40B7
043438JSL $C428FDSR: $0428FDリターン用の変数にセット
04343CPLYPull Y
04343DPLXPull X
04343EPLAPull A
04343FPLBPull DB
043440PLPPull P Flag
043441RTLreturn

このSRは引数として1バイト☓3個の引数を取ります。これらの値の取得はSR:042777で行われ、引数の値は7E40B5-B7にセットされます。このSRでは同時にプログラムカウンタの操作も行っているのですが実装については省略します。次が一番重要なSRになります。

  • SR: $04289E 対象のスタートアドレスを特定(失敗c=on)
04289ELDA $0000,XA=$0000+X
0428A1AND #$00FFA&=#$00FF
0428A4STA $40BD$40BD=A第1引数セット
0428A7LDA $0001,XA=$0001+X
0428AAAND #$00FFA&=#$00FF第2引数判定
0428ADCMP #$00FFA==#$00FF?#$FFはAレジスタを意味する
0428B0BNE #$04if(z==off) goto $0428B6
0428B2LDA $08,SA=Stack($08)SR: $043414の初めにPUSHしたAレジスタの値を取得
0428B4BRA #$15goto $0428CB
0428B6CMP #$00FEA==#$00FE?#$FEはXレジスタを意味する
0428B9BNE #$04if(z==off) goto $0428BF
0428BBLDA $06,SA=Stack($06)SR: $043414の初めにPUSHしたXレジスタの値を取得
0428BDBRA #$0Cgoto $0428CB
0428BFCMP #$00FDA==#$00FD?#$FDはYレジスタを意味する
0428C2BNE #$04if(z==off) goto $0428C8
0428C4LDA $04,SA=Stack($04)SR: $043414の初めにPUSHしたYレジスタの値を取得
0428C6BRA #$03goto $0428CB
0428C8TAXX=A
0428C9LDA $00,XA=DP($00+X)A,X,YでなければDP($00+X)から値を取得
0428CBLDX $40BDX=$40BD第1引数判定
0428CECPX #$0006X==#$0006?#$06は第2引数が開始アドレスであることを意味する
0428D1BEQ #$0Fif(z==on) goto $0428E2
0428D3CPX #$0004X==#$0004?#$04は第2引数がルイーダ待機中のPCのインデックスであることを意味する
0428D6BEQ #$1Fif(z==on) goto $0428F7
0428D8CPX #$0005X==#$0005?#$05は第2引数がバークに残したPCのインデックスであることを意味する
0428DBBEQ #$16if(z==on) goto $0428F3
0428DDASLA<<1それ以外はアドレスリスト($7E36E8-)のインデックスを意味する
0428DETAXX=A
0428DFLDA $36E8,XA=$36E8+X
0428E2CMP #$3925A>=#$3925?アドレスが1人目の開始アドレスより前は異常
0428E5BCC #$07if(c==off) goto $0428EE
0428E7CMP #$3EC5A>=#$3EC5?アドレスが23人目より後は異常
0428EABCS #$02if(c==on) goto $0428EE
0428ECCLCc=off
0428EDRTLreturn
0428EESECc=on
0428EFLDA #$0000A=#$0000開始アドレスに0をセットして戻る
0428F2RTLreturn
0428F3CLCc=off
0428F4ADC $36E4A+=($36E4+c)ルイーダ待機中人数を足す
0428F7CLCc=off
0428F8ADC $36E2A+=($36E2+c)パーティ人数を足す
0428FBBRA #$E0goto $0428DD

第1引数は第2引数の種別を意味しているということになります。第1引数が#$06の場合のみ第2引数は対象のPCの開始アドレスそのものを意味します。それ以外は第2引数は7E36E8-の配列のインデックスを意味します。これで対象の開始アドレスが特定できたので必要な値をAレジスタにセットした後、リターン用の変数にその値をセットして終わりになります。

  • SR: $0428FD リターン用の変数にセット
0428FDPHAPush A
0428FELDA $0000,XA=$0000+X
042901AND #$00FFA&=#$00FF第3引数を取得
042904TAXX=A
042905PLAPull A
042906CPX #$00FFX==#$00FF?#$FFはAレジスタを意味する
042909BNE #$03if(z==off) goto $04290E
04290BSTA $08,SStack($08)=ASR: $043414の初めにPUSHしたAレジスタにセット
04290DRTLreturn
04290ECPX #$00FEX==#$00FE?#$FEはXレジスタを意味する
042911BNE #$03if(z==off) goto $042916
042913STA $06,SStack($06)=ASR: $043414の初めにPUSHしたXレジスタにセット
042915RTLreturn
042916CPX #$00FDX==#$00FD?#$FDはYレジスタを意味する
042919BNE #$03if(z==off) goto $04291E
04291BSTA $04,SStack($04)=ASR: $043414の初めにPUSHしたYレジスタにセット
04291DRTLreturn
04291ESTA $00,XDP($00)+X=Aそれ以外はDP($00+X)にセット
042920RTLreturn

今回は力の値を例として取り上げましたが、同じような実装はPCのステータスに限らず、パーティ人数、所持金額、アイテム名称IDなど、様々な情報をやりとりする場面で使われています。これらのSRをコールする前後では、A、X、YやDPに操作したい値をセットしているはずなので、それと合わせると処理の理解が進むと思います。

通常PCの情報にアクセスするのはインデックスベースであることがほとんどで、アドレスベースでアクセスする必要性は一見なさそうですが、戦闘中においてはアドレスベースでアクセスするのが確実になります。戦闘中はすべて戦闘中キャラクター情報(7E2030-)で話が進みます。ここには戦闘開始時に情報がセットされ、戦闘終了時までキャラクターの並び順が変わっても(パルプンテの効果で発生)変わることはありません。戦闘開始時にA, B, C, Dの順番で並んでいた場合、先頭から戦闘中インデックス0, 1, 2, 3が振られますが、順番が変わって、D, C, B, Aとなった場合(なった時点で7E36E8-の順番も変更)、Dの戦闘中インデックスは3のままです。このインデックスの値をそのまま使ってHPなどの情報を取得しようとすると、Aの値が返ってきておかしなことになります。手段としては「対象の戦闘中インデックスから移動中のインデックスに変換する」方式も考えられるのですが、「対象の戦闘中インデックスから移動中開始アドレスを取得する」のほうが直接的です。

  • SR: $02BE8A 戦闘中情報取得/変更
02BE8APHPPush P Flag
02BE8BPHBPush DB
02BE8CREP #$30m=off(A/M:16b) x=off(X/Y:16b)
02BE8EPHXPush X
02BE8FPHYPush Y
02BE90SEP #$20m=on(A/M:8b)
02BE92LDA $09,SA=Stack($09)
02BE94PHAPush A
02BE95PLBPull DB
02BE96LDY #$0001Y=#$0001
02BE99LDA ($07,S),YA=Stack($07+Y)
02BE9BREP #$20m=off(A/M:16b)
02BE9DAND #$00FFA&=#$00FF引数1バイト取得
02BEA0TAYY=A
02BEA1LDA $07,SA=Stack($07)
02BEA3INCA++
02BEA4STA $07,SStack($07)=Aプログラムカウンタインクリメント
02BEA6PEA #$7E7EPush #$7E7E
02BEA9PLBPull DB
02BEAAPLBPull DB
02BEABLDA $2428A=$2428対象の戦闘中インデックスを取得
02BEAECMP #$0018A>=#$0018?
02BEB1BCS #$FEif(c==on) goto $02BEB1
02BEB3ASLA<<1
02BEB4TAXX=A
02BEB5LDA $C2CBD3,XA=$02CBD3+X戦闘中開始アドレスを取得する
02BEB9TYXX=YこのSRの引数をXレジスタに移す
02BEBATAYY=A
02BEBBLDA $2050,YA=$2050+Y
02BEBEAND #$0002A&=#$0002
02BEC1BEQ #$FEif(z==on) goto $02BEC1
02BEC3LDA $2050,YA=$2050+Y
02BEC6AND #$0001A&=#$0001
02BEC9BNE #$0Dif(z==off) goto $02BED8敵味方フラグをチェック
02BECBJSR $BEE9SR: $($02BEE9+X)敵用SRアドレスリスト
02BECEBCS #$11if(c==on) goto $02BEE1
02BED0PLYPull Y
02BED1PLXPull X
02BED2PLBPull DB
02BED3PLPPull P Flag
02BED4PHAPush A
02BED5PLAPull A
02BED6CLCc=off
02BED7RTLreturn
02BED8LDA $2049,YA=$2049+Y移動中開始アドレスを取得する(モシャス前)
02BEDBTAYY=A開始アドレスをYレジスタにセット
02BEDCJSR $3A30SR: $($023A30+X)味方用SRアドレスリスト
02BEDFBCC #$EFif(c==off) goto $02BED0
02BEE1PLYPull Y
02BEE2PLXPull X
02BEE3PLBPull DB
02BEE4PLPPull P Flag
02BEE5PHAPush A
02BEE6PLAPull A
02BEE7SECc=on
02BEE8RTLreturn

実際に$023A30でコールされているSRを1つ見てみます。

  • SR $02C030 戦闘中PC情報取得変更SR_SR_0000
02C030JSL $C43115SR: $043115 引数:1#$06 引数:2#$FD 引数:3#$FF現在HP取得
02C037RTS

アドレスベースで情報を得るので、第1引数は#$06、第2引数は$02BEDBでYレジスタにセットしているので#$FDになり、戻り値をAレジスタにセットさせるので第3引数は#$FFになります。この並びのSRをみると第1引数はどれも#$06になっています。当たり前といえば当たり前の話ですが、今の今までこの辺をぼんやりとしか理解しないでよく派手にバグらせずに済んだと思うと恥ずかしい限りですが、よくよく考えて見ればどれも既存のプログラムの延長線上だったので「ようわからんけど他と合わせておけばおk」程度の認識で何とかなっていたのでしょう。

*1:プレイヤーキャラクター

コメント

タイトルとURLをコピーしました