/*
 *	utop - うたわれるものtoＢＭＰ画像コンバータ
 *	Copyright (C) 2002 Naoyuki Sawa <nsawa@north.hokkai.net>
 *
 *	* Sun May 26 21:10:00 JST 2002 Naoyuki Sawa <nsawa@north.hokkai.net>
 *	- 1st release.
 *	* Fri May 31 05:04:00 JST 2002 Naoyuki Sawa <nsawa@north.hokkai.net>
 *	- ラベルからのインデクスオフセット(ofs)のコメント表示を追加。
 *	- 全キャラクタ数(N_CHRS)の定義を追加。
 *	* Sat Jun 01 07:01:00 JST 2002 Naoyuki Sawa <nsawa@north.hokkai.net>
 *	- 「アイテム名=ラベル名」形式の指定に対応。
 *	* Sat Jun 01 08:33:00 JST 2002 Naoyuki Sawa <nsawa@north.hokkai.net>
 *	- 偶数抜き(even)、奇数抜き(odd)対応。。
 *	* Sat Jun 01 11:42:00 JST 2002 Naoyuki Sawa <nsawa@north.hokkai.net>
 *	- 最大イメージサイズ(MAX_IMAGE_SIZE)を1024->4096に変更。
 *	* Sat Jul 23 20:45:00 JST 2002 Naoyuki Sawa <nsawa@north.hokkai.net>
 *	- 画像をパックしないモード(-u[u])を追加。
 *	- 32ビット一枚絵(egbg.aのイベント絵など)に対応。
 *	  ８ビット一枚絵(戦闘マップなど)には未対応です。
 *	* Mon Jul 24 00:05:00 JST 2002 Naoyuki Sawa <nsawa@north.hokkai.net>
 *	- 32ビット一枚絵の一部は、なぜか640x480ではなく644x480になっています。
 *	  644x480では使いづらいことが多いので、640x480に切り詰めるようにしました。
 *	  644x480のままの方がよければ、block()関数の該当部分の処理をコメントアウトしてください。
 */
#define STRICT
#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <malloc.h>

#define PCKSIG		((short)0xAF1E)
typedef struct _PCKHDR {
	short sig;	/* シグネチャ */
	short cnt;	/* 要素数     */
} PCKHDR;

typedef struct _PCKENT {
	char name[24];	/* 名前       */
	int len;	/* サイズ     */
	int ofs;	/* オフセット */
} PCKENT;

typedef struct _BLKHDR {
	int w, h;	/* ブロックサイズ[ピクセル] */
	int x, y;	/* ブロック原点  [ピクセル] */
	int fmt;	/* フォーマット             */
	int unk1;	/* (不明:wと同じ?)          */
	int unk2;	/* (不明:hと同じ?)          */
	int unk3;	/* (不明:パディング?)       */
} BLKHDR;

typedef struct _CELHDR9 {
	int y   : 8;	/* 垂直スキップ */
	int cnt : 8;	/* ピクセル数   */
	int unk1: 3;	/* 未使用       */
	int run : 1;	/* ラン切り替え */
	int unk2: 1;	/* 未使用       */
	int x   :11;	/* 水平スキップ */
} CELHDR9;

typedef struct _CELHDR32 {
	int y:16;	/* 垂直スキップ */
	int x:16;	/* 水平スキップ */
} CELHDR32;

char uta_dir[_MAX_DIR]; /* 「うたわれるもの」フォルダ */

/***************************************************************************/

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

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

	abort();
}

/***************************************************************************/

#define MAX_IMAGE_SIZE	4096
#define ALPHA_CUTOFF	1

int palette[256];
int back_color = 0x00FF00;
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,
};

class Image {
public:
	Image(int w, int h);
	virtual ~Image();

	int w, h;
	int* bits;

	int get(int x, int y) const;
	void set(int x, int y, int c);
	int save24(const char* path);
	int save8a(const char* path);
	int save8(const char* path);
	void put(int x, int y, Image* img);
	void resize(int w, int h);
	void reduce(int s);
	void merge(int& x, int& y, Image* img, int rect[4]);
};

Image::Image(int w, int h)
{
	this->w = 0;
	this->h = 0;
	bits = NULL;
	resize(w, h);
}

Image::~Image()
{
	free(bits);
}

int
Image::get(int x, int y) const
{
	if(x < 0 || w - 1 < x) abort();
	if(y < 0 || h - 1 < y) abort();
	return bits[w * y + x];
}

void
Image::set(int x, int y, int c)
{
	if(x < 0 || w - 1 < x) abort();
	if(y < 0 || h - 1 < y) abort();
	bits[w * y + x] = c;
}

int
Image::save24(const char* path)
{
	int retval;
	int x, y, c;
	int line_bytes;
	BITMAPFILEHEADER bf;
	BITMAPINFOHEADER bi;

	FILE* fp;
	char* line_buffer;

	/* エラー処理のため。 */
	fp = NULL;
	line_buffer = NULL;

	/* ファイルを開く。 */
	fp = fopen(path, "wb");
	if(fp == NULL) goto ERR;

	/* ラインバッファを確保。 */
	line_bytes = (3 * w) + 3 & ~3;
	line_buffer = (char*)malloc(line_bytes);
	if(line_buffer == NULL) abort();
	memset(line_buffer + 3 * w, 0, line_bytes - 3 * w); /* パディング部分をクリア */

	/* ファイルヘッダ出力。 */
	memset(&bf, 0, sizeof bf);
	bf.bfType = 'MB';
	bf.bfSize = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) + line_bytes * h;
	bf.bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER);
	retval = fwrite(&bf, 1, sizeof bf, fp);
	if(retval != sizeof bf) goto ERR;

	/* ビットマップヘッダ出力。 */
	memset(&bi, 0, sizeof bi);
	bi.biSize = sizeof bi;
	bi.biWidth = w;
	bi.biHeight = h;
	bi.biPlanes = 1;
	bi.biBitCount = 24;
	retval = fwrite(&bi, 1, sizeof bi, fp);
	if(retval != sizeof bi) goto ERR;

	/* データ出力。 */
	for(y = h - 1; y >= 0; y--) {
		for(x = 0; x < w; x++) {
			c = get(x, y);
			memcpy(line_buffer + 3 * x, &c, 3);
		}
		retval = fwrite(line_buffer, 1, line_bytes, fp);
		if(retval != line_bytes) goto ERR;
	}

	/* ファイルを閉じる。 */
	fclose(fp);
	free(line_buffer);

	return 0;
ERR:
	if(fp != NULL) fclose(fp);
	if(line_buffer != NULL) free(line_buffer);

	return -1;
}

int
Image::save8a(const char* path)
{
	int retval;
	int x, y, c;
	int line_bytes;
	BITMAPFILEHEADER bf;
	BITMAPINFOHEADER bi;
	RGBQUAD rgb[256];

	FILE* fp;
	char* line_buffer;

	/* エラー処理のため。 */
	fp = NULL;
	line_buffer = NULL;

	/* ファイルを開く。 */
	fp = fopen(path, "wb");
	if(fp == NULL) goto ERR;

	/* ラインバッファを確保。 */
	line_bytes = w + 3 & ~3;
	line_buffer = (char*)malloc(line_bytes);
	if(line_buffer == NULL) abort();
	memset(line_buffer + w, 0, line_bytes - w); /* パディング部分をクリア */

	/* ファイルヘッダ出力。 */
	memset(&bf, 0, sizeof bf);
	bf.bfType = 'MB';
	bf.bfSize = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) + sizeof(RGBQUAD) * 256 + line_bytes * h;
	bf.bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) + sizeof(RGBQUAD) * 256;
	retval = fwrite(&bf, 1, sizeof bf, fp);
	if(retval != sizeof bf) goto ERR;

	/* ビットマップヘッダ出力。 */
	memset(&bi, 0, sizeof bi);
	bi.biSize = sizeof bi;
	bi.biWidth = w;
	bi.biHeight = h;
	bi.biPlanes = 1;
	bi.biBitCount = 8;
	retval = fwrite(&bi, 1, sizeof bi, fp);
	if(retval != sizeof bi) goto ERR;

	/* パレット出力。 */
	for(c = 0; c < 256; c++) {
		rgb[c].rgbRed       = c;
		rgb[c].rgbGreen     = c;
		rgb[c].rgbBlue      = c;
		rgb[c].rgbReserved  = 0;
	}
	retval = fwrite(rgb, 1, sizeof rgb, fp);
	if(retval != sizeof rgb) goto ERR;

	/* データ出力。 */
	for(y = h - 1; y >= 0; y--) {
		for(x = 0; x < w; x++) {
			c = get(x, y);
			line_buffer[x] = c >> 24;
		}
		retval = fwrite(line_buffer, 1, line_bytes, fp);
		if(retval != line_bytes) goto ERR;
	}

	/* ファイルを閉じる。 */
	fclose(fp);
	free(line_buffer);

	return 0;
ERR:
	if(fp != NULL) fclose(fp);
	if(line_buffer != NULL) free(line_buffer);

	return -1;
}

int
Image::save8(const char* path)
{
	int retval;
	int x, y, c, a, r, g, b;
	int line_bytes;
	BITMAPFILEHEADER bf;
	BITMAPINFOHEADER bi;
	RGBQUAD rgb[256];

	FILE* fp;
	char* line_buffer;

	/* エラー処理のため。 */
	fp = NULL;
	line_buffer = NULL;

	/* ファイルを開く。 */
	fp = fopen(path, "wb");
	if(fp == NULL) goto ERR;

	/* ラインバッファを確保。 */
	line_bytes = w + 3 & ~3;
	line_buffer = (char*)malloc(line_bytes);
	if(line_buffer == NULL) abort();
	memset(line_buffer + w, 0, line_bytes - w); /* パディング部分をクリア */

	/* ファイルヘッダ出力。 */
	memset(&bf, 0, sizeof bf);
	bf.bfType = 'MB';
	bf.bfSize = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) + sizeof(RGBQUAD) * 256 + line_bytes * h;
	bf.bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) + sizeof(RGBQUAD) * 256;
	retval = fwrite(&bf, 1, sizeof bf, fp);
	if(retval != sizeof bf) goto ERR;

	/* ビットマップヘッダ出力。 */
	memset(&bi, 0, sizeof bi);
	bi.biSize = sizeof bi;
	bi.biWidth = w;
	bi.biHeight = h;
	bi.biPlanes = 1;
	bi.biBitCount = 8;
	retval = fwrite(&bi, 1, sizeof bi, fp);
	if(retval != sizeof bi) goto ERR;

	/* パレット出力。 */
	for(c = 0; c < 4; c++) {
		rgb[c].rgbRed       = 85 * (3 - c);
		rgb[c].rgbGreen     = 85 * (3 - c);
		rgb[c].rgbBlue      = 85 * (3 - c);
		rgb[c].rgbReserved  = 0;
	}
	for(c = 4; c < 256; c++) {
		rgb[c].rgbRed       = 0;
		rgb[c].rgbGreen     = 255;
		rgb[c].rgbBlue      = 0;
		rgb[c].rgbReserved  = 0;
	}
	retval = fwrite(rgb, 1, sizeof rgb, fp);
	if(retval != sizeof rgb) goto ERR;

	/* データ出力。 */
	for(y = h - 1; y >= 0; y--) {
		for(x = 0; x < w; x++) {
			c = get(x, y);
			a = c >> 24 & 0xFF;
			r = c >> 16 & 0xFF;
			g = c >>  8 & 0xFF;
			b = c >>  0 & 0xFF;
			if(a >= ALPHA_CUTOFF) {
				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;
			}
			line_buffer[x] = c;
		}
		retval = fwrite(line_buffer, 1, line_bytes, fp);
		if(retval != line_bytes) goto ERR;
	}

	/* ファイルを閉じる。 */
	fclose(fp);
	free(line_buffer);

	return 0;
ERR:
	if(fp != NULL) fclose(fp);
	if(line_buffer != NULL) free(line_buffer);

	return -1;
}

void
Image::put(int x, int y, Image* img)
{
	int sx, sy, c;

	for(sy = 0; sy < img->h; sy++) {
		for(sx = 0; sx < img->w; sx++) {
			c = img->get(sx, sy);
			set(x + sx, y + sy, c);
		}
	}
}

void
Image::resize(int w, int h)
{
	int x, y, old_w, old_h, *old_bits;

	old_w = this->w;
	old_h = this->h;
	old_bits = bits;

	if(w < 0 || MAX_IMAGE_SIZE < w) abort();
	if(h < 0 || MAX_IMAGE_SIZE < h) abort();
	this->w = w;
	this->h = h;
	bits = (int*)malloc(sizeof(int) * w * h);
	if(bits == NULL) abort();

	for(y = 0; y < h; y++) {
		for(x = 0; x < w; x++) {
			bits[w * y + x] = x < old_w && y < old_h ? old_bits[old_w * y + x] : back_color;
		}
	}
}

void
Image::reduce(int s)
{
	int dx, dy, sx, sy, w, h, c, a, r, g, b, n;

	w = (this->w + (s - 1)) / s;
	h = (this->h + (s - 1)) / s;

	for(dy = 0; dy < h; dy++) {
		for(dx = 0; dx < w; dx++) {
			a = 0;
			r = 0;
			g = 0;
			b = 0;
			n = 0;
			for(sy = dy * s; sy < (dy + 1) * s; sy++) {
				if(sy >= this->h) continue;
				for(sx = dx * s; sx < (dx + 1) * s; sx++) {
					if(sx >= this->w) continue;
					c = get(sx, sy);
					a += c >> 24 & 0xFF;
					r += c >> 16 & 0xFF;
					g += c >>  8 & 0xFF;
					b += c >>  0 & 0xFF;
					n++;
				}
			}
			a /= n;
			r /= n;
			g /= n;
			b /= n;
			c = a << 24 | r << 16 | g << 8 | b << 0;
			set(dx, dy, c);
		}
	}

	resize(w, h);
}

void
Image::merge(int& x, int& y, Image* img, int rect[4])
{
	if(img->w > w) abort();

	if(x + img->w > w) {
		x = 0;
		y = h;
		resize(w, h + img->h);
	} else if(y + img->h > h) {
		resize(w, y + img->h);
	}
	put(x, y, img);

	if(rect != NULL) {
		rect[0] = x;
		rect[1] = y;
		rect[2] = img->w;
		rect[3] = img->h;
	}

	x += img->w;
}

/***************************************************************************/

int
block9(BLKHDR* bh, Image* img, int x_min, int y_min)
{
	unsigned char* p;
	int run, x, y, n, c, a;
	CELHDR9* ch;

	p = (unsigned char*)(bh + 1);
	run = 0;
	x = 0;
	y = 0;
	for(;;) {
		if(*(int*)p == -1) break; /* 終了 */

		ch = (CELHDR9*)p;
		p = (unsigned char*)(ch + 1);

		/* 次のピクセルの開始位置。 */
		x += ch->x;
		if(x < 0 || img->w - 1 < x) abort();
		y += ch->y;
		if(y < 0 || img->h - 1 < y) abort();

		/* ラン切替。 */
		if(ch->run) run = !run;

		/* ピクセルデータ。 */
		if(ch->cnt & 1) abort();
		n = ch->cnt / 2;
		while(n-- != 0) {
			if(run) {
				a = 0xFF;
				c = *p++;
			} else {
				a = *p++;
				c = *p++;
				if(a > 0x7F) abort();
				a *= 2;
			}
			c = palette[c] | a << 24;
			img->set(bh->x + x - x_min, bh->y + y - y_min, c);
			x++;
		}
	}

	return 0;
}

/***************************************************************************/

int
block32(BLKHDR* bh, Image* img, int x_min, int y_min)
{
	int* p;
	int run, x, y, n, c, a;
	CELHDR32* ch;

	p = (int*)(bh + 1);
	run = 0;
	x = 0;
	y = 0;
	for(;;) {
		c = *p++;
		if(c == -1) { /* 終了 */
			break;
		} else if(c == -2) { /* ラン切替 */
			run = !run;
			continue;
		}

		ch = (CELHDR32*)p;
		p = (int*)(ch + 1);

		/* 次のピクセルの開始位置。 */
		x += ch->x;
		if(x < 0 || img->w - 1 < x) abort();
		y += ch->y;
		if(y < 0 || img->h - 1 < y) abort();

		/* ピクセルデータ。 */
		n = *p++;
		while(n-- != 0) {
			c = *p++;
			a = c >> 24 & 0xFF;
			c &= 0xFFFFFF;
			if(run) {
				/* 不透明のランは、A=0だが、255に置き換える。 */
				if(a != 0) abort();
				a = 0xFF;
			} else {
				/* 半透明のランは、A=0〜127なので、二倍する。 */
				if(a > 0x7F) abort();
				a *= 2;
			}
			c |= a << 24;
			img->set(bh->x + x - x_min, bh->y + y - y_min, c);
			x++;
		}
	}

	return 0;
}

/***************************************************************************/

Image*
block(BLKHDR* bh)
{
	int retval;
	int i, x, y, cnt, *ofs, init, x_min, y_min, x_max, y_max;
	int* rgba;
	char* base;
	BLKHDR* bh_pos;

	Image* img;

	/* エラー処理のため。 */
	img = NULL;

	if(bh->fmt == 0x00200001) { /* 背景 */
		img = new Image(bh->w, bh->h);

		/* ヘッダの後に 1ピクセル32ビット×幅×高さ のベタデータが続きます。 */
		rgba = (int*)(bh + 1);
		for(y = 0; y < bh->h; y++) {
			for(x = 0; x < bh->w; x++) {
				img->set(x, y, 0xFF000000 | *rgba++);
			}
		}

		/* なぜか640x480の絵の一部は644x480になってるので、右4ピクセル削除します。
		 * 644x480でもよければ、この処理はコメントアウトしても構いません。
		 */
		if(bh->w == 644 && bh->h == 480) img->resize(640, 480);

	} else { /* チップ・カットイン */
		cnt = bh->w;
		ofs = (int*)(bh + 1);
		base = (char*)(ofs + cnt);

		/* 全体のイメージサイズを決定。 */
		init = 0;
		for(i = 0; i < cnt; i++) {
			bh_pos = (BLKHDR*)(base + ofs[i]);
			switch(bh_pos->fmt) {
			case 0x00200000: /* チップのパレット */
				if(bh_pos->w != 256) goto ERR;
				break;
			case 0x00090004: /* チップ */
			case 0x00200004: /* カットイン */
				if(!init || bh_pos->x             < x_min) x_min = bh_pos->x            ;
				if(!init || bh_pos->y             < y_min) y_min = bh_pos->y            ;
				if(!init || bh_pos->x + bh_pos->w > x_max) x_max = bh_pos->x + bh_pos->w;
				if(!init || bh_pos->y + bh_pos->h > y_max) y_max = bh_pos->y + bh_pos->h;
				init = 1;
				break;
			default:
				goto ERR;
			}
		}
		if(!init) goto ERR;

		/* 全体イメージを作成。 */
		img = new Image(x_max - x_min, y_max - y_min);

		/* 各ブロックを展開。 */
		for(i = 0; i < cnt; i++) {
			bh_pos = (BLKHDR*)(base + ofs[i]);
			switch(bh_pos->fmt) {
			case 0x00200000: /* チップのパレット */
				memcpy(palette, bh_pos + 1, sizeof(int) * 256);
				break;
			case 0x00090004: /* チップ */
				retval = block9(bh_pos, img, x_min, y_min);
				if(retval != 0) goto ERR;
				break;
			case 0x00200004: /* カットイン */
				retval = block32(bh_pos, img, x_min, y_min);
				if(retval != 0) goto ERR;
				break;
			default:
				goto ERR;
			}
		}
	}

	return img;
ERR:
	if(img != NULL) delete img;

	return NULL;
}

/***************************************************************************/

Image*
extract(const char* path, const char* name, int index)
{
	int retval;
	int i, cnt, *ofs;
	PCKHDR ph;
	char* base;
	BLKHDR* bh_pos;

	FILE* fp;
	PCKENT* pe;
	BLKHDR* bh;
	Image* img;

	/* エラー処理のため。 */
	fp = NULL;
	pe = NULL;
	bh = NULL;
	img = NULL;

	/* ファイルを開きます。 */
	fp = fopen(path, "rb");
	if(fp == NULL) goto ERR;

	/* パックヘッダを読みます。 */
	retval = fread(&ph, 1, sizeof ph, fp);
	if(retval != sizeof ph) goto ERR;
	if(ph.sig != PCKSIG) goto ERR;
	pe = (PCKENT*)malloc(sizeof(PCKENT) * ph.cnt);
	if(pe == NULL) goto ERR;
	retval = fread(pe, 1, sizeof(PCKENT) * ph.cnt, fp);
	if(retval != (int)(sizeof(PCKENT) * ph.cnt)) goto ERR;

	/* アイテムを探します。 */
	for(i = 0; i < ph.cnt; i++) {
		if(strcmpi(pe[i].name, name) == 0) break;
	}
	if(i == ph.cnt) goto ERR;

	/* アイテムをロードします。
	 * 既にファイル位置はテーブルの直後(ベース位置)にあるので、
	 * 現在位置からの相対でシークします。
	 */
	retval = fseek(fp, pe[i].ofs, SEEK_CUR);
	if(retval != 0) goto ERR;
	bh = (BLKHDR*)malloc(pe[i].len);
	if(bh == NULL) goto ERR;
	retval = fread(bh, 1, pe[i].len, fp);
	if(retval != pe[i].len) goto ERR;

	if(bh->fmt == 0x00200001) { /* 背景 */
		if(index != 0) goto ERR;
		img = block(bh);
	} else if(bh->fmt == 0x00000040) { /* カットイン */
		if(index != 0) goto ERR;
		img = block(bh);
	} else if(bh->fmt == 0x00000084) { /* チップ */
		cnt = bh->w;
		ofs = (int*)(bh + 1);
		base = (char*)(ofs + cnt);
		if(index < 0 || cnt - 1 <index) goto ERR;
		bh_pos = (BLKHDR*)(base + ofs[index]);
		img = block(bh_pos);
	} else {
		goto ERR;
	}

	/* ファイルを閉じます。 */
	fclose(fp);
	free(pe);
	free(bh);

	return img;
ERR:
	if(fp != NULL) fclose(fp);
	if(pe != NULL) free(pe);
	if(bh != NULL) free(bh);
	if(img != NULL) delete img;

	return NULL;
}

/***************************************************************************/

int
getcnt(const char* path, const char* name)
{
	int retval;
	int i, cnt;
	PCKHDR ph;

	FILE* fp;
	PCKENT* pe;
	BLKHDR* bh;

	/* エラー処理のため。 */
	fp = NULL;
	pe = NULL;
	bh = NULL;

	/* ファイルを開きます。 */
	fp = fopen(path, "rb");
	if(fp == NULL) goto ERR;

	/* パックヘッダを読みます。 */
	retval = fread(&ph, 1, sizeof ph, fp);
	if(retval != sizeof ph) goto ERR;
	if(ph.sig != PCKSIG) goto ERR;
	pe = (PCKENT*)malloc(sizeof(PCKENT) * ph.cnt);
	if(pe == NULL) goto ERR;
	retval = fread(pe, 1, sizeof(PCKENT) * ph.cnt, fp);
	if(retval != (int)(sizeof(PCKENT) * ph.cnt)) goto ERR;

	/* アイテムを探します。 */
	for(i = 0; i < ph.cnt; i++) {
		if(strcmpi(pe[i].name, name) == 0) break;
	}
	if(i == ph.cnt) goto ERR;

	/* アイテムをロードします。
	 * 既にファイル位置はテーブルの直後(ベース位置)にあるので、
	 * 現在位置からの相対でシークします。
	 */
	retval = fseek(fp, pe[i].ofs, SEEK_CUR);
	if(retval != 0) goto ERR;
	bh = (BLKHDR*)malloc(pe[i].len);
	if(bh == NULL) goto ERR;
	retval = fread(bh, 1, pe[i].len, fp);
	if(retval != pe[i].len) goto ERR;

	if(bh->fmt == 0x00200001) {        /* 背景 */
		cnt = 1;
	} else if(bh->fmt == 0x00000040) { /* カットイン */
		cnt = 1;
	} else if(bh->fmt == 0x00000084) { /* チップ */
		cnt = bh->w;
	} else {
		goto ERR;
	}

	/* ファイルを閉じます。 */
	fclose(fp);
	free(pe);
	free(bh);

	return cnt;
ERR:
	if(fp != NULL) fclose(fp);
	if(pe != NULL) free(pe);
	if(bh != NULL) free(bh);

	return -1;
}

/***************************************************************************/

#define BMP_WIDTH	256
#define MAX_REDUCE	16
#define DELIM		" \t\r\n,"
#define SCR_EXT		".scr"
#define ARC_EXT		".a"
#define ITEM_EXT	".px"
#define BMP_EXT		".bmp"

char scr_path[_MAX_PATH];
char scr_drive[_MAX_DRIVE];
char scr_dir[_MAX_DIR];
char scr_fname[_MAX_FNAME];
char scr_ext[_MAX_EXT];
char arc_path[_MAX_PATH];
char arc_fname[_MAX_FNAME];
char item_path[MAX_PATH];
char item_fname[_MAX_FNAME];
char bmp_path[_MAX_PATH];
char bmp_fname[_MAX_FNAME];
char label_name[_MAX_FNAME];

int c_out;			/* -cオプションが指定されたら1           */
int bmp_width = BMP_WIDTH;	/* -wオプションによる指定値              */
int u_pack;			/* -uオプションが指定されたら1、-uuなら2 */

/* 使い方を表示して、終了します。 */
void
usage()
{
	fprintf(stderr, "使い方：\n");
	fprintf(stderr, "  utop [options] <script-filename[.scr]>\n");
	fprintf(stderr, "\n");
	fprintf(stderr, "オプション：\n");
	fprintf(stderr, "  -c        標準出力に座標定義を出力\n");
	fprintf(stderr, "  -d<path>  「うたわれるもの」フォルダ （初期値＝レジストリから取得）\n");
	fprintf(stderr, "  -w<width> 生成する画像ファイルの幅   （初期値＝%d）\n", BMP_WIDTH);
	fprintf(stderr, "  -u[u]     画像をパックしません       （-cや-wとの併用はできません）\n");
	fprintf(stderr, "            「-uu」と指定すると、ファイル名にアーカイブ名を含めません\n");
	exit(1);
}

int
main(int argc, char* argv[])
{
	int retval;
	int i, n, no, x, y, line, reduce, index, step, cnt, ofs;
	int rect[4];
	HKEY hkey;
	FILE* fp_scr;
	char *p;
	char str[1024];
	char label[32];
	char cmd[1024];
	Image* img;

	/* コマンドライン解析。 */
	for(i = 1; i < argc; i++) {
		p = argv[i];
		if(p[0] == '-') { /* オプション */
			switch(p[1]) {
			case 'c': /* 標準出力に座標定義を出力 */
				if(c_out) usage(); /* 二重指定 */
				c_out = 1;
				break;
			case 'd': /* 「うたわれるもの」フォルダ指定 */
				if(strlen(uta_dir) != 0) usage(); /* 二重指定 */
				strncpy(uta_dir, &p[2], sizeof uta_dir);
				break;
			case 'w': /* 生成する画像ファイルの幅指定 */
				bmp_width = atoi(&p[2]);
				if(bmp_width == 0) usage(); /* 数値じゃない */
				break;
			case 'u': /* 画像をパックしない */
				if(u_pack) usage(); /* 二重指定 */
				switch(p[2]) {
				case '\0': /* -u */
					u_pack = 1;
					break;
				case 'u': /* -uu */
					u_pack = 2;
					break;
				default:
					usage();
				}
				break;
			default:
				usage();
			}
		} else { /* スクリプトファイル指定 */
			if(strlen(scr_path) != 0) usage(); /* 二重指定 */
			strncpy(scr_path, p, sizeof scr_path);
		}
	}

	/* 画像をパックしないときは、座標定義出力や画像サイズの指定はできません。 */
	if(u_pack) {
		if(c_out) usage();
		if(bmp_width != BMP_WIDTH) usage();
	}

	/* うたわれディレクトリを取得。 */
	if(strlen(uta_dir) == 0) {
		retval = RegOpenKeyEx(HKEY_CURRENT_USER, "Software\\Leaf\\utaware", 0, KEY_QUERY_VALUE, &hkey);
		if(retval == 0) {
			n = sizeof uta_dir;
			retval = RegQueryValueEx(hkey, "InstallDir", 0, NULL, (unsigned char*)uta_dir, (unsigned long*)&n);
			RegCloseKey(hkey);
		}
		if(retval != 0) err("「うたわれるもの」がインストールされていないようです。");
	}

	/* スクリプトファイルを開きます。 */
	if(strlen(scr_path) == 0) usage(); /* スクリプトファイル指定忘れ */
	_splitpath(scr_path, scr_drive, scr_dir, scr_fname, scr_ext);
	if(strlen(scr_ext) == 0) {
		strcpy(scr_ext, SCR_EXT);
		_makepath(scr_path, scr_drive, scr_dir, scr_fname, scr_ext);
	}
	fp_scr = fopen(scr_path, "rt");
	if(fp_scr == NULL) err("%sが開けません。", scr_path);

	/* スクリプトファイルの各行について... */
	if(bmp_width <= 0 || bmp_width % 4 != 0) err("画像ファイルの幅は、４の倍数でなければいけません。");
	Image bmp(bmp_width, 0); /* 「-u[u]」指定時は使われません */
	no = 0;
	x = 0;
	y = 0;
	line = 0;
	reduce = 1;
	while(fgets(str, sizeof str, fp_scr) != NULL) {
		line++;

		/* 「#」以降をコメントとして切り捨て。 */
		p = strchr(str, '#');
		if(p != NULL) *p = '\0';

		/* 最初のトークンによって... */
		p = strtok(str, DELIM);
		if(p == NULL) continue; /* 空行 */
		if(strcmp(p, "REDUCE") == 0) { /* 縮小率変更 */
			p = strtok(NULL, DELIM);
			if(p == NULL) err("%s:%d: 縮小率が取得できません。", scr_path, line);
			reduce = atoi(p);
			if(reduce < 0 || MAX_REDUCE < reduce) err("%s:%d: 縮小率が不正です。", scr_path, line);
			p = strtok(NULL, DELIM);
			if(p != NULL) err("%s:%d: 書式エラー", scr_path, line);

		} else if(strcmp(p, "ARCHIVE") == 0) { /* 書庫変更 */
			p = strtok(NULL, DELIM);
			if(p == NULL) err("%s:%d: 書庫名が取得できません。", scr_path, line);
			strncpy(arc_fname, p, sizeof arc_fname);
			p = strtok(NULL, DELIM);
			if(p != NULL) err("%s:%d: 書式エラー", scr_path, line);

		} else if(strcmp(p, "NEWLINE") == 0) { /* 強制改行、「-u[u]」指定時は無意味です */
			p = strtok(NULL, DELIM);
			if(p != NULL) err("%s:%d: 書式エラー", scr_path, line);
			x = 0;
			y = bmp.h;

		} else { /* 切り出し */
			strncpy(item_fname, p, sizeof item_fname);
			{
				/* 2002/06/01 「アイテム名=ラベル名」書式追加
				 * strtok()ループの中なので、ここではstrtok()は使えない。
				 */
				char* p2;
				p2 = strchr(item_fname, '=');
				if(p2 == NULL) {
					/* name 0,1,2,3... なら、
					 * nameをラベル名兼アイテム名とみなす。
					 */
					strcpy(label_name, item_fname);
				} else {
					/* name1=name2 0,1,2,3,... なら、
					 * name1をアイテム名、name2をラベル名をみなす。
					 */
					strcpy(label_name, p2 + 1);
					*p2 = '\0';
				}
			}
			_makepath(arc_path, NULL, uta_dir, arc_fname, ARC_EXT);
			_makepath(item_path, NULL, NULL, item_fname, ITEM_EXT);

			/* 先頭インデクスに対するラベルを生成。 */
			if(c_out) {
				sprintf(label, "%s_%s", arc_fname, /*item_fname->06/01変更->*/label_name);
				printf("#ifndef CHRDEF\n");
				printf("#define %-*s %d\n", sizeof label, strupr(label), no);
				printf("#else /*CHRDEF*/\n");
			}

			/* 各インデクスについて... */
			cnt = getcnt(arc_path, item_path);
			if(cnt <= 0) err("%s:%d: 要素数取得エラー (%s, %s)", scr_path, line, arc_path, item_path);
			p = strtok(NULL, DELIM);
			if(p == NULL) {
				index = 0;
				step = 1;
			} else if(strcmpi(p, "even") == 0) {
				index = 0;
				step = 2;
				p = strtok(NULL, DELIM); /* "even"の後ろにはインデクス指定してはいけない */
				if(p != NULL) err("%s:%d: 書式エラー", scr_path, line);
			} else if(strcmpi(p, "odd") == 0) {
				index = 1; /* 要素数1だといきなりエラーになるが、まあありえないだろう */
				step = 2;
				p = strtok(NULL, DELIM); /* "odd"の後ろにはインデクス指定してはいけない */
				if(p != NULL) err("%s:%d: 書式エラー", scr_path, line);
			} 
			ofs = 0;
			for(;;) {
				if(p != NULL) index = atoi(p);

				/* イメージを抽出。 */
				img = extract(arc_path, item_path, index);
				if(img == NULL) err("%s:%d: 抽出エラー (%s, %s, %d)", scr_path, line, arc_path, item_path, index);

				/* イメージを縮小。 */
				img->reduce(reduce);
				if(!u_pack) { /* 通常の画像パックモード */
					/* 一枚のビットマップにパック。 */
					bmp.merge(x, y, img, rect);
				} else { /* 「-u[u]」指定時（パックしない） */
					/* ビットマップを作成。 */
					if(cnt == 1) { /* カットイン：「CHAR_ERU01」または「ERU01」のようなファイル名 */
						if(u_pack == 1) {
							sprintf(bmp_fname, "%s_%s", arc_fname, label_name);
						} else { /* u_pack == 2 */
							sprintf(bmp_fname, "%s", label_name);
						}
					} else { /* チップ：「CHIP_HAK_1」または「HAK_1」のようなファイル名 */
						if(u_pack == 1) {
							sprintf(bmp_fname, "%s_%s_%d", arc_fname, label_name, index);
						} else { /* u_pack == 2 */
							sprintf(bmp_fname, "%s_%d", label_name, index);
						}
					}
					_makepath(bmp_path, scr_drive, scr_dir, bmp_fname, BMP_EXT);
					retval = img->save8(bmp_path);
					if(retval != 0) err("%sが作成できません。", bmp_path);
					/* P/ECE用データに変換。 */
					sprintf(cmd, "dpbmpcnv -q -b -f2m \"%s\"", bmp_path); /* -q必要! */
					retval = system(cmd);
					if(retval != 0) err("P/ECE用データへの変換に失敗しました。");
				}

				/* キャラクタ定義。 */
				if(c_out) {
					printf("{ %4d,%4d,%4d,%4d }, /* %2d */\n", rect[0], rect[1], rect[2], rect[3], ofs);
					no++;
					ofs++;
				}

				/* イメージを開放。 */
				delete img;

				if(p != NULL) {
					p = strtok(NULL, DELIM);
					if(p == NULL) break;
				} else {
					index += step;
					if(index >= cnt) break;
				}
			}
			if(c_out) {
				printf("#endif /*CHRDEF*/\n");
			}
		}
	}
	/* 全キャラクタ数の定義を生成。 */
	if(c_out) {
		printf("#ifndef CHRDEF\n");
		printf("#define %-*s %d\n", sizeof label, "N_CHRS", no);
		printf("#endif /*CHRDEF*/\n");
	}

	/* 通常の画像パックモードなら... */
	if(!u_pack) {
		/* ビットマップを作成。 */
		_makepath(bmp_path, scr_drive, scr_dir, scr_fname, BMP_EXT);
		retval = bmp.save8(bmp_path);
		if(retval != 0) err("%sが作成できません。", bmp_path);
		/* P/ECE用データに変換。 */
		sprintf(cmd, "dpbmpcnv -q -b -f2m \"%s\"", bmp_path); /* -q必要! */
		retval = system(cmd);
		if(retval != 0) err("P/ECE用データへの変換に失敗しました。");
	}

	return 0;
}
