// verify.c
// Copyright : 2022-02-25 Yutaka Sawada
// License : The MIT license

#ifndef _UNICODE
#define _UNICODE
#endif
#ifndef UNICODE
#define UNICODE
#endif
#ifndef _WIN32_WINNT
#define _WIN32_WINNT 0x0600	// Windows Vista or later
#endif

#include <stdio.h>

#include <windows.h>

#include "common.h"
#include "crc.h"
#include "verify.h"
#include "ini.h"
#include "phmd5.h"

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

// CRC-32 を比較する
//  0 = ファイルが存在して完全である
//  1 = ファイルが存在しない
//  2 = ファイルが破損してる
static int file_crc32_check(
	int num,
	char *ascii_name,
	wchar_t *uni_name,
	wchar_t *file_path,
	unsigned int crc2)
{
	unsigned char buf[IO_SIZE];
	int len, off, bad_flag;
	unsigned int crc, time_last, meta_data[7];
	__int64 file_size = 0, file_left;
	HANDLE hFile;

	prog_last = -1;
	time_last = GetTickCount() / UPDATE_TIME;	// 時刻の変化時に経過を表示する
	wcscpy(file_path, base_dir);
	// 先頭の「..\」を許可してるので、基準ディレクトリから上に遡る。
	len = base_len - 1;
	off = 0;
	while ((uni_name[off] == '.') && (uni_name[off + 1] == '.') && (uni_name[off + 2] == '\\')){
		off += 3;
		file_path[len] = 0;
		while (file_path[len] != '\\'){
			file_path[len] = 0;
			len--;
		}
	}
	wcscat(file_path, uni_name + off);

	// 読み込むファイルを開く
	hFile = CreateFile(file_path, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
	if (hFile == INVALID_HANDLE_VALUE){
		bad_flag = 1;	// 消失は記録しない
	} else {
		bad_flag = check_ini_state(num, meta_data, hFile);
		memcpy(&file_size, meta_data, 8);
		if (bad_flag == -2){	// 記録が無い場合 (属性取得エラーは不明にする)
			file_left = file_size;
			crc = 0xFFFFFFFF;	// 初期化
			while (file_left > 0){
				len = IO_SIZE;
				if (file_left < IO_SIZE)
					len = (int)file_left;
				if (!ReadFile(hFile, buf, len, &bad_flag, NULL) || (len != bad_flag))
					break;	// 読み取りエラーは必ず破損になる
				file_left -= len;
				// CRC-32 を更新する
				crc = crc_update(crc, buf, len);

				// 経過表示
				if (GetTickCount() / UPDATE_TIME != time_last){
					if (print_progress_file((int)(((file_size - file_left) * 1000) / file_size), uni_name)){
						CloseHandle(hFile);
						return 2;
					}
					time_last = GetTickCount() / UPDATE_TIME;
				}
			}
			crc ^= 0xFFFFFFFF;	// 最終処理
			if ((crc != crc2) || (file_left > 0)){
				bad_flag = 2;	// ハッシュ値が異なる、または途中まで
			} else {
				bad_flag = 0;	// 完全
			}
			write_ini_state(num, meta_data, bad_flag);	// 検査結果を記録する、完全か破損
		}
		CloseHandle(hFile);
	}

	switch (bad_flag){
	case 0:
		printf("%13I64d Complete : \"%s\"\n", file_size, ascii_name);
		break;
	case 1:
		printf("            0 Missing  : \"%s\"\n", ascii_name);
		bad_flag = 4 | 8;
		break;
	case 2:
		printf("%13I64d Damaged  : \"%s\"\n", file_size, ascii_name);
		bad_flag = 4;
		break;
	default:	// IOエラー等
		printf("            ? Unknown  : \"%s\"\n", ascii_name);
		bad_flag = 4;
		break;
	}
	fflush(stdout);
	return bad_flag;
}

// SFV ファイル
int verify_sfv(
	char *ascii_buf,
	wchar_t *file_path)
{
	wchar_t *line_off, uni_buf[MAX_LEN];
	unsigned int err = 0, rv, line_len, name_len, crc, line_num, num, comment;

	// ファイル数を調べる
	comment = 0;
	num = 0;
	line_num = 1;
	line_off = text_buf;
	while (*line_off != 0){	// 一行ずつ処理する
		line_len = 0;
		while (line_off[line_len] != 0){	// 改行までを一行とする
			if (line_off[line_len] == '\n')
				break;
			if (line_off[line_len] == '\r')
				break;
			line_len++;
		}
		// 行の内容が適正か調べる
		if (line_off[0] == ';'){	// コメント
			if (((comment & 1) == 0) && (line_len > 8) && (line_len < MAX_LEN)){
				// クリエイターの表示がまだなら
				if (wcsncmp(line_off, L"; Generated by ", 15) == 0){	// WIN-SFV32, SFV32nix
					wcsncpy(uni_buf, line_off + 15, line_len - 15);
					uni_buf[line_len - 15] = 0;
					comment |= 1;
				} else if (wcsncmp(line_off, L"; Using ", 8) == 0){
					wcsncpy(uni_buf, line_off + 8, line_len - 8);
					uni_buf[line_len - 8] = 0;
					comment |= 1;	// 同一行を二度表示しない
				}
				if (comment & 1){
					uni_buf[COMMENT_LEN - 1] = 0;	// 表示する文字数を制限する
					utf16_to_cp(uni_buf, ascii_buf, COMMENT_LEN * 3, cp_output);
					printf("Creator : %s\n", ascii_buf);
					comment |= 4;
				}
			}
			if (((comment & 6) == 0) && (line_len < MAX_LEN)){
				// 最初のコメントの表示がまだなら
				crc = 1;
				while (crc < line_len){
					if (line_off[crc] != ' ')
						break;
					crc++;
				}
				if (crc < line_len){
					wcsncpy(uni_buf, line_off + crc, line_len - crc);
					uni_buf[line_len - crc] = 0;
					uni_buf[COMMENT_LEN - 1] = 0;	// 表示する文字数を制限する
					utf16_to_cp(uni_buf, ascii_buf, COMMENT_LEN * 3, cp_output);
					printf("Comment : %s\n", ascii_buf);
				}
				comment |= 2;	// 「;」だけの行も認識する
			}
			comment &= ~4;	// bit 4 を消す
		} else if ((line_len > 9) && (line_off[line_len - 9] == ' ')	// CRC の前がスペース
					&& (base16_len(line_off + (line_len - 8)) == 8)){	// 16進数で8文字
			// ファイル名
			name_len = line_len - 9;
			if (base_len + name_len < MAX_LEN){
				while (line_off[name_len - 1] == ' ')
					name_len--;
				// ファイル名の前後が「"」で囲まれてる場合は取り除く
				if ((line_off[0] == '"') && (line_off[name_len - 1] == '"')){
					name_len -= 2;
					wcsncpy(uni_buf, line_off + 1, name_len);
				} else {
					wcsncpy(uni_buf, line_off, name_len);
				}
				uni_buf[name_len] = 0;
				rv = sanitize_filename(uni_buf);	// ファイル名を浄化する
				if (rv > 1){
					if ((comment & 8) == 0){
						comment |= 8;
						printf("\nWarning about filenames :\n");
					}
					if (rv == 16){
						printf("line%d: filename is invalid\n", line_num);
						num++;	// 浄化できないファイル名
					} else if ((rv & 6) == 0){
						utf16_to_cp(uni_buf, ascii_buf, MAX_LEN * 3, cp_output);
						printf("line%d: \"%s\" is invalid\n", line_num, ascii_buf);
					} else {
						utf16_to_cp(uni_buf, ascii_buf, MAX_LEN * 3, cp_output);
						printf("line%d: \"%s\" was sanitized\n", line_num, ascii_buf);
					}
				}
				if (rv != 16)
					file_num++;
			} else {
				if ((comment & 8) == 0){
					comment |= 8;
					printf("\nWarning about filenames :\n");
				}
				printf("line%d: filename is invalid\n", line_num);
				num++;	// 長すぎるファイル名
			}
		} else if (line_len > 0){
			//printf("line %d is invalid\n", line_num);
			num++;	// 内容が認識できない行
		}
		// 次の行へ
		line_off += line_len;
		if (*line_off == '\n')
			line_off++;
		if (*line_off == '\r'){
			line_off++;
			if (*line_off == '\n')	// 「\r\n」を一つの改行として扱う
				line_off++;
		}
		line_num++;
	}
	if (comment & 8)
		printf("\n");
	// チェックサム・ファイルの状態
	if (num == 0){
		printf("Status  : Good\n");
	} else {
		printf("Status  : Damaged\n");
		err |= 16;	// 後で 256に変更する
	}
	if (file_num == 0){
		printf("valid file is not found\n");
		return 1;
	}

	printf("\nInput File list : %d\n", file_num);
	printf(" CRC-32  :  Filename\n");
	fflush(stdout);
	line_off = text_buf;
	while (*line_off != 0){	// 一行ずつ処理する
		line_len = 0;
		while (line_off[line_len] != 0){	// 改行までを一行とする
			if (line_off[line_len] == '\n')
				break;
			if (line_off[line_len] == '\r')
				break;
			line_len++;
		}
		// 行の内容が適正か調べる
		if ((line_off[0] != ';') &&		// コメントではない
				(line_len > 9) && (line_off[line_len - 9] == ' ')	// CRC の前がスペース
				&& (base16_len(line_off + (line_len - 8)) == 8)){	// 16進数で8文字
			// ファイル名
			name_len = line_len - 9;
			if (base_len + name_len < MAX_LEN){
				while (line_off[name_len - 1] == ' ')
					name_len--;
				// ファイル名の前後が「"」で囲まれてる場合は取り除く
				if ((line_off[0] == '"') && (line_off[name_len - 1] == '"')){
					name_len -= 2;
					wcsncpy(uni_buf, line_off + 1, name_len);
				} else {
					wcsncpy(uni_buf, line_off, name_len);
				}
				uni_buf[name_len] = 0;
				rv = sanitize_filename(uni_buf);	// ファイル名を浄化する
				if (rv != 16){
					utf16_to_cp(uni_buf, ascii_buf, MAX_LEN * 3, cp_output);
					// CRC
					crc = get_val32h(line_off + (line_len - 8));
					printf("%08X : \"%s\"\n", crc, ascii_buf);
				}
			}
		}
		// 次の行へ
		line_off += line_len;
		if (*line_off == '\n')
			line_off++;
		if (*line_off == '\r'){
			line_off++;
			if (*line_off == '\n')	// 「\r\n」を一つの改行として扱う
				line_off++;
		}
	}


	printf("\nVerifying Input File    :\n");
	printf("         Size Status   :  Filename\n");
	fflush(stdout);
	if (recent_data != 0){	// 前回の検査結果を使うなら
		PHMD5 ctx;
		// チェックサム・ファイル識別用にハッシュ値を計算する
		Phmd5Begin(&ctx);
		Phmd5Process(&ctx, (unsigned char *)text_buf, text_len * 2);
		Phmd5End(&ctx);
		// 前回の検査結果が存在するか
		check_ini_file(ctx.hash, text_len);
	}
	num = 0;
	line_off = text_buf;
	while (*line_off != 0){	// 一行ずつ処理する
		line_len = 0;
		while (line_off[line_len] != 0){	// 改行までを一行とする
			if (line_off[line_len] == '\n')
				break;
			if (line_off[line_len] == '\r')
				break;
			line_len++;
		}
		// 行の内容が適正か調べる
		if ((line_off[0] != ';') &&		// コメントではない
				(line_len > 9) && (line_off[line_len - 9] == ' ')	// CRC の前がスペース
				&& (base16_len(line_off + (line_len - 8)) == 8)){	// 16進数で8文字
			// ファイル名
			name_len = line_len - 9;
			if (base_len + name_len < MAX_LEN){
				while (line_off[name_len - 1] == ' ')
					name_len--;
				// ファイル名の前後が「"」で囲まれてる場合は取り除く
				if ((line_off[0] == '"') && (line_off[name_len - 1] == '"')){
					name_len -= 2;
					wcsncpy(uni_buf, line_off + 1, name_len);
				} else {
					wcsncpy(uni_buf, line_off, name_len);
				}
				uni_buf[name_len] = 0;
				rv = sanitize_filename(uni_buf);	// ファイル名を浄化する
				if (rv != 16){
					utf16_to_cp(uni_buf, ascii_buf, MAX_LEN * 3, cp_output);
					// CRC
					crc = get_val32h(line_off + (line_len - 8));
					rv = file_crc32_check(num, ascii_buf, uni_buf, file_path, crc);
					if (rv & 3){
						err = rv;
						break;
					}
					if (rv & 0xC){	// Missing or Damaged
						err |= rv;
						err += 0x100;
					}
					num++;
				}
			}
		}
		// 次の行へ
		line_off += line_len;
		if (*line_off == '\n')
			line_off++;
		if (*line_off == '\r'){
			line_off++;
			if (*line_off == '\n')	// 「\r\n」を一つの改行として扱う
				line_off++;
		}
	}

	return err;
}

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

// MD5 を比較する
//  0 = ファイルが存在して完全である
//  1 = ファイルが存在しない
//  2 = ファイルが破損してる
static int file_md5_check(
	int num,
	char *ascii_name,
	wchar_t *uni_name,
	wchar_t *file_path,
	unsigned char *hash)
{
	unsigned char buf[IO_SIZE];
	int len, off, bad_flag;
	unsigned int time_last, meta_data[8];
	__int64 file_size = 0, file_left;
	HANDLE hFile;
	PHMD5 ctx;

	prog_last = -1;
	time_last = GetTickCount() / UPDATE_TIME;	// 時刻の変化時に経過を表示する
	wcscpy(file_path, base_dir);
	// 先頭の「..\」を許可してるので、基準ディレクトリから上に遡る。
	len = base_len - 1;
	off = 0;
	while ((uni_name[off] == '.') && (uni_name[off + 1] == '.') && (uni_name[off + 2] == '\\')){
		off += 3;
		file_path[len] = 0;
		while (file_path[len] != '\\'){
			file_path[len] = 0;
			len--;
		}
	}
	wcscat(file_path, uni_name + off);
	//printf("path = \"%S\"\n", file_path);

	// 読み込むファイルを開く
	hFile = CreateFile(file_path, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
	if (hFile == INVALID_HANDLE_VALUE){
		bad_flag = 1;	// 消失は記録しない
	} else {
		bad_flag = check_ini_state(num, meta_data, hFile);
		memcpy(&file_size, meta_data, 8);
		if (bad_flag == -2){	// 記録が無い場合 (属性取得エラーは不明にする)
			file_left = file_size;
			Phmd5Begin(&ctx);	// 初期化
			while (file_left > 0){
				len = IO_SIZE;
				if (file_left < IO_SIZE)
					len = (int)file_left;
				if (!ReadFile(hFile, buf, len, &bad_flag, NULL) || (len != bad_flag))
					break;	// 読み取りエラーは必ず破損になる
				file_left -= len;
				// MD5 を更新する
				Phmd5Process(&ctx, buf, len);

				// 経過表示
				if (GetTickCount() / UPDATE_TIME != time_last){
					if (print_progress_file((int)(((file_size - file_left) * 1000) / file_size), uni_name)){
						CloseHandle(hFile);
						return 2;
					}
					time_last = GetTickCount() / UPDATE_TIME;
				}
			}
			Phmd5End(&ctx);	// 最終処理
//			printf("%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X %d\n",
//				ctx.hash[0], ctx.hash[1], ctx.hash[2], ctx.hash[3], ctx.hash[4], ctx.hash[5], ctx.hash[6], ctx.hash[7],
//				ctx.hash[8], ctx.hash[9], ctx.hash[10], ctx.hash[11], ctx.hash[12], ctx.hash[13], ctx.hash[14], ctx.hash[15]);
			if ((memcmp(hash, ctx.hash, 16) != 0) || (file_left > 0)){
				bad_flag = 2;	// ハッシュ値が異なる、または途中まで
			} else {
				bad_flag = 0;	// 完全
			}
			write_ini_state(num, meta_data, bad_flag);	// 検査結果を記録する、完全か破損
		}
		CloseHandle(hFile);
	}

	switch (bad_flag){
	case 0:
		printf("%13I64d Complete : \"%s\"\n", file_size, ascii_name);
		break;
	case 1:
		printf("            0 Missing  : \"%s\"\n", ascii_name);
		bad_flag = 4 | 8;
		break;
	case 2:
		printf("%13I64d Damaged  : \"%s\"\n", file_size, ascii_name);
		bad_flag = 4;
		break;
	default:	// IOエラー等
		printf("            ? Unknown  : \"%s\"\n", ascii_name);
		bad_flag = 4;
		break;
	}
	fflush(stdout);
	return bad_flag;
}

// MD5 ファイル
int verify_md5(
	char *ascii_buf,
	wchar_t *file_path)
{
	unsigned char hash[16];
	wchar_t *line_off, uni_buf[MAX_LEN], num_buf[3];
	unsigned int err = 0, i, rv, line_len, name_len, line_num, num, comment;

	// ファイル数を調べる
	comment = 0;
	num = 0;
	line_num = 1;
	line_off = text_buf;
	while (*line_off != 0){	// 一行ずつ処理する
		line_len = 0;
		while (line_off[line_len] != 0){	// 改行までを一行とする
			if (line_off[line_len] == '\n')
				break;
			if (line_off[line_len] == '\r')
				break;
			line_len++;
		}
		// 行の内容が適正か調べる
		if (line_off[0] == ';'){	// コメント
			if (((comment & 1) == 0) && (line_len > 8) && (line_len < MAX_LEN)){
				// クリエイターの表示がまだなら
				if (wcsncmp(line_off, L"; Generated by ", 15) == 0){	// md5sum, Easy MD5 Creator
					wcsncpy(uni_buf, line_off + 15, line_len - 15);
					uni_buf[line_len - 15] = 0;
					comment = 1;
				} else if (wcsncmp(line_off, L"; Using ", 8) == 0){
					wcsncpy(uni_buf, line_off + 8, line_len - 8);
					uni_buf[line_len - 8] = 0;
					comment = 1;
				}
				if (comment & 1){
					uni_buf[COMMENT_LEN - 1] = 0;	// 表示する文字数を制限する
					utf16_to_cp(uni_buf, ascii_buf, COMMENT_LEN * 3, cp_output);
					printf("Creator : %s\n", ascii_buf);
					comment |= 4;	// 同一行を二度表示しない
				}
			}
			if (((comment & 6) == 0) && (line_len < MAX_LEN)){
				// 最初のコメントの表示がまだなら
				i = 1;
				while (i < line_len){
					if (line_off[i] != ' ')
						break;
					i++;
				}
				if (i < line_len){
					wcsncpy(uni_buf, line_off + i, line_len - i);
					uni_buf[line_len - i] = 0;
					uni_buf[COMMENT_LEN - 1] = 0;	// 表示する文字数を制限する
					utf16_to_cp(uni_buf, ascii_buf, COMMENT_LEN * 3, cp_output);
					printf("Comment : %s\n", ascii_buf);
				}
				comment |= 2;	// 「;」だけの行も認識する
			}
			comment &= ~4;	// bit 4 を消す
		} else if (line_off[0] == '#'){	// コメント
			if (((comment & 1) == 0) && (line_len > 8) && (line_len < MAX_LEN)){
				// クリエイターの表示がまだなら
				if (wcsncmp(line_off, L"# Generated by ", 15) == 0){	// OpenHashTab
					wcsncpy(uni_buf, line_off + 15, line_len - 15);
					uni_buf[line_len - 15] = 0;
					comment = 1;
				} else if (wcsncmp(line_off, L"# MD5 checksum generated by ", 28) == 0){	// IsoBuster
					wcsncpy(uni_buf, line_off + 28, line_len - 28);
					uni_buf[line_len - 28] = 0;
					comment = 1;
				} else if (wcsncmp(line_off, L"#MD5 checksums generated by ", 28) == 0){	// xACT
					wcsncpy(uni_buf, line_off + 28, line_len - 28);
					uni_buf[line_len - 28] = 0;
					comment = 1;
				} else if (wcsncmp(line_off, L"# MD5 checksums generated by ", 29) == 0){	// MD5summer
					wcsncpy(uni_buf, line_off + 29, line_len - 29);
					uni_buf[line_len - 29] = 0;
					comment = 1;
				}
				if (comment & 1){
					uni_buf[COMMENT_LEN - 1] = 0;	// 表示する文字数を制限する
					utf16_to_cp(uni_buf, ascii_buf, COMMENT_LEN * 3, cp_output);
					printf("Creator : %s\n", ascii_buf);
				}
			}
		} else if (line_len > 33){	// コメントではない
			// MD5 の後がスペース (md5sum 形式)
			if ((line_off[32] == ' ') && (base16_len(line_off) == 32)){
				// ファイル名
				name_len = line_len - 33;
				if (base_len + name_len < MAX_LEN){
					// タイプ記号「*」までのスペースは複数個でもいい
					while (line_off[line_len - name_len] == ' ')
						name_len--;
					if (line_off[line_len - name_len] == '*')
						name_len--;
					wcsncpy(uni_buf, line_off + (line_len - name_len), name_len);
					uni_buf[name_len] = 0;
					rv = sanitize_filename(uni_buf);	// ファイル名を浄化する
					if (rv > 1){
						if ((comment & 8) == 0){
							comment |= 8;
							printf("\nWarning about filenames :\n");
						}
						if (rv == 16){
							printf("line%d: filename is invalid\n", line_num);
							num++;	// 浄化できないファイル名
						} else if ((rv & 6) == 0){
							utf16_to_cp(uni_buf, ascii_buf, MAX_LEN * 3, cp_output);
							printf("line%d: \"%s\" is invalid\n", line_num, ascii_buf);
						} else {
							utf16_to_cp(uni_buf, ascii_buf, MAX_LEN * 3, cp_output);
							printf("line%d: \"%s\" was sanitized\n", line_num, ascii_buf);
						}
					}
					if (rv != 16)
						file_num++;
				} else {
					if ((comment & 8) == 0){
						comment |= 8;
						printf("\nWarning about filenames :\n");
					}
					printf("line%d: filename is invalid\n", line_num);
					num++;	// 長すぎるファイル名
				}

			// ファイル名の後にMD5
			} else if ((line_off[line_len - 33] == ' ') && (base16_len(line_off + (line_len - 32)) == 32)){
				// MD5 の前が括弧 (BSD/OpenSSL 形式)
				if (((wcsncmp(line_off, L"MD5(", 4) == 0) || (wcsncmp(line_off, L"MD5 (", 5) == 0))
						&& ((wcsncmp(line_off + (line_len - 35), L")= ", 3) == 0) ||
							(wcsncmp(line_off + (line_len - 36), L") = ", 4) == 0))){
					// ファイル名
					name_len = line_len - 35 - 4;
					if (base_len + name_len < MAX_LEN){
						if (line_off[line_len - 35] != ')')
							name_len--;
						if (line_off[3] == ' '){
							name_len--;
							wcsncpy(uni_buf, line_off + 5, name_len);
						} else {
							wcsncpy(uni_buf, line_off + 4, name_len);
						}
						uni_buf[name_len] = 0;
						rv = sanitize_filename(uni_buf);	// ファイル名を浄化する
						if (rv > 1){
							if ((comment & 8) == 0){
								comment |= 8;
								printf("\nWarning about filenames :\n");
							}
							if (rv == 16){
								printf("line%d: filename is invalid\n", line_num);
								num++;	// 浄化できないファイル名
							} else if ((rv & 6) == 0){
								utf16_to_cp(uni_buf, ascii_buf, MAX_LEN * 3, cp_output);
								printf("line%d: \"%s\" is invalid\n", line_num, ascii_buf);
							} else {
								utf16_to_cp(uni_buf, ascii_buf, MAX_LEN * 3, cp_output);
								printf("line%d: \"%s\" was sanitized\n", line_num, ascii_buf);
							}
						}
						if (rv != 16)
							file_num++;
					} else {
						if ((comment & 8) == 0){
							comment |= 8;
							printf("\nWarning about filenames :\n");
						}
						printf("line%d: filename is invalid\n", line_num);
						num++;	// 長すぎるファイル名
					}
				} else {	// MD5 の前がスペース (Easy MD5 Creator 形式)
					// ファイル名
					name_len = line_len - 33;
					if (base_len + name_len < MAX_LEN){
						while (line_off[name_len - 1] == ' ')
							name_len--;
						wcsncpy(uni_buf, line_off, name_len);
						uni_buf[name_len] = 0;
						rv = sanitize_filename(uni_buf);	// ファイル名を浄化する
						if (rv > 1){
							if ((comment & 8) == 0){
								comment |= 8;
								printf("\nWarning about filenames :\n");
							}
							if (rv == 16){
								printf("line%d: filename is invalid\n", line_num);
								num++;	// 浄化できないファイル名
							} else if ((rv & 6) == 0){
								utf16_to_cp(uni_buf, ascii_buf, MAX_LEN * 3, cp_output);
								printf("line%d: \"%s\" is invalid\n", line_num, ascii_buf);
							} else {
								utf16_to_cp(uni_buf, ascii_buf, MAX_LEN * 3, cp_output);
								printf("line%d: \"%s\" was sanitized\n", line_num, ascii_buf);
							}
						}
						if (rv != 16)
							file_num++;
					} else {
						if ((comment & 8) == 0){
							comment |= 8;
							printf("\nWarning about filenames :\n");
						}
						printf("line%d: filename is invalid\n", line_num);
						num++;	// 長すぎるファイル名
					}
				}
			} else {
				num++;	// 内容が認識できない行
			}
		} else if (line_len > 0){
			num++;	// 内容が認識できない行
		}
		// 次の行へ
		line_off += line_len;
		if (*line_off == '\n')
			line_off++;
		if (*line_off == '\r'){
			line_off++;
			if (*line_off == '\n')	// 「\r\n」を一つの改行として扱う
				line_off++;
		}
		line_num++;
	}
	if (comment & 8)
		printf("\n");
	// チェックサム・ファイルの状態
	if (num == 0){
		printf("Status  : Good\n");
	} else {
		printf("Status  : Damaged\n");
		err |= 16;	// 後で 256に変更する
	}
	if (file_num == 0){
		printf("valid file is not found\n");
		return 1;
	}

	printf("\nInput File list : %d\n", file_num);
	printf("\t    MD5 Hash\t\t :  Filename\n");
	fflush(stdout);
	line_off = text_buf;
	while (*line_off != 0){	// 一行ずつ処理する
		line_len = 0;
		while (line_off[line_len] != 0){	// 改行までを一行とする
			if (line_off[line_len] == '\n')
				break;
			if (line_off[line_len] == '\r')
				break;
			line_len++;
		}
		// 行の内容が適正か調べる
		if ((line_off[0] != ';') && (line_len > 33)){	// コメントではない
			// MD5 の後がスペース (md5sum 形式)
			if ((line_off[32] == ' ') && (base16_len(line_off) == 32)){
				// ファイル名
				name_len = line_len - 33;
				if (base_len + name_len < MAX_LEN){
					// タイプ記号「*」までのスペースは複数個でもいい
					while (line_off[line_len - name_len] == ' ')
						name_len--;
					if (line_off[line_len - name_len] == '*')
						name_len--;
					wcsncpy(uni_buf, line_off + (line_len - name_len), name_len);
					uni_buf[name_len] = 0;
					rv = sanitize_filename(uni_buf);	// ファイル名を浄化する
					if (rv != 16){
						utf16_to_cp(uni_buf, ascii_buf, MAX_LEN * 3, cp_output);
						// MD5
						num_buf[2] = 0;
						for (i = 0; i < 16; i++){
							wcsncpy(num_buf, line_off + (i * 2), 2);
							hash[i] = (unsigned char)get_val32h(num_buf);
						}
						printf("%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X : \"%s\"\n",
							hash[0], hash[1], hash[2], hash[3], hash[4], hash[5], hash[6], hash[7],
							hash[8], hash[9], hash[10], hash[11], hash[12], hash[13], hash[14], hash[15], ascii_buf);
					}
				}

			// ファイル名の後にMD5
			} else if ((line_off[line_len - 33] == ' ') && (base16_len(line_off + (line_len - 32)) == 32)){
				// MD5 の前が括弧 (BSD/OpenSSL 形式)
				if (((wcsncmp(line_off, L"MD5(", 4) == 0) || (wcsncmp(line_off, L"MD5 (", 5) == 0))
						&& ((wcsncmp(line_off + (line_len - 35), L")= ", 3) == 0) ||
							(wcsncmp(line_off + (line_len - 36), L") = ", 4) == 0))){
					// ファイル名
					name_len = line_len - 35 - 4;
					if (base_len + name_len < MAX_LEN){
						if (line_off[line_len - 35] != ')')
							name_len--;
						if (line_off[3] == ' '){
							name_len--;
							wcsncpy(uni_buf, line_off + 5, name_len);
						} else {
							wcsncpy(uni_buf, line_off + 4, name_len);
						}
						uni_buf[name_len] = 0;
						rv = sanitize_filename(uni_buf);	// ファイル名を浄化する
						if (rv != 16){
							utf16_to_cp(uni_buf, ascii_buf, MAX_LEN * 3, cp_output);
							// MD5
							num_buf[2] = 0;
							for (i = 0; i < 16; i++){
								wcsncpy(num_buf, line_off + (line_len - 32 + (i * 2)), 2);
								hash[i] = (unsigned char)get_val32h(num_buf);
							}
							printf("%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X : \"%s\"\n",
								hash[0], hash[1], hash[2], hash[3], hash[4], hash[5], hash[6], hash[7],
								hash[8], hash[9], hash[10], hash[11], hash[12], hash[13], hash[14], hash[15], ascii_buf);
						}
					}
				} else {	// MD5 の前がスペース (Easy MD5 Creator 形式)
					// ファイル名
					name_len = line_len - 33;
					if (base_len + name_len < MAX_LEN){
						while (line_off[name_len - 1] == ' ')
							name_len--;
						wcsncpy(uni_buf, line_off, name_len);
						uni_buf[name_len] = 0;
						rv = sanitize_filename(uni_buf);	// ファイル名を浄化する
						if (rv != 16){
							utf16_to_cp(uni_buf, ascii_buf, MAX_LEN * 3, cp_output);
							// MD5
							num_buf[2] = 0;
							for (i = 0; i < 16; i++){
								wcsncpy(num_buf, line_off + (line_len - 32 + (i * 2)), 2);
								hash[i] = (unsigned char)get_val32h(num_buf);
							}
							printf("%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X : \"%s\"\n",
								hash[0], hash[1], hash[2], hash[3], hash[4], hash[5], hash[6], hash[7],
								hash[8], hash[9], hash[10], hash[11], hash[12], hash[13], hash[14], hash[15], ascii_buf);
						}
					}
				}
			}
		}
		// 次の行へ
		line_off += line_len;
		if (*line_off == '\n')
			line_off++;
		if (*line_off == '\r'){
			line_off++;
			if (*line_off == '\n')	// 「\r\n」を一つの改行として扱う
				line_off++;
		}
	}

	printf("\nVerifying Input File    :\n");
	printf("         Size Status   :  Filename\n");
	fflush(stdout);
	if (recent_data != 0){	// 前回の検査結果を使うなら
		PHMD5 ctx;
		// チェックサム・ファイル識別用にハッシュ値を計算する
		Phmd5Begin(&ctx);
		Phmd5Process(&ctx, (unsigned char *)text_buf, text_len * 2);
		Phmd5End(&ctx);
		// 前回の検査結果が存在するか
		check_ini_file(ctx.hash, text_len);
	}
	num = 0;
	line_off = text_buf;
	while (*line_off != 0){	// 一行ずつ処理する
		line_len = 0;
		while (line_off[line_len] != 0){	// 改行までを一行とする
			if (line_off[line_len] == '\n')
				break;
			if (line_off[line_len] == '\r')
				break;
			line_len++;
		}
		// 行の内容が適正か調べる
		if ((line_off[0] != ';') && (line_len > 33)){	// コメントではない
			// MD5 の後がスペース (md5sum 形式)
			if ((line_off[32] == ' ') && (base16_len(line_off) == 32)){
				// ファイル名
				name_len = line_len - 33;
				if (base_len + name_len < MAX_LEN){
					// タイプ記号「*」までのスペースは複数個でもいい
					while (line_off[line_len - name_len] == ' ')
						name_len--;
					if (line_off[line_len - name_len] == '*')
						name_len--;
					wcsncpy(uni_buf, line_off + (line_len - name_len), name_len);
					uni_buf[name_len] = 0;
					rv = sanitize_filename(uni_buf);	// ファイル名を浄化する
					if (rv != 16){
						utf16_to_cp(uni_buf, ascii_buf, MAX_LEN * 3, cp_output);
						// MD5
						num_buf[2] = 0;
						for (i = 0; i < 16; i++){
							wcsncpy(num_buf, line_off + (i * 2), 2);
							hash[i] = (unsigned char)get_val32h(num_buf);
						}
						rv = file_md5_check(num, ascii_buf, uni_buf, file_path, hash);
						if (rv & 3){
							err = rv;
							break;
						}
						if (rv & 0xC){	// Missing or Damaged
							err |= rv;
							err += 0x100;
						}
						num++;
					}
				}

			// ファイル名の後にMD5
			} else if ((line_off[line_len - 33] == ' ') && (base16_len(line_off + (line_len - 32)) == 32)){
				// MD5 の前が括弧 (BSD/OpenSSL 形式)
				if (((wcsncmp(line_off, L"MD5(", 4) == 0) || (wcsncmp(line_off, L"MD5 (", 5) == 0))
						&& ((wcsncmp(line_off + (line_len - 35), L")= ", 3) == 0) ||
							(wcsncmp(line_off + (line_len - 36), L") = ", 4) == 0))){
					// ファイル名
					name_len = line_len - 35 - 4;
					if (base_len + name_len < MAX_LEN){
						if (line_off[line_len - 35] != ')')
							name_len--;
						if (line_off[3] == ' '){
							name_len--;
							wcsncpy(uni_buf, line_off + 5, name_len);
						} else {
							wcsncpy(uni_buf, line_off + 4, name_len);
						}
						uni_buf[name_len] = 0;
						rv = sanitize_filename(uni_buf);	// ファイル名を浄化する
						if (rv != 16){
							utf16_to_cp(uni_buf, ascii_buf, MAX_LEN * 3, cp_output);
							// MD5
							num_buf[2] = 0;
							for (i = 0; i < 16; i++){
								wcsncpy(num_buf, line_off + (line_len - 32 + (i * 2)), 2);
								hash[i] = (unsigned char)get_val32h(num_buf);
							}
							rv = file_md5_check(num, ascii_buf, uni_buf, file_path, hash);
							if (rv & 3){
								err = rv;
								break;
							}
							if (rv & 0xC){	// Missing or Damaged
								err |= rv;
								err += 0x100;
							}
							num++;
						}
					}
				} else {	// MD5 の前がスペース (Easy MD5 Creator 形式)
					// ファイル名
					name_len = line_len - 33;
					if (base_len + name_len < MAX_LEN){
						while (line_off[name_len - 1] == ' ')
							name_len--;
						wcsncpy(uni_buf, line_off, name_len);
						uni_buf[name_len] = 0;
						rv = sanitize_filename(uni_buf);	// ファイル名を浄化する
						if (rv != 16){
							utf16_to_cp(uni_buf, ascii_buf, MAX_LEN * 3, cp_output);
							// MD5
							num_buf[2] = 0;
							for (i = 0; i < 16; i++){
								wcsncpy(num_buf, line_off + (line_len - 32 + (i * 2)), 2);
								hash[i] = (unsigned char)get_val32h(num_buf);
							}
							rv = file_md5_check(num, ascii_buf, uni_buf, file_path, hash);
							if (rv & 3){
								err = rv;
								break;
							}
							if (rv & 0xC){	// Missing or Damaged
								err |= rv;
								err += 0x100;
							}
							num++;
						}
					}
				}
			}
		}
		// 次の行へ
		line_off += line_len;
		if (*line_off == '\n')
			line_off++;
		if (*line_off == '\r'){
			line_off++;
			if (*line_off == '\n')	// 「\r\n」を一つの改行として扱う
				line_off++;
		}
	}

	return err;
}

