/*
 *	ptenshi - main.c
 *
 *	「天使のいない12月(DVD版)」PGDコンバータ
 *	Copyright (C) 2003 Naoyuki Sawa
 *
 *	* Fri Oct 3 00:00:00 JST 2003 Naoyuki Sawa
 *	- 作成開始。
 */
#define STRICT
#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <limits.h>
#include <assert.h>

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

#define VERSION		"20031004"

#define REG_KEY		"Software\\Leaf\\天使のいない１２月(DVD版)"
#define REG_VALUE	"InstallDir"

#define ORIGINAL_WIDTH	800

/********** アーカイブ形式 **********/
/* 実ファイル ".a" */

#define ARC_SIG		0xaf1e
typedef struct _ARC_HEAD {
	unsigned short sig;	/* シグネチャ */
	unsigned short cnt;	/* ファイル数 */
} ARC_HEAD;

typedef struct _ARC_FILE {
	char fname[23];		/* ファイル名 */
	unsigned char type;	/* 圧縮・暗号化タイプ */
	int len;		/* ファイルサイズ */
	int ofs;		/* ファイルテーブル直後からのオフセット */
} ARC_FILE;

/* 内部管理用 */
typedef struct _ARC {
	FILE* fp;		/* ファイルポインタ */
	int cnt;		/* ファイル数 */
	ARC_FILE* file;		/* ファイルテーブル */
} ARC;

/********** 背景画像形式 **********/
/* egbg.a内ファイル "*.px" */

typedef struct _BG_HEAD {
	int w;			/* 幅  [ピクセル] */
	int h;			/* 高さ[ピクセル] */
	int unk2;
	int unk3;
	int unk4;
	int unk5;
	int unk6;
	int unk7;
} BG_HEAD;

/********** キャラ画像形式 **********/
/* char.a内ファイル "*.px" */

typedef struct _CHR_HEAD {
	int cnt;		/* セル数 */
	int unk1;
	int unk2;
	int unk3;
	int unk4;
	int unk5;
	int unk6;
	int unk7;
} CHR_HEAD;

typedef struct _CELL_HEAD {
	int w;			/* 幅  [ピクセル] */
	int h;			/* 高さ[ピクセル] */
	int x;			/* 画像内でのセル原点X */
	int y;			/* 画像内でのセル原点Y */
	int unk4;
	int unk5;
	int unk6;
	int unk7;
} CELL_HEAD;

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

/* 画像が含まれているアーカイブ名リスト */
const char* const arc_list[] = {
	"egbg",
	"char",
};
#define ARC_LIST_COUNT	(sizeof arc_list / sizeof *arc_list)

char app_dir[_MAX_PATH];	/* 「天使のいない12月(DVD版)」のフォルダ名 */
char file_name[_MAX_PATH];	/* 抽出するファイル名("toh101"など)、または全展開("*") */

int opt_bmp;			/* BMP出力(テスト用) */
int opt_chr;			/* 立ち絵モード(下切り取り) */
int opt_all;			/* 抽出するファイル名に"*"が指定されたら1になります */

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

void err(const char* fmt, ...);
void usage();
void extract();
ARC* arc_open(const char* fname);
void arc_close(ARC* arc);
int expand(unsigned char** ppbuf);
void decode(unsigned char* eax, unsigned edx);
unsigned char* bmp32to8(unsigned char* in_buf);
unsigned char* reduce(unsigned char* in_buf);
unsigned char* make_bg(unsigned char* src, int size);
unsigned char* make_chr(unsigned char* src, int size);

/****************************************************************************
 *	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, "ptenshi - 「天使のいない12月(DVD版)(Leaf/AQUAPLUS)」PGDコンバータ(%s)\n", VERSION);
	fprintf(stderr, "Copyright (C) 2003 Naoyuki Sawa\n");
	fprintf(stderr, "使い方：\n");
	fprintf(stderr, "  ptenshi [options] <file-name>\n");
	fprintf(stderr, "\n");
	fprintf(stderr, "  ファイル名に「*」と指定すると、抽出可能な全てのファイルを抽出します。\n");
	fprintf(stderr, "  <例> ptenshi -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>  天使のいない12月(DVD版)フォルダ指定(初期値=レジストリから取得)\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': /* 「天使のいない12月(DVD版)」インストールフォルダ指定 */
				if(strlen(app_dir) != 0) usage(); /* 二重指定 */
				strncpy(app_dir, &p[2], sizeof app_dir);
				break;
			default:
				usage();
			}
		} else {
			/* 抽出するファイル名 */
			if(strlen(file_name) != 0) usage(); /* 二重指定 */
			strncpy(file_name, p, sizeof file_name);
		}
	}

	/* 抽出するファイル名を検査。 */
	if(strlen(file_name) == 0) usage();
	if(strcmp(file_name, "*") == 0) opt_all = 1; /* 全展開? */

	/* 「天使のいない12月(DVD版)」インストールフォルダを調べます。 */
	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("「天使のいない12月(DVD版)」がインストールされていないようです。");
	}

	/* ファイル展開。 */
	extract();

	return 0;
}

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

/* type-1 => 0:圧縮あり / 1:圧縮なし、インデックス外も圧縮なし */
const unsigned char expand_type[0x89] = {
	0,1,0,1,0,1,1,1, 1,1,1,1,1,1,1,1,	/* type-1 = 00〜0f */
	1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,	/* type-1 = 10〜1f */
	1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,	/* type-1 = 20〜2f */
	1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,	/* type-1 = 30〜3f */
	1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,	/* type-1 = 40〜4f */
	1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,	/* type-1 = 50〜5f */
	1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,	/* type-1 = 60〜6f */
	1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,0,	/* type-1 = 70〜7f */
	0,0,0,0,0,0,0,0, 0,			/* type-1 = 80〜88 */
};
#define EXPAND_TYPE_SIZE	(sizeof expand_type / sizeof *expand_type)

/* type-3 => 0〜2:暗号化タイプ0〜2(2のみ実装) / 3:暗号化なし、インデックス外も暗号化なし */
const unsigned char decode_type[0x87] = {
	0,3,1,3,3,3,3,3, 3,3,3,3,3,3,3,3,	/* flag-3 = 00〜0f */
	3,3,3,3,3,3,3,3, 3,3,3,3,3,3,3,3,	/* flag-3 = 10〜1f */
	3,3,3,3,3,3,3,3, 3,3,3,3,3,3,3,3,	/* flag-3 = 20〜2f */
	3,3,3,3,3,3,3,3, 3,3,3,3,3,3,3,3,	/* flag-3 = 30〜3f */
	3,3,3,3,3,3,3,3, 3,3,3,3,3,3,3,3,	/* flag-3 = 40〜4f */
	3,3,3,3,3,3,3,3, 3,3,3,3,3,3,3,3,	/* flag-3 = 50〜5f */
	3,3,3,3,3,3,3,3, 3,3,3,3,3,3,3,3,	/* flag-3 = 60〜6f */
	3,3,3,3,3,3,3,3, 3,3,3,3,3,2,2,2,	/* flag-3 = 70〜7f */
	2,2,2,2,2,2,2				/* flag-3 = 80〜86 */
};
#define DECODE_TYPE_SIZE	(sizeof decode_type / sizeof *decode_type)

void
extract()
{
	int retval;
	int done;
	int i_arc;
	int i_file;
	int type;
	int size;
	ARC* arc;
	ARC_FILE* file;
	unsigned char* buf;
	FILE* fp;
	char path[_MAX_PATH];
	char fname[_MAX_FNAME];
	char cmd[_MAX_PATH];
	BITMAPFILEHEADER* bf;

	done = 0; /* 単独展開時、指定ファイル展開後に1になります */

	/* 各アーカイブについて... */
	for(i_arc = 0; !done && i_arc < ARC_LIST_COUNT; i_arc++) {
		/* アーカイブを開きます。 */
		arc = arc_open(arc_list[i_arc]);

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

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

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

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

				/* ファイルを読み込みます。 */
				fseek(arc->fp, sizeof(ARC_HEAD) + sizeof(ARC_FILE) * arc->cnt + file->ofs, SEEK_SET);
				size = file->len;
				retval = fread(buf, 1, size, arc->fp);
				if(retval != size) err("%s: ファイルが読めません。", file->fname);

				/* 解凍します。 */
				type = file->type - 1;
				if(0 <= type && type <= EXPAND_TYPE_SIZE - 1) {
					type = expand_type[type];
					switch(type) {
					case 0:
						/* 圧縮あり。 */
						size = expand(&buf); /* 解凍後のバッファで置き換えて、サイズを取得 */
						break;
					case 1:
						/* 圧縮なし。 */
						break;
					default:
						abort(); /* ありえない */
					}
				}

				/* 復号します。(復号処理ではサイズは変わりません) */
				type = file->type - 3;
				if(0 <= type && type <= DECODE_TYPE_SIZE - 1) {
					type = decode_type[type];
					switch(type) {
					case 2:
						if(((BG_HEAD*)buf)->unk4 == 0x200001) {
							/* 暗号化あり。 */
							decode(buf, file->type & 0x0f); /* 3引く前のtypeの下位4ビットを復号キーとして使用 */
							break;
						}
						// FALL THRU */
					case 0:
					case 1:
						/* 未対応の暗号化形式。 */
						if(opt_all) { /* 全展開ならスキップ */
							fprintf(stderr, "%s: 未対応の暗号化タイプです。\n", file->fname);
							goto SKIP;
						} else { /* 単独展開ならエラー */
							err("%s: 未対応の暗号化タイプです。", file->fname);
						}
					default:
						abort(); /* ありえない */
					}
				}

				/* ビットマップ作成。 */
				switch(i_arc) {
				case 0: /* egbg.a */
					if(((BG_HEAD*)buf)->unk4 != 0x200001) {
						if(opt_all) { /* 全展開ならスキップ */
							fprintf(stderr, "%s: 未対応の画像形式です。\n", file->fname);
							goto SKIP;
						} else { /* 単独展開ならエラー */
							err("%s: 未対応の画像形式です。", file->fname);
						}
					}
					buf = make_bg(buf, size);
					break;
				case 1: /* char.a */
					if((((CHR_HEAD*)buf)->unk4 & 0xff) != 0x40) {
						if(opt_all) { /* 全展開ならスキップ */
							fprintf(stderr, "%s: 未対応の画像形式です。\n", file->fname);
							goto SKIP;
						} else { /* 単独展開ならエラー */
							err("%s: 未対応の画像形式です。", file->fname);
						}
					}
					buf = make_chr(buf, size);
					break;
				default:
					abort(); /* ありえない */
				}

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

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

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

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

				/* PGD変換。 */
				if(opt_bmp == 0) {
					switch(i_arc) {
					case 0: /* egbg.a: 2bitマスクなし */
						sprintf(cmd, "dpbmpcnv -q -b -f2  \"%s\"", path); /* -q必要! */
						break;
					case 1: /* char.a: 2bitマスクあり */
						sprintf(cmd, "dpbmpcnv -q -b -f2m \"%s\"", path); /* -q必要! */
						break;
					default:
						abort(); /* ありえない */
					}
					if(system(cmd) != 0) err("PGD変換に失敗しました。");
					remove(path);	/* BMPはもういらないので削除 */
				}

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

		/* アーカイブを閉じます。 */
		arc_close(arc);
	}

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

/****************************************************************************
 *	arc_open
 ****************************************************************************/

ARC*
arc_open(const char* fname)
{
	int retval;
	char path[_MAX_PATH];
	ARC* arc;
	ARC_HEAD head;

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

	/* アーカイブを開きます。 */
	_makepath(path, NULL, app_dir, fname, "a");
	arc->fp = fopen(path, "rb");
	if(arc->fp == NULL) err("%s: ファイルが開けません。", fname);

	/* ヘッダを読み込みます。 */
	retval = fread(&head, sizeof(ARC_HEAD), 1, arc->fp);
	if(retval != 1) err("%s: ヘッダが読めません。", fname);

	/* シグネチャを検査します。 */
	if(head.sig != ARC_SIG) err("%s: シグネチャが不正です。", fname);

	/* ファイルテーブルを読み込むためのメモリを確保します。 */
	arc->cnt = head.cnt;
	arc->file = (ARC_FILE*)calloc(arc->cnt, sizeof(ARC_FILE));
	if(arc->file == NULL) err("%s: メモリ不足です。", fname);

	/* ファイルテーブルを読み込みます。 */
	retval = fread(arc->file, sizeof(ARC_FILE), arc->cnt, arc->fp);
	if(retval != arc->cnt) err("%s: ファイルテーブルが読めません。", fname);

	return arc;
}

/****************************************************************************
 *	arc_close
 ****************************************************************************/

void
arc_close(ARC* arc)
{
	fclose(arc->fp);
	free(arc->file);
	free(arc);
}

/****************************************************************************
 *	expand
 ****************************************************************************/

int
expand(unsigned char** ppbuf)
{
#define	N	4096
#define	F	18
static unsigned char tmp_buf[N + F];

	unsigned char *src, *src_save;
	unsigned char *dst, *dst_save;
	int i, j, k, r;
	int size, flags, pos, len;

	/* 入力バッファを取得。 */
	src = src_save = *ppbuf;

	/* 出力サイズを取得。 */
	size = *(unsigned int*)src;
	src += 4;

	/* 出力バッファを確保。 */
	dst = dst_save = (unsigned char*)calloc(size, 1);
	if(dst == NULL) err("メモリ不足です。");

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

	/* カウンタが出力サイズに達するまで... */
	i = 0;
	while(i < size) {
		/* チャンクの1バイト目はフラグ。 */
		flags = *(src++);
		for(j = 0; j < 8; j++) { /* フラグのbit0〜7が0:無圧縮/1:圧縮に対応 */
			if(i >= size) break;
			if(flags & 1) { /* ＜＜＜ここがおでマルとは違う！ */
				/* 0:無圧縮 */
				*(tmp_buf + r++) = *(dst++) = *(src++);
				r &= (N - 1);
				i++;
			} else {
				/* 1:圧縮 */
				pos = *(src++);	/* pppp_pppp: p=スライド辞書位置のbit 7〜0           */
				len = *(src++);	/* pppp_llll: p=スライド辞書位置のbit11〜8、l=長さ-2 */
				pos |= ((len & 0xf0) << 4);
				len = (len & 0x0f) + 2;
				for(k = 0; k <= len; k++) {
					if(i >= size) break;
					*(tmp_buf + r++) = *(dst++) = *(tmp_buf + ((pos + k) & (N - 1)));
					r &= (N - 1);
					i++;
				}
			}
			flags >>= 1; /* ＜＜＜ここがおでマルとは違う！ */
		}
	}

	/* 入力バッファを開放。 */
	free(src_save);

	/* 出力バッファを格納。 */
	*ppbuf = dst_save;

	return size;

#undef N
#undef F
}

/****************************************************************************
 *	decode
 ****************************************************************************/

void
decode(unsigned char* eax, unsigned edx)
{
	unsigned size, I0h, ecx, Och;
	unsigned char O8h[4];
	unsigned char bl;

	size = *(int*)&eax[0] * *(int*)&eax[4];
	eax += 32;

	I0h= edx;			// オリジナルキー保存
	ecx = 0;			// 直前のピクセル
	Och = 0;			// 直前のピクセル
	memset(O8h, 0, sizeof O8h);	// 複合後のRGBA

	while(size) {
	/* mov bl,[eax]			*/	bl = eax[0];
	/* sub bl,dl			*/	bl -= edx;
	/* add bl,[eax+003h]		*/	bl += eax[3];
	/* mov dl,[eax+001h]		*/	*(unsigned char*)&edx = eax[1];
	/* add bl,cl			*/	bl += ecx;
	/* mov cl,[eax+002h]		*/	*(unsigned char*)&ecx = eax[2];
	/* mov [esp+008h],bl		*/	O8h[0] = bl;
	/* sub dl,[esp+010h]		*/	*(unsigned char*)&edx -= I0h;
	/* mov bl,[eax+003h]		*/	bl = eax[3];
	/* add dl,bl			*/	*(unsigned char*)&edx += bl;
	/* add dl,ch			*/	*(unsigned char*)&edx += ecx >> 8;
	/* mov [esp+009h],dl		*/	O8h[1] = edx;
	/* mov edx,dword [esp+010h]	*/	edx = I0h;
	/* sub cl,dl			*/	*(unsigned char*)&ecx -= edx;
	/* add cl,bl			*/	*(unsigned char*)&ecx += bl;
	/* add cl,[esp+00eh]		*/	*(unsigned char*)&ecx += Och >> 16;
	/* mov [esp+00ah],cl		*/	O8h[2] = ecx;
	/* mov ecx,dword [esp+008h]	*/	ecx = *(unsigned*)&O8h[0];
	/* mov dword [eax],ecx		*/	*(unsigned*)&eax[0] = ecx;
	/* add eax,byte +004h		*/	eax += 4;
	/* dec esi			*/	size--;
	/* mov dword [esp+00ch],ecx	*/	Och = ecx;
	}
}

/****************************************************************************
 *	bmp32to8
 ****************************************************************************/

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 = (float)ORIGINAL_WIDTH / 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;
}

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

unsigned char*
make_bg(unsigned char* src, int size)
{
	int x;
	int y;
	int width;
	int height;
	BG_HEAD* head;

	unsigned char* dst;
	BITMAPFILEHEADER* bf;
	BITMAPINFOHEADER* bi;
	unsigned* src_bits;
	unsigned* dst_bits;

	/* 画像ヘッダを取得。 */
	head = (BG_HEAD*)src;
	width = head->w;
	height = head->h;
	src_bits = (unsigned*)(head + 1);

	/* ビットマップ作成用メモリを確保します。 */
	dst = (unsigned char*)calloc(
		sizeof(BITMAPFILEHEADER) +
		sizeof(BITMAPINFOHEADER) +
		4 * width * height,
		1);
	if(dst == NULL) err("メモリ不足です。");
	bf = (BITMAPFILEHEADER*)dst;
	bi = (BITMAPINFOHEADER*)(bf + 1);
	dst_bits = (unsigned*)(bi + 1);

	/* ビットマップファイルヘッダ作成。 */
	bf->bfType    = 'MB';
	bf->bfSize    = sizeof(BITMAPFILEHEADER) +
			sizeof(BITMAPINFOHEADER) +
			4 * width * height;
	bf->bfOffBits = sizeof(BITMAPFILEHEADER) +
			sizeof(BITMAPINFOHEADER);

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

	/* ピクセルデータをコピー。(Windows BMPなので上下反転) */
	for(y = 0; y < height; y++) {
		for(x = 0; x < width; x++) {
			*(dst_bits + width * (height - 1 - y) + x) =
			*(src_bits + width *               y  + x) | 0xff000000;
		}
	}

	/* 入力バッファを破棄し、ビットマップを返します。 */
	free(src);
	return dst;
}

/****************************************************************************
 *	make_chr
 ****************************************************************************/

unsigned char*
make_chr(unsigned char* src, int size)
{
#define IMAGE_SIZE	1024
static unsigned image[IMAGE_SIZE][IMAGE_SIZE];

	int i_cell;
	int x;
	int y;
	int width;
	int height;
	int cnt;
	CHR_HEAD* chr;
	int* table;
	unsigned char* base;
	CELL_HEAD* cell;
	unsigned* p;

	unsigned char* dst;
	BITMAPFILEHEADER* bf;
	BITMAPINFOHEADER* bi;
	unsigned* src_bits;
	unsigned* dst_bits;

	/********** セル描画 **********/

	/* まず画像をクリア。 */
	int x_min = INT_MAX;
	int x_max = INT_MIN;
	int y_min = INT_MAX;
	int y_max = INT_MIN;
	for(y = 0; y < IMAGE_SIZE; y++) {
		for(x = 0; x < IMAGE_SIZE; x++) {
			image[y][x] = 0x0000ff00;
		}
	}

	/* 画像ヘッダ取得。 */
	chr = (CHR_HEAD*)src;

	/* セルオフセットテーブル取得。 */
	table = (int*)(chr + 1);

	/* セルオフセットベース取得。 */
	base = (unsigned char*)(table + chr->cnt);

	/* 各セルについて... */
	for(i_cell = 0; i_cell < chr->cnt; i_cell++) {
		cell = (CELL_HEAD*)(base + table[i_cell]);

		/* セル原点取得。 */
		x = cell->x;
		y = cell->y;

		p = (unsigned*)(cell + 1);
		for(;;) {
			/* 半透明ループ。 */
			while(*p < 0xfffffffe) {
				p++;
				x += (short)(*p >> 16);
				y += (short)(*p >>  0);
				p++;
				cnt = *p;
				p++;
				do {
					if(x < 0 || IMAGE_SIZE - 1 < x) abort();
					if(y < 0 || IMAGE_SIZE - 1 < y) abort();
					if(x < x_min) x_min = x;
					if(x > x_max) x_max = x;
					if(y < y_min) y_min = y;
					if(y > y_max) y_max = y;
					if(*p & 0x80000000) abort(); /* α成分が0〜7fであることを確認 */
					image[y][x] = (*p & 0x00ffffff) | (*p & 0xff000000) << 1; /* α成分は0〜7fなので2倍する */
					//半透明成分をチェックする時は、次の行を有効にしてください。
					//image[y][x] = (image[y][x] & 0xff000000) | (image[y][x] & 0xff000000) >> 8 | (image[y][x] & 0xff000000) >> 16 | (image[y][x] & 0xff000000) >> 24;
					x++;
					p++;
				} while(--cnt);
			}
			if(*p == 0xffffffff) break;
			p++;
			/* 不透明ループ。 */
			while(*p < 0xfffffffe) {
				p++;
				x += (short)(*p >> 16);
				y += (short)(*p >>  0);
				p++;
				cnt = *p;
				p++;
				do {
					if(x < 0 || IMAGE_SIZE - 1 < x) abort();
					if(y < 0 || IMAGE_SIZE - 1 < y) abort();
					if(x < x_min) x_min = x;
					if(x > x_max) x_max = x;
					if(y < y_min) y_min = y;
					if(y > y_max) y_max = y;
					image[y][x] = *p | 0xff000000; /* 不透明ループではα成分を強制的にffに置き換える(ファイル上では00で格納されてる) */
					//半透明成分をチェックする時は、次の行を有効にしてください。
					//image[y][x] = (image[y][x] & 0xff000000) | (image[y][x] & 0xff000000) >> 8 | (image[y][x] & 0xff000000) >> 16 | (image[y][x] & 0xff000000) >> 24;
					x++;
					p++;
				} while(--cnt);
			}
			if(*p == 0xffffffff) break;
			p++;
		}
	}

	/********** ビットマップ作成 **********/

	width = x_max - x_min + 1;
	height = y_max - y_min + 1;
	src_bits = &image[y_min][x_min];

	/* ビットマップ作成用メモリを確保します。 */
	dst = (unsigned char*)calloc(
		sizeof(BITMAPFILEHEADER) +
		sizeof(BITMAPINFOHEADER) +
		4 * width * height,
		1);
	if(dst == NULL) err("メモリ不足です。");
	bf = (BITMAPFILEHEADER*)dst;
	bi = (BITMAPINFOHEADER*)(bf + 1);
	dst_bits = (unsigned*)(bi + 1);

	/* ビットマップファイルヘッダ作成。 */
	bf->bfType    = 'MB';
	bf->bfSize    = sizeof(BITMAPFILEHEADER) +
			sizeof(BITMAPINFOHEADER) +
			4 * width * height;
	bf->bfOffBits = sizeof(BITMAPFILEHEADER) +
			sizeof(BITMAPINFOHEADER);

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

	/* ピクセルデータをコピー。(Windows BMPなので上下反転) */
	for(y = 0; y < height; y++) {
		for(x = 0; x < width; x++) {
			*((unsigned*)dst_bits + width      * (height - 1 - y) + x) =
			*((unsigned*)src_bits + IMAGE_SIZE *               y  + x);
		}
	}

	/* 入力バッファを破棄し、ビットマップを返します。 */
	free(src);
	return dst;

#undef IMAGE_SIZE
}
