4.5. 人オブジェクト解析

4.5.1. 解析
4.5.1.1. トレース
4.5.1.2. $7E8025 Write
4.5.1.3. JSL $C92BD4
4.5.1.4. 構造体 $FF243C
4.5.2. TODO リスト

この文書では、人オブジェクトデータの概念、構造および抽出方法について述べる。

デバッグフロアにいるある女性に話しかけると、 フリーズしてしまう件を調査していたら、 「はなす」の処理の流れが部分的に見えてきた。 すなわち、フロアの切替時に「人オブジェクト」とも言うべきデータ、 その初期化ルーチンで、データを ROM 領域から引っ張ってくる処理である。

4.5.1. 解析

4.5.1.1. トレース

まず最初に GSD ではなく、Peer's Snes9x Tracer を用いてトレースを採取する (未確認だが、GSD はトレースの大部分をカットしているような気がするため)。 Num / キーが Trace On/Off のスイッチとなる。 最強女に「はなす」直前からトレースを開始する。 そして、フリーズしたと思ったら即トレース終了。 SMC ファイルのあるフォルダか、 エミュレータのあるフォルダに log ファイルが生成されているので、 それをエディタで開き、ある地点から遡行するようにコードを眺めていく……。

記者は以前の解析により、 この人物のセリフのメッセージ ID を知っている。 この値 #$1817 を検索する。そこがコード遡行の開始地点となる。

$C0/3047 E2 20       SEP #$20                 A:00CD  X:0002  Y:000E  ...
$C0/3049 A3 0D       LDA $0D,s    [$00:0859]  A:00CD  X:0002  Y:000E  ...
$C0/304B 48          PHA                      A:00CD  X:0002  Y:000E  ...
$C0/304C AB          PLB                      A:00CD  X:0002  Y:000E  ...
$C0/304D C2 20       REP #$20                 A:00CD  X:0002  Y:000E  ...
$C0/304F A0 01 00    LDY #$0001               A:00CD  X:0002  Y:000E  ...
$C0/3052 B3 0B       LDA ($0B,s),y[$CD:FB27]  A:00CD  X:0002  Y:0001  ...
$C0/3054 8F 98 59 7E STA $7E5998  [$7E:5998]  A:1817  X:0002  Y:0001  ...
$C0/3058 A3 0B       LDA $0B,s    [$00:0857]  A:1817  X:0002  Y:0001  ...
$C0/305A 1A          INC A                    A:FB26  X:0002  Y:0001  ...
$C0/305B 1A          INC A                    A:FB27  X:0002  Y:0001  ...
$C0/305C 83 0B       STA $0B,s    [$00:0857]  A:FB28  X:0002  Y:0001  ...
$C0/305E F4 7E 7E    PEA $7E7E    [$CD:7E7E]  A:FB28  X:0002  Y:0001  ...
$C0/3061 AB          PLB                      A:FB28  X:0002  Y:0001  ...
$C0/3062 AB          PLB                      A:FB28  X:0002  Y:0001  ...
$C0/3063 60          RTS                      A:FB28  X:0002  Y:0001  ...

$CDFB27 からメッセージ ID を得ていることがわかった。 $7E5998 はメッセージ ID を格納するアドレスだったことを思い出す。 これを踏まえてトレースを遡行すると、これらのラインのすぐ近くに、 明らかに重要な意味を持つルーチンを発見することができた。

$C0/CAC3 C2 30     REP #$30               A:00CD  X:0002  Y:000E  ...
$C0/CAC5 F4 7E 7E  PEA $7E7E  [$7E:7E7E]  A:00CD  X:0002  Y:000E  ...
$C0/CAC8 AB        PLB                    A:00CD  X:0002  Y:000E  ...
$C0/CAC9 AB        PLB                    A:00CD  X:0002  Y:000E  ...
$C0/CACA DC B0 06  JML [$06B0][$CD:FB26]  A:00CD  X:0002  Y:000E  ...

$CD/FB26 00 17     BRK #$17               A:00CD  X:0002  Y:000E  ...
*** BRK

$C0CACAJML 命令において、 $0006B0 に格納されている値 #$CDFB26 をサブルーチンのアドレスとして ジャンプしている。 そのサブルーチン $CDFB26 でいきなり BRK しているが、 オペランド #$17 のあるアドレスの次のアドレスにある値 #$18 と合わせれば、 この BRK 命令がメッセージ表示処理そのものであることが推測できる。

ということは次にすることは、$0006B0 に値がセットされるところを探すことだ。 トレースをもう少し遡ると、以下のようなロード命令を発見することができる。 これは最強女の場合であるが、LDA $8017,y 命令で $7E8025#$FB26 がロードされ、 $7E8057#$00CD がロードされていることがわかる。 それでは、$7E8025 に値がいつセットされるのかを GSD を使って確認することにしよう。

$C0/CA4C  B9 17 80     LDA $8017,y[$7E:8025]  A:168F  X:0002  Y:000E ...
$C0/CA4F  8F B0 06 00  STA $0006B0[$00:06B0]  A:FB26  X:0002  Y:000E ...
$C0/CA53  B9 57 80     LDA $8057,y[$7E:8065]  A:FB26  X:0002  Y:000E ...
$C0/CA56  8F B2 06 00  STA $0006B2[$00:06B2]  A:00CD  X:0002  Y:000E ...

4.5.1.2. $7E8025 Write

最強女のいるフロアから一旦抜け、Breakpoints ダイアログで $7E8025 を Write だけチェックを入れる。 そして再び最強女のフロアに入ると、以下のラインでプログラムが一時停止する。

$C0/B6BD 99 17 80    STA $8017,y[$7E:8025]   A:CC62 X:06BE Y:000E ...
$C0/B909 9D 17 80    STA $8017,x[$7E:8025]   A:0A48 X:000E Y:0008 ...

上記いずれかのラインで、それぞれ何度か一時停止してからフロアを歩けるようになる。 あらかじめ用意しておいた「バンクごとのアセンブリコードリスト」で、 それぞれのラインを含むルーチンの状況を見ることにする。

$C0B6BD のほうは長いルーチンなので見たくない。 というか、STA 命令の直前で LDA #$CC62 していることを確認すれば十分。 定数を $8017,Y にロードしているだけなので、 ここは今調べたいこととは直接関係がないとみなして飛ばす。

$C0B909 のほうは、我々の欲しいデータに近いところをアクセスしているように見える (コメントは作業用のもの)。

// $00 は $C9/2C0E あたりでセット
C0/B903:    DA          PHX 
C0/B904:    5A          PHY 
C0/B905:    BB          TYX                 x = y;
C0/B906:    A8          TAY                 y = a;  // 8 と仮定する
                                            // 台詞アクションと関係あり
C0/B907:    B700        LDA [$00],Y         // FF243C 構造体 [$08]
C0/B909:    9D1780      STA $8017,X         $8017,X = ([$00],Y:=a);
C0/B90C:    C8          INY
C0/B90D:    C8          INY                 y += 2;
C0/B90E:    B700        LDA [$00],Y         // FF243C 構造体 [$0A]
C0/B910:    29FF00      AND #$00FF          // 台詞アクションと関係あり
C0/B913:    9D5780      STA $8057,X         $8057,X = [$00],Y & 00FFh;
C0/B916:    7A          PLY 
C0/B917:    FA          PLX 
C0/B918:    60          RTS                 return;

GSD のステップ実行機能とコードリストを用いて、 このサブルーチンの呼び出し元を探す。 それは $C0B2BA であることが判明する。 このルーチンは頻繁に JSL $C92BD4 を行っている。 このルーチン呼び出しが実に特徴的である―――。

4.5.1.3. JSL $C92BD4

ルーチン $C0B2BA は長いので、アセンブリコードリスト全体の掲載は省略する。 ポイントは、数回行われる JSL $C92C39 である。 JSL 先のサブルーチンから RTL する際、 プログラムカウンタ (PC) は通常より 11 バイト先にズレているのである。

C0/B317:    22392CC9    JSR $C92C39         ■a = $40 処理の結果
C0/B31B:    00          // ($09,S),1 - ゼロかそうでないかが重要
C0/B31C:    0B00        // ($09,S),2 - $00,X との Multiplier
C0/B31E:    3C24        // ($09,S),4 - *P
C0/B320:    FF          // ($09,S),6 - $42 へ加算
C0/B321:    0000        // ($09,S),7 - *P と加算 -> $40 と加算 (3byte)
C0/B323:    3F          // ($09,S),9 - $40 と AND する
C0/B324:    0000        // ($09,S),A - 上位バイトが非ゼロであれば、シフト操作になる
                        // 
C0/B326:    20D1B8      JSR $B8D1           ■$7E8557,(y=a) = y; if a != 0028h.
C0/B329:    A90500      LDA #$0005          a = 0005h;
C0/B32C:    20EAB8      JSR $B8EA           // [$00],Y 関係; ここの処理が謎
C0/B32F:    A90800      LDA #$0008          a = 0008h;
// !! メッセージアクションアドレスセット !!
C0/B332:    2003B9      JSR $B903           ■$8017,y = [$00],a; $8057,y = [$00],a+2 & 00FFh;
C0/B335:    22392CC9    JSR $C92C39         ■a = $40 処理の結果
C0/B339:    00          // 上と同じように参照される
C0/B33A:    0B00        //
C0/B33C:    3C24FF      //
C0/B33E:    FF          //
C0/B33F:    0000        //
C0/B341:    C0          //
C0/B342:    3F00        //

サブルーチン $C92C39 は、スタックポインタ (S) を巧みに利用して、 この 11 バイトのデータを参照する。 そして、このサブルーチンをつぶさに解析すれば、 この 11 バイトの各値がどのような意味を持ち、 どのように使われるのかが(おおまかに)わかる。 それが上のコードのコメントに殴り書きしたものである。

$FF243C から #$000B バイトの構造体が、連続して配置されていることがわかる。 あとはバイナリダンプをロムイメージから抽出して、データの意味のアタリをつける。

4.5.1.4. 構造体 $FF243C

アドレス $FF243C から #$0B バイトの構造体のようなデータが、 実に #$74E 個直列して格納されている。 この構造体のデータの意味は、以下のようなものである。

表 4.16 構造体 $FF243C メモリレイアウト

Byte:Bit 80 40 20 10 08 04 02 01
00 人グラフィック ID (Lo) Unknown
01 向き 人グラフィック ID (Hi)
02 X 座標 (Lo)
03 Y 座標 (Lo) X 座標 (Hi)
04 未使用 はなす時に向き固定 十字キーの処理関係か 座標基準値のようなもの Y 座標 (Hi)
05 歩き方処理アドレス
06
07
08 はなす処理アドレス
09
0A

Unknown となっているメンバは、現在調査中である。

人グラフィック ID

フロアに入った瞬間におけるその人物のグラフィック種別を示す値である。 例えば、#$00 は主人公のそれであり、#$01 はハッサンのそれであり、 商人・神父・おばさん・バニー・ネコ等のグラフィックなどを特定できる値である。 この ID とグラフィックの対応表は、 ドラクエビューア [URL2] で知ることができる。

向き

フロアに入った瞬間にその人物がどの方向を向いているかを示すものである。

表 4.17 向き

向き
0
1
2
3

X 座標

フロアに入った瞬間におけるその人物の位置の X 座標である。 ただし、メンバ「座標基準値のようなもの」からのオフセット値である可能性がある。

Y 座標

フロアに入った瞬間におけるその人物の位置の Y 座標である。 ただし、メンバ「座標基準値のようなもの」からのオフセット値である可能性がある。

標基準値のようなもの

「X 座標」「Y 座標」に影響する値のようだが、詳しくは不明。

十字キーの処理関係か

十字キーを押したときに、その人物がどう移動するかを示す値かもしれない。 詳細は不明。

はなす時に向き固定

その人物にはなすコマンドを使用したときに、 その人物がこちらを振り向くかどうかを意味する値かもしれない。

歩き方処理アドレス

その人物がどのように歩き回るかを実装したサブルーチンのアドレスである。

はなす処理アドレス

その人物にはなすコマンドを使用したときに、 どのような処理をするのかを実装したサブルーチンのアドレスである。

4.5.2. TODO リスト

  • 構造体 $FF243C の各メンバの正確な意味を把握する