/*
 *	pthpse.c
 *
 *	「ToHeart PSE」PGDコンバータ
 *	Copyright (C) 2003 Naoyuki Sawa
 *
 *	* Fri Jul 4 21:30:00 JST 2003 Naoyuki Sawa
 *	- 作成開始。
 */
#define STRICT
#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>

/****************************************************************************
 *	マクロ・定数
 ****************************************************************************/

#define VERSION		"20030705"

#define REG_KEY		"Software\\AQUAPLUS\\ToHeartPSE"
#define REG_VALUE	"InstallDir"

/* LEAFPACK形式
 *
 * 先頭+            0,              8:	"LEAFPACK"
 * 先頭+            8,     暗号キー長:	暗号キー
 *     .
 *     .				暗号化されたファイル
 *     .
 * 終端-24*ファイル数-3,24*ファイル数:	ファイル情報テーブル (FILEINFO構造体参照)
 * 終端-              3,            2:	ファイル数
 * 終端-              1,            1:	暗号キー長
 */

/* FILEINFO構造体
 *	テーブル全体が暗号化されています。
 *	テーブルの(           0)バイト目から暗号キーの(           0)バイト目を引く,
 *	テーブルの(           1)バイト目から暗号キーの(           1)バイト目を引く,
 *	テーブルの(暗号キー長-1)バイト目から暗号キーの(暗号キー長-1)バイト目を引く,
 *	テーブルの(暗号キー長  )バイト目から暗号キーの(           0)バイト目を引く,
 *	テーブルの(暗号キー長+1)バイト目から暗号キーの(           1)バイト目を引く,...
 * という処理で、暗号化解除します。
 *
 * + 0,8:	ベース名。8文字に満たない場合は、空白文字を詰めます。
 * + 8,4:	拡張子。4文字に満たない場合は、ヌル文字を詰めます。
 * +12,4:	ファイル位置。LEAFPACKファイル先頭からの絶対位置です。
 * +16,4:	ファイル長。
 * +20,4:	次のファイル位置。今回は使いません。
 */
typedef struct _FILEINFO {
	char fname[8 + 4];
	int pos;
	int len;
	int next;
} FILEINFO;

/* 各ファイルの内容も、ファイル情報テーブルと同様に暗号化されています。
 * ファイル情報テーブルは全エントリ通しで暗号化されていましたが、ファイルは一つづつ暗号化されています。
 * ファイルの形式については、LEAFFUL形式,LEAFCFL形式を参照してください。
 * 他にも形式がありますが、今回は使いません。
 */

/* LEAFFUL形式
 * 透過なしのフルカラー画像です。
 *
 * + 0, 8:	"LEAFFUL\0"
 * + 8, 2:	X基点。今回は使いません。
 * +10, 2:	Y基点。今回は使いません。
 * +12, 2:	幅。
 * +14, 2:	高さ。
 * +16, 4:	20固定。LZ圧縮データ開始位置を示しているようですが、今回は使いません。
 * +20,...	LZ圧縮データ。
 *
 * LZ展開データがそのまま、(B,G,R)のフルカラービットマップになります。
 */
typedef struct _LEAFFUL {
	char signature[8];
	unsigned short x;
	unsigned short y;
	unsigned short w;
	unsigned short h;
	int ofs;
} LEAFFUL;

/* LEAFCFL形式
 * 透過ありのフルカラー形式です。
 *
 * + 0, 8:	"LEAFCFL\0"
 * + 8, 2:	X基点。今回は使いません。
 * +10, 2:	Y基点。今回は使いません。
 * +12, 2:	幅。
 * +14, 2:	高さ。
 * +16, 4:	24固定。LZ圧縮データ開始位置を示しているようですが、今回は使いません。
 * +20, 4:	LZ展開データサイズ。
 * +24,...	LZ圧縮データ。
 *
 * LZ展開データの内容は、1ピクセル毎に次のいずれかの形式となります。
 *
 * (0x00)	完全透過	1バイト
 * (0xFF,B,G,R)	完全不透明	4バイト
 * (0x??,B,G,R)	半透明		4バイト
 *
 * 半透明時、背景とのマージは次のように行います。
 *
 * R出力 = ((0x?? * R) + ((0xff-0x??) * 背景R)) >> 8
 * G出力 = ((0x?? * G) + ((0xff-0x??) * 背景G)) >> 8
 * B出力 = ((0x?? * B) + ((0xff-0x??) * 背景B)) >> 8
 *
 * この計算式では 0x??=0xff のときに完全不透明にならないので、
 * 前述のように 0x??=0xff の場合を場合分けで完全不透明としてるんだと思います。
 *
 * このコンバータでは半透明マージは行わないので、上述の計算は行っていません。
 */
typedef struct _LEAFCFL {
	char signature[8];
	unsigned short x;
	unsigned short y;
	unsigned short w;
	unsigned short h;
	int ofs;
	int len;
} LEAFCFL;

/* LZ圧縮について
 * おでマル圧縮の変形版です。
 * ビット検査方向が違うのと、入力データをビット反転してから使う点が異なります。
 * 詳しくはソースを参照してください。
 */

/* ↓次の構造体は実際のファイル形式には対応していません。
 * ↓このツール内部の管理形式です。
 */
typedef struct _LEAFPACK {
	FILE* fp;		/* ファイルポインタ */
	int key_len;		/* 暗号キー長 */
	unsigned char* key;	/* 暗号キー */
	int file_cnt;		/* ファイル数 */
	FILEINFO* file;		/* ファイル情報 */
} LEAFPACK;

/****************************************************************************
 *	グローバル変数
 ****************************************************************************/

/* 画像が含まれているパックファイル名リスト */
const char* const pack_list[] = {
	"bck.pak",
	"chr.pak",
	"sys.pak",
	"vis.pak",
};
#define PACK_LIST_COUNT	(sizeof pack_list / sizeof *pack_list)

char app_dir[_MAX_DIR];		/* 「ToHeart PSE」のフォルダ名 */
char item_name[_MAX_PATH];	/* 抽出するアイテム名（s870など） */

int opt_bmp;			/* BMP出力（テスト用） */
int opt_chr;			/* 立ち絵モード（下切り取り） */

/****************************************************************************
 *	関数宣言
 ****************************************************************************/

void err(const char* fmt, ...);
void usage();
LEAFPACK* pack_open(const char* fname);
void pack_close(LEAFPACK* pack);
void decode_key(LEAFPACK* pack, void* ptr, int len);
void decode_lz(unsigned char* dst, unsigned char* src, int out_len);
unsigned char* decode_ful(unsigned char* in_buf);
unsigned char* decode_cfl(unsigned char* in_buf);
void extract(const char* item_name);
unsigned char* bmp24to32(unsigned char* in_buf);
unsigned char* bmp32to8(unsigned char* in_buf);
unsigned char* reduce(unsigned char* in_buf);

/****************************************************************************
 *	err
 ****************************************************************************/

void
err(const char* fmt, ...)
{
	va_list ap;

	va_start(ap, fmt);
	vfprintf(stderr, fmt, ap);
	va_end(ap);

	exit(1);
}

/****************************************************************************
 *	usage
 ****************************************************************************/

void
usage()
{
	fprintf(stderr, "pthpse - 「ToHeart PSE（AQUAPLUS）」PGDコンバータ（%s）\n", VERSION);
	fprintf(stderr, "Copyright (C) 2003 Naoyuki Sawa\n");
	fprintf(stderr, "使い方：\n");
	fprintf(stderr, "  pthpse [options] <item-name>\n");
	fprintf(stderr, "\n");
	fprintf(stderr, "  アイテム名に「*」と指定すると、抽出可能な全てのアイテムを抽出します。\n");
	fprintf(stderr, "  ＜例＞ pthpse -b *\n");
	fprintf(stderr, "         全てのファイルをBMPファイルとして抽出します。\n");
	fprintf(stderr, "\n");
	fprintf(stderr, "オプション：\n");
	fprintf(stderr, "  -b        -b2と同じです。\n");
	fprintf(stderr, "  -b1       ビットマップファイルをそのまま出力します。\n");
	fprintf(stderr, "  -b2       ビットマップファイルを縮小して出力します。\n");
	fprintf(stderr, "  -b3       ビットマップファイルを縮小・減色して出力します。\n");
	fprintf(stderr, "  -b/-b1/-b2/-b3のどれも指定しなければ、PGDファイルを出力します。\n");
	fprintf(stderr, "  -c        縮小時、縦にはみ出す場合は、下側を切り取ります。\n");
	fprintf(stderr, "  -d<path>  ToHeart PSEフォルダ指定 （初期値＝レジストリから取得）\n");
	exit(1);
}

/****************************************************************************
 *	main
 ****************************************************************************/

int
main(int argc, char* argv[])
{
	int retval, i, n;
	char* p;
	HKEY hKey;

	/* コマンドライン解析。 */
	for(i = 1; i < argc; i++) {
		p = argv[i];
		if(p[0] == '-') { /* オプション */
			switch(p[1]) {
			case 'b': /* BMP出力 */
				if(opt_bmp) usage(); /* 二重指定 */
				if(p[2]) {
					opt_bmp = p[2] - '0';
				} else {
					opt_bmp = 2; /* -bのみなら2 */
				}
				if(opt_bmp < 1 || 3 < opt_bmp) usage();
				break;
			case 'c': /* 立ち絵モード */
				if(opt_chr) usage(); /* 二重指定 */
				opt_chr = 1;
				break;
			case 'd': /* 「ToHeart PSE」インストールフォルダ指定 */
				if(strlen(app_dir) != 0) usage(); /* 二重指定 */
				strncpy(app_dir, &p[2], sizeof app_dir);
				break;
			default:
				usage();
			}
		} else {
			/* 抽出するアイテム名 */
			if(strlen(item_name) != 0) usage(); /* 二重指定 */
			strncpy(item_name, p, sizeof item_name);
		}
	}

	/* 「ToHeart PSE」インストールフォルダを調べます。 */
	if(strlen(app_dir) == 0) {
		retval = RegOpenKeyEx(HKEY_CURRENT_USER, REG_KEY, 0, KEY_QUERY_VALUE, &hKey);
		if(retval == 0) {
			n = sizeof app_dir;
			retval = RegQueryValueEx(hKey, REG_VALUE, 0, NULL, (unsigned char*)app_dir, (unsigned long*)&n);
			RegCloseKey(hKey);
		}
		if(retval != 0) err("「ToHeart PSE」がインストールされていないようです。");
	}

	/* アイテム名が指定されたら単独展開、指定されていなければ全展開へ。 */
	if(strlen(item_name) == 0) usage();
	if(strcmp(item_name, "*") == 0) {
		extract(NULL);
	} else {
		extract(item_name);
	}

	return 0;
}

/****************************************************************************
 *	pack_open
 ****************************************************************************/

LEAFPACK*
pack_open(const char* fname)
{
	int retval, i, j;
	char path[_MAX_PATH];
	char ext[_MAX_FNAME];
	char signature[8];
	LEAFPACK* pack;
	FILEINFO* file;

	/* LEAFPACK構造体を確保します。 */
	pack = (LEAFPACK*)calloc(1, sizeof(LEAFPACK));
	if(pack == NULL) err("%s: メモリ不足です。", fname);

	/* パックファイルを開きます。 */
	_makepath(path, NULL, app_dir, fname, NULL);
	pack->fp = fopen(path, "rb");
	if(pack->fp == NULL) err("%s: ファイルが開けません。", fname);

	/* シグネチャを確認します。 */
	retval = fread(signature, 1, 8, pack->fp);
	if(retval != 8) err("%s: シグネチャが読めません。", fname);
	if(memcmp(signature, "LEAFPACK", 8) != 0) err("%s: シグネチャが不正です。", fname);

	/* 暗号キー長を取得します。 */
	fseek(pack->fp, -1, SEEK_END);
	retval = fread(&pack->key_len, 1, 1, pack->fp);
	if(retval != 1) err("%s: 暗号キー長が読めません。", fname);

	/* 暗号キーのメモリを確保します。 */
	pack->key = (unsigned char*)calloc(pack->key_len, 1);
	if(pack->key == NULL) err("%s: メモリ不足です。", fname);

	/* 暗号キーを読み込みます。 */
	fseek(pack->fp, 8, SEEK_SET);
	retval = fread(pack->key, 1, pack->key_len, pack->fp);
	if(retval != pack->key_len) err("%s: 暗号キーが読めません。", fname);

	/* ファイル数を取得します。 */
	fseek(pack->fp, -3, SEEK_END);
	retval = fread(&pack->file_cnt, 2, 1, pack->fp);
	if(retval != 1) err("%s: ファイル数が読めません。", fname);

	/* ファイル情報テーブルのメモリを確保します。 */
	pack->file = (FILEINFO*)calloc(pack->file_cnt, sizeof(FILEINFO));
	if(pack->file == NULL) err("%s: メモリ不足です。", fname);

	/* ファイル情報テーブルを読み込みます。 */
	fseek(pack->fp, -((int)sizeof(FILEINFO) * pack->file_cnt + 3), SEEK_END);
	retval = fread(pack->file, sizeof(FILEINFO), pack->file_cnt, pack->fp);
	if(retval != pack->file_cnt) err("%s: ファイル情報テーブルが読めません。", fname);

	/* ファイル情報テーブルの暗号化を解除します。 */
	decode_key(pack, pack->file, sizeof(FILEINFO) * pack->file_cnt);

	/* 各ファイル情報について... */
	for(i = 0; i < pack->file_cnt; i++) {
		file = &pack->file[i];
		/* 処理しやすいよう、ファイル名を補正しておきます。("12345   ABC\0"→"12345.abc\0") */
		memset(path, 0, sizeof path);
		memcpy(path, file->fname + 0, 8);
		j = strlen(path) - 1;
		while(j >= 0) {
			if(!isspace(path[j])) break;
			path[j] = '\0';
			j--;
		}
		memset(ext, 0, sizeof ext);
		memcpy(ext, file->fname + 8, 4);
		j = strlen(ext) - 1;
		while(j >= 0) {
			if(!isspace(ext[j])) break;
			ext[j] = '\0';
			j--;
		}
		memset(file->fname, 0, 8 + 4); /* デバッグしやすいようにゴミを消しておく */
		_makepath(file->fname, NULL, NULL, path, ext);
		_strlwr(file->fname);
	}

	return pack;
}

/****************************************************************************
 *	pack_close
 ****************************************************************************/

void
pack_close(LEAFPACK* pack)
{
	fclose(pack->fp);
	free(pack->file);
	free(pack->key);
	free(pack);
}

/****************************************************************************
 *	decode_key
 ****************************************************************************/

void
decode_key(LEAFPACK* pack, void* ptr, int len)
{
	unsigned char* p;
	unsigned char* key;
	int key_len, key_ofs;

	p = (unsigned char*)ptr;
	key = pack->key;
	key_len = pack->key_len;
	key_ofs = 0;
	while(len) {
		*p++ -= key[key_ofs++];
		if(key_ofs == key_len) key_ofs = 0;
		len--;
	}
}

/****************************************************************************
 *	decode_lz
 ****************************************************************************/

#define		N	4096
#define		F	18

static unsigned char tmp_buf[N + F];

void
decode_lz(unsigned char* dst, unsigned char* src, int out_len)
{
	int i, k, r;
	int flags, pos, len;
	unsigned char c;

	/* スライド辞書バッファを初期化。 */
	memset(tmp_buf, 0, sizeof tmp_buf);
	r = N - F; /* 初期位置は後ろの方？ */

	/* カウンタが出力サイズに達するまで... */
	i = 0;
	while(i < out_len) {
		/* チャンクの1バイト目はフラグ。 */
		flags = *src;
		src++;
		flags ^= 0xff; /* ビット反転 */
		flags = (flags << 8) | 0xff; /* 下位8ビットは8回ループのためのマークです */
		do {
			if(i >= out_len) break;
			if(flags & 0x8000) {
				/* 1:無圧縮 */
				c = *src;
				src++;
				c ^= 0xff; /* ビット反転 */
				*(tmp_buf + r++) = *(dst++) = c;
				r &= (N - 1);
				i++;
			} else {
				/* 0:圧縮 */
				len = *(unsigned short*)src;
				src += 2;
				len ^= 0xffff; /* ビット反転 */
				pos = len >> 4;
				len = (len & 0x0f) + 3;
				for(k = 0; k < len; k++) {
					if(i >= out_len) break;
					*(tmp_buf + r++) = *(dst++) = *(tmp_buf + ((pos + k) & (N - 1)));
					r &= (N - 1);
					i++;
				}
			}
			flags <<= 1;
		} while(flags & 0xff);
	}
}

/****************************************************************************
 *	extract
 ****************************************************************************/

unsigned char*
decode_ful(unsigned char* in_buf)
{
	int len;
	LEAFFUL* hdr;
	unsigned char* out_buf;
	BITMAPFILEHEADER* bf;
	BITMAPINFOHEADER* bi;
	unsigned char* bits;

	/* 画像ヘッダを取得します。 */
	hdr = (LEAFFUL*)in_buf;
	len = hdr->w * hdr->h * 3/*RGB*/;

	/* 出力バッファを確保します。 */
	out_buf = calloc(sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) + len, 1);
	if(out_buf == NULL) err("decode_ful: メモリ不足です。");
	bf   = (BITMAPFILEHEADER*)(out_buf);
	bi   = (BITMAPINFOHEADER*)(out_buf + sizeof(BITMAPFILEHEADER));
	bits =                     out_buf + sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER);

	/* ビットマップファイル情報作成。 */
	bf->bfType    = 'MB';
	bf->bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER);
	bf->bfSize    = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) + len;

	/* ビットマップ情報作成。 */
	bi->biSize     = sizeof(BITMAPINFOHEADER);
	bi->biWidth    = hdr->w;
	bi->biHeight   = hdr->h;
	bi->biPlanes   = 1;
	bi->biBitCount = 24;

	/* LZ展開。 */
	decode_lz(bits, in_buf + hdr->ofs, len);

	return out_buf;
}

/****************************************************************************
 *	extract
 ****************************************************************************/

unsigned char*
decode_cfl(unsigned char* in_buf)
{
	int len;
	LEAFCFL* hdr;
	unsigned char* out_buf;
	unsigned char* tmp_buf;
	BITMAPFILEHEADER* bf;
	BITMAPINFOHEADER* bi;
	unsigned char* bits;
	unsigned char* src;
	unsigned char* dst;
	unsigned char* dst_end;
	unsigned char c;

	/* 画像ヘッダを取得します。 */
	hdr = (LEAFCFL*)in_buf;
	len = hdr->w * hdr->h * 4/*ARGB*/;

	/* 出力バッファを確保します。 */
	out_buf = calloc(sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) + len, 1);
	if(out_buf == NULL) err("decode_cfl: メモリ不足です。");
	bf   = (BITMAPFILEHEADER*)(out_buf);
	bi   = (BITMAPINFOHEADER*)(out_buf + sizeof(BITMAPFILEHEADER));
	bits =                     out_buf + sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER);

	/* ビットマップファイル情報作成。 */
	bf->bfType    = 'MB';
	bf->bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER);
	bf->bfSize    = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) + len;

	/* ビットマップ情報作成。 */
	bi->biSize     = sizeof(BITMAPINFOHEADER);
	bi->biWidth    = hdr->w;
	bi->biHeight   = hdr->h;
	bi->biPlanes   = 1;
	bi->biBitCount = 32;

	/* LZ展開バッファを確保します。 */
	tmp_buf = (unsigned char*)calloc(hdr->len, 1);
	if(tmp_buf == NULL) err("decode_cfl: メモリ不足です。");

	/* LZ展開。 */
	decode_lz(tmp_buf, in_buf + hdr->ofs, hdr->len);

	/* 透過圧縮展開。 */
	src = tmp_buf;
	dst = bits;
	dst_end = bits + len;
	while(dst < dst_end) {
		c = *src++;
		switch(c) {
		case 0x00:
			*dst++ = 0;
			*dst++ = 0xff; /* 透過部分がわかりやすいように緑にしました */
			*dst++ = 0;
			*dst++ = 0;
			break;
		case 0xff:
			*dst++ = *src++;
			*dst++ = *src++;
			*dst++ = *src++;
			*dst++ = 0xff;
			break;
		default:
			*dst++ = *src++;
			*dst++ = *src++;
			*dst++ = *src++;
			*dst++ = c;
			break;
		}
	}

	/* LZ展開バッファを開放します。 */
	free(tmp_buf);

	return out_buf;
}

/****************************************************************************
 *	extract
 ****************************************************************************/

void
extract(const char* item_name)
{
	int retval, i_pack, i_file, done, alpha;
	LEAFPACK* pack;
	FILEINFO* file;
	char fname[_MAX_PATH];
	char cmd[_MAX_PATH];
	unsigned char *in_buf, *out_buf;
	char bmp_path[_MAX_PATH];
	BITMAPFILEHEADER* bf;
	FILE* fp;

	done = 0;	/* 単独展開時、指定エントリ展開後に1になります。 */

	/* 各パックファイルについて... */
	for(i_pack = 0; !done && i_pack < PACK_LIST_COUNT; i_pack++) {
		/* パックファイルを開きます。 */
		pack = pack_open(pack_list[i_pack]);

		/* 各ファイルについて... */
		for(i_file = 0; !done && i_file < pack->file_cnt; i_file++) {
			file = &pack->file[i_file];

			/* 全展開か、または指定エントリに一致したら... */
			_splitpath(file->fname, NULL, NULL, fname, NULL);
			if(item_name == NULL || _strcmpi(fname, item_name) == 0) {
				/* 全展開なら、処理中のファイルを表示。 */
				if(item_name == NULL) printf("%s(%3d): %s\n", pack_list[i_pack], i_file, file->fname);

				/* 安全な開放のために、NULLに初期化しておきます。 */
				in_buf = NULL;
				out_buf = NULL;

				/* 入力バッファを確保します。 */
				in_buf = (unsigned char*)calloc(file->len, 1);
				if(in_buf == NULL) err("%s: メモリ不足です。", file->fname);

				/* ファイルを読み込みます。 */
				fseek(pack->fp, file->pos, SEEK_SET);
				retval = fread(in_buf, 1, file->len, pack->fp);
				if(retval != file->len) err("%s: ファイルが読めません。", file->fname);

				/* 暗号化を解除します。 */
				decode_key(pack, in_buf, file->len);

				/* ファイル形式によって... */
				       if(memcmp(in_buf, "LEAFFUL\0", 8) == 0) {
					out_buf = decode_ful(in_buf); /* =>24bpp */
					out_buf = bmp24to32(out_buf); /* =>32bpp */
					alpha = 0;
				} else if(memcmp(in_buf, "LEAFCFL\0", 8) == 0) {
					out_buf = decode_cfl(in_buf); /* =>32bpp */
					alpha = 1;
				} else {
					if(item_name == NULL) goto SKIP; /* 全展開ならスキップ */
					err("%s: 未対応のファイル形式です。", file->fname);
				}

				if(opt_bmp == 1) goto L10;	/* BMP出力へ */

				/* 縮小。 */
				out_buf = reduce(out_buf);

				if(opt_bmp == 2) goto L10;	/* BMP出力へ */

				/* グレイスケール変換。 */
				out_buf = bmp32to8(out_buf);
L10:
				/* ビットマップを出力します。 */
				_splitpath(file->fname, NULL, NULL, fname, NULL);
				_makepath(bmp_path, NULL, NULL, fname, ".bmp");
				fp = fopen(bmp_path, "wb");
				bf = (BITMAPFILEHEADER*)out_buf;
				retval = fwrite(out_buf, 1, bf->bfSize, fp);
				if(retval != (int)bf->bfSize) err("%s: ビットマップ出力に失敗しました。", file->fname);
				fclose(fp);

				/* PGD変換。 */
				if(opt_bmp == 0) {
					if(alpha) {	/* 2bitマスク付き */
						sprintf(cmd, "dpbmpcnv -q -b -f2m \"%s\"", bmp_path); /* -q必要! */
					} else {	/* 2bitマスクなし */
						sprintf(cmd, "dpbmpcnv -q -b -f2  \"%s\"", bmp_path); /* -q必要! */
					}
					if(system(cmd) != 0) err("PGD変換に失敗しました。");
					remove(bmp_path);	/* BMPはもういらないので削除 */
				}

				/* 単独展開なら、完了マーク。 */
				if(item_name != NULL) done = 1;
SKIP:
				/* バッファを開放します。 */
				free(out_buf);
				free(in_buf);
			}
		}

		/* パックファイルを閉じます。 */
		pack_close(pack);
	}

	/* 単独展開で、指定されたエントリが見つからなければ、エラー。 */
	if(item_name && !done) err("%sが見つかりません。", item_name);
}

/****************************************************************************
 *	bmp24to32
 ****************************************************************************/

unsigned char*
bmp24to32(unsigned char* in_buf)
{
	int w, h, x, y;

	BITMAPFILEHEADER* bf_in;
	BITMAPINFOHEADER* bi_in;
	int stride_in;
	unsigned char *bits_in, *pin;

	int out_size;
	unsigned char* out_buf;
	BITMAPFILEHEADER* bf_out;
	BITMAPINFOHEADER* bi_out;
	int stride_out;
	unsigned char *bits_out, *pout;

	/* 入力ビットマップ情報を取得。 */
	bf_in   = (BITMAPFILEHEADER*)(in_buf);
	bi_in   = (BITMAPINFOHEADER*)(in_buf + sizeof(BITMAPFILEHEADER));
	bits_in = (unsigned char   *)(in_buf + bf_in->bfOffBits);
	w = bi_in->biWidth;
	h = bi_in->biHeight;
	stride_in = (3 * w + 3) & ~3;

	/* 出力ビットマップ情報を作成。 */
	stride_out = 4 * w;
	out_size = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) + stride_out * h;
	out_buf = (unsigned char*)calloc(out_size, 1);
	if(out_buf == NULL) err("ビットマップ変換用バッファが確保できません。");
	bf_out   = (BITMAPFILEHEADER*)(out_buf);
	bi_out   = (BITMAPINFOHEADER*)(out_buf + sizeof(BITMAPFILEHEADER));
	bits_out = (unsigned char   *)(out_buf + sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER));

	bf_out->bfType = 'MB';
	bf_out->bfSize = out_size;
	bf_out->bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER);

	bi_out->biSize = sizeof(BITMAPINFOHEADER);
	bi_out->biWidth = w;
	bi_out->biHeight = h;
	bi_out->biPlanes = 1;
	bi_out->biBitCount = 32;

	for(y = 0; y < h; y++) {
		for(x = 0; x < w; x++) {
			pin  = bits_in  + stride_in  * (h - 1 - y) + 3 * x;
			pout = bits_out + stride_out * (h - 1 - y) + 4 * x;
			;
			*pout++ = *pin++;
			*pout++ = *pin++;
			*pout++ = *pin++;
			*pout++ = 255;
		}
	}

	/* 入力ビットマップを開放し、代わりに出力ビットマップを返します。 */
	free(in_buf);
	return out_buf;
}

/****************************************************************************
 *	bmp8to32
 ****************************************************************************/

const int DITHER[8][8] = {
         0,32, 8,40, 2,34,10,42,
        48,16,56,24,50,18,58,26,
        12,44, 4,36,14,46, 6,38,
        60,28,52,20,62,30,54,22,
         3,35,11,43, 1,33, 9,41,
        51,19,59,27,49,17,57,25,
        15,47, 7,39,13,45, 5,37,
        63,31,55,23,61,29,53,21,
};

unsigned char*
bmp32to8(unsigned char* in_buf)
{
	int w, h, x, y, r, g, b, a, c;

	BITMAPFILEHEADER* bf_in;
	BITMAPINFOHEADER* bi_in;
	int stride_in;
	unsigned char *bits_in, *pin;

	int out_size;
	unsigned char* out_buf;
	BITMAPFILEHEADER* bf_out;
	BITMAPINFOHEADER* bi_out;
	RGBQUAD* rgb_out;
	int stride_out;
	unsigned char *bits_out, *pout;

	/* 入力ビットマップ情報を取得。 */
	bf_in   = (BITMAPFILEHEADER*)(in_buf);
	bi_in   = (BITMAPINFOHEADER*)(in_buf + sizeof(BITMAPFILEHEADER));
	bits_in = (unsigned char   *)(in_buf + bf_in->bfOffBits);
	w = bi_in->biWidth;
	h = bi_in->biHeight;
	stride_in = 4 * w;

	/* 出力ビットマップ情報を作成。 */
	stride_out = (w + 3) & ~3;
	out_size = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) + sizeof(RGBQUAD) * 256 + stride_out * h;
	out_buf = (unsigned char*)calloc(out_size, 1);
	if(out_buf == NULL) err("ビットマップ変換用バッファが確保できません。");
	bf_out   = (BITMAPFILEHEADER*)(out_buf);
	bi_out   = (BITMAPINFOHEADER*)(out_buf + sizeof(BITMAPFILEHEADER));
	rgb_out  = (RGBQUAD         *)(out_buf + sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER));
	bits_out = (unsigned char   *)(out_buf + sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) + sizeof(RGBQUAD) * 256);

	bf_out->bfType = 'MB';
	bf_out->bfSize = out_size;
	bf_out->bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) + sizeof(RGBQUAD) * 256;

	bi_out->biSize = sizeof(BITMAPINFOHEADER);
	bi_out->biWidth = w;
	bi_out->biHeight = h;
	bi_out->biPlanes = 1;
	bi_out->biBitCount = 8;

	for(c = 0; c < 4; c++) {
		rgb_out[c].rgbRed       = 85 * (3 - c);
		rgb_out[c].rgbGreen     = 85 * (3 - c);
		rgb_out[c].rgbBlue      = 85 * (3 - c);
		rgb_out[c].rgbReserved  = 0;
	}
	for(c = 4; c < 256; c++) {
		rgb_out[c].rgbRed       = 0;
		rgb_out[c].rgbGreen     = 255;
		rgb_out[c].rgbBlue      = 0;
		rgb_out[c].rgbReserved  = 0;
	}

	for(y = 0; y < h; y++) {
		for(x = 0; x < w; x++) {
			pin  = bits_in  + stride_in  * (h - 1 - y) + 4 * x;
			pout = bits_out + stride_out * (h - 1 - y) + 1 * x;
			;
			b = *pin++;
			g = *pin++;
			r = *pin++;
			a = *pin++;
			;
			if(a) {
				c = (r * 3 + g * 5 + b * 2) / 10;
				c = (c + DITHER[y & 7][x & 7] + (85 - 63) / 2) / 85;
				if(c > 3) c = 3;
				c = 3 - c;
			} else {
				c = 4;
			}
			;
			*pout++ = c;
		}
	}

	/* 入力ビットマップを開放し、代わりに出力ビットマップを返します。 */
	free(in_buf);
	return out_buf;
}

/****************************************************************************
 *	reduce
 ****************************************************************************/

#define WIDTH_LIMIT	128
#define HEIGHT_LIMIT	 88

unsigned char*
reduce(unsigned char* in_buf)
{
	int x_in, y_in, x_out, y_out, r, g, b, a, n, x_base, y_base;
	double REDUCE_LEVEL;

	int w_in;
	int h_in;
	BITMAPFILEHEADER* bf_in;
	BITMAPINFOHEADER* bi_in;
	int stride_in;
	unsigned char *bits_in, *pin;

	int out_size;
	int w_out;
	int h_out;
	unsigned char* out_buf;
	BITMAPFILEHEADER* bf_out;
	BITMAPINFOHEADER* bi_out;
	int stride_out;
	unsigned char *bits_out, *pout;

	/* 入力ビットマップ情報を取得。 */
	bf_in   = (BITMAPFILEHEADER*)(in_buf);
	bi_in   = (BITMAPINFOHEADER*)(in_buf + sizeof(BITMAPFILEHEADER));
	bits_in = (unsigned char   *)(in_buf + bf_in->bfOffBits);
	w_in = bi_in->biWidth;
	h_in = bi_in->biHeight;
	stride_in = 4 * w_in;

	/* 立ち絵モード時は比率固定。 */
	if(opt_chr) {
		REDUCE_LEVEL = 640.0 / WIDTH_LIMIT;
	/* 背景モード時は比率可変。 */
	} else {
		double rl1 = (double)w_in / WIDTH_LIMIT;
		double rl2 = (double)h_in / HEIGHT_LIMIT;
		REDUCE_LEVEL = rl1 <= rl2 ? rl1 : rl2; /* 画面いっぱいになるように */
	}

	/* 出力ビットマップ情報を作成。 */
	w_out = (int)(w_in / REDUCE_LEVEL);
	h_out = (int)(h_in / REDUCE_LEVEL);
	x_base = 0;
	y_base = 0;
	if(w_out > WIDTH_LIMIT) {
		x_base = (int)((w_out - WIDTH_LIMIT) / 2 * REDUCE_LEVEL);
		w_out = WIDTH_LIMIT;
	}
	if(h_out > HEIGHT_LIMIT) {
		if(!opt_chr) y_base = (int)((h_out - HEIGHT_LIMIT) / 2 * REDUCE_LEVEL);
		h_out = HEIGHT_LIMIT;
	}
	stride_out = 4 * w_out;
	out_size = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) + stride_out * h_out;
	out_buf = (unsigned char*)calloc(out_size, 1);
	if(out_buf == NULL) err("ビットマップ変換用バッファが確保できません。");
	bf_out   = (BITMAPFILEHEADER*)(out_buf);
	bi_out   = (BITMAPINFOHEADER*)(out_buf + sizeof(BITMAPFILEHEADER));
	bits_out = (unsigned char   *)(out_buf + sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER));

	bf_out->bfType = 'MB';
	bf_out->bfSize = out_size;
	bf_out->bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER);

	bi_out->biSize = sizeof(BITMAPINFOHEADER);
	bi_out->biWidth = w_out;
	bi_out->biHeight = h_out;
	bi_out->biPlanes = 1;
	bi_out->biBitCount = 32;

	/* 縮小処理。 */
	for(y_out = 0; y_out < h_out; y_out++) {
		for(x_out = 0; x_out < w_out; x_out++) {
			/* 縮小元のピクセルを加算。 */
			r = 0;
			g = 0;
			b = 0;
			a = 0;
			n = 0;
			for(y_in = y_base + (int)(y_out * REDUCE_LEVEL); y_in < y_base + (int)((y_out + 1) * REDUCE_LEVEL); y_in++) {
				if(y_in < 0 || h_in - 1 < y_in) continue;
				for(x_in = x_base + (int)(x_out * REDUCE_LEVEL); x_in < x_base + (int)((x_out + 1) * REDUCE_LEVEL); x_in++) {
					if(x_in < 0 || w_in - 1 < x_in) continue;
					pin  = bits_in  + stride_in  * (h_in - 1 - y_in) + 4 * x_in;
					b += *pin++;
					g += *pin++;
					r += *pin++;
					a += *pin++;
					n++;
				}
			}
			/* 平均して縮小後のピクセルへ。 */
			pout  = bits_out + stride_out  * (h_out - 1 - y_out) + 4 * x_out;
			if(n != 0) {
				r = (r + n / 2) / n;
				g = (g + n / 2) / n;
				b = (b + n / 2) / n;
				a = (a + n / 2) / n;
			}
			*pout++ = b;
			*pout++ = g;
			*pout++ = r;
			*pout++ = a;
		}
	}

	/* 入力ビットマップを開放し、代わりに出力ビットマップを返します。 */
	free(in_buf);
	return out_buf;
}
