5.18. 転職解析

5.18.1. 解析
5.18.1.1. 転職共通処理サブルーチン
5.18.1.2. 装備品の交換

ダーマの神殿にいる神官が行う転職手続きを処理するプログラムの逆アセンブルコードを解析する。 「転職したいキャラクターの性格によって神官が感想を漏らす」処理と、 「転職直後のキャラクターの装備をセットする」処理がファミコン版にはなかった機能だが、 前者は性格と新職業の対をキーにするデータ構造の存在をにおわせるし、 後者は職業とその装備可能アイテムの線形リスト探索処理を想像させられて、 興味をそそられる解析テーマとなっている。

5.18.1. 解析

転職処理のメインルーチンは、 他の店屋系統の共通処理サブルーチンが連なるバンク $C3 の逆アセンブルコードをファイルに書き出し、 その後半部をテキストエディターで眺めていれば見つかるものだ。

5.18.1.1. 転職共通処理サブルーチン

サブルーチン $C3E477 は、 ダーマの神殿の中央にいる神官に「はなす」と呼び出される処理である。 逆アセンブルコードで見ると大変長いプログラムのようだが、 台詞表示サブルーチン呼び出しと「はい・いいえ」メニューウィンドウの表示サブルーチンの呼び出しが多いため、 解読は本質的に容易い。

  • 台詞「ここは転職をつかさどるダーマの神殿」(ID: #$0B10) を表示する。

  • 「はい・いいえ」ウィンドウを表示して、プレイヤーの入力を待つ。

    • キャンセルボタン or 「いいえ」⇒【転職しない】へジャンプ。

  • 【だれを】

    • 台詞「どなたの職業をかえたいのじゃ?」(ID: #$0B12) を表示する。

    • ウィンドウ「だれを」を表示し、プレイヤーの入力を待つ。

      • キャンセルボタン ⇒【やっぱりやめる】へジャンプ。

    • $33D6, $BE7D, Y に選択キャラクターの並び順 (0,1,2,3) をセットする。

    • キャラクター状態取得サブルーチン $C43F87 を呼び出し、 パーティにいるキャラクター (Y) の生死状態値を取得 (A) する。

      • A & 0010h ⇒【死んでいる】へジャンプ。

    • キャラクター職業取得サブルーチン $C46951 を呼び出し、 パーティにいるキャラクター (Y) の職業 ID を取得 (X) する。

    • $33DA = X; 職業 ID

    • 職業構造体データアドレス取得サブルーチン $C4691B を呼び出し、 職業 (X) のデータアドレスを取得 (X) する。

    • $BE77 = X; 職業データアドレス

    • キャラクター主人公判定サブルーチン $C42DA1 を呼び出し、 パーティにいるキャラクター (Y) が主人公であるか、テスト (Carry) する。

      • 主人公でない ⇒【レベルテスト】へジャンプ。

    • 台詞「おろか者め! 勇者をやめたいというのか?」(ID: #$0B14) を表示する。

    • 【ほかにも】へジャンプ。

  • 【死んでいる】

    • 台詞「たわけが! 死んでおるではないか」(ID: #$0B13) を表示する。

    • 【ほかにも】へジャンプ。

  • 【やっぱりやめる】

    • 台詞「ふむ…やめると申すか?」(ID: #$0B1C) を表示する。

    • 【ほかにも】へジャンプ。

  • 【レベルテスト】

    • キャラクターレベル取得サブルーチン $C42FEB を呼び出し、 パーティにいるキャラクター (Y) のレベルを取得 (X) する。

      • X >= 20 ⇒【どの職業】へジャンプ。

    • 台詞「未熟者の分際で職を変えたいとは~」(ID: #$0B15) を表示する。

    • 【ほかにも】へジャンプ。

  • 【どの職業】

    • 台詞「○○○○がなりたいのはどの職業じゃな?」(ID: #$0B16) を表示する。

    • 職業リストウィンドウを表示して、プレイヤーの入力を待つ。

      • キャンセルボタン ⇒【やっぱりやめる】へジャンプ。

    • $33DC = A; 職業 ID

    • 職業名取得サブルーチン $C4691B を呼び出し、 職業 (A) の名前文字列 ID を取得 (X) する。

    • $BE77 = X; 名前文字列 ID

    • 台詞「○○○○は ××××になりたいと申すか?」(ID: #$0B17) を表示する。

    • 「はい・いいえ」ウィンドウを表示して、プレイヤーの入力を待つ。

      • キャンセルボタン or 「いいえ」⇒【どの職業】へジャンプ。

    • 選択した職業 ($33DC) と今の職業 ($33DA) を比較する。

      • 異なる職業である ⇒【確認】へジャンプ。

    • 台詞「○○○○はもうそれになっているぞ」(ID: #$0B18) を表示する。

    • 【どの職業】へジャンプ。

  • 【確認】

    • サブルーチン $E5F8 を呼び出し、 キャラクターの性格によっては神官がコメントを出す。

    • 台詞「一度レベル 1 に戻り~」(ID: #$0B19) を表示する。

    • 「はい・いいえ」ウィンドウを表示して、プレイヤーの入力を待つ。

      • キャンセルボタン or 「いいえ」⇒【やっぱりやめる】へジャンプ。

    • 台詞「おお 神よ ○○○○が新たな職につくことを~」(ID: #$0B1A) を表示する。

    • 効果音 (ID: #$0028) を鳴らし、鳴り終えるまで待つ。

    • サブルーチン $E79C を呼び出し、 必要ならば「さとりのしょ」をキャラクターの所持品から削除する。

    • A = $33D6; 選択キャラクター

    • X = $33DC; 選択職業

    • サブルーチン $C46987 を呼び出し、 転職後のキャラクターのパラメータ・ステータス(実は呪いが解ける)を決定する。

    • サブルーチン $C3E7CC を呼び出し、 選択キャラクターの装備品を変更する。

    • 台詞「よろしい。では今から○○○○は××××じゃ!」(ID: #$0B1B) を表示する。

    • 【ほかにも】へジャンプ。

  • 【やっぱりやめる】

    • 台詞「ふむ… やめると申すか? それもよろしかろう」(ID: #$0B1C) を表示する。

  • 【ほかにも】

    • 台詞「ほかにも転職したい者はおるかな?」(ID: #$0B1D) を表示する。

    • 「はい・いいえ」ウィンドウを表示して、プレイヤーの入力を待つ。

      • キャンセルボタン or 「いいえ」⇒【終了】へジャンプ。

    • 【だれを】へジャンプ。

  • 【転職しない】

    • 台詞「転職をせぬのじゃな? まあそれもよかろう」(ID: #$0B11) を表示する。

  • 【終了】

    • 台詞「ではゆくがよい」(ID: #$0B1E) を表示する。

    • 呼び出し元に制御を戻す。

5.18.1.1.1. 性格によっては神官がコメントを出すサブルーチン

サブルーチン $E5F8 は、転職するキャラクターの性格によって、 神官が余計な一言をはなす処理である。 以下のような流れになる。

  • 【事前条件】

    • $33D6 == 転職したいキャラクターの並び順

    • $33DC == 新職業 ID

  • キャラクター性格取得サブルーチン $C42E53 を呼び出し、 パーティーにいるキャラクター (A) の性格を取得 (Y) する。

  • 構造体データアドレス取得サブルーチン $C90501 を呼び出し、 性格 (Y) データ格納アドレスを取得 ($78) する。

  • X = $33DC; 新職業 ID

  • 性格名取得サブルーチン $C42F28 を呼び出し、 性格 (Y) の名前を表す文字列 ID を取得 (Y) する。

  • 転職したい職業の職業 ID (X) により、 性格構造体データのフィールド値を以下のように取得 (A) する。 フィールドの値は必要に応じて右シフトしたものになる。

    表 5.26 $E5F8 職業による分岐

    ID 職業 フィールド
    #$0000 せんし A = data[#$0007] & 0780
    #$0001 ぶとうか A = data[#$0008] & 0078
    #$0002 まほうつかい A = data[#$0009] & 0078
    #$0003 そうりょ A = data[#$0008] & 0780
    #$0004 しょうにん A = data[#$000A] & 0780
    #$0005 あそびにん A = data[#$000A] & 0078
    #$0006 とうぞく A = data[#$000B] & 0078
    #$0007 けんじゃ A = data[#$0009] & 0780

  • A レジスタの値を基にジャンプテーブルで定義されたアドレスにジャンプし、 転職したいキャラクターの性格と、 転職したい職業に応じたコメントをダーマの神官に話させる。

    表 5.27 $E5F8 職業による分岐

    A Address コメント
    #$0000 $E6E3 コメントなし
    #$0001 $E6E4 「ねっからの××××じゃのう」
    #$0002 $E6F4 「なんという××××じゃ」
    #$0003 $E704 「けっこう わらかしてくれるぞ」
    #$0004 $E71C 「さすが××××性格だけのことはあるのう」
    #$0005 $E726 「これも世の中がいけないからかの」
    #$0006 $E744 「ちょっと大変そうな気もするが」
    #$0007 $E762 「うけねらいかの?」
    #$0008 $E77A 「天職ともいえるナイスチョイスじゃのう」
    #$0009 $E784 「ずいぶん成長したものよのう」

  • 呼び出し元に制御を戻す。

5.18.1.1.2. 「さとりのしょ」を消費するサブルーチン

サブルーチン $E79C は必要に応じて、 転職するキャラクターの所持品から「さとりのしょ」を一つだけ消費する。

  • 【事前条件】

    • $33D6 == キャラクターのパーティでの並び順

    • $33DC == 新職業 ID

  • 新職業 ID ($33DC) が「けんじゃ」のそれ (#$0007) と同じであるかをテストする。

    • 異なる ⇒ 呼び出し元に制御を戻す。

  • A = $33D6; 並び順

  • キャラクター職業 ID 取得サブルーチン $C46951 を呼び出し、 パーティのキャラクター (A) の職業 ID を取得 (A) する。

    • 転職前の職業 (A) が「あそびにん」(#$0005) ⇒ 呼び出し元に制御を戻す。

  • A = #$0088; 「さとりのしょ」アイテム ID

  • X = $33D6; 並び順

  • アイテムサブルーチン $C44DC0 を呼び出し、 アイテム (A) をパーティのキャラクター (X) が何番目に所持しているか (A) 調べる。

    • 所持していない ⇒ 呼び出し元に制御を戻す。

  • アイテム削除サブルーチン $C4487F を呼び出し、 キャラクター (X) の所持品から(A 番目の)アイテムを削除する。

「あそびにん」が「けんじゃ」に転職する場合、「さとりのしょ」を持ち物としてあっても消費されないことがわかる。

5.18.1.2. 装備品の交換

サブルーチン $C3E7CC は装備品の交換を行う処理だ。 A レジスタの値を変えつつ、補助サブルーチンを呼び続ける。

  • 【事前条件】

    • A == 対象となる職業構造体データの格納アドレス

    • X == ??

  • $78 = A; キャラクターアドレス

  • A = 0 としてから、サブルーチン $E7ED を呼び出して、最適な武器を装備させる。

  • A = 1 としてから、サブルーチン $E7ED を呼び出して、最適なよろいを装備させる。

  • A = 2 としてから、サブルーチン $E7ED を呼び出して、最適な盾を装備させる。

  • A = 3 としてから、サブルーチン $E7ED を呼び出して、最適なかぶとを装備させる。

  • 呼び出し元に制御を戻す。

5.18.1.2.1. ある装備種別に関して最適なものを調べる

サブルーチン $E7ED は、転職キャラクターに最適な装備品を検索し、可能ならば装備させる処理である。 まず、転職したいキャラクターの持ち物から、 職業・性別・呪いを加味した上で、 装備可能な道具をすべてチェックする。 そのうち一番パラメータが上昇するものを検索する。 次に、ふくろから同様に検索する。 最後に、検索が成功していれば、その道具をそのキャラクターに装備させる、 という処理である。

  • 【事前条件】

    • $78 = キャラクターアドレス

    • A == アイテム種別 (0,1,2,3)

  • $2BB8 = A; アイテム種別 (0,1,2,3)

  • キャラクター所持品個数取得サブルーチン $C446A4 を呼び出し、 キャラクター ($78) の所持品個数を取得 (A) する。

  • $2BB2 = A; $7A = A; 所持品個数

  • $2BBA = 0; アイテムを装備品として見たときの、ステータス上昇値を格納するためのアドレスとなる。

  • $2BBC = #$FFFF; $2BBE = #$FFFF; それぞれアドレス差とアイテム ID を格納するためのアドレスとなる。

  • キャラクター所持品個数取得サブルーチン $C44708 を呼び出し、 キャラクター ($78) の最初の所持品が格納されているアドレスを取得 (Y) する。

  • $2BB6 = Y - 1; キャラクターの最初の所持品が格納されているアドレス

  • 【キャラクターの所持品を調べる】

    $7A をループカウンターとして、キャラクターの持っている道具を調べる。

    • X = $0000,Y & 00FFh; キャラクターの所持品アイテム ID

    • アイテム種別取得サブルーチン $C44FE2 を呼び出し、 アイテム (X) の種別を取得 (A) する。

      • A != $2BB8 ⇒ ループを continue する。

    • アイテム呪い属性取得サブルーチン $C4508A を呼び出し、 アイテム (X) が呪われているかどうか (Carry) を調べる。

      • 呪われている ⇒ ループを continue する。

    • キャラクター装備後パラメータ計算サブルーチン $C44A72 を呼び出し、 キャラクターが装備品を装備した場合のパラメータを取得する。

      • パラメータが $2BBA 以下 ⇒ ループを continue する。

    • $2BBA = A; パラメータ上昇値

    • $2BBC = Y - $2BB6; そのアイテムの $2BB6 基準のアドレスオフセット

    • $2BBE = X; そのアイテムのアイテム ID

  • 【所持品調査終了】

    • キャラクターの所持品個数 ($2BB2) をチェックする。

      • $2BB2 == 12 ⇒【$2BBC テスト】へジャンプ。

    • $2BB4 = 0; 候補アイテム個数カウンター

    • Y = #$FFFF; ふくろ走査カウンター

  • 【ふくろの中身を調べる】

    Y = 0 から 255 まで、ふくろの道具を調べる。

    • A = $3825,Y & #$00FF; アイテム[Y] の個数

      • A == 0 ⇒【ふくろ調査終了】へジャンプ。

    • X = $3725,Y & #$00FF; アイテム[Y] のアイテム ID

    • アイテム種別取得サブルーチン $C44FE2 を呼び出し、 アイテム (X) の種別を取得 (A) する。

      • A != $2BB8 ⇒ ループを continue する。

    • アイテム呪い属性取得サブルーチン $C4508A を呼び出し、 アイテム (X) が呪われているかどうか (Carry) を調べる。

      • 呪われている ⇒ ループを continue する。

    • キャラクター装備後パラメータ計算サブルーチン $C44A72 を呼び出し、 キャラクターが装備品を装備した場合のパラメータを取得する。

      • 呪われているか、パラメータ値が $2BBA 以下 ⇒ ループを continue する。

    • $2BBA = A; パラメータ上昇値

    • $2BBC = Y - $2BB6; そのアイテムの $2BB6 基準のアドレスオフセット

    • $2BBE = X; そのアイテムのアイテム ID

    • ++$2BB4

  • 【ふくろ調査終了】

    • $2BB4 をチェックする。

      • $2BB4 == 0 ⇒【$2BBC テスト】へジャンプ。

    • A = $2BBE; パラメータ上昇が最も大きい装備可能アイテムのアイテム ID

    • 道具袋アイテム減らしサブルーチン $C453F7 を呼び出し、 道具袋に入っているアイテム (A) の個数を 1 だけ減らす。

    • キャラクター持ち物変更サブルーチン $C44739 を呼び出し、 キャラクター ($78) の持ち物にアイテム (A) を追加し、配列を並び替える。

    • $2BBC = キャラクターの持ち物[0] アドレスからのオフセット; 装備させたいアイテムを格納しているアドレス

  • $2BBC テスト】

    • $2BBC を調べる。

      • $2BBC < 0 ⇒【終了】へジャンプ。

    • キャラクター装備変更サブルーチン $C44C1B を呼び出し、 キャラクター ($78) にアイテム (A) を装備させる。

  • 【終了】

    • 呼び出し元に制御を戻す。