ザナクロのソース全体を、Visual Studioのプロジェクトとして取り込むことで、ロジックやパラメータ設定の追跡が容易になった。細部を追いかけ、必要ならば変更を加え、さらにビルドまで対応できる環境として、Visual Studioは大変都合が良いのだが。ソースの全体像をつかむには、それに応じたツールを用いるのが良い。例えば、今回取り上げるSoucetrailだ。
www.sourcetrail.com
Sourcetrailを用いると、ソースコード中に定義された関数、パラメータなどの定義をクリック操作でたどることができる。そのため、ソース・ファイル、関数、パラメータ間の依存関係も把握しやすい。
コードを改変に興味はないが、ただコードを読んでみたいユーザーにも、馴染みやすい環境だ。
この投稿では、Visual StudioのプロジェクトからJSONファイルを出力し、それをSourcetrailに読み込ませて、コードを追跡するまでの手順を紹介する。
なお、この投稿での環境は、前回の投稿で紹介した手順を済ませた状態を引き継いでいる。手順を実行する前に、あらかじめ前回の手順を済ませておくのが理想的だが、Visual Studioの手順を飛ばして、直接Sourcetrailにソースコードを読み込ませることもできる。
とはいえ、ザナクロのコードはShift JIS、JISが混在している。初回投稿で紹介したように、ソースコードのUTF8変換は対応しておいた方が良い。
impsbl.hatenablog.jp
impsbl.hatenablog.jp
前提
フォルダ
この投稿では、ザナクロのソース・ファイルは、次のフォルダに収録されている。
D:\user temp\work\xanadu\src
フォルダ”xanadu”を、プロジェクト・フォルダとして登録する。つまり、次のフォルダだ。
D:\user temp\work\xanadu
Sourcetrail
この投稿で紹介する作業を始める前に、Sourcetrailのインストールを完了しておく必要がある。
Source Trail : Solving programming and code problems for programmers
Sourcetrail Extension
Visual StudioからSourcetrailへ取り込むためのJSONファイルを出力するため、拡張機能を導入する必要がある。
まずVisual Studioから次の操作を行う。いずれの作業も、ザナクロのプロジェクトを開いた状態で実施している。一連の作業についてのスクリーン・キャプチャは、下記「参照:一連の作業」を展開してほしい。
ファイルメニューから「拡張機能の管理」を選択し、
”sourcetrail”を検索する。表示された”Sourcetrail Extension”をダウンロードする。自動的にインストールされるので、終了後にVisual Studioを再起動する。
再起動後に追加されたメニューから、”Create Compilation Database”を選択する。
拡張機能 > Sourcetrail > Create Compilation Database
表示された画面にてプロジェクトを選択し、次のように設定する。指示のない項目はデフォルト値を用いる。
Configuration | Release |
Platform | x86 |
Directory | d:\user temp\work\xanadu |
”Create”ボタン押下で、JSONファイルが指定フォルダに出力される。Sourcetrailへの自動インポートが提案されるが、”Finish”ボタン押下で作業を終了する。
🔎参照:一連の作業
Sourcetrail
プロジェクト
Sourcetrailを起動する。”New Project”ボタン押下で、新規プロジェクトを作成する。
”New Project”で指定する情報は任意で構わない。私は次のように設定した。
Sourcetrail Project Name | xanadu_clone |
Sourcetrail Project Location | d:\user temp\work\xanadu\sourcetrail |
"NEW SOURCE GROUP”では、次のように選択する。"Create Compilation Database”は作成済みなので無視する。
タブ”C” > C/C++ from Visual Studio > Next > Next
もしVisual Studioを利用しておらず、ザナクロのソースコードを直接読み込ませる場合は、”Empty C Source Group”を選択する。
”Edit Project”では、次の情報を追加する。指示のない項目は任意、あるいはデフォルトで構わない。
Compilation Database | D:\user temp\work\xanadu\compile_commands.json |
Additional Include Paths | D:\user temp\work\xanadu\include C:\Program Files (x86)\Embarcadero\Studio\20.0\include\windows\crtl |
”Additional Include Paths”に指定したパスの一方は、RAD Studio (C++ Builder)が提供するWindows用ヘッダー・ファイルの在処だ。Visual Studio同様、Sourcetrailもコードを自動的に精査して、エラーや警告を出力する。その数を少しでも軽減するための配慮だ。
なお、ザナクロのソースにはWindowsだけでなく、LinuxやFreeBSD向けに用いられるコードも含まれている。Windows環境には、これらのコンパイルに必要なリソースが含まれていないため、それに起因する警告、エラー表示は避けられない。
🔎参照:一連の作業
コードの追跡
得られた出力結果は、次の項目でまとめられている。
- ファイル
- マクロ
- 構造体
- 関数
- グローバル変数
- 型定義
関心のある所から、クリックしていく。プログラムは”main.c”から始まる。特にWindowsの場合は、次のパスに格納されているものだ。
D:\user temp\work\xanadu\src\win32
該当ファイル中に定義された関数”WinMain”が実行され、関数”start”が関数”init”を呼び…まずザナクロの画面領域が定義され、出力されていく。
次のようにクリックしていくと、「一連の作業」にまとめたように画面遷移していく。その操作に合わせてエディタ画面には、ファイルや関数のコードが連動して出力される。
Files > main.c > WinMain > start
🔎参照:一連の作業
逆さツララのダメージ
『XANADU scenario 2』には「逆さツララ」と呼ばれる地形が存在する。その地形に触れるのは、言うなればスパイクを踏みつけることとなり、そのときの装備、アイテム数に応じたダメージを負うことになる。
バグ、あるいは仕様かは分からないが、ある条件を満たすと、このダメージが200万を超えるのだという。ザナクロは、この現象を実装しているか追跡してみよう。
まずVisual Studioで「逆さツララ」を検索すると、”field.c”で次のように定義されているのが分かる。ここをスタート地点として、Sourcetrailで追跡する。
- field.c
/* 逆さツララ */ static int field_user_trapped; /* ダメージフラグ */ static void user_fall_hazard(void);
関数”user_fall_hazard”にてダメージが計算され、HPからダメージが差し引かれているのが分かる。
void user_fall_hazard(void) { int i, damage; /* ダメージの計算 */ damage = 1; for (i = 0; i < MAX_GOODS; i++) { damage += user.inventory[GOODS_WEAPON][i].stock; damage += user.inventory[GOODS_SCROLL][i].stock; damage += user.inventory[GOODS_ARMOUR][i].stock; damage += user.inventory[GOODS_SHIELD][i].stock; damage += user.inventory[GOODS_MAGIC_ITEM][i].stock; } damage *= 100; user.status.HP -= damage; format_message("DMG-%d", damage); status_update_HP(red_pixel); field_user_trapped = 1; se_play(SE_TRAPPED); }
ダメージは、int型の変数”damage”として定義されており、その最大値を考慮すれば200万を超える場合はあり得る。
実際のダメージは所持品数合計の100倍だ。”goods.c”に定義されている所持品全てを255ずつ所持していたとすると、ダメージは2,448,000になるのだが、オリジナルのプレイ中に、この状況が成立することはあり得ないだろう。
考えられるとすれば、ダメージ計算処理が繰り返し呼び出された結果として、合計ダメージ数が200万を超えた場合だが、それならば、その繰り返し回数だけ被ダメージ・メッセージが表示される。一度に200万超のダメージを被るわけではない。
ザナクロのソースを見るたびに思うのは、オブジェクト指向でもないのに、かなり整然かつ明瞭に構造化されていることだ。ツールを用いなくとも、とても読みやすい。
おそらくオリジナルでは、そのような構造化ができておらず(きっと、そのようなことを考慮する時代ではなかったように思う)、想定していなかった条件が重なった結果として、200万超のダメージが出力されたのではないか。
その200万超ダメージはカルマ、毒の取得に関連するらしいのだが、ザナクロは見事に構造化されているお陰で、それらが相互に影響することがない。カルマ、毒に関連する要素は、関数”fall_hazard”に含まれていないし、関連も無いのは、コード、並びにSourcetailの出力から明らかだ。
少なくとも、ザナクロではパラメータを改変して、意図的に所持品数を増やさない限り、「逆さツララ」200万超のダメージを被ることはないだろう。
ちなみに、
該当箇所の処理を消してしまう、あるいはロジックは変えず、100倍するところを0倍とすれば、「逆さツララ」のダメージは無効化できる。
毒の取得によって、カルマが-5されるのは、次の箇所で定義されている。
- battle.c
case OTHER_POTION: user.status.KRM = max(0, user.status.KRM - 5); goto its_poison;