// common.c
// Copyright : 2023-03-13 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 <conio.h>
#include <stdio.h>

#include <windows.h>
#include <shlobj.h>
#include <shlwapi.h>

#include "common1.h"


// グローバル変数
wchar_t recovery_file[MAX_LEN];	// リカバリ・ファイルのパス
wchar_t base_dir[MAX_LEN];		// ソース・ファイルの基準ディレクトリ
wchar_t ini_path[MAX_LEN];		// 検査結果ファイルのパス

unsigned int cp_output;

/*
// Console Input Code Page から Windown OS の precomposed UTF-16 に変換する
int cp_to_utf16(char *in, wchar_t *out)
{
	if (!MultiByteToWideChar(cp_input, 0, in, -1, out, MAX_LEN)){
		wcscpy(out, L"cannot encode");
		return 1;
	}
	return 0;
}
*/

// Windown OS の precomposed UTF-16 から Console Output Code Page に変換する
int utf16_to_cp(wchar_t *in, char *out)
{
	if (cp_output == CP_UTF8){
		if (WideCharToMultiByte(CP_UTF8, 0, in, -1, out, MAX_LEN * 3, NULL, NULL) == 0){
			strcpy(out, "cannot encode");
			return 1;
		}
		return 0;
	}

	// 似た文字への自動変換を行わない
	if (!WideCharToMultiByte(cp_output, WC_NO_BEST_FIT_CHARS, in, -1, out, MAX_LEN * 2, NULL, NULL)){
		strcpy(out, "cannot encode");
		return 1;
	}
	return 0;
}

// ファイル・パスから、先頭にある "\\?\" を省いて、Console Output Code Page に変換する
int path_to_cp(wchar_t *path, char *out)
{
	unsigned int dwFlags = WC_NO_BEST_FIT_CHARS;	// 似た文字への自動変換を行わない

	if (cp_output == CP_UTF8)
		dwFlags = 0;

	if (wcsncmp(path, L"\\\\?\\", 4) == 0){	// "\\?\" を省く
		path += 4;
		if (wcsncmp(path, L"UNC\\", 4) == 0){	// "\\?\UNC" を省いて "\" を追加する
			path += 3;
			*out = '\\';
			out += 1;
		}
	}

	if (WideCharToMultiByte(cp_output, dwFlags, path, -1, out, MAX_LEN * 3, NULL, NULL) == 0){
		strcpy(out, "cannot encode");
		return 1;
	}
	return 0;
}

// UTF-16 のファイル・パスを画面出力用の Code Page を使って表示する
void printf_cp(unsigned char *format, wchar_t *path)
{
	unsigned char buf[MAX_LEN * 3];
	path_to_cp(path, buf);
	printf(format, buf);
}

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

// ファイル・パスがファイル・リスト上に既に存在するか調べる
int search_file_path(
	wchar_t *list,			// ファイル・リスト
	int total_len,			// ファイル・リストの文字数
	wchar_t *search_file)	// 検索するファイルのパス
{
	int off = 0;

	while (off < total_len){
		if (_wcsicmp(list + off, search_file) == 0)
			return 1;
		// 次のファイル名の位置へ
		//off += (wcslen(list + off) + 1);
		while (list[off] != 0)
			off++;
		off++;
	}

	return 0;
}

// ファイル・リストの内容を並び替える
void sort_list(
	wchar_t *list,	// ファイル・リスト
	int total_len)	// ファイル・リストの文字数
{
	wchar_t *work_buf;
	int off1 = 0, off2, off3, work_off = 0;

	// 作業バッファーを確保する
	work_buf = (wchar_t *)calloc(total_len, 2);
	if (work_buf == NULL)
		return;	// 並べ替え失敗

	while (off1 < total_len){
		if (list[off1] == 0){
			break;
		} else if (list[off1] == '*'){	// 既にコピー済なら
			//off1 += (wcslen(list + off1) + 1);
			while (list[off1] != 0)
				off1++;
			off1++;
			continue;
		}
		off3 = off1;

		//off2 = off3 + wcslen(list + off3) + 1;	// 次の項目
		off2 = off3;
		while (list[off2] != 0)
			off2++;
		off2++;
		while (off2 < total_len){
//printf("%S (%d), %S (%d)\n", list + off3, off3, list + off2, off2);
			if (list[off2] != '*'){	// まだなら項目を比較する
				if (wcscmp(list + off3, list + off2) > 0)	// 大文字と小文字を区別する
					off3 = off2;
			}
			//off2 += (wcslen(list + off2) + 1);	// 次の項目
			while (list[off2] != 0)
				off2++;
			off2++;
		}

		// 順番にコピーしていく
//printf("get %S (%d)\n", list + off3, off3);
		wcscpy(work_buf + work_off, list + off3);
		work_off += (wcslen(work_buf + work_off) + 1);
		list[off3] = '*';	// コピーした印
		if (off3 == off1){
			//off1 += (wcslen(list + off1) + 1);
			while (list[off1] != 0)
				off1++;
			off1++;
		}
	}

	// 作業バッファーから戻す
	memcpy(list, work_buf, total_len * 2);
	free(work_buf);
}

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

// ファイル・パスからファイル名の位置を戻す
wchar_t * offset_file_name(wchar_t *file_path)
{
	int i;

	for (i = wcslen(file_path) - 2; i >= 0; i--){
		if ((file_path[i] == '\\') || (file_path[i] == '/'))
			break;
	}
	i++;
	return file_path + i;
}

// ファイル・パスからファイル名だけ取り出す
void get_file_name(
	wchar_t *file_path,		// ファイル・パス
	wchar_t *file_name)		// ファイル名
{
	int i, len, find = -1;

	len = wcslen(file_path);
	for (i = len - 2; i >= 0; i--){
		if ((file_path[i] == '\\') || (file_path[i] == '/')){
			find = i;
			break;
		}
	}
	find++;
	for (i = find; i < len + 1; i++){
		file_name[i - find] = file_path[i];
	}
}

// ファイル・パスからディレクトリだけ取り出す、末尾は「\」か「/」
void get_base_dir(
	wchar_t *file_path,		// ファイル・パス
	wchar_t *base_path)		// ディレクトリ
{
	int i, len, find = 0;

	len = wcslen(file_path);
	if (len <= 1){
		base_path[0] = 0;
		return;
	}

	// 最初から末尾が「\」か「/」なら
	if ((file_path[len - 1] == '\\') || (file_path[len - 1] == '/'))
		len--;	// それを取り除いてひとつ親のディレクトリを返す
	for (i = len - 1; i >= 0; i--){
		if (find == 0){
			if ((file_path[i] == '\\') || (file_path[i] == '/')){
				find = 1;
				base_path[i] = file_path[i];
				base_path[i + 1] = 0;
			}
		} else {
			base_path[i] = file_path[i];
		}
	}
	if (find == 0)
		base_path[0] = 0;
}

// 絶対パスかどうかを判定する
int is_full_path(wchar_t *path)
{
	if ((path[0] == 0) || (path[1] == 0))
		return 0;	// 2文字以下だと短すぎ

	// 通常のドライブ指定「c:\」の形式
	if ((path[1] == ':') && ((path[2] == '\\') || (path[2] == '/')))
		return 1;

	//「\\?\」が付くのは絶対パスだけ
	// ネットワークの UNC パスなら「\\<server>\<share>」
	if (((path[0] == '\\') || (path[0] == '/')) && ((path[1] == '\\') || (path[1] == '/')))
		return 1;

	return 0;
}

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

#define PREFIX_LEN	4	// 「\\?\」の長さ

// 相対パスを絶対パスに変換し、パスの先頭に "\\?\" を追加する
// 戻り値 : 0=エラー, 5～=新しいパスの長さ
int copy_path_prefix(
	wchar_t *new_path,	// 新しいパス
	int max_len,		// 新しいパスの最大長さ (末尾の null文字も含む)
	wchar_t *src_path,	// 元のパス (相対パスでもよい)
	wchar_t *dir_path)	// 相対パスの場合に基準となるディレクトリ (NULL ならカレント・ディレクトリ)
{
	wchar_t tmp_path[MAX_LEN], *src_name, *new_name;
	int len;

	// 不正なファイル名を拒否する (検索記号は許可する)
	if (src_path[0] == 0)
		return 0;
	if (wcspbrk(src_path, L"\"<>|"))	// WinOS ではファイル名に「"<>|」の文字は使えない
		return 0;

	// 不適切な名前のファイルは、変換時に浄化されることがある
	src_name = offset_file_name(src_path);

	// 相対パスに対して基準となるディレクトリが指定されてるなら
	if ((dir_path != NULL) && (is_full_path(src_path) == 0)){
		// そのまま基準ディレクトリを連結して絶対パスにする
		len = wcslen(dir_path);
		if (len + (int)wcslen(src_path) >= max_len)
			return 0;
		wcscpy(tmp_path, dir_path);	// dir_path の末尾は「\」にしておくこと
		wcscpy(tmp_path + len, src_path);
		if (GetFullPathName(tmp_path, max_len, new_path, &new_name) == 0)
			return 0;
	} else {	// カレント・ディレクトリを使う
		// 相対パスから絶対パスに変換する (ディレクトリ記号も「\」に統一される)
		if (GetFullPathName(src_path, max_len, new_path, &new_name) == 0)
			return 0;
	}

	// 元のファイル名と比較して、浄化を取り消す
	if (new_name == NULL)
		new_name = offset_file_name(new_path);
	if (_wcsicmp(src_name, new_name) != 0){
		len = (int)wcslen(new_name);
		if ((_wcsnicmp(src_name, new_name, len) == 0) && ((src_name[len] == ' ') || (src_name[len] == '.'))){
			len = (int)wcslen(src_name);
			if ((src_name[len - 1] == ' ') || (src_name[len - 1] == '.')){
				// 末尾の「 」や「.」が取り除かれてるなら元に戻す
				wcscpy(new_name, src_name);
			}
		}
	}

	// 先頭に "\\?\" が存在しないなら、追加する
	if ((wcsncmp(new_path, L"\\\\?\\", PREFIX_LEN) != 0)){
		len = wcslen(new_path);
		if ((new_path[0] == '\\') && (new_path[1] == '\\')){	// UNC パスなら
			if (len + PREFIX_LEN + 2 >= max_len)
				return 0;	// バッファー・サイズが足りなくて追加できない
			memmove(new_path + (PREFIX_LEN + 3), new_path + 1, len * 2);
			new_path[PREFIX_LEN    ] = 'U';
			new_path[PREFIX_LEN + 1] = 'N';
			new_path[PREFIX_LEN + 2] = 'C';
		} else {	// 通常のドライブ記号で始まるパスなら
			if (len + PREFIX_LEN >= max_len)
				return 0;	// バッファー・サイズが足りなくて追加できない
			memmove(new_path + PREFIX_LEN, new_path, (len + 1) * 2);
		}
		memcpy(new_path, L"\\\\?\\", PREFIX_LEN * 2);
	}

	// 8.3形式の短いファイル名を長いファイル名に変換する
	if (GetFileAttributes(new_path) == INVALID_FILE_ATTRIBUTES){
		// 指定されたファイルが存在しない場合 (作成するPARファイルや「*?」で検索するソース・ファイル)
		int name_len;
		// 区切り文字を探す
		name_len = wcslen(new_path);
		for (len = name_len - 1; len >= 0; len--){
			if (new_path[len] == '\\')
				break;
		}
		len++;
		wcscpy(tmp_path, new_path + len);	// 存在しないファイル名を記録しておく
		name_len -= len;
		new_path[len] = 0;	// ファイル名を消す
		len = GetLongPathName(new_path, new_path, max_len);
		if (len == 0){	// 変換エラー
			//printf("GetLongPathName (not exist) : err = %d\n", GetLastError());
			//print_win32_err();
			return 0;
		} else {	// 退避させておいたファイル名を追加する
			len += name_len;
			if (len < max_len)
				wcscat(new_path, tmp_path);
		}
	} else {
		len = GetLongPathName(new_path, new_path, max_len);
		if (len == 0){	// 変換エラー
			//printf("GetLongPathName (exist) : err = %d\n", GetLastError());
			return 0;
		}
	}
	if (len >= max_len)
		return 0;

	return len;
}

// ファイル・パスから、先頭にある "\\?\" を省いた長さを戻す
int len_without_prefix(wchar_t *file_path)
{
	// 最初から "\\?\" が無ければ
	if (wcsncmp(file_path, L"\\\\?\\", PREFIX_LEN) != 0)
		return wcslen(file_path);	// そのままの長さ

	// "\\?\UNC\<server>\<share>" ならネットワーク・パス
	if (wcsncmp(file_path + PREFIX_LEN, L"UNC\\", 4) == 0)
		return wcslen(file_path + PREFIX_LEN + 2);	// "\\?\UNC\" -> "\\" なので6文字減らす

	// "\\?\<drive:>\<path>" なら
	return wcslen(file_path + PREFIX_LEN);	// "\\?\" を省くので4文字減らす
}

// ファイル・パスから、先頭にある "\\?\" を省いてコピーする
int copy_without_prefix(
	wchar_t *dst_path,	// コピー先 (コピー元と同じアドレスでもよい)
	wchar_t *src_path)	// コピー元のパス
{
	int i, len;

	len = wcslen(src_path);	// 元の文字数

	// 最初から "\\?\" が無ければ
	if (wcsncmp(src_path, L"\\\\?\\", PREFIX_LEN) != 0){
		for (i = 0; i <= len; i++)
			dst_path[i] = src_path[i];	// そのままコピーする
		return len;
	}

	// "\\?\UNC\<server>\<share>" ならネットワーク・パス
	if (wcsncmp(src_path + PREFIX_LEN, L"UNC\\", 4) == 0){
		len -= PREFIX_LEN + 2;	// "\\?\UNC\" -> "\\" なので6文字減らす
		for (i = 1; i <= len; i++)
			dst_path[i] = src_path[PREFIX_LEN + 2 + i];
		dst_path[0] = '\\';	// 先頭を "\\" に変えておく
		return len;
	}

	// "\\?\<drive:>\<path>" なら
	len -= PREFIX_LEN;	// "\\?\UNC\" -> "\\" なので6文字減らす
	for (i = 0; i <= len; i++)
		dst_path[i] = src_path[PREFIX_LEN + i];
	return len;
}

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

// ファイルを置き換える (ファイル名の修正、テンポラリー・ファイルからの書き戻しなど)
int replace_file(
	wchar_t *dest_path,	// 置き換える先のパス (移動先、修正されたファイル名)
	wchar_t *sorc_path,	// 置き換える元のパス (移動元、現在のファイル名)
	int switch_b)		// 既存のファイルをバックアップするかどうか
{
	unsigned int rv, num;

	// 移動元のファイルが存在するかどうか
	num = GetFileAttributes(sorc_path);
	if (num == INVALID_FILE_ATTRIBUTES){
		return 1;	// 移動させるファイルが存在しない
	} else if (num & FILE_ATTRIBUTE_READONLY){	// 読み取り専用属性なら、その属性を解除する
		if (SetFileAttributes(sorc_path, num ^ FILE_ATTRIBUTE_READONLY) == 0)
			return 2;	// 移動元の読み取り専用属性を解除できなかった
	}

	// 移動先のファイルが存在するかどうか
	rv = GetFileAttributes(dest_path);
	if (rv == INVALID_FILE_ATTRIBUTES){	// ファイルが存在しないなら、そのまま移動する
		if (MoveFile(sorc_path, dest_path) == 0)
			return 4;	// 移動失敗
		if (num & FILE_ATTRIBUTE_READONLY){	// 読み取り専用属性を付け直す
			rv = GetFileAttributes(dest_path);
			SetFileAttributes(dest_path, rv | FILE_ATTRIBUTE_READONLY);	// 失敗してもエラーにしない
		}

	} else {	// 既にファイルが存在するなら
		wchar_t back_path[MAX_LEN];

		back_path[0] = 0;
		if (switch_b){	// 既存のファイルを削除せずに別名で残す
			for (num = 1; num < 100; num++){
				swprintf(back_path, _countof(back_path), L"%s.%d", dest_path, num);	// バックアップ・ファイルのパス
				if (GetFileAttributes(back_path) == INVALID_FILE_ATTRIBUTES)
					break;	// 存在しないなら OK
			}
			if (num >= 100)
				back_path[0] = 0;	// バックアップがいっぱいならそれ以上作らない
		}
		if (back_path[0] != 0){	// バックアップを作って置き換える
			num = ReplaceFile(dest_path, sorc_path, back_path, REPLACEFILE_IGNORE_MERGE_ERRORS, 0, 0);
			if (num == 0){
				num = GetLastError();
				if ((num == 32) ||	// The process cannot access the file because it is being used by another process.
						(num == 1175) ||	// ERROR_UNABLE_TO_REMOVE_REPLACED
						(num == 1176)){		// ERROR_UNABLE_TO_MOVE_REPLACEMENT
					Sleep(100);
					num = ReplaceFile(dest_path, sorc_path, back_path, REPLACEFILE_IGNORE_MERGE_ERRORS, 0, 0);
				} else if (num == 1177){	// ERROR_UNABLE_TO_MOVE_REPLACEMENT_2
					Sleep(100);
					num = MoveFile(sorc_path, dest_path);	// 既存ファイルはバックアップ済みなら、移動するだけ
				} else {
					num = 0;
				}
				if (num == 0)
					return 6;
			}
			if (switch_b & 2)	// バックアップをゴミ箱に入れる
				delete_file_recycle(back_path);
		} else {	// 既存のファイルを削除して置き換える
			if (rv & FILE_ATTRIBUTE_READONLY){	// 読み取り専用属性なら、一旦その属性を解除する
				if (SetFileAttributes(dest_path, rv ^ FILE_ATTRIBUTE_READONLY) == 0)
					return 7;	// 移動先の読み取り専用属性を解除できなかった = 削除もできない
			}
			num = ReplaceFile(dest_path, sorc_path, NULL, REPLACEFILE_IGNORE_MERGE_ERRORS, 0, 0);
			if (num == 0){
				num = GetLastError();
				if ((num == 32) ||	// The process cannot access the file because it is being used by another process.
						(num == 1175)){	// ERROR_UNABLE_TO_REMOVE_REPLACED
					Sleep(100);
					num = ReplaceFile(dest_path, sorc_path, NULL, REPLACEFILE_IGNORE_MERGE_ERRORS, 0, 0);
				} else if (num == 1176){	// ERROR_UNABLE_TO_MOVE_REPLACEMENT
					Sleep(100);
					num = MoveFile(sorc_path, dest_path);	// 既存ファイルは削除済みなら、移動するだけ
				} else {
					num = 0;
				}
				if (num == 0)
					return 8;
			}
			if (rv & FILE_ATTRIBUTE_READONLY){	// 読み取り専用属性を付け直す
				rv = GetFileAttributes(dest_path);
				SetFileAttributes(dest_path, rv | FILE_ATTRIBUTE_READONLY);	// 失敗してもエラーにしない
			}
		}
	}

	return 0;
}

// ファイルを指定サイズに縮小する
int shorten_file(
	wchar_t *file_path,		// ファイル・パス
	__int64 new_size,
	int switch_b)			// 既存のファイルをバックアップするかどうか
{
	wchar_t back_path[MAX_LEN];
	unsigned int rv, len, num;
	HANDLE hFileWrite;
	LARGE_INTEGER qwi;	// Quad Word Integer

	back_path[0] = 0;
	if (switch_b){	// 既存のファイルを削除せずに別名で残す
		for (num = 1; num < 100; num++){
			swprintf(back_path, _countof(back_path), L"%s.%d", file_path, num);	// バックアップ・ファイルのパス
			if (GetFileAttributes(back_path) == INVALID_FILE_ATTRIBUTES)
				break;	// 存在しないなら OK
		}
		if (num >= 100)
			back_path[0] = 0;	// バックアップがいっぱいならそれ以上作らない
	}

	if (back_path[0] != 0){	// バックアップを作って置き換える
		wchar_t temp_path[MAX_LEN];
		// テンポラリー・ファイルを作ってファイル内容をコピーする
		get_temp_name(file_path, temp_path);
		hFileWrite = CreateFile(temp_path, GENERIC_WRITE, 0, NULL, CREATE_NEW, FILE_ATTRIBUTE_NORMAL, NULL);
		if (hFileWrite == INVALID_HANDLE_VALUE){	// 作れなかったら
			back_path[0] = 0;
		} else if (new_size <= 0){
			CloseHandle(hFileWrite);
		} else {
			// ファイル・サイズを指定サイズにする
			qwi.QuadPart = new_size;
			if (!SetFilePointerEx(hFileWrite, qwi, NULL, FILE_BEGIN)){
				back_path[0] = 0;
			} else if (!SetEndOfFile(hFileWrite)){
				back_path[0] = 0;
			} else {
				// ポインターを先頭に戻す
				qwi.QuadPart = 0;
				if (!SetFilePointerEx(hFileWrite, qwi, NULL, FILE_BEGIN)){
					back_path[0] = 0;
				} else {
					__int64 left_size = new_size;
					HANDLE hFileRead;
					hFileRead = CreateFile(file_path, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
					if (hFileRead == INVALID_HANDLE_VALUE){
						back_path[0] = 0;
					} else {	// 指定サイズまでコピーする
						unsigned char buf[IO_SIZE];
						while (left_size > 0){
							len = IO_SIZE;
							if (left_size < IO_SIZE)
								len = (unsigned int)left_size;
							left_size -= len;
							if (!ReadFile(hFileRead, buf, len, &rv, NULL) || (len != rv)){
								back_path[0] = 0;
								break;
							}
							if (!WriteFile(hFileWrite, buf, len, &rv, NULL)){
								back_path[0] = 0;
								break;
							}
						}
						CloseHandle(hFileRead);
					}
				}
			}
			CloseHandle(hFileWrite);
			if (back_path[0] == 0)	// 失敗したらテンポラリー・ファイルは削除する
				DeleteFile(temp_path);
		}

		if (back_path[0] != 0){	// バックアップを作って置き換える
			if (ReplaceFile(file_path, temp_path, back_path, REPLACEFILE_IGNORE_MERGE_ERRORS, 0, 0) == 0){
				DeleteFile(temp_path);
				return 1;
			}
			if (switch_b & 2)	// バックアップをゴミ箱に入れる
				delete_file_recycle(back_path);
			return 0;
		}
	}

	num = GetFileAttributes(file_path);
	if (num & FILE_ATTRIBUTE_READONLY){	// 読み取り専用属性なら、一旦その属性を解除する
		if (SetFileAttributes(file_path, num ^ FILE_ATTRIBUTE_READONLY) == 0)
			return 1;	// 読み取り専用属性を解除できなかった = 縮小もできない
	}

	// 直接開いて縮小する
	hFileWrite = CreateFile(file_path, GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL);
	if (hFileWrite == INVALID_HANDLE_VALUE)
		return 1;
	// ファイル・サイズを指定サイズにする
	qwi.QuadPart = new_size;
	if (!SetFilePointerEx(hFileWrite, qwi, NULL, FILE_BEGIN)){
		CloseHandle(hFileWrite);
		return 1;
	}
	if (!SetEndOfFile(hFileWrite)){
		CloseHandle(hFileWrite);
		return 1;
	}
	CloseHandle(hFileWrite);

	if (num & FILE_ATTRIBUTE_READONLY){	// 読み取り専用属性を付け直す
		num = GetFileAttributes(file_path);
		SetFileAttributes(file_path, num | FILE_ATTRIBUTE_READONLY);	// 失敗してもエラーにしない
	}

	return 0;
}

// デバイス名かどうかを判定する
static int check_device_name(wchar_t *name, int len)
{
	if (len >= 3){
		if ((name[3] == 0) || (name[3] == '.')){
			if (_wcsnicmp(name, L"CON", 3) == 0)
				return 1;
			if (_wcsnicmp(name, L"PRN", 3) == 0)
				return 1;
			if (_wcsnicmp(name, L"AUX", 3) == 0)
				return 1;
			if (_wcsnicmp(name, L"NUL", 3) == 0)
				return 1;
		}
		if (len >= 4){
			if ((name[4] == 0) || (name[4] == '.')){
				if (_wcsnicmp(name, L"COM", 3) == 0){
					if ((name[3] >= 0x31) && (name[3] <= 0x39))
						return 1;
				}
				if (_wcsnicmp(name, L"LPT", 3) == 0){
					if ((name[3] >= 0x31) && (name[3] <= 0x39))
						return 1;
				}
			}
		}
	}

	return 0;
}

// ファイル名が有効か確かめて、問題があれば浄化する
// 戻り値 0=変更無し, +2=浄化した, +4=文字数が変わった, +8=警告, 16=エラー
int sanitize_filename(wchar_t *name)
{
	int i, rv = 0, len = 0;

	// 制御文字 1～31 (改行やタブなど) を削除する
	while (len < MAX_LEN){
		if (name[len] == 0){
			break;
		} else if (name[len] < 32){
			i = len;
			do {
				name[i] = name[i + 1];
				i++;
			} while (name[i] != 0);
			rv |= 4;
		} else {
			len++;
		}
	}
	if (len == 0)
		return 16;

	// WinOS ではファイル名に「\/:*?"<>|」の文字は使えないので置換する
	for (i = 0; i < len; i++){
		if ((name[i] == '\\') || (name[i] == '/') || (name[i] == ':')
				|| (name[i] == '*') || (name[i] == '?') || (name[i] == '"')
				|| (name[i] == '<') || (name[i] == '>') || (name[i] == '|')){
			name[i] = '_';	// 「\,/,:,*,?,",<,>,|」 ->「_」
			rv |= 2;
		}
	}

	// 先頭の「 」、末尾の「.」「 」を警告する
	if ((name[0] == ' ') || (name[len - 1] == '.') || (name[len - 1] == ' '))
		rv |= 8;

	// デバイス名を警告する
	if (check_device_name(name, len) != 0)
		rv |= 8;

	if (len == 0)	// 最終的にファイル名が無くなればエラー
		return 16;
	return rv;
}

// 修復中のテンポラリ・ファイルの名前を作る
// 末尾に _par.tmp を追加する (ADD_LEN 以下の文字数)
void get_temp_name(
	wchar_t *file_path,		// ファイル・パス
	wchar_t *temp_path)		// テンポラリ・ファイルのパス
{
	wcscpy(temp_path, file_path);
	wcscat(temp_path, L"_par.tmp");	// 末尾に追加する
}

void print_hash(unsigned char hash[16])
{
	printf("%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X",
		hash[0], hash[1], hash[2], hash[3], hash[4], hash[5], hash[6], hash[7],
		hash[8], hash[9], hash[10], hash[11], hash[12], hash[13], hash[14], hash[15]);
}

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

unsigned int memory_use;	// メモリー使用量 0=auto, 1～7 -> 1/8 ～ 7/8

// 空きメモリー量と制限値から使用できるメモリー量を計算する
unsigned int get_mem_size(unsigned __int64 data_size)
{
	unsigned int mem_size, *tmp_p;
	MEMORYSTATUSEX statex;

	statex.dwLength = sizeof(statex);
	if (GlobalMemoryStatusEx(&statex)){
		unsigned __int64 mem_size64, cache_size;
/*
		printf("MemoryLoad    = %d\n", statex.dwMemoryLoad);
		printf("TotalPhys     = %10I64d\n", statex.ullTotalPhys);
		printf("AvailPhys     = %10I64d\n", statex.ullAvailPhys);
		printf("TotalPageFile = %10I64d\n", statex.ullTotalPageFile);
		printf("AvailPageFile = %10I64d\n", statex.ullAvailPageFile);
		printf("TotalVirtual  = %10I64d\n", statex.ullTotalVirtual);
		printf("AvailVirtual  = %10I64d\n", statex.ullAvailVirtual);
*/
		cache_size = statex.ullAvailPhys >> 3;	// available size / 8
		if (data_size + cache_size < statex.ullAvailPhys){	// all file data can be cached in memory
			mem_size64 = statex.ullAvailPhys - data_size;	// using size = available size - data size
			if (memory_use){	// limited upto ?/8
				cache_size *= memory_use;
				if (mem_size64 > cache_size)
					mem_size64 = cache_size;
			}
		} else {
			if (memory_use){	// manual setting
				mem_size64 = cache_size * memory_use;	// using size = available size / 8 * memory_use
			} else {	// auto setting (mostly for 32-bit OS)
				// disk cache size is range from 100MB to 200MB.
				cache_size += statex.ullTotalPhys >> 4;	// cache size = available size / 8 + total size / 16
				if (cache_size > statex.ullAvailPhys >> 2)
					cache_size = statex.ullAvailPhys >> 2;	// cache size should be less than 1/4 of available size.
				mem_size64 = statex.ullAvailPhys - cache_size;	// using size = available size - cache size
			}
		}

		if (mem_size64 + 0x00200000 > statex.ullAvailVirtual)
			mem_size64 = statex.ullAvailVirtual - 0x00200000;	// keep 2MB for other task
		if (mem_size64 >= 0x7F000000){	// max is 2032MB
			mem_size = 0x7F000000;
		} else {
			mem_size = (unsigned int)mem_size64 & 0xFFFF0000;	// multiple of 64KB
		}

	} else {
		print_win32_err();
		mem_size = 0x20000000;	// メモリー量を認識できない場合は 512MB と仮定する
	}

	// try to allocate for test
	//printf("\nget_mem_size: try %d MB ... ", mem_size >> 20);
	tmp_p = malloc(mem_size);
	while ((tmp_p == NULL) && (mem_size > 0x08000000)){	// if over than 128MB
		mem_size -= 0x01000000;	// reduce 16MB
		tmp_p = malloc(mem_size);
	}
	if (tmp_p != NULL)
		free(tmp_p);
	//printf("%d MB ok\n", mem_size >> 20);

	return mem_size;
}

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

#define MAX_NAME_LEN	69	// 経過表示のタイトルの最大文字数 (末尾の null 文字を含む)

int prog_last;	// 前回と同じ進捗状況は出力しないので記録しておく

// ファイル・パスを短縮されたファイル名だけにしてコピーする
// ASCII 文字は 1文字, それ以外は 2文字として数えることに注意！
static void copy_filename(wchar_t *out, wchar_t *in)
{
	int i, len, name_off, ext_off, ext_len;

	// ファイル名部分の文字数を数える
	name_off = 0;
	ext_off = 0;
	len = 0;
	for (i = 0; in[i] != 0; i++){
		if ((in[i] == '\\') && (in[i + 1] != 0)){
			len = 0;
			name_off = i + 1;
		} else {
			if (in[i] < 0x80){
				len += 1;
				if ((in[i] == '.') && (in[i + 1] != 0))
					ext_off = i;
			} else {
				len += 2;
				ext_off = 0;	// 拡張子は ASCII文字だけにする
			}
		}
	}
	if (len < MAX_NAME_LEN){
		wcscpy(out, in + name_off);
		return;
	}

	// 拡張子の長さを調べる
	ext_len = 0;
	if (ext_off > name_off){
		while (in[ext_off + ext_len] != 0)
			ext_len++;
		if (ext_len >= EXT_LEN)
			ext_len = 0;
	}

	// ファイル名が長すぎる場合は、末尾を省いて拡張子を追加する
	len = 0;
	for (i = 0; in[name_off + i] != 0; i++){
		if (in[name_off + i] < 0x80){
			if (len >= MAX_NAME_LEN - 2 - ext_len)
				break;
			len += 1;
		} else {
			if (len >= MAX_NAME_LEN - 3 - ext_len)
				break;
			len += 2;
		}
		out[i] = in[name_off + i];
	}
	out[i++] = '~';
	while (ext_len > 0){
		out[i++] = in[ext_off++];
		ext_len--;
	}
	out[i] = 0;
}

// 経過のパーセントを表示する
// 普段は 0 を返す、キャンセル時は 0以外
int print_progress(int prog_now)	// 表示する % 値
{
	if (prog_now < 0)	// 範囲外なら
		return 0;

	if (_kbhit()){	// キー入力があるか
		int ch = _getch();
		if ((ch == 'c') || (ch == 'C')){	// Cancel
			printf("\nCancel\n");
			return 2;
		}

		// 一時停止と再開の処理
		if ((ch == 'p') || (ch == 'P')){	// Pause
			printf(" Pause\r");	// パーセントを上書きする
			do {
				ch = _getch();	// 再度入力があるまで待つ、CPU 占有率 0%
				if ((ch == 'c') || (ch == 'C')){	// 停止中でもキャンセルは受け付ける
					printf("\nCancel\n");
					return 2;
				}
			} while ((ch != 'r') && (ch != 'R'));	// Resume
			prog_last = -1;	// Pause の文字を上書きする
		}
	}

	// 前回と同じ進捗状況は出力しない
	if (prog_now == prog_last)
		return 0;
	printf("%3d.%d%%\r", prog_now / 10, prog_now % 10);
	prog_last = prog_now;
	fflush(stdout);

	return 0;
}

// 経過のパーセントとテキストを表示する
void print_progress_text(int prog_now, char *text)
{
	if (prog_now < 0)	// 範囲外なら
		return;
	printf("%3d.%d%% : %s\r", prog_now / 10, prog_now % 10, text);
	prog_last = prog_now;
	fflush(stdout);
}

// 経過のパーセントやファイル名を表示する
int print_progress_file(int prog_now, wchar_t *file_name)
{
	if (prog_now < 0)	// 範囲外なら
		return 0;

	if (_kbhit()){	// キー入力があるか
		int ch = _getch();
		if ((ch == 'c') || (ch == 'C')){	// Cancel
			if (prog_last >= 0)
				printf("\n");
			printf("Cancel\n");
			return 2;
		}

		// 一時停止と再開の処理
		if ((ch == 'p') || (ch == 'P')){	// Pause
			printf(" Pause\r");	// パーセントを上書きする
			do {
				ch = _getch();	// 再度入力があるまで待つ、CPU 占有率 0%
				if ((ch == 'c') || (ch == 'C')){	// 停止中でもキャンセルは受け付ける
					printf("\nCancel\n");
					return 2;
				}
			} while ((ch != 'r') && (ch != 'R'));	// Resume
			if (prog_now == prog_last)
				prog_last = prog_now + 1;	// Pause の文字を上書きする
		}
	}

	if ((prog_last < 0) && (file_name != NULL)){	// 初めて経過を表示する時だけファイル名を表示する
		char text[MAX_NAME_LEN * 3];
		wchar_t short_name[MAX_NAME_LEN];

		copy_filename(short_name, file_name);
		utf16_to_cp(short_name, text);
		printf("%3d.%d%% : \"%s\"\r", prog_now / 10, prog_now % 10, text);
	} else if (prog_now == prog_last){	// 前回と同じ進捗状況は出力しない
		return 0;
	} else {
		printf("%3d.%d%%\r", prog_now / 10, prog_now % 10);
	}
	prog_last = prog_now;
	fflush(stdout);

	return 0;
}

void print_progress_done(void)	// 終了と改行を表示する
{
	if (prog_last >= 0){	// そもそも経過表示がなかった場合は表示しない
		if (prog_last != 1000){
			printf("100.0%%\n");
		} else {
			printf("\n");
		}
		fflush(stdout);
		prog_last = -1;	// 進捗状況をリセットする
	}
}

// キャンセルと一時停止を行う
int cancel_progress(void)
{
	if (_kbhit()){	// キー入力があるか
		int ch = _getch();
		if ((ch == 'c') || (ch == 'C')){	// Cancel
			printf("Cancel\n");
			return 2;
		}

		// 一時停止と再開の処理
		if ((ch == 'p') || (ch == 'P')){	// Pause
			printf(" Pause\r");
			do {
				ch = _getch();	// 再度入力があるまで待つ、CPU 占有率 0%
				if ((ch == 'c') || (ch == 'C')){	// 停止中でもキャンセルは受け付ける
					printf("Cancel\n");
					return 2;
				}
			} while ((ch != 'r') && (ch != 'R'));	// Resume
		}
	}

	return 0;
}

// Win32 API のエラー・メッセージを表示する
void print_win32_err(void)
{
	unsigned int en;
	LPVOID lpMsgBuf = NULL;

	en = GetLastError();
	if (cp_output == CP_UTF8){
		if (FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM |
				FORMAT_MESSAGE_IGNORE_INSERTS | FORMAT_MESSAGE_MAX_WIDTH_MASK,
				NULL, en, 0, (LPWSTR) &lpMsgBuf, 0, NULL) > 0){
			char buf[MAX_LEN * 3];
			// エンコードを UTF-16 から UTF-8 に変換する
			utf16_to_cp(lpMsgBuf, buf);
			printf("0x%X, %s\n", en, buf);
		}
	} else {
		if (FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM |
				FORMAT_MESSAGE_IGNORE_INSERTS | FORMAT_MESSAGE_MAX_WIDTH_MASK,
				NULL, en, 0, (LPSTR) &lpMsgBuf, 0, NULL) > 0){
			printf("0x%X, %s\n", en, (char *)lpMsgBuf);
		}
	}
	if (lpMsgBuf != NULL)
		LocalFree(lpMsgBuf);
}

// ファイルをゴミ箱に移す
int delete_file_recycle(wchar_t *file_path)
{
	wchar_t temp_path[MAX_PATH + PREFIX_LEN];
	int rv;
	SHFILEOPSTRUCT FileOp;

	// "\\?\" が付いてると ShellAPI が動作しないので取り除く
	rv = len_without_prefix(file_path);
	if (rv >= MAX_PATH - 1){	// パスの文字数が MAX_PATH - 1 以下でないといけない
		// 短いパス名に変換してみる
		rv = GetShortPathName(file_path, temp_path, MAX_PATH + PREFIX_LEN);
		if ((rv == 0) || (rv >= MAX_PATH + PREFIX_LEN))
			return 1;	// パスが長すぎで短縮できない？
		// 更にプリフィックスを取り除く
		rv = copy_without_prefix(temp_path, temp_path);
	} else {
		rv = copy_without_prefix(temp_path, file_path);
	}
	temp_path[rv + 1] = 0;	// 末尾にもう一個 null 文字を追加する

	memset(&FileOp, 0, sizeof(FileOp));
	FileOp.wFunc = FO_DELETE;
	FileOp.pFrom = temp_path;
	FileOp.fFlags = FOF_ALLOWUNDO | FOF_NOCONFIRMATION | FOF_NOERRORUI | FOF_SILENT;
	rv = SHFileOperation(&FileOp);
	if (FileOp.fAnyOperationsAborted == TRUE)
		rv = 1;	// 削除がキャンセルされた
	return rv;
}

// エクスプローラーで隠しファイルを表示する設定になってるか調べる
unsigned int get_show_hidden(void)
{
	unsigned int rv;
	SHELLSTATE ssf;

	rv = FILE_ATTRIBUTE_HIDDEN;
	// Explorer の設定を調べる
	SHGetSetSettings(&ssf, SSF_SHOWALLOBJECTS | SSF_SHOWSUPERHIDDEN, FALSE);
	// 隠しファイルを表示するかどうか
	if (ssf.fShowAllObjects){	// 表示する設定なら
		rv = FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_SYSTEM;

		// 保護されたオペレーティングシステムファイルを表示するかどうか
		if (ssf.fShowSuperHidden)	// 表示する設定なら
			rv = INVALID_FILE_ATTRIBUTES;
	}

	return rv;
}

