/*
 *	dfpck - FilePack ユーティリティー
 *	Copyright (C) 2002 Naoyuki Sawa <nsawa@north.hokkai.net>
 *
 *	* Mon May 13 11:51:00 JST 2002 Naoyuki Sawa <nsawa@north.hokkai.net>
 *	- 作成開始。
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>

/* [NOTE]
 * * コマンドライン引数のワイルドカードを展開するために、
 *   [プロジェクトの設定]->[リンク]->[オブジェクト/ライブラリモジュール]に
 *   「setargv.obj」を追加しています。
 */

#define VERSION		"20020514"
#define LIST_FMT	"%8d %s\n"

char arc_path[_MAX_PATH];
char arc_drive[_MAX_DRIVE];
char arc_dir[_MAX_DIR];
char arc_fname[_MAX_FNAME];
char arc_ext[_MAX_EXT];

/* fpkファイル形式に関する構造体 */
#define SIGNATURE	'FPAK'
#define FNAME_LEN	16
typedef struct _FILE_PAC_INFO {	/* パック情報 */
	unsigned long head;	/* シグネチャ */
	long famount;		/* ファイル数 */
} FILE_PAC_INFO;
typedef struct _FILE_INFO {	/* ファイル情報                         */
	char fname[FNAME_LEN];	/* ファイル名                           */
	unsigned long offset;	/* オフセット(fpkファイル先頭を0とする) */
	unsigned long size;	/* ファイルサイズ                       */
} FILE_INFO;

/* 内部データ管理用の構造体 */
typedef struct _FILE_ITEM {
	char fname[FNAME_LEN];	/* ファイル名   */
	int size;		/* データサイズ */
	char* data;		/* データ       */
} FILE_ITEM;
int n_file_items;		/* アイテム数     */
FILE_ITEM* file_items;		/* アイテムリスト */

void cmd_list(int n_fnames, char* fnames[]);
void cmd_extract(int n_fnames, char* fnames[]);
void cmd_append(int n_fnames, char* fnames[]);
void cmd_create(int n_fnames, char* fnames[]);

FILE_ITEM* find(const char* fname);
void load();
void save();

void
err(const char* fmt, ...)
{
	va_list ap;
	va_start(ap, fmt);
	 fprintf(stderr, "### ");
	vfprintf(stderr, fmt, ap);
	 fprintf(stderr, "\n");
	va_end(ap);
	abort();
}

void
usage(int err)
{
	fprintf(stderr, "dfpck - FilePack ユーティリティー (%s)\n", VERSION);
	fprintf(stderr, "Copyright (C) 2002 Naoyuki Sawa <nsawa@north.hokkai.net>\n");
	fprintf(stderr, "\n");
	fprintf(stderr, "使い方: dfpck <command> <archive[.fpk]> [filename...]\n");
	fprintf(stderr, "\n");
	fprintf(stderr, "COMMAND:\n");
	fprintf(stderr, "	c	新しい書庫ファイルを作成します。\n");
	fprintf(stderr, "	r	既存の書庫にファイルを追加します。\n");
	fprintf(stderr, "	t	既存の書庫のファイル一覧を表示します。\n");
	fprintf(stderr, "	x	既存の書庫からファイルを抽出します。\n");

	if(err) {
		abort();
	} else {
		exit(0);
	}
}

int
main(int argc, char* argv[])
{
	/* 引数無しで起動されたら、使い方を表示します。 */
	if(argc == 1) usage(0);

	/* 少なくとも、コマンドと書庫ファイル名が必要です。 */
	if(argc < 1 + 2) usage(1);

	/* 書庫ファイル名を取得します。 */
	strcpy(arc_path, argv[2]);
	_splitpath(arc_path, arc_drive, arc_dir, arc_fname, arc_ext);
	if(strlen(arc_ext) == 0) {
		strcpy(arc_ext, ".fpk");
		_makepath(arc_path, arc_drive, arc_dir, arc_fname, arc_ext);
	}

	/* コマンドによって... */
	if(strcmp(argv[1], "c") == 0) {
		cmd_create(argc - 3, &argv[3]);
	} else if(strcmp(argv[1], "r") == 0) {
		cmd_append(argc - 3, &argv[3]);
	} else if(strcmp(argv[1], "t") == 0) {
		cmd_list(argc - 3, &argv[3]);
	} else if(strcmp(argv[1], "x") == 0) {
		cmd_extract(argc - 3, &argv[3]);
	} else {
		usage(1);
	}

	return 0;
}

void
cmd_list(int n_fnames, char* fnames[])
{
	int i;
	FILE_ITEM* item_pos;

	/* 書庫ファイルを読み込みます。 */
	load();

	if(n_fnames == 0) {
		/* 全ファイルリストを表示します。 */
		for(i = 0, item_pos = file_items;
		    i < n_file_items;
		    i++, item_pos++) {
			/* ファイル情報を表示します。 */
			printf(LIST_FMT, item_pos->size, item_pos->fname);
		}
	} else {
		/* 指定されたファイルのリストを表示します。 */
		for(i = 0; i < n_fnames; i++) {
			/* ファイルを探します。 */
			item_pos = find(fnames[i]);
			if(item_pos == NULL) err("%s ファイルが見つかりません。", fnames[i]);
			/* ファイル情報を表示します。 */
			printf(LIST_FMT, item_pos->size, item_pos->fname);
		}
	}
}

void
cmd_extract(int n_fnames, char* fnames[])
{
	int retval, i;
	FILE_ITEM* item_pos;
	FILE* fp;

	/* 書庫ファイルを読み込みます。 */
	load();

	if(n_fnames == 0) {
		/* 全ファイルを抽出します。 */
		for(i = 0, item_pos = file_items;
		    i < n_file_items;
		    i++, item_pos++) {
			/* ファイルを書き込みます。 */
			fp = fopen(item_pos->fname, "wb");
			if(fp == NULL) err("%s ファイルが作成できません。", item_pos->fname);
			retval = fwrite(item_pos->data, 1, item_pos->size, fp);
			if(retval != item_pos->size) err("%s ファイルが書き込めません。", item_pos->fname);
			fclose(fp);
		}
	} else {
		/* 指定されたファイルを抽出します。 */
		for(i = 0; i < n_fnames; i++) {
			/* ファイルを探します。 */
			item_pos = find(fnames[i]);
			if(item_pos == NULL) err("%s ファイルが見つかりません。", fnames[i]);
			/* ファイルを書き込みます。 */
			fp = fopen(item_pos->fname, "wb");
			if(fp == NULL) err("%s ファイルが作成できません。", fnames[i]);
			retval = fwrite(item_pos->data, 1, item_pos->size, fp);
			if(retval != item_pos->size) err("%s ファイルが書き込めません。", fnames[i]);
			fclose(fp);
		}
	}

}

void
cmd_append(int n_fnames, char* fnames[])
{
	/* 書庫ファイルを読み込みます。 */
	load();

	/* 後は、新規作成と同じ処理です。 */
	cmd_create(n_fnames, fnames);
}

void
cmd_create(int n_fnames, char* fnames[])
{
	int retval, i;
	FILE_ITEM* item_pos;
	char path[_MAX_PATH];
	char fname[_MAX_FNAME];
	char ext[_MAX_EXT];
	FILE* fp;

	/* ファイル管理テーブルを拡張します。 */
	file_items = (FILE_ITEM*)realloc(file_items, sizeof(FILE_ITEM) * (n_file_items + n_fnames));
	if(file_items == NULL) err("ファイル管理テーブルのメモリ不足です。");
	/* まだn_file_itemsは更新しない！ */

	/* 指定されたファイルを読み込みます。 */
	for(i = 0, item_pos = file_items + n_file_items;
	    i < n_fnames;
	    i++, item_pos++) {
		/* ファイル名を格納します。 */
		_splitpath(fnames[i], NULL, NULL, fname, ext);
		_makepath(path, NULL, NULL, fname, ext);
		strlwr(path);
		if(strlen(path) > FNAME_LEN - 1) err("%s ファイル名が長すぎます。", fnames[i]);
		if(find(path) != NULL) err("%s ファイル名が重複しています。", fnames[i]);
		strcpy(item_pos->fname, path);
		/* ファイルを読み込みます。 */
		fp = fopen(fnames[i], "rb");
		if(fp == NULL) err("%s ファイルが開けません。", fnames[i]);
		retval = fseek(fp, 0, SEEK_END);
		if(retval != 0) err("%s ファイルのシークエラーです。", fnames[i]);
		item_pos->size = ftell(fp);
		rewind(fp);
		item_pos->data = (char*)malloc(item_pos->size);
		if(item_pos->data == NULL) err("%s ファイルを読み込むためのメモリ不足です。", fnames[i]);
		retval = fread(item_pos->data, 1, item_pos->size, fp);
		if(retval != item_pos->size) err("%s ファイルが読み込めません。", fnames[i]);
		fclose(fp);
	}

	/* ここでn_file_itemsを更新！ */
	n_file_items += n_fnames;

	/* 書庫ファイルを書き込みます。 */
	save();
}

FILE_ITEM*
find(const char* fname)
{
	int i;
	FILE_ITEM* item_pos;

	for(i = 0, item_pos = file_items;
	    i < n_file_items;
	    i++, item_pos++) {
		if(strcmpi(item_pos->fname, fname) == 0) return item_pos;
	}

	return NULL;
}

void
load()
{
	int retval, i;
	FILE* fp;
	FILE_PAC_INFO pac;
	FILE_INFO *info, *info_pos;
	FILE_ITEM *item_pos;

	/* 書庫ファイルを開きます。 */
	fp = fopen(arc_path, "rb");
	if(fp == NULL) err("%s 書庫ファイルが開けません。", arc_path);

	/* ヘッダを読み込みます。 */
	retval = fread(&pac, 1, sizeof pac, fp);
	if(retval != sizeof pac) err("ヘッダが読み込めません。");
	if(pac.head != SIGNATURE) err("シグネチャが不正です。");
	if(pac.famount < 0) err("ファイル数が不正です。");
	n_file_items = pac.famount;

	/* ファイル情報を読み込みます。 */
	info = (FILE_INFO*)malloc(sizeof(FILE_INFO) * n_file_items);
	if(info == NULL) err("ファイル情報を読み込むためのメモリ不足です。");
	retval = fread(info, 1, sizeof(FILE_INFO) * n_file_items, fp);
	if(retval != (int)(sizeof(FILE_INFO) * n_file_items)) err("ファイル情報が読み込めません。");

	/* ファイルを読み込みます。 */
	file_items = (FILE_ITEM*)malloc(sizeof(FILE_ITEM) * n_file_items);
	if(file_items == NULL) err("ファイル管理テーブルのメモリ不足です。");
	for(i = 0, info_pos = info, item_pos = file_items;
	    i < n_file_items;
	    i++, info_pos++, item_pos++) {
		if(memchr(info_pos->fname, '\0', FNAME_LEN) == NULL) err("ファイル名が壊れています。");
		strcpy(item_pos->fname, info_pos->fname);
		retval = fseek(fp, info_pos->offset, SEEK_SET);
		if(retval != 0) err("%s ファイル位置が壊れています。", info_pos->fname);
		item_pos->size =               info_pos->size;
		item_pos->data = (char*)malloc(info_pos->size);
		if(item_pos->data == NULL) err("%s ファイルを読み込むためのメモリ不足です。", info_pos->fname);
		retval = fread(item_pos->data, 1, item_pos->size, fp);
		if(retval != item_pos->size) err("%s ファイルサイズが壊れています。", info_pos->fname);
	}

	/* ファイル情報を開放します。 */
	free(info);

	/* 書庫ファイルを閉じます。 */
	fclose(fp);
}

void
save()
{
	int retval, i, offset;
	FILE* fp;
	FILE_PAC_INFO pac;
	FILE_INFO *info, *info_pos;
	FILE_ITEM *item_pos;

	/* ファイル名の順に並び替えます。
	 * * FILE_ITEMの先頭に直接ファイル名が格納されているので、
	 *   比較関数にはstrcmp()がそのまま使えます。
	 */
	qsort(file_items, n_file_items, sizeof(FILE_ITEM), strcmp);

	/* 書庫ファイルを作成します。 */
	fp = fopen(arc_path, "wb");
	if(fp == NULL) err("%s 書庫ファイルが作成できません。", arc_path);

	/* ヘッダを書き込みます。 */
	pac.head = SIGNATURE;
	pac.famount = n_file_items;
	retval = fwrite(&pac, 1, sizeof pac, fp);
	if(retval != sizeof pac) err("ヘッダが書き込めません。");

	/* ファイル情報を書き込みます。 */
	info = (FILE_INFO*)malloc(sizeof(FILE_INFO) * n_file_items);
	if(info == NULL) err("ファイル情報を書き込むためのメモリ不足です。");
	offset = sizeof(FILE_PAC_INFO) + sizeof(FILE_INFO) * n_file_items;
	for(i = 0, info_pos = info, item_pos = file_items;
	    i < n_file_items;
	    i++, info_pos++, item_pos++) {
		strncpy(info_pos->fname, item_pos->fname, FNAME_LEN); /* 余分を'\0'で埋めるため */
		offset = offset + 3 & ~3; /* データ開始位置を4バイト境界へ整列 */
		info_pos->offset = offset;
		info_pos->size = item_pos->size;
		offset += item_pos->size;
	}
	retval = fwrite(info, 1, sizeof(FILE_INFO) * n_file_items, fp);
	if(retval != (int)(sizeof(FILE_INFO) * n_file_items)) err("ファイル情報が書き込めません。");

	/* ファイルを読み込みます。 */
	for(i = 0, info_pos = info, item_pos = file_items;
	    i < n_file_items;
	    i++, info_pos++, item_pos++) {
		retval = fseek(fp, info_pos->offset, SEEK_SET);
		if(retval != 0) err("%s ファイルのシークエラーです。", item_pos->fname);
		retval = fwrite(item_pos->data, 1, item_pos->size, fp);
		if(retval != item_pos->size) err("%s ファイルが書き込めません。", item_pos->fname);
	}

	/* ファイル情報を開放します。 */
	free(info);

	/* 書庫ファイルを閉じます。 */
	fclose(fp);
}
