以前バイナリスレで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種類があります。
- Xレジスタに開始アドレスがセットされていることを前提としてAレジスタにデータを返す/Aレジスタのデータに変更する
- 1をラップしてより柔軟なデータのやり取りをできるようにする
1の実装は極めてシンプルです。
- SR: $043442 力取得
043442 | LDA $000C,X | A=$000C+X | |
---|---|---|---|
043445 | AND #$00FF | A&=#$00FF | |
043448 | RTL | return |
- SR: $043442 力変更
043450 | CMP #$00FF | A>=#$00FF? | |
---|---|---|---|
043453 | BCC #$03 | if(c==off) goto $043458 | |
043455 | LDA #$00FF | A=#$00FF | |
043458 | SEP #$20 | m=on(A/M:8b) | |
04345A | STA $000C,X | $000C+X=A | |
04345D | REP #$20 | m=off(A/M:16b) | |
04345F | RTL | return |
Xに各PCの開始アドレスがセットされていることを期待してAに値をセット/Aの値で変更を行っています。他のステータスについても同様の実装がされています。次に2の実装を見てみます。
- SR: $043414 力取得
043414 | PHP | Push P Flag | |
---|---|---|---|
043415 | PHB | Push DB | |
043416 | REP #$30 | m=off(A/M:16b) x=off(X/Y:16b) | |
043418 | PHA | Push A | |
043419 | PHX | Push X | |
04341A | PHY | Push Y | |
04341B | JSL $C42777 | SR: $042777 | 呼び出し元の後の3バイトを引数として取得する |
04341F | PEA #$7E7E | Push #$7E7E | |
043422 | PLB | Pull DB | |
043423 | PLB | Pull DB | |
043424 | LDX #$40B5 | X=#$40B5 | |
043427 | JSL $C4289E | SR: $04289E | 対象キャラのフィールド上情報スタートアドレスを取得 |
04342B | BCC #$03 | if(c==off) goto $043430 | |
04342D | LDA #$3925 | A=#$3925 | |
043430 | TAX | X=A | |
043431 | JSL $C43442 | SR: $043442 | 力取得 |
043435 | LDX #$40B7 | X=#$40B7 | |
043438 | JSL $C428FD | SR: $0428FD | リターン用の変数にセット |
04343C | PLY | Pull Y | |
04343D | PLX | Pull X | |
04343E | PLA | Pull A | |
04343F | PLB | Pull DB | |
043440 | PLP | Pull P Flag | |
043441 | RTL | return |
このSRは引数として1バイト☓3個の引数を取ります。これらの値の取得はSR:042777で行われ、引数の値は7E40B5-B7にセットされます。このSRでは同時にプログラムカウンタの操作も行っているのですが実装については省略します。次が一番重要なSRになります。
- SR: $04289E 対象のスタートアドレスを特定(失敗c=on)
04289E | LDA $0000,X | A=$0000+X | |
---|---|---|---|
0428A1 | AND #$00FF | A&=#$00FF | |
0428A4 | STA $40BD | $40BD=A | 第1引数セット |
0428A7 | LDA $0001,X | A=$0001+X | |
0428AA | AND #$00FF | A&=#$00FF | 第2引数判定 |
0428AD | CMP #$00FF | A==#$00FF? | #$FFはAレジスタを意味する |
0428B0 | BNE #$04 | if(z==off) goto $0428B6 | |
0428B2 | LDA $08,S | A=Stack($08) | SR: $043414の初めにPUSHしたAレジスタの値を取得 |
0428B4 | BRA #$15 | goto $0428CB | |
0428B6 | CMP #$00FE | A==#$00FE? | #$FEはXレジスタを意味する |
0428B9 | BNE #$04 | if(z==off) goto $0428BF | |
0428BB | LDA $06,S | A=Stack($06) | SR: $043414の初めにPUSHしたXレジスタの値を取得 |
0428BD | BRA #$0C | goto $0428CB | |
0428BF | CMP #$00FD | A==#$00FD? | #$FDはYレジスタを意味する |
0428C2 | BNE #$04 | if(z==off) goto $0428C8 | |
0428C4 | LDA $04,S | A=Stack($04) | SR: $043414の初めにPUSHしたYレジスタの値を取得 |
0428C6 | BRA #$03 | goto $0428CB | |
0428C8 | TAX | X=A | |
0428C9 | LDA $00,X | A=DP($00+X) | A,X,YでなければDP($00+X)から値を取得 |
0428CB | LDX $40BD | X=$40BD | 第1引数判定 |
0428CE | CPX #$0006 | X==#$0006? | #$06は第2引数が開始アドレスであることを意味する |
0428D1 | BEQ #$0F | if(z==on) goto $0428E2 | |
0428D3 | CPX #$0004 | X==#$0004? | #$04は第2引数がルイーダ待機中のPCのインデックスであることを意味する |
0428D6 | BEQ #$1F | if(z==on) goto $0428F7 | |
0428D8 | CPX #$0005 | X==#$0005? | #$05は第2引数がバークに残したPCのインデックスであることを意味する |
0428DB | BEQ #$16 | if(z==on) goto $0428F3 | |
0428DD | ASL | A<<1 | それ以外はアドレスリスト($7E36E8-)のインデックスを意味する |
0428DE | TAX | X=A | |
0428DF | LDA $36E8,X | A=$36E8+X | |
0428E2 | CMP #$3925 | A>=#$3925? | アドレスが1人目の開始アドレスより前は異常 |
0428E5 | BCC #$07 | if(c==off) goto $0428EE | |
0428E7 | CMP #$3EC5 | A>=#$3EC5? | アドレスが23人目より後は異常 |
0428EA | BCS #$02 | if(c==on) goto $0428EE | |
0428EC | CLC | c=off | |
0428ED | RTL | return | |
0428EE | SEC | c=on | |
0428EF | LDA #$0000 | A=#$0000 | 開始アドレスに0をセットして戻る |
0428F2 | RTL | return | |
0428F3 | CLC | c=off | |
0428F4 | ADC $36E4 | A+=($36E4+c) | ルイーダ待機中人数を足す |
0428F7 | CLC | c=off | |
0428F8 | ADC $36E2 | A+=($36E2+c) | パーティ人数を足す |
0428FB | BRA #$E0 | goto $0428DD |
第1引数は第2引数の種別を意味しているということになります。第1引数が#$06の場合のみ第2引数は対象のPCの開始アドレスそのものを意味します。それ以外は第2引数は7E36E8-の配列のインデックスを意味します。これで対象の開始アドレスが特定できたので必要な値をAレジスタにセットした後、リターン用の変数にその値をセットして終わりになります。
- SR: $0428FD リターン用の変数にセット
0428FD | PHA | Push A | |
---|---|---|---|
0428FE | LDA $0000,X | A=$0000+X | |
042901 | AND #$00FF | A&=#$00FF | 第3引数を取得 |
042904 | TAX | X=A | |
042905 | PLA | Pull A | |
042906 | CPX #$00FF | X==#$00FF? | #$FFはAレジスタを意味する |
042909 | BNE #$03 | if(z==off) goto $04290E | |
04290B | STA $08,S | Stack($08)=A | SR: $043414の初めにPUSHしたAレジスタにセット |
04290D | RTL | return | |
04290E | CPX #$00FE | X==#$00FE? | #$FEはXレジスタを意味する |
042911 | BNE #$03 | if(z==off) goto $042916 | |
042913 | STA $06,S | Stack($06)=A | SR: $043414の初めにPUSHしたXレジスタにセット |
042915 | RTL | return | |
042916 | CPX #$00FD | X==#$00FD? | #$FDはYレジスタを意味する |
042919 | BNE #$03 | if(z==off) goto $04291E | |
04291B | STA $04,S | Stack($04)=A | SR: $043414の初めにPUSHしたYレジスタにセット |
04291D | RTL | return | |
04291E | STA $00,X | DP($00)+X=A | それ以外はDP($00+X)にセット |
042920 | RTL | return |
今回は力の値を例として取り上げましたが、同じような実装は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 戦闘中情報取得/変更
02BE8A | PHP | Push P Flag | |
---|---|---|---|
02BE8B | PHB | Push DB | |
02BE8C | REP #$30 | m=off(A/M:16b) x=off(X/Y:16b) | |
02BE8E | PHX | Push X | |
02BE8F | PHY | Push Y | |
02BE90 | SEP #$20 | m=on(A/M:8b) | |
02BE92 | LDA $09,S | A=Stack($09) | |
02BE94 | PHA | Push A | |
02BE95 | PLB | Pull DB | |
02BE96 | LDY #$0001 | Y=#$0001 | |
02BE99 | LDA ($07,S),Y | A=Stack($07+Y) | |
02BE9B | REP #$20 | m=off(A/M:16b) | |
02BE9D | AND #$00FF | A&=#$00FF | 引数1バイト取得 |
02BEA0 | TAY | Y=A | |
02BEA1 | LDA $07,S | A=Stack($07) | |
02BEA3 | INC | A++ | |
02BEA4 | STA $07,S | Stack($07)=A | プログラムカウンタインクリメント |
02BEA6 | PEA #$7E7E | Push #$7E7E | |
02BEA9 | PLB | Pull DB | |
02BEAA | PLB | Pull DB | |
02BEAB | LDA $2428 | A=$2428 | 対象の戦闘中インデックスを取得 |
02BEAE | CMP #$0018 | A>=#$0018? | |
02BEB1 | BCS #$FE | if(c==on) goto $02BEB1 | |
02BEB3 | ASL | A<<1 | |
02BEB4 | TAX | X=A | |
02BEB5 | LDA $C2CBD3,X | A=$02CBD3+X | 戦闘中開始アドレスを取得する |
02BEB9 | TYX | X=Y | このSRの引数をXレジスタに移す |
02BEBA | TAY | Y=A | |
02BEBB | LDA $2050,Y | A=$2050+Y | |
02BEBE | AND #$0002 | A&=#$0002 | |
02BEC1 | BEQ #$FE | if(z==on) goto $02BEC1 | |
02BEC3 | LDA $2050,Y | A=$2050+Y | |
02BEC6 | AND #$0001 | A&=#$0001 | |
02BEC9 | BNE #$0D | if(z==off) goto $02BED8 | 敵味方フラグをチェック |
02BECB | JSR $BEE9 | SR: $($02BEE9+X) | 敵用SRアドレスリスト |
02BECE | BCS #$11 | if(c==on) goto $02BEE1 | |
02BED0 | PLY | Pull Y | |
02BED1 | PLX | Pull X | |
02BED2 | PLB | Pull DB | |
02BED3 | PLP | Pull P Flag | |
02BED4 | PHA | Push A | |
02BED5 | PLA | Pull A | |
02BED6 | CLC | c=off | |
02BED7 | RTL | return | |
02BED8 | LDA $2049,Y | A=$2049+Y | 移動中開始アドレスを取得する(モシャス前) |
02BEDB | TAY | Y=A | 開始アドレスをYレジスタにセット |
02BEDC | JSR $3A30 | SR: $($023A30+X) | 味方用SRアドレスリスト |
02BEDF | BCC #$EF | if(c==off) goto $02BED0 | |
02BEE1 | PLY | Pull Y | |
02BEE2 | PLX | Pull X | |
02BEE3 | PLB | Pull DB | |
02BEE4 | PLP | Pull P Flag | |
02BEE5 | PHA | Push A | |
02BEE6 | PLA | Pull A | |
02BEE7 | SEC | c=on | |
02BEE8 | RTL | return |
実際に$023A30でコールされているSRを1つ見てみます。
- SR $02C030 戦闘中PC情報取得変更SR_SR_0000
02C030 | JSL $C43115 | SR: $043115 引数:1#$06 引数:2#$FD 引数:3#$FF | 現在HP取得 |
---|---|---|---|
02C037 | RTS |
アドレスベースで情報を得るので、第1引数は#$06、第2引数は$02BEDBでYレジスタにセットしているので#$FDになり、戻り値をAレジスタにセットさせるので第3引数は#$FFになります。この並びのSRをみると第1引数はどれも#$06になっています。当たり前といえば当たり前の話ですが、今の今までこの辺をぼんやりとしか理解しないでよく派手にバグらせずに済んだと思うと恥ずかしい限りですが、よくよく考えて見ればどれも既存のプログラムの延長線上だったので「ようわからんけど他と合わせておけばおk」程度の認識で何とかなっていたのでしょう。
*1:プレイヤーキャラクター
コメント