// par1.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 <stdio.h>

#include <windows.h>
#include <imagehlp.h>

#include "common1.h"
#include "phmd5.h"
#include "md5_1.h"
#include "ini.h"
#include "par1.h"
#include "version.h"

/*
Fast Galois Field Arithmetic Library in C/C++
Copright (C) 2007 James S. Plank
*/
#include "gf8.h"

/*
// 行列の表示用関数
void galois_print_matrix(int *matrix, int rows, int cols)
{
	int i, j;
	printf("\n");
	for (i = 0; i < rows; i++) {
		for (j = 0; j < cols; j++) {
			printf("%4d", matrix[i*cols+j]);
		}
		printf("\n");
	}
}
*/

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/*
gflib の行列作成用関数や行列の逆変換用の関数を元にして、
計算のやり方を PAR 1.0 用に修正する。

par-v1.1.tar.gz に含まれる rs.doc
Dummies guide to Reed-Solomon coding. を参考にする

解説ではパリティ計算用の行列の要素、掛ける値は
F(i,j) = i^(j-1) ・・・ i の (j-1) 乗
ソース・ブロックは i = 1～N で、
パリティ・ブロックは j = 1～M になってる。

実際に計算する関数では
ソース・ブロックが i = 0 ～ cols - 1 で、
パリティ・ブロックが j = 0 ～ rows - 1 なので、

理論のF(i,j) = F(i+1, j+1) = (i+1) ^ (j+1-1) = (i+1) ^ j
・・・ (i+1) の j 乗
*/

// PAR 1.0 のパリティ作成用の行列
int * make_encode_matrix(
	int rows,	// 横行、パリティのブロック数
	int cols,	// 縦列、ソースのブロック数
	int first)	// 最初のパリティ・ブロック番号
{
	int *mat, i, j;

	mat = malloc(sizeof(int) * rows * cols);
	if (mat == NULL){
		printf("malloc, %d\n", sizeof(int) * rows * cols);
		return NULL;
	}

	for (j = 0; j < rows; j++){
		for (i = 0; i < cols; i++)
			mat[cols * j + i] = galois_power(i + 1, first + j);
	}

	return mat;
}

/*
失われたソース・ブロックの数 = 利用するパリティ・ブロックの数を縦行とし、
ソース・ブロックの数を横列とする長方形の行列を作る。

例えば、ソース・ブロックを A, B, C, D, E 、
そのパリティ・ブロックを X, Y, Z とする。
その場合の関係式は次のようになる。

1: 1*A xor 1*B xor 1*C xor  1*D xor  1*E = X
2: 1*A xor 2*B xor 3*C xor  4*D xor  5*E = Y
3: 1*A xor 4*B xor 5*C xor 16*D xor 17*E = Z

これは PARファイルを作成する際の行列と同じ。
   1   1   1   1   1
   1   2   3   4   5
   1   4   5  16  17

ここで、B, C, D の三ブロックが失われて、それぞれを X, Y, Z で補うなら、
その状態は次のような関係式になる。

1: 1*A xor 1*B xor 1*C xor  1*D xor  1*E = 0*A xor 1*X xor 0*Y xor 0*Z xor 0*E
2: 1*A xor 2*B xor 3*C xor  4*D xor  5*E = 0*A xor 0*X xor 1*Y xor 0*Z xor 0*E
3: 1*A xor 4*B xor 5*C xor 16*D xor 17*E = 0*A xor 0*X xor 0*Y xor 1*Z xor 0*E

左：パリティ・ブロックの係数
   1   1   1   1   1
   1   2   3   4   5
   1   4   5  16  17

右：そのパリティ・ブロックがどのソース・ブロックの位置にくるか
   0   1   0   0   0
   0   0   1   0   0
   0   0   0   1   0

ここでまず、存在するソース・ブロックの列を 0にする。

1: 0*A xor 1*B xor 1*C xor  1*D xor 0*E = 1*A xor 1*X xor 0*Y xor 0*Z xor  1*E
2: 0*A xor 2*B xor 3*C xor  4*D xor 0*E = 1*A xor 0*X xor 1*Y xor 0*Z xor  5*E
3: 0*A xor 4*B xor 5*C xor 16*D xor 0*E = 1*A xor 0*X xor 0*Y xor 1*Z xor 17*E

左：ソース・ブロックが存在した所が 0になる
   0   1   1   1   0
   0   2   3   4   0
   0   4   5  16   0

右：逆にこちらではソース・ブロックが存在した所が反転する
   1   1   0   0   1
   1   0   1   0   5
   1   0   0   1  17

Gaussian Elimination (ガウスの消去法) で元の左の行列を右のに変換する

各行で 0でない値を探して、それが 1になるように、何倍かする。
まずは 1行目：

1: 0*A xor 1*B xor 1*C xor  1*D xor 0*E = 1*A xor 1*X xor 0*Y xor 0*Z xor  1*E
1 行目の 2列目は 1なのでそのまま。

他の行の同じ列に 0でない値があれば、それを 0にするために、
行を何倍かしたものを XOR する

2: 0*A xor 2*B xor 3*C xor  4*D xor 0*E = 1*A xor 0*X xor 1*Y xor 0*Z xor  5*E
2 行目の 2列目は 2なので、1行目の各値を 2倍したものを XOR する。

3: 0*A xor 4*B xor 5*C xor 16*D xor 0*E = 1*A xor 0*X xor 0*Y xor 1*Z xor 17*E
3 行目の 2列目は 4なので、1行目の各値を 4倍したものを XOR する。

1: 0*A xor 1*B xor 1*C xor  1*D xor 0*E = 1*A xor 1*X xor 0*Y xor 0*Z xor  1*E
2: 0*A xor 0*B xor 1*C xor  6*D xor 0*E = 3*A xor 2*X xor 1*Y xor 0*Z xor  7*E
3: 0*A xor 0*B xor 1*C xor 20*D xor 0*E = 5*A xor 4*X xor 0*Y xor 1*Z xor 21*E

次は 2行目：

2: 0*A xor 0*B xor 1*C xor  6*D xor 0*E = 3*A xor 2*X xor 1*Y xor 0*Z xor  7*E
2 行目の 3列目は 1なのでそのまま。

1: 0*A xor 1*B xor 1*C xor  1*D xor 0*E = 1*A xor 1*X xor 0*Y xor 0*Z xor  1*E
1 行目の 3列目は 1なので、1行目の各値をそのまま XOR する。

3: 0*A xor 0*B xor 1*C xor 20*D xor 0*E = 5*A xor 4*X xor 0*Y xor 1*Z xor 21*E
3 行目の 3列目は 1なので、1行目の各値をそのまま XOR する。

1: 0*A xor 1*B xor 0*C xor  7*D xor 0*E = 2*A xor 3*X xor 1*Y xor 0*Z xor  6*E
2: 0*A xor 0*B xor 1*C xor  6*D xor 0*E = 3*A xor 2*X xor 1*Y xor 0*Z xor  7*E
3: 0*A xor 0*B xor 0*C xor 18*D xor 0*E = 6*A xor 6*X xor 1*Y xor 1*Z xor 18*E

次は 3行目：

3: 0*A xor 0*B xor 0*C xor 18*D xor 0*E = 6*A xor 6*X xor 1*Y xor 1*Z xor 18*E
3 行目の 4列目は 18なので、18で割る

1: 0*A xor 1*B xor 0*C xor  7*D xor 0*E = 2*A xor 3*X xor 1*Y xor 0*Z xor  6*E
1 行目の 4列目は 7なので、1行目の各値を 7倍したものを XOR する。

2: 0*A xor 0*B xor 1*C xor  6*D xor 0*E = 3*A xor 2*X xor 1*Y xor 0*Z xor  7*E
2 行目の 4列目は 6なので、1行目の各値を 6倍したものを XOR する。

1: 0*A xor 1*B xor 0*C xor 0*D xor 0*E =   3*A xor   2*X xor 123*Y xor 122*Z xor 1*E
2: 0*A xor 0*B xor 1*C xor 0*D xor 0*E = 184*A xor 185*X xor 187*Y xor 186*Z xor 1*E
3: 0*A xor 0*B xor 0*C xor 1*D xor 0*E = 186*A xor 186*X xor 192*Y xor 192*Z xor 1*E

左：元の右の行列と同じになってる
   0   1   0   0   0
   0   0   1   0   0
   0   0   0   1   0

右：これが失われたソース・ブロックを復元するための行列となる
   3   2 123 122   1
 184 185 187 186   1
 186 186 192 192   1
*/

// 復元用の行列を作る、十分な数のパリティ・ブロックが必要
int * make_decode_matrix(
	int parity_max,	// 本来のパリティ・ブロックの数
	int rows,		// 横行、行列の縦サイズ、失われたソース・ブロックの数 = 利用するパリティ・ブロック数
	int cols,		// 縦列、行列の横サイズ、本来のソース・ブロック数
	int *exist,		// どのブロックが存在するか
	int *id)		// 失われたソース・ブロックをどのパリティ・ブロックで代用したか
{
	int *mat_max, *mat_l, *mat_r;
	int factor, row_start, row_start2, col_find, i, j, k;

	// 失われたソース・ブロックをどのパリティ・ブロックで代用するか
	j = 0;
	for (i = 0; i < cols; i++){
		id[i] = i;
		if (exist[i] == 0){ // ソース・ブロックが存在しない所
			while (j < parity_max){
				if (exist[cols + j] != 0){ // その番号のパリティ・ブロックが存在すれば
					id[i] = cols + j;
					j++;
					break;
				} else {
					j++;
				}
			}
			if (id[i] < cols){
				printf("\nneed more parity volume\n");
				return NULL; // パリティ・ブロックの数が足りなければ
			}
		}
	}

	// 全てのパリティ・ブロックが存在すると仮定した行列を作る
	mat_max = make_encode_matrix(parity_max, cols, 0);
	if (mat_max == NULL)
		return NULL;

	// 存在して利用するパリティ・ブロックだけの行列を作る
	mat_l = malloc(sizeof(int) * rows * cols);
	if (mat_l == NULL) {
		free(mat_max);
		printf("malloc, %d\n", sizeof(int) * rows * cols);
		return NULL;
	}
	j = 0;
	for (i = 0; i < cols; i++){
		if (id[i] >= cols){
			for (k = 0; k < cols; k++)
				mat_l[cols * j + k] = mat_max[cols * (id[i] - cols) + k];
			j++;
		}
	}
	free(mat_max);
	//printf("\nLeft :\n");
	//galois_print_matrix(mat_l, rows, cols);

	// 失われたソース・ブロックと代用するパリティ・ブロックの位置を表す行列を作る
	mat_r = (int *)calloc(rows * cols, sizeof(int));
	if (mat_r == NULL){
		free(mat_l);
		printf("calloc, %d\n", sizeof(int) * rows * cols);
		return NULL;
	}
	j = 0;
	for (i = 0; i < cols; i++){
		if (id[i] >= cols){
			// j = 行番号
			// id[i] = その行を補ったパリティ・ブロックの番号
			// i = その行のソース・ブロック番号
			mat_r[cols * j + i] = 1;
			j++;
		}
	}
	//printf("\nRight :\n");
	//galois_print_matrix(mat_r, rows, cols);

	// mat_l の存在するソース・ブロックの列を 0 にするような値で XOR する
	for (i = 0; i < cols; i++){
		if (exist[i] != 0){ // ソース・ブロックが存在する列
			for (j = 0; j < rows; j++){ // 行ごとに
				factor = mat_l[cols * j + i]; // j 行の i 列の値
				mat_l[cols * j + i] = 0; // mat_l[cols * j + i] ^= factor; と同じ
				mat_r[cols * j + i] ^= factor;
			}
		}
	}
	//printf("\nErase source block :\n");
	//galois_print_matrix(mat_l, rows, cols);
	//printf("\n");
	//galois_print_matrix(mat_r, rows, cols);

	// Gaussian Elimination
	for (j = 0; j < rows; j++){
		row_start = cols * j; // その行の開始位置

		// mat_l の各行ごとに最初の 0でない値を探す
		for (col_find = 0; col_find < cols; col_find++){
			if (mat_l[row_start + col_find] != 0)
				break;
		}
		if (col_find == cols){ // 見つからなければ、その行列の逆行列を計算できない
			printf("\nmatrix is not invertible\n");
			k = 0;
			for (i = 0; i < cols; i++){
				if (id[i] >= cols){
					if (k == j){
						printf(" parity volume %u is useless\n", (1 + id[i] - cols));
						break;
					}
					k++;
				}
			}
			free(mat_l);
			free(mat_r);
			return NULL;
		}
		factor = mat_l[row_start + col_find]; // col_find 列に 0 ではない値を発見
		if (factor != 1){ // factor が 1でなければ、1にする為に factor で割る
			for (k = 0; k < cols; k++){
				mat_l[row_start + k] = galois_multtable_divide(mat_l[row_start + k], factor);
				mat_r[row_start + k] = galois_multtable_divide(mat_r[row_start + k], factor);
			}
		}

		// 別の行の同じ col_find 列が 0以外なら、その値を 0にするために、
		// j 行を何倍かしたものを XOR する
		for (i = 0; i < rows; i++){
			if (i == j)
				continue; // 同じ行はとばす
			row_start2 = cols * i; // その行の開始位置
			factor = mat_l[row_start2 + col_find]; // i 行の col_find 列の値
			if (factor != 0){ // 0でなければ
				// 先の計算により、j 行の col_find 列の値は必ず 1なので、この factor が倍率になる
				if (factor == 1){ // 倍率が 1なら、単純に XOR するだけ
					for (k = 0; k < cols; k++){
						mat_l[row_start2 + k] ^= mat_l[row_start + k];
						mat_r[row_start2 + k] ^= mat_r[row_start + k];
					}
				} else {
					for (k = 0; k < cols; k++){
						mat_l[row_start2 + k] ^= galois_multtable_multiply(mat_l[row_start + k], factor);
						mat_r[row_start2 + k] ^= galois_multtable_multiply(mat_r[row_start + k], factor);
					}
				}
			}
		}

		//printf("\nDivide & Eliminate :\n");
		//galois_print_matrix(mat_l, rows, cols);
		//printf("\n");
		//galois_print_matrix(mat_r, rows, cols);
	}

	free(mat_l);
	return mat_r;
}

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

#define CHUNK_SIZE 65536

typedef struct {
	HANDLE hFile;	// ファイル・ハンドル
	__int64 size;	// ファイルの残りサイズ
} file_ctx;

// リード・ソロモン符号を使ってエンコードする
int rs_encode(
	int source_num,		// ソース・ブロックの数
	__int64 block_size,	// ブロック・サイズ (最大ファイル・サイズ)
	int parity_num,		// パリティ・ブロックの数
	int first_num,		// 最初のパリティ・ブロック番号
	file_ctx *files,
	HANDLE *par_hFile,
	PHMD5 *par_md5)
{
	unsigned char *buffer, *block = NULL;
	int err = 0, i, j;
	int *mat = NULL, factor;
	unsigned int io_size, unit_size, len, rv;
	unsigned int k, offset, length, chunk_count;
	unsigned int time_last, prog_num = 0, prog_base;
	__int64 block_left;
//unsigned int time1;

	// 利用できるメモリー量を調べる
	io_size = get_mem_size(block_size * (__int64)(source_num + parity_num));
	if (io_size > 1048576 * 2)
		io_size -= (parity_num * source_num) * 4 + 1048576; // 行列に必要なメモリーとスタック用に 1MB
	if (io_size < 1048576)
		io_size = 1048576;
	io_size /= source_num + 1;	// 何個分必要か

	// ブロック・サイズより大きい、またはブロック・サイズ自体が小さい場合は
	if (((__int64)io_size >= block_size) || (block_size <= 4096)){
		io_size = (unsigned int)block_size;	// ブロック・サイズと同じにする
	} else {	// ブロック・サイズを 2の乗数サイズの断片に分割する
		// 断片化する場合でもブロック数が多いと 256 * 4096 = 1MB は使う
		__int64 fragment_size = 4096;	// 最低サイズ
		while (fragment_size * 2 <= (__int64)io_size)
			fragment_size *= 2;
		io_size = (unsigned int)fragment_size;
	}
	io_size = (io_size + 3) & 0xFFFFFFFC;	// 4の倍数にする
	unit_size = io_size + HASH_SIZE;		// チェックサムの分だけ増やす
	prog_base = (unsigned int)((block_size + (__int64)io_size - 1) / (__int64)io_size) * parity_num;	// 全体の断片の個数
	//printf("io_size = %d, prog_base = %d\n", io_size, prog_base);

	block_left = block_size;
	if (galois_create_mult_tables() < 0){
		printf("galois_create_mult_tables\n");
		return 1;
	}

	// 作業バッファーを確保する
	block = malloc(unit_size * (source_num + 1));
	if (block == NULL){
		printf("malloc, %d\n", unit_size * (source_num + 1));
		err = 1;
		goto error_end;
	}
	buffer = block + unit_size;

	// パリティ計算用の行列演算の準備をする
	mat = make_encode_matrix(parity_num, source_num, first_num); // 変換行列
	if (mat == NULL){
		err = 1;
		goto error_end;
	}
	//galois_print_matrix(mat, parity_num, source_num);

	// chunk がキャッシュに収まるようにすれば速くなる！ (ストリップマイニングという最適化手法)
	// CPU L2キャッシュ・サイズが 256KB として、1/4 なら 64KB
	// chunk サイズが 2**16 なので分割数は 1～65536 の範囲になる
	chunk_count = (unit_size + CHUNK_SIZE - 1) / CHUNK_SIZE;
	//printf("split count = %d, chunk size = %d\n", chunk_count, CHUNK_SIZE);

//time1 = GetTickCount();
	// バッファー・サイズごとにパリティ・ブロックを作成する
	time_last = GetTickCount();
	while (block_left > 0){
		// バッファーにソース・ファイルの内容を読み込む
		for (i = 0; i < source_num; i++){
			if (files[i].size > 0){
				if (files[i].size < io_size){
					len = (unsigned int)(files[i].size);
				} else {
					len = io_size;
				}
				files[i].size -= len;
				if (!ReadFile(files[i].hFile, buffer + (unit_size * i), len, &rv, NULL) || (len != rv)){
					print_win32_err();
					printf("ReadFile, data file %d\n", i);
					err = 1;
					goto error_end;
				}
				if (len < io_size)
					memset(buffer + (unit_size * i + len), 0, io_size - len);
				// ソース・ブロックのチェックサムを計算する
				checksum4(buffer + (unit_size * i), buffer + (unit_size * i + io_size), io_size);
			} else {
				// ソース・ブロックの値が全て 0 なら、チェックサムも 0 になる。
				memset(buffer + (unit_size * i), 0, unit_size);
			}
		}
		// バッファーに読み込んだサイズ
		if (block_left < io_size){
			len = (unsigned int)block_left;
		} else {
			len = io_size;
		}
		block_left -= len;

		// パリティ・ブロックごとに
		for (i = 0; i < parity_num; i++){
			/*	without strip mining
			memset(block, 0, unit_size);	// パリティ・ブロックを 0で埋める
			// ソース・ブロックごとにパリティを追加していく
			for (j = 0; j < source_num; j++){
				factor = mat[i * source_num + j];
				if (factor == 1){
					galois_region_xor(buffer + (unit_size * j), block, unit_size);
				} else if (factor != 0){
					galois_region_multiply(buffer + (unit_size * j), block, unit_size, factor);
				}
			}
			*/
			length = CHUNK_SIZE;
			for (k = 0; k < chunk_count; k++){	// chunk の番号
				offset = k * CHUNK_SIZE;
				if (offset + length >= unit_size){	// 最後の chunk なら
					length = unit_size - offset;
					k = chunk_count;
				}

				memset(block + offset, 0, length);	// パリティ・ブロックを 0で埋める
				// ソース・ブロックごとにパリティを追加していく
				for (j = 0; j < source_num; j++){
					factor = mat[i * source_num + j];
					if (factor == 1){
						galois_region_xor(buffer + (unit_size * j + offset), block + offset, length);
					} else if (factor != 0){
						galois_region_multiply(buffer + (unit_size * j + offset), block + offset, length, factor);
					}
				}
			}

			// 経過表示
			prog_num++;
			if (GetTickCount() - time_last >= UPDATE_TIME){
				if (print_progress((prog_num * 1000) / prog_base)){
					err = 2;
					goto error_end;
				}
				time_last = GetTickCount();
			}

			// パリティ・ブロックのチェックサムを検証する
			checksum4(block, (unsigned char *)&rv, io_size);
			if (memcmp(block + io_size, &rv, HASH_SIZE) != 0){
				printf("checksum mismatch, parity volume %d\n", i);
				err = 1;
				goto error_end;
			}
			Phmd5Process(&(par_md5[i]), block, len); // ハッシュ値を計算する
			// リカバリ・ファイルへ書き込む
			if (!WriteFile(par_hFile[i], block, len, &rv, NULL)){
				print_win32_err();
				printf("WriteFile, parity volume %d\n", first_num + 1 + i);
				err = 1;
				goto error_end;
			}
		}
	}
	print_progress_done();	// 改行して行の先頭に戻しておく
//time1 = GetTickCount() - time1;
//printf("encode %u.%03u sec\n", time1 / 1000, time1 % 1000);

error_end:
	if (block)
		free(block);
	if (mat)
		free(mat);
	// gflib のテーブルを解放する
	galois_free_tables();
	return err;
}

// リード・ソロモン符号を使ってデコードする
int rs_decode(
	int source_num,		// 本来のソース・ブロックの数
	int block_lost,		// 失われたソース・ブロックの数
	__int64 block_size,	// ブロック・サイズ (最大ファイル・サイズ)
	int parity_max,		// 本来のパリティ・ブロックの数
	int *exist,			// そのブロックが存在するか
	file_ctx *files)
{
	unsigned char *buffer, *block = NULL;
	int err = 0, i, j;
	int *mat = NULL, *id = NULL, factor;
	int block_recover;
	unsigned int io_size, unit_size, len, rv, len2;
	unsigned int k, offset, length, chunk_count;
	unsigned int time_last, prog_num = 0, prog_base;
	__int64 block_left;

	// 利用できるメモリー量を調べる
	io_size = get_mem_size(block_size * (__int64)(source_num + block_lost));
	io_size -= (block_lost * source_num * 8) + (parity_max * source_num * 4) + (source_num * 4);
	io_size -= 1048576; // 行列に必要なメモリーとスタック用に 1MB
	if (io_size < 1048576)
		io_size = 1048576;
	io_size /= source_num + 1;	// 何個分必要か

	// ブロック・サイズより大きい、またはブロック・サイズ自体が小さい場合は
	if (((__int64)io_size >= block_size) || (block_size <= 4096)){
		io_size = (unsigned int)block_size;	// ブロック・サイズと同じにする
	} else {	// ブロック・サイズを 2の乗数サイズの断片に分割する
		// 断片化する場合でもブロック数が多いと 256 * 4096 = 1MB は使う
		__int64 fragment_size = 4096;	// 最低サイズ
		while (fragment_size * 2 <= (__int64)io_size)
			fragment_size *= 2;
		io_size = (unsigned int)fragment_size;
	}
	io_size = (io_size + 3) & 0xFFFFFFFC;	// 4の倍数にする
	unit_size = io_size + HASH_SIZE;		// チェックサムの分だけ増やす
	prog_base = (unsigned int)((block_size + (__int64)io_size - 1) / (__int64)io_size) * block_lost;	// 全体の断片の個数
	//printf("io_size = %d\n", io_size);

	block_left = block_size;
	if (galois_create_mult_tables() < 0){
		printf("galois_create_mult_tables\n");
		return 1;
	}

	// 作業バッファーを確保する
	block = malloc(unit_size * (source_num + 1));
	if (block == NULL){
		printf("malloc, %d\n", unit_size * (source_num + 1));
		err = 1;
		goto error_end;
	}
	buffer = block + unit_size;

	// パリティ計算用の行列演算の準備をする
	id = malloc(sizeof(int) * source_num);
	if (id == NULL){
		printf("malloc, %d\n", sizeof(int) * source_num);
		err = 1;
		goto error_end;
	}

	mat = make_decode_matrix(parity_max, block_lost, source_num, exist, id);
	if (mat == NULL){
		err = 1;
		goto error_end;
	}
	//galois_print_matrix(mat, block_lost, source_num);
	//for (i = 0; i < source_num; i++)
	//	printf("id[%d] = %d\n", i, id[i]);

	// chunk サイズが 2**16 なので分割数は 1～65536 の範囲になる
	chunk_count = (unit_size + CHUNK_SIZE - 1) / CHUNK_SIZE;

	// バッファー・サイズごとにソース・ブロックを復元する
	time_last = GetTickCount();
	while (block_left > 0){
		// バッファーにソース・ファイルとリカバリ・ファイルの内容を読み込む
		for (i = 0; i < source_num; i++){
			//printf("%d: id = %d, exist = %d, size = %I64u \n", i, id[i], exist[id[i]], files[id[i]].size);
			if (files[id[i]].size > 0){
				if (files[id[i]].size < io_size){
					len = (unsigned int)(files[id[i]].size);
				} else {
					len = io_size;
				}
				files[id[i]].size -= len;
				if (!ReadFile(files[id[i]].hFile, buffer + (unit_size * i), len, &rv, NULL)){
					print_win32_err();
					printf("ReadFile, data file %d\n", id[i]);
					err = 1;
					goto error_end;
				} else if (len != rv){
					printf("ReadFile, data file %d, %d, %d\n", id[i], len, rv);
					err = 1;
					goto error_end;
				}
				if (len < io_size)
					memset(buffer + (unit_size * i + len), 0, io_size - len);
				// ソース・ブロックのチェックサムを計算する
				checksum4(buffer + (unit_size * i), buffer + (unit_size * i + io_size), io_size);
			} else {
				// ソース・ブロックの値が全て 0 なら、チェックサムも 0 になる。
				memset(buffer + (unit_size * i), 0, unit_size);
			}
		}
		// バッファーに読み込んだサイズ
		if (block_left < io_size){
			len = (unsigned int)block_left;
		} else {
			len = io_size;
		}
		block_left -= len;

		// 失われたソース・ブロックごとに
		block_recover = 0;
		for (i = 0; i < source_num; i++){
			if (id[i] >= source_num){ // パリティ・ブロックで補った部分
				/*	without strip mining
				memset(block, 0, unit_size);	// ソース・ブロックを 0で埋める
				// 失われたソース・ブロックを復元していく
				for (j = 0; j < source_num; j++) {
					factor = mat[source_num * block_recover + j];
					if (factor == 1){
						galois_region_xor(buffer + (unit_size * j), block, unit_size);
					} else if (factor != 0){
						galois_region_multiply(buffer + (unit_size * j), block, unit_size, factor);
					}
				}
				*/
				length = CHUNK_SIZE;
				for (k = 0; k < chunk_count; k++){	// chunk の番号
					offset = k * CHUNK_SIZE;
					if (offset + length >= unit_size){	// 最後の chunk なら
						length = unit_size - offset;
						k = chunk_count;
					}

					memset(block + offset, 0, length);	// ソース・ブロックを 0で埋める
					// 失われたソース・ブロックを復元していく
					for (j = 0; j < source_num; j++) {
						factor = mat[source_num * block_recover + j];
						if (factor == 1){
							galois_region_xor(buffer + (unit_size * j + offset), block + offset, length);
						} else if (factor != 0){
							galois_region_multiply(buffer + (unit_size * j + offset), block + offset, length, factor);
						}
					}
				}

				// 経過表示
				prog_num++;
				if (GetTickCount() - time_last >= UPDATE_TIME){
					if (print_progress((prog_num * 1000) / prog_base)){
						err = 2;
						goto error_end;
					}
					time_last = GetTickCount();
				}

				// 復元されたソース・ブロックのチェックサムを検証する
				checksum4(block, (unsigned char *)&rv, io_size);
				if (memcmp(block + io_size, &rv, HASH_SIZE) != 0){
					printf("checksum mismatch, recovered data file %d\n", i);
					err = 1;
					goto error_end;
				}
				block_recover++;
				// ソース・ファイルに書き込むサイズ
				if (files[i].size < len){
					len2 = (unsigned int)(files[i].size);
				} else {
					len2 = len;
				}
				files[i].size -= len2;
				// ソース・ファイルへ書き込む
				if (!WriteFile(files[i].hFile, block, len2, &rv, NULL)){
					print_win32_err();
					printf("WriteFile, data file %d\n", i);
					err = 1;
					goto error_end;
				}
			}
		}
	}
	print_progress_done();	// 改行して行の先頭に戻しておく

error_end:
	if (block)
		free(block);
	if (id)
		free(id);
	if (mat)
		free(mat);
	// gflib のテーブルを解放する
	galois_free_tables();
	return err;
}

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

// PAR 1.0 のヘッダーを作成する
int set_header(
	unsigned char *buf,
	file_ctx *files,	// ソース・ブロックのハンドルとサイズ
	int source_num,		// ソース・ブロックの数
	int file_num,		// ソース・ファイルの数
	wchar_t *list_buf,	// ソース・ファイルのリスト
	int list_len)		// ファイル・リストの文字数
{
	unsigned char hash[16], hash16[16], set_hash[16 * 256];
	unsigned char hash0[16] = {0xd4, 0x1d, 0x8c, 0xd9, 0x8f, 0x00, 0xb2, 0x04,
							0xe9, 0x80, 0x09, 0x98, 0xec, 0xf8, 0x42, 0x7e};
	wchar_t file_path[MAX_LEN], *file_name;
	int i, off, list_off, len, num = 0;
	__int64 file_size, total_file_size, prog_now;
	WIN32_FILE_ATTRIBUTE_DATA AttrData;

	memset(buf, 0, 96);
	// Identification String
	buf[0] = 'P';
	buf[1] = 'A';
	buf[2] = 'R';
	// Version Number
	i = 0x00010000; // PAR 1.0
	memcpy(buf + 8, &i, 4);
	// "program"-"version"-"subversion"-"subsubversion".
	buf[12] = PRODUCT_VERSION & 0xF;		// subsubversion
	buf[13] = (PRODUCT_VERSION >> 4) & 0xF;	// subversion
	buf[14] = PRODUCT_VERSION >> 8;			// version, ちなみに par-cmdline の最終バージョンは 0.9.0 ?
	buf[15] = 2;	// program, 1=Mirror, 2=PAR, 3=SmartPar, 0&4～=Unknown client
	// control hash の 16バイトは最後に書き込む
	// set hash の 16バイトは後で書き込む
	// volume number はパリティ・ブロックごとに変更する
	// number of files
	memcpy(buf + 56, &file_num, 4);
	// start offset of the file list
	buf[64] = 0x60;
	// file list size の 8バイトは後で書き込む
	// start offset of the data の 8バイトは後で書き込む
	// data size はパリティ・ブロックならブロック・サイズを書き込む

	// 合計ファイル・サイズを求める
	total_file_size = 0;
	prog_now = 0;
	list_off = 0;
	while (list_off < list_len){
		// ファイル名を取得する
		file_name = list_buf + list_off;	// サブ・ディレクトリを含まないのでそのまま使う
		list_off += wcslen(file_name) + 1;

		wcscpy(file_path, base_dir);
		wcscat(file_path, file_name);
		if (!GetFileAttributesEx(file_path, GetFileExInfoStandard, &AttrData)){
			print_win32_err();
			printf_cp("cannot open input file, %s\n", file_name);
			return 0;
		}
		file_size = ((__int64)(AttrData.nFileSizeHigh) << 32) | (__int64)(AttrData.nFileSizeLow);
		total_file_size += file_size;
	}

	// ここから file list
	off = 0x0060;
	list_off = 0;
	while (list_off < list_len){
		// ファイル名を取得する
		file_name = list_buf + list_off;	// サブ・ディレクトリを含まないのでそのまま使う
		len = wcslen(file_name);
		list_off += (len + 1);
		len *= 2;	// 文字数からバイト数にする

		// entry size
		i = 0x38 + len;
		memcpy(buf + off, &i, 4);
		memset(buf + off + 4, 0, 4);
		off += 8;
		// status field
		wcscpy(file_path, base_dir);
		wcscat(file_path, file_name);
		if (!GetFileAttributesEx(file_path, GetFileExInfoStandard, &AttrData)){
			print_win32_err();
			printf_cp("cannot open input file, %s\n", file_name);
			return 0;
		}
		file_size = ((__int64)(AttrData.nFileSizeHigh) << 32) | (__int64)(AttrData.nFileSizeLow);
		if (file_size > 0){
			i = 1;
		} else {
			i = 0; // 空のファイルはパリティ計算に含めない
		}
		memcpy(buf + off, &i, 4);
		memset(buf + off + 4, 0, 4);
		off += 8;
		// size
		memcpy(buf + off, &file_size, 8);
		off += 8;
		// MD5 hash
		if (file_size > 0){
			// パリティ・ブロック計算用に開く
			files[num].size = file_size;
			files[num].hFile = CreateFile(file_path, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
			if (files[num].hFile == INVALID_HANDLE_VALUE){
				print_win32_err();
				printf_cp("cannot open input file, %s\n", file_name);
				return 0;
			}
			// MD5 を計算する
			i = file_md5_total(files[num].hFile, file_size, hash, hash16, total_file_size, &prog_now);
			if (i != 0){
				if (i < 0)
					return i;
				printf_cp("file_md5_total, %s\n", file_name);
				return 0;
			}
		} else {
			memcpy(hash, hash0, 16);
		}
		memcpy(buf + off, hash, 16);
		off += 16;
		if (file_size > 0)
			memcpy(set_hash + (num * 16), hash, 16);
		// 16k MD5 hash
		if (file_size > 16384)
			memcpy(hash, hash16, 16);
		memcpy(buf + off, hash, 16);
		off += 16;
		// filename
		memcpy(buf + off, file_name, len);
		off += len;

		if (file_size > 0)
			num++;
	}

	// set hash をここで書き込む
	data_md5(set_hash, source_num * 16, hash);
	memcpy(buf + 32, hash, 16);
	// file list size をここで書き込む
	i = off - 96;
	memcpy(buf + 72, &i, 4);
	// start offset of the data をここで書き込む
	memcpy(buf + 80, &off, 4);

	// 最後にファイルのハッシュ値を計算する
	data_md5(buf + 32, off - 32, hash);
	memcpy(buf + 16, hash, 16);

	return off;
}

// インデックス・ファイルを作り直す
int recreate_par(
	wchar_t *file_path,		// PAR ファイルのパス
	unsigned char *pxx_buf,	// PXX ファイルのヘッダー
	unsigned char *buf)		// 作業バッファー
{
	unsigned char hash[16];
	int data_off, rv;
	HANDLE hFileWrite;

	// start offset of the data を読み取る
	memcpy(&data_off, pxx_buf + 80, 4);
	memcpy(buf, pxx_buf, data_off); // ヘッダー部分をコピーする

	// volume number を 0にする
	memset(buf + 48, 0, 8);
	// data size を 0にする
	memset(buf + 88, 0, 8);
	// control hash を計算しなおす
	data_md5(buf + 32, data_off - 32, hash);
	memcpy(buf + 16, hash, 16);

	// ファイルに書き込む
	hFileWrite = CreateFile(file_path, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
	if (hFileWrite == INVALID_HANDLE_VALUE)
		return 1;
	if (!WriteFile(hFileWrite, buf, data_off, &rv, NULL)){
		CloseHandle(hFileWrite);
		return 1;
	}
	CloseHandle(hFileWrite);
	return 0;
}

// PAR 1.0 のヘッダーから値を読み取る
int get_header(
	wchar_t *file_name,
	HANDLE hFile,
	unsigned char *buf,
	int buf_size,
	unsigned char *set_hash,	// NULL でなければ set hash、同じ構成の PXX かどうかを識別するため
	int *volume_num,			// リカバリ・ファイルの番号
	int *file_num,				// ソース・ファイルの数
	int *list_off,				// ソース・ファイルのリストの開始位置
	int *list_size,				// ソース・ファイルのリストのサイズ
	int *data_off,				// ソースの開始位置
	__int64 *data_size)			// ソースのサイズ
{
	unsigned char hash[16];
	int i;
	unsigned int rv, meta_data[7];

	if (buf_size <= 96)
		return 1;

	// ファイルの先頭に戻す
	if (SetFilePointer(hFile, 0, NULL, FILE_BEGIN) == INVALID_SET_FILE_POINTER){
		print_win32_err();
		return 1;
	}

	// ヘッダー部分を読み込む
	if (!ReadFile(hFile, buf, buf_size, &rv, NULL)){
		print_win32_err();
		printf("ReadFile, header\n");
		return 1;
	}

	// Identification String
	memcpy(&i, buf, 4);
	if (i != 5390672) // 'P' + 'A' * 256 + 'R' * 65536
		return 1;
	memcpy(&i, buf + 4, 4);
	if (i != 0)
		return 1;
	// Version Number
	memcpy(&i, buf + 8, 4);
	if ((i & 0xffff0000) != 0x00010000) // PAR v1.** にだけ対応する
		return 1;

	// 完全なリカバリ・ファイルを見つけた後だけ、検査結果を探す
	if (list_size == NULL){
		i = check_ini_state(0x10FF, meta_data, hFile);
		if (i == -2){	// 記録が無い、または状態が変化してる
			// control hash
			if (rv = file_md5_from32(file_name, hFile, hash))
				return rv;
			i = 0;
			if (memcmp(hash, buf + 16, 16) != 0)
				i = 1;
			// 今回の検査結果を書き込む
			write_ini_state(0x10FF, meta_data, i);
		}
		if (i != 0)
			return 1;

	} else {
		// control hash
		if (rv = file_md5_from32(file_name, hFile, hash))
			return rv;
		if (memcmp(hash, buf + 16, 16) != 0)
			return 1;
	}

	// set hash
	if (set_hash != NULL)
		memcpy(set_hash, buf + 32, 16);
	// volume number
	if (volume_num != NULL)
		memcpy(volume_num, buf + 48, 4);
	// number of files
	if (file_num != NULL)
		memcpy(file_num, buf + 56, 4);
	// start offset of the file list
	if (list_off != NULL)
		memcpy(list_off, buf + 64, 4);
	// file list size
	if (list_size != NULL)
		memcpy(list_size, buf + 72, 4);
	// start offset of the data
	if (data_off != NULL)
		memcpy(data_off, buf + 80, 4);
	// data size
	if (data_size != NULL)
		memcpy(data_size, buf + 88, 8);

	return 0;
}

// ファイル・リストからブロック数を読み取る
int get_source_num(
	unsigned char *buf,
	__int64 *block_size)
{
	int i, off, file_num, entry_size, status, source_num = 0;
	__int64 file_size;

	// number of files
	memcpy(&file_num, buf + 56, 4);
	// start offset of the file list
	memcpy(&off, buf + 64, 4);

	for (i = 0; i < file_num; i++){
		// entry size
		memcpy(&entry_size, buf + off, 4);
		// status field
		memcpy(&status, buf + (off + 8), 4);
		if ((status & 1) == 1){
			source_num++;
			// file size
			memcpy(&file_size, buf + (off + 16), 8);
			if (file_size > *block_size)
				*block_size = file_size;
		}

		off += entry_size;
	}

	printf("%3d \r", source_num);
	return source_num;
}

// コメントを読み取る
static void read_comment(
	wchar_t *par_comment,
	unsigned char *buf,
	int len)
{
	if (par_comment[0] != 0)
		return;

	if (len >= COMMENT_LEN * 2)
		len = (COMMENT_LEN - 1) * 2;
	memcpy(par_comment, buf, len);
	len /= 2;	// 文字数にする
	par_comment[len] = 0;	// 末尾を null 文字にする
	for (len = 0; len < COMMENT_LEN; len++){	// 表示する前に sanitalize する
		if (par_comment[len] == 0){
			break;
		} else if ((par_comment[len] <= 31) || (par_comment[len] == 127)){	// 制御文字を消す
			par_comment[len] = ' ';
		}
	}
}

// リカバリ・ファイルを作成する
int par1_create(
	int switch_p,			// インデックス・ファイルを作らない
	int source_num,			// ソース・ブロックの数
	__int64 block_size,		// ブロック・サイズ (最大ファイル・サイズ)
	int parity_num,			// パリティ・ブロックの数
	int first_vol,			// 最初のリカバリ・ファイル番号
	int file_num,			// ソース・ファイルの数
	wchar_t *list_buf,		// ソース・ファイルのリスト
	int list_len,			// ファイル・リストの文字数
	wchar_t *par_comment)	// コメント
{
	unsigned char *header_buf = NULL;
	wchar_t recovery_base[MAX_LEN];
	int err = 0, i;
	int int4, buf_size;
	unsigned int rv;
	HANDLE *par_hFile = NULL;
	file_ctx *files = NULL;
	PHMD5 *par_md5 = NULL;

	buf_size = 0x0060 + (0x0038 * file_num) + (list_len * 2); // 最大サイズ分確保しておく
	header_buf = malloc(buf_size);
	if (header_buf == NULL){
		printf("malloc, %d\n", buf_size);
		err = 1;
		goto error_end;
	}

	files = malloc(sizeof(file_ctx) * source_num);
	if (files == NULL){
		printf("malloc, %d\n", sizeof(file_ctx) * source_num);
		err = 1;
		goto error_end;
	}
	for (i = 0; i < source_num; i++)
		files[i].hFile = NULL;

	int4 = sizeof(HANDLE) * parity_num;
	if (int4 == 0)
		int4 = sizeof(HANDLE);
	par_hFile = malloc(int4);
	if (par_hFile == NULL){
		printf("malloc, %d\n", int4);
		err = 1;
		goto error_end;
	}
	par_hFile[0] = NULL;
	for (i = 1; i < parity_num; i++)
		par_hFile[i] = NULL;

	if (parity_num > 0){
		par_md5 = malloc(sizeof(PHMD5) * parity_num);
		if (par_md5 == NULL){
			printf("malloc, %d\n", sizeof(PHMD5) * parity_num);
			err = 1;
			goto error_end;
		}
		for (i = 0; i < parity_num; i++)
			Phmd5Begin(&(par_md5[i]));
	}

	printf("\n");
	print_progress_text(0, "Computing file hash");

	// ヘッダーを作成する
	buf_size = set_header(header_buf, files, source_num, file_num, list_buf, list_len);
	if (buf_size <= 0){
		if (buf_size < 0){
			err = 2;
		} else {
			err = 1;
		}
		goto error_end;
	}

	if (switch_p == 0){	// インデックス・ファイルを作るなら
		// コメントがあるならヘッダーを修正する
		if (int4 = wcslen(par_comment) * 2)
			memcpy(header_buf + 88, &int4, 4);

		// リカバリ・ファイル (*.PAR) を書き込む
		par_hFile[0] = CreateFile(recovery_file, GENERIC_READ | GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
		if (par_hFile[0] == INVALID_HANDLE_VALUE){
			print_win32_err();
			printf_cp("cannot create file, %s\n", recovery_file);
			par_hFile[0] = NULL;
			err = 1;
			goto error_end;
		}
		if (!WriteFile(par_hFile[0], header_buf, buf_size, &rv, NULL)){
			print_win32_err();
			printf("WriteFile, index file\n");
			err = 1;
			goto error_end;
		}
		// コメントがあるならコメントも書き込む
		if (int4 = wcslen(par_comment) * 2){
			if (!WriteFile(par_hFile[0], par_comment, int4, &int4, NULL)){
				print_win32_err();
				printf("WriteFile, index file\n");
				err = 1;
				goto error_end;
			}
			// MD5を計算しなおす
			if (err = file_md5_from32(NULL, par_hFile[0], header_buf + 16)){
				if (err == 1)
					printf("file_md5_from32\n");
				goto error_end;
			}
			if (SetFilePointer(par_hFile[0], 16, NULL, FILE_BEGIN) == INVALID_SET_FILE_POINTER){
				print_win32_err();
				err = 1;
				goto error_end;
			}
			if (!WriteFile(par_hFile[0], header_buf + 16, 16, &rv, NULL)){
				print_win32_err();
				printf("WriteFile, index file\n");
				err = 1;
				goto error_end;
			}
		}
		CloseHandle(par_hFile[0]);
		par_hFile[0] = NULL;
	}
	print_progress_done();
	if (parity_num == 0) // パリティ・ブロックを作らない場合はここで終わる
		goto creation_end;

	// ソース・ファイルから読み込む準備をする
	for (i = 0; i < source_num; i++){
		if (SetFilePointer(files[i].hFile, 0, NULL, FILE_BEGIN) == INVALID_SET_FILE_POINTER){
			print_win32_err();
			err = 1;
			goto error_end;
		}
	}

	// リカバリ・ファイル (*.PXX) の名前の基
	wcscpy(recovery_base, recovery_file);
	int4 = wcslen(recovery_base);
	recovery_base[int4 - 1] = 0;
	recovery_base[int4 - 2] = 0;
	print_progress_text(0, "Creating parity volume");

	// リカバリ・ファイル (*.PXX) に書き込む準備をする
	memcpy(header_buf + 88, &block_size, 8); // ブロック・サイズにする
	for (i = 0; i < parity_num; i++){
		int4 = first_vol + i;	// 作成する parity volume の番号にする
		swprintf(recovery_file, MAX_LEN, L"%s%02d", recovery_base, int4);
		par_hFile[i] = CreateFile(recovery_file, GENERIC_READ | GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
		if (par_hFile[i] == INVALID_HANDLE_VALUE){
			print_win32_err();
			par_hFile[i] = NULL;
			printf_cp("cannot create file, %s\n", recovery_file);

			// これまでに作ったリカバリ・ファイルを削除する
			if (switch_p == 0){
				wcscpy(recovery_file, recovery_base);
				wcscat(recovery_file, L"ar");
				DeleteFile(recovery_file);
			}
			for (int4 = 0; int4 < i; int4++){
				CloseHandle(par_hFile[int4]);
				par_hFile[int4] = NULL;
				swprintf(recovery_file, MAX_LEN, L"%s%02d", recovery_base, first_vol + int4);
				DeleteFile(recovery_file); // 途中までのリカバリ・ファイルを削除する
			}

			err = 1;
			goto error_end;
		}

		// ヘッダーを書き込む
		memcpy(header_buf + 48, &int4, 2); // volume number にする
		if (!WriteFile(par_hFile[i], header_buf, buf_size, &rv, NULL)){
			print_win32_err();
			printf("WriteFile, parity volume %d\n", int4);
			err = 1;
			goto error_end;
		}
		// ヘッダー部分からのハッシュ値を計算しかけておく
		Phmd5Process(&(par_md5[i]), header_buf + 32, buf_size - 32);
	}

	// パリティ・ブロックを作成する
	if (err = rs_encode(source_num, block_size, parity_num, first_vol - 1, files, par_hFile, par_md5)){
		if (err == 2){ // キャンセルされたのなら、作業中のファイルを削除する
			if (switch_p == 0){
				wcscpy(recovery_file, recovery_base);
				wcscat(recovery_file, L"ar");
				DeleteFile(recovery_file);
			}
			for (i = 0; i < parity_num; i++){
				CloseHandle(par_hFile[i]);
				par_hFile[i] = NULL;
				swprintf(recovery_file, MAX_LEN, L"%s%02d", recovery_base, first_vol + i);
				DeleteFile(recovery_file); // 途中までのリカバリ・ファイルを削除する
			}
		}
		goto error_end;
	}

	// リカバリ・ファイル (*.PXX) の MD5を計算しなおす
	for (i = 0; i < parity_num; i++){
		Phmd5End(&(par_md5[i])); // ハッシュ値の計算終了
		memcpy(header_buf + 16, par_md5[i].hash, 16);
		if (SetFilePointer(par_hFile[i], 16, NULL, FILE_BEGIN) == INVALID_SET_FILE_POINTER){
			print_win32_err();
			err = 1;
			goto error_end;
		}
		if (!WriteFile(par_hFile[i], header_buf + 16, 16, &rv, NULL)){
			print_win32_err();
			printf("WriteFile, parity volume %d\n", i);
			err = 1;
			goto error_end;
		}
	}

creation_end:
	printf("\nCreated successfully\n");
error_end:
	if (list_buf)
		free(list_buf);
	if (header_buf)
		free(header_buf);
	if (par_md5)
		free(par_md5);
	if (files){
		for (i = 0; i < source_num; i++){
			if (files[i].hFile != NULL)
				CloseHandle(files[i].hFile);
		}
		free(files);
	}
	if (par_hFile){
		for (i = 0; i < parity_num; i++){
			if (par_hFile[i] != NULL)
				CloseHandle(par_hFile[i]);
		}
		free(par_hFile);
	}
	return err;
}

// ソース・ファイルの破損や欠損を調べる
int par1_verify(
	int switch_b,			// 既存のファイルを別名にしてどかす
	int switch_p,			// インデックス・ファイルを作り直す
	wchar_t *par_comment)	// コメント
{
	char ascii_buf[MAX_LEN * 3], ascii_buf2[MAX_LEN * 3];
	unsigned char *header_buf = NULL, *header_buf2 = NULL, hash[16], set_hash[16];
	wchar_t file_name[MAX_LEN], file_path[MAX_LEN], find_path[MAX_LEN];
	wchar_t par_char[] = L"AR";
	int err = 0, i, j, bad_flag, find_flag, parity_flag, par_flag, exist[99];
	int len, dir_len, base_len, buf_size, entry_size, entry_size2, blk;
	int volume_num, volume_max, file_num, list_off, list_size, data_off;
	int buf_size2, volume_num2, file_num2, list_off2, data_off2;
	int parity_num, source_num, block_lost = 0, need_repair = 0, recovery_lost = 0;
	unsigned int rv, meta_data[7];
	__int64 file_size, file_size2, data_size, block_size = 0, total_file_size;
	HANDLE hFile = NULL, hFind;
	WIN32_FIND_DATA FindData;

	// リカバリ・ファイルの名前
	wcscpy(file_path, recovery_file);
	len = wcslen(file_path);
	file_path[len - 2] = 0; // 末尾の2文字を消去する
	if (file_path[len - 3] == 'p'){
		par_char[0] = 'a';
		par_char[1] = 'r';
	}
	// ディレクトリの長さ
	dir_len = len - 3;
	while (dir_len >= 0){
		if ((file_path[dir_len] == '\\') || (file_path[dir_len] == '/'))
			break;
		dir_len--;
	}
	dir_len++;
	base_len = wcslen(base_dir);

	// リカバリ・ファイルの一覧を表示する
	printf("PAR File list :\n");
	printf("         Size :  Filename\n");
	total_file_size = 0;
	parity_num = 0;
	volume_max = 0;
	for (i = 0; i <= 99; i++){
		if (i == 0){
			swprintf(find_path, MAX_LEN, L"%s%s", file_path, par_char);
			blk = 0;
		} else {
			swprintf(find_path, MAX_LEN, L"%s%02d", file_path, i);
			blk = 1;
		}
		// ファイルを開くことができるか、サイズを取得できるかを確かめる
		hFile = CreateFile(find_path, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
		if (hFile == INVALID_HANDLE_VALUE){
			continue;
		}
		// ファイルのサイズを取得する
		if (!GetFileSizeEx(hFile, (PLARGE_INTEGER)&file_size)){
			CloseHandle(hFile);
			continue;
		}
		CloseHandle(hFile);
		total_file_size += file_size;
		parity_num++;
		volume_max = i;
		// リカバリ・ファイルの名前
		utf16_to_cp(find_path + dir_len, ascii_buf);
		printf("%13I64d : \"%s\"\n", file_size, ascii_buf);
	}
	hFile = NULL;
	printf("\nPAR File total size\t: %I64d\n", total_file_size);
	printf("PAR File possible count\t: %d\n\n", parity_num);

	// 指定されたリカバリ・ファイルを開く
	hFile = CreateFile(recovery_file, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
	if (hFile == INVALID_HANDLE_VALUE){
		print_win32_err();
		printf("valid file is not found\n");
		err = 1;
		goto error_end;
	}
	// ファイルのサイズを取得する
	if (!GetFileSizeEx(hFile, (PLARGE_INTEGER)&file_size)){
		print_win32_err();
		err = 1;
		goto error_end;
	}

	// ヘッダー用のバッファーを確保する
	buf_size = 0x0060 + ((0x0038 + (260 * 2)) * 256) + (COMMENT_LEN * 2); // 最大サイズ
	if ((__int64)buf_size > file_size)
		buf_size = (unsigned int)file_size;
	header_buf = malloc(buf_size);
	if (header_buf == NULL){
		printf("malloc, %d\n", buf_size);
		err = 1;
		goto error_end;
	}

	// PAR や PXX ファイルとして正しいかを調べる
	par_flag = 0;
	par_comment[0] = 0;
	for (i = 0; i < 99; i++)
		exist[i] = -1;
	if (rv = get_header(NULL, hFile, header_buf, buf_size, set_hash,
			&volume_num, &file_num, &list_off, &list_size, &data_off, &data_size)){
		if (rv == 2){	// キャンセル
			err = 2;
			goto error_end;
		}
		volume_num = -1;
		par_flag |= 2;
	} else {
		// 各ブロックが存在するかを記録していく
		j = get_source_num(header_buf, &block_size);
		check_ini_file(set_hash, j);	// 前回の検査結果が存在するかどうか
		if (volume_num == 0){
			par_flag |= 1;
			rv = (int)data_size;
			if (buf_size < (int)data_size)
				rv = buf_size;
			if (rv > 1)
				read_comment(par_comment, header_buf + data_off, rv);
		}
	}
	CloseHandle(hFile);
	hFile = NULL;

	// 他のリカバリ・ファイルのヘッダー用のバッファーを確保する
	if (volume_num == -1){
		buf_size2 = 0x0060 + ((0x0038 + (260 * 2)) * 256) + (COMMENT_LEN * 2); // 最大サイズ
	} else {
		buf_size2 = 0x0060 + ((0x0038 + (260 * 2)) * file_num);
	}
	header_buf2 = malloc(buf_size2);
	if (header_buf2 == NULL){
		printf("malloc, %d\n", buf_size2);
		err = 1;
		goto error_end;
	}

	// 他のリカバリ・ファイルも調べる
	printf("Loading PAR File:\n");
	printf(" Block Status   :  Filename\n");
	fflush(stdout);
	parity_num = 0;
	for (i = 0; i <= 99; i++){
		if (err = cancel_progress())	// キャンセル処理
			goto error_end;
		if (i == 0){
			swprintf(find_path, MAX_LEN, L"%s%s", file_path, par_char);
			blk = 0;
		} else {
			swprintf(find_path, MAX_LEN, L"%s%02d", file_path, i);
			blk = 1;
		}
		utf16_to_cp(find_path + dir_len, ascii_buf);

		// 既にチェック済みなら
		if ((_wcsicmp(recovery_file + dir_len, find_path + dir_len) == 0) && (volume_num != -1)){
			if ((volume_num > 0) && (volume_num <= 99)){
				if (exist[volume_num - 1] == -1)	// 係数が重複してるのは使えない
					parity_num++;
				exist[volume_num - 1] = i;
				if (volume_max < volume_num)
					volume_max = volume_num;
			}
			if ((par_flag & 2) != 0){
				printf(" 0 / %d Damaged  : \"%s\"\n", blk, ascii_buf);
				recovery_lost++;
			} else if (i == volume_num){
				printf(" %d / %d Good     : \"%s\"\n", blk, blk, ascii_buf);
			} else {
				blk = (volume_num > 0) ? 1 : 0;
				printf(" %d / %d Misnamed : \"%s\"\n", blk, blk, ascii_buf);
				recovery_lost++;	// リカバリ・ファイルの破損は修復必要とは判定しない
			}
			continue;
		}

		// ファイルを開く
		hFile = CreateFile(find_path, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
		if (hFile == INVALID_HANDLE_VALUE)
			continue;
		// PAR や PXX ファイルとして正しいかを調べる
		bad_flag = 0;
		if (volume_num == -1){
			if (rv = get_header(find_path + dir_len, hFile, header_buf, buf_size, set_hash,
					&volume_num, &file_num, &list_off, &list_size, &data_off, &data_size)){
				if (rv == 2){	// キャンセル
					err = 2;
					goto error_end;
				}
				volume_num = -1;
				bad_flag = 1;
			} else {
				j = get_source_num(header_buf, &block_size);
				check_ini_file(set_hash, j);	// 前回の検査結果が存在するかどうか
				if (volume_num == 0){
					par_flag |= 1;
					rv = (int)data_size;
					if (buf_size < (int)data_size)
						rv = buf_size;
					if (rv > 1)
						read_comment(par_comment, header_buf + data_off, (int)data_size);
				} else if (volume_num <= 99){
					parity_num++;
					exist[volume_num - 1] = i;
					if (volume_max < volume_num)
						volume_max = volume_num;
				}
				if (i != volume_num){	// ブロック番号と拡張子が異なるなら
					bad_flag = 3;
					blk = (volume_num > 0) ? 1 : 0;
				}
			}
		} else {
			if (rv = get_header(find_path + dir_len, hFile, header_buf2, buf_size2, hash,
					&volume_num2, &file_num2, &list_off2, NULL, &data_off2, &data_size)){
				if (rv == 2){	// キャンセル
					err = 2;
					goto error_end;
				}
				bad_flag = 1;
			} else { // 同じ構成の PXX かを確認する
				if (memcmp(hash, set_hash, 16) != 0)
					bad_flag = 2;
				if ((file_num2 != file_num) || (list_off2 != list_off) || (data_off2 != data_off))
					bad_flag = 2;
				if ((block_size) && (volume_num2 > 0)){
					if (data_size != block_size)
						bad_flag = 2;
				}
				if (!bad_flag){
					if (volume_num2 == 0){
						par_flag |= 1;
						rv = (int)data_size;
						if (buf_size2 < (int)data_size)
							rv = buf_size2;
						if (rv > 1)
							read_comment(par_comment, header_buf2 + data_off2, rv);
					} else if (volume_num2 <= 99){
						if (exist[volume_num2 - 1] == -1)	// 係数が重複してるのは使えない
							parity_num++;
						exist[volume_num2 - 1] = i;
						if (volume_max < volume_num2)
							volume_max = volume_num2;
					}
					if (i != volume_num2){	// ブロック番号と拡張子が異なるなら
						bad_flag = 3;
						blk = (volume_num2 > 0) ? 1 : 0;
					}
				}
			}
		}
		switch (bad_flag){
		case 0:
			printf(" %d / %d Good     : \"%s\"\n", blk, blk, ascii_buf);
			break;
		case 1:
			printf(" 0 / %d Damaged  : \"%s\"\n", blk, ascii_buf);
			recovery_lost++;
			break;
		case 2:
			printf(" %d / %d Useless  : \"%s\"\n", blk, blk, ascii_buf);
			recovery_lost++;
			break;
		case 3:
			printf(" %d / %d Misnamed : \"%s\"\n", blk, blk, ascii_buf);
			recovery_lost++;	// リカバリ・ファイルの破損は修復必要とは判定しない
			break;
		}
		CloseHandle(hFile);
		hFile = NULL;

		fflush(stdout);
	}

	if (volume_num == -1){ // Recovery Set が見つからなければここで終わる
		printf("valid file is not found\n");
		err = 1;
		goto error_end;
	}
	// セット・ハッシュとプログラムのバージョンを表示する
	printf("\nSet Hash: ");
	print_hash(set_hash);
	memcpy(&rv, header_buf + 12, 4);
	printf("\nCreator : ");
	switch (rv >> 24){
	case 1:	// Mirror
		printf("Mirror");
		break;
	case 2:	// PAR
		printf("PAR");
		break;
	case 3:	// SmartPar
		printf("SmartPar");
		break;
	default:
		printf("Unknown client %d", rv >> 24);
	}
	printf(" version %d.%d.%d\n", (rv >> 16) & 0xFF, (rv >> 8) & 0xFF, rv & 0xFF);
	if ((par_comment[0] != 0) && (!utf16_to_cp(par_comment, ascii_buf)))
		printf("Comment : %s\n", ascii_buf); // コメントをユニコードから戻せた場合だけ表示する

	if (((par_flag & 1) == 0) && (switch_p)){ // PAR ファイルを作り直す
		swprintf(find_path, MAX_LEN, L"%s%s", file_path, par_char);
		get_temp_name(find_path, file_name);
		if (recreate_par(file_name, header_buf, header_buf2) == 0){
			if (replace_file(find_path, file_name, switch_b) == 0){	// 破損したPARファイルが存在するならどかす
				utf16_to_cp(find_path + dir_len, ascii_buf);
				printf("\nRestored file :\n");
				printf("         Size :  Filename\n");
				printf("%13u : \"%s\"\n", data_off, ascii_buf);
			} else {
				DeleteFile(file_name);
			}
		}
	}
	free(header_buf2);
	header_buf2 = NULL;
	printf("\nParity Volume count\t: %d\n", volume_max);
	printf("Parity Volume found\t: %d\n", parity_num);
	fflush(stdout);

	// ファイル・リストを表示する
	printf("\nInput File list : %d\n", file_num);
	printf("         Size B :  Filename\n");
	total_file_size = 0;
	source_num = 0;
	list_off2 = list_off;
	for (i = 0; i < file_num; i++){
		// entry size
		memcpy(&entry_size, header_buf + list_off2, 4);
		// status field
		memcpy(&parity_flag, header_buf + (list_off2 + 8), 2);
		parity_flag &= 0x01; // bit 0 = file is saved in the parity volume set
		if (parity_flag){
			source_num++;
			blk = 1;
		} else {
			blk = 0;
		}
		// size
		memcpy(&data_size, header_buf + (list_off2 + 16), 8);
		total_file_size += data_size;
		// filename
		len = (entry_size - 56) / 2;
		if (len >= MAX_LEN)
			len = MAX_LEN - 1;
		memcpy(file_name, header_buf + (list_off2 + 56), len * 2);
		file_name[len] = 0;
		utf16_to_cp(file_name, ascii_buf);
		// ファイル名が有効かどうか確かめる
		j = sanitize_filename(file_name);
		if (j != 0){
			if (j == 16){
				printf("filename is not valied, %s\n", ascii_buf);
				err = 1;
				goto error_end;
			}
			utf16_to_cp(file_name, ascii_buf);
		}
		printf("%13I64u %d : \"%s\"\n", data_size, blk, ascii_buf);
		list_off2 += entry_size; // 次のエントリー位置にずらす
	}
	printf("\nData File count : %d\n", source_num);
	printf("Max file size\t: %I64d\n", block_size);
	printf("Total data size : %I64d\n", total_file_size);

	// ソース・ファイルが存在していて正しいかを調べる
	printf("\nVerifying Input File   :\n");
	printf("         Size Status   :  Filename\n");
	fflush(stdout);
	for (i = 0; i < file_num; i++){
		if (err = cancel_progress())	// キャンセル処理
			goto error_end;
		find_flag = bad_flag = parity_flag = 0;

		// entry size
		memcpy(&entry_size, header_buf + list_off, 4);
		// status field
		memcpy(&parity_flag, header_buf + (list_off + 8), 2);
		parity_flag &= 0x01; // bit 0 = file is saved in the parity volume set
		// size
		memcpy(&data_size, header_buf + (list_off + 16), 8);
		file_size = 0;
		// filename
		len = (entry_size - 56) / 2;
		if (len >= MAX_LEN)
			len = MAX_LEN - 1;
		memcpy(file_name, header_buf + (list_off + 56), len * 2);
		file_name[len] = 0;
		utf16_to_cp(file_name, ascii_buf);
		// ファイル名が有効かどうか確かめる
		wcscpy(file_path, file_name);
		j = sanitize_filename(file_path);
		if (j != 0){
			wcscpy(file_name, file_path);
			utf16_to_cp(file_name, ascii_buf);
		}
		// ファイル名を基準ディレクトリに追加してパスにする
		if (base_len + wcslen(file_name) >= MAX_LEN - ADD_LEN){
			printf("filename is too long\n");
			err = 1;
			goto error_end;
		}
		wcscpy(file_path, base_dir);
		wcscpy(file_path + base_len, file_name);

		// ファイルが存在するか
		hFile = CreateFile(file_path, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
		if (hFile != INVALID_HANDLE_VALUE){ // ファイルが存在するなら内容を確認する
			// 検査結果の記録があるかどうか
			bad_flag = check_ini_state(i, meta_data, hFile);
			memcpy(&file_size, meta_data, 8);
			if (bad_flag == -1){	// ファイル・サイズが不明なら消失扱いにする
				bad_flag = 1;
			} else if (bad_flag == -2){	// 検査結果が無かった場合
				bad_flag = 0;
				if (file_size >= data_size){ // 末尾にゴミが付いてないか調べる
					if (file_size > data_size)
						bad_flag = 2;
					if (data_size > 0){ // 16k MD5 hash が一致するか確かめる
						if (file_md5_16k(hFile, data_size, hash)){
							bad_flag = 1;
						} else if (memcmp(hash, header_buf + (list_off + 40), 16) != 0){
							bad_flag = 1;
						} else if (data_size > 16384){ // MD5 hash が一致するか確かめる
							if (rv = file_md5(file_name, hFile, data_size, hash)){
								if (rv == 2){
									err = 2;
									goto error_end;
								}
								bad_flag = 1;
							} else if (memcmp(hash, header_buf + (list_off + 24), 16) != 0){
								bad_flag = 1;
							}
							// 16k MD5 hash が一致した時だけ検査結果を記録する
							write_ini_state(i, meta_data, bad_flag);
						}
					}
				} else if (file_size < data_size){
					bad_flag = 1;
				}
			}
			CloseHandle(hFile);
			hFile = NULL;
			if (!bad_flag)
				find_flag = 3;
		}

		// ファイルが存在しないか、不完全だった場合は、サイズとハッシュ値で検索する
		if ((data_size) && ((hFile == INVALID_HANDLE_VALUE) || (bad_flag))){
			wcscpy(find_path, base_dir);
			wcscpy(find_path + base_len, L"*");
			hFind = FindFirstFile(find_path, &FindData);
			if (hFind != INVALID_HANDLE_VALUE){
				do {
					if ((FindData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) == 0){ // フォルダは無視する
						find_flag = 1;
						file_size2 = ((__int64)(FindData.nFileSizeHigh) << 32) | (__int64)(FindData.nFileSizeLow);
						if (file_size2 != data_size){ // ファイル・サイズが一致しなければ
							find_flag = 0;
						} else { // ファイル名がリスト上にあれば除外する
							memcpy(&list_off2, header_buf + 64, 4);
							for (j = 0; j < file_num; j++){
								// entry size
								memcpy(&entry_size2, header_buf + list_off2, 4);
								// filename
								len = (entry_size2 - 56) / 2;
								if (len >= MAX_LEN)
									len = MAX_LEN - 1;
								memcpy(file_name, header_buf + (list_off2 + 56), len * 2);
								file_name[len] = 0;
								sanitize_filename(file_name);
								if (_wcsicmp(file_name, FindData.cFileName) == 0){
									find_flag = 0;
									break;
								}
								list_off2 += entry_size2; // 次のエントリー位置にずらす
							}
						}
						// リスト上に無くてファイル・サイズが一致すれば
						if ((find_flag != 0) && (base_len + wcslen(FindData.cFileName) < MAX_LEN)){
							wcscpy(find_path + base_len, FindData.cFileName);
							hFile = CreateFile(find_path, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
							if (hFile != INVALID_HANDLE_VALUE){
								j = check_ini_state(i, meta_data, hFile);
								if (j == -2){	// 検査結果が無かった場合
									if (data_size > 0){ // 16k MD5 hash が一致するか確かめる
										if (file_md5_16k(hFile, data_size, hash)){
											find_flag = 0;
										} else if (memcmp(hash, header_buf + (list_off + 40), 16) != 0){
											find_flag = 0;
										} else if (data_size > 16384){ // MD5 hash が一致するか確かめる
											j = 0;
											if (rv = file_md5(FindData.cFileName, hFile, data_size, hash)){
												if (rv == 2){
													err = 2;
													goto error_end;
												}
												find_flag = 0;
												j = 1;
											} else if (memcmp(hash, header_buf + (list_off + 24), 16) != 0){
												find_flag = 0;
												j = 1;
											}
											// 16k MD5 hash が一致した時だけ検査結果を記録する
											write_ini_state(i, meta_data, j);
										}
									}
								} else if (j != 0){	// 完全以外なら
									find_flag = 0;
								}
								CloseHandle(hFile);
								hFile = NULL;
								if (find_flag){ // 異なる名前だが同じ内容のファイルを発見した
									utf16_to_cp(FindData.cFileName, ascii_buf2);
									break;
								}
							} else {
								find_flag = 0;
							}
						}
					}
				} while (FindNextFile(hFind, &FindData)); // 次のファイルを検索する
				FindClose(hFind);
			}
		}
		if (find_flag == 3){
			printf("            = Complete : \"%s\"\n", ascii_buf);
		} else if (find_flag == 1){
			need_repair++;
			if (bad_flag){
				printf("%13I64u Damaged~ : \"%s\"\n", file_size, ascii_buf);
			} else {
				printf("            - Missing~ : \"%s\"\n", ascii_buf);
			}
			printf("%13I64u Misnamed : \"%s\"\n", data_size, ascii_buf2);
		} else if (bad_flag == 2){
			need_repair++;
			printf("%13I64u Appended : \"%s\"\n", file_size, ascii_buf);
		} else if (bad_flag){
			if (parity_flag)
				block_lost++;
			if (data_size == 0)
				need_repair++;
			printf("%13I64u Damaged  : \"%s\"\n", file_size, ascii_buf);
		} else {
			if (parity_flag)
				block_lost++;
			if (data_size == 0)
				need_repair++;
			printf("            - Missing  : \"%s\"\n", ascii_buf);
		}

		fflush(stdout);
		list_off += entry_size; // 次のエントリー位置にずらす
	}
	printf("\nData File lost\t: %d\n\n", block_lost);
	if ((block_lost == 0) && (need_repair == 0))
		printf("All Files Complete\n");
	if (recovery_lost > 0){	// 不完全なリカバリ・ファイルがあるなら
		err = 256;
		printf("%d PAR File(s) Incomplete\n", recovery_lost);
	}

	// 修復する必要があるかどうか
	if ((block_lost > 0) || (need_repair > 0)){
		err |= 4;
		if (need_repair > 0){
			printf("Ready to rename %d file(s)\n", need_repair);
			err |= 32;
		}
		if (block_lost > 0){
			if (block_lost > parity_num){
				printf("Need %d more volume(s) to repair %d file(s)\n", block_lost - parity_num, block_lost);
				err |= 8;
			} else {
				printf("Ready to repair %d file(s)\n", block_lost);
				err |= 128;
			}
		}
	}

error_end:
	close_ini_file();
	if (hFile != NULL)
		CloseHandle(hFile);
	if (header_buf)
		free(header_buf);
	if (header_buf2)
		free(header_buf2);
	return err;
}

// ソース・ファイルの破損や欠損を修復する
int par1_repair(
	int switch_b,			// 既存のファイルを別名にしてどかす
	int switch_p,			// インデックス・ファイルを作り直す
	wchar_t *par_comment)	// コメント
{
	char ascii_buf[MAX_LEN * 3], ascii_buf2[MAX_LEN * 3];
	unsigned char *header_buf = NULL, *header_buf2 = NULL, hash[16], set_hash[16];
	wchar_t file_name[MAX_LEN * 2], file_path[MAX_LEN], find_path[MAX_LEN * 2];
	wchar_t par_char[] = L"AR";
	int err = 0, i, j, bad_flag, find_flag, parity_flag, par_flag, exist[256];
	int len, dir_len, base_len, buf_size, entry_size, entry_size2, blk;
	int volume_num, volume_max, file_num, list_off, list_size, data_off;
	int buf_size2, volume_num2, file_num2, list_off2, data_off2;
	int parity_num, source_num, block_lost = 0, need_repair = 0, recovery_lost = 0;
	int parity_max, block_max;
	unsigned int rv, meta_data[7];
	__int64 file_size, file_size2, data_size, block_size = 0, total_file_size;
	HANDLE hFile = NULL, hFind;
	WIN32_FIND_DATA FindData;
	file_ctx *files = NULL, *tmp_p;

	// リカバリ・ファイルの名前
	wcscpy(file_path, recovery_file);
	len = wcslen(file_path);
	file_path[len - 2] = 0; // 末尾の2文字を消去する
	if (file_path[len - 3] == 'p'){
		par_char[0] = 'a';
		par_char[1] = 'r';
	}
	// ディレクトリの長さ
	dir_len = len - 3;
	while (dir_len >= 0){
		if ((file_path[dir_len] == '\\') || (file_path[dir_len] == '/'))
			break;
		dir_len--;
	}
	dir_len++;
	base_len = wcslen(base_dir);

	// リカバリ・ファイルの一覧を表示する
	printf("PAR File list :\n");
	printf("         Size :  Filename\n");
	total_file_size = 0;
	parity_num = 0;
	volume_max = 0;
	for (i = 0; i <= 99; i++){
		if (i == 0){
			swprintf(find_path, MAX_LEN, L"%s%s", file_path, par_char);
			blk = 0;
		} else {
			swprintf(find_path, MAX_LEN, L"%s%02d", file_path, i);
			blk = 1;
		}
		// ファイルを開くことができるか、サイズを取得できるかを確かめる
		hFile = CreateFile(find_path, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
		if (hFile == INVALID_HANDLE_VALUE){
			continue;
		}
		// ファイルのサイズを取得する
		if (!GetFileSizeEx(hFile, (PLARGE_INTEGER)&file_size)){
			CloseHandle(hFile);
			continue;
		}
		CloseHandle(hFile);
		total_file_size += file_size;
		parity_num++;
		volume_max = i;
		// リカバリ・ファイルの名前
		utf16_to_cp(find_path + dir_len, ascii_buf);
		printf("%13I64d : \"%s\"\n", file_size, ascii_buf);
	}
	hFile = NULL;
	printf("\nPAR File total size\t: %I64d\n", total_file_size);
	printf("PAR File possible count\t: %d\n\n", parity_num);

	// 指定されたリカバリ・ファイルを開く
	hFile = CreateFile(recovery_file, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
	if (hFile == INVALID_HANDLE_VALUE){
		print_win32_err();
		printf("valid file is not found\n");
		err = 1;
		goto error_end;
	}
	// ファイルのサイズを取得する
	if (!GetFileSizeEx(hFile, (PLARGE_INTEGER)&file_size)){
		print_win32_err();
		err = 1;
		goto error_end;
	}

	// ヘッダー用のバッファーを確保する
	buf_size = 0x0060 + ((0x0038 + (260 * 2)) * 256) + (COMMENT_LEN * 2); // 最大サイズ
	if ((__int64)buf_size > file_size)
		buf_size = (unsigned int)file_size;
	header_buf = malloc(buf_size);
	if (header_buf == NULL){
		printf("malloc, %d\n", buf_size);
		err = 1;
		goto error_end;
	}

	// PAR や PXX ファイルとして正しいかを調べる
	parity_max = 0;
	par_flag = 0;
	par_comment[0] = 0;
	for (i = 0; i < 256; i++)
		exist[i] = -1;
	if (rv = get_header(NULL, hFile, header_buf, buf_size, set_hash,
			&volume_num, &file_num, &list_off, &list_size, &data_off, &data_size)){
		if (rv == 2){	// キャンセル
			err = 2;
			goto error_end;
		}
		volume_num = -1;
		par_flag |= 2;
	} else {
		// 各ブロックが存在するかを記録していく
		block_max = get_source_num(header_buf, &block_size);
		check_ini_file(set_hash, block_max);	// 前回の検査結果が存在するかどうか
		if (volume_num == 0){
			par_flag |= 1;
			rv = (int)data_size;
			if (buf_size < (int)data_size)
				rv = buf_size;
			if (rv > 1)
				read_comment(par_comment, header_buf + data_off, rv);
		}
	}
	CloseHandle(hFile);
	hFile = NULL;

	// 他のリカバリ・ファイルのヘッダー用のバッファーを確保する
	if (volume_num == -1){
		buf_size2 = 0x0060 + ((0x0038 + (260 * 2)) * 256) + (COMMENT_LEN * 2); // 最大サイズ
	} else {
		buf_size2 = 0x0060 + ((0x0038 + (260 * 2)) * file_num);
	}
	header_buf2 = malloc(buf_size2);
	if (header_buf2 == NULL){
		printf("malloc, %d\n", buf_size2);
		err = 1;
		goto error_end;
	}

	// 他のリカバリ・ファイルも調べる
	printf("Loading PAR File:\n");
	printf(" Block Status   :  Filename\n");
	fflush(stdout);
	parity_num = 0;
	for (i = 0; i <= 99; i++){
		if (err = cancel_progress())	// キャンセル処理
			goto error_end;
		if (i == 0){
			swprintf(find_path, MAX_LEN, L"%s%s", file_path, par_char);
			blk = 0;
		} else {
			swprintf(find_path, MAX_LEN, L"%s%02d", file_path, i);
			blk = 1;
		}
		utf16_to_cp(find_path + dir_len, ascii_buf);

		// 既にチェック済みなら
		if ((_wcsicmp(recovery_file + dir_len, find_path + dir_len) == 0) && (volume_num != -1)){
			if ((volume_num > 0) && (volume_num <= 99)){
				if (exist[block_max + volume_num - 1] == -1)	// 係数が重複してるのは使えない
					parity_num++;
				if (parity_max < volume_num)
					parity_max = volume_num;
				if (block_max + volume_num <= 256)
					exist[block_max + volume_num - 1] = i;
				if (volume_max < volume_num)
					volume_max = volume_num;
			}
			if ((par_flag & 2) != 0){
				printf(" 0 / %d Damaged  : \"%s\"\n", blk, ascii_buf);
				recovery_lost++;
			} else if (i == volume_num){
				printf(" %d / %d Good     : \"%s\"\n", blk, blk, ascii_buf);
			} else {
				blk = (volume_num > 0) ? 1 : 0;
				printf(" %d / %d Misnamed : \"%s\"\n", blk, blk, ascii_buf);
				recovery_lost++;	// リカバリ・ファイルの破損は修復必要とは判定しない
			}
			continue;
		}

		// ファイルを開く
		hFile = CreateFile(find_path, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
		if (hFile == INVALID_HANDLE_VALUE)
			continue;
		// PAR や PXX ファイルとして正しいかを調べる
		bad_flag = 0;
		if (volume_num == -1){
			if (rv = get_header(find_path + dir_len, hFile, header_buf, buf_size, set_hash,
					&volume_num, &file_num, &list_off, &list_size, &data_off, &data_size)){
				if (rv == 2){	// キャンセル
					err = 2;
					goto error_end;
				}
				volume_num = -1;
				bad_flag = 1;
			} else {
				block_max = get_source_num(header_buf, &block_size);
				check_ini_file(set_hash, block_max);	// 前回の検査結果が存在するかどうか
				if (volume_num == 0){
					par_flag |= 1;
					rv = (int)data_size;
					if (buf_size < (int)data_size)
						rv = buf_size;
					if (rv > 1)
						read_comment(par_comment, header_buf + data_off, (int)data_size);
				} else if (volume_num <= 99){
					parity_num++;
					if (parity_max < volume_num)
						parity_max = volume_num;
					if (block_max + volume_num <= 256)
						exist[block_max + volume_num - 1] = i;
					if (volume_max < volume_num)
						volume_max = volume_num;
				}
				if (i != volume_num){ // ブロック番号と拡張子が異なるなら
					bad_flag = 3;
					blk = (volume_num > 0) ? 1 : 0;
				}
			}
		} else {
			if (rv = get_header(find_path + dir_len, hFile, header_buf2, buf_size2, hash,
					&volume_num2, &file_num2, &list_off2, NULL, &data_off2, &data_size)){
				if (rv == 2){	// キャンセル
					err = 2;
					goto error_end;
				}
				bad_flag = 1;
			} else { // 同じ構成の PXX かを確認する
				if (memcmp(hash, set_hash, 16) != 0)
					bad_flag = 2;
				if ((file_num2 != file_num) || (list_off2 != list_off) || (data_off2 != data_off))
					bad_flag = 2;
				if ((block_size) && (volume_num2 > 0)){
					if (data_size != block_size)
						bad_flag = 2;
				}
				if (!bad_flag){
					if (volume_num2 == 0){
						par_flag |= 1;
						rv = (int)data_size;
						if (buf_size2 < (int)data_size)
							rv = buf_size2;
						if (rv > 1)
							read_comment(par_comment, header_buf2 + data_off2, rv);
					} else if (volume_num2 <= 99){
						if (exist[block_max + volume_num2 - 1] == -1)	// 係数が重複してるのは使えない
							parity_num++;
						// 各ブロックが存在するかを記録していく
						if (parity_max < volume_num2)
							parity_max = volume_num2;
						if (block_max + volume_num2 <= 256)
							exist[block_max + volume_num2 - 1] = i;
						if (volume_max < volume_num2)
							volume_max = volume_num2;
					}
					if (i != volume_num2){ // ブロック番号と拡張子が異なるなら
						bad_flag = 3;
						blk = (volume_num2 > 0) ? 1 : 0;
					}
				}
			}
		}
		switch (bad_flag){
		case 0:
			printf(" %d / %d Good     : \"%s\"\n", blk, blk, ascii_buf);
			break;
		case 1:
			printf(" 0 / %d Damaged  : \"%s\"\n", blk, ascii_buf);
			recovery_lost++;
			break;
		case 2:
			printf(" %d / %d Useless  : \"%s\"\n", blk, blk, ascii_buf);
			recovery_lost++;
			break;
		case 3:
			printf(" %d / %d Misnamed : \"%s\"\n", blk, blk, ascii_buf);
			recovery_lost++;	// リカバリ・ファイルの破損は修復必要とは判定しない
			break;
		}
		CloseHandle(hFile);
		hFile = NULL;

		fflush(stdout);
	}

	if (volume_num == -1){ // Recovery Set が見つからなければここで終わる
		printf("valid file is not found\n");
		err = 1;
		goto error_end;
	}
	// セット・ハッシュとプログラムのバージョンを表示する
	printf("\nSet Hash: ");
	print_hash(set_hash);
	memcpy(&rv, header_buf + 12, 4);
	printf("\nCreator : ");
	switch (rv >> 24){
	case 1:	// Mirror
		printf("Mirror");
		break;
	case 2:	// PAR
		printf("PAR");
		break;
	case 3:	// SmartPar
		printf("SmartPar");
		break;
	default:
		printf("Unknown client %d", rv >> 24);
	}
	printf(" version %d.%d.%d\n", (rv >> 16) & 0xFF, (rv >> 8) & 0xFF, rv & 0xFF);
	if ((par_comment[0] != 0) && (!utf16_to_cp(par_comment, ascii_buf)))
		printf("Comment : %s\n", ascii_buf); // コメントをユニコードから戻せた場合だけ表示する

	if (((par_flag & 1) == 0) && (switch_p)){ // PAR ファイルを作り直す
		swprintf(find_path, MAX_LEN, L"%s%s", file_path, par_char);
		get_temp_name(find_path, file_name);
		if (recreate_par(file_name, header_buf, header_buf2) == 0){
			if (replace_file(find_path, file_name, switch_b) == 0){	// 破損したPARファイルが存在するならどかす
				utf16_to_cp(find_path + dir_len, ascii_buf);
				printf("\nRestored file :\n");
				printf("         Size :  Filename\n");
				printf("%13u : \"%s\"\n", data_off, ascii_buf);
			} else {
				DeleteFile(file_name);
			}
		}
	}
	free(header_buf2);
	header_buf2 = NULL;
	printf("\nParity Volume count\t: %d\n", volume_max);
	printf("Parity Volume found\t: %d\n", parity_num);
	fflush(stdout);

	// リカバリ・ファイルを開く
	len = sizeof(file_ctx) * (block_max + parity_max);
	files = malloc(len);
	if (files == NULL){
		printf("malloc, %d\n", len);
		err = 1;
		goto error_end;
	}
	for (i = 0; i < (block_max + parity_max); i++)
		files[i].hFile = NULL;
	for (i = block_max; i < (block_max + parity_max); i++){
		if (exist[i] != -1){ // リカバリ・ファイルが存在するなら
			if (exist[i] == 0){
				swprintf(find_path, MAX_LEN, L"%s%s", file_path, par_char);
			} else {
				swprintf(find_path, MAX_LEN, L"%s%02d", file_path, exist[i]);
			}
			files[i].hFile = CreateFile(find_path, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
			if (files[i].hFile == INVALID_HANDLE_VALUE){
				exist[i] = 0;
				continue;
			}
			// ファイル・サイズを取得する
			if (!GetFileSizeEx(files[i].hFile, (PLARGE_INTEGER)&file_size2)){
				exist[i] = 0;
				continue;
			}
			files[i].size = file_size2;
			files[i].size -= data_off;
			//printf("%d: off = %d, size = %I64u \n", i, data_off, files[i].size);
			// 開始位置をリード・ソロモン符号のデータの所にしておく
			if (SetFilePointer(files[i].hFile, data_off, NULL, FILE_BEGIN) == INVALID_SET_FILE_POINTER){
				exist[i] = 0;
				continue;
			}
			exist[i] = 1;
		} else {
			exist[i] = 0;
		}
	}
	for (i = 0; i < block_max; i++)
		exist[i] = 0;

	// ファイル・リストを表示する
	printf("\nInput File list : %d\n", file_num);
	printf("         Size B :  Filename\n");
	total_file_size = 0;
	source_num = 0;
	list_off2 = list_off;
	for (i = 0; i < file_num; i++){
		// entry size
		memcpy(&entry_size, header_buf + list_off2, 4);
		// status field
		memcpy(&parity_flag, header_buf + (list_off2 + 8), 2);
		parity_flag &= 0x01; // bit 0 = file is saved in the parity volume set
		if (parity_flag){
			source_num++;
			blk = 1;
		} else {
			blk = 0;
		}
		// size
		memcpy(&data_size, header_buf + (list_off2 + 16), 8);
		total_file_size += data_size;
		// filename
		len = (entry_size - 56) / 2;
		if (len >= MAX_LEN)
			len = MAX_LEN - 1;
		memcpy(file_name, header_buf + (list_off2 + 56), len * 2);
		file_name[len] = 0;
		utf16_to_cp(file_name, ascii_buf);
		// ファイル名が有効かどうか確かめる
		j = sanitize_filename(file_name);
		if (j != 0){
			if (j == 16){
				printf("filename is not valied, %s\n", ascii_buf);
				err = 1;
				goto error_end;
			}
			utf16_to_cp(file_name, ascii_buf);
		}
		printf("%13I64u %d : \"%s\"\n", data_size, blk, ascii_buf);
		list_off2 += entry_size; // 次のエントリー位置にずらす
	}
	printf("\nData File count : %d\n", source_num);
	printf("Max file size\t: %I64d\n", block_size);
	printf("Total data size : %I64d\n", total_file_size);

	// ソース・ファイルが存在していて正しいかを調べる
	printf("\nVerifying Input File   :\n");
	printf("         Size Status   :  Filename\n");
	fflush(stdout);
	source_num = 0;
	for (i = 0; i < file_num; i++){
		if (err = cancel_progress())	// キャンセル処理
			goto error_end;
		find_flag = bad_flag = parity_flag = 0;

		// entry size
		memcpy(&entry_size, header_buf + list_off, 4);
		// status field
		memcpy(&parity_flag, header_buf + (list_off + 8), 2);
		parity_flag &= 0x0001; // bit 0 = file is saved in the parity volume set
		if (parity_flag)
			source_num++;
		// size
		memcpy(&data_size, header_buf + (list_off + 16), 8);
		file_size = 0;
		// filename
		len = (entry_size - 56) / 2;
		if (len >= MAX_LEN)
			len = MAX_LEN - 1;
		memcpy(file_name, header_buf + (list_off + 56), len * 2);
		file_name[len] = 0;
		utf16_to_cp(file_name, ascii_buf);
		// ファイル名が有効かどうか確かめる
		wcscpy(file_path, file_name);
		j = sanitize_filename(file_path);
		if (j != 0){
			wcscpy(file_name, file_path);
			utf16_to_cp(file_name, ascii_buf);
		}
		// ファイル名を基準ディレクトリに追加してパスにする
		if (base_len + wcslen(file_name) >= MAX_LEN - ADD_LEN){
			printf("filename is too long\n");
			err = 1;
			goto error_end;
		}
		wcscpy(file_path, base_dir);
		wcscpy(file_path + base_len, file_name);

		// ファイルが存在するか
		hFile = CreateFile(file_path, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
		if (hFile != INVALID_HANDLE_VALUE){ // ファイルが存在するなら内容を確認する
			// 検査結果の記録があるかどうか
			bad_flag = check_ini_state(i, meta_data, hFile);
			memcpy(&file_size, meta_data, 8);
			if (bad_flag == -1){	// ファイル・サイズが不明なら消失扱いにする
				bad_flag = 1;
			} else if (bad_flag == -2){	// 検査結果が無かった場合
				bad_flag = 0;
				if (file_size >= data_size){ // 末尾にゴミが付いてないか調べる
					if (file_size > data_size)
						bad_flag = 2;
					if (data_size > 0){ // 16k MD5 hash が一致するか確かめる
						if (file_md5_16k(hFile, data_size, hash)){
							bad_flag = 1;
						} else if (memcmp(hash, header_buf + (list_off + 40), 16) != 0){
							bad_flag = 1;
						} else if (data_size > 16384){ // MD5 hash が一致するか確かめる
							if (rv = file_md5(file_name, hFile, data_size, hash)){
								if (rv == 2){
									err = 2;
									goto error_end;
								}
								bad_flag = 1;
							} else if (memcmp(hash, header_buf + (list_off + 24), 16) != 0){
								bad_flag = 1;
							}
							// 16k MD5 hash が一致した時だけ検査結果を記録する
							write_ini_state(i, meta_data, bad_flag);
						}
					}
				} else if (file_size < data_size){
					bad_flag = 1;
				}
			}
			CloseHandle(hFile);
			hFile = NULL;
			if (!bad_flag){
				find_flag = 3;
				if (parity_flag)
					exist[source_num - 1] = 1;
			}
		}

		// ファイルが存在しないか、不完全だった場合は、サイズとハッシュ値で検索する
		if ((data_size) && ((hFile == INVALID_HANDLE_VALUE) || (bad_flag))){
			wcscpy(find_path, base_dir);
			wcscpy(find_path + base_len, L"*");
			hFind = FindFirstFile(find_path, &FindData);
			if (hFind != INVALID_HANDLE_VALUE){
				do {
					if ((FindData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) == 0){ // フォルダは無視する
						find_flag = 1;
						file_size2 = ((__int64)(FindData.nFileSizeHigh) << 32) | (__int64)(FindData.nFileSizeLow);
						if (file_size2 != data_size){ // ファイル・サイズが一致しなければ
							find_flag = 0;
						} else { // ファイル名がリスト上にあれば除外する
							memcpy(&list_off2, header_buf + 64, 4);
							for (j = 0; j < file_num; j++){
								// entry size
								memcpy(&entry_size2, header_buf + list_off2, 4);
								// filename
								len = (entry_size2 - 56) / 2;
								if (len >= MAX_LEN)
									len = MAX_LEN - 1;
								memcpy(file_name, header_buf + (list_off2 + 56), len * 2);
								file_name[len] = 0;
								sanitize_filename(file_name);
								if (_wcsicmp(file_name, FindData.cFileName) == 0){
									find_flag = 0;
									break;
								}
								list_off2 += entry_size2; // 次のエントリー位置にずらす
							}
						}
						// リスト上に無くてファイル・サイズが一致すれば
						if ((find_flag != 0) && (base_len + wcslen(FindData.cFileName) < MAX_LEN)){
							wcscpy(find_path + base_len, FindData.cFileName);
							hFile = CreateFile(find_path, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
							if (hFile != INVALID_HANDLE_VALUE){
								j = check_ini_state(i, meta_data, hFile);
								if (j == -2){	// 検査結果が無かった場合
									if (data_size > 0){ // 16k MD5 hash が一致するか確かめる
										if (file_md5_16k(hFile, data_size, hash)){
											find_flag = 0;
										} else if (memcmp(hash, header_buf + (list_off + 40), 16) != 0){
											find_flag = 0;
										} else if (data_size > 16384){ // MD5 hash が一致するか確かめる
											j = 0;
											if (rv = file_md5(FindData.cFileName, hFile, data_size, hash)){
												if (rv == 2){
													err = 2;
													goto error_end;
												}
												find_flag = 0;
												j = 1;
											} else if (memcmp(hash, header_buf + (list_off + 24), 16) != 0){
												find_flag = 0;
												j = 1;
											}
											// 16k MD5 hash が一致した時だけ検査結果を記録する
											write_ini_state(i, meta_data, j);
										}
									}
								} else if (j != 0){	// 完全以外なら
									find_flag = 0;
								}
								CloseHandle(hFile);
								hFile = NULL;
								if (find_flag){ // 異なる名前だが同じ内容のファイルを発見した
									utf16_to_cp(FindData.cFileName, ascii_buf2);
									// ファイル名を修正する
									if (replace_file(file_path, find_path, switch_b) == 0){
										find_flag = 2;
//									} else {
//										printf("cannot rename to %s\n", ascii_buf);
//										err = 1;
//										goto error_end;
									}
									break;
								}
							} else {
								find_flag = 0;
							}
						}
					}
				} while (FindNextFile(hFind, &FindData)); // 次のファイルを検索する
				FindClose(hFind);
			}
		}
		if (find_flag == 3){
			printf("            = Complete : \"%s\"\n", ascii_buf);
		} else if (find_flag == 1){
			need_repair++;
			need_repair |= 0x40000000;	// 修復失敗の印
			if (bad_flag){
				printf("%13I64u Failed~  : \"%s\"\n", file_size, ascii_buf);
			} else {
				printf("            - Failed   : \"%s\"\n", ascii_buf);
			}
			printf("%13I64u Misnamed : \"%s\"\n", data_size, ascii_buf2);
		} else if (find_flag == 2){
			need_repair += 0x10001;	// 修復できた数を記録する
			if (parity_flag)
				exist[source_num - 1] = 1;
			printf("%13I64u Restored : \"%s\"\n", data_size, ascii_buf);
		} else if (bad_flag == 2){
			if (shorten_file(file_path, data_size, switch_b) != 0)	// ファイルを小さくする
				bad_flag = 1;
			need_repair++;
			if (bad_flag == 2){	// 修復 (縮小) できたら
				need_repair += 0x10000;	// 上位 16-bit に修復できた数を記録する
				if (parity_flag)
					exist[source_num - 1] = 1;
				printf("%13I64u Restored : \"%s\"\n", data_size, ascii_buf);
			} else {	// 修復できなかったなら
				if (parity_flag)
					block_lost++;
				printf("%13I64u Appended : \"%s\"\n", file_size, ascii_buf);
			}
		} else if (bad_flag){
			bad_flag = 1;
			if (data_size == 0){	// サイズが 0ならすぐに復元できる
				need_repair++;
				hFile = CreateFile(file_path, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
				if (hFile != INVALID_HANDLE_VALUE){
					bad_flag = 0;
					need_repair += 0x10000;	// 上位 16-bit に修復できた数を記録する
				}
				CloseHandle(hFile);
				hFile = NULL;
			}
			if (bad_flag){
				if (parity_flag)
					block_lost++;
				printf("%13I64u Damaged  : \"%s\"\n", file_size, ascii_buf);
			} else {
				if (parity_flag)
					exist[source_num - 1] = 1;
				printf("%13I64u Restored : \"%s\"\n", data_size, ascii_buf);
			}
		} else {
			bad_flag = 1;
			if (data_size == 0){ // サイズが 0ならすぐに復元できる
				need_repair++;
				hFile = CreateFile(file_path, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
				if (hFile != INVALID_HANDLE_VALUE){
					bad_flag = 0;
					need_repair += 0x10000;	// 上位 16-bit に修復できた数を記録する
				}
				CloseHandle(hFile);
				hFile = NULL;
			}
			if (bad_flag){
				if (parity_flag)
					block_lost++;
				printf("            - Missing  : \"%s\"\n", ascii_buf);
			} else {
				if (parity_flag)
					exist[source_num - 1] = 1;
				printf("%13I64u Restored : \"%s\"\n", data_size, ascii_buf);
			}
		}

		if (parity_flag){
			// ソース・ファイルを開く
			if (exist[source_num - 1]){ // ソース・ブロックが存在するなら
				files[source_num - 1].hFile = CreateFile(file_path, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
				if (files[source_num - 1].hFile == INVALID_HANDLE_VALUE){
					exist[source_num - 1] = 0;
				} else {
					if (!GetFileSizeEx(files[source_num - 1].hFile, (PLARGE_INTEGER)&file_size2)){
						exist[source_num - 1] = 0;
					} else {
						files[source_num - 1].size = file_size2;
					}
				}
			} else { // 破損してる、または存在しないなら
				get_temp_name(file_path, find_path); // 修復中のテンポラリ・ファイル
				files[source_num - 1].hFile = CreateFile(find_path, GENERIC_READ | GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
				if (files[source_num - 1].hFile == INVALID_HANDLE_VALUE){
					print_win32_err();
					printf_cp("cannot create file, %s\n", find_path);
					err = 1;
					goto error_end;
				}
				files[source_num - 1].size = data_size; 
			}
			//printf("%d: exist = %d, size = %I64u \n", source_num - 1, exist[source_num - 1], files[source_num - 1].size);
		}

		fflush(stdout);
		list_off += entry_size; // 次のエントリー位置にずらす
	}
	printf("\nData File lost\t: %d\n\n", block_lost);

	// 修復に必要なパリティ・ブロックの数が少なくて済むなら最大値を調節する
	if (block_lost < parity_num){
		volume_num = 0;
		len = block_max + parity_max;
		for (i = block_max; i < len; i++){
			if (exist[i]){
				volume_num++;
				if (volume_num > block_lost){
					volume_num--;
					exist[i] = 0;
					CloseHandle(files[i].hFile);
					files[i].hFile = NULL;
				} else {
					parity_max = 1 + i - block_max;
				}
			}
		}
		len = sizeof(file_ctx) * (block_max + parity_max);
		tmp_p = (file_ctx *)realloc(files, len);
		if (tmp_p == NULL){
			printf("realloc, %d\n", len);
			err = 1;
			goto error_end;
		} else {
			files = tmp_p;
		}
	}

	if ((block_lost == 0) && (need_repair == 0))
		printf("All Files Complete\n");
	if (recovery_lost > 0){	// 不完全なリカバリ・ファイルがあるなら
		err = 256;
		printf("%d PAR File(s) Incomplete\n", recovery_lost);
	}

	// 修復する必要があるかどうか
	if (block_lost == 0){
		if (need_repair > 0){
			need_repair &= 0x3FFFFFFF;
			printf("Restored file count\t: %d\n\n", need_repair >> 16);
			need_repair = (need_repair & 0xFFFF) - (need_repair >> 16);
			if (need_repair == 0){	// 全て修復できたのなら
				printf("Repaired successfully\n");
				err |= 16;
			} else {
				printf("Failed to repair %d file(s)\n", need_repair);
				err |= 16 | 4;
			}
		}
		goto error_end;
	} else {
		if (need_repair > 0){
			printf("Restored file count\t: %d\n\n", (need_repair >> 16) & 0x3FFF);
			if (need_repair & 0x40000000){
				need_repair = (need_repair & 0xFFFF) - ((need_repair >> 16) & 0x3FFF);
				printf("Failed to repair %d file(s)\n", block_lost + need_repair);
				err |= 16 | 4;
				goto error_end;
			}
			need_repair &= 0x3FFFFFFF;
			need_repair = (need_repair & 0xFFFF) - (need_repair >> 16);
		}
		if (block_lost > parity_num){
			printf("Need %d more volume(s) to repair %d file(s)\n", block_lost - parity_num, block_lost);
			err |= 8 | 4;
			goto error_end;
		} else {
			printf("Ready to repair %d file(s)\n", block_lost);
		}
	}

	// 失われたブロックを復元する
	printf("\n");
	print_progress_text(0, "Recovering data");
	if (err = rs_decode(block_max, block_lost, block_size, parity_max, exist, files)){
		goto error_end;
	}

	// 正しく修復できたか確かめる
	printf("\nVerifying repair: %d\n", block_lost);
	printf(" Status   :  Filename\n");
	fflush(stdout);
	wcscpy(file_path, base_dir);
	find_flag = 0;
	source_num = 0;
	memcpy(&list_off, header_buf + 64, 4);
	for (i = 0; i < file_num; i++){
		bad_flag = 0;

		// entry size
		memcpy(&entry_size, header_buf + list_off, 4);
		// status field
		memcpy(&parity_flag, header_buf + (list_off + 8), 2);
		parity_flag &= 0x0001; // bit 0 = file is saved in the parity volume set
		// size
		memcpy(&data_size, header_buf + (list_off + 16), 8);
		// filename
		len = (entry_size - 56) / 2;
		if (len >= MAX_LEN)
			len = MAX_LEN - 1;
		memcpy(file_name, header_buf + (list_off + 56), len * 2);
		file_name[len] = 0;
		utf16_to_cp(file_name, ascii_buf);
		sanitize_filename(file_name);

		// ファイル名を基準ディレクトリに追加してパスにする
		wcscpy(file_path + base_len, file_name);

		//printf("%d : list_off = %d, exist = %d \n", i, list_off, exist[source_num]);
		// 修復したファイルのハッシュ値を計算する
		if (parity_flag){
			if (exist[source_num] == 0){
				if (file_md5(file_name, files[source_num].hFile, data_size, hash)){
					bad_flag = 1;
				} else {
					for (j = 0; j < 16; j++){
						if (hash[j] != header_buf[list_off + 24 + j]){
							bad_flag = 1;
							break;
						}
					}
				}
				CloseHandle(files[source_num].hFile);
				files[source_num].hFile = NULL;
				get_temp_name(file_path, find_path);
				if (bad_flag){ // 失敗
					DeleteFile(find_path); // テンポラリ・ファイルを削除する
					printf(" Failed   : \"%s\"\n", ascii_buf);
				} else { // 復元成功
					if (replace_file(file_path, find_path, switch_b) == 0){	// 修復したファイルを戻す
						block_lost--;
						find_flag++;
						printf(" Repaired : \"%s\"\n", ascii_buf);
					} else {	// ファイルを戻せなかった場合は、テンポラリ・ファイルを残した方がいいかも？
						// まあ PAR2 と違ってすぐに修復できるから、削除してもよさそう。
						DeleteFile(find_path); // テンポラリ・ファイルを削除する
						printf(" Locked   : \"%s\"\n", ascii_buf);
					}
				}
				fflush(stdout);
			}
			source_num++;
		}

		list_off += entry_size; // 次のエントリー位置にずらす
	}
	printf("\nRepaired file count\t: %d\n\n", find_flag);

	err = 16;
	if (block_lost + need_repair == 0){	// 全て修復できたなら
		printf("Repaired successfully\n");
	} else {
		printf("Failed to repair %d file(s)\n", block_lost + need_repair);
		err |= 4;
	}
	if (recovery_lost > 0)
		err |= 256;

error_end:
	close_ini_file();
	if (files){
		// エラーが発生したら、作業中のファイルを削除する
		source_num = 0;
		memcpy(&list_off, header_buf + 64, 4);
		for (i = 0; i < file_num; i++){
			// entry size
			memcpy(&entry_size, header_buf + list_off, 4);
			// status field
			memcpy(&parity_flag, header_buf + (list_off + 8), 2);
			parity_flag &= 0x0001; // bit 0 = file is saved in the parity volume set
			// filename
			len = (entry_size - 56) / 2;
			if (len >= MAX_LEN)
				len = MAX_LEN - 1;
			memcpy(file_name, header_buf + (list_off + 56), len * 2);
			file_name[len] = 0;
			sanitize_filename(file_name);

			// ファイル名を基準ディレクトリに追加してパスにする
			wcscpy(file_path + base_len, file_name);

			if (parity_flag){
				if ((exist[source_num] == 0) && (files[source_num].hFile)){
					CloseHandle(files[source_num].hFile);
					files[source_num].hFile = NULL;
					get_temp_name(file_path, find_path);
					DeleteFile(find_path); // テンポラリ・ファイルを削除する
				}
				source_num++;
			}

			list_off += entry_size; // 次のエントリー位置にずらす
		}
		for (i = 0; i < (source_num + parity_max); i++){
			if (files[i].hFile != NULL)
				CloseHandle(files[i].hFile);
		}
		free(files);
	}
	if (hFile != NULL)
		CloseHandle(hFile);
	if (header_buf)
		free(header_buf);
	if (header_buf2)
		free(header_buf2);
	return err;
}

// ソース・ファイルの一覧を表示する
int par1_list(
	int switch_h,				// ハッシュ値も表示する
	wchar_t *par_comment)		// コメント
{
	char ascii_buf[MAX_LEN * 3];
	unsigned char *header_buf = NULL, hash[16];
	wchar_t file_name[MAX_LEN];
	int err = 0, i, parity_flag;
	int len, buf_size, entry_size, blk;
	int volume_num, file_num, list_off, list_size, data_off;
	int source_num = 0;
	__int64 file_size, data_size, block_size = 0;
	HANDLE hFile = NULL;

	// 指定されたリカバリ・ファイルを開く
	hFile = CreateFile(recovery_file, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
	if (hFile == INVALID_HANDLE_VALUE){
		print_win32_err();
		printf("cannot open PAR file\n");
		err = 1;
		goto error_end;
	}

	// ファイルのサイズを取得する
	if (!GetFileSizeEx(hFile, (PLARGE_INTEGER)&file_size)){
		print_win32_err();
		err = 1;
		goto error_end;
	}

	// ヘッダー用のバッファーを確保する
	buf_size = 0x0060 + ((0x0038 + (260 * 2)) * 256) + (COMMENT_LEN * 2); // 最大サイズ
	if ((__int64)buf_size > file_size)
		buf_size = (unsigned int)file_size;
	header_buf = malloc(buf_size);
	if (header_buf == NULL){
		printf("malloc, %d\n", buf_size);
		err = 1;
		goto error_end;
	}

	// PAR や PXX ファイルとして正しいかを調べる
	if (err = get_header(NULL, hFile, header_buf, buf_size, hash,
			&volume_num, &file_num, &list_off, &list_size, &data_off, &data_size)){
		if (err != 2)	// キャンセル以外なら
			printf("valid file is not found\n");
		goto error_end;
	} else {
		if ((volume_num == 0) && (data_size > 0)){ // コメントがある
			len = (int)data_size;
			if (buf_size < (int)data_size)
				len = buf_size;
			if (len > 1)
				read_comment(par_comment, header_buf + data_off, len);
			if (!utf16_to_cp(par_comment, ascii_buf))
				printf("Comment : %s\n", ascii_buf); // コメントをユニコードから戻せた場合だけ表示する
		}
	}
	CloseHandle(hFile);
	hFile = NULL;

	printf("Input File list : %d\n", file_num);
	if (switch_h){
		printf("         Size B             MD5 Hash             :  Filename\n");
	} else {
		printf("         Size B :  Filename\n");
	}
	// ソース・ファイルの情報を表示する
	for (i = 0; i < file_num; i++){
		// entry size
		memcpy(&entry_size, header_buf + list_off, 4);
		// status field
		memcpy(&parity_flag, header_buf + (list_off + 8), 2);
		parity_flag &= 0x01; // bit 0 = file is saved in the parity volume set
		if (parity_flag){
			source_num++;
			blk = 1;
		} else {
			blk = 0;
		}
		// size
		memcpy(&file_size, header_buf + (list_off + 16), 8);
		if (file_size > block_size)
			block_size = file_size;
		// hash
		memcpy(hash, header_buf + (list_off + 40), 16);
		// filename
		len = (entry_size - 56) / 2;
		if (len >= MAX_LEN)
			len = MAX_LEN - 1;
		memcpy(file_name, header_buf + (list_off + 56), len * 2);
		file_name[len] = 0;
		utf16_to_cp(file_name, ascii_buf);

		// 項目番号、ソース・ブロックかどうか、ファイル・サイズ、MD5ハッシュ値、ファイル名
		if (switch_h){
			printf("%13I64u %d ", file_size, blk);
			print_hash(hash);
			printf(" : \"%s\"\n", ascii_buf);
		} else {
			printf("%13I64u %d : \"%s\"\n", file_size, blk, ascii_buf);
		}

		list_off += entry_size; // 次のエントリー位置にずらす
	}

	// 調べた結果を表示する
	printf("\nData File count : %d\n", source_num);
	printf("Max file size\t: %I64u\n", block_size);
	printf("\nListed successfully\n");
error_end:
	if (header_buf)
		free(header_buf);
	if (hFile != NULL)
		CloseHandle(hFile);
	return err;
}

// CRC-32 チェックサムを使って自分自身の破損を検出する
int par1_checksum(wchar_t *uni_buf)	// 作業用
{
	unsigned int crc, chk, chk2;
	unsigned int len, tmp, i;
	unsigned char *pAddr, *p;
	HANDLE hFile, hMap;

	// 実行ファイルのパスを取得する
	tmp = GetModuleFileName(NULL, uni_buf, MAX_LEN);
	if ((tmp == 0) || (tmp >= MAX_LEN))
		return 1;
	//printf("%S\n", uni_buf);

	// 実行ファイルの PE checksum と CRC-32 を検証する
	hFile = CreateFile(uni_buf, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
	if (hFile == INVALID_HANDLE_VALUE){
		print_win32_err();
		return 1;
	}
	len = GetFileSize(hFile, &chk2);
	if (len == INVALID_FILE_SIZE)
		return 1;
	hMap = CreateFileMapping(hFile, NULL, PAGE_READONLY, chk2, len, NULL);
	if (hMap == NULL){
		CloseHandle(hFile);
		return 1;
	}
	pAddr = MapViewOfFile(hMap, FILE_MAP_READ, 0, 0, len);
	if (pAddr == NULL){
		CloseHandle(hMap);
		CloseHandle(hFile);
		return 1;
	}
	if (CheckSumMappedFile(pAddr, len, &chk2, &chk) == NULL){	// PE checksum
		UnmapViewOfFile(pAddr);
		CloseHandle(hMap);
		CloseHandle(hFile);
		return 1;
	}
	crc = 0xFFFFFFFF;
	p = pAddr;
	while (len--){
		tmp = (*p++);
		for (i = 0; i < 8; i++){
			if ((tmp ^ crc) & 1){
				crc = (crc >> 1) ^ 0xEDB88320;
			} else {
				crc = crc >> 1;
			}
			tmp = tmp >> 1;
		}
	}
	crc ^= 0xFFFFFFFF;
	UnmapViewOfFile(pAddr);
	CloseHandle(hMap);
	CloseHandle(hFile);

	if (chk != chk2)
		return 2;
	if (crc != 0x11111111)
		return 3;
	return 0;
}

