5.8. ウィンドウ解析

5.8.1. 解析
5.8.1.1. $C31753 ウィンドウ構造体アクセスサブルーチン
5.8.1.2. ウィンドウ関連のサブルーチン
5.8.1.3. $C30000 ウィンドウ構造体

ウィンドウシステムを可能な限り解析する。 解析の初期段階でウィンドウ解析を済ませておくべきだったと記者は反省している。 というのも、逆アセンブリコードを解析する際に、 ウィンドウ表示サブルーチン呼び出しがそれと識別できると、 サブルーチンの役割が直感的に判明することが十分期待できるからだ。

結論を先に述べると、ウィンドウ構造体のメンバー解析が非常に手こずってしまった。 未解析の各フィールドの意味は、おいおい特定していくつもりだ。

5.8.1. 解析

記者がその昔、65816 コードの知識もまるでない時代に、 ROM イメージをバイナリエディタでテキトーに改竄し、 偶然にウィンドウデータの格納位置を発見したのが出発点だ。 メモリ空間ベースで言うと $C30000 からしばらくウィンドウデータだ。 アセンブリコードの解析能力が身についた現在となっては、 その構造体の内部にアクセスするコードを検査し、その様子を解釈することで、 どのようにしてウィンドウデータを扱っているのかを見ていく。

5.8.1.1. $C31753 ウィンドウ構造体アクセスサブルーチン

構造体アドレス取得のために用いる汎用サブルーチン $C90501 呼び出しを ROM イメージからすべて抽出したリストを眺めていると、 00 00 C3 という値を「引数」として指定している箇所がただひとつあった。 そのサブルーチン呼び出しを含むのが、サブルーチン $C31753 だ。

サブルーチン $C31753 のアセンブリコードを中身を解析すると このサブルーチンだけでウィンドウ一個のすべてのメンバーデータを読み取っていることが判明した。 ここにそのサブルーチン全体のコードを示したいところだが、紙面の都合で省くことにした。 メンバー数が少なくないため、サブルーチン全体が長い。 従って、構造体のメモリレイアウトはすぐに解析できることになる。 所定のメモリに各メンバーデータをストアして、それだけでサブルーチンは制御を呼び出し元に戻す。

5.8.1.2. ウィンドウ関連のサブルーチン

プログラム中に現れる、ウィンドウ関連のサブルーチン呼び出しを以下に示す。

表 5.12 ウィンドウ関連のサブルーチン

サブルーチン 意味
$C3165B A レジスタが示す ID のウィンドウを表示する。
$C32251 「引数」に示す ID のウィンドウを表示する。1 個。
$C3226F 「引数」に示す ID のウィンドウを表示する。2 個。
$C32296 「引数」に示す ID のウィンドウを表示する。3 個。

サブルーチン $C3165B はアキュームレータにウィンドウ ID をロードしてから呼び出すと、 そのウィンドウを表示するというものだ。 以下の使用例は、ゲーム最初の性格診断実装から引用したものだ。 プレイヤーにより入力済みの「月」に基づいて、表示する「日」ウィンドウを決定する。

C9/31A3:   2288A9C1    JSR $C1A988         ■[D4][AD]    うまれた日を おしえてください。[AC]
C9/31A7:   600B
(...)
C9/31B3:   0A          ASL A               1
C9/31B4:   AA          TAX
(...)
C9/31C5:   BF8B32C9    LDA $C9328B,X       ; ウィンドウ ID の配列。以下を参照。
C9/31C9:   225B16C3    JSR $C3165B         2
(...)
; LDA $C9328B,X のためのウィンドウ ID の配列
C9/328B:   3F00  ; 1 月は 31 日まである
C9/328D:   4100  ; 2 月は 29 日まである可能性がある
C9/328F:   3F00  ; 3 月は 31 日まである
C9/3291:   4000  ; 4 月は 30 日まである
C9/3293:   3F00  ; ...
C9/3295:   4000
C9/3297:   3F00
C9/3299:   3F00
C9/329B:   4000
C9/329D:   3F00
C9/329F:   4000
C9/32A1:   3F00

1

プレイヤーが誕生月を選択したとすると、その月の値マイナス 1A レジスタに格納されている。X = 2A により、 その月に対応するカレンダーウィンドウをマップしてある配列 $C9328B にアクセスするためのインデックスを決める。

ウィンドウ ID と対応するウィンドウの中身
ID Window
#$003F なんにちうまれウィンドウ 31 日までバージョン
#$0040 なんにちうまれウィンドウ 30 日までバージョン
#$0041 なんにちうまれウィンドウ 29 日までバージョン

2

サブルーチン $C3165B 呼び出しにより、 A レジスタに格納されている値をウィンドウ ID とするウィンドウを画面に表示する。

サブルーチン $C32251 は本来のリターン位置にある 1 バイトをウィンドウ ID として解釈して、そのウィンドウを表示するというものだ。 もちろん、リターン後のプログラムカウンターの位置を適切に修正する処理も含む。 以下に、$C32251 の使用例として、冒険の書を新規作成する処理部の抜粋を示す。 JSR $C32251 の次の 1 バイトが命令でないことに注意して読んで欲しい。

C3/BDEA:    225122C3    JSR $C32251         ■(RTL+1) ウィンドウ一枚表示
C3/BDEE:    47          ; ぼうけんのしょ(空の冒険の書のリスト)
C3/BDEF:    B06D        BCS $BE5E
C3/BDF1:    8578        STA $78
C3/BDF3:    2245C3C3    JSR $C3C345         ■名前入力関連の処理
C3/BDF7:    225122C3    JSR $C32251         ■(RTL+1) ウィンドウ一枚表示
C3/BDFB:    3A          ; 名前設定 文字リストウィンドウ
C3/BDFC:    B0EC        BCS $BDEA
C3/BDFE:    227FC3C3    JSR $C3C37F         ■名前入力関連の処理 (memmov)
C3/BE02:    226BC6C3    JSR $C3C66B         ■「ロト」テスト
C3/BE06:    900D        BCC $BE15
C3/BE08:    22D4A8C1    JSR $C1A8D4         ■あなたのなまえを いれてください。[AC]
C3/BE0C:    2901
C3/BE0E:    22F740C3    JSR $C340F7
C3/BE12:    4CF7BD      JMP $BDF7
C3/BE15:    223FEBC3    JSR $C3EB3F         ■入力が予約名かどうかチェック
C3/BE19:    B0ED        BCS $BE08
C3/BE1B:    225122C3    JSR $C32251         ■(RTL+1) ウィンドウ一枚表示
C3/BE1F:    4D          ; おとこ・おんな
C3/BE20:    B0D5        BCS $BDF7
C3/BE22:    22D5C3C3    JSR $C3C3D5         ■a = (a == #$0063 ? 0 : 1)
C3/BE26:    8D5F40      STA $405F
C3/BE29:    A90B00      LDA #$000B
C3/BE2C:    8D6140      STA $4061
C3/BE2F:    A90300      LDA #$0003
C3/BE32:    8DBA33      STA $33BA
C3/BE35:    225122C3    JSR $C32251         ■(RTL+1) ウィンドウ一枚表示
C3/BE39:    4A          ; ひょうじそくど
C3/BE3A:    B0DF        BCS $BE1B
C3/BE3C:    8D6740      STA $4067
C3/BE3F:    A96100      LDA #$0061
C3/BE42:    8DBA33      STA $33BA
C3/BE45:    225122C3    JSR $C32251         ■(RTL+1) ウィンドウ一枚表示
C3/BE49:    4B          ; ステレオ・モノラル

サブルーチン $C3226F, $C32296 は上述サブルーチン $C32251 の複数枚バージョンだ。 それぞれ 1 バイト * 2, 1 バイト * 3 をウィンドウ ID の配列としてコード中に埋め込み、 それらのウィンドウを同時に表示するというものだ。

ここで挙げたサブルーチンはすべて「ウィンドウを表示する」ものだが、 逆にウィンドウ ID を指定して「ウィンドウを消去する」サブルーチンが別に存在する。

5.8.1.3. $C30000 ウィンドウ構造体

アドレス $C30000 からすべてのウィンドウ構造体データが定義されている。 構造体オブジェクトひとつが占めるデータサイズは #$000C バイトだ。 ウィンドウオブジェクトが #$00C0 個配列されている。 いつものように ID がゼロのオブジェクトはダミーだ。

ウィンドウ構造体のメモリレイアウトは以下のようになっている。 "..." となっている部分は、データフィールドがバイト境界をまたぐことを示す。

ここはまだ書きかけだ。

表 5.13 $C30000 ウィンドウ構造体

Byte:Bit 80 40 20 10 08 04 02 01
00 ... X 座標
01 ... Y 座標
02 JSR ($3BB7,X) JSR ($1ACC,X) 高さ
03 $2A40 | #$1000 $2A40 | #$0100 カーソルタイプ カーソル位置保存処理
04 $2A40 | #$4000 効果音 $2A40 | #$0400 最大項目数 $2A40 | #$0200
05 JSR ($41D3,X) $28A8,X
06 サブウィンドウ ID [0]
07 サブウィンドウ ID [1]
08 サブウィンドウ ID [2]
09 描画ルーチン
0A
0B $29C8,X

X 座標

そのウィンドウが収まる最小矩形の左上頂点の X 座標だ。 座標系はスクリーン座標系だが、単位はピクセルではないようだ。

Y 座標

上述「X 座標」フィールドの Y バージョンだ。

そのウィンドウが収まる最小矩形の、X 軸に平行な辺の長さだ。 単位はピクセルではない。

高さ

上述「幅」フィールドの高さバージョンだ。

JSR ($1ACC,X)

サブルーチンテーブル $C31ACC のインデックスだ。

表 5.14 JSR ($1ACC,X)

処理
0 $29B0,Y = $33B8; $2998,Y = $33BA
1 $2998,Y = $33B8; $29B0,Y = $33BA
2
3 何もしない

JSR ($3BB7,X)

サブルーチンテーブル $C33BB7 のインデックスだ。 $33BC, $33BE のセットする値の決め方に影響する。 おそらくウィンド表示直後のカーソル位置を決めるサブルーチンなのではないかと思う。

表 5.15 JSR ($3BB7,X)

処理
0 $33BC$33BE を何かの計算結果とする
1 上述 0 における $33BC$33BE を入れ替えた処理
2 $33BC = $2998,Y; $33BE = $29B0,Y
3 何もしない

カーソル位置保存処理

サブルーチンテーブル $C33BBF のインデックスだ。 プレイヤーがウィンドウでの項目選択を行った直後に呼び出されるサブルーチンであり、 ここで、カーソル位置を特定のアドレスにセーブする。

カーソルタイプ

ウィンドウが持つカーソルの種類を示す値だ。

表 5.16 カーソルタイプ

カーソル
0 カーソルを持たない
1 カーソルを持つ
2 *マークが文字上を動くタイプのカーソルを持つ
3 0 と似ている

$2A40 | #$0100

To be written.

$2A40 | #$1000

To be written.

$2A40 | #$0200

To be written.

最大項目数

ウィンドウの 1 ページにある項目の数の最大値だ。 カーソル位置保存サブルーチンが、 選択項目のカーソル位置が(場合によってはページをまたぐので) 最初の項目から何番目に相当するのかを計算するために用いる。

$2A40 | #$0400

To be written.

効果音

ウィンドウ表示時に効果音 (ID: #$0050) を鳴らすか否かを表す。 これが 1 である代表的なものは 「はい・いいえ」ウィンドウだ。

$2A40 | #$4000

To be written.

$28A8,X

To be written.

JSR ($41D3,X)

サブルーチンテーブル $C341D3 のインデックスだ。 他のサブルーチンテーブルにも、この値がインデックスとして用いられている。

サブウィンドウ ID

このウィンドウと同時に表示する他のウィンドウの ID だ。 一度に複数のウィンドウを表示するためのサブルーチンが存在するが、 それと混同してはならない。

描画ルーチン

このウィンドウの中身を、つまり項目とそれらの並び方を決めるサブルーチンのアドレスだ。

$29C8,X

不明。 すべてのウィンドウデータは、 このフィールドが 0 なので実質未使用とみなせる。