mirror of
https://github.com/hyb-oyqq/FRAISEMOE-Addons-Installer-NEXT.git
synced 2025-12-18 13:00:29 +00:00
feat(core): 增强哈希验证机制和进度反馈
- 在下载管理器和离线模式管理器中集成哈希验证功能,确保补丁文件的完整性。 - 添加进度对话框以显示哈希验证过程,提升用户体验。 - 优化哈希验证线程,支持进度更新和错误处理,确保在验证失败时提供清晰反馈。 - 更新相关逻辑以支持新功能,提升代码可维护性和可读性。
This commit is contained in:
@@ -6,7 +6,7 @@ from urllib.parse import urlparse
|
|||||||
import re
|
import re
|
||||||
|
|
||||||
from PySide6 import QtWidgets, QtCore
|
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.QtGui import QIcon, QPixmap, QFont
|
||||||
from PySide6.QtWidgets import QPushButton, QDialog, QHBoxLayout
|
from PySide6.QtWidgets import QPushButton, QDialog, QHBoxLayout
|
||||||
|
|
||||||
@@ -660,7 +660,7 @@ class DownloadManager:
|
|||||||
if debug_mode:
|
if debug_mode:
|
||||||
logger.info(f"DEBUG: 成功复制并验证补丁文件 {_7z_path}")
|
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)
|
self.main_window.extraction_handler.extraction_finished.connect(self.on_extraction_finished)
|
||||||
else:
|
else:
|
||||||
if debug_mode:
|
if debug_mode:
|
||||||
@@ -762,8 +762,92 @@ class DownloadManager:
|
|||||||
else:
|
else:
|
||||||
self.on_download_stopped()
|
self.on_download_stopped()
|
||||||
return
|
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):
|
def on_extraction_finished(self, continue_download):
|
||||||
"""解压完成后的回调,决定是否继续下载队列
|
"""解压完成后的回调,决定是否继续下载队列
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import tempfile
|
|||||||
import py7zr
|
import py7zr
|
||||||
import traceback
|
import traceback
|
||||||
from PySide6.QtWidgets import QMessageBox
|
from PySide6.QtWidgets import QMessageBox
|
||||||
|
from PySide6.QtCore import QTimer
|
||||||
|
|
||||||
from data.config import PLUGIN, PLUGIN_HASH, GAME_INFO
|
from data.config import PLUGIN, PLUGIN_HASH, GAME_INFO
|
||||||
from utils import msgbox_frame
|
from utils import msgbox_frame
|
||||||
@@ -231,8 +232,74 @@ class OfflineModeManager:
|
|||||||
Returns:
|
Returns:
|
||||||
bool: 哈希值是否匹配
|
bool: 哈希值是否匹配
|
||||||
"""
|
"""
|
||||||
# 使用patch_detector模块验证哈希值
|
debug_mode = self._is_debug_mode()
|
||||||
return self.main_window.patch_detector.verify_patch_hash(game_version, file_path)
|
|
||||||
|
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):
|
def install_offline_patches(self, selected_games):
|
||||||
"""直接安装离线补丁,完全绕过下载模块
|
"""直接安装离线补丁,完全绕过下载模块
|
||||||
@@ -284,6 +351,9 @@ class OfflineModeManager:
|
|||||||
|
|
||||||
# 设置到主窗口,供结果显示使用
|
# 设置到主窗口,供结果显示使用
|
||||||
self.main_window.download_queue_history = selected_games
|
self.main_window.download_queue_history = selected_games
|
||||||
|
|
||||||
|
# 记录未找到离线补丁文件的游戏
|
||||||
|
self.missing_offline_patches = []
|
||||||
|
|
||||||
# 创建安装任务列表
|
# 创建安装任务列表
|
||||||
install_tasks = []
|
install_tasks = []
|
||||||
@@ -293,6 +363,8 @@ class OfflineModeManager:
|
|||||||
if not patch_file:
|
if not patch_file:
|
||||||
if debug_mode:
|
if debug_mode:
|
||||||
logger.warning(f"DEBUG: 未找到 {game_version} 的离线补丁文件,跳过")
|
logger.warning(f"DEBUG: 未找到 {game_version} 的离线补丁文件,跳过")
|
||||||
|
# 记录未找到离线补丁文件的游戏
|
||||||
|
self.missing_offline_patches.append(game_version)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# 获取游戏目录
|
# 获取游戏目录
|
||||||
@@ -359,6 +431,43 @@ class OfflineModeManager:
|
|||||||
|
|
||||||
# 使用patch_detector进行安装后哈希比较
|
# 使用patch_detector进行安装后哈希比较
|
||||||
self.main_window.patch_detector.after_hash_compare()
|
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
|
return
|
||||||
|
|
||||||
# 获取下一个任务
|
# 获取下一个任务
|
||||||
|
|||||||
@@ -11,10 +11,93 @@ import re
|
|||||||
from PySide6.QtGui import QIcon, QPixmap
|
from PySide6.QtGui import QIcon, QPixmap
|
||||||
from data.config import APP_NAME, CONFIG_FILE
|
from data.config import APP_NAME, CONFIG_FILE
|
||||||
from utils.logger import setup_logger
|
from utils.logger import setup_logger
|
||||||
|
import datetime
|
||||||
|
import traceback
|
||||||
|
import subprocess
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
# 初始化logger
|
# 初始化logger
|
||||||
logger = setup_logger("helpers")
|
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):
|
def resource_path(relative_path):
|
||||||
"""获取资源的绝对路径,适用于开发环境和Nuitka打包环境"""
|
"""获取资源的绝对路径,适用于开发环境和Nuitka打包环境"""
|
||||||
if getattr(sys, 'frozen', False):
|
if getattr(sys, 'frozen', False):
|
||||||
|
|||||||
@@ -1,28 +1,313 @@
|
|||||||
|
import os
|
||||||
|
import hashlib
|
||||||
|
import py7zr
|
||||||
|
import tempfile
|
||||||
|
import traceback
|
||||||
from PySide6.QtCore import QThread, Signal
|
from PySide6.QtCore import QThread, Signal
|
||||||
from utils import HashManager
|
from utils.logger import setup_logger
|
||||||
from data.config import BLOCK_SIZE
|
|
||||||
|
# 初始化logger
|
||||||
|
logger = setup_logger("hash_thread")
|
||||||
|
|
||||||
class HashThread(QThread):
|
class HashThread(QThread):
|
||||||
pre_finished = Signal(dict)
|
pre_finished = Signal(dict)
|
||||||
after_finished = Signal(dict)
|
after_finished = Signal(dict)
|
||||||
|
|
||||||
def __init__(self, mode, install_paths, plugin_hash, installed_status, parent=None):
|
def __init__(self, mode, install_paths, plugin_hash, installed_status, main_window=None):
|
||||||
super().__init__(parent)
|
"""初始化哈希检查线程
|
||||||
|
|
||||||
|
Args:
|
||||||
|
mode: 检查模式,"pre"或"after"
|
||||||
|
install_paths: 安装路径字典
|
||||||
|
plugin_hash: 插件哈希值字典
|
||||||
|
installed_status: 安装状态字典
|
||||||
|
main_window: 主窗口实例,用于访问UI和状态
|
||||||
|
"""
|
||||||
|
super().__init__()
|
||||||
self.mode = mode
|
self.mode = mode
|
||||||
self.install_paths = install_paths
|
self.install_paths = install_paths
|
||||||
self.plugin_hash = plugin_hash
|
self.plugin_hash = plugin_hash
|
||||||
self.installed_status = installed_status
|
self.installed_status = installed_status.copy()
|
||||||
# 每个线程都应该有自己的HashManager实例
|
self.main_window = main_window
|
||||||
self.hash_manager = HashManager(BLOCK_SIZE)
|
|
||||||
|
|
||||||
def run(self):
|
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":
|
if self.mode == "pre":
|
||||||
updated_status = self.hash_manager.cfg_pre_hash_compare(
|
status_copy = self.installed_status.copy()
|
||||||
self.install_paths, self.plugin_hash, self.installed_status
|
|
||||||
)
|
for game_version, install_path in self.install_paths.items():
|
||||||
self.pre_finished.emit(updated_status)
|
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":
|
elif self.mode == "after":
|
||||||
result = self.hash_manager.cfg_after_hash_compare(
|
result = {"passed": True, "game": "", "message": ""}
|
||||||
self.install_paths, self.plugin_hash, self.installed_status
|
|
||||||
)
|
for game_version, install_path in self.install_paths.items():
|
||||||
self.after_finished.emit(result)
|
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)}")
|
||||||
Reference in New Issue
Block a user