// create.c
// Copyright : 2022-02-16 Yutaka Sawada
// License : GPL

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

#include <process.h>
#include <stdio.h>

#include <windows.h>

#include "common2.h"
#include "phmd5.h"
#include "md5_crc.h"
#include "version.h"
#include "gf16.h"
#include "create.h"

//#define TIMER // 実験用

// ソート時に項目を比較する
static int sort_cmp(const void *elem1, const void *elem2)
{
	const file_ctx_c *file1, *file2;
	int i;

	file1 = elem1;
	file2 = elem2;

	// サイズが 0 の空ファイルやフォルダは non-recovery set に含める
	if (file1->size == 0){
		if (file2->size > 0)
			return 1;
	} else {
		if (file2->size == 0)
			return -1;
	}

	// File ID の順に並び替える
	for (i = 15; i >= 0; i--){
		if (file1->id[i] > file2->id[i]){
			return 1; // 1 > 2
		} else if (file1->id[i] < file2->id[i]){
			return -1; // 1 < 2
		}
	}
	return 0; // 1 = 2
}

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

// PAR 2.0 のパケット・ヘッダーを作成する
void set_packet_header(
	unsigned char *buf,			// ヘッダーを格納する 64バイトのバッファー
	unsigned char *set_id,		// Recovery Set ID、16バイト
	int type_num,				// そのパケットの型番号
	unsigned int body_size)	// パケットのデータ・サイズ
{
	// Magic sequence
	memcpy(buf, "PAR2\0PKT", 8);
	// Length of the entire packet
	body_size += 64;	// パケット全体のサイズにする
	memcpy(buf + 8, &body_size, 4);
	memset(buf + 12, 0, 4);
	// MD5 Hash of packet は後で計算する
	// Recovery Set ID
	memcpy(buf + 32, set_id, 16);
	// Type
	memcpy(buf + 48, "PAR 2.0\0", 8);
	memset(buf + 56, 0, 8);
	switch (type_num){
	case 1:		// Main packet
		memcpy(buf + 56, "Main", 4);
		break;
	case 2:		// File Description packet
		memcpy(buf + 56, "FileDesc", 8);
		break;
	case 3:		// Input File Slice Checksum packet
		memcpy(buf + 56, "IFSC", 4);
		break;
	case 4:		// Recovery Slice packet
		memcpy(buf + 56, "RecvSlic", 8);
		break;
	case 5:		// Creator packet
		memcpy(buf + 56, "Creator", 7);
		break;
	case 10:	// Unicode Filename packet
		memcpy(buf + 56, "UniFileN", 8);
		break;
	case 11:	// ASCII Comment packet
		memcpy(buf + 56, "CommASCI", 8);
		break;
	case 12:	// Unicode Comment packet
		memcpy(buf + 56, "CommUni", 7);
		break;
	}
}

// ソース・ファイルの情報を集める
int get_source_files(file_ctx_c *files)
{
	unsigned char work_buf[16 + 8 + (MAX_LEN * 3)];
	unsigned char hash0[16] = {0xd4, 0x1d, 0x8c, 0xd9, 0x8f, 0x00, 0xb2, 0x04,
							0xe9, 0x80, 0x09, 0x98, 0xec, 0xf8, 0x42, 0x7e};
	wchar_t file_name[MAX_LEN], file_path[MAX_LEN];
	int i, list_off = 0, num, len, max = 0;
	WIN32_FILE_ATTRIBUTE_DATA AttrData;

	wcscpy(file_path, base_dir);
	for (num = 0; num < file_num; num++){
		files[num].name = list_off;	// ファイル名の位置を記録する
		wcscpy(file_name, list_buf + list_off);
		// ファイル名が駄目だと警告する
		if (check_filename(file_name) != 0){
			if (max == 0){
				max = 1;
				printf("\nWarning about filenames :\n");
			}
			utf16_to_cp(file_name, work_buf, cp_output);
			printf("file%d: \"%s\" is invalid\n", num, work_buf);
		}
		//list_off += (wcslen(file_name) + 1);
		while (list_buf[list_off] != 0)
			list_off++;
		list_off++;

		// 属性を調べる
		wcscpy(file_path + base_len, file_name);
		if (!GetFileAttributesEx(file_path, GetFileExInfoStandard, &AttrData)){
			print_win32_err();
			printf_cp("GetFileAttributesEx, %s\n", file_name);
			return 1;
		}
		if (AttrData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY){	// フォルダなら
			memcpy(files[num].hash, hash0, 16);
			files[num].size = 0;
		} else {	// ファイルなら
			files[num].size = ((__int64)AttrData.nFileSizeHigh << 32) | (unsigned __int64)AttrData.nFileSizeLow;
			if (files[num].size == 0){	// 空のファイルなら
				memcpy(files[num].hash, hash0, 16);
			} else {
				// ファイルの先頭 16KB 分のハッシュ値を計算する
				if (file_md5_16(file_path, files[num].hash)){
					printf_cp("file_md5_16, %s\n", file_name);
					return 1;
				}
			}
		}

		// File ID を計算する
		memcpy(work_buf, files[num].hash, 16);
		memcpy(work_buf + 16, &(files[num].size), 8);
		unix_directory(file_name);	// 記録時のディレクトリ記号は「/」にする
		utf16_to_utf8(file_name, work_buf + 24);
		len = (int)strlen(work_buf + 24);	// File ID 計算時のファイル名のサイズは 4の倍数にしない
		data_md5(work_buf, 24 + len, files[num].id);
		// 他のファイルと同じ File ID になってないか調べる
		do {
			for (i = 0; i < num; i++){
				if (memcmp(files[num].id, files[i].id, 16) == 0){	// File ID が同じなら
					files[num].id[0] += 1;	// 最下位バイトを変化させる
					break;
				}
			}
		} while (i < num);	// File ID を必ずユニークな値にする
	}

	// ファイルを File ID の順に並び替える
	qsort(files, file_num, sizeof(file_ctx_c), sort_cmp);

	return 0;
}

// 共通パケットを作成する
int set_common_packet(
	unsigned char *buf,
	int *packet_num,			// 共通パケットの数
	int switch_u,				// ユニコードのファイル名も記録する
	file_ctx_c *files)
{
	char ascii_buf[MAX_LEN * 3];
	unsigned char set_id[16], file_hash[16], *main_packet_buf;
	unsigned char hash0[16] = {0xd4, 0x1d, 0x8c, 0xd9, 0x8f, 0x00, 0xb2, 0x04,
							0xe9, 0x80, 0x09, 0x98, 0xec, 0xf8, 0x42, 0x7e};
	wchar_t file_name[MAX_LEN];
	int i, err, off, off2, num, len, data_size, main_packet_size;
	unsigned int time_last;
	__int64 prog_now = 0;

#ifdef TIMER
unsigned int time_start = GetTickCount();
#endif
	print_progress_text(0, "Computing file hash");

	// 最初に Main packet を作成しておく
	main_packet_size = 12 + (file_num * 16);
	main_packet_buf = (unsigned char *)malloc(main_packet_size);
	if (main_packet_buf == NULL){
		printf("malloc, %d\n", main_packet_size);
		return 1;
	}
	memcpy(main_packet_buf, &block_size, 4);
	memset(main_packet_buf + 4, 0, 4);
	memcpy(main_packet_buf + 8, &entity_num, 4);
	for (num = 0; num < file_num; num++)	// 並び替えられた順序で File ID をコピーする
		memcpy(main_packet_buf + (12 + (16 * num)), files[num].id, 16);
	data_md5(main_packet_buf, main_packet_size, set_id);	// Recovery Set ID を計算する
	*packet_num = 1;

	// ファイルごとのパケットを作成する
	time_last = GetTickCount();
	off = 0;
	for (num = 0; num < file_num; num++){
		// File Description packet
		off2 = off;	// 位置を記録しておく
		memcpy(buf + (off + 64), files[num].id, 16);
		memcpy(buf + (off + 64 + 32), files[num].hash, 16);	// 全体のハッシュ値は後で書き込む
		memcpy(buf + (off + 64 + 48), &(files[num].size), 8);
		// ファイル名を UTF-8 に変換する
		wcscpy(file_name, list_buf + files[num].name);
		unix_directory(file_name);	// 記録時のディレクトリ記号は「/」にする
		utf16_to_utf8(file_name, ascii_buf);
		len = (int)strlen(ascii_buf);
		for (i = len + 1; (i < len + 4) || (i < MAX_LEN * 3); i++)
			ascii_buf[i] = 0;
		len = (len + 3) & 0xFFFFFFFC;
		memcpy(buf + (off + 64 + 56), ascii_buf, len);
		data_size = 56 + len;
		set_packet_header(buf + off, set_id, 2, data_size);	// パケット・ヘッダーを作成する
		//data_md5(buf + (off + 32), 32 + data_size, buf + (off + 16));	// パケットの MD5 を計算する
		off += (64 + data_size);
		(*packet_num)++;

		if (files[num].size > 0){	// Input File Slice Checksum packet
			// QuickPar はなぜかサイズが 0 のファイルに対してもチェックサムのパケットが必要・・・
			// 無意味なパケットを避けるために non-recovery set を使った方がいい?
			memcpy(buf + (off + 64), files[num].id, 16);
			// ファイルの MD5 ハッシュ値とブロックのチェックサムを同時に計算する
			err = file_hash_crc(list_buf + files[num].name, files[num].size, file_hash, buf + (off + 64 + 16), &time_last, &prog_now);
			if (err){
				if (err == 1)
					printf_cp("file_hash_crc, %s\n", file_name);
				off = err;
				goto error_end;
			}
			len = (int)((files[num].size + (__int64)block_size - 1) / block_size);
			// ファイルの MD5-16k はもう不要なので、ブロックの CRC-32 に変更する
			memset(files[num].hash, 0, 16);
			for (i = 0; i < len; i++){	// XOR して 16バイトに減らす
				memcpy(&err, buf + (off + 64 + 16 + 20 * i + 16), 4);
				((unsigned int *)files[num].hash)[i & 3] ^= err;
			}
//			printf("%d: 0x%08X %08X %08X %08X\n", num,
//				((unsigned int *)files[num].hash)[0], ((unsigned int *)files[num].hash)[1],
//				((unsigned int *)files[num].hash)[2], ((unsigned int *)files[num].hash)[3]);
			i = 16 + 20 * len;	// パケット内容のサイズ
			set_packet_header(buf + off, set_id, 3, i);	// パケット・ヘッダーを作成する
			data_md5(buf + (off + 32), 32 + i, buf + (off + 16));	// パケットの MD5 を計算する
			off += (64 + i);
			(*packet_num)++;
		} else {
			memcpy(file_hash, hash0, 16);
		}

		// File Description packet の続き
		memcpy(buf + (off2 + 64 + 16), file_hash, 16);	// 計算したハッシュ値をここで書き込む
		data_md5(buf + (off2 + 32), 32 + data_size, buf + (off2 + 16));	// パケットの MD5 を計算する

		if (switch_u != 0){	// ユニコードのファイル名
			// 指定があってもファイル名が ASCII 文字だけならユニコードのパケットは作らない
			len = (int)wcslen(file_name);
			for (i = 0; i < len; i++){
				if (file_name[i] != ascii_buf[i])
					break;
			}
			if (i < len){
				// Unicode Filename packet
				memcpy(buf + (off + 64), files[num].id, 16);
				len = (int)wcslen(file_name) * 2;
				memcpy(buf + (off + 64 + 16), file_name, len);
				if (len & 3)
					memset(buf + (off + 64 + 16 + len), 0, 2);
				data_size = 16 + ((len + 3) & 0xFFFFFFFC);
				set_packet_header(buf + off, set_id, 10, data_size);	// パケット・ヘッダーを作成する
				data_md5(buf + (off + 32), 32 + data_size, buf + (off + 16));	// パケットの MD5 を計算する
				off += (64 + data_size);
				(*packet_num)++;
			}
		}
	}
	print_progress_done();	// 改行して行の先頭に戻しておく

	// Main packet
	set_packet_header(buf + off, set_id, 1, main_packet_size);	// パケット・ヘッダーを作成する
	memcpy(buf + (off + 64), main_packet_buf, main_packet_size);
	data_md5(buf + (off + 32), 32 + main_packet_size, buf + (off + 16));	// パケットの MD5 を計算する
	off += (64 + main_packet_size);

#ifdef TIMER
time_start = GetTickCount() - time_start;
printf("hash %d.%03d sec", time_start / 1000, time_start % 1000);
if (time_start > 0){
	time_start = (int)((total_file_size * 125) / ((__int64)time_start * 131072));
	printf(", %d MB/s\n", time_start);
} else {
	printf("\n");
}
#endif

error_end:
	free(main_packet_buf);
	return off;
}

#define MAX_MULTI_READ	4	// SSDで同時に読み込む最大ファイル数

// SSD 上で複数ファイルのハッシュ値を同時に求めるバージョン
int set_common_packet_multi(
	unsigned char *buf,
	int *packet_num,			// 共通パケットの数
	int switch_u,				// ユニコードのファイル名も記録する
	file_ctx_c *files)
{
	char ascii_buf[MAX_LEN * 3];
	unsigned char set_id[16], file_hash[16], *main_packet_buf;
	unsigned char hash0[16] = {0xd4, 0x1d, 0x8c, 0xd9, 0x8f, 0x00, 0xb2, 0x04,
							0xe9, 0x80, 0x09, 0x98, 0xec, 0xf8, 0x42, 0x7e};
	wchar_t file_name[MAX_LEN];
	int i, err = 0, off, off2, num, len, data_size, main_packet_size, multi_read;
	unsigned int rv, time_last;
	__int64 prog_now = 0, prog_loop;
	HANDLE hSub[MAX_MULTI_READ];
	FILE_HASH_TH th[MAX_MULTI_READ];

#ifdef TIMER
unsigned int time_start = GetTickCount();
#endif

	memset(hSub, 0, sizeof(HANDLE) * MAX_MULTI_READ);
	memset(th, 0, sizeof(FILE_HASH_TH) * MAX_MULTI_READ);
	// Core数に応じてスレッド数を増やす
	if ((memory_use & 32) != 0){	// NVMe SSD
		if (cpu_num >= 8){	// 8 ~ 16 Cores
			multi_read = 4;
		} else {	// 3 Cores + Hyper-threading, or 4 ~ 7 Cores
			multi_read = 3;
		}
	} else {	// SATA SSD
		multi_read = 2;
	}
	if (multi_read > entity_num)
		multi_read = entity_num;
#ifdef TIMER
	printf("cpu_num = %d, entity_num = %d, multi_read = %d\n", cpu_num, entity_num, multi_read);
#endif

	print_progress_text(0, "Computing file hash");

	// 最初に Main packet を作成しておく
	main_packet_size = 12 + (file_num * 16);
	main_packet_buf = (unsigned char *)malloc(main_packet_size);
	if (main_packet_buf == NULL){
		printf("malloc, %d\n", main_packet_size);
		return 1;
	}
	memcpy(main_packet_buf, &block_size, 4);
	memset(main_packet_buf + 4, 0, 4);
	memcpy(main_packet_buf + 8, &entity_num, 4);
	for (num = 0; num < file_num; num++)	// 並び替えられた順序で File ID をコピーする
		memcpy(main_packet_buf + (12 + (16 * num)), files[num].id, 16);
	data_md5(main_packet_buf, main_packet_size, set_id);	// Recovery Set ID を計算する
	*packet_num = 1;

	// ファイルごとのパケットを作成する
	time_last = GetTickCount();
	off = 0;
	for (num = 0; num < file_num; num++){
		// File Description packet
		off2 = off;	// 位置を記録しておく
		memcpy(buf + (off + 64), files[num].id, 16);
		memcpy(buf + (off + 64 + 32), files[num].hash, 16);	// 全体のハッシュ値は後で書き込む
		memcpy(buf + (off + 64 + 48), &(files[num].size), 8);
		// ファイル名を UTF-8 に変換する
		wcscpy(file_name, list_buf + files[num].name);
		unix_directory(file_name);	// 記録時のディレクトリ記号は「/」にする
		utf16_to_utf8(file_name, ascii_buf);
		len = (int)strlen(ascii_buf);
		for (i = len + 1; (i < len + 4) || (i < MAX_LEN * 3); i++)
			ascii_buf[i] = 0;
		len = (len + 3) & 0xFFFFFFFC;
		memcpy(buf + (off + 64 + 56), ascii_buf, len);
		data_size = 56 + len;	// data_size = off - off2 - 64 なのでサイズを記録しなくても逆算できる
		set_packet_header(buf + off, set_id, 2, data_size);	// パケット・ヘッダーを作成する
		//data_md5(buf + (off + 32), 32 + data_size, buf + (off + 16));	// パケットの MD5 を計算する
		off += (64 + data_size);
		(*packet_num)++;

		if (files[num].size > 0){	// Input File Slice Checksum packet
			// QuickPar はなぜかサイズが 0 のファイルに対してもチェックサムのパケットが必要・・・
			// 無意味なパケットを避けるために non-recovery set を使った方がいい?
			memcpy(buf + (off + 64), files[num].id, 16);
			len = (int)((files[num].size + (__int64)block_size - 1) / block_size);	// ブロック数
			data_size = 16 + 20 * len;	// パケット内容のサイズ
			set_packet_header(buf + off, set_id, 3, data_size);	// パケット・ヘッダーを作成する
			if (multi_read < 2){	// ファイルを一個ずつ処理する
				// ファイルの MD5 ハッシュ値とブロックのチェックサムを同時に計算する
				err = file_hash_crc(file_name, files[num].size, file_hash, buf + (off + 64 + 16), &time_last, &prog_now);
				if (err){
					if (err == 1)
						printf_cp("file_hash_crc, %s\n", file_name);
					goto error_end;
				}
				// ファイルの MD5-16k はもう不要なので、ブロックの CRC-32 に変更する
				memset(files[num].hash, 0, 16);
				for (i = 0; i < len; i++){	// XOR して 16バイトに減らす
					memcpy(&rv, buf + (off + 64 + 16 + 20 * i + 16), 4);
					((unsigned int *)files[num].hash)[i & 3] ^= rv;
				}
//				printf("%d: 0x%08X %08X %08X %08X\n", num,
//					((unsigned int *)files[num].hash)[0], ((unsigned int *)files[num].hash)[1],
//					((unsigned int *)files[num].hash)[2], ((unsigned int *)files[num].hash)[3]);
				data_md5(buf + (off + 32), 32 + data_size, buf + (off + 16));	// パケットの MD5 を計算する

				// File Description packet の続き
				memcpy(buf + (off2 + 64 + 16), file_hash, 16);	// 計算したハッシュ値をここで書き込む
				data_md5(buf + (off2 + 32), 32 + (off - off2 - 64), buf + (off2 + 16));	// パケットの MD5 を計算する

			} else {	// 複数ファイルを同時に処理する
				if (num < multi_read){	// 最初の何個かは順番にスレッドを割り当てる
					i = num;
				} else {	// 全て使用済みなら、どれかが終わるのを待つ
					do {
						rv = WaitForMultipleObjects(multi_read, hSub, FALSE, UPDATE_TIME / 2);
						if (GetTickCount() - time_last >= UPDATE_TIME){
							prog_loop = 0;
							for (i = 0; i < multi_read; i++)
								prog_loop += th[i].loop;	// サブ・スレッドの進捗状況を合計する
							prog_loop *= IO_SIZE;
							prog_loop += prog_now;
							if (print_progress((int)((prog_loop * 1000) / total_file_size))){
								err = 2;
								goto error_end;
							}
							time_last = GetTickCount();
						}
					} while (rv == WAIT_TIMEOUT);
					if (rv == WAIT_FAILED){
						print_win32_err();
						err = 1;
						goto error_end;
					}
					i = rv - WAIT_OBJECT_0;	// 終了したスレッド
					if (GetExitCodeThread(hSub[i], &rv) == 0){	// サブ・スレッドのエラー確認
						print_win32_err();
						err = 1;
						goto error_end;
					} else if (rv != 0){
						err = rv;
						goto error_end;
					}
					CloseHandle(hSub[i]);
					prog_now += th[i].file_size;
				}
				//printf("thread = %d, size = %I64d\n", i, files[num].size);
				th[i].file_name = list_buf + files[num].name;
				th[i].file_size = files[num].size;
				th[i].hash = buf + (off2 + 64 + 16);
				th[i].sum = buf + (off + 64 + 16);
				th[i].crc = (unsigned int *)(files[num].hash);
				th[i].loop = 0;
				//_mm_sfence();	// メモリーへの書き込みを完了してからスレッドを起動する
				hSub[i] = (HANDLE)_beginthreadex(NULL, STACK_SIZE + MAX_LEN * 2, file_hash_crc2, (LPVOID)&(th[i]), 0, NULL);
				if (hSub[i] == NULL){
					print_win32_err();
					printf("error, sub-thread\n");
					err = 1;
					goto error_end;
				}
			}
			off += (64 + data_size);
			(*packet_num)++;

		} else {
			// File Description packet の続き
			memcpy(buf + (off2 + 64 + 16), hash0, 16);	// 予め計算してあるハッシュ値をコピーする
			data_md5(buf + (off2 + 32), 32 + (off - off2 - 64), buf + (off2 + 16));	// パケットの MD5 を計算する
		}

		if (switch_u != 0){	// ユニコードのファイル名
			// 指定があってもファイル名が ASCII 文字だけならユニコードのパケットは作らない
			len = (int)wcslen(file_name);
			for (i = 0; i < len; i++){
				if (file_name[i] != ascii_buf[i])
					break;
			}
			if (i < len){
				// Unicode Filename packet
				memcpy(buf + (off + 64), files[num].id, 16);
				len = (int)wcslen(file_name) * 2;
				memcpy(buf + (off + 64 + 16), file_name, len);
				if (len & 3)
					memset(buf + (off + 64 + 16 + len), 0, 2);
				data_size = 16 + ((len + 3) & 0xFFFFFFFC);
				set_packet_header(buf + off, set_id, 10, data_size);	// パケット・ヘッダーを作成する
				data_md5(buf + (off + 32), 32 + data_size, buf + (off + 16));	// パケットの MD5 を計算する
				off += (64 + data_size);
				(*packet_num)++;
			}
		}
	}

	// Main packet
	set_packet_header(buf + off, set_id, 1, main_packet_size);	// パケット・ヘッダーを作成する
	memcpy(buf + (off + 64), main_packet_buf, main_packet_size);
	data_md5(buf + (off + 32), 32 + main_packet_size, buf + (off + 16));	// パケットの MD5 を計算する
	off += (64 + main_packet_size);

	if (multi_read >= 2){	// 全てのサブ・スレッドが終了するまで待つ
		do {
			rv = WaitForMultipleObjects(multi_read, hSub, TRUE, UPDATE_TIME / 2);
			if (GetTickCount() - time_last >= UPDATE_TIME){
				prog_loop = 0;
				for (i = 0; i < multi_read; i++)
					prog_loop += th[i].loop;	// サブ・スレッドの進捗状況を合計する
				prog_loop *= IO_SIZE;
				prog_loop += prog_now;
				if (print_progress((int)((prog_loop * 1000) / total_file_size))){
					err = 2;
					goto error_end;
				}
				time_last = GetTickCount();
			}
		} while (rv == WAIT_TIMEOUT);
		if (rv == WAIT_FAILED){
			print_win32_err();
			err = 1;
			goto error_end;
		}
	}
	print_progress_done();	// 改行して行の先頭に戻しておく
#ifdef TIMER
time_start = GetTickCount() - time_start;
printf("hash %d.%03d sec", time_start / 1000, time_start % 1000);
if (time_start > 0){
	time_start = (int)((total_file_size * 125) / ((__int64)time_start * 131072));
	printf(", %d MB/s\n", time_start);
} else {
	printf("\n");
}
#endif

error_end:
	free(main_packet_buf);
	if (multi_read >= 2){
		if (err != 0){	// エラーが発生した場合
			for (i = 0; i < multi_read; i++)	// 終了指示をだす
				InterlockedExchange(&(th[i].loop), -1);
		}
		for (i = 0; i < multi_read; i++){
			if (hSub[i]){
				WaitForSingleObject(hSub[i], INFINITE);
				GetExitCodeThread(hSub[i], &rv);
				if ((err == 0) && (rv != 0))
					err = rv;
				CloseHandle(hSub[i]);
			}
		}
	}
	if (err)
		return err;
	return off;
}

// ハッシュ値を後で計算する
int set_common_packet_1pass(
	unsigned char *buf,
	int *packet_num,			// 共通パケットの数
	int switch_u,				// ユニコードのファイル名も記録する
	file_ctx_c *files)
{
	char ascii_buf[MAX_LEN * 3];
	unsigned char set_id[16], *main_packet_buf;
	unsigned char hash0[16] = {0xd4, 0x1d, 0x8c, 0xd9, 0x8f, 0x00, 0xb2, 0x04,
							0xe9, 0x80, 0x09, 0x98, 0xec, 0xf8, 0x42, 0x7e};
	wchar_t file_name[MAX_LEN], file_path[MAX_LEN];
	int i, off, num, len, data_size, main_packet_size;
	unsigned int time_last;
	__int64 prog_now = 0;

	wcscpy(file_path, base_dir);

	// 最初に Main packet を作成しておく
	main_packet_size = 12 + (file_num * 16);
	main_packet_buf = (unsigned char *)malloc(main_packet_size);
	if (main_packet_buf == NULL){
		printf("malloc, %d\n", main_packet_size);
		return 1;
	}
	memcpy(main_packet_buf, &block_size, 4);
	memset(main_packet_buf + 4, 0, 4);
	memcpy(main_packet_buf + 8, &entity_num, 4);
	for (num = 0; num < file_num; num++)	// 並び替えられた順序で File ID をコピーする
		memcpy(main_packet_buf + (12 + (16 * num)), files[num].id, 16);
	data_md5(main_packet_buf, main_packet_size, set_id);	// Recovery Set ID を計算する
	*packet_num = 1;

	// ファイルごとのパケットを作成する
	time_last = GetTickCount();
	off = 0;
	for (num = 0; num < file_num; num++){
		// File Description packet
		memcpy(buf + (off + 64), files[num].id, 16);
		memcpy(buf + (off + 64 + 32), files[num].hash, 16);
		memcpy(buf + (off + 64 + 48), &(files[num].size), 8);
		// ファイル名を UTF-8 に変換する
		wcscpy(file_name, list_buf + files[num].name);
		wcscpy(file_path + base_len, file_name);
		unix_directory(file_name);	// 記録時のディレクトリ記号は「/」にする
		utf16_to_utf8(file_name, ascii_buf);
		len = (int)strlen(ascii_buf);
		for (i = len + 1; (i < len + 4) || (i < MAX_LEN * 3); i++)
			ascii_buf[i] = 0;
		len = (len + 3) & 0xFFFFFFFC;
		memcpy(buf + (off + 64 + 56), ascii_buf, len);
		data_size = 56 + len;
		set_packet_header(buf + off, set_id, 2, data_size);	// パケット・ヘッダーを作成する
		if (files[num].size > 0){
			// ファイルの MD5-16k はもう不要なので、パケットの位置とサイズに変更する
			//printf("File[%d], off = %d, size = %d\n", num, off, data_size);
			memcpy(files[num].hash, &off, 4);	// ハッシュ値の位置 = off + 64 + 16
			memcpy(files[num].hash + 4, &data_size, 4);
		} else {
			memcpy(buf + (off + 64 + 16), hash0, 16);
			data_md5(buf + (off + 32), 32 + data_size, buf + (off + 16));	// パケットの MD5 を計算する
		}
		off += (64 + data_size);
		(*packet_num)++;

		if (files[num].size > 0){	// Input File Slice Checksum packet
			memcpy(buf + (off + 64), files[num].id, 16);
			len = (int)((files[num].size + (__int64)block_size - 1) / block_size);
			data_size = 16 + 20 * len;	// パケット内容のサイズ
			set_packet_header(buf + off, set_id, 3, data_size);	// パケット・ヘッダーを作成する
			// ファイルの MD5-16k はもう不要なので、パケットの位置とサイズに変更する
			//printf("Checksum[%d], off = %d, size = %d\n", num, off, data_size);
			memcpy(files[num].hash + 8, &off, 4);	// チェックサムの位置 = off + 64 + 16
			memcpy(files[num].hash + 12, &data_size, 4);
			//data_md5(buf + (off + 32), 32 + data_size, buf + (off + 16));	// パケットの MD5 を計算する
			off += (64 + data_size);
			(*packet_num)++;
		}

		if (switch_u != 0){	// ユニコードのファイル名
			// 指定があってもファイル名が ASCII 文字だけならユニコードのパケットは作らない
			len = (int)wcslen(file_name);
			for (i = 0; i < len; i++){
				if (file_name[i] != ascii_buf[i])
					break;
			}
			if (i < len){
				// Unicode Filename packet
				memcpy(buf + (off + 64), files[num].id, 16);
				len = (int)wcslen(file_name) * 2;
				memcpy(buf + (off + 64 + 16), file_name, len);
				if (len & 3)
					memset(buf + (off + 64 + 16 + len), 0, 2);
				data_size = 16 + ((len + 3) & 0xFFFFFFFC);
				set_packet_header(buf + off, set_id, 10, data_size);	// パケット・ヘッダーを作成する
				data_md5(buf + (off + 32), 32 + data_size, buf + (off + 16));	// パケットの MD5 を計算する
				off += (64 + data_size);
				(*packet_num)++;
			}
		}
	}

	// Main packet
	set_packet_header(buf + off, set_id, 1, main_packet_size);	// パケット・ヘッダーを作成する
	memcpy(buf + (off + 64), main_packet_buf, main_packet_size);
	data_md5(buf + (off + 32), 32 + main_packet_size, buf + (off + 16));	// パケットの MD5 を計算する
	off += (64 + main_packet_size);

	free(main_packet_buf);
	return off;
}

// パケットは作成済みなので、ハッシュ値だけ計算して追加する
int set_common_packet_hash(
	unsigned char *buf,
	file_ctx_c *files)
{
	unsigned char file_hash[16];
	int err, off, off2, num, data_size;
	unsigned int time_last;
	__int64 prog_now = 0;

#ifdef TIMER
unsigned int time_start = GetTickCount();
#endif
	print_progress_text(0, "Computing file hash");

	// ファイルごとのパケットを作成する
	time_last = GetTickCount();
	for (num = 0; num < file_num; num++){
		if (files[num].size > 0){
			// Input File Slice Checksum packet
			memcpy(&off, files[num].hash + 8, 4);	// チェックサムの位置 = off + 64 + 16
			memcpy(&data_size, files[num].hash + 12, 4);
			//printf("off = %d, data_size = %d\n", off, data_size);
			// ファイルの MD5 ハッシュ値とブロックのチェックサムを同時に計算する
			err = file_hash_crc(list_buf + files[num].name, files[num].size, file_hash, buf + (off + 64 + 16), &time_last, &prog_now);
			if (err){
				if (err == 1)
					printf_cp("file_hash_crc, %s\n", list_buf + files[num].name);
				return err;
			}
			data_md5(buf + (off + 32), 32 + data_size, buf + (off + 16));	// パケットの MD5 を計算する

			// File Description packet
			memcpy(&off2, files[num].hash, 4);	// ハッシュ値の位置 = off + 64 + 16
			memcpy(&data_size, files[num].hash + 4, 4);
			//printf("off2 = %d, data_size = %d\n", off2, data_size);
			memcpy(buf + (off2 + 64 + 16), file_hash, 16);	// 計算したハッシュ値をここで書き込む
			data_md5(buf + (off2 + 32), 32 + data_size, buf + (off2 + 16));	// パケットの MD5 を計算する

			data_size = (int)((files[num].size + (__int64)block_size - 1) / block_size);
			// ファイルの MD5-16k はもう不要なので、ブロックの CRC-32 に変更する
			memset(files[num].hash, 0, 16);
			for (off2 = 0; off2 < data_size; off2++){	// XOR して 16バイトに減らす
				memcpy(&err, buf + (off + 64 + 16 + 20 * off2 + 16), 4);
				((unsigned int *)files[num].hash)[off2 & 3] ^= err;
			}
		}
	}
	print_progress_done();	// 改行して行の先頭に戻しておく

#ifdef TIMER
time_start = GetTickCount() - time_start;
printf("hash %d.%03d sec\n", time_start / 1000, time_start % 1000);
#endif
	return 0;
}

// ソース・ファイルの情報から共通パケットのサイズを計算する
int measure_common_packet(
	int *packet_num,			// 共通パケットの数
	int switch_u)				// ユニコードのファイル名も記録する
{
	char ascii_buf[MAX_LEN * 3];
	wchar_t *file_name, file_path[MAX_LEN];
	int i, list_off = 0, off = 0, len, data_size;
	__int64 file_size;
	WIN32_FILE_ATTRIBUTE_DATA AttrData;

	wcscpy(file_path, base_dir);

	// 最初に Main packet を作成しておく
	data_size = 12 + (file_num * 16);
	off += (64 + data_size);
	*packet_num = 1;

	// Item index for debug output
	if (split_size >= 4){
		printf("\n         Size  Slice Split :  Filename\n");
	} else {
		printf("\n         Size  Slice :  Filename\n");
	}

	// ファイルごとのパケットを作成する
	while (list_off < list_len){
		file_name = list_buf + list_off;
		//list_off += (wcslen(file_name) + 1);
		while (list_buf[list_off] != 0)
			list_off++;
		list_off++;

		// 属性を調べる
		wcscpy(file_path + base_len, file_name);
		if (!GetFileAttributesEx(file_path, GetFileExInfoStandard, &AttrData)){
			print_win32_err();
			printf_cp("GetFileAttributesEx, %s\n", file_name);
			return 1;
		}
		if (AttrData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY){	// フォルダなら
			file_size = 0;
		} else {	// ファイルなら
			file_size = ((__int64)AttrData.nFileSizeHigh << 32) | (unsigned __int64)AttrData.nFileSizeLow;
		}
		// Line for debug output
		i = 0;
		if (file_size > 0)
			i = (int)((file_size + block_size - 1) / block_size);	// ブロック数
		printf("%13I64d %6d", file_size, i);
		if (split_size >= 4){
			i = (int)((file_size + split_size - 1) / split_size);	// 分割数
			printf(" %5d", i);
		}
		printf_cp(" : \"%s\"\n", file_name);

		// File Description packet
		// ファイル名を UTF-8 に変換する
		utf16_to_utf8(file_name, ascii_buf);
		len = (int)strlen(ascii_buf);
		len = (len + 3) & 0xFFFFFFFC;
		data_size = 56 + len;
		off += (64 + data_size);
		(*packet_num)++;

		if (file_size > 0){	// Input File Slice Checksum packet
			data_size = 16;
			data_size += (int)(((file_size + block_size - 1) / block_size) * 20);
			off += (64 + data_size);
			(*packet_num)++;
		}

		if (switch_u != 0){	// ユニコードのファイル名
			// 指定があってもファイル名が ASCII 文字だけならユニコードのパケットは作らない
			len = (int)wcslen(file_name);
			for (i = 0; i < len; i++){
				if (file_name[i] != ascii_buf[i]){
					break;
				}
			}
			if (i < len){
				// Unicode Filename packet
				len = (int)wcslen(file_name) * 2;
				data_size = 16 + ((len + 3) & 0xFFFFFFFC);
				off += (64 + data_size);
				(*packet_num)++;
			}
		}
	}

	return off;
}

// 末尾パケットを作成する
int set_footer_packet(
	unsigned char *buf,
	wchar_t *par_comment,		// コメント
	unsigned char *set_id)		// Recovery Set ID
{
	unsigned char ascii_buf[COMMENT_LEN * 3];
	int off = 0, len, data_size;

	if (par_comment[0] != 0){	// ASCII と Unicode のどちらか一方だけを記録する
		data_size = 0;
		// 7-bit ASCII 文字以外が存在するかどうかを調べる
		len = 0;
		while (par_comment[len] != 0){
			if ((par_comment[len] & 0xFF80) != 0){
				data_size = 3;
				break;
			}
			len++;
		}
		if (data_size == 0){
			// ASCII への変換に失敗した場合はユニコード・パケットを記録する
			if (!WideCharToMultiByte(CP_ACP, 0, par_comment, -1, ascii_buf, COMMENT_LEN * 3, NULL, &len) || len){
				data_size = 1;
			} else if (wcslen(par_comment) != strlen(ascii_buf)){	// 文字数とバイト数が一致しないなら
				data_size = 2;
			}
		}
		if (data_size == 0){
			// ASCII Comment packet
			len = (int)strlen(ascii_buf);
			memcpy(buf + (off + 64), ascii_buf, len);
			if (len & 3)
				memset(buf + (off + 64 + len), 0, 3);
			data_size = (len + 3) & 0xFFFFFFFC;
			set_packet_header(buf + off, set_id, 11, data_size);	// パケット・ヘッダーを作成する
			data_md5(buf + (off + 32), 32 + data_size, buf + (off + 16));	// パケットの MD5 を計算する
			off += (64 + data_size);
		} else {
			// Unicode Comment packet
			memset(buf + (off + 64), 0, 16);
			len = (int)wcslen(par_comment) * 2;
			memcpy(buf + (off + 64 + 16), par_comment, len);
			if (len & 3)
				memset(buf + (off + 64 + 16 + len), 0, 2);
			data_size = 16 + ((len + 3) & 0xFFFFFFFC);
			set_packet_header(buf + off, set_id, 12, data_size);	// パケット・ヘッダーを作成する
			data_md5(buf + (off + 32), 32 + data_size, buf + (off + 16));	// パケットの MD5 を計算する
			off += (64 + data_size);
		}
	}

	// Creator packet
	strcpy(ascii_buf, "par2j v" PRODUCT_VERSION);
	len = (int)strlen(ascii_buf);
	memcpy(buf + (off + 64), ascii_buf, len);
	if (len & 3)
		memset(buf + (off + 64 + len), 0, 3);
	data_size = (len + 3) & 0xFFFFFFFC;
	//strcpy(buf + (off + 64), "par2j v1.1.6\0");
	//data_size = 12;
	set_packet_header(buf + off, set_id, 5, data_size);	// パケット・ヘッダーを作成する
	data_md5(buf + (off + 32), 32 + data_size, buf + (off + 16));	// パケットの MD5 を計算する
	off += (64 + data_size);

	return off;
}

// 末尾パケットのサイズを計算する
int measure_footer_packet(
	wchar_t *par_comment)		// コメント
{
	unsigned char ascii_buf[COMMENT_LEN * 3];
	int off = 0, len, data_size;

	if (par_comment[0] != 0){	// ASCII と Unicode のどちらか一方だけを記録する
		data_size = 0;
		// ASCII への変換に失敗した場合はユニコード・パケットを記録する
		if (!WideCharToMultiByte(CP_ACP, 0, par_comment, -1, ascii_buf, COMMENT_LEN * 3, NULL, &len) || len){
			data_size = 1;
		} else if (wcslen(par_comment) != strlen(ascii_buf)){	// 文字数とバイト数が一致しないなら
			data_size = 2;
		}

		if (data_size == 0){
			// ASCII Comment packet
			len = (int)strlen(ascii_buf);
			data_size = (len + 3) & 0xFFFFFFFC;
			off += (64 + data_size);
		} else {
			// Unicode Comment packet
			len = (int)wcslen(par_comment) * 2;
			data_size = 16 + ((len + 3) & 0xFFFFFFFC);
			off += (64 + data_size);
		}
	}

	// Creator packet
	//len = strlen("par2j v*.*.*\0");
	//data_size = (len + 3) & 0xFFFFFFFC;
	off += (64 + 12);

	return off;
}

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

// ボリューム番号の桁数を求める
static int calc_max_num(
	int block_distri,	// パリティ・ブロックの分配方法 (3-bit目は番号の付け方)
	int *max_num2)		// 最大スライス個数の桁数
{
	int i, j, num;
	int exp_num, block_start, block_count, block_start_max, block_count_max = 0;

	if (block_distri >> 2){	// ファイル番号にする、vol_1, vol_2, vol_3, ...
		i = recovery_num;
		for (j = 1; j < 10; j++){
			i /= 10;
			if (i == 0)
				break;
		}
		return j;	// ファイル数の桁数
	}

	block_start = 0;
	block_count = 0;
	exp_num = 1;
	for (num = 0; num < recovery_num; num++){
		// リカバリ・ファイルのファイル名
		switch (block_distri & 3){
		case 0:	// 同じ数なら
			block_count = parity_num / recovery_num;
			j = parity_num % recovery_num;	// 割り切れない場合は
			if ((j > 0) && (num < j))
				block_count++;
			break;
		case 1:	// 倍々で異なる数にする
			if (num == recovery_num - 1){	// 最後のファイルに余った分を入れる
				block_count = parity_num - block_start;
			} else {
				exp_num = recovery_limit & 0xFFFF;
				if (num >= 16){
					block_count = exp_num;
				} else {
					block_count = ((unsigned int)recovery_limit >> 16) << num;
					if (block_count > exp_num)
						block_count = exp_num;
				}
			}
			break;
		case 2:	// 1,2,4,8,16 と2の乗数なら
			block_count = exp_num;
			if (block_count >= recovery_limit){
				block_count = recovery_limit;
			} else {
				exp_num *= 2;
			}
			break;
		case 3:	// 1,1,2,5,10,10,20,50 という decimal weights sizing scheme なら
			block_count = exp_num;
			if (block_count >= recovery_limit){
				block_count = recovery_limit;
			} else {
				switch (num % 4){
				case 1:
				case 3:
					exp_num = exp_num * 2;
					break;
				case 2:
					exp_num = (exp_num / 2) * 5;
					break;
				}
			}
			break;
		}
		if (block_start + block_count > parity_num)
			block_count = parity_num - block_start;
		if (block_count_max < block_count)
			block_count_max = block_count;
		block_start += block_count;
	}
	block_start_max = first_num + block_start - block_count;	// 最後に足した分を引く
	for (j = 1; j < 10; j++){
		block_start_max /= 10;
		if (block_start_max == 0)
			break;
	}
	block_start_max = j;
	for (j = 1; j < 10; j++){
		block_count_max /= 10;
		if (block_count_max == 0)
			break;
	}
	*max_num2 = j;

	return block_start_max;
}

// ランダムに書き込むには管理者権限が必要
//#define RANDOM_ACCESS

// リカバリ・ファイルを作成して共通パケットをコピーする
int create_recovery_file(
	wchar_t *recovery_path,		// 作業用
	int packet_limit,			// リカバリ・ファイルのパケット繰り返しの制限
	int block_distri,			// パリティ・ブロックの分配方法 (3-bit目は番号の付け方)
	int packet_num,				// 共通パケットの数
	unsigned char *common_buf,	// 共通パケットのバッファー
	int common_size,			// 共通パケットのバッファー・サイズ
	unsigned char *footer_buf,	// 末尾パケットのバッファー
	int footer_size,			// 末尾パケットのバッファー・サイズ
	HANDLE *rcv_hFile,			// 各リカバリ・ファイルのハンドル
	parity_ctx_c *p_blk)		// 各パリティ・ブロックの情報
{
	wchar_t recovery_base[MAX_LEN], file_ext[EXT_LEN], *tmp_p;
	int i, j, num;
	int exp_num, block_start, block_count, block_start_max, block_count_max;
	int repeat_max, packet_to, packet_from, packet_size, common_off;
	unsigned int time_last, prog_num = 0;
	__int64 file_off;
#ifdef RANDOM_ACCESS
	HANDLE hToken;
#endif

#ifdef TIMER
unsigned int time_start = GetTickCount();
#endif
	print_progress_text(0, "Constructing recovery file");
	time_last = GetTickCount();

#ifdef RANDOM_ACCESS
	// SE_MANAGE_VOLUME_NAME 権限を有効にする
	if (OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken) != 0){
		LUID luid;
		if (LookupPrivilegeValue(NULL, SE_MANAGE_VOLUME_NAME, &luid ) != 0){
			TOKEN_PRIVILEGES tp;
			tp.PrivilegeCount = 1;
			tp.Privileges[0].Luid = luid;
			tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
			AdjustTokenPrivileges(hToken, FALSE, &tp, sizeof(TOKEN_PRIVILEGES), (PTOKEN_PRIVILEGES)NULL, (PDWORD)NULL);
			if (GetLastError() != ERROR_SUCCESS){
				print_win32_err();
			} else {
				printf("\nSE_MANAGE_VOLUME_NAME: ok\n");
			}
		}
		CloseHandle(hToken);
	}
#endif

	// リカバリ・ファイルの拡張子には指定されたものを使う
	file_ext[0] = 0;
	wcscpy(recovery_base, recovery_file);
	tmp_p = offset_file_name(recovery_base);
	tmp_p = wcsrchr(tmp_p, '.');
	if (tmp_p != NULL){
		if (wcslen(tmp_p) < EXT_LEN){
			wcscpy(file_ext, tmp_p);	// 拡張子を記録しておく
			*tmp_p = 0;	// 拡張子を取り除く
		}
	}

	// ボリューム番号の桁数を求める
	block_start_max = calc_max_num(block_distri, &block_count_max);

	// リカバリ・ファイルを作成して共通パケットを書き込む
	block_start = 0;
	block_count = 0;
	exp_num = 1;
	for (num = 0; num < recovery_num; num++){
		// リカバリ・ファイルのファイル名
		switch (block_distri & 3){
		case 0:	// 同じ数なら
			block_count = parity_num / recovery_num;
			j = parity_num % recovery_num;	// 割り切れない場合は
			if ((j > 0) && (num < j))
				block_count++;
			break;
		case 1:	// 倍々で異なる数にする
			if (num == recovery_num - 1){	// 最後のファイルに余った分を入れる
				block_count = parity_num - block_start;
			} else {
				exp_num = recovery_limit & 0xFFFF;
				if (num >= 16){
					block_count = exp_num;
				} else {
					block_count = ((unsigned int)recovery_limit >> 16) << num;
					if (block_count > exp_num)
						block_count = exp_num;
				}
			}
			break;
		case 2:	// 1,2,4,8,16 と2の乗数なら
			block_count = exp_num;
			if (block_count >= recovery_limit){
				block_count = recovery_limit;
			} else {
				exp_num *= 2;
			}
			break;
		case 3:	// 1,1,2,5,10,10,20,50 という decimal weights sizing scheme なら
			block_count = exp_num;
			if (block_count >= recovery_limit){
				block_count = recovery_limit;
			} else {
				switch (num % 4){
				case 1:
				case 3:
					exp_num = exp_num * 2;
					break;
				case 2:
					exp_num = (exp_num / 2) * 5;
					break;
				}
			}
			break;
		}
		if (block_start + block_count > parity_num)
			block_count = parity_num - block_start;
		if (block_distri >> 2){
			// ファイル番号にする、vol_1, vol_2, vol_3, ...
			swprintf(recovery_path, MAX_LEN, L"%s.vol_%0*d%s", recovery_base, block_start_max, num + 1, file_ext);
		} else {
			// QuickPar方式、volXX+YY
			swprintf(recovery_path, MAX_LEN, L"%s.vol%0*d+%0*d%s", recovery_base, block_start_max,
				first_num + block_start, block_count_max, block_count, file_ext);
		}
		// リカバリ・ファイルを開く
		//move_away_file(recovery_path);	// 既存のファイルをどかす
		if (split_size == 1){	// 書庫ファイルに連結する場合は、後で読めるようにする
			rcv_hFile[num] = CreateFile(recovery_path, GENERIC_READ | GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
		} else {	// 書庫ファイルに連結しないので、読み込む必要が無い
			rcv_hFile[num] = CreateFile(recovery_path, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
		}
		if (rcv_hFile[num] == INVALID_HANDLE_VALUE){
			print_win32_err();
			rcv_hFile[num] = NULL;
			printf_cp("cannot create file, %s\n", recovery_path);
			return 1;
		}

		// パケットの繰り返し回数を計算する
		repeat_max = 1;
		for (j = 2; j <= block_count; j *= 2)	// 繰り返し回数は log2(block_count)
			repeat_max++;
		if ((packet_limit > 0) && (repeat_max > packet_limit))
			repeat_max = packet_limit;	// 繰り返し回数を制限する
		// リカバリ・ファイルの大きさを計算する
		file_off = (__int64)(68 + block_size) * block_count;
		file_off += (common_size * repeat_max) + footer_size;
		// そのサイズにする
		if (!SetFilePointerEx(rcv_hFile[num], *((PLARGE_INTEGER)&file_off), NULL, FILE_BEGIN)){
			print_win32_err();
			return 1;
		}
		if (!SetEndOfFile(rcv_hFile[num])){
			print_win32_err();
			return 1;
		}
#ifdef RANDOM_ACCESS
		if (!SetFileValidData(rcv_hFile[num], file_off)){
			print_win32_err();
			return 1;
		}
#endif

		file_off = 0;
		repeat_max *= packet_num;	// リカバリ・ファイルの共通パケットの数
		packet_from = 0;
		common_off = 0;

		// Recovery Slice packet は後から書き込む
		for (j = block_start; j < block_start + block_count; j++){
			prog_num++;
			// 経過表示
			if (GetTickCount() - time_last >= UPDATE_TIME){
				if (print_progress((prog_num * 1000) / parity_num))
					return 2;
				time_last = GetTickCount();
			}

			// Recovery Slice packet
			file_off += 68;
			p_blk[j].file = num;
			p_blk[j].off = file_off;	// 開始位置だけ記録しておく
			file_off += block_size;

			// どれだけの共通パケットを書き込むか
			packet_size = 0;
			packet_to = (int)((__int64)repeat_max * (j - block_start + 1) / block_count);
			while (packet_to - packet_from > 0){
				memcpy(&i, common_buf + (common_off + (packet_size + 8)), 4);	// そのパケットのデータ・サイズを調べる
				packet_size += i;
				packet_from++;
			}
			if (packet_size > 0){	// 共通パケットを書き込む
				if (file_write_data(rcv_hFile[num], file_off, common_buf + common_off, packet_size)){
					printf_cp("file_write_data, %s\n", recovery_path);
					return 1;
				}
				// オフセットが半分を超えたら戻しておく
				common_off += packet_size;
				if (common_off >= common_size)
					common_off -= common_size;
				file_off += packet_size;
			}
		}
		block_start += block_count;

		// 末尾パケットを書き込む
		if (file_write_data(rcv_hFile[num], file_off, footer_buf, footer_size)){
			printf_cp("file_write_data, %s\n", recovery_path);
			return 1;
		}
	}
	print_progress_done();	// 改行して行の先頭に戻しておく

#ifdef TIMER
time_start = GetTickCount() - time_start;
printf("write %d.%03d sec\n", time_start / 1000, time_start % 1000);
#endif

	return 0;
}

// パリティ・ブロックが完成した後に、encode_method5 から直接呼び出す
// リカバリ・ファイルを一個ずつ書き込んだ方が単純でいい！
// 各リカバリ・ファイルのハンドル、各パリティ・ブロックの情報、は不要になるし
int create_recovery_file_1pass(
	wchar_t *recovery_base,
	wchar_t *recovery_path,		// 作業用
	int packet_limit,			// リカバリ・ファイルのパケット繰り返しの制限
	int block_distri,			// パリティ・ブロックの分配方法 (3-bit目は番号の付け方)
	int packet_num,				// 共通パケットの数
	unsigned char *common_buf,	// 共通パケットのバッファー
	int common_size,			// 共通パケットのバッファー・サイズ
	unsigned char *footer_buf,	// 末尾パケットのバッファー
	int footer_size,			// 末尾パケットのバッファー・サイズ
	HANDLE *rcv_hFile,			// 各リカバリ・ファイルのハンドル
	unsigned char *p_buf,		// 計算済みのパリティ・ブロック
	unsigned int unit_size)
{
	unsigned char *packet_header, hash[HASH_SIZE];
	wchar_t file_ext[EXT_LEN], *tmp_p;
	int rv, j, num;
	int exp_num, block_start, block_count, block_start_max, block_count_max;
	int repeat_max, packet_to, packet_from, packet_size, common_off;
	unsigned int time_last, prog_write;
	__int64 prog_num, prog_base;
	HANDLE hFile;

	// パリティ・ブロック計算に続いて経過表示する
	prog_write = source_num >> 4;	// 計算で 94%、書き込みで 6% ぐらい
	if (prog_write == 0)
		prog_write = 1;
	prog_base = (__int64)(source_num + prog_write) * parity_num;	// ブロックの合計掛け算個数
	prog_num = source_num  * parity_num;	// パリティ・ブロック計算が終了した直後から始める
	time_last = GetTickCount();

	if (recovery_path[MAX_LEN - 1] == 0){	// インデックス・ファイルを作るときだけ
		// パリティ・ブロックを含まないリカバリ・ファイルを書き込む
		//move_away_file(recovery_file);	// 既存のファイルをどかす
		hFile = CreateFile(recovery_file, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
		if (hFile == INVALID_HANDLE_VALUE){
			if (GetLastError() == ERROR_PATH_NOT_FOUND){	// Path not found (3)
				make_dir(recovery_file);	// 途中のフォルダが存在しないのなら作成する
				hFile = CreateFile(recovery_file, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
			}
			if (hFile == INVALID_HANDLE_VALUE){
				print_win32_err();
				printf_cp("cannot create file, %s\n", recovery_file);
				return 1;
			}
		}
		if (!WriteFile(hFile, common_buf, common_size, &rv, NULL)){
			print_win32_err();
			CloseHandle(hFile);
			return 1;
		}
		if (!WriteFile(hFile, footer_buf, footer_size, &rv, NULL)){
			print_win32_err();
			CloseHandle(hFile);
			return 1;
		}
		CloseHandle(hFile);
	}

	// Recovery Slice packet 用のパケット・ヘッダーを作成しておく
	packet_header = p_buf - 160;	// ソース・ブロック領域を利用する
	set_packet_header(packet_header, common_buf + 32, 4, block_size + 4);

	// リカバリ・ファイルの拡張子には指定されたものを使う
	file_ext[0] = 0;
	wcscpy(recovery_base, recovery_file);
	tmp_p = offset_file_name(recovery_base);
	tmp_p = wcsrchr(tmp_p, '.');
	if (tmp_p != NULL){
		if (wcslen(tmp_p) < EXT_LEN){
			wcscpy(file_ext, tmp_p);	// 拡張子を記録しておく
			*tmp_p = 0;	// 拡張子を取り除く
		}
	}

	// ボリューム番号の桁数を求める
	block_start_max = calc_max_num(block_distri, &block_count_max);

	// リカバリ・ファイルを作成して共通パケットを書き込む
	block_start = 0;
	block_count = 0;
	exp_num = 1;
	for (num = 0; num < recovery_num; num++){
		// リカバリ・ファイルのファイル名
		switch (block_distri & 3){
		case 0:	// 同じ数なら
			block_count = parity_num / recovery_num;
			j = parity_num % recovery_num;	// 割り切れない場合は
			if ((j > 0) && (num < j))
				block_count++;
			break;
		case 1:	// 倍々で異なる数にする
			if (num == recovery_num - 1){	// 最後のファイルに余った分を入れる
				block_count = parity_num - block_start;
			} else {
				exp_num = recovery_limit & 0xFFFF;
				if (num >= 16){
					block_count = exp_num;
				} else {
					block_count = ((unsigned int)recovery_limit >> 16) << num;
					if (block_count > exp_num)
						block_count = exp_num;
				}
			}
			break;
		case 2:	// 1,2,4,8,16 と2の乗数なら
			block_count = exp_num;
			if (block_count >= recovery_limit){
				block_count = recovery_limit;
			} else {
				exp_num *= 2;
			}
			break;
		case 3:	// 1,1,2,5,10,10,20,50 という decimal weights sizing scheme なら
			block_count = exp_num;
			if (block_count >= recovery_limit){
				block_count = recovery_limit;
			} else {
				switch (num % 4){
				case 1:
				case 3:
					exp_num = exp_num * 2;
					break;
				case 2:
					exp_num = (exp_num / 2) * 5;
					break;
				}
			}
			break;
		}
		if (block_start + block_count > parity_num)
			block_count = parity_num - block_start;
		if (block_distri >> 2){
			// ファイル番号にする、vol_1, vol_2, vol_3, ...
			swprintf(recovery_path, MAX_LEN, L"%s.vol_%0*d%s", recovery_base, block_start_max, num + 1, file_ext);
		} else {
			// QuickPar方式、volXX+YY
			swprintf(recovery_path, MAX_LEN, L"%s.vol%0*d+%0*d%s", recovery_base, block_start_max,
				first_num + block_start, block_count_max, block_count, file_ext);
		}
		// リカバリ・ファイルを開く
		//move_away_file(recovery_path);	// 既存のファイルをどかす
		if (rcv_hFile != NULL){	// 書庫ファイルに連結する場合は、後で読めるようにする
			hFile = CreateFile(recovery_path, GENERIC_READ | GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
		} else {	// 書庫ファイルに連結しないので、読み込む必要が無い
			hFile = CreateFile(recovery_path, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
		}
		if (hFile == INVALID_HANDLE_VALUE){
			print_win32_err();
			printf_cp("cannot create file, %s\n", recovery_path);
			return 1;
		}
		if (rcv_hFile != NULL)
			rcv_hFile[num] = hFile;	// ファイル・ハンドルを記録する

		// パケットの繰り返し回数を計算する
		repeat_max = 1;
		for (j = 2; j <= block_count; j *= 2)	// 繰り返し回数は log2(block_count)
			repeat_max++;
		if ((packet_limit > 0) && (repeat_max > packet_limit))
			repeat_max = packet_limit;	// 繰り返し回数を制限する

		repeat_max *= packet_num;	// リカバリ・ファイルの共通パケットの数
		packet_from = 0;
		common_off = 0;

		// Recovery Slice packet は後から書き込む
		for (j = block_start; j < block_start + block_count; j++){
			// パリティ・ブロックのチェックサムを検証する
			checksum16_return(p_buf, hash, unit_size - HASH_SIZE);
			if (memcmp(p_buf + unit_size - HASH_SIZE, hash, HASH_SIZE) != 0){
				printf("checksum mismatch, recovery slice %d\n", first_num + j);
				if (rcv_hFile == NULL)
					CloseHandle(hFile);
				return 1;
			}

			// Recovery Slice packet
			memcpy(p_buf - 68, packet_header, 64);	// パケット・ヘッダーをコピーする
			rv = first_num + j;	// 最初のパリティ・ブロック番号の分だけ足す
			memcpy(p_buf - 4, &rv, 4);	// Recovery Slice の番号を書き込む
			data_md5(p_buf - 36, 36 + block_size, p_buf - 52);	// パケットの MD5 を計算する
			if (!WriteFile(hFile, p_buf - 68, 68 + block_size, &rv, NULL)){
				print_win32_err();
				printf("file_write_data, recovery slice %d\n", first_num + j);
				if (rcv_hFile == NULL)
					CloseHandle(hFile);
				return 1;
			}
			p_buf += unit_size;

			// 経過表示
			prog_num += prog_write;
			if (GetTickCount() - time_last >= UPDATE_TIME){
				if (print_progress((int)((prog_num * 1000) / prog_base))){
					if (rcv_hFile == NULL)
						CloseHandle(hFile);
					return 2;
				}
				time_last = GetTickCount();
			}

			// どれだけの共通パケットを書き込むか
			packet_size = 0;
			packet_to = (int)((__int64)repeat_max * (j - block_start + 1) / block_count);
			while (packet_to - packet_from > 0){
				memcpy(&rv, common_buf + (common_off + (packet_size + 8)), 4);	// そのパケットのデータ・サイズを調べる
				packet_size += rv;
				packet_from++;
			}
			if (packet_size > 0){	// 共通パケットを書き込む
				if (!WriteFile(hFile, common_buf + common_off, packet_size, &rv, NULL)){
					print_win32_err();
					if (rcv_hFile == NULL)
						CloseHandle(hFile);
					return 1;
				}
				// オフセットが半分を超えたら戻しておく
				common_off += packet_size;
				if (common_off >= common_size)
					common_off -= common_size;
			}
		}
		block_start += block_count;

		// 末尾パケットを書き込む
		if (!WriteFile(hFile, footer_buf, footer_size, &rv, NULL)){
			print_win32_err();
			if (rcv_hFile == NULL)
				CloseHandle(hFile);
			return 1;
		}
		if (rcv_hFile == NULL){	// 後でファイル・ハンドルが必要なければ
			//FlushFileBuffers(hFile);	// 書き込みが完了するのを待つ？
			// 今まで問題視されなかった訳だし、特に気にしなくていいかも・・・
			CloseHandle(hFile);	// ファイルを閉じる
		}
	}
	print_progress_done();	// 改行して行の先頭に戻しておく
	//printf("prog_num = %I64d / %I64d\n", prog_num, prog_base);

	return 0;
}

// 作成中のリカバリ・ファイルを削除する
void delete_recovery_file(
	wchar_t *recovery_path,		// 作業用
	int block_distri,			// パリティ・ブロックの分配方法 (3-bit目は番号の付け方)
	int switch_p,				// インデックス・ファイルを作らない
	HANDLE *rcv_hFile)			// 各リカバリ・ファイルのハンドル
{
	wchar_t recovery_base[MAX_LEN], file_ext[EXT_LEN], *tmp_p;
	int j, num;
	int exp_num, block_start, block_count, block_start_max, block_count_max;

	if (switch_p == 0)
		DeleteFile(recovery_file);	// インデックス・ファイルを削除する

	// リカバリ・ファイルの拡張子には指定されたものを使う
	file_ext[0] = 0;
	wcscpy(recovery_base, recovery_file);
	tmp_p = offset_file_name(recovery_base);
	tmp_p = wcsrchr(tmp_p, '.');
	if (tmp_p != NULL){
		if (wcslen(tmp_p) < EXT_LEN){
			wcscpy(file_ext, tmp_p);	// 拡張子を記録しておく
			*tmp_p = 0;	// 拡張子を取り除く
		}
	}

	// ボリューム番号の桁数を求める
	block_start_max = calc_max_num(block_distri, &block_count_max);

	// リカバリ・ファイルを削除する
	block_start = 0;
	block_count = 0;
	exp_num = 1;
	for (num = 0; num < recovery_num; num++){
		// リカバリ・ファイルのファイル名
		switch (block_distri & 3){
		case 0:	// 同じ数なら
			block_count = parity_num / recovery_num;
			j = parity_num % recovery_num;	// 割り切れない場合は
			if ((j > 0) && (num < j))
				block_count++;
			break;
		case 1:	// 倍々で異なる数にする
			if (num == recovery_num - 1){	// 最後のファイルに余った分を入れる
				block_count = parity_num - block_start;
			} else {
				exp_num = recovery_limit & 0xFFFF;
				if (num >= 16){
					block_count = exp_num;
				} else {
					block_count = ((unsigned int)recovery_limit >> 16) << num;
					if (block_count > exp_num)
						block_count = exp_num;
				}
			}
			break;
		case 2:	// 1,2,4,8,16 と2の乗数なら
			block_count = exp_num;
			if (block_count >= recovery_limit){
				block_count = recovery_limit;
			} else {
				exp_num *= 2;
			}
			break;
		case 3:	// 1,1,2,5,10,10,20,50 という decimal weights sizing scheme なら
			block_count = exp_num;
			if (block_count >= recovery_limit){
				block_count = recovery_limit;
			} else {
				switch (num % 4){
				case 1:
				case 3:
					exp_num = exp_num * 2;
					break;
				case 2:
					exp_num = (exp_num / 2) * 5;
					break;
				}
			}
			break;
		}
		if (block_start + block_count > parity_num)
			block_count = parity_num - block_start;
		if (block_distri >> 2){
			// ファイル番号にする、vol_1, vol_2, vol_3, ...
			swprintf(recovery_path, MAX_LEN, L"%s.vol_%0*d%s", recovery_base, block_start_max, num + 1, file_ext);
		} else {
			// QuickPar方式、volXX+YY
			swprintf(recovery_path, MAX_LEN, L"%s.vol%0*d+%0*d%s", recovery_base, block_start_max,
				first_num + block_start, block_count_max, block_count, file_ext);
		}
		block_start += block_count;

		if (rcv_hFile != NULL){
			if (rcv_hFile[num] != NULL){	// ファイルが作成済みなら (開いていれば)
				CloseHandle(rcv_hFile[num]);
				rcv_hFile[num] = NULL;
				DeleteFile(recovery_path);	// 途中までのリカバリ・ファイルを削除する
			}
		} else {
			DeleteFile(recovery_path);	// 作成したリカバリ・ファイルを削除する
		}
	}
}

// リカバリ・ファイルのサイズを計算する
void measure_recovery_file(
	wchar_t *recovery_path,		// 作業用 (最初はコメントが入ってる)
	int packet_limit,			// リカバリ・ファイルのパケット繰り返しの制限
	int block_distri,			// パリティ・ブロックの分配方法
	int packet_num,				// 共通パケットの数
	int common_size,			// 共通パケットのバッファー・サイズ
	int footer_size,			// 末尾パケットのバッファー・サイズ
	int switch_p)				// インデックス・ファイルを作らない
{
	char ascii_buf[MAX_LEN * 3];
	wchar_t recovery_base[MAX_LEN], file_ext[EXT_LEN], *tmp_p;
	int j, num;
	int exp_num, block_start, block_count, block_start_max, block_count_max;
	int footer_num, packet_count, repeat_max;
	__int64 file_size;

	get_file_name(recovery_file, recovery_base);	// ファイル名だけにする
	footer_num = 1;
	if (recovery_path[0] != 0)
		footer_num++;

	total_file_size = 0;	// リカバリ・ファイルの合計サイズを計算する
	if (switch_p == 0){
		// Index File を先に表示する
		file_size = common_size + footer_size;
		total_file_size += file_size;
		packet_count = packet_num + footer_num;
		utf16_to_cp(recovery_base, ascii_buf, cp_output);
		printf("%13I64d %7d      0 : \"%s\"\n", file_size, packet_count, ascii_buf);
	}
	if (parity_num == 0)	// パリティ・ブロックを作らない場合はここで終わる
		return;

	// リカバリ・ファイルの拡張子には指定されたものを使う
	file_ext[0] = 0;
	tmp_p = offset_file_name(recovery_base);
	tmp_p = wcsrchr(tmp_p, '.');
	if (tmp_p != NULL){
		if (wcslen(tmp_p) < EXT_LEN){
			wcscpy(file_ext, tmp_p);	// 拡張子を記録しておく
			*tmp_p = 0;	// 拡張子を取り除く
		}
	}

	// ボリューム番号の桁数を求める
	block_start_max = calc_max_num(block_distri, &block_count_max);

	// リカバリ・ファイルを作成して共通パケットを書き込む
	block_start = 0;
	block_count = 0;
	exp_num = 1;
	for (num = 0; num < recovery_num; num++){
		// リカバリ・ファイルのファイル名
		switch (block_distri & 3){
		case 0:	// 同じ数なら
			block_count = parity_num / recovery_num;
			j = parity_num % recovery_num;	// 割り切れない場合は
			if ((j > 0) && (num < j))
				block_count++;
			break;
		case 1:	// 倍々で異なる数にする
			if (num == recovery_num - 1){	// 最後のファイルに余った分を入れる
				block_count = parity_num - block_start;
			} else {
				exp_num = recovery_limit & 0xFFFF;
				if (num >= 16){
					block_count = exp_num;
				} else {
					block_count = ((unsigned int)recovery_limit >> 16) << num;
					if (block_count > exp_num)
						block_count = exp_num;
				}
			}
			break;
		case 2:	// 1,2,4,8,16 と2の乗数なら
			block_count = exp_num;
			if (block_count >= recovery_limit){
				block_count = recovery_limit;
			} else {
				exp_num *= 2;
			}
			break;
		case 3:	// 1,1,2,5,10,10,20,50 という decimal weights sizing scheme なら
			block_count = exp_num;
			if (block_count >= recovery_limit){
				block_count = recovery_limit;
			} else {
				switch (num % 4){
				case 1:
				case 3:
					exp_num = exp_num * 2;
					break;
				case 2:
					exp_num = (exp_num / 2) * 5;
					break;
				}
			}
			break;
		}
		if (block_start + block_count > parity_num)
			block_count = parity_num - block_start;
		if (block_distri >> 2){
			// ファイル番号にする、vol_1, vol_2, vol_3, ...
			swprintf(recovery_path, MAX_LEN, L"%s.vol_%0*d%s", recovery_base, block_start_max, num + 1, file_ext);
		} else {
			// QuickPar方式、volXX+YY
			swprintf(recovery_path, MAX_LEN, L"%s.vol%0*d+%0*d%s", recovery_base, block_start_max,
				first_num + block_start, block_count_max, block_count, file_ext);
		}
		utf16_to_cp(recovery_path, ascii_buf, cp_output);

		// パケットの繰り返し回数を計算する
		repeat_max = 1;
		for (j = 2; j <= block_count; j *= 2)	// 繰り返し回数は log2(block_count)
			repeat_max++;
		if ((packet_limit > 0) && (repeat_max > packet_limit))
			repeat_max = packet_limit;	// 繰り返し回数を制限する
		// リカバリ・ファイルの大きさを計算する
		file_size = (__int64)(68 + block_size) * block_count;
		file_size += (common_size * repeat_max) + footer_size;
		total_file_size += file_size;
		packet_count = repeat_max * packet_num;	// リカバリ・ファイルの共通パケットの数
		packet_count += footer_num + block_count;
		printf("%13I64d %7d %6d : \"%s\"\n", file_size, packet_count, block_count, ascii_buf);

		block_start += block_count;
	}
}

// ソース・ファイルを分割する (分割サイズは split_size で指定する)
int split_files(
	file_ctx_c *files,
	int *cur_num, int *cur_id)	// エラー発生時は、その時点でのファイル番号と分割番号が戻る
{
	unsigned char buf[IO_SIZE];
	wchar_t file_path[MAX_LEN], recovery_dir[MAX_LEN];
	wchar_t *file_name, *file_ext;
	int num, id, split_num, split_max, total_num, dir_len;
	int name_len, ext_len, num2;
	unsigned int rv, len, split_left;
	unsigned int time_last, prog_num = 0;
	__int64 num8, file_left;
	HANDLE hFile, hFile_src;

	// 分割したファイルをどこに保存するか
	get_base_dir(recovery_file, recovery_dir);
	if (compare_directory(base_dir, recovery_dir) == 0){	// 分割先が同じ場所ならコピーしない
		recovery_dir[0] = 0;
		dir_len = base_len;
	} else {
		dir_len = (int)wcslen(recovery_dir);
		//printf_cp("\n save_path = %s \n", recovery_dir);
	}

	// うまく分割できるか確かめる
	total_num = 0;
	for (num = 0; num < file_num; num++){
		if (files[num].size > (__int64)split_size){
			num8 = (files[num].size + (__int64)split_size - 1) / split_size;
			if (num8 > 99999){	// 分割個数が 99999を超えたらエラー
				printf("too many split file, %d\n", num);
				*cur_num = -1;
				*cur_id = 0;
				return 1;
			}
			total_num += (int)num8;
		} else if (recovery_dir[0] != 0){	// 分割先が異なる場合だけコピーする
			total_num++;
		}
	}
	//printf("split total num = %d\n", total_num);
	if (total_num == 0)
		return 0;	// 分割する必要なし

	// 分割したファイルが、ソース・ファイルを上書きしないかチェックする
	for (num = 0; num < file_num; num++){
		// 拡張子を捜す
		ext_len = 0;
		file_name = list_buf + files[num].name;
		file_ext = wcsrchr(file_name, '.');
		if (file_ext != NULL){
			name_len = (int)(file_ext - file_name);
			ext_len = (int)wcslen(file_ext);
			if ((ext_len >= 4) && (ext_len <= 6)){	// 最長で ".99999" になる
				split_num = 0;
				for (id = 1; id < ext_len; id++){
					if ((file_ext[id] < '0') || (file_ext[id] > '9')){
						ext_len = 0;
						break;
					}
					split_num = split_num * 10 + (file_ext[id] - '0');
				}
				// 分割先が異なる場合は、分割されるソース・ファイルを上書きしない
				if ((ext_len > 0) && (recovery_dir[0] != 0) && (files[num].size > split_size))
					ext_len = 0;
			} else {
				ext_len = 0;
			}
		}
		if (ext_len > 0){	// 全て数字の拡張子を持つソース・ファイルがあるなら
			//printf_cp("\n risky name = %s \n", file_name);
			for (num2 = 0; num2 < file_num; num2++){
				if (num2 == num)
					continue;
				if (_wcsnicmp(list_buf + files[num2].name, file_name, name_len) == 0){
					//printf_cp(" match name = %s \n", list_buf + files[num2].name);
					num8 = (files[num2].size + (__int64)split_size - 1) / split_size;
					split_max = (int)num8;
					//printf("split_num = %d, split max = %d, ext_len = %d \n", split_num, split_max, ext_len);
					// 分割数的に問題なければ除外する
					if ((split_max == 1) || (split_num > split_max))
						continue;	// 分割サイズよりも小さなファイルは、数字の拡張子が付かない
					if (((split_max < 1000) && (ext_len >= 5)) || ((split_max < 10000) && (ext_len >= 6)))
						continue;	// 拡張子の桁数が異なる
					// 上書きする危険性があるのでエラーにする
					printf_cp("split bad file, %s\n", file_name);
					*cur_num = -1;
					*cur_id = 0;
					return 1;
				}
			}
		}
	}

	// 分割サイズよりも大きなファイルだけ分割する
	printf("\n");
	print_progress_text(0, "Spliting file");
	time_last = GetTickCount();
	for (num = 0; num < file_num; num++){
		if (files[num].size > (__int64)split_size){	// 分割する
			file_left = files[num].size;
			num8 = (file_left + (__int64)split_size - 1) / split_size;
			split_num = (int)num8;
			if (split_num <= 999){
				split_max = 3;	// 標準では 001～999 の 999分割までだから 3桁
			} else if (split_num <= 9999){
				split_max = 4;
			} else {	// ソース・ブロックは 32768個以下だから分割数もそれ以下のはず
				split_max = 5;
			}

			// ソース・ファイルを開く
			wcscpy(file_path, base_dir);
			wcscpy(file_path + base_len, list_buf + files[num].name);
			hFile_src = CreateFile(file_path, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
			if (hFile_src == INVALID_HANDLE_VALUE){
				print_win32_err();
				printf_cp("cannot open file, %s\n", list_buf + files[num].name);
				*cur_num = num;
				*cur_id = 0;
				return 1;
			}
			if (recovery_dir[0] != 0)
				wcscpy(file_path, recovery_dir);	// 異なる場所に保存する
			for (id = 1; id <= split_num; id++){
				swprintf(file_path + dir_len, _countof(file_path) - dir_len, L"%s.%0*d", list_buf + files[num].name, split_max, id);
				hFile = CreateFile(file_path, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
				if (hFile == INVALID_HANDLE_VALUE){
					print_win32_err();
					CloseHandle(hFile_src);
					printf_cp("cannot create file, %s\n", file_path);
					*cur_num = num;
					*cur_id = id - 1;
					return 1;
				}
				// ファイルを分割サイズごとに書き込んでいく
				split_left = split_size;
				if ((__int64)split_left > file_left)
					split_left = (unsigned int)file_left;
				file_left -= split_left;
				while (split_left){
					len = IO_SIZE;
					if (split_left < IO_SIZE)
						len = split_left;
					split_left -= len;
					if (!ReadFile(hFile_src, buf, len, &rv, NULL) || (len != rv)){
						print_win32_err();
						CloseHandle(hFile_src);
						CloseHandle(hFile);
						printf("ReadFile, input file %d\n", num);
						*cur_num = num;
						*cur_id = id;
						return 1;
					}
					if (!WriteFile(hFile, buf, len, &rv, NULL)){
						print_win32_err();
						CloseHandle(hFile_src);
						CloseHandle(hFile);
						printf("WriteFile, split file %d.%03d", num, id);
						*cur_num = num;
						*cur_id = id;
						return 1;
					}
				}
				CloseHandle(hFile);

				// 経過表示
				prog_num++;
				if (GetTickCount() - time_last >= UPDATE_TIME){
					if (print_progress((prog_num * 1000) / total_num)){
						CloseHandle(hFile_src);
						*cur_num = num;
						*cur_id = id;
						return 2;
					}
					time_last = GetTickCount();
				}
			}
			CloseHandle(hFile_src);

		} else if (recovery_dir[0] != 0){	// 小さなファイルはそのままコピーする
			// ソース・ファイルのパス
			wcscpy(file_path, base_dir);
			wcscpy(file_path + base_len, list_buf + files[num].name);
			// コピー先のパス
			wcscpy(recovery_dir + dir_len, list_buf + files[num].name);
			rv = CopyFile(file_path, recovery_dir, FALSE);
			if (rv != 0){	// 読み取り専用属性を解除しておく
				len = GetFileAttributes(recovery_dir);
				if ((len & (FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_READONLY)) != 0){
					len &= ~(FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_READONLY);
					rv = SetFileAttributes(recovery_dir, len);
				}
			}
			if (rv == 0){
				print_win32_err();
				printf_cp("cannot copy file, %s\n", recovery_dir);
				*cur_num = num;
				*cur_id = 0;
				return 1;
			}
			recovery_dir[dir_len] = 0;	// ディレクトリに戻しておく

			// 経過表示
			prog_num++;
			if (GetTickCount() - time_last >= UPDATE_TIME){
				if (print_progress((prog_num * 1000) / total_num)){
					*cur_num = num;
					*cur_id = 0;
					return 2;
				}
				time_last = GetTickCount();
			}
		}
	}
	print_progress_done();	// 改行して行の先頭に戻しておく

	return 0;
}

// 分割されたソース・ファイルを削除する
void delete_split_files(
	file_ctx_c *files,
	int max_num, int max_id)	// エラー発生時のファイル番号と分割番号
{
	wchar_t file_path[MAX_LEN], recovery_dir[MAX_LEN];
	int num, id, split_num, split_max, dir_len;
	__int64 num8, file_left;

	// 分割したファイルをどこに保存するか
	get_base_dir(recovery_file, recovery_dir);
	if (compare_directory(base_dir, recovery_dir) == 0){	// 分割先が同じ場所ならコピーしない
		recovery_dir[0] = 0;
		dir_len = base_len;
	} else {
		dir_len = (int)wcslen(recovery_dir);
	}

	// 分割サイズよりも大きなファイルだけ分割する
	for (num = 0; num <= max_num; num++){
		if (files[num].size > (__int64)split_size){	// 分割されたファイルを削除する
			file_left = files[num].size;
			num8 = (file_left + (__int64)split_size - 1) / split_size;
			split_num = (int)num8;	// 分割個数は 99999 以下のはず
			if (split_num <= 999){
				split_max = 3;	// 標準では 001～999 の 999分割までだから 3桁
			} else if (split_num <= 9999){
				split_max = 4;
			} else {	// if (split_num <= 99999){
				split_max = 5;
			}
			if (num == max_num)
				split_num = max_id;	// 中断した所まで削除する
			for (id = 1; id <= split_num; id++){
				if (recovery_dir[0] != 0){
					wcscpy(file_path, recovery_dir);	// 異なる場所に保存した
				} else {
					wcscpy(file_path, base_dir);
				}
				swprintf(file_path + dir_len, _countof(file_path) - dir_len, L"%s.%0*d", list_buf + files[num].name, split_max, id);
				if (!DeleteFile(file_path))	// 既に分割してるソース・ファイルを削除する
					return;	// 削除に失敗したら出る
			}

		} else if (recovery_dir[0] != 0){	// 別の場所にコピーした場合だけ削除する
			wcscpy(recovery_dir + dir_len, list_buf + files[num].name);	// コピー先のパス
			if (!DeleteFile(recovery_dir))	// コピーしたソース・ファイルを削除する
				return;	// 削除に失敗したら出る
			recovery_dir[dir_len] = 0;	// ディレクトリに戻しておく
		}
	}
}

#define ZIP_SEARCH_SIZE	1022

// リカバリ・ファイルをソース・ファイルの末尾にくっつける
// フォーマットによってはソース・ファイルが使えなくなる恐れがあるので、制限する？
// 前方から読み込んでいくストリーム形式だと問題ないが、フッターを先に読む形式だと無理
// 7z 書庫は問題ないが、ZIP 書庫は末尾を工夫しないといけない。
// bz2 と gz は 7-Zip で展開時にエラーが表示される・・・
// EXE や DLL ファイルはチェックサムが変わるので、読み込みエラーが発生するかも。
// JPEG, GIF, PNG, MP3 などは問題ないっぽいけど、リカバリ・レコードを埋め込む必要が無い。
int append_recovery_file(
	wchar_t *file_name,	// ソース・ファイルの名前
	HANDLE *rcv_hFile,	// 各リカバリ・ファイルのハンドル
	int no_index)		// インデックス・ファイルが存在しない
{
	unsigned char buf[IO_SIZE], header[ZIP_SEARCH_SIZE];
	wchar_t file_path[MAX_LEN], save_path[MAX_LEN];
	int num, header_size, header_off;
	unsigned int rv, len, attach_flag;
	unsigned int time_last;
	HANDLE hFileWrite = NULL, hFileRead = NULL;
	LARGE_INTEGER qwi, orig_size;	// Quad Word Integer

	// 結合したファイルをどこに保存するか
	attach_flag = 0;
	printf("\n");
	print_progress_text(0, "Appending recovery record");
	time_last = GetTickCount();
	get_base_dir(recovery_file, save_path);
	if (compare_directory(base_dir, save_path) != 0){	// 結合先が異なる場所ならコピーする
		wcscpy(file_path, base_dir);
		wcscat(file_path, file_name);
		wcscat(save_path, file_name);
		if (CopyFile(file_path, save_path, FALSE) == 0){
			print_win32_err();
			printf_cp("cannot copy file, %s\n", save_path);
			return 1;
		}
		attach_flag = 0x02000000;	// コピーした印
	} else {
		wcscpy(save_path, base_dir);
		wcscat(save_path, file_name);
		attach_flag = 0x01000000;	// ソース・ファイルが同じ場所の印
	}
	// 読み取り専用属性を解除しておく
	len = GetFileAttributes(save_path);
	if ((len & (FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_READONLY)) != 0){
		len &= ~(FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_READONLY);
		SetFileAttributes(save_path, len);
	}

	// ソース・ファイルを開く
	hFileWrite = CreateFile(save_path, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL);
	if (hFileWrite == INVALID_HANDLE_VALUE){
		print_win32_err();
		printf_cp("cannot open file, %s\n", save_path);
		return 1;
	}
	// 対応するフォーマットであることを確認する
	// 7z = 先頭 6-byte の Signature で判定する
	// zip = 先頭 4-byte の local file header signature と
	//       末尾 22-byte の end of central directory record で判定する
	if (!ReadFile(hFileWrite, buf, 28, &len, NULL) || (len != 28)){
		print_win32_err();
		goto error_end;
	}
	if (((unsigned int *)buf)[0] == 0x04034b50){
		// 末尾を何バイト読み込むか(ZIPのコメントに対応する)
		if (total_file_size < ZIP_SEARCH_SIZE){
			header_size = (int)total_file_size - 32;	// 最低でも 32 + 22 = 54 バイトはあるはず
		} else {
			header_size = ZIP_SEARCH_SIZE;
		}
		// ファイルの末尾 header_size バイトを読み込む
		qwi.QuadPart = - header_size;
		if (!SetFilePointerEx(hFileWrite, qwi, NULL, FILE_END)){
			print_win32_err();
			goto error_end;
		}
		if (!ReadFile(hFileWrite, header, header_size, &len, NULL) || (len != header_size)){
			print_win32_err();
			goto error_end;
		}
		// 後ろから Signature を探す
		num = header_size - 22;
		while (num >= 0){
			if (((unsigned int *)(header + num))[0] == 0x06054b50){
				memcpy(&len, header + num + 12, 4);	// ヘッダーに記録されてるサイズを確認する
				memcpy(&rv, header + num + 16, 4);
				if (rv + len + header_size - num == total_file_size){
					attach_flag |= 0x10000000;
					header_off = num;
					header_size -= num;
					break;
				}
			} else if (((unsigned int *)(header + num))[0] == 0x06064b50){
				memcpy(&len, header + num + 40, 4);	// ZIP64ヘッダーに記録されてるサイズを確認する
				memcpy(&orig_size.QuadPart, header + num + 48, 8);
				if (len + orig_size.QuadPart + header_size - num == total_file_size){
					attach_flag |= 0x10000000;
					header_off = num;
					header_size -= num;
					break;
				}
			}
			num--;
		}
	} else if ((((unsigned short *)buf)[0] == 0x7A37) && (((unsigned int *)(buf + 2))[0] == 0x1C27AFBC)){
		__int64 size1, size2;
		memcpy(&size1, buf + 12, 8);	// ヘッダーに記録されてるサイズを確認する
		memcpy(&size2, buf + 20, 8);
		if (32 + size1 + size2 == total_file_size)
			attach_flag |= 0x20000000;
	}
	if ((attach_flag & 0x30000000) == 0){
		printf("\ninvalid archive format\n");
		goto error_end;
	}

	// ファイルの位置を末尾にする
	qwi.QuadPart = 0;
	if (!SetFilePointerEx(hFileWrite, qwi, &orig_size, FILE_END)){
		print_win32_err();
		goto error_end;
	}
	//printf("\n qwi.QuadPart = %I64d\n", orig_size.QuadPart);

	if (no_index == 0){	// インデックス・ファイルを開く
		hFileRead = CreateFile(recovery_file, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
		if (hFileRead == INVALID_HANDLE_VALUE){
			print_win32_err();
			printf_cp("cannot open file, %s\n", recovery_file);
			goto error_end;
		}

		// 末尾に追加していく
		do {
			if (!ReadFile(hFileRead, buf, IO_SIZE, &len, NULL)){
				print_win32_err();
				goto error_end;
			}
			if (len == 0)
				break;
			attach_flag |= len;
			if (!WriteFile(hFileWrite, buf, len, &rv, NULL)){
				print_win32_err();
				goto error_end;
			}
		} while (len > 0);
		CloseHandle(hFileRead);
		hFileRead = NULL;
	}

	// リカバリ・ファイルを開く
	for (num = 0; num < recovery_num; num++){
		// 経過表示
		if (GetTickCount() - time_last >= UPDATE_TIME){
			if (print_progress(((num + 1) * 1000) / recovery_num)){
				goto error_end;
			}
			time_last = GetTickCount();
		}

		// ファイルの位置を先頭にする
		qwi.QuadPart = 0;
		if (!SetFilePointerEx(rcv_hFile[num], qwi, NULL, FILE_BEGIN)){
			print_win32_err();
			goto error_end;
		}

		// 末尾に追加していく
		do {
			if (!ReadFile(rcv_hFile[num], buf, IO_SIZE, &len, NULL)){
				print_win32_err();
				goto error_end;
			}
			if (len == 0)
				break;
			attach_flag |= len;
			if (!WriteFile(hFileWrite, buf, len, &rv, NULL)){
				print_win32_err();
				goto error_end;
			}
		} while (len > 0);
	}

	// ZIP 書庫なら末尾にヘッダーを追加する
	if (attach_flag & 0x10000000){
		if (!WriteFile(hFileWrite, header + header_off, header_size, &rv, NULL) || (rv != header_size)){
			print_win32_err();
			goto error_end;
		}
	}

	print_progress_done();	// 改行して行の先頭に戻しておく
	CloseHandle(hFileWrite);
	return 0;

error_end:	// エラー発生時
	if (attach_flag & 0x02000000){	// コピーしたファイルを削除する
		if (hFileWrite != NULL){
			CloseHandle(hFileWrite);
			hFileWrite = NULL;
		}
		DeleteFile(save_path);
	} else if ((attach_flag & 0x01FFFFFF) > 0x01000000){	// 追加したバイトを取り除く
		if (SetFilePointerEx(hFileWrite, orig_size, NULL, FILE_BEGIN) != 0)
			SetEndOfFile(hFileWrite);
	}
	if (hFileWrite != NULL)
		CloseHandle(hFileWrite);
	if (hFileRead != NULL)
		CloseHandle(hFileRead);
	return 1;
}

