diff --git a/.gitignore b/.gitignore index 30d388a..8f70fbb 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ -build* \ No newline at end of file +build* +.qtcreator/ \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json index fcf3710..e7a1afe 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -19,6 +19,9 @@ "ignoreFailures": true } ], + "args": [ + + ] //"visualizerFile": "${workspaceRoot}/.vscode/qt6.natvis", }, "cmake.configureArgs": [ diff --git a/CMakeLists.txt b/CMakeLists.txt index c581933..dd95e29 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -27,6 +27,7 @@ configure_file(ToolBox.h.in ToolBox.h) message(STATUS "Version file config to: ${CMAKE_CURRENT_BINARY_DIR}") include_directories(${CMAKE_CURRENT_BINARY_DIR}) +include_directories(public) add_subdirectory(fileUpdater) if(WIN32) diff --git a/fileUpdater/main.cpp b/fileUpdater/main.cpp index f32f7b9..fb199f8 100644 --- a/fileUpdater/main.cpp +++ b/fileUpdater/main.cpp @@ -15,6 +15,8 @@ #include #include +#include "SimMd5.hpp" + #ifdef _WIN32 #include #endif @@ -33,27 +35,30 @@ struct FileMapping { // 操作记录结构 struct OperationRecord { - fs::path source; // 源文件绝对路径 - fs::path target; // 目标文件绝对路径 - fs::path backup; // 备份路径 - bool exists; // 目标文件是否存在 - bool isNew; // 是否是新增文件 + std::string backupRelative; // 相对备份路径 + fs::path source; // 源文件绝对路径 + fs::path target; // 目标文件绝对路径 + fs::path backup; // 备份路径 + bool exists; // 目标文件是否存在 + bool isNew; // 是否是新增文件 }; class AutoUpdateTool { private: std::string configFile; - fs::path workDir; // 工作目录(新文件所在) - fs::path updateDir; // 更新目录(目标目录) - fs::path backupDir; // 备份目录 - std::string fromType; // 源类型:"m" 或 "n" - std::string toType; // 目标类型:"m" 或 "n" + fs::path workDir; // 工作目录(新文件所在) + fs::path updateDir{"."}; // 更新目录(目标目录) + fs::path backupDir; // 备份目录 + std::string fromType; // 源类型:"m" 或 "n" + std::string toType; // 目标类型:"m" 或 "n" std::vector mappings; std::shared_ptr logger; std::string markerDir; // 标记目录名 std::string logPath_; std::string logName_{"fileUpdater.log"}; + bool verify_{true}; + SimpleMD5 md5_; public: AutoUpdateTool() @@ -94,7 +99,7 @@ public: app.add_option("-w,--work-dir", workDir, "工作目录(新文件所在)")->default_val(fs::current_path()); - app.add_option("-u,--update-dir", updateDir, "更新目录(目标目录)")->required(); + app.add_option("-u,--update-dir", updateDir, "更新目录(目标目录)"); app.add_option("-b,--backup-dir", backupDir, "备份目录")->required(); @@ -102,6 +107,8 @@ public: app.add_option("-t,--to", toType, "目标类型 (m 或 n)")->required()->check(CLI::IsMember({"m", "n"})); + app.add_flag("-v,--verify", verify_, "是否进行备份校验(默认是)。"); + try { app.parse(argc, argv); @@ -223,6 +230,8 @@ public: for (const auto& mapping : mappings) { // 确定源路径和目标路径 std::string fromPath, toPath; + std::string backupRelativePath; + if (fromType == "m") { fromPath = mapping.m; } else { @@ -235,9 +244,20 @@ public: toPath = mapping.n; } - // 构建绝对路径 - fs::path sourceAbs = workDir / fromPath; - fs::path targetAbs = updateDir / toPath; + fs::path sourceAbs; + fs::path targetAbs; + + if (fs::path(fromPath).is_absolute()) { + sourceAbs = fs::path(fromPath); + } else { + sourceAbs = workDir / fromPath; + backupRelativePath = fromPath; + } + if (fs::path(toPath).is_absolute()) { + targetAbs = fs::path(toPath); + } else { + targetAbs = updateDir / toPath; + } // 步骤1:检查源路径是否存在 if (!checkPath(sourceAbs, mapping.isFile)) { @@ -262,6 +282,7 @@ public: record.target = targetAbs; record.exists = fs::exists(targetAbs); record.isNew = !record.exists; + record.backupRelative = backupRelativePath; if (mapping.isFile) { // 文件处理 @@ -288,6 +309,7 @@ public: dirRecord.target = targetAbs / relPath; dirRecord.exists = fs::exists(dirRecord.target); dirRecord.isNew = !dirRecord.exists; + dirRecord.backupRelative = backupRelativePath; records.push_back(dirRecord); } } @@ -327,9 +349,10 @@ public: // 获取用户输入的标记目录 bool getMarkerDirectory() { - std::cout << "\n请输入标记目录名称(留空使用时间戳): "; + std::cout << "\n请输入此次备份标记名称(留空使用时间戳): "; std::getline(boost::nowide::cin, markerDir); + boost::trim(markerDir); if (markerDir.empty()) { markerDir = getCurrentTimestamp(); } @@ -350,8 +373,8 @@ public: { try { // 计算相对更新目录的路径 - fs::path relPath = fs::relative(record.target, updateDir); - fs::path backupPath = backupDir / markerDir / relPath; + // fs::path relPath = fs::relative(record.target, updateDir); + fs::path backupPath = backupDir / markerDir / record.backupRelative; // 创建目标目录 fs::create_directories(backupPath.parent_path()); @@ -360,8 +383,30 @@ public: fs::copy(record.target, backupPath, fs::copy_options::overwrite_existing); logger->debug("已备份: {} -> {}", record.target.string(), backupPath.string()); + + if (!verify_) { + return true; + } + + // 校验MD5 + auto hashSrc = md5_.hash_file(record.target.string()); + std::string hashBk; + + if (fs::is_directory(backupPath)) { + hashBk = md5_.hash_file(fs::path(backupPath).append(fs::path(record.target).filename().string()).string()); + } else { + hashBk = md5_.hash_file(backupPath.string()); + } + + if (hashBk == hashSrc) { + logger->info("==> Hash验证已通过。[{}]", hashSrc); + } else { + logger->error("==> Hash验证未通过。[{}][{}]", hashSrc, hashBk); + return false; + } return true; } catch (const fs::filesystem_error& e) { + // logger->error("备份失败: {} - {}", record.target.string(), zoostCommon::ToU8Copy(e.what())); logger->error("备份失败: {} - {}", record.target.string(), e.what()); return false; } @@ -403,7 +448,8 @@ public: if (backupFile(record)) { backupCount++; } else { - logger->warn("备份失败,但继续执行更新"); + logger->warn("备份失败...,请检查,停止更新动作..."); + return false; } } } diff --git a/public/SimMd5.hpp b/public/SimMd5.hpp new file mode 100644 index 0000000..224bff3 --- /dev/null +++ b/public/SimMd5.hpp @@ -0,0 +1,239 @@ +#include +#include +#include +#include +#include +#include + +class SimpleMD5 { +private: + // 基础函数定义 + #define F(x, y, z) (((x) & (y)) | ((~x) & (z))) + #define G(x, y, z) (((x) & (z)) | ((y) & (~z))) + #define H(x, y, z) ((x) ^ (y) ^ (z)) + #define I(x, y, z) ((y) ^ ((x) | (~z))) + + // 循环左移 + #define ROTATE_LEFT(x, n) (((x) << (n)) | ((x) >> (32 - (n)))) + + // 转换函数 + #define FF(a, b, c, d, x, s, ac) { \ + (a) += F((b), (c), (d)) + (x) + (ac); \ + (a) = ROTATE_LEFT((a), (s)); \ + (a) += (b); \ + } + + #define GG(a, b, c, d, x, s, ac) { \ + (a) += G((b), (c), (d)) + (x) + (ac); \ + (a) = ROTATE_LEFT((a), (s)); \ + (a) += (b); \ + } + + #define HH(a, b, c, d, x, s, ac) { \ + (a) += H((b), (c), (d)) + (x) + (ac); \ + (a) = ROTATE_LEFT((a), (s)); \ + (a) += (b); \ + } + + #define II(a, b, c, d, x, s, ac) { \ + (a) += I((b), (c), (d)) + (x) + (ac); \ + (a) = ROTATE_LEFT((a), (s)); \ + (a) += (b); \ + } + + uint32_t state[4]; // 状态变量 (A, B, C, D) + + // 填充数据 + void pad(std::vector& data) { + uint64_t original_bit_length = data.size() * 8; + + // 添加 1 bit (0x80 字节) + data.push_back(0x80); + + // 填充 0 直到长度对 64 取模等于 56 + while ((data.size() % 64) != 56) { + data.push_back(0x00); + } + + // 添加原始消息长度 (小端序) + for (int i = 0; i < 8; i++) { + data.push_back(static_cast(original_bit_length >> (i * 8))); + } + } + + // 转换 4 字节为 32 位整数 (小端序) + uint32_t to_int32(const uint8_t* bytes) { + return (static_cast(bytes[0])) | + (static_cast(bytes[1]) << 8) | + (static_cast(bytes[2]) << 16) | + (static_cast(bytes[3]) << 24); + } + + // 主转换函数 + void transform(const uint8_t block[64]) { + uint32_t a = state[0]; + uint32_t b = state[1]; + uint32_t c = state[2]; + uint32_t d = state[3]; + uint32_t x[16]; + + // 将 64 字节块解码为 16 个 32 位整数 + for (int i = 0, j = 0; i < 16; i++, j += 4) { + x[i] = to_int32(&block[j]); + } + + /* 第 1 轮 */ + FF(a, b, c, d, x[0], 7, 0xd76aa478); + FF(d, a, b, c, x[1], 12, 0xe8c7b756); + FF(c, d, a, b, x[2], 17, 0x242070db); + FF(b, c, d, a, x[3], 22, 0xc1bdceee); + FF(a, b, c, d, x[4], 7, 0xf57c0faf); + FF(d, a, b, c, x[5], 12, 0x4787c62a); + FF(c, d, a, b, x[6], 17, 0xa8304613); + FF(b, c, d, a, x[7], 22, 0xfd469501); + FF(a, b, c, d, x[8], 7, 0x698098d8); + FF(d, a, b, c, x[9], 12, 0x8b44f7af); + FF(c, d, a, b, x[10], 17, 0xffff5bb1); + FF(b, c, d, a, x[11], 22, 0x895cd7be); + FF(a, b, c, d, x[12], 7, 0x6b901122); + FF(d, a, b, c, x[13], 12, 0xfd987193); + FF(c, d, a, b, x[14], 17, 0xa679438e); + FF(b, c, d, a, x[15], 22, 0x49b40821); + + /* 第 2 轮 */ + GG(a, b, c, d, x[1], 5, 0xf61e2562); + GG(d, a, b, c, x[6], 9, 0xc040b340); + GG(c, d, a, b, x[11], 14, 0x265e5a51); + GG(b, c, d, a, x[0], 20, 0xe9b6c7aa); + GG(a, b, c, d, x[5], 5, 0xd62f105d); + GG(d, a, b, c, x[10], 9, 0x02441453); + GG(c, d, a, b, x[15], 14, 0xd8a1e681); + GG(b, c, d, a, x[4], 20, 0xe7d3fbc8); + GG(a, b, c, d, x[9], 5, 0x21e1cde6); + GG(d, a, b, c, x[14], 9, 0xc33707d6); + GG(c, d, a, b, x[3], 14, 0xf4d50d87); + GG(b, c, d, a, x[8], 20, 0x455a14ed); + GG(a, b, c, d, x[13], 5, 0xa9e3e905); + GG(d, a, b, c, x[2], 9, 0xfcefa3f8); + GG(c, d, a, b, x[7], 14, 0x676f02d9); + GG(b, c, d, a, x[12], 20, 0x8d2a4c8a); + + /* 第 3 轮 */ + HH(a, b, c, d, x[5], 4, 0xfffa3942); + HH(d, a, b, c, x[8], 11, 0x8771f681); + HH(c, d, a, b, x[11], 16, 0x6d9d6122); + HH(b, c, d, a, x[14], 23, 0xfde5380c); + HH(a, b, c, d, x[1], 4, 0xa4beea44); + HH(d, a, b, c, x[4], 11, 0x4bdecfa9); + HH(c, d, a, b, x[7], 16, 0xf6bb4b60); + HH(b, c, d, a, x[10], 23, 0xbebfbc70); + HH(a, b, c, d, x[13], 4, 0x289b7ec6); + HH(d, a, b, c, x[0], 11, 0xeaa127fa); + HH(c, d, a, b, x[3], 16, 0xd4ef3085); + HH(b, c, d, a, x[6], 23, 0x04881d05); + HH(a, b, c, d, x[9], 4, 0xd9d4d039); + HH(d, a, b, c, x[12], 11, 0xe6db99e5); + HH(c, d, a, b, x[15], 16, 0x1fa27cf8); + HH(b, c, d, a, x[2], 23, 0xc4ac5665); + + /* 第 4 轮 */ + II(a, b, c, d, x[0], 6, 0xf4292244); + II(d, a, b, c, x[7], 10, 0x432aff97); + II(c, d, a, b, x[14], 15, 0xab9423a7); + II(b, c, d, a, x[5], 21, 0xfc93a039); + II(a, b, c, d, x[12], 6, 0x655b59c3); + II(d, a, b, c, x[3], 10, 0x8f0ccc92); + II(c, d, a, b, x[10], 15, 0xffeff47d); + II(b, c, d, a, x[1], 21, 0x85845dd1); + II(a, b, c, d, x[8], 6, 0x6fa87e4f); + II(d, a, b, c, x[15], 10, 0xfe2ce6e0); + II(c, d, a, b, x[6], 15, 0xa3014314); + II(b, c, d, a, x[13], 21, 0x4e0811a1); + II(a, b, c, d, x[4], 6, 0xf7537e82); + II(d, a, b, c, x[11], 10, 0xbd3af235); + II(c, d, a, b, x[2], 15, 0x2ad7d2bb); + II(b, c, d, a, x[9], 21, 0xeb86d391); + + state[0] += a; + state[1] += b; + state[2] += c; + state[3] += d; + } + +public: + SimpleMD5() { + reset(); + } + + // 重置状态 + void reset() { + state[0] = 0x67452301; + state[1] = 0xefcdab89; + state[2] = 0x98badcfe; + state[3] = 0x10325476; + } + + // 计算字符串的 MD5 + std::string hash(const std::string& input) { + return hash(reinterpret_cast(input.c_str()), input.length()); + } + + // 计算字节数组的 MD5 + std::string hash(const uint8_t* data, size_t length) { + reset(); + + // 将数据复制到 vector 以便填充 + std::vector buffer(data, data + length); + + // 填充数据 + pad(buffer); + + // 处理每个 64 字节块 + for (size_t i = 0; i < buffer.size(); i += 64) { + transform(&buffer[i]); + } + + // 将结果转换为十六进制字符串 + return to_hex_string(); + } + + // 计算文件的 MD5 + std::string hash_file(const std::string& filename) { + reset(); + + FILE* file = fopen(filename.c_str(), "rb"); + if (!file) { + return ""; + } + + std::vector buffer; + uint8_t chunk[1024]; + size_t bytes_read; + + while ((bytes_read = fread(chunk, 1, sizeof(chunk), file)) > 0) { + buffer.insert(buffer.end(), chunk, chunk + bytes_read); + } + + fclose(file); + + return hash(buffer.data(), buffer.size()); + } + +private: + // 将结果转换为十六进制字符串 + std::string to_hex_string() { + std::stringstream ss; + ss << std::hex << std::setfill('0'); + + // 以小端序输出每个状态变量 + for (int i = 0; i < 4; i++) { + // 将 32 位整数按字节输出 + for (int j = 0; j < 4; j++) { + uint8_t byte = (state[i] >> (j * 8)) & 0xFF; + ss << std::setw(2) << static_cast(byte); + } + } + + return ss.str(); + } +}; \ No newline at end of file