無圧縮zipを作ってみる(2)

 本来なら、これだけでzipファイルが完成しそうなものですが、いかんせんこの方式では古すぎるため、大部分のzipアーカイバは、データ不足を理由に展開ができません。最近のzipアーカイバはファイルの頭から読み込むのではなく、ファイルのおしりにあるファイル構造が定義されたヘッダデータを読み取ってから、おのおののアーカイブデータにアクセスするようになっています。

zipファイルの構成

 アーカイブデータに関する定義(中央ディレクトリ)は、アーカイブデータの数だけファイル内に存在します。中央ディレクトリのヘッダ内容は下記の構造に加え、ファイル名、拡張データ、コメントの可変長データが続きます。

struct CentralDirHeader
{
	unsigned int signature;
	unsigned short madever;
	unsigned short needver;
	unsigned short option;
	unsigned short comptype;
	unsigned short filetime;
	unsigned short filedate;
	unsigned int crc32;
	unsigned int compsize;
	unsigned int uncompsize;
	unsigned short fnamelen;
	unsigned short extralen;
	unsigned short commentlen;
	unsigned short disknum;
	unsigned short inattr;
	unsigned int outattr;
	unsigned int headerpos;
};

signatureでは、0x04034B50(KB0304)を指定します。

madeverでは、zipファイルを作成する際に利用したフォーマットのバージョンを指定します。今回はVer 1.0をあらわす「10」を代入します。

needver,option,comptype,filetime,crc32,filedate,compsize,uncompsize,fnamelen,extralenは、ZipHeaderで宣言されている同名の変数と同じ意味なので、参照先となるアーカイブデータと同じ値を使用します。全く同じデータがひとつのzipファイルに繰り返して格納されることになりますが、これは過去のzipフォーマットとの互換性を維持するためなので、アーカイブデータで宣言されているデータとは違うものを入れてしまわないようにしましょう。

commentlenでは、アーカイブデータにたいするコメントのサイズを指定します。コメントがない場合は0になります。

disknumでは、ファイルの始まるディスクの番号を指定することになりますが、0でも特に問題はありません。

inattrでは、アーカイブデータの属性を指定します。0x0ならバイナリデータ、0x1ならテキストデータとなり、アーカイバによっては、テキストデータ属性のデータは、展開時に改行コードなどを修正することもあります。

outattrでは、オリジナルのファイルが持っていたファイル属性を指定します。これはOSによって値が変わってきますが、ウィンドウズならばGetFileAttributes()で取得した値を格納することになります(たとえばファイルが隠し属性ならFILE_ATTRIBUTE_HIDDEN=2となります)。

headerposには対応するアーカイブデータの始まるファイル位置(ヘッダの先頭)を入力します。


 zipファイルの最後には、上記で解説した中央ディレクトリ全体に関するデータを格納します。

struct EndCentDirHeader
{
	unsigned int signature;
	unsigned short disknum;
	unsigned short startdisknum;
	unsigned short diskdirentry;
	unsigned short direntry;
	unsigned int dirsize;
	unsigned int startpos;
	unsigned short commentlen;
};

signatureでは、0x06054B50(KB0506)を指定します。

disknum,startdisknumには中央ディレクトリに関するディスク番号をいれますが、これらは0で問題ありません。

diskdirentry,direntryにはそれぞれ、ファイルに存在するアーカイブデータの総数を代入します。

dirsizeには、中央ディレクトリ(KB0304)のすべてのヘッダを含めたサイズを指定します。

startposには始めの中央ディレクトリが置かれている場所を指定します。たとえばKB0304グループが500バイト目から始まるのであれば、startposには500が入ります。

commentlenにはこのヘッダに続くzipファイル自体に対するコメントのサイズを入力します。コメントがない場合は0になります。


 以上を踏まえ、Windows標準のzipアーカイバで開くことのできる無圧縮zipファイルを生成するプログラムを作ってみましょう。

 構造体をそのまま保存すると、変なデータが紛れ込むこともあるので、構造体からファイルに出力する際はfwrite(&header,...)のようにはせず、個別に変数を保存していくようにしましょう。

// 無圧縮zipを作成するテストプログラム
#define ARCHIVECOUNT 3

void _tmain()
{
	InitCRC32();

	char *teststrs[ARCHIVECOUNT] = {
		"真の友情は、前と後ろ、どちらから見ても同じもの。\r\n"
		"前から見ればバラ、後ろから見ればトゲなどというものではない。",
		"近頃の若い者云々という中年以上の発言は、"
		"おおむね青春に対する嫉妬の裏返しの表現である。",
		"Whatever you do will be insignificant, "
		"but it is very important that you do it."
	};
	

	// ヘッダ情報の作成
	ZipHeader zipheader[ARCHIVECOUNT];
	CentralDirHeader archeader[ARCHIVECOUNT];
	memset(zipheader, 0, sizeof(ZipHeader) * ARCHIVECOUNT);
	memset(archeader, 0, sizeof(CentralDirHeader) * ARCHIVECOUNT);

	char filename[ARCHIVECOUNT][20];
	time_t now; tm *nowtm;
	for(int i = 0; i < ARCHIVECOUNT; i++){
		zipheader[i].signature = ZIPSIG_CENTDIR;	// PK0304
		zipheader[i].needver = 10;

		// 現在時刻を取得
		now = time(NULL);
		nowtm = localtime(&now);
		zipheader[i].filedate = GetDosDate(nowtm->tm_year + 1900, nowtm->tm_mon, nowtm->tm_mday);
		zipheader[i].filetime = GetDosTime(nowtm->tm_hour, nowtm->tm_min, nowtm->tm_sec);

		// 展開時に用いるファイル名の設定
		sprintf(filename[i], "sample%d.txt", i + 1);
		zipheader[i].fnamelen = strlen(filename[i]);
		zipheader[i].filename = filename[i];

		// ファイルサイズの設定
		zipheader[i].uncompsize = strlen(teststrs[i]);
		zipheader[i].compsize = zipheader[i].uncompsize;

		zipheader[i].crc32  = GetCRC32((unsigned char*)teststrs[i], zipheader[i].uncompsize);

		archeader[i].signature = ZIPSIG_ARCHIVEFILE;	// PK0102
		CopyToArchiveFileHeader(&archeader[i], &zipheader[i]);
	}


	FILE *fp = _tfopen(_T("test.zip"), _T("wb"));
	if(fp == NULL) return;

	// アーカイブデータの書き込み
	for(int i = 0; i < ARCHIVECOUNT; i++){
		// データの位置を格納
		archeader[i].headerpos = ftell(fp);

		// ヘッダ
		WriteZipHeader(fp, &zipheader[i]);
		// アーカイブデータ
		fwrite(teststrs[i], zipheader[i].compsize, 1, fp);
	}

	// 中央ディレクトリ情報開始位置の格納
	unsigned int dirstartpos = ftell(fp);

	// 中央ディレクトリの書き込み
	for(int i = 0; i < ARCHIVECOUNT; i++){
		archeader[i].madever = 10;
		WriteArchiveHeader(fp, &archeader[i]);
	}

	// 終端ヘッダの書き込み
	EndCentDirHeader endheader;
	memset(&endheader, 0, sizeof(EndCentDirHeader));
	endheader.signature = ZIPSIG_ENDCENTDIR;	// PK0506
	endheader.direntry = ARCHIVECOUNT;
	endheader.diskdirentry = endheader.direntry;
	endheader.startpos = dirstartpos;
	endheader.dirsize = ftell(fp) - dirstartpos;
	WriteEndCentDirHeader(fp, &endheader);

	fclose(fp);
}
 プログラムを実行すると、プロジェクトファイルがあるフォルダに、3つのテキストファイルが収納された「test.zip」が生成されます。

展開イメージ

 CRC32を生成したり、構造体に関する処理のヘッダやソースファイルを含めたプロジェクトファイルはこちらのリンクよりダウンロードしていただけます。