/*
 *	dpvskkrn - P/VNScripterスクリプト to PGD [こころナビ(製品版)]
 *
 *	P/VNScripterスクリプトの「bg」「ld」コマンドで使われている画像ファイル名を抽出して、
 *	pkkrnv.exeを呼び出し、必要なPGDファイルを作成します。
 *	同じフォルダにpkkrnv.exeが必要です。
 *
 *	このプログラムが行うのは、PGDの作成までです。
 *	ファイルパックにはdfpck.exeを利用してください。
 *
 *	※実行環境はP/VNScripter Ver1.0 (2004/06/04) を想定していますが、
 *	  スクリプト書式は旧P/VNS形式("PVNS")にのみ対応しています。
 *	  たぶん、旧P/VNS Ver2.x でも動作すると思います。
 *	  P/VNScripterの新しいスクリプト書式("PVSC")には未対応です。
 *
 *	※「こころナビ」製品版 version 1.1にて動作確認しました。
 *	  version 1.0では動作確認していません。
 *	  初期出荷分は version 1.0 とのことですので、修正パッチを適用して1.1にしてください。
 *	  後期出荷分は最初から version 1.1 です。僕が買った製品のCDも、最初から1.1でした。
 *
 *	* Sat Jun 12 13:39:00 JST 2004 Naoyuki Sawa
 *	- 作成開始。
 */
#define STRICT
#include <windows.h>

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include <io.h>
#include <mbstring.h>

#define MAX_ITEMS			500	/* 最大アイテム数 */
#define DELIM			" ,\"\t\r\n"	/* 簡単のために、「,」と「"」もデリミタにしてしまいます */

#define REG_KEY			"Software\\Q-X\\こころナビ"
#define REG_VALUE		"install"
#define EPK_NAME		"scr.epk"
#define FILE_NAME		"StrMsg.str"

char app_dir[_MAX_PATH];	/* 「こころナビ」のインストールフォルダ名 */

char* items[MAX_ITEMS];		/* アイテムリスト   */
char  types[MAX_ITEMS];		/* bgなら0、ldなら1 */
int n_items;			/* アイテム数       */

#define MAX_MSGS		32768		/* 最大メッセージ数 */

char* msgs[MAX_MSGS];		/* メッセージリスト */
int n_msgs;			/* メッセージ数 */

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

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

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

	exit(1);
}

/****************************************************************************
 *	load_msg
 ****************************************************************************/

/***** アーカイブ形式 (*.epk) *****/

typedef struct _EPKHEAD {
	char signature[4];	/* "EPK " */
	int data_offset;	/* 最初のデータ開始位置へのオフセット(アーカイブ先頭からの絶対位置) */
	int file_count;		/* ファイル数 */
	int _reserved1;
} EPKHEAD;
/* この後に EPKFILE files[file_count] が続きます */

typedef struct _EPKFILE {
	int name_offset;	/* ファイル名へのオフセット(アーカイブ先頭からの絶対位置) */
	int data_offset;	/* データ本体へのオフセット(アーカイブ先頭からの絶対位置) */
	int data_size;		/* データ本体のサイズ */
	int _reserved1;
} EPKFILE;

void
load_msg()
{
	int retval;
	int i;
	int j;
	int len;
	int ch;
	FILE* fp;
	char path[_MAX_PATH];
	char line[1024];
	char* buf;
	char* data;
	char* p;
	EPKHEAD* head;
	EPKFILE* file;

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

	/* ファイルサイズを取得します。 */
	retval = fseek(fp, 0, SEEK_END);
	if(retval != 0) err("%s: シークエラーです。", path);
	len = ftell(fp);
	rewind(fp); /* 忘れないで!! */

	/* ファイル全体を読み込むためのバッファを確保します。 */
	buf = (char*)calloc(len, 1);
	if(!buf) err("%s: メモリ不足です。", path);

	/* ファイル全体を読み込みます。 */
	retval = fread(buf, 1, len, fp);
	if(retval != len) err("%s: ファイル読み込みエラーです。", path);

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

	/* メッセージファイルを探します。 */
	head = (EPKHEAD*)buf;
	if(memcmp(head->signature, "EPK ", 4) != 0) err("%s: シグネチャエラー。", path);
	file = (EPKFILE*)(head + 1);
	for(i = 0; i < head->file_count; i++) {
		if(strcmpi(&buf[file->name_offset], FILE_NAME) == 0) break;
		file++;
	}
	if(i == head->file_count) err("%s: %sが見つかりません。", path, FILE_NAME);

	/* データ先頭位置とデータサイズを取得します。 */
	data = &buf[file->data_offset];
	len = file->data_size;

	/* 暗号化解除。 */
	for(i = 16; i < len; i++) {
		data[i] ^= 0xff;
	}

	/* メッセージ展開。 */
	p = &data[16];
	j = 0;
	while(p < &data[len]) {
		ch = _mbsnextc(p);
		switch(ch) {
		case '\0':
			if(j >= sizeof line) err("%s: 行バッファ不足です。", path);
			line[j] = '\0';
			if(n_msgs >= MAX_MSGS) err("%s: メッセージリスト不足です。", path);
			msgs[n_msgs] = strdup(line);
			if(!msgs[n_msgs]) err("%s: メモリ不足です。", path);
			n_msgs++;
			j = 0; /* 行バッファクリア。忘れずに!! */
			break;
		case '①':
			if(j >= sizeof line - 4) err("%s: 行バッファ不足です。", path);
			memcpy(&line[j], "今関", 4);
			j += 4;
			break;
		case '②':
			if(j >= sizeof line - 6) err("%s: 行バッファ不足です。", path);
			memcpy(&line[j], "勇太郎", 6);
			j += 6;
			break;
		case '③':
			if(j >= sizeof line - 8) err("%s: 行バッファ不足です。", path);
			memcpy(&line[j], "リュウヤ", 8);
			j += 8;
			break;
		default:
			if(ch & 0xff00) {
				if(j >= sizeof line - 2) err("%s: 行バッファ不足です。", path);
				line[j++] = ch >> 8;
				line[j++] = ch & 0xff;
			} else {
				/* 半角文字は削除。半角文字はぜんぶ制御文字です。 */
			}
			break;
		}
		p = _mbsinc(p);
	}

	/* バッファを開放します。 */
	free(buf);
}

/****************************************************************************
 *	make
 ****************************************************************************/

int
main(int argc, char* argv[])
{
	int retval;
	int i;
	int j;
	int len;
	char* p1;
	char* p2;
	char str[1024];
	HKEY hKey;

	/* 標準入力はP/VNScripterスクリプトからのリダイレクトでなければいけません。 */
	if(isatty(fileno(stdin))) {
USAGE:
		fprintf(stderr, "P/VNScripterスクリプト to PGD [こころナビ(製品版)]\n");
		fprintf(stderr, "\n");
		fprintf(stderr, "使い方:\n");
		fprintf(stderr, "  dpvskkrn [pkkrnv.exeへのｵﾌﾟｼｮﾝ] < ｽｸﾘﾌﾟﾄﾃﾝﾌﾟﾚｰﾄ > 実効ｽｸﾘﾌﾟﾄ.pvn \n");
		fprintf(stderr, "\n");
		fprintf(stderr, "スクリプト拡張書式:\n");
		fprintf(stderr, "  @メッセージ番号 ... メッセージ内容にて置き換えます。\n");
		fprintf(stderr, "  @dump           ... チェック用メッセージリストを出力します。\n");
		exit(1);
	}

	/* コマンドライン解析。(pkkrnv.exeへのオプションのうち、-dだけを共有します) */
	for(i = 1; i < argc; i++) {
		p1 = argv[i];
		if(p1[0] == '-') { /* オプション */
			switch(p1[1]) {
			case 'd': /* 「こころナビ」インストールフォルダ指定 */
				if(strlen(app_dir) != 0) goto USAGE; /* 二重指定 */
				strncpy(app_dir, &p1[2], sizeof app_dir);
				break;
			default:
				break; /* pkkrnv.exeへのオプションなので、無視します。 */
			}
		}
	}

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

	/* メッセージリストを準備します。 */
	load_msg();

	/* P/VNScripterスクリプトの全行を走査して... */
	while(gets(str) != NULL) {
		/* チェック用メッセージリスト出力？ (@dump) */
		if(strcmpi(str, "@dump") == 0) {
			printf(";%s\n", str);
			for(i = 0; i < n_msgs; i++) {
				printf(";%5d: %s\n", i, msgs[i]);
			}

		/* メッセージ置き換え？ (@メッセージ番号) */
		} else if(sscanf(str, "@%d", &i) == 1) {
			/* メッセージに置き換えて出力。 */
			if(i < 0 || i > n_msgs - 1) err("\"%s\"はメッセージ番号が不正です。\n", str);
			printf(";%s\n", str);
			puts(msgs[i]);

		} else {
			/* そのまま出力。 */
			puts(str);

			/* コメント以降を切り捨てます。 */
			p1 = strchr(str, ';');
			if(p1 != NULL) *p1 = '\0';

			/* この行の各トークンについて... */
			p1 = strtok(str, DELIM);
			while(p1 != NULL) {
				/* 「bg」または「ld」コマンド？ */
				if(strcmp(p1, "bg") == 0 || strcmp(p1, "ld") == 0) {
					/* 次のトークンが画像ファイル名？ */
					p2 = strtok(NULL, DELIM);
					if(p2 == NULL) break; /* もうトークンがない */
					len = strlen(p2);
					if(len < 1/*少なくとも1文字のfname*/ + 4/*.pgd*/) continue; /* ～.pgdじゃない */
					if(strcmp(&p2[len - 4], ".pgd") != 0) continue;             /* ～.pgdじゃない */
					p2[len - 4] = '\0'; /* 拡張子切り捨て */

					/* アイテム数がいっぱいでなく、新しく出現したアイテムなら、登録。 */
					if(n_items < MAX_ITEMS) {
						for(i = 0; i < n_items; i++) {
							if(strcmp(items[i], p2) == 0) break; /* もうある */
						}
						if(i == n_items) { /* 登録 */
							items[n_items] = strdup(p2);
							types[n_items] = strcmp(p1, "ld") == 0 ? 1 : 0;	/* 0:bg/1:ld */
							n_items++;
						}
					}
				}

				/* 次のトークンへ。 */
				p1 = strtok(NULL, DELIM);
			}
		}
	}

	/* アイテムリスト出力。 */
	if(n_items != 0) {
		for(i = 0; i < n_items; i++) {
			/* コマンドライン作成。 */
			strcpy(str, "pkkrnv ");
			for(j = 1; j < argc; j++) { /* 全てのオプションをパススルー */
				strcat(str, argv[j]);
				strcat(str, " ");
			}
			if(types[i]) strcat(str, "-c "); /* 「ld」なら立ち絵モード */
			strcat(str, items[i]); /* アイテム指定 */
			if(system(str) != 0) err("\"%s\"の実行中にエラーが発生しました。\n", str);
		}
	}

	return 0;
}
