From 4dcc2fe7756374a88a3758a4b9556f40558a583e Mon Sep 17 00:00:00 2001 From: Yutaka Sawada <60930312+Yutaka-Sawada@users.noreply.github.com> Date: Sun, 12 Mar 2023 11:07:38 +0900 Subject: [PATCH] Add files via upload --- source/sfv_md5/common.c | 1015 ++++++++++++++++++++++++++++++++ source/sfv_md5/common.h | 139 +++++ source/sfv_md5/crc.c | 51 ++ source/sfv_md5/crc.h | 7 + source/sfv_md5/create.c | 181 ++++++ source/sfv_md5/create.h | 15 + source/sfv_md5/ini.c | 414 +++++++++++++ source/sfv_md5/ini.h | 34 ++ source/sfv_md5/main.c | 861 +++++++++++++++++++++++++++ source/sfv_md5/phmd5.c | 79 +++ source/sfv_md5/phmd5.h | 59 ++ source/sfv_md5/phmd5a.c | 130 ++++ source/sfv_md5/res_sfv_md5.rc | Bin 0 -> 902 bytes source/sfv_md5/sfv_md5.vcxproj | 88 +++ source/sfv_md5/verify.c | 969 ++++++++++++++++++++++++++++++ source/sfv_md5/verify.h | 11 + source/sfv_md5/version.h | 2 + 17 files changed, 4055 insertions(+) create mode 100644 source/sfv_md5/common.c create mode 100644 source/sfv_md5/common.h create mode 100644 source/sfv_md5/crc.c create mode 100644 source/sfv_md5/crc.h create mode 100644 source/sfv_md5/create.c create mode 100644 source/sfv_md5/create.h create mode 100644 source/sfv_md5/ini.c create mode 100644 source/sfv_md5/ini.h create mode 100644 source/sfv_md5/main.c create mode 100644 source/sfv_md5/phmd5.c create mode 100644 source/sfv_md5/phmd5.h create mode 100644 source/sfv_md5/phmd5a.c create mode 100644 source/sfv_md5/res_sfv_md5.rc create mode 100644 source/sfv_md5/sfv_md5.vcxproj create mode 100644 source/sfv_md5/verify.c create mode 100644 source/sfv_md5/verify.h create mode 100644 source/sfv_md5/version.h diff --git a/source/sfv_md5/common.c b/source/sfv_md5/common.c new file mode 100644 index 0000000..6861679 --- /dev/null +++ b/source/sfv_md5/common.c @@ -0,0 +1,1015 @@ +// common.c +// Copyright : 2022-01-20 Yutaka Sawada +// License : The MIT license + +#ifndef _UNICODE +#define _UNICODE +#endif +#ifndef UNICODE +#define UNICODE +#endif +#ifndef _WIN32_WINNT +#define _WIN32_WINNT 0x0600 // Windows Vista or later +#endif + +#include +#include + +#include + +#include "common.h" + + +// グローバル変数 +wchar_t checksum_file[MAX_LEN]; // チェックサム・ファイルのパス +wchar_t base_dir[MAX_LEN]; // ソース・ファイルの基準ディレクトリ +wchar_t ini_path[MAX_LEN]; // 検査結果ファイルのパス + +int base_len; // ソース・ファイルの基準ディレクトリの長さ +int file_num; // ソース・ファイルの数 + +// 可変長サイズの領域にテキストを保存する +wchar_t *text_buf; // チェックサム・ファイルのテキスト内容 +int text_len; // テキストの文字数 +int text_max; // テキストの最大文字数 + +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +unsigned int cp_output; // Console Output Code Page + +// 指定された Code Page から UTF-16 に変換する +int cp_to_utf16(char *in, wchar_t *out, int max_size, unsigned int cp) +{ + int err = 0; + unsigned int dwFlags = MB_ERR_INVALID_CHARS; // 不適切な文字列を警告する + + if (cp == CP_UTF8) + dwFlags = 0; + + if (MultiByteToWideChar(cp, dwFlags, in, -1, out, max_size) == 0){ + err = GetLastError(); + if ((dwFlags != 0) && (err == ERROR_INVALID_FLAGS)){ + //printf("MultiByteToWideChar, ERROR_INVALID_FLAGS\n"); + if (MultiByteToWideChar(cp, 0, in, -1, out, max_size) != 0) + return 0; + err = GetLastError(); + } + wcscpy(out, L"cannot encode"); + return err; + } + return 0; +} + +// Windown OS の UTF-16 から指定された Code Page に変換する +int utf16_to_cp(wchar_t *in, char *out, int max_size, unsigned int cp) +{ + unsigned int dwFlags = WC_NO_BEST_FIT_CHARS; // 似た文字への自動変換を行わない + + if (cp == CP_UTF8) + dwFlags = 0; + + if (WideCharToMultiByte(cp, dwFlags, in, -1, out, max_size, NULL, NULL) == 0){ + if ((dwFlags != 0) && (GetLastError() == ERROR_INVALID_FLAGS)){ + //printf("WideCharToMultiByte, ERROR_INVALID_FLAGS\n"); + if (WideCharToMultiByte(cp, 0, in, -1, out, max_size, NULL, NULL) != 0) + return 0; + } + strcpy(out, "cannot encode"); + return 1; + } + return 0; +} + +// 文字列が UTF-8 かどうかを判定する (0 = maybe UTF-8) +int check_utf8(unsigned char *text) +{ + unsigned char c1; + int tail_len = 0; // UTF8-tail が何バイト続くか + + while (*text != 0){ + c1 = *text; + // 禁止 0xC0~0xC1,0xF5~0xFF + if ((c1 == 0xC0) || (c1 == 0xC1) || (c1 >= 0xF5)) + return 1; // 禁止文字 + if (tail_len == 0){ // 第1バイトなら + // 1バイト文字 + if (c1 <= 0x7F){ + tail_len = 0; + // 2バイト文字の第1バイト + } else if ((c1 >= 0xC2) && (c1 <= 0xDF)){ + tail_len = 1; + // 3バイト文字の第1バイト + } else if ((c1 >= 0xE0) && (c1 <= 0xEF)){ + tail_len = 2; + // 4バイト文字の第1バイト + } else if (c1 >= 0xF0){ + tail_len = 3; + } else { + return 2; // 第1バイトとして不適当な文字 + } + } else { // 第2バイト以後なら + if ((c1 >= 0x80) && (c1 <= 0xBF)){ + tail_len--; + } else { + return 2; // 第2バイト以後として不適当な文字 + } + } + text++; // 次の文字へ + } + return 0; +} + +// 文字列が UTF-16 かどうかを判定して変換する +int utf16_to_utf16(unsigned char *in, int len, wchar_t *out) +{ + unsigned char *text; + int i; + int little_endian = 0, space_mark = 0, return_mark = 0; + + if (len % 2) + return 1; // サイズは 2の倍数のはず + + // BOM を探す + if ((in[0] == 0xFF) && (in[1] == 0xFE)){ + text = in + 2; + little_endian = 1; // UTF-16LE + } else if ((in[0] == 0xFE) && (in[1] == 0xFF)){ + text = in + 2; + } else { + text = in; + } + + // 必ず存在するはずのスペースと改行文字が一致するか調べる + if (little_endian == 1){ + for (i = 0; i < len; i += 2){ + if ((text[i] == ' ') && (text[i + 1] == 0)) + space_mark++; + if (((text[i] == '\n') || (text[i] == '\r'))&& (text[i + 1] == 0)) + return_mark++; + } + } else { + for (i = 0; i < len; i += 2){ + if ((text[i + 1] == 0) && (text[i] == ' ')) + space_mark++; + if ((text[i + 1] == 0) && ((text[i] == '\n') || (text[i] == '\r'))) + return_mark++; + } + } + if ((space_mark == 0) && (return_mark == 0)) + return 1; + + // 変換する + if (little_endian == 1){ + // Little Endian ならそのままコピーする + memcpy(out, text, len); + } else { + // Big Endian なら上位と下位を入れ替える + for (i = 0; i < len; i += 2){ + out[i / 2] = ((unsigned short)(text[i]) << 8) || ((unsigned short)(text[i + 1])); + } + } + + return 0; +} + +// ファイル・パスから、先頭にある "\\?\" を省いて、指定された Code Page に変換する +int path_to_cp(wchar_t *path, char *out, unsigned int cp) +{ + unsigned int dwFlags = WC_NO_BEST_FIT_CHARS; // 似た文字への自動変換を行わない + + if (cp == 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, dwFlags, path, -1, out, MAX_LEN * 3, NULL, NULL) == 0){ + if ((dwFlags != 0) && (GetLastError() == ERROR_INVALID_FLAGS)){ + //printf("WideCharToMultiByte, ERROR_INVALID_FLAGS\n"); + if (WideCharToMultiByte(cp, 0, path, -1, out, MAX_LEN * 3, NULL, NULL) != 0) + return 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, cp_output); + printf(format, buf); +} + +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +// ファイルの offset バイト目から size バイトのデータを buf に読み込む +int file_read_data( + HANDLE hFileRead, + __int64 offset, + unsigned char *buf, + unsigned int size) +{ + unsigned int rv; + + // ファイルの位置を offsetバイト目にする + if (!SetFilePointerEx(hFileRead, *((PLARGE_INTEGER)&offset), NULL, FILE_BEGIN)){ + print_win32_err(); + return 1; + } + + // size バイトを読み込む + if (!ReadFile(hFileRead, buf, size, &rv, NULL)){ + print_win32_err(); + return 1; + } + if (size != rv) + return 1; // 指定サイズを読み込めなかったらエラーになる + + return 0; +} + +// ファイルの offset バイト目に size バイトのデータを buf から書き込む +int file_write_data( + HANDLE hFileWrite, + __int64 offset, + unsigned char *buf, + unsigned int size) +{ + unsigned int rv; + + // ファイルの位置を offsetバイト目にする + if (!SetFilePointerEx(hFileWrite, *((PLARGE_INTEGER)&offset), NULL, FILE_BEGIN)){ + print_win32_err(); + return 1; + } + + // size バイトを書き込む + if (!WriteFile(hFileWrite, buf, size, &rv, NULL)){ + print_win32_err(); + return 1; + } + + return 0; +} + +// ファイルの offset バイト目に size バイトの指定値を書き込む +int file_fill_data( + HANDLE hFileWrite, + __int64 offset, + unsigned char value, + unsigned int size) +{ + unsigned char buf[IO_SIZE]; + unsigned int rv, len; + + // ファイルの位置を offsetバイト目にする + if (!SetFilePointerEx(hFileWrite, *((PLARGE_INTEGER)&offset), NULL, FILE_BEGIN)){ + print_win32_err(); + return 1; + } + + // 指定された値で埋める + memset(buf, value, IO_SIZE); + while (size){ + len = IO_SIZE; + if (size < IO_SIZE) + len = size; + size -= len; + if (!WriteFile(hFileWrite, buf, len, &rv, NULL)){ + print_win32_err(); + return 1; + } + } + + return 0; +} + +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +// ファイル・パスがファイル・リスト上に既に存在するか調べる +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); +} + +// テキストに新しい文字列を追加する +int add_text(wchar_t *new_text) // 追加するテキスト +{ + wchar_t *tmp_p; + int len; + + len = wcslen(new_text); + + if (text_len + len >= text_max){ // 領域が足りなくなるなら拡張する + text_max += ALLOC_LEN; + tmp_p = (wchar_t *)realloc(text_buf, text_max * 2); + if (tmp_p == NULL){ + return 1; + } else { + text_buf = tmp_p; + } + } + + wcscpy(text_buf + text_len, new_text); + text_len += len; + + return 0; +} + +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +// ファイル・パスからファイル名の位置を戻す +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; +} + +// ディレクトリ記号の「\」を「/」に置換する +void unix_directory(wchar_t *path) +{ + wchar_t *tmp_p; + + tmp_p = wcschr(path, '\\'); + while (tmp_p != NULL){ + *tmp_p = '/'; + tmp_p = wcschr(tmp_p, '\\'); + } +} + +// 絶対パスかどうかを判定する +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 パスなら「\\\」 + if (((path[0] == '\\') || (path[0] == '/')) && ((path[1] == '\\') || (path[1] == '/'))) + return 1; + + return 0; +} + +// デバイス名かどうかを判定する +static int check_device_name(wchar_t *name, int len) +{ + if (len >= 3){ + if ((name[3] == 0) || (name[3] == '.') || (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] == '.') || (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=変更無し, +1=ディレクトリ記号, +2=浄化した, +4=文字数が変わった, +8=警告, 16=エラー +int sanitize_filename(wchar_t *name) +{ + int i, j, rv = 0, len = 0, off; + + // 制御文字 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){ + name[0] = 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] = '_'; // 「*,?,",<,>,|」 ->「_」 + rv |= 2; + } else if (name[i] == ':'){ // NTFS の「alternate data stream」の区切りに「:」が使われる + for (j = i + 1; j < len; j++){ + // 「:」の後にディレクトリ記号や再度同じのが出現するのは駄目 + if ((name[j] == '\\') || (name[j] == '/') || (name[j] == ':')){ + j = -1; + break; + } + } + if ((i == 0) || (i == len - 1) || (j < 0)){ + name[i] = '_'; // 「:」 ->「_」 + rv |= 2; + } + } else if (name[i] == '/'){ // ディレクトリ記号を変換する + name[i] = '\\'; // 「/」 -> 「\」 + rv |= 1; + } + } + + // 先頭の「 」、末尾の「.」「 」を警告する + if ((name[0] == ' ') || (name[len - 1] == '.') || (name[len - 1] == ' ')) + rv |= 8; + // 「\」の前後に「 」があれば警告する + i = len - 1; + while (i > 0){ + if ((name[i] == '\\') && ((name[i - 1] == ' ') || (name[i + 1] == ' '))) + rv |= 8; + i--; + } + + // 「\」の前に「.」があれば削除する (ディレクトリ移動を防ぐ) + // ただし、SFV/MD5 ではファイルを書き換えないので、親ディレクトリへの移動は許可する。 + off = 0; + while ((name[off] == '.') && (name[off + 1] == '.') && (name[off + 2] == '\\')) + off += 3; + i = len - 1; + while (i > off){ // 先に許可した所まで遡る。 + if ((name[i] == '\\') && (name[i - 1] == '.')){ + for (j = i - 1; j < len; j++) + name[j] = name[j + 1]; + len--; + rv |= 4; + } + i--; + } + + // 連続した「\」を一文字にする + i = 0; + while (i < len){ + if ((name[i] == '\\') && (name[i + 1] == '\\')){ + for (j = i + 1; j < len; j++) + name[j] = name[j + 1]; + len--; + rv |= 4; + } else { + i++; + } + } + + // 先頭の「\」を削除する + if (name[0] == '\\'){ + for (i = 0; i < len; i++) + name[i] = name[i + 1]; + len--; + rv |= 4; + } + + // デバイス名を警告する (サブ・ディレクトリも含める) + i = len; + while (i > 0){ + i--; + if ((i == 0) || ((i > 0) && (name[i - 1] == '\\'))){ + if (check_device_name(name + i, len) != 0) + rv |= 8; + } + } + + if (len == 0){ // 最終的にファイル名が無くなればエラー + name[0] = 0; + return 16; + } + return rv; +} + +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#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\\" ならネットワーク・パス + if (wcsncmp(file_path + PREFIX_LEN, L"UNC\\", 4) == 0) + return wcslen(file_path + PREFIX_LEN + 2); // "\\?\UNC\" -> "\\" なので6文字減らす + + // "\\?\\" なら + return wcslen(file_path + PREFIX_LEN); // "\\?\" を省くので4文字減らす +} + +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +// ユニコードの16進数文字列から数値を読み取る +unsigned int get_val32h(wchar_t *s){ + int i; + unsigned int v, ret = 0; + + for (i = 0; i < 8; i++){ + v = s[i]; + if ((v >= 0x30) && (v <= 0x39)){ // 0 to 9 + ret = (ret * 16) + (v - 0x30); + } else if ((v >= 0x61) && (v <= 0x66)){ // a to f + ret = (ret * 16) + (v + 10 - 0x61); + } else if ((v >= 0x41) && (v <= 0x46)){ // A to F + ret = (ret * 16) + (v + 10 - 0x41); + } else { // 数字以外の文字に出会ったら終わる + break; + } + } + return ret; +} + +// 16進数の文字が何個続いてるか +unsigned int base16_len(wchar_t *s) +{ + unsigned int v, len; + + len = 0; + while (len <= 32){ + v = s[len]; + if ((v >= 0x30) && (v <= 0x39)){ // 0 to 9 + len++; + } else if ((v >= 0x61) && (v <= 0x66)){ // a to f + len++; + } else if ((v >= 0x41) && (v <= 0x46)){ // A to F + len++; + } else { // 数字以外の文字に出会ったら終わる + break; + } + } + return len; +} + +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#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){ // 初めて経過を表示する時だけファイル名を表示する + 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, sizeof(text), cp_output); + 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; // 進捗状況をリセットする + } +} + +// 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, sizeof(buf), CP_UTF8); + 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); +} + diff --git a/source/sfv_md5/common.h b/source/sfv_md5/common.h new file mode 100644 index 0000000..32b5f02 --- /dev/null +++ b/source/sfv_md5/common.h @@ -0,0 +1,139 @@ + +#define MAX_LEN 1024 // ファイル名の最大文字数 (末尾のNULL文字も含む) +#define ADD_LEN 32 // 作業中にファイル名に追加する文字数 +#define EXT_LEN 16 // 拡張子として認識する最大文字数 +#define COMMENT_LEN 128 // コメントの最大文字数 +#define ALLOC_LEN 4096 // 可変長領域を何バイトごとに確保するか +#define IO_SIZE 65536 // 16384 以上にすること +#define UPDATE_TIME 1024 // 更新間隔 ms + +// グローバル変数 +extern wchar_t checksum_file[MAX_LEN]; // チェックサム・ファイルのパス +extern wchar_t base_dir[MAX_LEN]; // ソース・ファイルの基準ディレクトリ +extern wchar_t ini_path[MAX_LEN]; // 検査結果ファイルのパス + +extern int base_len; // ソース・ファイルの基準ディレクトリの長さ +extern int file_num; // ソース・ファイルの数 + +// 可変長サイズの領域にテキストを保存する +extern wchar_t *text_buf; // チェックサム・ファイルのテキスト内容 +extern int text_len; // テキストの文字数 +extern int text_max; // テキストの最大文字数 + +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +extern unsigned int cp_output; // Console Output Code Page + +// 指定された Code Page から UTF-16 に変換する +int cp_to_utf16(char *in, wchar_t *out, int max_size, unsigned int cp); + +// Windown OS の UTF-16 から指定された Code Page に変換する +int utf16_to_cp(wchar_t *in, char *out, int max_size, unsigned int cp); + +// 文字列が UTF-8 かどうかを判定する (0 = maybe UTF-8) +int check_utf8(unsigned char *text); + +// 文字列が UTF-16 かどうかを判定して変換する +int utf16_to_utf16(unsigned char *in, int len, wchar_t *out); + +// UTF-16 のファイル・パスを画面出力用の Code Page を使って表示する +void printf_cp(unsigned char *format, wchar_t *path); + +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +// ファイルの offset バイト目から size バイトのデータを buf に読み込む +int file_read_data( + HANDLE hFileRead, + __int64 offset, + unsigned char *buf, + unsigned int size); + +// ファイルの offset バイト目に size バイトのデータを buf から書き込む +int file_write_data( + HANDLE hFileWrite, + __int64 offset, + unsigned char *buf, + unsigned int size); + +// ファイルの offset バイト目に size バイトの指定値を書き込む +int file_fill_data( + HANDLE hFileWrite, + __int64 offset, + unsigned char value, + unsigned int size); + +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +// ファイル・パスがファイル・リスト上に既に存在するか調べる +int search_file_path( + wchar_t *list, // ファイル・リスト + int total_len, // ファイル・リストの文字数 + wchar_t *search_file); // 検索するファイルのパス + +// ファイル・リストの内容を並び替える +void sort_list( + wchar_t *list, // ファイル・リスト + int total_len); // ファイル・リストの文字数 + +// テキストに新しい文字列を追加する +int add_text(wchar_t *new_text); // 追加するテキスト + +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +// ファイル・パスからファイル名の位置を戻す +wchar_t * offset_file_name(wchar_t *file_path); + +// ファイル・パスからファイル名だけ取り出す +void get_file_name( + wchar_t *file_path, // ファイル・パス + wchar_t *file_name); // ファイル名 + +// ファイル・パスからディレクトリだけ取り出す、末尾は「\」か「/」 +void get_base_dir( + wchar_t *file_path, // ファイル・パス + wchar_t *base_path); // ディレクトリ + +// ディレクトリ記号の「\」を「/」に置換する +void unix_directory(wchar_t *path); + +// 絶対パスかどうかを判定する +int is_full_path(wchar_t *path); + +// ファイル名が有効か確かめて、問題があれば浄化する +int sanitize_filename(wchar_t *name); + +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +// 相対パスを絶対パスに変換し、パスの先頭に "\\?\" を追加する +// 戻り値 : 0=エラー, 5~=新しいパスの長さ +int copy_path_prefix( + wchar_t *new_path, // 新しいパス + int max_len, // 新しいパスの最大長さ (末尾の null文字も含む) + wchar_t *src_path, // 元のパス (相対パスでもよい) + wchar_t *dir_path); // 相対パスの場合に基準となるディレクトリ (NULL ならカレント・ディレクトリ) + +// ファイル・パスから、先頭にある "\\?\" を省いた長さを戻す +int len_without_prefix(wchar_t *file_path); + +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +// ユニコードの16進数文字列から数値を読み取る +unsigned int get_val32h(wchar_t *s); + +// 16進数の文字が何個続いてるか +unsigned int base16_len(wchar_t *s); + +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +extern int prog_last; // 前回と同じ進捗状況は出力しないので記録しておく + +// 経過のパーセント表示、キャンセルと一時停止ができる +// 普段は 0 を返す、キャンセル時は 0以外 +int print_progress(int prog_now); +void print_progress_text(int prog_now, char *text); +int print_progress_file(int prog_now, wchar_t *file_name); +void print_progress_done(void); + +// Win32 API のエラー・メッセージを表示する +void print_win32_err(void); + diff --git a/source/sfv_md5/crc.c b/source/sfv_md5/crc.c new file mode 100644 index 0000000..56a1540 --- /dev/null +++ b/source/sfv_md5/crc.c @@ -0,0 +1,51 @@ +// crc.c +// Copyright : 2021-05-14 Yutaka Sawada +// License : The MIT license + +#include "crc.h" + +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ +// CRC-32 計算用 + +// CRC-32 +#define CRC_POLY 0xEDB88320 // (little endian) +unsigned int crc_table[256]; + +// CRC 計算用のテーブルを作る +void init_crc_table(void) +{ + unsigned int i, j, r; + + for (i = 0; i < 256; i++){ // CRC-32 + r = i; + for (j = 0; j < 8; j++) + r = (r >> 1) ^ (CRC_POLY & ~((r & 1) - 1)); + crc_table[i] = r; + } +} + +// CRC-32 を更新する +unsigned int crc_update(unsigned int crc, unsigned char *buf, unsigned int len) +{ +/* + while (len--) + crc = crc_table[(crc & 0xFF) ^ (*buf++)] ^ (crc >> 8); +*/ + // 4バイトごとに計算する + while (len >= 4){ + crc ^= *((unsigned int *)buf); + crc = crc_table[crc & 0xFF] ^ (crc >> 8); + crc = crc_table[crc & 0xFF] ^ (crc >> 8); + crc = crc_table[crc & 0xFF] ^ (crc >> 8); + crc = crc_table[crc & 0xFF] ^ (crc >> 8); + len -= 4; + buf += 4; + } + + // 余りは 1バイトずつ計算する + while (len--) + crc = crc_table[(crc & 0xFF) ^ (*buf++)] ^ (crc >> 8); + + return crc; +} + diff --git a/source/sfv_md5/crc.h b/source/sfv_md5/crc.h new file mode 100644 index 0000000..5d4589c --- /dev/null +++ b/source/sfv_md5/crc.h @@ -0,0 +1,7 @@ + +// CRC 計算用のテーブルを作る +void init_crc_table(void); + +// CRC-32 を更新する +unsigned int crc_update(unsigned int crc, unsigned char *buf, unsigned int len); + diff --git a/source/sfv_md5/create.c b/source/sfv_md5/create.c new file mode 100644 index 0000000..43868d5 --- /dev/null +++ b/source/sfv_md5/create.c @@ -0,0 +1,181 @@ +// create.c +// Copyright : 2022-02-16 Yutaka Sawada +// License : The MIT license + +#ifndef _UNICODE +#define _UNICODE +#endif +#ifndef UNICODE +#define UNICODE +#endif +#ifndef _WIN32_WINNT +#define _WIN32_WINNT 0x0600 // Windows Vista or later +#endif + +#include + +#include + +#include "common.h" +#include "crc.h" +#include "create.h" +#include "phmd5.h" + +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +// SFV ファイル +int create_sfv( + wchar_t *uni_buf, + wchar_t *file_name, // 検査対象のファイル名 + __int64 *prog_now, // 経過表示での現在位置 + __int64 total_size) // 合計ファイル・サイズ +{ + unsigned char buf[IO_SIZE]; + unsigned int rv, len, crc, time_last; + __int64 file_size = 0, file_left; + HANDLE hFile; + + time_last = GetTickCount() / UPDATE_TIME; // 時刻の変化時に経過を表示する + + // 読み込むファイルを開く + wcscpy(uni_buf, base_dir); + wcscpy(uni_buf + base_len, file_name); + hFile = CreateFile(uni_buf, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL); + if (hFile == INVALID_HANDLE_VALUE){ + printf("\n"); + print_win32_err(); + return 1; + } + if (!GetFileSizeEx(hFile, (PLARGE_INTEGER)&file_size)){ + printf("\n"); + print_win32_err(); + CloseHandle(hFile); + return 1; + } + file_left = file_size; + + // CRC を計算する + crc = 0xFFFFFFFF; // 初期化 + while (file_left > 0){ + len = IO_SIZE; + if (file_left < IO_SIZE) + len = (unsigned int)file_left; + if (!ReadFile(hFile, buf, len, &rv, NULL) || (len != rv)){ + printf("\n"); + print_win32_err(); + CloseHandle(hFile); + return 1; + } + file_left-= len; + (*prog_now) += len; + // CRC-32 を更新する + crc = crc_update(crc, buf, len); + + // 経過表示 + if (GetTickCount() / UPDATE_TIME != time_last){ + if (print_progress((int)(((*prog_now) * 1000) / total_size))){ + CloseHandle(hFile); + return 2; + } + time_last = GetTickCount() / UPDATE_TIME; + } + } + crc ^= 0xFFFFFFFF; // 最終処理 + CloseHandle(hFile); + + // チェックサムを記録する + len = wcslen(file_name); + for (rv = 0; rv < len; rv++){ + if (file_name[rv] == ' ') + break; + } + if (rv < len){ + uni_buf[0] = '"'; + wcscpy(uni_buf + 1, file_name); // 「"」で囲む + uni_buf[len + 1] = '"'; + uni_buf[len + 2] = 0; + } else { + wcscpy(uni_buf, file_name); // 変換前にコピーする + } + unix_directory(uni_buf); + add_text(uni_buf); + wsprintf(uni_buf, L" %08X\r\n", crc); + add_text(uni_buf); + + return 0; +} + +// MD5 ファイル +int create_md5( + wchar_t *uni_buf, + wchar_t *file_name, // 検査対象のファイル名 + __int64 *prog_now, // 経過表示での現在位置 + __int64 total_size) // 合計ファイル・サイズ +{ + unsigned char buf[IO_SIZE]; + unsigned int rv, len, time_last; + __int64 file_size = 0, file_left; + HANDLE hFile; + PHMD5 ctx; + + time_last = GetTickCount() / UPDATE_TIME; // 時刻の変化時に経過を表示する + + // 読み込むファイルを開く + wcscpy(uni_buf, base_dir); + wcscpy(uni_buf + base_len, file_name); + hFile = CreateFile(uni_buf, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL); + if (hFile == INVALID_HANDLE_VALUE){ + printf("\n"); + print_win32_err(); + return 1; + } + if (!GetFileSizeEx(hFile, (PLARGE_INTEGER)&file_size)){ + printf("\n"); + print_win32_err(); + CloseHandle(hFile); + return 1; + } + file_left = file_size; + + // MD5 を計算する + Phmd5Begin(&ctx); // 初期化 + while (file_left > 0){ + len = IO_SIZE; + if (file_left < IO_SIZE) + len = (unsigned int)file_left; + if (!ReadFile(hFile, buf, len, &rv, NULL) || (len != rv)){ + printf("\n"); + print_win32_err(); + CloseHandle(hFile); + return 1; + } + file_left-= len; + (*prog_now) += len; + // MD5 を更新する + Phmd5Process(&ctx, buf, len); + + // 経過表示 + if (GetTickCount() / UPDATE_TIME != time_last){ + if (print_progress((int)(((*prog_now) * 1000) / total_size))){ + CloseHandle(hFile); + return 2; + } + time_last = GetTickCount() / UPDATE_TIME; + } + } + Phmd5End(&ctx); // 最終処理 + CloseHandle(hFile); + + // チェックサムを記録する + wsprintf(uni_buf, L"%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X *", + ctx.hash[0], ctx.hash[1], ctx.hash[2], ctx.hash[3], ctx.hash[4], ctx.hash[5], ctx.hash[6], ctx.hash[7], + ctx.hash[8], ctx.hash[9], ctx.hash[10], ctx.hash[11], ctx.hash[12], ctx.hash[13], ctx.hash[14], ctx.hash[15]); + add_text(uni_buf); + wcscpy(uni_buf, file_name); // 変換前にコピーする + unix_directory(uni_buf); + add_text(uni_buf); + add_text(L"\r\n"); + + return 0; +} + diff --git a/source/sfv_md5/create.h b/source/sfv_md5/create.h new file mode 100644 index 0000000..8f320d5 --- /dev/null +++ b/source/sfv_md5/create.h @@ -0,0 +1,15 @@ + +// SFV ファイル +int create_sfv( + wchar_t *uni_buf, + wchar_t *file_name, // 検査対象のファイル名 + __int64 *prog_end, // 経過表示での終了位置 + __int64 total_size); // 合計ファイル・サイズ + +// MD5 ファイル +int create_md5( + wchar_t *uni_buf, + wchar_t *file_name, // 検査対象のファイル名 + __int64 *prog_end, // 経過表示での終了位置 + __int64 total_size); // 合計ファイル・サイズ + diff --git a/source/sfv_md5/ini.c b/source/sfv_md5/ini.c new file mode 100644 index 0000000..0816fa2 --- /dev/null +++ b/source/sfv_md5/ini.c @@ -0,0 +1,414 @@ +// ini.c +// Copyright : 2022-02-16 Yutaka Sawada +// License : The MIT license + +#ifndef _UNICODE +#define _UNICODE +#endif +#ifndef UNICODE +#define UNICODE +#endif +#ifndef _WIN32_WINNT +#define _WIN32_WINNT 0x0600 // Windows Vista or later +#endif + +#include +#include + +#include + +#include "common.h" +#include "crc.h" +#include "ini.h" + +/* +ファイル名は 0_#.bin (2+32+4 = 38文字) +「#」部分はチェックサム・ファイルを UTF-16 にエンコードした内容の +MD5ハッシュ値を 16進数表記にする。 + +ファイル・フォーマット + +2: 検査結果の書式バージョン +4: 6バイト目以降からの CRC-32 チェックサム + +4: チェックサム・ファイルの UTF-16 エンコード後の文字数 +4: チェックサム・ファイルに記録されてるソース・ファイルの数 + + +前半 12バイトでファイル項目を識別して、後半 18バイトに状態を保存する。 +4: ボリュームのシリアル番号 +8: ファイルのオブジェクトID +8: ソース・ファイルのサイズ +4: ソース・ファイルの作成日時 +4: ソース・ファイルの更新日時 +2: 下位 1-bit はファイルの状態 0=完全, 1=破損あるいはエラー + 上位 15-bit はソース・ファイル番号 0~32767 + +*/ + +#define INI_VERSION 0x1270 // 検査結果の書式が決まった時のバージョン +#define REUSE_MIN 16384 // ファイル・サイズがこれより大きければ検査結果を利用する (16KB 以上 2GB 未満) +#define HEADER_SIZE 14 +#define STATE_SIZE 30 +#define STATE_READ 136 +//#define VERBOSE 1 // 状態を冗長に出力する + +static HANDLE hIniBin = NULL; // バイナリ・データ用の検査結果ファイル +static int ini_off; + +// 検査結果をどのくらいの期間保存するか +int recent_data = 0; +/* + 0 = 検査結果の再利用機能を無効にする(読み込まないし、記録もしない) +1~7= 前回の検査結果を読み込んで、今回のを記録する。 + 指定された期間よりも経過した古い記録は削除される。 + 1= 1日, 2= 3日, 3= 1週間, 4= 半月, 5= 1ヶ月, 6= 1年, 7= 無制限 + +8 = 同じセットの記録を削除する、今回の結果は記録する。 + 他のセットは指定された期間よりも古いものだけ削除する。 +*/ + +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +// File Time (UTC) を UNIX Time (UTC) に変換する +unsigned int time_f_u(FILETIME *file_time) +{ + unsigned __int64 int8; + + // 1970/01/01 の File Time との差を求める + memcpy(&int8, file_time, 8); + int8 = (int8 - 116444736000000000) / 10000000; + + return (unsigned int)int8; +} + +#ifdef VERBOSE +// File Time (UTC) を日付の文字列に変換する +static void time_f_date(FILETIME *ft, char *buf) +{ + int len; + SYSTEMTIME st_utc, st; + + FileTimeToSystemTime(ft, &st_utc); + if (SystemTimeToTzSpecificLocalTime(NULL, &st_utc, &st) == 0) + memcpy(&st, &st_utc, sizeof(SYSTEMTIME)); + len = GetDateFormatA(LOCALE_USER_DEFAULT, DATE_SHORTDATE | LOCALE_USE_CP_ACP, &st, NULL, buf, 32); + if (len > 0){ + buf[len - 1] = ' '; + len = GetTimeFormatA(LOCALE_USER_DEFAULT, LOCALE_USE_CP_ACP, &st, NULL, buf + len, 32 - len); + } + if (len == 0){ + wsprintfA(buf, "%4d/%02d/%02d %2d:%02d:%02d", + st.wYear, st.wMonth, st.wDay, st.wHour, st.wMinute, st.wSecond); + } +} +#endif + +// 2GB 未満のファイルの開始位置以降のハッシュ値を計算する +static unsigned int file_crc_part(HANDLE hFile) +{ + unsigned char buf[4096]; + unsigned int len, crc = 0xFFFFFFFF; + + // 末尾まで読み込む + do { + if (!ReadFile(hFile, buf, 4096, &len, NULL) || (len == 0)) + break; + // CRC-32 計算 + crc = crc_update(crc, buf, len); + } while (len > 0); + + return crc ^ 0xFFFFFFFF; +} + +// .BIN ファイルの offset バイト目から size バイトのデータを buf に読み込む +static int file_read_bin( + int offset, + unsigned char *buf, + unsigned int size) +{ + unsigned int rv; + + // ファイルの位置を offsetバイト目にする + rv = SetFilePointer(hIniBin, offset, NULL, FILE_BEGIN); + if (rv == INVALID_SET_FILE_POINTER) + return 1; + + // size バイトを読み込む + if ((!ReadFile(hIniBin, buf, size, &rv, NULL)) || (size != rv)) + return 1; // 指定サイズを読み込めなかったらエラーになる + + return 0; +} + +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ +// 検査結果ファイルを利用する為の関数 + +// ini_path で指定されてる検査結果ファイルを削除する +static void delete_ini_file(void) +{ + // 開いてるなら閉じる + if (hIniBin != NULL){ + CloseHandle(hIniBin); + hIniBin = NULL; + } + + DeleteFile(ini_path); // .bin ファイルを削除する + + recent_data = 0; // これ以降は検査結果は利用できない +} + +// 検査するチェックサム・ファイルが同じであれば、再検査する必要は無い +int check_ini_file(unsigned char *set_hash, unsigned int set_len) +{ + unsigned char set_data[HEADER_SIZE * 2]; + wchar_t path[MAX_LEN], ini_name[INI_NAME_LEN + 1]; + int i, match; + unsigned int file_time, time_now, time_limit; + HANDLE hFind; + WIN32_FIND_DATA FindData; + FILETIME ft; + + if (recent_data == 0) + return 0; + + // 設定ファイルのパス + wsprintf(ini_name, L"0_%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X.bin", + set_hash[0], set_hash[1], set_hash[2], set_hash[3], set_hash[4], set_hash[5], set_hash[6], set_hash[7], + set_hash[8], set_hash[9], set_hash[10], set_hash[11],set_hash[12], set_hash[13], set_hash[14], set_hash[15]); + + // 期限を設定する + switch (recent_data & 7){ + case 1: + time_limit = 3600 * 24; // 1 day + break; + case 2: + time_limit = 3600 * 24 * 3; // 3 days + break; + case 3: + time_limit = 3600 * 24 * 7; // 1 week + break; + case 4: + time_limit = 3600 * 24 * 15; // half month + break; + case 5: + time_limit = 3600 * 24 * 30; // 1 month + break; + case 6: + time_limit = 3600 * 24 * 365; // 1 year + break; + case 7: + time_limit = 0xFFFFFFFF; // unlimited + break; + default: // 期間が指定されてない + recent_data = 0; + return 0; + } + GetSystemTimeAsFileTime(&ft); // 現在のシステム時刻 (UTC) を取得する + time_now = time_f_u(&ft); // UNIX Time に変換する + + // 記録の日時を調べて、古いのは削除する + match = 0; + wcscpy(path, ini_path); + wcscat(path, L"0_*.bin"); // 文字数は後でチェックする + hFind = FindFirstFile(path, &FindData); + if (hFind != INVALID_HANDLE_VALUE){ + do { + if (((FindData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) == 0) && + (wcslen(FindData.cFileName) == INI_NAME_LEN)){ + // 指定時間より古い記録は削除する + file_time = time_f_u(&(FindData.ftLastWriteTime)); // 更新日時 + if (time_now - file_time > time_limit){ + wcscpy(path, ini_path); + wcscat(path, FindData.cFileName); + DeleteFile(path); +#if VERBOSE > 1 + time_f_date(&(FindData.ftLastWriteTime), (char *)path); + printf("Delete result: %S, %s\n", FindData.cFileName, (char *)path); +#endif + } else if (wcscmp(FindData.cFileName, ini_name) == 0){ + if (recent_data & 8){ // 検査結果を読み込まないが、今回のは書き込む場合 + // 以前の検査結果が存在すれば削除する + wcscpy(path, ini_path); + wcscat(path, ini_name); + DeleteFile(path); +#if VERBOSE > 1 + printf("Delete result: %S\n", ini_name); +#endif + } else { + match = 1; // 設定ファイルが存在する + } +#ifdef VERBOSE + ft = FindData.ftLastWriteTime; // 検査結果の更新日時 +#endif + } + } + } while (FindNextFile(hFind, &FindData)); // 次のファイルを検索する + FindClose(hFind); + } + + // バイナリ・データ用の設定ファイルを開く + wcscat(ini_path, ini_name); + hIniBin = CreateFile(ini_path, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ, NULL, OPEN_ALWAYS, FILE_FLAG_RANDOM_ACCESS, NULL); + if (hIniBin == INVALID_HANDLE_VALUE){ + hIniBin = NULL; + recent_data = 0; // 開けない場合は再利用しない + return 0; + } + //printf("ini file path = %S\n", ini_path); + + // 既に同名の (Set ID が同じ) ファイルが存在する場合 + set_data[0] = INI_VERSION >> 8; // 検査結果の書式バージョン + set_data[1] = INI_VERSION & 0xFF; + memset(set_data + 2, 0, 4); // CRC-32 は後で書き込む + memcpy(set_data + 6, &set_len, 4); + memcpy(set_data + 10, &file_num, 4); + if (match != 0){ // ID が同じでも Set 内容が異なる場合は初期化する + i = file_read_data(hIniBin, 0, set_data + HEADER_SIZE, HEADER_SIZE); + if ((set_data[HEADER_SIZE ] != (INI_VERSION >> 8)) || + (set_data[HEADER_SIZE + 1] != (INI_VERSION & 0xFF))){ + i = 1; // 古いバージョンの検査結果は参照しない + } + if (i == 0){ // 検査結果が破損してないか確かめる + if (SetFilePointer(hIniBin, 6, 0, FILE_BEGIN) != INVALID_SET_FILE_POINTER) + time_now = file_crc_part(hIniBin); + i = memcmp(&time_now, set_data + (HEADER_SIZE + 2), 4); + } + if (i == 0) // チェックサム・ファイルのデータを比較する + i = memcmp(set_data + 6, set_data + (HEADER_SIZE + 6), HEADER_SIZE - 6); + if (i != 0) // ID が同じでも Set 内容が異なる場合は初期化する + match = 0; + } + + // 一致しなかった場合は今回のデータを記録する + if (match == 0){ + // 今回のデータを書き込む + if (file_write_data(hIniBin, 0, set_data, HEADER_SIZE) != 0){ + delete_ini_file(); + return 0; + } + if (SetEndOfFile(hIniBin) == 0){ + delete_ini_file(); + return 0; + } + return 0; +#ifdef VERBOSE + } else { + time_f_date(&ft, (char *)path); + printf("Date of result: %s\n", (char *)path); +#endif + } + + return 1; +} + +// 検査結果ファイルを閉じる +void close_ini_file(void) +{ + if ((recent_data != 0) && (hIniBin != NULL)){ + unsigned int new_crc, old_crc; + + // 閉じる前に検査結果の CRC-32 を計算して記録しておく + FlushFileBuffers(hIniBin); + file_read_data(hIniBin, 2, (unsigned char *)&old_crc, 4); + new_crc = file_crc_part(hIniBin); + if (new_crc != old_crc) // 検査結果が同じなら更新しない + file_write_data(hIniBin, 2, (unsigned char *)&new_crc, 4); + CloseHandle(hIniBin); + hIniBin = NULL; + } +} + +// 検査結果が記録されてるかどうか +// -1=エラー, -2=記録なし, 0=完全, 2=破損 +int check_ini_state( + int num, // ファイル番号 + unsigned int meta[7], // サイズ、作成日時、更新日時、ボリューム番号、オブジェクト番号 + HANDLE hFile) // そのファイルのハンドル +{ + unsigned char buf[STATE_SIZE * STATE_READ]; + unsigned int rv, off, state; + BY_HANDLE_FILE_INFORMATION fi; + + // 現在のファイル属性を取得する + memset(&fi, 0, sizeof(BY_HANDLE_FILE_INFORMATION)); + if (GetFileInformationByHandle(hFile, &fi) != 0){ + meta[0] = fi.nFileSizeLow; + meta[1] = fi.nFileSizeHigh; + if ((recent_data == 0) || ((fi.nFileSizeLow <= REUSE_MIN) && (fi.nFileSizeHigh == 0))) + return -2; // 小さなファイルは記録を参照しない + meta[2] = time_f_u(&(fi.ftCreationTime)); + meta[3] = time_f_u(&(fi.ftLastWriteTime)); + meta[4] = fi.dwVolumeSerialNumber; + meta[5] = fi.nFileIndexLow; + meta[6] = fi.nFileIndexHigh; + } else { + meta[0] = 0; + meta[1] = 0; + return -1; // 属性の読み取りエラー + } + + // ヘッダーの直後から開始する + if (SetFilePointer(hIniBin, HEADER_SIZE, NULL, FILE_BEGIN) == INVALID_SET_FILE_POINTER){ + delete_ini_file(); + return -2; + } + ini_off = HEADER_SIZE; // ファイル項目の位置 + // 一度に STATE_READ 個ずつ読み込む + while (ReadFile(hIniBin, buf, STATE_SIZE * STATE_READ, &rv, NULL) != 0){ + if (rv < STATE_SIZE) + break; + // ファイル識別番号から同じファイルの記録を探す + for (off = 0; off < rv; off += STATE_SIZE){ + if (memcmp(buf + off, meta + 4, 12) == 0){ + ini_off += off; // 同じファイルの記録があった + state = 0; + memcpy(&state, buf + (off + 28), 2); +#ifdef VERBOSE + printf("check state, num = %d, offset = %d, state = %d\n", num, HEADER_SIZE + off, (state & 1) << 1); +#endif + // 番号が一致したら、ファイルのサイズと日時を比較する + if (((state >> 1) == (num & 0x7FFF)) && (memcmp(buf + (off + 12), meta, 16) == 0)) + return (state & 1) << 1; + return -2; // 状態が変化してる + } + } + ini_off += rv; + } + + ini_off = 0; + return -2; // これ以上の検査記録は無い +} + +// ソース・ファイル状態は完全か破損だけ (消失だと検査しない) +void write_ini_state( + int num, // ファイル番号 + unsigned int meta[7], // サイズ、作成日時、更新日時、ボリューム番号、オブジェクト番号 + int state) // 状態、0=完全, 2=破損 +{ + unsigned char buf[STATE_SIZE]; + + if ((recent_data == 0) || ((meta[0] <= REUSE_MIN) && (meta[1] == 0))) + return; // 小さなファイルは検査結果を記録しない + + if (ini_off == 0){ // 記録が無ければ末尾に追加する + ini_off = GetFileSize(hIniBin, NULL); + if (ini_off == INVALID_FILE_SIZE){ + delete_ini_file(); + return; + } + } + + // 今回の状態を書き込む + memcpy(buf, meta + 4, 12); + memcpy(buf + 12, meta, 16); + buf[28] = (unsigned char)((state >> 1) | (num << 1)); + buf[29] = (unsigned char)(num >> 7); +#ifdef VERBOSE + printf("write state, num = %d, offset = %d, state = %d\n", num, ini_off, state); +#endif + if (file_write_data(hIniBin, ini_off, buf, STATE_SIZE) != 0){ + delete_ini_file(); + return; + } +} + diff --git a/source/sfv_md5/ini.h b/source/sfv_md5/ini.h new file mode 100644 index 0000000..37dcff1 --- /dev/null +++ b/source/sfv_md5/ini.h @@ -0,0 +1,34 @@ +#ifndef _INI_H_ +#define _INI_H_ + +#ifdef __cplusplus +extern "C" { +#endif + + +#define INI_NAME_LEN 38 // 検査結果ファイルのファイル名の文字数 + +extern int recent_data; + +unsigned int time_f_u(FILETIME *file_time); + +int check_ini_file(unsigned char *set_hash, unsigned int set_len); +void close_ini_file(void); +void write_ini_file(void); + +int check_ini_state( + int num, // ファイル番号 + unsigned int meta[7], // サイズ、作成日時、更新日時、ボリューム番号、オブジェクト番号 + HANDLE hFile); // そのファイルのハンドル + +void write_ini_state( + int num, // ファイル番号 + unsigned int meta[7], // サイズ、作成日時、更新日時、ボリューム番号、オブジェクト番号 + int state); // 状態 + + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/source/sfv_md5/main.c b/source/sfv_md5/main.c new file mode 100644 index 0000000..ab38cba --- /dev/null +++ b/source/sfv_md5/main.c @@ -0,0 +1,861 @@ +// main.c +// Copyright : 2022-02-25 Yutaka Sawada +// License : The MIT license + +#ifndef _UNICODE +#define _UNICODE +#endif +#ifndef UNICODE +#define UNICODE +#endif +#ifndef _WIN32_WINNT +#define _WIN32_WINNT 0x0600 // Windows Vista or later +#endif + +#include + +#include +#include + +#include "crc.h" +#include "common.h" +#include "create.h" +#include "verify.h" +#include "ini.h" +#include "version.h" + +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +void print_help(void) +{ + printf( +"Usage\n" +"c(reate) [fo,t, d,u] [input files]\n" +"v(erify) [vs,vd,d,u] \n" +"\nOption\n" +" /fo : Search file only for wildcard\n" +" /t : Save time stamp\n" +" /vs: Skip verification by recent result\n" +" /vd\"*\": Set directory of recent result\n" +" /d\"*\" : Set directory of input files\n" +" /u : Console output is encoded with UTF-8\n" + ); +} + +// CRC-32 チェックサムを使って自分自身の破損を検出する +long test_checksum(wchar_t *file_path) // 作業用 +{ + unsigned long rv, crc, chk, chk2; + unsigned char *pAddr; + HANDLE hFile, hMap; + + init_crc_table(); // CRC 計算用のテーブルを作成する + + // 実行ファイルのパスを取得する + rv = GetModuleFileName(NULL, file_path, MAX_LEN); + if ((rv == 0) || (rv >= MAX_LEN)) + return 1; + + // 実行ファイルの PE checksum と CRC-32 を検証する + hFile = CreateFile(file_path, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL); + if (hFile == INVALID_HANDLE_VALUE){ + print_win32_err(); + return 1; + } + rv = GetFileSize(hFile, &chk2); + if (rv == INVALID_FILE_SIZE) + return 1; + hMap = CreateFileMapping(hFile, NULL, PAGE_READONLY, chk2, rv, NULL); + if (hMap == NULL){ + CloseHandle(hFile); + return 1; + } + pAddr = MapViewOfFile(hMap, FILE_MAP_READ, 0, 0, rv); + if (pAddr == NULL){ + CloseHandle(hMap); + CloseHandle(hFile); + return 1; + } + if (CheckSumMappedFile(pAddr, rv, &chk2, &chk) == NULL){ // PE checksum + UnmapViewOfFile(pAddr); + CloseHandle(hMap); + CloseHandle(hFile); + return 1; + } + crc = crc_update(0xFFFFFFFF, pAddr, rv) ^ 0xFFFFFFFF; + UnmapViewOfFile(pAddr); + CloseHandle(hMap); + CloseHandle(hFile); + + if (chk != chk2) + return 2; + if (crc != 0x00000000) + return 3; + return 0; +} + +// ファイルを検索してファイル・リストに追加する +static wchar_t * search_files( + wchar_t *list_buf, // ファイル・リスト + wchar_t *search_path, // 検索するファイルのフル・パス + long dir_len, // ディレクトリ部分の長さ + long file_only, // ファイルのみにするかどうか + long single_file, // -1 = *や?で検索指定、0~ = 単独指定 + long *list_max, // ファイル・リストの確保サイズ + long *list_len, // ファイル・リストの文字数 + __int64 *total_size) // 合計ファイル・サイズ +{ + wchar_t *tmp_p; + long len, l_max, l_off, dir_len2; + HANDLE hFind; + WIN32_FIND_DATA FindData; + + if (list_buf == NULL){ + l_off = 0; + l_max = ALLOC_LEN; + list_buf = (wchar_t *)malloc(l_max); + if (list_buf == NULL){ + printf("malloc, %d\n", l_max); + return NULL; + } + } else { + l_max = *list_max; + l_off = *list_len; + } + + // 検索する + hFind = FindFirstFile(search_path, &FindData); + if (hFind == INVALID_HANDLE_VALUE) + return list_buf; // 見つからなかったらそのまま + do { + if ((single_file < 0) && (FindData.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN)) + continue; // 検索中は隠し属性が付いてるファイルを無視する + + len = wcslen(FindData.cFileName); + if (dir_len + len >= MAX_LEN - ADD_LEN - 2){ // 末尾に「\*」を付けて再検索するので + FindClose(hFind); + free(list_buf); + printf("filename is too long\n"); + return NULL; + } + + // 現在のディレクトリ部分に見つかったファイル名を連結する + wcscpy(search_path + dir_len, FindData.cFileName); + + // フォルダなら + if (FindData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY){ + if ((file_only == 0) && wcscmp(FindData.cFileName, L".") && wcscmp(FindData.cFileName, L"..")){ + // フォルダの末尾は「\」にする + wcscat(search_path, L"\\"); + // そのフォルダの中身を更に検索する + dir_len2 = wcslen(search_path); + search_path[dir_len2 ] = '*'; // 末尾に「*」を追加する + search_path[dir_len2 + 1] = 0; + list_buf = search_files(list_buf, search_path, dir_len2, file_only, single_file, &l_max, &l_off, total_size); + if (list_buf == NULL){ + FindClose(hFind); + printf("cannot search inner folder\n"); + return NULL; + } + } + } else { // ファイルなら + if (!search_file_path(list_buf, l_off, search_path + base_len)){ // ファイル名が重複しないようにする + if ((l_off + dir_len- base_len + len) * 2 >= l_max){ // 領域が足りなくなるなら拡張する + l_max += ALLOC_LEN; + tmp_p = (wchar_t *)realloc(list_buf, l_max); + if (tmp_p == NULL){ + FindClose(hFind); + free(list_buf); + printf("realloc, %d\n", l_max); + return NULL; + } else { + list_buf = tmp_p; + } + } + + // リストにコピーする + wcscpy(list_buf + l_off, search_path + base_len); + l_off += dir_len - base_len + len + 1; + file_num++; + (*total_size) += ((__int64)FindData.nFileSizeHigh << 32) | (__int64)FindData.nFileSizeLow; + } + } + } while (FindNextFile(hFind, &FindData)); // 次のファイルを検索する + FindClose(hFind); + + *list_max = l_max; + *list_len = l_off; + return list_buf; +} + +// ファイルの詳細情報を記録する +long save_detail( + wchar_t *uni_buf, + wchar_t *file_name) // 検査対象のファイル名 +{ + __int64 file_size; + HANDLE hFile; + FILETIME ftWrite; + SYSTEMTIME stUTC, stLocal; + + // 調べるファイルを開く + wcscpy(uni_buf, base_dir); + wcscpy(uni_buf + base_len, file_name); + hFile = CreateFile(uni_buf, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL); + if (hFile == INVALID_HANDLE_VALUE){ + print_win32_err(); + return 1; + } + // ファイル・サイズ + if (!GetFileSizeEx(hFile, (PLARGE_INTEGER)&file_size)){ + print_win32_err(); + CloseHandle(hFile); + return 1; + } + // 更新日時 + if (!GetFileTime(hFile, NULL, NULL, &ftWrite)){ + print_win32_err(); + CloseHandle(hFile); + return 1; + } + FileTimeToSystemTime(&ftWrite, &stUTC); + SystemTimeToTzSpecificLocalTime(NULL, &stUTC, &stLocal); + CloseHandle(hFile); + + // サイズと日時を書き込む + wsprintf(uni_buf, L";%13I64d %02d:%02d.%02d %4d-%02d-%02d ", file_size, + stLocal.wHour, stLocal.wMinute, stLocal.wSecond, + stLocal.wYear, stLocal.wMonth, stLocal.wDay); + add_text(uni_buf); + + // ファイル名を書き込む + wcscpy(uni_buf, file_name); // 変換前にコピーする + unix_directory(uni_buf); + add_text(uni_buf); + add_text(L"\r\n"); + + return 0; +} + +// チェックサム・ファイルを書き込む +static long write_checksum(wchar_t *uni_buf, + wchar_t *list_buf, long list_len, __int64 total_size, long switch_t) +{ + char *ascii_buf; + wchar_t *ads_p; + long err = 0, rv, len, format; + __int64 prog_now = 0; + HANDLE hFile; + FILETIME ftWrite; + + len = wcslen(checksum_file); + if (_wcsicmp(checksum_file + (len - 4), L".sfv") == 0){ // SFVファイル + format = 1; + } else if (_wcsicmp(checksum_file + (len - 4), L".md5") == 0){ // MD5ファイル + format = 2; +// } else { +// printf("unknown format\n"); +// return 1; + } + + // チェックサム・ファイルのテキストを UTF-16 の文字列で作成する + text_len = 0; + text_max = ALLOC_LEN; + text_buf = (wchar_t *)malloc(text_max * 2); + if (text_buf == NULL){ + printf("malloc, %d\n", text_max * 2); + return 1; + } + + // コメントを書き込む + wsprintf(uni_buf, L"; Generated by SFV/MD5 checker v%hs", PRODUCT_VERSION); + add_text(uni_buf); + if (switch_t >= 1){ // 作成した日時を書き込む + SYSTEMTIME stLocal; + GetLocalTime(&stLocal); + wsprintf(uni_buf, L" on %4d-%02d-%02d at %02d:%02d.%02d", + stLocal.wYear, stLocal.wMonth, stLocal.wDay, + stLocal.wHour, stLocal.wMinute, stLocal.wSecond); + add_text(uni_buf); + if (switch_t >= 2){ // ファイルの詳細を書き込むなら + add_text(L"\r\n;"); + if (file_num > 1){ + add_text(L"\r\n"); + add_text(L"; Size (Bytes) Time Date Filename\n"); + add_text(L"; ------------ -------- ---------- ------------"); + } + } + } + add_text(L"\r\n"); + + if (switch_t >= 2){ // ファイルのサイズと更新日時を書き込む + len = 0; + while (len < list_len){ + if (save_detail(uni_buf, list_buf + len) != 0){ + printf("cannot save detail\n"); + free(text_buf); + return 1; + } + len += wcslen(list_buf + len) + 1; + } + add_text(L";\r\n"); + } + + // 各ファイルのチェックサムを計算する + printf("\n"); + print_progress_text(0, "Computing file hash"); + len = 0; + while (len < list_len){ + if (format == 1){ + rv = create_sfv(uni_buf, list_buf + len, &prog_now, total_size); + } else if (format == 2){ + rv = create_md5(uni_buf, list_buf + len, &prog_now, total_size); + } else { + rv = 1; + } + if (rv != 0){ + free(text_buf); + return rv; // エラー、キャンセルなど + } + len += wcslen(list_buf + len) + 1; + } + print_progress_done(); // 改行して行の先頭に戻しておく + + // 既存ファイルの alternate data stream に書き込む場合は更新日時をそのままにする + ads_p = wcschr(offset_file_name(checksum_file), ':'); + if (ads_p != NULL){ + *ads_p = 0; + hFile = CreateFile(checksum_file, GENERIC_READ, 0, NULL, OPEN_EXISTING, 0, NULL); + *ads_p = ':'; + if (hFile == INVALID_HANDLE_VALUE){ + ads_p = NULL; + } else { + if (!GetFileTime(hFile, NULL, NULL, &ftWrite)){ + //print_win32_err(); + //printf("cannot read time stamp\n"); + ads_p = NULL; + } + CloseHandle(hFile); + } + } + + // UTF-16 から UTF-8 に変換する + len = text_len * 3; + ascii_buf = (char *)malloc(len); + if (ascii_buf == NULL){ + printf("malloc, %d\n", len); + free(text_buf); + return 1; + } + if (utf16_to_cp(text_buf, ascii_buf, len, CP_UTF8) != 0){ + printf("cannot encode text\n"); + free(ascii_buf); + free(text_buf); + return 1; + } + len = strlen(ascii_buf); + + // チェックサム・ファイルを開いて、エンコードしたテキストを書き込む + hFile = CreateFile(checksum_file, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); + if (hFile == INVALID_HANDLE_VALUE){ + print_win32_err(); + printf("cannot create checksum file\n"); + free(ascii_buf); + free(text_buf); + return 1; + } + if (!WriteFile(hFile, ascii_buf, len, &rv, NULL)){ + print_win32_err(); + printf("cannot write checksum file\n"); + CloseHandle(hFile); + free(ascii_buf); + free(text_buf); + return 1; + } + // main stream の更新日時を元に戻す + if (ads_p != NULL) + SetFileTime(hFile, NULL, NULL, &ftWrite); + + CloseHandle(hFile); // チェックサム・ファイルを閉じる + free(ascii_buf); + free(text_buf); + + printf("\nCreated successfully\n"); + return 0; +} + +// チェックサム・ファイルを読み込む +static long read_checksum(char *ascii_buf, wchar_t *file_path) +{ + unsigned char *data_buf; + unsigned long err = 0, rv, file_size; + HANDLE hFile; + + // 読み込むファイルを開く + hFile = CreateFile(checksum_file, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL); + if (hFile == INVALID_HANDLE_VALUE){ + print_win32_err(); + printf("cannot open checksum file\n"); + return 1; + } + + // 2MB を越えるチェックサム・ファイルには対応しない + file_size = GetFileSize(hFile, NULL); + if ((file_size == INVALID_FILE_SIZE) || (file_size > (2 << 21))){ + CloseHandle(hFile); + printf("checksum file is too large\n"); + return 1; + } + printf("Checksum Size\t: %d\n\n", file_size); + fflush(stdout); + + // ハッシュ値を記録するには最低でも 1(ファイル名)+1(スペース)+8(CRC-32) = 10バイト必要 + if (file_size < 10){ + CloseHandle(hFile); + printf("Status : Damaged\n"); + printf("valid file is not found\n"); + return 1; + } + + // ファイル全体を読み込む + data_buf = (unsigned char *)malloc(file_size + 2); + if (data_buf == NULL){ + CloseHandle(hFile); + printf("malloc, %d\n", file_size); + return 1; + } + text_max = file_size + 1; // 全て ASCII 文字と仮定して、末尾に null 文字を付けた時の文字数 + text_buf = (wchar_t *)malloc(text_max * 2); + if (text_buf == NULL){ + free(data_buf); + CloseHandle(hFile); + printf("malloc, %d\n", text_max * 2); + return 1; + } + if (!ReadFile(hFile, data_buf, file_size, &rv, NULL) || (file_size != rv)){ + free(data_buf); + free(text_buf); + CloseHandle(hFile); + printf("ReadFile, %d\n", file_size); + return 1; + } + CloseHandle(hFile); + data_buf[file_size ] = 0; // 末尾に null 文字を付けておく + data_buf[file_size + 1] = 0; + + // UTF-16 の文字列に変換する + rv = utf16_to_utf16(data_buf, file_size + 2, text_buf); + if (rv != 0){ // UTF-16 ではなかった + if (check_utf8(data_buf) == 0){ // UTF-8 として変換する + rv = 0; // BOMが付いてるかどうか + if ((data_buf[0] == 0xEF) && (data_buf[1] == 0xBB) && (data_buf[2] == 0xBF)) + rv = 3; + rv = cp_to_utf16(data_buf + rv, text_buf, file_size + 1, CP_UTF8); + } else { + rv = cp_to_utf16(data_buf, text_buf, file_size + 1, CP_ACP); + if (rv != 0) + rv = cp_to_utf16(data_buf, text_buf, file_size + 1, 1252); // Latin-1 CP1252 で変換を試みる + } + } + free(data_buf); + if (rv != 0){ + free(text_buf); + printf("cannot decode text\n"); + return 1; + } + text_len = wcslen(text_buf); // 文字数には末尾の null 文字を含まないことに注意 + + // チェックサムを検証する + rv = wcslen(checksum_file); + if (_wcsicmp(checksum_file + (rv - 4), L".sfv") == 0){ // SFVファイル + err = verify_sfv(ascii_buf, file_path); + } else if (_wcsicmp(checksum_file + (rv - 4), L".md5") == 0){ // MD5ファイル + err = verify_md5(ascii_buf, file_path); +// } else { +// err = 1; +// printf("unknown format\n"); + } + free(text_buf); + close_ini_file(); + + // 検査結果を表示する + printf("\n"); + if (err == 0){ + printf("All Files Complete\n"); + } else { + if (err & 16) + printf("Checksum File Incomplete\n"); + if (err & 0xFFFFFF00){ + printf("%d Files Missing or Damaged\n", err >> 8); + //err &= 0xFF; + } + err = ((err & 0x10) << 4) | (err & 0xEF); // 16を 256 に変更する + } + + return err; +} + +// チェックサム・ファイルとソース・ファイルの名前に問題が無いか確かめる +static long check_filename( + wchar_t *list_buf, + long list_len) +{ + wchar_t *tmp_p; + long num, list_off = 0; + + // SFV ファイル作成時に、ソース・ファイルの先頭が「;」だとコメントと区別できない + if (_wcsicmp(checksum_file + (wcslen(checksum_file) - 4), L".sfv") == 0){ // SFVファイル + for (num = 0; num < file_num; num++){ + tmp_p = list_buf + list_off; + while (list_buf[list_off] != 0) + list_off++; + list_off++; + + // ファイル名の先頭を調べる + if (tmp_p[0] == ';'){ + printf_cp("filename is invalid, %s\n", tmp_p); + return 1; + } + } + } + + // 基準ディレクトリ外にチェックサム・ファイルが存在するなら問題なし + if (_wcsnicmp(checksum_file, base_dir, base_len) != 0) + return 0; + + // チェックサム・ファイルとソース・ファイルが同じ名前にならないようにする + for (num = 0; num < file_num; num++){ + tmp_p = list_buf + list_off; + while (list_buf[list_off] != 0) + list_off++; + list_off++; + + // ファイル名との一致を調べる + if (_wcsicmp(checksum_file + base_len, tmp_p) == 0){ + printf_cp("filename is invalid, %s\n", tmp_p); + return 1; + } + } + return 0; +} + +wmain(long argc, wchar_t *argv[]) +{ + char ascii_buf[MAX_LEN * 3]; + wchar_t file_path[MAX_LEN], *tmp_p; + long i, j, switch_set = 0; +/* +t = switch_set & 0x00000003 +fo= switch_set & 0x00000020 +*/ + printf("SFV/MD5 checker version " FILE_VERSION " by Yutaka Sawada\n\n"); + if (argc < 3){ + printf("Self-Test: "); + i = test_checksum(file_path); + if (i == 0){ + printf("Success"); + } else if (i == 2){ + printf("PE checksum is different"); + } else if (i == 3){ + printf("CRC-32 is different"); + } else { + printf("Error\0thedummytext"); + } + printf("\n\n"); + print_help(); + return 0; + } + + // 初期化 + checksum_file[0] = 0; + base_dir[0] = 0; + ini_path[0] = 0; + file_num = 0; + cp_output = GetConsoleOutputCP(); + + // コマンド + switch (argv[1][0]){ + case 'c': // create + case 'v': // verify + break; + default: + print_help(); + return 0; + } + + // オプションとチェックサム・ファイルの指定 + for (i = 2; i < argc; i++){ + tmp_p = argv[i]; + // オプション + if (((tmp_p[0] == '/') || (tmp_p[0] == '-')) && (tmp_p[0] == argv[2][0])){ + tmp_p++; // 先頭の識別文字をとばす + // オプション + if (wcscmp(tmp_p, L"u") == 0){ + cp_output = CP_UTF8; + } else if (wcscmp(tmp_p, L"fo") == 0){ + switch_set |= 0x20; + + // オプション (数値) + } else if (wcsncmp(tmp_p, L"vs", 2) == 0){ + recent_data = 0; + j = 2; + while ((j < 2 + 2) && (tmp_p[j] >= '0') && (tmp_p[j] <= '9')){ + recent_data = (recent_data * 10) + (tmp_p[j] - '0'); + j++; + } + if ((recent_data == 8) || (recent_data > 15)) + recent_data = 0; + } else if (wcsncmp(tmp_p, L"t", 1) == 0){ + j = -1; + if ((tmp_p[1] >= '0') && (tmp_p[1] <= '2')) // 0~2 の範囲 + j = tmp_p[1] - '0'; + if (j != -1) + switch_set |= j; + + // オプション (文字列) + } else if (wcsncmp(tmp_p, L"vd", 2) == 0){ + tmp_p += 2; + j = copy_path_prefix(ini_path, MAX_LEN - INI_NAME_LEN - 1, tmp_p, NULL); + if (j == 0){ + printf("save-directory is invalid\n"); + return 1; + } + if (ini_path[j - 1] != '\\'){ // 末尾が「\」でなければ付けておく + ini_path[j ] = '\\'; + ini_path[j + 1] = 0; + } + //printf("Save Directory : %S\n", ini_path); + j = GetFileAttributes(ini_path); + if ((j == INVALID_FILE_ATTRIBUTES) || !(j & FILE_ATTRIBUTE_DIRECTORY)){ + printf("save-directory is invalid\n"); + return 1; + } + } else if (wcsncmp(tmp_p, L"d", 1) == 0){ + tmp_p++; + j = copy_path_prefix(base_dir, MAX_LEN - 2, tmp_p, NULL); // 末尾に追加される分の余裕を見ておく + if (j == 0){ + printf("base-directory is invalid\n"); + return 1; + } + if (base_dir[j - 1] != '\\'){ // 末尾が「\」でなければ付けておく + base_dir[j ] = '\\'; + base_dir[j + 1] = 0; + } + j = GetFileAttributes(base_dir); + if ((j == INVALID_FILE_ATTRIBUTES) || !(j & FILE_ATTRIBUTE_DIRECTORY)){ + printf("base-directory is invalid\n"); + return 1; + } + } else { // 未対応のオプション + utf16_to_cp(tmp_p - 1, ascii_buf, MAX_LEN * 3, cp_output); + printf("invalid option, %s\n", ascii_buf); + return 1; + } + continue; + } + // オプション以外ならリカバリ・ファイル + j = copy_path_prefix(checksum_file, MAX_LEN, tmp_p, NULL); + if (j == 0){ + printf("checksum filename is invalid\n"); + return 1; + } + // 拡張子は SFV か MD5 のみ + if (_wcsicmp(checksum_file + (j - 4), L".sfv") == 0){ + init_crc_table(); // CRC 計算用のテーブルを作る + } else if (_wcsicmp(checksum_file + (j - 4), L".md5") == 0){ + if (recent_data != 0) + init_crc_table(); + } else { // 拡張子が違うなら + wcscpy(checksum_file + j, L".sfv"); // 標準で SFV 形式にする + if (argv[1][0] == 'c'){ // 作成なら + init_crc_table(); + } else { // 検査なら + if (GetFileAttributes(checksum_file) == INVALID_FILE_ATTRIBUTES){ + wcscpy(checksum_file + j, L".md5"); // SFV で駄目なら MD5 にする + if (GetFileAttributes(checksum_file) == INVALID_FILE_ATTRIBUTES){ + printf("file format is unknown\n"); + return 1; + } else if (recent_data != 0){ + init_crc_table(); + } + } else { + init_crc_table(); + } + } + } + if (argv[1][0] == 'c'){ // 作成なら + // 指定されたファイル名が適切か調べる + if (sanitize_filename(offset_file_name(checksum_file)) != 0){ + printf("checksum filename is invalid\n"); + return 1; + } + } else { // 検査なら + j = GetFileAttributes(checksum_file); + if ((j == INVALID_FILE_ATTRIBUTES) || (j & FILE_ATTRIBUTE_DIRECTORY)){ + wchar_t search_path[MAX_LEN]; + long name_len, dir_len, path_len, find_flag = 0; + HANDLE hFind; + WIN32_FIND_DATA FindData; + if (wcspbrk(offset_file_name(checksum_file), L"*?") == NULL){ + // 「*」や「?」で検索しない場合、ファイルが見つからなければエラーにする + printf("valid file is not found\n"); + return 1; + } + // 指定された拡張子で検索する + path_len = wcslen(checksum_file); + hFind = FindFirstFile(checksum_file, &FindData); + if (hFind != INVALID_HANDLE_VALUE){ + get_base_dir(checksum_file, search_path); + dir_len = wcslen(search_path); + do { + //printf("file name = %S\n", FindData.cFileName); + name_len = wcslen(FindData.cFileName); + if ((dir_len + name_len < MAX_LEN) && // ファイル名が長すぎない + (_wcsicmp(FindData.cFileName + (name_len - 4), checksum_file + (path_len - 4)) == 0)){ // 拡張子が同じ + find_flag = 1; + break; // 見つけたファイル名で問題なし + } + } while (FindNextFile(hFind, &FindData)); // 次のファイルを検索する + FindClose(hFind); + } + if (find_flag == 0){ + printf("valid file is not found\n"); + return 1; + } + wcscpy(checksum_file + dir_len, FindData.cFileName); + } + } + break; + } + if (checksum_file[0] == 0){ // チェックサム・ファイルが指定されてないなら + printf("checksum file is not specified\n"); + return 1; + } + + // input file の位置が指定されて無くて、 + // 最初のソース・ファイル指定に絶対パスや相対パスが含まれてるなら、それを使う + if (argv[1][0] == 'c'){ + if ((base_dir[0] == 0) && (i + 1 < argc)){ + tmp_p = argv[i + 1]; + if (is_full_path(tmp_p) != 0){ // 絶対パスなら + if (copy_path_prefix(file_path, MAX_LEN - 2, tmp_p, NULL) == 0){ + printf("base-directory is invalid\n"); + return 1; + } + get_base_dir(file_path, base_dir); // 最初のソース・ファイルの位置にする + if (len_without_prefix(base_dir) == 0) // サブ・ディレクトリが無ければ + base_dir[0] = 0; + } + } + } + if (base_dir[0] == 0) // input file の位置が指定されてないなら + get_base_dir(checksum_file, base_dir); // リカバリ・ファイルの位置にする + base_len = wcslen(base_dir); + + // 動作環境の表示 + // 「\\?\」は常に付加されてるので表示する際には無視する + printf_cp("Base Directory\t: \"%s\"\n", base_dir); + printf_cp("Checksum File\t: \"%s\"\n", checksum_file); + + if (argv[1][0] == 'c'){ + wchar_t *list_buf; + long dir_len, list_len, list_max; + __int64 total_size = 0; // 合計ファイル・サイズ + // チェックサム・ファイル作成ならソース・ファイルのリストがいる + i++; + if (i >= argc){ + printf("input file is not specified\n"); + return 1; + } + // 入力ファイルの指定 + file_num = 0; + list_len = 0; + list_max = 0; + list_buf = NULL; + for (; i < argc; i++){ + // ファイルが基準ディレクトリ以下に存在することを確認する + tmp_p = argv[i]; + j = copy_path_prefix(file_path, MAX_LEN - 2, tmp_p, base_dir); // 絶対パスにしてから比較する + if (j == 0){ + free(list_buf); + printf_cp("filename is invalid, %s\n", tmp_p); + return 1; + } + if ((j <= base_len) || (_wcsnicmp(base_dir, file_path, base_len) != 0)){ // 基準ディレクトリ外なら + free(list_buf); + printf_cp("out of base-directory, %s\n", tmp_p); + return 1; + } + //printf("%d = %S\n", i, argv[i]); + //printf_cp("search = %s\n", file_path); + // 「*」や「?」で検索しない場合、ファイルが見つからなければエラーにする + j = -1; + if (wcspbrk(file_path + base_len, L"*?") == NULL) + j = file_num; + // ファイルを検索する + dir_len = wcslen(file_path) - 2; // ファイル名末尾の「\」を無視して、ディレクトリ部分の長さを求める + while (file_path[dir_len] != '\\') + dir_len--; + dir_len++; + list_buf = search_files(list_buf, file_path, dir_len, switch_set & 0x20, j, &list_max, &list_len, &total_size); + if (list_buf == NULL) + return 1; + if ((j != -1) && (j == file_num)){ // ファイルが見つかったか確かめる + free(list_buf); + printf_cp("input file is not found, %s\n", file_path + base_len); + return 1; + } + } + if (list_len == 0){ + if (list_buf) + free(list_buf); + printf("input file is not found\n"); + return 1; + } + if (check_filename(list_buf, list_len)){ + free(list_buf); + return 1; + } +/*{ +FILE *fp; +fp = fopen("list_buf.txt", "wb"); +fwrite(list_buf, 2, list_len, fp); +fclose(fp); +}*/ + + // ファイル・リストの内容を並び替える (リストの末尾は null 1個にすること) + sort_list(list_buf, list_len); + + printf("\nInput File count\t: %d\n", file_num); + printf("Input File total size\t: %I64d\n", total_size); + i = write_checksum(file_path, list_buf, list_len, total_size, switch_set & 3); + + } else { + // 検査結果ファイルの位置が指定されてないなら + if ((recent_data != 0) && (ini_path[0] == 0)){ + // 実行ファイルのディレクトリにする + j = GetModuleFileName(NULL, ini_path, MAX_LEN); + if ((j == 0) || (j >= MAX_LEN)){ + printf("GetModuleFileName\n"); + return 1; + } + while (j > 0){ + if (ini_path[j - 1] == '\\'){ + ini_path[j] = 0; + break; + } + j--; + } + if (j >= MAX_LEN - INI_NAME_LEN - 1){ + printf("save-directory is invalid\n"); + return 1; + } + } + + i = read_checksum(ascii_buf, file_path); + } + + //printf("ExitCode: %d\n", i); + return i; +} + diff --git a/source/sfv_md5/phmd5.c b/source/sfv_md5/phmd5.c new file mode 100644 index 0000000..415ab31 --- /dev/null +++ b/source/sfv_md5/phmd5.c @@ -0,0 +1,79 @@ +/*---------------------------------------------------------------------------- +; +; MD5 hash generator -- Paul Houle (paulhoule.com) 11/13/2017 +; +; Non-time critical C logic. All API entry points are here. +; See phmd5.h for documentation. +; +;---------------------------------------------------------------------------*/ + +#include +#include "phmd5.h" + +// First call -- initialize pmd5 structure for use. +void Phmd5Begin(PHMD5 *pmd5) { + unsigned __int32 *uhash = (unsigned __int32 *) pmd5->hash; + + uhash[0] = 0x67452301; // init hash per rfc1321 + uhash[1] = 0xEFCDAB89; + uhash[2] = 0x98BADCFE; + uhash[3] = 0x10325476; + + pmd5->totbyt = 0; // init count of data bytes processed +} + +// Last call -- after this, pmd5->hash holds final MD5 hash. +void Phmd5End(PHMD5 *pmd5) { + char pad[72]; // pad buffer (worst case is 72 bytes) + unsigned padc; // size of needed pad (9-72 bytes) + + padc = 64 - ((unsigned) pmd5->totbyt & 63); // pad to 64-byte boundary + if (padc < 9) padc += 64; // add a block if we need more room + memset(pad, 0, padc); // clear entire pad area + pad[0] = (char) 0x80; // place input stream terminator + // place 64-bit input data bit count + *(unsigned __int64 *) &pad[padc - 8] = pmd5->totbyt << 3; + Phmd5Process(pmd5, pad, padc); // process the pad +} + +// Work done here -- call for as many input blocks that need to be processed. +// pdata points to the input data, bytecnt is pdata size (0..n bytes). +// See phmd5.h regarding how to use this optimally. +void Phmd5Process(PHMD5 *pmd5, char *pdata, size_t bytecnt) { + unsigned resid = (unsigned) pmd5->totbyt; + + pmd5->totbyt += bytecnt; // update total bytes processed + + resid &= 63; // count of bytes now in pmd5->buf + + // This block handles the case of residual data in pmd5->buf. + // After this block pmd5->buf is empty (except perhaps on exit). + + if (resid) { // if residual exists, + unsigned cb = 64 - resid; + if (cb > bytecnt) cb = (unsigned) bytecnt; + memcpy(pmd5->buf + resid, pdata, cb); + pdata += cb; + bytecnt -= cb; + if (resid + cb < 64) return; + Phmd5DoBlocks(pmd5->hash, pmd5->buf, 64); + } + + // This block processes input data in-place, if the data is dword + // aligned and in 64-byte chunks. + + if ((unsigned) bytecnt & ~63 && ((size_t) pdata & 3) == 0) { + Phmd5DoBlocks(pmd5->hash, pdata, bytecnt & ~63); + pdata += bytecnt & ~63; + bytecnt &= 63; + } + + while (bytecnt) { // handle residual/non-aligned data + unsigned cb = 64 > (unsigned) bytecnt ? (unsigned) bytecnt : 64; + memcpy(pmd5->buf, pdata, cb); + pdata += cb; + bytecnt -= cb; + if (cb < 64) return; + Phmd5DoBlocks(pmd5->hash, pmd5->buf, 64); + }; +} diff --git a/source/sfv_md5/phmd5.h b/source/sfv_md5/phmd5.h new file mode 100644 index 0000000..e43a19c --- /dev/null +++ b/source/sfv_md5/phmd5.h @@ -0,0 +1,59 @@ +/*---------------------------------------------------------------------------- +; +; MD5 hash generator -- Paul Houle (paulhoule.com) 11/13/2017 +; +; This code is in the public domain. Please attribute the author. +; +; There are a lot of MD5 generators; here's another. This one targets a +; little-endian memory architecture only (eg X86). The benefit of this +; is speed -- bytes within larger elements never need to be reversed, +; which means the source data can be processed in-place. +; +; Though other compilers might be usable, this was developed using +; Microsoft 32/64-bit C 12.0 [Version 18.00.30723]. Vendor specific +; definitions (eg. _rotl, __int32, __int64) are used. +; Build commands: +; +; cl /c /Ox phmd5.c +; cl /c /Ox phmd5a.c +; +; Link the resulting .obj's into your executable and #include "phmd5.h" +; +; How to call the routines to generate a hash: +; +; (1) Allocate a PHMD5 type struct -- it's small, can be static or local. +; A pointer to this struct is the first argument to all functions. +; +; (2) Call Phmd5Begin() once -- this initializes the PHMD5 struct. +; +; (3) Call Phmd5Process() as many times as necessary for all data +; to be included in the MD5 hash. +; +; (4) Call Phmd5End() once. The final 16-byte MD5 hash will then be +; available in PHMD5->hash. Note the finished hash is a simple array +; of bytes, and must be treated/displayed/copied/etc that way. +; +; For best performance the Phmd5Process() "pdata" pointer should be 32-bit +; aligned (a multiple of 4) and "bytecnt" should be a multiple of 64. +; As long as both of these conditions continue to be met the input data is +; processed in-place; otherwise, some speed (10-15%) is lost as the data +; is copied to an internal blocking buffer before being proceessed. +; +;---------------------------------------------------------------------------*/ + +#ifndef _PHMD5_DEFINED // include guard +#define _PHMD5_DEFINED + +#include +typedef struct { + unsigned char hash[16]; // final 16-byte hash winds up here + unsigned __int64 totbyt; // processed byte count + char buf[64]; // input blocking buffer +} PHMD5; + +void Phmd5Begin(PHMD5 *pmd5); +void Phmd5Process(PHMD5 *pmd5, char *pdata, size_t bytecnt); +void Phmd5End(PHMD5 *pmd5); +void Phmd5DoBlocks(unsigned char *hash, char *pdata, size_t bytecnt); + +#endif diff --git a/source/sfv_md5/phmd5a.c b/source/sfv_md5/phmd5a.c new file mode 100644 index 0000000..0572cd9 --- /dev/null +++ b/source/sfv_md5/phmd5a.c @@ -0,0 +1,130 @@ +/*---------------------------------------------------------------------------- +; +; MD5 hash generator -- Paul Houle (paulhoule.com) 11/13/2017 +; +; Called only from phmd5.c -- see phmd5.h for overview +; +; This is the same logic in phmd5a.asm, implemented (after the fact) in C. +; The C compiler must support the "_rotl()" function generated inline to +; achieve maximal performance. +; +; It turns out the MSFT C compiler, coupled with newer processor, results +; in timings comparable to the 32-bit only phmd5a.asm hand-written assembly. +; Therefore this "C" implementation is now used. +; Avoiding assembly allows the code to be compiled either 32 or 64 bit. +; +; Note that a "little-endian" memory architecture is assumed. +; +; The Fx() and MD5STEP() macros were written by Colin Plumb in 1993. +; MD5STEP() was changed slightly to match how phmd5a.asm operates. +; +;---------------------------------------------------------------------------*/ + +#include // for _rotl() +#include "phmd5.h" + +// MD5 Optimisation Tricks by Anime Tosho +// https://github.com/animetosho/md5-optimisation#optimisation-tricks-single-buffer +// Dependency shortcut in G function +#define F1(x, y, z) ((x & y) + (~x & z)) + +//#define F1(x, y, z) (z ^ (x & (y ^ z))) +#define F2(x, y, z) F1(z, x, y) +#define F3(x, y, z) (x ^ y ^ z) +#define F4(x, y, z) (y ^ (x | ~z)) + +#define MD5STEP(f, w, x, y, z, ix, s, sc) \ + w = _rotl(w + f(x, y, z) + ((unsigned *) pdata)[ix] + sc, s) + x + +void Phmd5DoBlocks( + unsigned char *hash, + char *pdata, + size_t bytecnt +) { + unsigned __int32 a = *(unsigned __int32 *) &hash[ 0]; + unsigned __int32 b = *(unsigned __int32 *) &hash[ 4]; + unsigned __int32 c = *(unsigned __int32 *) &hash[ 8]; + unsigned __int32 d = *(unsigned __int32 *) &hash[12]; + + do { + MD5STEP(F1, a, b, c, d, 0, 7, 0xd76aa478); + MD5STEP(F1, d, a, b, c, 1, 12, 0xe8c7b756); + MD5STEP(F1, c, d, a, b, 2, 17, 0x242070db); + MD5STEP(F1, b, c, d, a, 3, 22, 0xc1bdceee); + MD5STEP(F1, a, b, c, d, 4, 7, 0xf57c0faf); + MD5STEP(F1, d, a, b, c, 5, 12, 0x4787c62a); + MD5STEP(F1, c, d, a, b, 6, 17, 0xa8304613); + MD5STEP(F1, b, c, d, a, 7, 22, 0xfd469501); + MD5STEP(F1, a, b, c, d, 8, 7, 0x698098d8); + MD5STEP(F1, d, a, b, c, 9, 12, 0x8b44f7af); + MD5STEP(F1, c, d, a, b, 10, 17, 0xffff5bb1); + MD5STEP(F1, b, c, d, a, 11, 22, 0x895cd7be); + MD5STEP(F1, a, b, c, d, 12, 7, 0x6b901122); + MD5STEP(F1, d, a, b, c, 13, 12, 0xfd987193); + MD5STEP(F1, c, d, a, b, 14, 17, 0xa679438e); + MD5STEP(F1, b, c, d, a, 15, 22, 0x49b40821); + + MD5STEP(F2, a, b, c, d, 1, 5, 0xf61e2562); + MD5STEP(F2, d, a, b, c, 6, 9, 0xc040b340); + MD5STEP(F2, c, d, a, b, 11, 14, 0x265e5a51); + MD5STEP(F2, b, c, d, a, 0, 20, 0xe9b6c7aa); + MD5STEP(F2, a, b, c, d, 5, 5, 0xd62f105d); + MD5STEP(F2, d, a, b, c, 10, 9, 0x02441453); + MD5STEP(F2, c, d, a, b, 15, 14, 0xd8a1e681); + MD5STEP(F2, b, c, d, a, 4, 20, 0xe7d3fbc8); + MD5STEP(F2, a, b, c, d, 9, 5, 0x21e1cde6); + MD5STEP(F2, d, a, b, c, 14, 9, 0xc33707d6); + MD5STEP(F2, c, d, a, b, 3, 14, 0xf4d50d87); + MD5STEP(F2, b, c, d, a, 8, 20, 0x455a14ed); + MD5STEP(F2, a, b, c, d, 13, 5, 0xa9e3e905); + MD5STEP(F2, d, a, b, c, 2, 9, 0xfcefa3f8); + MD5STEP(F2, c, d, a, b, 7, 14, 0x676f02d9); + MD5STEP(F2, b, c, d, a, 12, 20, 0x8d2a4c8a); + + MD5STEP(F3, a, b, c, d, 5, 4, 0xfffa3942); + MD5STEP(F3, d, a, b, c, 8, 11, 0x8771f681); + MD5STEP(F3, c, d, a, b, 11, 16, 0x6d9d6122); + MD5STEP(F3, b, c, d, a, 14, 23, 0xfde5380c); + MD5STEP(F3, a, b, c, d, 1, 4, 0xa4beea44); + MD5STEP(F3, d, a, b, c, 4, 11, 0x4bdecfa9); + MD5STEP(F3, c, d, a, b, 7, 16, 0xf6bb4b60); + MD5STEP(F3, b, c, d, a, 10, 23, 0xbebfbc70); + MD5STEP(F3, a, b, c, d, 13, 4, 0x289b7ec6); + MD5STEP(F3, d, a, b, c, 0, 11, 0xeaa127fa); + MD5STEP(F3, c, d, a, b, 3, 16, 0xd4ef3085); + MD5STEP(F3, b, c, d, a, 6, 23, 0x04881d05); + MD5STEP(F3, a, b, c, d, 9, 4, 0xd9d4d039); + MD5STEP(F3, d, a, b, c, 12, 11, 0xe6db99e5); + MD5STEP(F3, c, d, a, b, 15, 16, 0x1fa27cf8); + MD5STEP(F3, b, c, d, a, 2, 23, 0xc4ac5665); + + MD5STEP(F4, a, b, c, d, 0, 6, 0xf4292244); + MD5STEP(F4, d, a, b, c, 7, 10, 0x432aff97); + MD5STEP(F4, c, d, a, b, 14, 15, 0xab9423a7); + MD5STEP(F4, b, c, d, a, 5, 21, 0xfc93a039); + MD5STEP(F4, a, b, c, d, 12, 6, 0x655b59c3); + MD5STEP(F4, d, a, b, c, 3, 10, 0x8f0ccc92); + MD5STEP(F4, c, d, a, b, 10, 15, 0xffeff47d); + MD5STEP(F4, b, c, d, a, 1, 21, 0x85845dd1); + MD5STEP(F4, a, b, c, d, 8, 6, 0x6fa87e4f); + MD5STEP(F4, d, a, b, c, 15, 10, 0xfe2ce6e0); + MD5STEP(F4, c, d, a, b, 6, 15, 0xa3014314); + MD5STEP(F4, b, c, d, a, 13, 21, 0x4e0811a1); + MD5STEP(F4, a, b, c, d, 4, 6, 0xf7537e82); + MD5STEP(F4, d, a, b, c, 11, 10, 0xbd3af235); + MD5STEP(F4, c, d, a, b, 2, 15, 0x2ad7d2bb); + MD5STEP(F4, b, c, d, a, 9, 21, 0xeb86d391); + + a += *(unsigned __int32 *) &hash[ 0]; + b += *(unsigned __int32 *) &hash[ 4]; + c += *(unsigned __int32 *) &hash[ 8]; + d += *(unsigned __int32 *) &hash[12]; + + *(unsigned __int32 *) &hash[ 0] = a; + *(unsigned __int32 *) &hash[ 4] = b; + *(unsigned __int32 *) &hash[ 8] = c; + *(unsigned __int32 *) &hash[12] = d; + + pdata += 64; + } while (bytecnt -= 64); +} diff --git a/source/sfv_md5/res_sfv_md5.rc b/source/sfv_md5/res_sfv_md5.rc new file mode 100644 index 0000000000000000000000000000000000000000..343802d8df3f9776c76832f9d9015f8a1a69742a GIT binary patch literal 902 zcma))-Ae*N6vfZ0LI1_(_2lbW4XpV!9REPq>Y*UV)U5B-+9t{Z*e>vu$&x8mthuJBjy2N+?Q`8K!E0*C zqe^n)44zlY(_gMUT4S+jxuq^-Mw{Q@Y~ztEMX2 zswk|oPyf^Lcc!mtzS#}Pt=R>42SO@Ji^=}JHSQJ+z?-=(nMXuVzE@**dA(Kn)|!{u JEyKL2 + + + + Release + Win32 + + + + {3B3B9386-A7FD-4ABB-AA3A-91F4AC1548FB} + sfv_md5 + Win32Proj + + + + Application + v143 + Unicode + true + + + + + + + + + + <_ProjectFileVersion>16.0.31025.104 + + + $(SolutionDir)$(Configuration)\ + $(Configuration)\ + false + false + + + + MaxSpeed + OnlyExplicitInline + true + WIN32;NDEBUG;_CONSOLE;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) + MultiThreaded + false + true + false + false + + Level3 + + + + imagehlp.lib;%(AdditionalDependencies) + false + Console + true + true + true + MachineX86 + UseLinkTimeCodeGeneration + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/source/sfv_md5/verify.c b/source/sfv_md5/verify.c new file mode 100644 index 0000000..aaadd23 --- /dev/null +++ b/source/sfv_md5/verify.c @@ -0,0 +1,969 @@ +// verify.c +// Copyright : 2022-02-25 Yutaka Sawada +// License : The MIT license + +#ifndef _UNICODE +#define _UNICODE +#endif +#ifndef UNICODE +#define UNICODE +#endif +#ifndef _WIN32_WINNT +#define _WIN32_WINNT 0x0600 // Windows Vista or later +#endif + +#include + +#include + +#include "common.h" +#include "crc.h" +#include "verify.h" +#include "ini.h" +#include "phmd5.h" + +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +// CRC-32 を比較する +// 0 = ファイルが存在して完全である +// 1 = ファイルが存在しない +// 2 = ファイルが破損してる +static int file_crc32_check( + int num, + char *ascii_name, + wchar_t *uni_name, + wchar_t *file_path, + unsigned int crc2) +{ + unsigned char buf[IO_SIZE]; + int len, off, bad_flag; + unsigned int crc, time_last, meta_data[7]; + __int64 file_size = 0, file_left; + HANDLE hFile; + + prog_last = -1; + time_last = GetTickCount() / UPDATE_TIME; // 時刻の変化時に経過を表示する + wcscpy(file_path, base_dir); + // 先頭の「..\」を許可してるので、基準ディレクトリから上に遡る。 + len = base_len - 1; + off = 0; + while ((uni_name[off] == '.') && (uni_name[off + 1] == '.') && (uni_name[off + 2] == '\\')){ + off += 3; + file_path[len] = 0; + while (file_path[len] != '\\'){ + file_path[len] = 0; + len--; + } + } + wcscat(file_path, uni_name + off); + + // 読み込むファイルを開く + hFile = CreateFile(file_path, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL); + if (hFile == INVALID_HANDLE_VALUE){ + bad_flag = 1; // 消失は記録しない + } else { + bad_flag = check_ini_state(num, meta_data, hFile); + memcpy(&file_size, meta_data, 8); + if (bad_flag == -2){ // 記録が無い場合 (属性取得エラーは不明にする) + file_left = file_size; + crc = 0xFFFFFFFF; // 初期化 + while (file_left > 0){ + len = IO_SIZE; + if (file_left < IO_SIZE) + len = (int)file_left; + if (!ReadFile(hFile, buf, len, &bad_flag, NULL) || (len != bad_flag)) + break; // 読み取りエラーは必ず破損になる + file_left -= len; + // CRC-32 を更新する + crc = crc_update(crc, buf, len); + + // 経過表示 + if (GetTickCount() / UPDATE_TIME != time_last){ + if (print_progress_file((int)(((file_size - file_left) * 1000) / file_size), uni_name)){ + CloseHandle(hFile); + return 2; + } + time_last = GetTickCount() / UPDATE_TIME; + } + } + crc ^= 0xFFFFFFFF; // 最終処理 + if ((crc != crc2) || (file_left > 0)){ + bad_flag = 2; // ハッシュ値が異なる、または途中まで + } else { + bad_flag = 0; // 完全 + } + write_ini_state(num, meta_data, bad_flag); // 検査結果を記録する、完全か破損 + } + CloseHandle(hFile); + } + + switch (bad_flag){ + case 0: + printf("%13I64d Complete : \"%s\"\n", file_size, ascii_name); + break; + case 1: + printf(" 0 Missing : \"%s\"\n", ascii_name); + bad_flag = 4 | 8; + break; + case 2: + printf("%13I64d Damaged : \"%s\"\n", file_size, ascii_name); + bad_flag = 4; + break; + default: // IOエラー等 + printf(" ? Unknown : \"%s\"\n", ascii_name); + bad_flag = 4; + break; + } + fflush(stdout); + return bad_flag; +} + +// SFV ファイル +int verify_sfv( + char *ascii_buf, + wchar_t *file_path) +{ + wchar_t *line_off, uni_buf[MAX_LEN]; + unsigned int err = 0, rv, line_len, name_len, crc, line_num, num, comment; + + // ファイル数を調べる + comment = 0; + num = 0; + line_num = 1; + line_off = text_buf; + while (*line_off != 0){ // 一行ずつ処理する + line_len = 0; + while (line_off[line_len] != 0){ // 改行までを一行とする + if (line_off[line_len] == '\n') + break; + if (line_off[line_len] == '\r') + break; + line_len++; + } + // 行の内容が適正か調べる + if (line_off[0] == ';'){ // コメント + if (((comment & 1) == 0) && (line_len > 8) && (line_len < MAX_LEN)){ + // クリエイターの表示がまだなら + if (wcsncmp(line_off, L"; Generated by ", 15) == 0){ // WIN-SFV32, SFV32nix + wcsncpy(uni_buf, line_off + 15, line_len - 15); + uni_buf[line_len - 15] = 0; + comment |= 1; + } else if (wcsncmp(line_off, L"; Using ", 8) == 0){ + wcsncpy(uni_buf, line_off + 8, line_len - 8); + uni_buf[line_len - 8] = 0; + comment |= 1; // 同一行を二度表示しない + } + if (comment & 1){ + uni_buf[COMMENT_LEN - 1] = 0; // 表示する文字数を制限する + utf16_to_cp(uni_buf, ascii_buf, COMMENT_LEN * 3, cp_output); + printf("Creator : %s\n", ascii_buf); + comment |= 4; + } + } + if (((comment & 6) == 0) && (line_len < MAX_LEN)){ + // 最初のコメントの表示がまだなら + crc = 1; + while (crc < line_len){ + if (line_off[crc] != ' ') + break; + crc++; + } + if (crc < line_len){ + wcsncpy(uni_buf, line_off + crc, line_len - crc); + uni_buf[line_len - crc] = 0; + uni_buf[COMMENT_LEN - 1] = 0; // 表示する文字数を制限する + utf16_to_cp(uni_buf, ascii_buf, COMMENT_LEN * 3, cp_output); + printf("Comment : %s\n", ascii_buf); + } + comment |= 2; // 「;」だけの行も認識する + } + comment &= ~4; // bit 4 を消す + } else if ((line_len > 9) && (line_off[line_len - 9] == ' ') // CRC の前がスペース + && (base16_len(line_off + (line_len - 8)) == 8)){ // 16進数で8文字 + // ファイル名 + name_len = line_len - 9; + if (base_len + name_len < MAX_LEN){ + while (line_off[name_len - 1] == ' ') + name_len--; + // ファイル名の前後が「"」で囲まれてる場合は取り除く + if ((line_off[0] == '"') && (line_off[name_len - 1] == '"')){ + name_len -= 2; + wcsncpy(uni_buf, line_off + 1, name_len); + } else { + wcsncpy(uni_buf, line_off, name_len); + } + uni_buf[name_len] = 0; + rv = sanitize_filename(uni_buf); // ファイル名を浄化する + if (rv > 1){ + if ((comment & 8) == 0){ + comment |= 8; + printf("\nWarning about filenames :\n"); + } + if (rv == 16){ + printf("line%d: filename is invalid\n", line_num); + num++; // 浄化できないファイル名 + } else if ((rv & 6) == 0){ + utf16_to_cp(uni_buf, ascii_buf, MAX_LEN * 3, cp_output); + printf("line%d: \"%s\" is invalid\n", line_num, ascii_buf); + } else { + utf16_to_cp(uni_buf, ascii_buf, MAX_LEN * 3, cp_output); + printf("line%d: \"%s\" was sanitized\n", line_num, ascii_buf); + } + } + if (rv != 16) + file_num++; + } else { + if ((comment & 8) == 0){ + comment |= 8; + printf("\nWarning about filenames :\n"); + } + printf("line%d: filename is invalid\n", line_num); + num++; // 長すぎるファイル名 + } + } else if (line_len > 0){ + //printf("line %d is invalid\n", line_num); + num++; // 内容が認識できない行 + } + // 次の行へ + line_off += line_len; + if (*line_off == '\n') + line_off++; + if (*line_off == '\r'){ + line_off++; + if (*line_off == '\n') // 「\r\n」を一つの改行として扱う + line_off++; + } + line_num++; + } + if (comment & 8) + printf("\n"); + // チェックサム・ファイルの状態 + if (num == 0){ + printf("Status : Good\n"); + } else { + printf("Status : Damaged\n"); + err |= 16; // 後で 256に変更する + } + if (file_num == 0){ + printf("valid file is not found\n"); + return 1; + } + + printf("\nInput File list : %d\n", file_num); + printf(" CRC-32 : Filename\n"); + fflush(stdout); + line_off = text_buf; + while (*line_off != 0){ // 一行ずつ処理する + line_len = 0; + while (line_off[line_len] != 0){ // 改行までを一行とする + if (line_off[line_len] == '\n') + break; + if (line_off[line_len] == '\r') + break; + line_len++; + } + // 行の内容が適正か調べる + if ((line_off[0] != ';') && // コメントではない + (line_len > 9) && (line_off[line_len - 9] == ' ') // CRC の前がスペース + && (base16_len(line_off + (line_len - 8)) == 8)){ // 16進数で8文字 + // ファイル名 + name_len = line_len - 9; + if (base_len + name_len < MAX_LEN){ + while (line_off[name_len - 1] == ' ') + name_len--; + // ファイル名の前後が「"」で囲まれてる場合は取り除く + if ((line_off[0] == '"') && (line_off[name_len - 1] == '"')){ + name_len -= 2; + wcsncpy(uni_buf, line_off + 1, name_len); + } else { + wcsncpy(uni_buf, line_off, name_len); + } + uni_buf[name_len] = 0; + rv = sanitize_filename(uni_buf); // ファイル名を浄化する + if (rv != 16){ + utf16_to_cp(uni_buf, ascii_buf, MAX_LEN * 3, cp_output); + // CRC + crc = get_val32h(line_off + (line_len - 8)); + printf("%08X : \"%s\"\n", crc, ascii_buf); + } + } + } + // 次の行へ + line_off += line_len; + if (*line_off == '\n') + line_off++; + if (*line_off == '\r'){ + line_off++; + if (*line_off == '\n') // 「\r\n」を一つの改行として扱う + line_off++; + } + } + + + printf("\nVerifying Input File :\n"); + printf(" Size Status : Filename\n"); + fflush(stdout); + if (recent_data != 0){ // 前回の検査結果を使うなら + PHMD5 ctx; + // チェックサム・ファイル識別用にハッシュ値を計算する + Phmd5Begin(&ctx); + Phmd5Process(&ctx, (unsigned char *)text_buf, text_len * 2); + Phmd5End(&ctx); + // 前回の検査結果が存在するか + check_ini_file(ctx.hash, text_len); + } + num = 0; + line_off = text_buf; + while (*line_off != 0){ // 一行ずつ処理する + line_len = 0; + while (line_off[line_len] != 0){ // 改行までを一行とする + if (line_off[line_len] == '\n') + break; + if (line_off[line_len] == '\r') + break; + line_len++; + } + // 行の内容が適正か調べる + if ((line_off[0] != ';') && // コメントではない + (line_len > 9) && (line_off[line_len - 9] == ' ') // CRC の前がスペース + && (base16_len(line_off + (line_len - 8)) == 8)){ // 16進数で8文字 + // ファイル名 + name_len = line_len - 9; + if (base_len + name_len < MAX_LEN){ + while (line_off[name_len - 1] == ' ') + name_len--; + // ファイル名の前後が「"」で囲まれてる場合は取り除く + if ((line_off[0] == '"') && (line_off[name_len - 1] == '"')){ + name_len -= 2; + wcsncpy(uni_buf, line_off + 1, name_len); + } else { + wcsncpy(uni_buf, line_off, name_len); + } + uni_buf[name_len] = 0; + rv = sanitize_filename(uni_buf); // ファイル名を浄化する + if (rv != 16){ + utf16_to_cp(uni_buf, ascii_buf, MAX_LEN * 3, cp_output); + // CRC + crc = get_val32h(line_off + (line_len - 8)); + rv = file_crc32_check(num, ascii_buf, uni_buf, file_path, crc); + if (rv & 3){ + err = rv; + break; + } + if (rv & 0xC){ // Missing or Damaged + err |= rv; + err += 0x100; + } + num++; + } + } + } + // 次の行へ + line_off += line_len; + if (*line_off == '\n') + line_off++; + if (*line_off == '\r'){ + line_off++; + if (*line_off == '\n') // 「\r\n」を一つの改行として扱う + line_off++; + } + } + + return err; +} + +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +// MD5 を比較する +// 0 = ファイルが存在して完全である +// 1 = ファイルが存在しない +// 2 = ファイルが破損してる +static int file_md5_check( + int num, + char *ascii_name, + wchar_t *uni_name, + wchar_t *file_path, + unsigned char *hash) +{ + unsigned char buf[IO_SIZE]; + int len, off, bad_flag; + unsigned int time_last, meta_data[8]; + __int64 file_size = 0, file_left; + HANDLE hFile; + PHMD5 ctx; + + prog_last = -1; + time_last = GetTickCount() / UPDATE_TIME; // 時刻の変化時に経過を表示する + wcscpy(file_path, base_dir); + // 先頭の「..\」を許可してるので、基準ディレクトリから上に遡る。 + len = base_len - 1; + off = 0; + while ((uni_name[off] == '.') && (uni_name[off + 1] == '.') && (uni_name[off + 2] == '\\')){ + off += 3; + file_path[len] = 0; + while (file_path[len] != '\\'){ + file_path[len] = 0; + len--; + } + } + wcscat(file_path, uni_name + off); + //printf("path = \"%S\"\n", file_path); + + // 読み込むファイルを開く + hFile = CreateFile(file_path, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL); + if (hFile == INVALID_HANDLE_VALUE){ + bad_flag = 1; // 消失は記録しない + } else { + bad_flag = check_ini_state(num, meta_data, hFile); + memcpy(&file_size, meta_data, 8); + if (bad_flag == -2){ // 記録が無い場合 (属性取得エラーは不明にする) + file_left = file_size; + Phmd5Begin(&ctx); // 初期化 + while (file_left > 0){ + len = IO_SIZE; + if (file_left < IO_SIZE) + len = (int)file_left; + if (!ReadFile(hFile, buf, len, &bad_flag, NULL) || (len != bad_flag)) + break; // 読み取りエラーは必ず破損になる + file_left -= len; + // MD5 を更新する + Phmd5Process(&ctx, buf, len); + + // 経過表示 + if (GetTickCount() / UPDATE_TIME != time_last){ + if (print_progress_file((int)(((file_size - file_left) * 1000) / file_size), uni_name)){ + CloseHandle(hFile); + return 2; + } + time_last = GetTickCount() / UPDATE_TIME; + } + } + Phmd5End(&ctx); // 最終処理 +// printf("%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X %d\n", +// ctx.hash[0], ctx.hash[1], ctx.hash[2], ctx.hash[3], ctx.hash[4], ctx.hash[5], ctx.hash[6], ctx.hash[7], +// ctx.hash[8], ctx.hash[9], ctx.hash[10], ctx.hash[11], ctx.hash[12], ctx.hash[13], ctx.hash[14], ctx.hash[15]); + if ((memcmp(hash, ctx.hash, 16) != 0) || (file_left > 0)){ + bad_flag = 2; // ハッシュ値が異なる、または途中まで + } else { + bad_flag = 0; // 完全 + } + write_ini_state(num, meta_data, bad_flag); // 検査結果を記録する、完全か破損 + } + CloseHandle(hFile); + } + + switch (bad_flag){ + case 0: + printf("%13I64d Complete : \"%s\"\n", file_size, ascii_name); + break; + case 1: + printf(" 0 Missing : \"%s\"\n", ascii_name); + bad_flag = 4 | 8; + break; + case 2: + printf("%13I64d Damaged : \"%s\"\n", file_size, ascii_name); + bad_flag = 4; + break; + default: // IOエラー等 + printf(" ? Unknown : \"%s\"\n", ascii_name); + bad_flag = 4; + break; + } + fflush(stdout); + return bad_flag; +} + +// MD5 ファイル +int verify_md5( + char *ascii_buf, + wchar_t *file_path) +{ + unsigned char hash[16]; + wchar_t *line_off, uni_buf[MAX_LEN], num_buf[3]; + unsigned int err = 0, i, rv, line_len, name_len, line_num, num, comment; + + // ファイル数を調べる + comment = 0; + num = 0; + line_num = 1; + line_off = text_buf; + while (*line_off != 0){ // 一行ずつ処理する + line_len = 0; + while (line_off[line_len] != 0){ // 改行までを一行とする + if (line_off[line_len] == '\n') + break; + if (line_off[line_len] == '\r') + break; + line_len++; + } + // 行の内容が適正か調べる + if (line_off[0] == ';'){ // コメント + if (((comment & 1) == 0) && (line_len > 8) && (line_len < MAX_LEN)){ + // クリエイターの表示がまだなら + if (wcsncmp(line_off, L"; Generated by ", 15) == 0){ // md5sum, Easy MD5 Creator + wcsncpy(uni_buf, line_off + 15, line_len - 15); + uni_buf[line_len - 15] = 0; + comment = 1; + } else if (wcsncmp(line_off, L"; Using ", 8) == 0){ + wcsncpy(uni_buf, line_off + 8, line_len - 8); + uni_buf[line_len - 8] = 0; + comment = 1; + } + if (comment & 1){ + uni_buf[COMMENT_LEN - 1] = 0; // 表示する文字数を制限する + utf16_to_cp(uni_buf, ascii_buf, COMMENT_LEN * 3, cp_output); + printf("Creator : %s\n", ascii_buf); + comment |= 4; // 同一行を二度表示しない + } + } + if (((comment & 6) == 0) && (line_len < MAX_LEN)){ + // 最初のコメントの表示がまだなら + i = 1; + while (i < line_len){ + if (line_off[i] != ' ') + break; + i++; + } + if (i < line_len){ + wcsncpy(uni_buf, line_off + i, line_len - i); + uni_buf[line_len - i] = 0; + uni_buf[COMMENT_LEN - 1] = 0; // 表示する文字数を制限する + utf16_to_cp(uni_buf, ascii_buf, COMMENT_LEN * 3, cp_output); + printf("Comment : %s\n", ascii_buf); + } + comment |= 2; // 「;」だけの行も認識する + } + comment &= ~4; // bit 4 を消す + } else if (line_off[0] == '#'){ // コメント + if (((comment & 1) == 0) && (line_len > 8) && (line_len < MAX_LEN)){ + // クリエイターの表示がまだなら + if (wcsncmp(line_off, L"# Generated by ", 15) == 0){ // OpenHashTab + wcsncpy(uni_buf, line_off + 15, line_len - 15); + uni_buf[line_len - 15] = 0; + comment = 1; + } else if (wcsncmp(line_off, L"# MD5 checksum generated by ", 28) == 0){ // IsoBuster + wcsncpy(uni_buf, line_off + 28, line_len - 28); + uni_buf[line_len - 28] = 0; + comment = 1; + } else if (wcsncmp(line_off, L"#MD5 checksums generated by ", 28) == 0){ // xACT + wcsncpy(uni_buf, line_off + 28, line_len - 28); + uni_buf[line_len - 28] = 0; + comment = 1; + } else if (wcsncmp(line_off, L"# MD5 checksums generated by ", 29) == 0){ // MD5summer + wcsncpy(uni_buf, line_off + 29, line_len - 29); + uni_buf[line_len - 29] = 0; + comment = 1; + } + if (comment & 1){ + uni_buf[COMMENT_LEN - 1] = 0; // 表示する文字数を制限する + utf16_to_cp(uni_buf, ascii_buf, COMMENT_LEN * 3, cp_output); + printf("Creator : %s\n", ascii_buf); + } + } + } else if (line_len > 33){ // コメントではない + // MD5 の後がスペース (md5sum 形式) + if ((line_off[32] == ' ') && (base16_len(line_off) == 32)){ + // ファイル名 + name_len = line_len - 33; + if (base_len + name_len < MAX_LEN){ + // タイプ記号「*」までのスペースは複数個でもいい + while (line_off[line_len - name_len] == ' ') + name_len--; + if (line_off[line_len - name_len] == '*') + name_len--; + wcsncpy(uni_buf, line_off + (line_len - name_len), name_len); + uni_buf[name_len] = 0; + rv = sanitize_filename(uni_buf); // ファイル名を浄化する + if (rv > 1){ + if ((comment & 8) == 0){ + comment |= 8; + printf("\nWarning about filenames :\n"); + } + if (rv == 16){ + printf("line%d: filename is invalid\n", line_num); + num++; // 浄化できないファイル名 + } else if ((rv & 6) == 0){ + utf16_to_cp(uni_buf, ascii_buf, MAX_LEN * 3, cp_output); + printf("line%d: \"%s\" is invalid\n", line_num, ascii_buf); + } else { + utf16_to_cp(uni_buf, ascii_buf, MAX_LEN * 3, cp_output); + printf("line%d: \"%s\" was sanitized\n", line_num, ascii_buf); + } + } + if (rv != 16) + file_num++; + } else { + if ((comment & 8) == 0){ + comment |= 8; + printf("\nWarning about filenames :\n"); + } + printf("line%d: filename is invalid\n", line_num); + num++; // 長すぎるファイル名 + } + + // ファイル名の後にMD5 + } else if ((line_off[line_len - 33] == ' ') && (base16_len(line_off + (line_len - 32)) == 32)){ + // MD5 の前が括弧 (BSD/OpenSSL 形式) + if (((wcsncmp(line_off, L"MD5(", 4) == 0) || (wcsncmp(line_off, L"MD5 (", 5) == 0)) + && ((wcsncmp(line_off + (line_len - 35), L")= ", 3) == 0) || + (wcsncmp(line_off + (line_len - 36), L") = ", 4) == 0))){ + // ファイル名 + name_len = line_len - 35 - 4; + if (base_len + name_len < MAX_LEN){ + if (line_off[line_len - 35] != ')') + name_len--; + if (line_off[3] == ' '){ + name_len--; + wcsncpy(uni_buf, line_off + 5, name_len); + } else { + wcsncpy(uni_buf, line_off + 4, name_len); + } + uni_buf[name_len] = 0; + rv = sanitize_filename(uni_buf); // ファイル名を浄化する + if (rv > 1){ + if ((comment & 8) == 0){ + comment |= 8; + printf("\nWarning about filenames :\n"); + } + if (rv == 16){ + printf("line%d: filename is invalid\n", line_num); + num++; // 浄化できないファイル名 + } else if ((rv & 6) == 0){ + utf16_to_cp(uni_buf, ascii_buf, MAX_LEN * 3, cp_output); + printf("line%d: \"%s\" is invalid\n", line_num, ascii_buf); + } else { + utf16_to_cp(uni_buf, ascii_buf, MAX_LEN * 3, cp_output); + printf("line%d: \"%s\" was sanitized\n", line_num, ascii_buf); + } + } + if (rv != 16) + file_num++; + } else { + if ((comment & 8) == 0){ + comment |= 8; + printf("\nWarning about filenames :\n"); + } + printf("line%d: filename is invalid\n", line_num); + num++; // 長すぎるファイル名 + } + } else { // MD5 の前がスペース (Easy MD5 Creator 形式) + // ファイル名 + name_len = line_len - 33; + if (base_len + name_len < MAX_LEN){ + while (line_off[name_len - 1] == ' ') + name_len--; + wcsncpy(uni_buf, line_off, name_len); + uni_buf[name_len] = 0; + rv = sanitize_filename(uni_buf); // ファイル名を浄化する + if (rv > 1){ + if ((comment & 8) == 0){ + comment |= 8; + printf("\nWarning about filenames :\n"); + } + if (rv == 16){ + printf("line%d: filename is invalid\n", line_num); + num++; // 浄化できないファイル名 + } else if ((rv & 6) == 0){ + utf16_to_cp(uni_buf, ascii_buf, MAX_LEN * 3, cp_output); + printf("line%d: \"%s\" is invalid\n", line_num, ascii_buf); + } else { + utf16_to_cp(uni_buf, ascii_buf, MAX_LEN * 3, cp_output); + printf("line%d: \"%s\" was sanitized\n", line_num, ascii_buf); + } + } + if (rv != 16) + file_num++; + } else { + if ((comment & 8) == 0){ + comment |= 8; + printf("\nWarning about filenames :\n"); + } + printf("line%d: filename is invalid\n", line_num); + num++; // 長すぎるファイル名 + } + } + } else { + num++; // 内容が認識できない行 + } + } else if (line_len > 0){ + num++; // 内容が認識できない行 + } + // 次の行へ + line_off += line_len; + if (*line_off == '\n') + line_off++; + if (*line_off == '\r'){ + line_off++; + if (*line_off == '\n') // 「\r\n」を一つの改行として扱う + line_off++; + } + line_num++; + } + if (comment & 8) + printf("\n"); + // チェックサム・ファイルの状態 + if (num == 0){ + printf("Status : Good\n"); + } else { + printf("Status : Damaged\n"); + err |= 16; // 後で 256に変更する + } + if (file_num == 0){ + printf("valid file is not found\n"); + return 1; + } + + printf("\nInput File list : %d\n", file_num); + printf("\t MD5 Hash\t\t : Filename\n"); + fflush(stdout); + line_off = text_buf; + while (*line_off != 0){ // 一行ずつ処理する + line_len = 0; + while (line_off[line_len] != 0){ // 改行までを一行とする + if (line_off[line_len] == '\n') + break; + if (line_off[line_len] == '\r') + break; + line_len++; + } + // 行の内容が適正か調べる + if ((line_off[0] != ';') && (line_len > 33)){ // コメントではない + // MD5 の後がスペース (md5sum 形式) + if ((line_off[32] == ' ') && (base16_len(line_off) == 32)){ + // ファイル名 + name_len = line_len - 33; + if (base_len + name_len < MAX_LEN){ + // タイプ記号「*」までのスペースは複数個でもいい + while (line_off[line_len - name_len] == ' ') + name_len--; + if (line_off[line_len - name_len] == '*') + name_len--; + wcsncpy(uni_buf, line_off + (line_len - name_len), name_len); + uni_buf[name_len] = 0; + rv = sanitize_filename(uni_buf); // ファイル名を浄化する + if (rv != 16){ + utf16_to_cp(uni_buf, ascii_buf, MAX_LEN * 3, cp_output); + // MD5 + num_buf[2] = 0; + for (i = 0; i < 16; i++){ + wcsncpy(num_buf, line_off + (i * 2), 2); + hash[i] = (unsigned char)get_val32h(num_buf); + } + printf("%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X : \"%s\"\n", + hash[0], hash[1], hash[2], hash[3], hash[4], hash[5], hash[6], hash[7], + hash[8], hash[9], hash[10], hash[11], hash[12], hash[13], hash[14], hash[15], ascii_buf); + } + } + + // ファイル名の後にMD5 + } else if ((line_off[line_len - 33] == ' ') && (base16_len(line_off + (line_len - 32)) == 32)){ + // MD5 の前が括弧 (BSD/OpenSSL 形式) + if (((wcsncmp(line_off, L"MD5(", 4) == 0) || (wcsncmp(line_off, L"MD5 (", 5) == 0)) + && ((wcsncmp(line_off + (line_len - 35), L")= ", 3) == 0) || + (wcsncmp(line_off + (line_len - 36), L") = ", 4) == 0))){ + // ファイル名 + name_len = line_len - 35 - 4; + if (base_len + name_len < MAX_LEN){ + if (line_off[line_len - 35] != ')') + name_len--; + if (line_off[3] == ' '){ + name_len--; + wcsncpy(uni_buf, line_off + 5, name_len); + } else { + wcsncpy(uni_buf, line_off + 4, name_len); + } + uni_buf[name_len] = 0; + rv = sanitize_filename(uni_buf); // ファイル名を浄化する + if (rv != 16){ + utf16_to_cp(uni_buf, ascii_buf, MAX_LEN * 3, cp_output); + // MD5 + num_buf[2] = 0; + for (i = 0; i < 16; i++){ + wcsncpy(num_buf, line_off + (line_len - 32 + (i * 2)), 2); + hash[i] = (unsigned char)get_val32h(num_buf); + } + printf("%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X : \"%s\"\n", + hash[0], hash[1], hash[2], hash[3], hash[4], hash[5], hash[6], hash[7], + hash[8], hash[9], hash[10], hash[11], hash[12], hash[13], hash[14], hash[15], ascii_buf); + } + } + } else { // MD5 の前がスペース (Easy MD5 Creator 形式) + // ファイル名 + name_len = line_len - 33; + if (base_len + name_len < MAX_LEN){ + while (line_off[name_len - 1] == ' ') + name_len--; + wcsncpy(uni_buf, line_off, name_len); + uni_buf[name_len] = 0; + rv = sanitize_filename(uni_buf); // ファイル名を浄化する + if (rv != 16){ + utf16_to_cp(uni_buf, ascii_buf, MAX_LEN * 3, cp_output); + // MD5 + num_buf[2] = 0; + for (i = 0; i < 16; i++){ + wcsncpy(num_buf, line_off + (line_len - 32 + (i * 2)), 2); + hash[i] = (unsigned char)get_val32h(num_buf); + } + printf("%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X : \"%s\"\n", + hash[0], hash[1], hash[2], hash[3], hash[4], hash[5], hash[6], hash[7], + hash[8], hash[9], hash[10], hash[11], hash[12], hash[13], hash[14], hash[15], ascii_buf); + } + } + } + } + } + // 次の行へ + line_off += line_len; + if (*line_off == '\n') + line_off++; + if (*line_off == '\r'){ + line_off++; + if (*line_off == '\n') // 「\r\n」を一つの改行として扱う + line_off++; + } + } + + printf("\nVerifying Input File :\n"); + printf(" Size Status : Filename\n"); + fflush(stdout); + if (recent_data != 0){ // 前回の検査結果を使うなら + PHMD5 ctx; + // チェックサム・ファイル識別用にハッシュ値を計算する + Phmd5Begin(&ctx); + Phmd5Process(&ctx, (unsigned char *)text_buf, text_len * 2); + Phmd5End(&ctx); + // 前回の検査結果が存在するか + check_ini_file(ctx.hash, text_len); + } + num = 0; + line_off = text_buf; + while (*line_off != 0){ // 一行ずつ処理する + line_len = 0; + while (line_off[line_len] != 0){ // 改行までを一行とする + if (line_off[line_len] == '\n') + break; + if (line_off[line_len] == '\r') + break; + line_len++; + } + // 行の内容が適正か調べる + if ((line_off[0] != ';') && (line_len > 33)){ // コメントではない + // MD5 の後がスペース (md5sum 形式) + if ((line_off[32] == ' ') && (base16_len(line_off) == 32)){ + // ファイル名 + name_len = line_len - 33; + if (base_len + name_len < MAX_LEN){ + // タイプ記号「*」までのスペースは複数個でもいい + while (line_off[line_len - name_len] == ' ') + name_len--; + if (line_off[line_len - name_len] == '*') + name_len--; + wcsncpy(uni_buf, line_off + (line_len - name_len), name_len); + uni_buf[name_len] = 0; + rv = sanitize_filename(uni_buf); // ファイル名を浄化する + if (rv != 16){ + utf16_to_cp(uni_buf, ascii_buf, MAX_LEN * 3, cp_output); + // MD5 + num_buf[2] = 0; + for (i = 0; i < 16; i++){ + wcsncpy(num_buf, line_off + (i * 2), 2); + hash[i] = (unsigned char)get_val32h(num_buf); + } + rv = file_md5_check(num, ascii_buf, uni_buf, file_path, hash); + if (rv & 3){ + err = rv; + break; + } + if (rv & 0xC){ // Missing or Damaged + err |= rv; + err += 0x100; + } + num++; + } + } + + // ファイル名の後にMD5 + } else if ((line_off[line_len - 33] == ' ') && (base16_len(line_off + (line_len - 32)) == 32)){ + // MD5 の前が括弧 (BSD/OpenSSL 形式) + if (((wcsncmp(line_off, L"MD5(", 4) == 0) || (wcsncmp(line_off, L"MD5 (", 5) == 0)) + && ((wcsncmp(line_off + (line_len - 35), L")= ", 3) == 0) || + (wcsncmp(line_off + (line_len - 36), L") = ", 4) == 0))){ + // ファイル名 + name_len = line_len - 35 - 4; + if (base_len + name_len < MAX_LEN){ + if (line_off[line_len - 35] != ')') + name_len--; + if (line_off[3] == ' '){ + name_len--; + wcsncpy(uni_buf, line_off + 5, name_len); + } else { + wcsncpy(uni_buf, line_off + 4, name_len); + } + uni_buf[name_len] = 0; + rv = sanitize_filename(uni_buf); // ファイル名を浄化する + if (rv != 16){ + utf16_to_cp(uni_buf, ascii_buf, MAX_LEN * 3, cp_output); + // MD5 + num_buf[2] = 0; + for (i = 0; i < 16; i++){ + wcsncpy(num_buf, line_off + (line_len - 32 + (i * 2)), 2); + hash[i] = (unsigned char)get_val32h(num_buf); + } + rv = file_md5_check(num, ascii_buf, uni_buf, file_path, hash); + if (rv & 3){ + err = rv; + break; + } + if (rv & 0xC){ // Missing or Damaged + err |= rv; + err += 0x100; + } + num++; + } + } + } else { // MD5 の前がスペース (Easy MD5 Creator 形式) + // ファイル名 + name_len = line_len - 33; + if (base_len + name_len < MAX_LEN){ + while (line_off[name_len - 1] == ' ') + name_len--; + wcsncpy(uni_buf, line_off, name_len); + uni_buf[name_len] = 0; + rv = sanitize_filename(uni_buf); // ファイル名を浄化する + if (rv != 16){ + utf16_to_cp(uni_buf, ascii_buf, MAX_LEN * 3, cp_output); + // MD5 + num_buf[2] = 0; + for (i = 0; i < 16; i++){ + wcsncpy(num_buf, line_off + (line_len - 32 + (i * 2)), 2); + hash[i] = (unsigned char)get_val32h(num_buf); + } + rv = file_md5_check(num, ascii_buf, uni_buf, file_path, hash); + if (rv & 3){ + err = rv; + break; + } + if (rv & 0xC){ // Missing or Damaged + err |= rv; + err += 0x100; + } + num++; + } + } + } + } + } + // 次の行へ + line_off += line_len; + if (*line_off == '\n') + line_off++; + if (*line_off == '\r'){ + line_off++; + if (*line_off == '\n') // 「\r\n」を一つの改行として扱う + line_off++; + } + } + + return err; +} + diff --git a/source/sfv_md5/verify.h b/source/sfv_md5/verify.h new file mode 100644 index 0000000..32fce1a --- /dev/null +++ b/source/sfv_md5/verify.h @@ -0,0 +1,11 @@ + +// SFV ファイル +int verify_sfv( + char *ascii_buf, + wchar_t *file_path); + +// MD5 ファイル +int verify_md5( + char *ascii_buf, + wchar_t *file_path); + diff --git a/source/sfv_md5/version.h b/source/sfv_md5/version.h new file mode 100644 index 0000000..c9e0ab1 --- /dev/null +++ b/source/sfv_md5/version.h @@ -0,0 +1,2 @@ +#define FILE_VERSION "1.3.2.8" // ファイルのバージョン番号 +#define PRODUCT_VERSION "1.3.2" // 製品のバージョン番号