1. k4nagatsuki
  2. cwxeditor

Wiki

Clone wiki

cwxeditor / AnalyzeDataDetail

CardWirth データ構造の調べ方

ここではCWXEditorの最初の開発者がどのようにしてCardWirthのデータ構造を調べたかを解説していきます。

前提となる知識

  • ある程度のコンピュータデータの知識。ビットとバイト、整数の表現方法なども分かっていればベターです。
  • 十六進数。ほとんどの場面で十六進数の数値を扱います。
  • バイナリエディタの使い方。自分の使い慣れているものでOKです。
  • プログラミングの知識も多少あればよりよいですが無くてもなんとかなるでしょう。

どのようにデータがファイルに書き込まれているか

CardWirthのデータは無圧縮のバイナリデータです。例えば次のようなデータをファイルに格納する事を考えてください。数値の右側は十六進数表現です。

int a = 123;    // 0x0000007B
int b = 1234;   // 0x000004D2
int c = 123456; // 0x0001E240

4バイトの整数が3つです。CardWirthのやり方では、ファイルの内容は次のようになります。

7B 00 00 00 D2 04 00 00 40 E2 01 00

全部で12バイトのデータが並んでいます。4バイトの整数が3つですから、長さは合っている事が分かると思います。4バイト毎に改行を入れてみましょう。

7B 00 00 00
D2 04 00 00
40 E2 01 00

最初に挙げたデータと見比べてみてください。十六進数の値が1バイトずつに区切られ、逆順に並んでいる事が分かるのではないでしょうか。

7B 00 00 00  <-->  0x 00 00 00 7B
D2 04 00 00  <-->  0x 00 00 04 D2
40 E2 01 00  <-->  0x 00 01 E2 40

どうして逆にするの、などといった理由の説明は省くとして(興味があったら調べてみてください!)、このやり方を「桁の小さい方が後ろ」という意味で「リトルエンディアン」といいます。CardWirthのデータは、ほとんどがこのリトルエンディアンで保存されています。

例えば体力が999(0x000003E7)のキャラクタのデータを覗くと、次のような4バイトがどこかに見つかるはずです。

E7 03 00 00

4バイトの数値の他にどんな値がある?

CardWirthのデータの中によく出てくるのは次のようなものです。

  • byte ... 1バイトの数値。1バイトしかないのでリトルエンディアンとか関係無い。
  • short ... 2バイトの数値。WORDとも呼ばれるが意味は同じ。
  • int ... 4バイトの数値。DWORDとも呼ばれるが意味は同じ。
  • 文字列 ... intでの文字数の後に、その文字数分だけ実際の文字のデータが続く。
  • 画像 ... intでのデータサイズの跡に、そのサイズ分だけ画像データが続く。

CWXEditorのソースコードを覗くと(CardWirthのデータ読み込み関係のコードはほとんどcwl.dの中にあります)、他にubyte、ushort、uintといったものが出てきますが、これはマイナス値を扱わないbyte、short、intという事です。マイナスには絶対にならない数、例えば能力値やラウンド数などはこれになっている場合があります(CardWirthではマイナスのデータはあまり出てこないので、むしろこちらの方が多いかもしれません)。

基本的なものはこれだけで、後は特殊な例外なので、これだけ覚えておけば充分にCardWirthのデータを解析する事ができます。

実際に解析してみる

では、実際にデータの内容を調べてみましょう。今回のターゲットはシナリオの概要が入っている"Summary.wsm"です。空っぽのシナリオを作って、バイナリエディタでSummary.wsmを開いてみます。シナリオ名は「解析ターゲット」です。

00 00 00 00 0F 00 00 00 89 F0 90 CD 83 5E 81 5B    ........解析ター
83 51 83 62 83 67 00 00 00 00 00 01 00 00 00 00    ゲット..........
00 00 00 00 00 00 00 00 40 9C 00 00 00 00 00 00    ........@.......
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00    ................

私の使っているStirlingというバイナリエディタではこのように表示されます。一目で分かるのは、シナリオのタイトルがどこにあるかです。CardWirthの文字列データは、最初に4バイトの数値で文字列の長さ、続けて実際の文字列という順でファイルに書き込まれます。「解析ターゲット」とある部分の前の4バイトを見ると、0F 00 00 00となっています。0Fは十進数では15なので、その次から15バイト分が文字列ということです。

試しにその部分だけ切り出してみると次のようになっています。これがタイトルという事です。

            0F 00 00 00 89 F0 90 CD 83 5E 81 5B
83 51 83 62 83 67 00

どうやらSummary.wsmは4バイトの謎の数値の後にタイトルの文字列が格納されるという構造になっているようです。

最初の4バイトはなんなのでしょう? シナリオを編集して、どのようにデータが変化するか見てみましょう。貼紙に画像を追加してみます。するとデータが次のようになりました。

A6 04 00 00 42 4D A6 04 00 00 00 00 00 00 3E 00
00 00 28 00 00 00 4A 00 00 00 5E 00 00 00 01 00
01 00 00 00 00 00 68 04 00 00 00 00 00 00 00 00
00 00 02 00 00 00 00 00 00 00 FF FF FF 00 00 00
... 長い長いデータ ...

私が作って貼紙に貼り付けた画像ファイルのサイズは1,190バイトです。十六進数だと0x000004A6。どうやら冒頭にある4バイトの数値と一致します。という事は、タイトルの前にあったのは画像データということになります。先程は画像が設定されていなかったので長さが0だったのです。

このように、実際にデータを変更してどこが変化したかを見る事により、どのデータがどの部分に対応しているのかを調べる事ができます。

自分の知りたい情報がどこに書かれているか知りたければ、その情報を変更して変化した箇所を探せばよいのです。

試しに、対象レベルがどこにあるか探してみましょう。まずは邪魔な画像データをどかせてデータを元に戻します。

00 00 00 00 0F 00 00 00 89 F0 90 CD 83 5E 81 5B    ........解析ター
83 51 83 62 83 67 00 00 00 00 00 01 00 00 00 00    ゲット..........
00 00 00 00 00 00 00 00 40 9C 00 00 00 00 00 00    ........@.......
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00    ................

次に、シナリオの対象レベルを1~9に変更して、またデータを見てみます。

00 00 00 00 0F 00 00 00 89 F0 90 CD 83 5E 81 5B
83 51 83 62 83 67 00 00 00 00 00 01 00 00 00 00
00 00 00 00 00 00 00 00 40 9C 00 00 00 00 00 00
00 00 00 00 00 00 00 00 01 00 00 00 09 00 00 00

最後の8バイトが変化しているのが分かるでしょうか。4バイトの数値で1と9があるようです。どうやら最後の8バイトが、それぞれ4バイトの対象レベル下限と対象レベル上限であるようです。

このようにして地道に調査していけば、ほとんどのデータを解析する事ができます。

おまけでSummray.wsmの全データについての情報を載せておきましょう。次のような順番でデータがファイルに書き込まれます。

  1. 貼紙の画像(画像)
  2. シナリオのタイトル(文字列)
  3. シナリオの解説(文字列)
  4. シナリオの作者名(文字列)
  5. 改行区切りの必要クーポン(文字列)
  6. クーポンの必要数(uint)
  7. 開始エリア+データバージョン(uint)
  8. ステップ数(uint)。この数だけ9.~11.を繰り返す。
  9. ステップ名(文字列)
  10. ステップの初期選択値(uint)
  11. ステップの値(文字列)×10
  12. フラグ数(uint)。この数だけ13.~16.を繰り返す。
  13. フラグ名(文字列)
  14. フラグの初期選択値(byte)。0がFALSE、1がTRUE。
  15. フラグのTRUE値(文字列)
  16. フラグのFALSE値(文字列)
  17. 不明(uint)。常に0
  18. 対象レベル下限(uint)。データバージョンが0の場合は存在しない
  19. 対象レベル上限(uint)。データバージョンが0の場合は存在しない

その他解析のヒント

データバージョンについて

先程のSummary.wsmのデータの中に奇妙な数値があった事には気づいているでしょうか。データの中程に、40 9C 00 00というものがあります。リトルエンディアンなので逆順にすると0x00009C40。十進数に直すと40000。特に入力した覚えもないのになぜ40000なんて数値が入っているのでしょうか。

しかもこの40000、開始エリアにIDが1のエリアを設定すると40001になります。ID10のエリアを選択すると40010。どうも開始エリアのIDに謎の数値40000が足されているようです。

これ、実はCardWirthのデータバージョンを示しています。CardWirthEditorのシナリオ概略ダイアログにあった4という数値(1.28時点)がそれです。CardWirthでは、たびたびシナリオデータのバージョンアップが行われてきました。そのバージョンを、1万を掛けて開始エリアIDに足す形で格納しているのです。

なぜそんな面倒な事をしているかというと、CardWirthの最初のバージョンでデータのバージョンアップというものが想定されていなかったせいではないかと思います。データバージョンを入れる領域を確保していなかったのです。そのため、苦肉の策として開始エリアの情報と合体させたのだと思われます。

CardWirthのデータの中には、これと同様に1万をかけた上で他のデータと合体させられたデータバージョンが随所にあります。次に挙げるのがデータバージョンと合体しているデータです。

  • シナリオ概略の開始エリアID
  • エリア・バトル・パッケージのID
  • キャスト・特殊技能・アイテム・召喚獣・情報カードのID
  • イベントコンテントの後続コンテント数
  • 背景セルの横幅

余談ですが、こうした例を見る限り、エリア等のIDは10000以上だと正しく機能しなくなるようです。ID 10000以上のエリアやカードを作らないように気をつけましょう。

見つけたいデータがどこにあるか知るには

例えば特定のイベントコンテントのデータ構造を知りたいと考えたとします。パッケージを作ってイベントを作成します。バイナリエディタでPackage1.widを開いて、そこではたと困ります。一体どこが今作ったイベントコンテントの場所なのでしょうか?

この中のどこが私の作った空白時間コンテントか分かりますか?

04 00 00 00 0F 00 00 00 90 56 8B 4B 83 70 83 62    ........新規パッ
83 50 81 5B 83 57 00 01 00 00 00 01 00 00 00 01    ケージ..........
00 00 00 00 13 00 00 00 83 70 83 62 83 50 81 5B    ........パッケー
83 57 83 43 83 78 83 93 83 67 00 41 9C 00 00 0A    ジイベント.A....
01 00 00 00 00 40 9C 00 00 0A 00 00 00             .....@.......

イベントコンテントの内容を手懸りにすれば見つかるかもしれません。私は空白時間に10×0.1秒を設定しました。10、0x0Aがある所が怪しいです。何箇所かあるので、さらに絞り込むために、値を11に変更してみます。

04 00 00 00 0F 00 00 00 90 56 8B 4B 83 70 83 62    ........新規パッ
83 50 81 5B 83 57 00 01 00 00 00 01 00 00 00 01    ケージ..........
00 00 00 00 13 00 00 00 83 70 83 62 83 50 81 5B    ........パッケー
83 57 83 43 83 78 83 93 83 67 00 41 9C 00 00 0A    ジイベント.A....
01 00 00 00 00 40 9C 00 00 0B 00 00 00             .....@.......

これで分かりました。最後の4バイトが0A 00 00 00から0B 00 00 00に変化しています。ここが該当箇所のようです。

他に、イベントコンテントに名前をつけてみるという手があります。空白時間コンテントの前にメッセージコンテントを起き、空白時間コンテントに名前をつけられるようにします。「ウェイト」とでもつけてみましょう。

04 00 00 00 0F 00 00 00 90 56 8B 4B 83 70 83 62    ........新規パッ
83 50 81 5B 83 57 00 01 00 00 00 01 00 00 00 01    ケージ..........
00 00 00 00 13 00 00 00 83 70 83 62 83 50 81 5B    ........パッケー
83 57 83 43 83 78 83 93 83 67 00 41 9C 00 00 06    ジイベント.A....
01 00 00 00 00 41 9C 00 00 0A 09 00 00 00 83 45    .....A........ウ
83 46 83 43 83 67 00 40 9C 00 00 0B 00 00 00 01    ェイト.@........
00 00 00 00 00 00 00 00                            ........

どの辺りが問題の空白時間コンテントであるかは一目瞭然です。

このようにして、自分で手懸りを置きながら探していけば、大抵のデータの所在は突き止められるのではないかと思います。

Updated