MamiList って何

Windows 上で動作する画像ビューア/ローダとしては、 GV, Susie, Grapholic などが有名だと思います。 主にゲーム用画像ローダとしては、 この中では Susie が最も有名なのではないでしょうか。

拙作 MamiList もそういったWindows上で動作する画像ローダの一つで、 また、エルフのゲームを中心にある程度のゲーム画像に対応しています。 また、画像だけでなく、MamiList は音声や動画の再生を行うこともできます。 PS や SS のほとんどのゲームで使われている CD-XA の BGM や音声を 再生するためのプレイヤとして一部で重宝されているみたいです。

私はそういったツールを作成している関係上、 ゲームを一通りプレイすると、 暇があればとりあえず解析作業なんかをすることがあります。 ここでは MamiList に組み込むために、 ゲームの画像や音声、動画、またはアーカイブ形式を解析した時の経験を元に せーじ(私)流のデータ解析手法を語っていきたいと思います。

解析の基本は「眺める」

どのような解析手法を用いるにしても、 とりあえず何らかのツールが必要になると思います。 せーじ流解析手法では最低限以下のツールが必要です。

があると便利です。 とりあえず、これだけあれば十分です。 「あれ?逆アセンブラは?」 もしかするとそう思う方もいるかもしれませんが、 せーじ流ではそれは必須ではありません。 どうしようもない場合に限りこれに頼ることもありますが、 基本的には バイナリDUMPツールだけで解析を行います。 そうです、せーじ流データ解析の基本はバイナリDUMPしたデータを ただ「眺める」だけなのです。 逆アセンブラを用いた画像解析手法については 過去すでに何回か紹介されているようなので、 その手法についてはそちらに任せるとして、 ここではもう一つの解析手法「眺める」について紹介していきたいと思います。

なお、バイナリDUMP ツールとして私は DOS アプリの FILMTN の 内蔵ページャを利用しています。 これは TAB 一発でテキスト/バイナリ表示を切り替えられるので非常に便利で、 なによりファイラーと一体になっており、 そのファイラー事体の使い勝っての良さが魅力的なツールです。

基本その2は「作った人の気持ちになる」

データ形式はデータを「眺め」ていると「何となく」わかってきます。 とはいうものの、その「何となく」という感覚がわからなければ なかなか解析するのは難しいかもしれません。 「何となく」を理解する。 そのためには、それを「作った人の気持ちになる」。 ということが重要です。

画像、音声、動画、アーカイブ形式。 これから解析しようというそれぞれの形式について、 少なくとも最低一つは「自分だったらこういう形式にする。」 というものを持っていることが重要です。 自分がそう思ったということは、 もしかするとこれから解析しようとしているデータの形式を考えた人もまったく同じ、 またはそれに近い形式を考えたかもしれないからです。 また、自分が思っていたものと違った場合でも、 どういうつもりでこのようなデータの並びになるデータ形式にしたのかを想像し、 この形式を「作った人の気持ちになる」ことが大切です。 それができれば、そのデータのすべてを解析したことになります。

つまり、この手法で解析を行うには、 たとえば画像なら最低でも一つ以上の画像形式について 知っている必要があるということになりますし、 画像データ圧縮ということについて 多少なりの知識を持っている必要があるということになります。 しかし、それは逆アセンブラを用いた解析手法でもあまりかわりありません。 やはり、どのような解析手段を用いるにも解析にはを行うには それなりの知識が必要になるということになります。

アーカイブ形式を解析

「眺める」にはまず目的の形式のファイルがどれかを探し当てなくてはなりません。 当たり前ですが、音声のファイルに対して、これが画像のファイルだと信じて 解析を行っても解析できるわけがないからです。 しかし、いきなりこの作業をはじめることのできるゲームは 最近では非常に少なくなっています。 多くのゲームでは複数のファイルを一つのファイルにアーカイブしたものを 利用しているからです。 ですから、まずはじめに「眺め」なくてはいけないのはアーカイブファイルであり、 この形式を解析し、アーカイブされている個々のファイルを切り出してから 各画像や音声、動画の解析作業が始まるわけです。

アーカイブ形式のパターン

それではアーカイブファイルを眺めてみるまえに、 アーカイブファイルの形式はどのようになっているのかについて考えてみましょう。 アーカイブファイルに保存されているデータには アーカイブされている各ファイルの情報と 各ファイルのデータの 2つの部分があります。 その 2つの部分の保存方法にはだいたい 3つのパターンがあります。

 +----------------+ +----------------+ +-------------+
 | アーカイブ情報 | | アーカイブ情報 | | File1の情報 |
 |----------------| |----------------| |-------------|
 |  File1の情報   | |  File1のData   | | File1のData |
 |----------------| |----------------| |-------------|
 |  File2の情報   | |  File2のData   | | File2の情報 |
 |----------------| |----------------| |-------------|
 |  File3の情報   | |  File3のData   | | File2のData |
 |----------------| |----------------| |-------------|
 |  File1のData   | |  File1の情報   | | File3の情報 |
 |----------------| |----------------| |-------------|
 |  File2のData   | |  File2の情報   | | File3のData |
 |----------------| |----------------| +-------------+
 |  File3のData   | |  File3の情報   |
 +----------------+ +----------------+
      (a)             (b)             (c)
           図 1 アーカイブ形式のパターン

一つ目のパターンは、各ファイルの情報がアーカイブファイルの先頭に すべてまとめて保存されており、 その情報の後ろに各データがまとめて保存されているパターンです。(図1(a)) ゲーム用のアーカイブファイルの形式としてはこのパターンが最も多く、 ファイルを実際に「眺めて」みたとき、データが暗号化されていない限り、 大量のファイル名が見えるので非常に簡単に解析を行うことができます。

二つ目のパターンは、一つ目のパターンの逆で、 アーカイブファイルの先頭に各ファイルのデータがまとめて保存されており、 その後ろに各ファイルの情報がまとめて保存されているパターンです。(図1(b)) ファイルのヘッダはファイルの先頭にあるものだと思い込み、 ファイルの先頭だけ眺め、ファイルの情報らしきものが見つからないので 解析をあきらめてしまうケースのある(せーじとしては) とてもいやらしいパターンです。 ゲームとしては、Leaf の ToHeart などで用いられている LeafPack 形式の アーカイブがこれにあたります。 LeafPack はさらに暗号化まで行われているので解析は容易ではなく、 これはさすがに「眺める」だけでは解析を行うことはできないでしょう。

最後のパターンは、各ファイルの情報とデータが対となって、 連続に保存されているパターンです。(図1(c)) これは LHa などが用いているアーカイブ形式です。 この形式はアーカイブファイルにファイルの追加、削除を頻繁に行う場合に 非常に有効な方法ですが、 ゲーム用のアーカイブとしてはプログラムが面倒なだけなので、 ゲームとして使われているケースは皆無だと思われます。

ゲーム用のアーカイバの作り易さからいえば、二つ目のパターン(図1(b))が 実は最も作り易いとは思うのですが、 やはりヘッダはファイルの先頭にあるべきだという先入観があるせいか、 一つ目のパターンが(図1(a)) が最も多く用いられているようです。

次にアーカイブ情報とファイルの情報にはどのようなことが記述されているか 考えてみましょう。

アーカイブ情報
アーカイブ情報には、 一般的にはそのアーカイブ形式を特定するための ID としての 文字列と、保存されているファイルの数が記録されていることが多いです。 ものによってはアーカイブ情報がない場合もあります。
ファイル情報
アーカイブファイル中のどの位置にファイルのデータがあるか、 データサイズはいくらかなどが記録されています。 一般的には
ファイル名
ファイルのデータのアーカイブファイル中の位置(オフセット)
ファイルのデータサイズ
の 3つだけが記述されているケースが一番多いようです。 ゲーム用のアーカイブはバックアップ目的のそれとは異なるので ファイルの作成日や属性を保存したりはしないのが普通です。 そういう意味で、ものによってはファイル名さえ記録されていないものも存在します。 また、データサイズやオフセットはどちらか一方さえあれば、 その前後関係から他方を求めることができるので、サイズかオフセットのどちらかしか 記録していないものもあります。 オフセットだけを記録し、 データの先頭にファイル名など、より詳しい情報を記録している AliceSoft の System3.5/3.6 で用いているようなアーカイブファイルの形式も存在します。

アーカイブファイルを実際に眺めてみよう

さて、それでは最も典型的なアーカイブファイルの先頭数バイトを 実際に「眺めて」みることにしましょう。 今回ここで「眺めて」みるのはエルフの「臭作」の画像のファイルを アーカイブしているGRAPH.ARC というファイルです。 このファイルの先頭数バイトのダンプリストを図2 に示します。

00000000 6B 02 00 00 65 76 31 31-30 62 2E 47 50 58 00 00 | k...ev110b.GPX.. |
00000010 00 00 00 00 0C 3A 00 00-B9 65 00 00 65 6E 64 31 | .....:..劫..end1 |
00000020 61 2E 47 50 58 00 00 00-00 00 00 00 C5 9F 00 00 | a.GPX.......E.. |
00000030 B4 6C 00 00 63 68 6F 69-5F 72 2E 47 50 58 00 00 | 伎..choi_r.GPX.. |
00000040 00 00 00 00 79 0C 01 00-FE 14 01 00 63 68 6F 69 | ....y.......choi |
00000050 5F 73 2E 47 50 58 00 00-00 00 00 00 77 21 02 00 | _s.GPX......w!.. |
00000060 02 37 01 00 63 68 6F 69-5F 74 2E 47 50 58 00 00 | .7..choi_t.GPX.. |
00000070 00 00 00 00 79 58 03 00-52 0A 01 00 63 68 6F 69 | ....yX..R...choi |
00000080 5F 75 2E 47 50 58 00 00-00 00 00 00 CB 62 04 00 | _u.GPX......魔.. |
00000090 25 3D 01 00 63 68 6F 69-5F 76 2E 47 50 58 00 00 | %=..choi_v.GPX.. |
000000A0 00 00 00 00 F0 9F 05 00-DE 3F 01 00 63 68 6F 69 | ....d淇..choi |
000000B0 5F 77 2E 47 50 58 00 00-00 00 00 00 CE DF 06 00 | _w.GPX......累.. |
000000C0 0F 49 01 00 63 68 6F 69-5F 78 2E 47 50 58 00 00 | .I..choi_x.GPX.. |
000000D0 00 00 00 00 DD 28 08 00-D6 E1 00 00 65 76 30 38 | ....檬..已..ev08 |
000000E0 2E 47 50 58 00 00 00 00-00 00 00 00 B3 0A 09 00 | .GPX........凱.. |
000000F0 77 B7 01 00 65 76 30 39-2E 47 50 58 00 00 00 00 | w勲.ev09.GPX.... |

    図2 臭作 GRAPH.ARC のダンプリスト

どうでしょう? こうして「眺めて」みるとファイル名のようなものが大量に見つかると思います。 最初のファイル名は 4バイト目から始まる "ev110b.GPX"、 次のファイル名は28バイト目から始まる "end1a.GPX"、 その次のファイル名は52バイト目から始まる "choi_r.GPX" と、 先頭 4バイト目から24バイト毎にファイル名らしきものが 出現していることに気が付くと思います。 ここで一つ予想してみます。 つまり、このアーカイブ形式は、図1(a)のパターンに該当し、 アーカイブ情報が 4バイトでファイル情報が 24バイトであると。 そして、24バイトのファイル情報にはファイル名、オフセット、 サイズが記録されていること。 そのように予想した上でいくつかのファイル情報から それらしくなるように表1のように分解してみます。

表1 アーカイブのダンプリストを分解してみる
16進ダンプ 文字列/10進表示
6B 02 00 00 619
65 76 31 31 30 62 2E 47 50 58 00 00 00 00 00 00 ev110b.GPX
0C 3A 00 00 14860
B9 65 00 00 26041
65 6E 64 31 61 2E 47 50 58 00 00 00-00 00 00 00 end1a.GPX
C5 9F 00 00 40901
B4 6C 00 00 27828
63 68 6F 69-5F 72 2E 47 50 58 00 00 00 00 00 00 choi_r.GPX
79 0C 01 00 68729
FE 14 01 00 70910

表1 をみると、アーカイブ情報には 619 というデータしか存在しないことが わかります。 このデータはまず最初はアーカイブファイル中の ファイル数であると考えるのが妥当でしょう。その仮定のもと解析を行い、 違えばあとで別の解釈をすることにします。

619 という数値をアーカイブファイル中のファイル数であると 仮定すると、最初のファイルのデータオフセットの 位置は計算によりだいたい予想することができます。 つまり、このアーカイブ形式が図1(a) のパターンであるとするならば、 最初のファイルのデータオフセットは

アーカイブファイル中のファイル数 × ファイル情報のサイズ + アーカイブ情報のサイズ
で求まります。 つまり、この場合
  619 x 24 + 4 	= 14860
ということになります。 この 14860 という数値は表1 の中の、 一つ目のファイル情報の 16バイト目からの 4バイトに 記録されていることがわかります。 このことからこの位置にデータオフセット情報が 記録されていると予想することができます。 どう見てもファイル情報の 0バイト目からの 16バイトはファイル名であることが わかりますから、さらに残りのファイル情報の 20バイト目からの 4バイトが ファイルサイズであると予想することができます。 このように予想した上で、 さらに次のデータのオフセットの位置を予想してみることにします。 これは
現在のデータオフセット + 現在のデータサイズ
で求めることができます。 つまり、
  14860 + 26041 = 40901
ということになるのです。 この 40901 という数値は次のデータの先ほどデータオフセットが 記録されている予想した位置に記録されていることがわかります。 さらにその次のデータオフセットの位置の計算も各自でやってみてください。 おそらく同じような結果になるとことでしょう。 以上のことから、アーカイブ情報およびファイル情報の形式は予想した通りの形式で あったことがわかります。 この形式を C言語の構造体で表現すると図3のようになるでしょう。
// sizeof long == 4 かつ リトルエンディアンの処理系に限る
// アーカイブ情報
typedef struct {
    long    num;        // アーカイブファイル数
} AINFO;
// ファイル情報
typedef struct {
    char    name[16];   // ファイル名
    long    offset;     // オフセット
    long    size;       // サイズ
} FINFO;	

図3 臭作のアーカイブ形式の構造体

これでこのアーカイブ形式に対して行った仮定はすべて成立しましたので、 めでたくアーカイブ形式の解析は終了です。 あとはこの解析結果を元にアーカイブファイルからファイルの切り出しを 行うツールを作成すればよいことになります。

GDIRリスト形式で出力してみよう

アーカイブ形式が解析できたところで、あとは画像解析などのために 実際にファイルの切り出しを行うことになるのですが、 いきなりそのすべてを切り出してしまうとハードディスクの容量が足りないという 状態になることがあるかもしれません。 また、解析したアーカイブ形式の数が増えてくると、 各種アーカイブ形式毎のソース中に ファイル切り出しに関するソースが入るのも面倒です。 Susie Plug-in を作るというのも一つ手ですが、 はじめから Plug-in を作るつもりの人ならともかく、 DOS用の画像コンバータを作成しようと思っている人には かなり面倒な作業になるでしょう。

ここではアーカイブ中のファイル情報を GDIRリスト形式で出力する方法を紹介します。 GDIRリスト形式とは拙作 MamiList など、 私が作成しているツールの多くが出力、利用している アーカイブファイル中のファイル情報を保存しているテキストファイルの形式です。 MamiList やその他のツールはこのファイルを読み込み、 リスト中の任意のファイルを切り出したり、 (それが表示可能ならば)画像を表示したり、 音声、動画を再生したり、ファイルのコンバートなどを行います。 つまり、ファイルの情報を GDIRリスト形式で出力し、ファイルの切り出しは 他のツールに任せようという方針です。

GDIRリスト形式の各行の形式は以下の通りです。

アーカイブファイル名 データオフセット データサイズ ファイル名

これはただのテキストファイルですのでプログラムをかかなくても、 メモ帳などで作成することももちろん可能です。

先ほど解析した臭作のアーカイブの情報を GDIRリスト形式で出力する 最も単純なプログラムをリスト1 に示します。 (なお、このプログラムは MS-DOS や Windows プログラミング環境以外の環境では 正しく動く保証はしませんので、それ以外の環境でプログラムするには それなりにソースを改造する必要があります。)

リスト1
#include <stdio.h>

typedef struct {
    long    num;
} AINFO;

typedef struct {
    char    name[16];
    long    offset;
    long    size;
} FINFO;

int main(int argc, char **argv) {
    char    *aname  = argv[1];
    FILE    *fp;

    if ( argc < 2 ) {
        fprintf(stderr, "usage: %s archive-file-name\n", argv[0]);
        return 1;
    }

    if ( fp = fopen(aname,"rb") ) {
        AINFO   ai;
        FINFO   fi;
        int     i;
        fread(&ai,1,sizeof(ai),fp);
        for ( i = 0; i < ai.num && fread(&fi,1,sizeof(fi),fp); i++ )
            printf("%s\t%ld\t%ld\t%s\n",aname,fi.offset, fi.size, fi.name);
        fclose(fp);

        return 0;
    }
    perror(aname);
    return 1;
}

おわりにと次回予告

今回実際に「眺めて」みた臭作のアーカイブ形式ほど 解析されることに対して無頓着なものはそれほど多くはありません。 普通はもう少し分かり難くしていたりします。 そういったものも具体例をあげて紹介できる機会があればいいなと思っています。 さて、次回(なんと次回があるらしい)は本命(?)の画像の解析について 紹介していきたいと思います。