From ba5e3cdbc14172ca83707e51378e31cf775e44b8 Mon Sep 17 00:00:00 2001 From: hyb-oyqq <1512383570@qq.com> Date: Thu, 7 Aug 2025 18:22:22 +0800 Subject: [PATCH] =?UTF-8?q?feat(core):=20=E5=A2=9E=E5=BC=BA=E5=93=88?= =?UTF-8?q?=E5=B8=8C=E9=AA=8C=E8=AF=81=E6=9C=BA=E5=88=B6=E5=92=8C=E8=BF=9B?= =?UTF-8?q?=E5=BA=A6=E5=8F=8D=E9=A6=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 在下载管理器和离线模式管理器中集成哈希验证功能,确保补丁文件的完整性。 - 添加进度对话框以显示哈希验证过程,提升用户体验。 - 优化哈希验证线程,支持进度更新和错误处理,确保在验证失败时提供清晰反馈。 - 更新相关逻辑以支持新功能,提升代码可维护性和可读性。 --- source/core/download_manager.py | 92 +++++++- source/core/offline_mode_manager.py | 113 +++++++++- source/utils/helpers.py | 83 ++++++++ source/workers/hash_thread.py | 319 ++++++++++++++++++++++++++-- 4 files changed, 584 insertions(+), 23 deletions(-) diff --git a/source/core/download_manager.py b/source/core/download_manager.py index 510de5f..7a20866 100644 --- a/source/core/download_manager.py +++ b/source/core/download_manager.py @@ -6,7 +6,7 @@ from urllib.parse import urlparse import re from PySide6 import QtWidgets, QtCore -from PySide6.QtCore import Qt +from PySide6.QtCore import Qt, QTimer from PySide6.QtGui import QIcon, QPixmap, QFont from PySide6.QtWidgets import QPushButton, QDialog, QHBoxLayout @@ -660,7 +660,7 @@ class DownloadManager: if debug_mode: logger.info(f"DEBUG: 成功复制并验证补丁文件 {_7z_path}") # 直接进入解压阶段 - self.main_window.extraction_handler.start_extraction(_7z_path, game_folder, plugin_path, game_version) + self.extraction_handler.start_extraction(_7z_path, game_folder, plugin_path, game_version) self.main_window.extraction_handler.extraction_finished.connect(self.on_extraction_finished) else: if debug_mode: @@ -762,8 +762,92 @@ class DownloadManager: else: self.on_download_stopped() return - - self.extraction_handler.start_extraction(_7z_path, game_folder, plugin_path, game_version) + + # 下载成功后,使用与离线模式相同的哈希校验机制 + debug_mode = self.is_debug_mode() + + if debug_mode: + logger.debug(f"DEBUG: 下载完成,开始验证补丁文件哈希: {_7z_path}") + + # 关闭进度窗口 + if hasattr(self.main_window, 'progress_window') and self.main_window.progress_window: + if self.main_window.progress_window.isVisible(): + self.main_window.progress_window.accept() + self.main_window.progress_window = None + + # 使用与离线模式相同的哈希校验机制 + from utils.helpers import ProgressHashVerifyDialog + from data.config import PLUGIN_HASH + from workers.hash_thread import OfflineHashVerifyThread + + # 创建并显示进度对话框 + progress_dialog = ProgressHashVerifyDialog( + f"验证补丁文件 - {APP_NAME}", + f"正在验证 {game_version} 的补丁文件完整性...", + self.main_window + ) + + # 创建哈希验证线程 + hash_thread = OfflineHashVerifyThread(game_version, _7z_path, PLUGIN_HASH, self.main_window) + + # 连接信号 + hash_thread.progress.connect(progress_dialog.update_progress) + hash_thread.finished.connect(lambda result, error: self._on_hash_verify_finished( + result, error, progress_dialog, _7z_path, game_folder, plugin_path, game_version + )) + + # 启动线程 + hash_thread.start() + + # 显示对话框,阻塞直到对话框关闭 + result = progress_dialog.exec() + + # 如果用户取消了验证,停止线程 + if result == ProgressHashVerifyDialog.Rejected and hash_thread.isRunning(): + if debug_mode: + logger.debug(f"DEBUG: 用户取消了哈希验证") + hash_thread.terminate() + hash_thread.wait() + # 取消后继续下一个任务 + self.next_download_task() + + def _on_hash_verify_finished(self, result, error, dialog, _7z_path, game_folder, plugin_path, game_version): + """哈希验证线程完成后的回调 + + Args: + result: 验证结果 + error: 错误信息 + dialog: 进度对话框 + _7z_path: 7z文件保存路径 + game_folder: 游戏文件夹路径 + plugin_path: 插件路径 + game_version: 游戏版本 + """ + debug_mode = self.is_debug_mode() + + # 存储结果到对话框,以便在exec()返回后获取 + dialog.hash_result = result + + if result: + if debug_mode: + logger.debug(f"DEBUG: 哈希验证成功") + dialog.set_status("验证成功") + # 短暂延时后关闭对话框 + QTimer.singleShot(500, dialog.accept) + + # 验证成功,进入解压阶段 + self.extraction_handler.start_extraction(_7z_path, game_folder, plugin_path, game_version) + else: + if debug_mode: + logger.debug(f"DEBUG: 哈希验证失败: {error}") + dialog.set_status(f"验证失败: {error}") + dialog.set_message("补丁文件验证失败,可能已损坏或被篡改。") + # 将取消按钮改为关闭按钮 + dialog.cancel_button.setText("关闭") + # 不自动关闭,让用户查看错误信息 + + # 在用户关闭对话框后,继续下一个任务 + dialog.rejected.connect(lambda: self.next_download_task()) def on_extraction_finished(self, continue_download): """解压完成后的回调,决定是否继续下载队列 diff --git a/source/core/offline_mode_manager.py b/source/core/offline_mode_manager.py index b08cc34..8851c32 100644 --- a/source/core/offline_mode_manager.py +++ b/source/core/offline_mode_manager.py @@ -5,6 +5,7 @@ import tempfile import py7zr import traceback from PySide6.QtWidgets import QMessageBox +from PySide6.QtCore import QTimer from data.config import PLUGIN, PLUGIN_HASH, GAME_INFO from utils import msgbox_frame @@ -231,8 +232,74 @@ class OfflineModeManager: Returns: bool: 哈希值是否匹配 """ - # 使用patch_detector模块验证哈希值 - return self.main_window.patch_detector.verify_patch_hash(game_version, file_path) + debug_mode = self._is_debug_mode() + + if debug_mode: + logger.debug(f"DEBUG: 开始验证补丁文件哈希: {file_path}") + + # 创建进度对话框 + from utils.helpers import ProgressHashVerifyDialog + from data.config import PLUGIN_HASH + from workers.hash_thread import OfflineHashVerifyThread + + # 创建并显示进度对话框 + progress_dialog = ProgressHashVerifyDialog( + f"验证补丁文件 - {self.app_name}", + f"正在验证 {game_version} 的补丁文件完整性...", + self.main_window + ) + + # 创建哈希验证线程 + hash_thread = OfflineHashVerifyThread(game_version, file_path, PLUGIN_HASH, self.main_window) + + # 连接信号 + hash_thread.progress.connect(progress_dialog.update_progress) + hash_thread.finished.connect(lambda result, error: self._on_hash_verify_finished(result, error, progress_dialog)) + + # 启动线程 + hash_thread.start() + + # 显示对话框,阻塞直到对话框关闭 + result = progress_dialog.exec() + + # 如果用户取消了验证,停止线程 + if result == ProgressHashVerifyDialog.Rejected and hash_thread.isRunning(): + if debug_mode: + logger.debug(f"DEBUG: 用户取消了哈希验证") + hash_thread.terminate() + hash_thread.wait() + return False + + # 返回对话框中存储的验证结果 + return hasattr(progress_dialog, 'hash_result') and progress_dialog.hash_result + + def _on_hash_verify_finished(self, result, error, dialog): + """哈希验证线程完成后的回调 + + Args: + result: 验证结果 + error: 错误信息 + dialog: 进度对话框 + """ + debug_mode = self._is_debug_mode() + + # 存储结果到对话框,以便在exec()返回后获取 + dialog.hash_result = result + + if result: + if debug_mode: + logger.debug(f"DEBUG: 哈希验证成功") + dialog.set_status("验证成功") + # 短暂延时后关闭对话框 + QTimer.singleShot(500, dialog.accept) + else: + if debug_mode: + logger.debug(f"DEBUG: 哈希验证失败: {error}") + dialog.set_status(f"验证失败: {error}") + dialog.set_message("补丁文件验证失败,可能已损坏或被篡改。") + # 将取消按钮改为关闭按钮 + dialog.cancel_button.setText("关闭") + # 不自动关闭,让用户查看错误信息 def install_offline_patches(self, selected_games): """直接安装离线补丁,完全绕过下载模块 @@ -284,6 +351,9 @@ class OfflineModeManager: # 设置到主窗口,供结果显示使用 self.main_window.download_queue_history = selected_games + + # 记录未找到离线补丁文件的游戏 + self.missing_offline_patches = [] # 创建安装任务列表 install_tasks = [] @@ -293,6 +363,8 @@ class OfflineModeManager: if not patch_file: if debug_mode: logger.warning(f"DEBUG: 未找到 {game_version} 的离线补丁文件,跳过") + # 记录未找到离线补丁文件的游戏 + self.missing_offline_patches.append(game_version) continue # 获取游戏目录 @@ -359,6 +431,43 @@ class OfflineModeManager: # 使用patch_detector进行安装后哈希比较 self.main_window.patch_detector.after_hash_compare() + + # 检查是否有未找到离线补丁文件的游戏 + if hasattr(self, 'missing_offline_patches') and self.missing_offline_patches: + if debug_mode: + logger.debug(f"DEBUG: 有未找到离线补丁文件的游戏: {self.missing_offline_patches}") + + # 在安装完成后询问用户是否切换到在线模式 + msg_box = msgbox_frame( + f"离线安装完成 - {self.app_name}", + f"\n以下游戏未找到对应的离线补丁文件:\n\n{chr(10).join(self.missing_offline_patches)}\n\n是否切换到在线模式继续安装?\n", + QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No + ) + result = msg_box.exec() + + if result == QMessageBox.StandardButton.Yes: + if debug_mode: + logger.debug("DEBUG: 用户选择切换到在线模式") + + # 切换到在线模式 + if hasattr(self.main_window, 'ui_manager'): + self.main_window.ui_manager.switch_work_mode("online") + + # 重置UI状态 + self.main_window.setEnabled(True) + self.main_window.ui.start_install_text.setText("开始安装") + else: + if debug_mode: + logger.debug("DEBUG: 用户选择不切换到在线模式") + + # 恢复UI状态 + self.main_window.setEnabled(True) + self.main_window.ui.start_install_text.setText("开始安装") + else: + # 恢复UI状态 + self.main_window.setEnabled(True) + self.main_window.ui.start_install_text.setText("开始安装") + return # 获取下一个任务 diff --git a/source/utils/helpers.py b/source/utils/helpers.py index ee14fcc..8e43ce0 100644 --- a/source/utils/helpers.py +++ b/source/utils/helpers.py @@ -11,10 +11,93 @@ import re from PySide6.QtGui import QIcon, QPixmap from data.config import APP_NAME, CONFIG_FILE from utils.logger import setup_logger +import datetime +import traceback +import subprocess +from pathlib import Path # 初始化logger logger = setup_logger("helpers") +class ProgressHashVerifyDialog(QDialog): + """带进度条的哈希验证对话框""" + + def __init__(self, title, message, parent=None): + """初始化对话框 + + Args: + title: 对话框标题 + message: 对话框消息 + parent: 父窗口 + """ + super().__init__(parent) + self.setWindowTitle(title) + self.setMinimumWidth(400) + self.setWindowFlags(self.windowFlags() & ~Qt.WindowContextHelpButtonHint) + + # 创建布局 + layout = QVBoxLayout(self) + + # 添加消息标签 + self.message_label = QLabel(message) + self.message_label.setAlignment(Qt.AlignCenter) + layout.addWidget(self.message_label) + + # 添加进度条 + self.progress_bar = QProgressBar() + self.progress_bar.setRange(0, 100) + self.progress_bar.setValue(0) + layout.addWidget(self.progress_bar) + + # 添加状态标签 + self.status_label = QLabel("正在准备...") + self.status_label.setAlignment(Qt.AlignCenter) + layout.addWidget(self.status_label) + + # 添加取消按钮 + button_layout = QHBoxLayout() + self.cancel_button = QPushButton("取消") + self.cancel_button.clicked.connect(self.reject) + button_layout.addStretch() + button_layout.addWidget(self.cancel_button) + layout.addLayout(button_layout) + + def update_progress(self, value): + """更新进度条 + + Args: + value: 进度值 (0-100) + """ + self.progress_bar.setValue(value) + + # 更新状态文本 + if value < 10: + self.status_label.setText("正在准备...") + elif value < 50: + self.status_label.setText("正在解压文件...") + elif value < 70: + self.status_label.setText("正在查找补丁文件...") + elif value < 95: + self.status_label.setText("正在计算哈希值...") + else: + self.status_label.setText("正在验证哈希值...") + + def set_message(self, message): + """设置消息文本 + + Args: + message: 消息文本 + """ + self.message_label.setText(message) + + def set_status(self, status): + """设置状态文本 + + Args: + status: 状态文本 + """ + self.status_label.setText(status) + def resource_path(relative_path): """获取资源的绝对路径,适用于开发环境和Nuitka打包环境""" if getattr(sys, 'frozen', False): diff --git a/source/workers/hash_thread.py b/source/workers/hash_thread.py index 008c6b7..e9ea7f4 100644 --- a/source/workers/hash_thread.py +++ b/source/workers/hash_thread.py @@ -1,28 +1,313 @@ +import os +import hashlib +import py7zr +import tempfile +import traceback from PySide6.QtCore import QThread, Signal -from utils import HashManager -from data.config import BLOCK_SIZE +from utils.logger import setup_logger + +# 初始化logger +logger = setup_logger("hash_thread") class HashThread(QThread): pre_finished = Signal(dict) after_finished = Signal(dict) - - def __init__(self, mode, install_paths, plugin_hash, installed_status, parent=None): - super().__init__(parent) + + def __init__(self, mode, install_paths, plugin_hash, installed_status, main_window=None): + """初始化哈希检查线程 + + Args: + mode: 检查模式,"pre"或"after" + install_paths: 安装路径字典 + plugin_hash: 插件哈希值字典 + installed_status: 安装状态字典 + main_window: 主窗口实例,用于访问UI和状态 + """ + super().__init__() self.mode = mode self.install_paths = install_paths self.plugin_hash = plugin_hash - self.installed_status = installed_status - # 每个线程都应该有自己的HashManager实例 - self.hash_manager = HashManager(BLOCK_SIZE) - + self.installed_status = installed_status.copy() + self.main_window = main_window + def run(self): + """运行线程""" + debug_mode = False + + # 尝试检测是否处于调试模式 + if self.main_window and hasattr(self.main_window, 'debug_manager'): + debug_mode = self.main_window.debug_manager._is_debug_mode() + if self.mode == "pre": - updated_status = self.hash_manager.cfg_pre_hash_compare( - self.install_paths, self.plugin_hash, self.installed_status - ) - self.pre_finished.emit(updated_status) + status_copy = self.installed_status.copy() + + for game_version, install_path in self.install_paths.items(): + if not os.path.exists(install_path): + status_copy[game_version] = False + if debug_mode: + logger.debug(f"DEBUG: 哈希预检查 - {game_version} 补丁文件不存在: {install_path}") + continue + + try: + expected_hash = self.plugin_hash.get(game_version, "") + if not expected_hash: + if debug_mode: + logger.debug(f"DEBUG: 哈希预检查 - {game_version} 没有预期哈希值,跳过哈希检查") + # 当没有预期哈希值时,保持当前状态不变 + continue + + with open(install_path, "rb") as f: + file_hash = hashlib.sha256(f.read()).hexdigest() + + if debug_mode: + logger.debug(f"DEBUG: 哈希预检查 - {game_version}") + logger.debug(f"DEBUG: 文件路径: {install_path}") + logger.debug(f"DEBUG: 预期哈希值: {expected_hash}") + logger.debug(f"DEBUG: 实际哈希值: {file_hash}") + logger.debug(f"DEBUG: 哈希匹配: {file_hash == expected_hash}") + + if file_hash == expected_hash: + status_copy[game_version] = True + if debug_mode: + logger.debug(f"DEBUG: 哈希预检查 - {game_version} 哈希匹配成功") + else: + status_copy[game_version] = False + if debug_mode: + logger.debug(f"DEBUG: 哈希预检查 - {game_version} 哈希不匹配") + except Exception as e: + status_copy[game_version] = False + if debug_mode: + logger.debug(f"DEBUG: 哈希预检查异常 - {game_version}: {str(e)}") + + self.pre_finished.emit(status_copy) + elif self.mode == "after": - result = self.hash_manager.cfg_after_hash_compare( - self.install_paths, self.plugin_hash, self.installed_status - ) - self.after_finished.emit(result) \ No newline at end of file + result = {"passed": True, "game": "", "message": ""} + + for game_version, install_path in self.install_paths.items(): + if not os.path.exists(install_path): + if debug_mode: + logger.debug(f"DEBUG: 哈希后检查 - {game_version} 补丁文件不存在: {install_path}") + continue + + try: + expected_hash = self.plugin_hash.get(game_version, "") + if not expected_hash: + if debug_mode: + logger.debug(f"DEBUG: 哈希后检查 - {game_version} 没有预期哈希值,跳过哈希检查") + # 当没有预期哈希值时,跳过检查 + continue + + with open(install_path, "rb") as f: + file_hash = hashlib.sha256(f.read()).hexdigest() + + if debug_mode: + logger.debug(f"DEBUG: 哈希后检查 - {game_version}") + logger.debug(f"DEBUG: 文件路径: {install_path}") + logger.debug(f"DEBUG: 预期哈希值: {expected_hash}") + logger.debug(f"DEBUG: 实际哈希值: {file_hash}") + logger.debug(f"DEBUG: 哈希匹配: {file_hash == expected_hash}") + + if file_hash != expected_hash: + result["passed"] = False + result["game"] = game_version + result["message"] = f"\n{game_version} 安装后的文件校验失败。\n\n文件可能已损坏或被篡改,请重新安装。\n" + if debug_mode: + logger.debug(f"DEBUG: 哈希后检查 - {game_version} 哈希不匹配") + break + elif debug_mode: + logger.debug(f"DEBUG: 哈希后检查 - {game_version} 哈希匹配成功") + except Exception as e: + result["passed"] = False + result["game"] = game_version + result["message"] = f"\n{game_version} 安装后的文件校验过程中发生错误。\n\n错误信息: {str(e)}\n" + if debug_mode: + logger.debug(f"DEBUG: 哈希后检查异常 - {game_version}: {str(e)}") + break + + self.after_finished.emit(result) + + +class OfflineHashVerifyThread(QThread): + """离线模式下验证补丁文件哈希的线程,支持进度更新""" + + progress = Signal(int) # 进度信号,0-100 + finished = Signal(bool, str) # 完成信号,(成功/失败, 错误信息) + + def __init__(self, game_version, file_path, plugin_hash, main_window=None): + """初始化离线哈希验证线程 + + Args: + game_version: 游戏版本名称 + file_path: 补丁压缩包文件路径 + plugin_hash: 插件哈希值字典 + main_window: 主窗口实例,用于访问UI和状态 + """ + super().__init__() + self.game_version = game_version + self.file_path = file_path + self.plugin_hash = plugin_hash + self.main_window = main_window + + def run(self): + """运行线程""" + debug_mode = False + + # 尝试检测是否处于调试模式 + if self.main_window and hasattr(self.main_window, 'debug_manager'): + debug_mode = self.main_window.debug_manager._is_debug_mode() + + # 获取预期的哈希值 + expected_hash = self.plugin_hash.get(self.game_version, "") + + if not expected_hash: + logger.warning(f"DEBUG: 未找到 {self.game_version} 的预期哈希值") + self.finished.emit(False, f"未找到 {self.game_version} 的预期哈希值") + return + + if debug_mode: + logger.debug(f"DEBUG: 开始验证补丁文件: {self.file_path}") + logger.debug(f"DEBUG: 游戏版本: {self.game_version}") + logger.debug(f"DEBUG: 预期哈希值: {expected_hash}") + + try: + # 检查文件是否存在 + if not os.path.exists(self.file_path): + if debug_mode: + logger.warning(f"DEBUG: 补丁文件不存在: {self.file_path}") + self.finished.emit(False, f"补丁文件不存在: {self.file_path}") + return + + # 检查文件大小 + file_size = os.path.getsize(self.file_path) + if debug_mode: + logger.debug(f"DEBUG: 补丁文件大小: {file_size} 字节") + + if file_size == 0: + if debug_mode: + logger.warning(f"DEBUG: 补丁文件大小为0,无效文件") + self.finished.emit(False, "补丁文件大小为0,无效文件") + return + + # 创建临时目录用于解压文件 + with tempfile.TemporaryDirectory() as temp_dir: + if debug_mode: + logger.debug(f"DEBUG: 创建临时目录: {temp_dir}") + + # 发送进度信号 - 10% + self.progress.emit(10) + + # 解压补丁文件 + try: + if debug_mode: + logger.debug(f"DEBUG: 开始解压文件: {self.file_path}") + + with py7zr.SevenZipFile(self.file_path, mode="r") as archive: + # 获取压缩包内文件列表 + file_list = archive.getnames() + if debug_mode: + logger.debug(f"DEBUG: 压缩包内文件列表: {file_list}") + + # 解压所有文件 + archive.extractall(path=temp_dir) + + if debug_mode: + logger.debug(f"DEBUG: 解压完成") + # 列出解压后的文件 + extracted_files = [] + for root, dirs, files in os.walk(temp_dir): + for file in files: + extracted_files.append(os.path.join(root, file)) + logger.debug(f"DEBUG: 解压后的文件列表: {extracted_files}") + except Exception as e: + if debug_mode: + logger.error(f"DEBUG: 解压补丁文件失败: {e}") + logger.error(f"DEBUG: 错误类型: {type(e).__name__}") + logger.error(f"DEBUG: 错误堆栈: {traceback.format_exc()}") + self.finished.emit(False, f"解压补丁文件失败: {str(e)}") + return + + # 发送进度信号 - 50% + self.progress.emit(50) + + # 获取补丁文件路径 + patch_file = None + if "Vol.1" in self.game_version: + patch_file = os.path.join(temp_dir, "vol.1", "adultsonly.xp3") + elif "Vol.2" in self.game_version: + patch_file = os.path.join(temp_dir, "vol.2", "adultsonly.xp3") + elif "Vol.3" in self.game_version: + patch_file = os.path.join(temp_dir, "vol.3", "update00.int") + elif "Vol.4" in self.game_version: + patch_file = os.path.join(temp_dir, "vol.4", "vol4adult.xp3") + elif "After" in self.game_version: + patch_file = os.path.join(temp_dir, "after", "afteradult.xp3") + + if not patch_file or not os.path.exists(patch_file): + if debug_mode: + logger.warning(f"DEBUG: 未找到解压后的补丁文件: {patch_file}") + # 尝试查找可能的替代文件 + alternative_files = [] + for root, dirs, files in os.walk(temp_dir): + for file in files: + if file.endswith('.xp3') or file.endswith('.int'): + alternative_files.append(os.path.join(root, file)) + if alternative_files: + logger.debug(f"DEBUG: 找到可能的替代文件: {alternative_files}") + + # 检查解压目录结构 + logger.debug(f"DEBUG: 检查解压目录结构:") + for root, dirs, files in os.walk(temp_dir): + logger.debug(f"DEBUG: 目录: {root}") + logger.debug(f"DEBUG: 子目录: {dirs}") + logger.debug(f"DEBUG: 文件: {files}") + self.finished.emit(False, f"未找到解压后的补丁文件") + return + + # 发送进度信号 - 70% + self.progress.emit(70) + + if debug_mode: + logger.debug(f"DEBUG: 找到解压后的补丁文件: {patch_file}") + + # 计算补丁文件哈希值 + try: + # 读取文件内容并计算哈希值,同时更新进度 + file_size = os.path.getsize(patch_file) + chunk_size = 1024 * 1024 # 1MB + hash_obj = hashlib.sha256() + + with open(patch_file, "rb") as f: + bytes_read = 0 + while chunk := f.read(chunk_size): + hash_obj.update(chunk) + bytes_read += len(chunk) + # 计算进度 (70-95%) + progress = 70 + int(25 * bytes_read / file_size) + self.progress.emit(min(95, progress)) + + file_hash = hash_obj.hexdigest() + + # 比较哈希值 + result = file_hash.lower() == expected_hash.lower() + + # 发送进度信号 - 100% + self.progress.emit(100) + + if debug_mode: + logger.debug(f"DEBUG: 补丁文件 {patch_file} 哈希值验证: {'成功' if result else '失败'}") + logger.debug(f"DEBUG: 预期哈希值: {expected_hash}") + logger.debug(f"DEBUG: 实际哈希值: {file_hash}") + + self.finished.emit(result, "" if result else "补丁文件哈希验证失败,文件可能已损坏或被篡改") + except Exception as e: + if debug_mode: + logger.error(f"DEBUG: 计算补丁文件哈希值失败: {e}") + logger.error(f"DEBUG: 错误类型: {type(e).__name__}") + self.finished.emit(False, f"计算补丁文件哈希值失败: {str(e)}") + except Exception as e: + if debug_mode: + logger.error(f"DEBUG: 验证补丁哈希值失败: {e}") + logger.error(f"DEBUG: 错误类型: {type(e).__name__}") + logger.error(f"DEBUG: 错误堆栈: {traceback.format_exc()}") + self.finished.emit(False, f"验证补丁哈希值失败: {str(e)}") \ No newline at end of file