mirror of
https://github.com/hyb-oyqq/FRAISEMOE-Addons-Installer-NEXT.git
synced 2025-12-21 06:18:36 +00:00
feat(core): 集成补丁检测器以增强补丁管理功能
- 在主窗口中添加补丁检测器,支持补丁的检测和验证。 - 更新补丁管理器以使用补丁检测器进行补丁安装状态检查。 - 优化下载管理器和离线模式管理器,整合补丁检测逻辑,提升用户体验。 - 添加进度窗口以显示下载状态,增强用户反馈。 - 重构相关逻辑以支持新功能,确保代码可维护性和可读性。
This commit is contained in:
@@ -10,6 +10,7 @@ from .privacy_manager import PrivacyManager
|
|||||||
from .cloudflare_optimizer import CloudflareOptimizer
|
from .cloudflare_optimizer import CloudflareOptimizer
|
||||||
from .download_task_manager import DownloadTaskManager
|
from .download_task_manager import DownloadTaskManager
|
||||||
from .extraction_handler import ExtractionHandler
|
from .extraction_handler import ExtractionHandler
|
||||||
|
from .patch_detector import PatchDetector
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
'MultiStageAnimations',
|
'MultiStageAnimations',
|
||||||
@@ -23,5 +24,6 @@ __all__ = [
|
|||||||
'PrivacyManager',
|
'PrivacyManager',
|
||||||
'CloudflareOptimizer',
|
'CloudflareOptimizer',
|
||||||
'DownloadTaskManager',
|
'DownloadTaskManager',
|
||||||
'ExtractionHandler'
|
'ExtractionHandler',
|
||||||
|
'PatchDetector'
|
||||||
]
|
]
|
||||||
@@ -75,35 +75,25 @@ class CloudflareOptimizer:
|
|||||||
# 解析域名
|
# 解析域名
|
||||||
hostname = urlparse(url).hostname
|
hostname = urlparse(url).hostname
|
||||||
|
|
||||||
# 检查hosts文件中是否已有该域名的IP记录
|
|
||||||
existing_ips = self.hosts_manager.get_hostname_entries(hostname) if hostname else []
|
|
||||||
|
|
||||||
# 判断是否继续优选的逻辑
|
# 判断是否继续优选的逻辑
|
||||||
if existing_ips and self.has_optimized_in_session:
|
if self.has_optimized_in_session:
|
||||||
# 如果本次会话中已执行过优选且hosts中存在记录,则跳过优选过程
|
# 如果本次会话中已执行过优选,则跳过优选过程
|
||||||
logger.info(f"发现hosts文件中已有域名 {hostname} 的优选IP记录且本次会话已优选过,跳过优选过程")
|
logger.info("本次会话已执行过优选,跳过优选过程")
|
||||||
|
|
||||||
# 设置标记为已优选完成
|
# 设置标记为已优选完成
|
||||||
self.optimization_done = True
|
self.optimization_done = True
|
||||||
self.countdown_finished = True
|
self.countdown_finished = True
|
||||||
|
|
||||||
# 尝试获取现有的IPv4和IPv6地址
|
|
||||||
ipv4_entries = [ip for ip in existing_ips if ':' not in ip] # IPv4地址不含冒号
|
|
||||||
ipv6_entries = [ip for ip in existing_ips if ':' in ip] # IPv6地址包含冒号
|
|
||||||
|
|
||||||
if ipv4_entries:
|
|
||||||
self.optimized_ip = ipv4_entries[0]
|
|
||||||
if ipv6_entries:
|
|
||||||
self.optimized_ipv6 = ipv6_entries[0]
|
|
||||||
|
|
||||||
logger.info(f"使用已存在的优选IP - IPv4: {self.optimized_ip}, IPv6: {self.optimized_ipv6}")
|
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
# 如果本次会话尚未优选过,或hosts中没有记录,则显示优选窗口
|
# 如果本次会话尚未优选过,则清理可能存在的旧记录
|
||||||
if existing_ips:
|
if hostname:
|
||||||
logger.info(f"发现hosts文件中已有域名 {hostname} 的优选IP记录,但本次会话尚未优选过")
|
# 检查hosts文件中是否已有该域名的IP记录
|
||||||
# 清理已有的hosts记录,准备重新优选
|
existing_ips = self.hosts_manager.get_hostname_entries(hostname)
|
||||||
self.hosts_manager.clean_hostname_entries(hostname)
|
if existing_ips:
|
||||||
|
logger.info(f"发现hosts文件中已有域名 {hostname} 的优选IP记录,但本次会话尚未优选过")
|
||||||
|
# 清理已有的hosts记录,准备重新优选
|
||||||
|
self.hosts_manager.clean_hostname_entries(hostname)
|
||||||
|
|
||||||
# 创建取消状态标记
|
# 创建取消状态标记
|
||||||
self.optimization_cancelled = False
|
self.optimization_cancelled = False
|
||||||
@@ -282,6 +272,9 @@ class CloudflareOptimizer:
|
|||||||
|
|
||||||
def _process_optimization_results(self):
|
def _process_optimization_results(self):
|
||||||
"""处理优选的IP结果,显示相应提示"""
|
"""处理优选的IP结果,显示相应提示"""
|
||||||
|
# 无论优选结果如何,都标记本次会话已执行过优选
|
||||||
|
self.has_optimized_in_session = True
|
||||||
|
|
||||||
use_ipv6 = False
|
use_ipv6 = False
|
||||||
if hasattr(self.main_window, 'config'):
|
if hasattr(self.main_window, 'config'):
|
||||||
use_ipv6 = self.main_window.config.get("ipv6_enabled", False)
|
use_ipv6 = self.main_window.config.get("ipv6_enabled", False)
|
||||||
@@ -376,9 +369,6 @@ class CloudflareOptimizer:
|
|||||||
from utils import save_config
|
from utils import save_config
|
||||||
save_config(self.main_window.config)
|
save_config(self.main_window.config)
|
||||||
|
|
||||||
# 记录本次会话已执行过优选
|
|
||||||
self.has_optimized_in_session = True
|
|
||||||
|
|
||||||
if success:
|
if success:
|
||||||
msg_box = QtWidgets.QMessageBox(self.main_window)
|
msg_box = QtWidgets.QMessageBox(self.main_window)
|
||||||
msg_box.setWindowTitle(f"成功 - {self.main_window.APP_NAME}")
|
msg_box.setWindowTitle(f"成功 - {self.main_window.APP_NAME}")
|
||||||
|
|||||||
@@ -201,38 +201,31 @@ class DownloadManager:
|
|||||||
return safe_config
|
return safe_config
|
||||||
|
|
||||||
def download_action(self):
|
def download_action(self):
|
||||||
"""开始下载流程"""
|
"""下载操作的主入口点"""
|
||||||
self.main_window.download_queue_history = []
|
if not self.selected_folder:
|
||||||
|
QtWidgets.QMessageBox.warning(
|
||||||
# 清除游戏检测器的目录缓存,确保获取最新的目录状态
|
self.main_window, f"通知 - {APP_NAME}", "\n未选择任何目录,请重新选择\n"
|
||||||
if hasattr(self.main_window, 'game_detector') and hasattr(self.main_window.game_detector, 'clear_directory_cache'):
|
)
|
||||||
self.main_window.game_detector.clear_directory_cache()
|
return
|
||||||
if self.is_debug_mode():
|
|
||||||
logger.debug("DEBUG: 已清除游戏目录缓存,确保获取最新状态")
|
|
||||||
|
|
||||||
|
# 识别游戏目录
|
||||||
game_dirs = self.main_window.game_detector.identify_game_directories_improved(self.selected_folder)
|
game_dirs = self.main_window.game_detector.identify_game_directories_improved(self.selected_folder)
|
||||||
|
|
||||||
debug_mode = self.is_debug_mode()
|
|
||||||
if debug_mode:
|
|
||||||
logger.debug(f"DEBUG: 开始下载流程, 识别到 {len(game_dirs)} 个游戏目录")
|
|
||||||
|
|
||||||
if not game_dirs:
|
if not game_dirs:
|
||||||
if debug_mode:
|
|
||||||
logger.warning("DEBUG: 未识别到任何游戏目录,设置目录未找到错误")
|
|
||||||
self.main_window.last_error_message = "directory_not_found"
|
|
||||||
QtWidgets.QMessageBox.warning(
|
QtWidgets.QMessageBox.warning(
|
||||||
self.main_window,
|
self.main_window, f"通知 - {APP_NAME}", "\n未在选择的目录中找到支持的游戏\n"
|
||||||
f"目录错误 - {APP_NAME}",
|
|
||||||
"\n未能识别到任何游戏目录。\n\n请确认您选择的是游戏的上级目录,并且该目录中包含NEKOPARA系列游戏文件夹。\n"
|
|
||||||
)
|
)
|
||||||
self.main_window.setEnabled(True)
|
self.main_window.setEnabled(True)
|
||||||
self.main_window.ui.start_install_text.setText("开始安装")
|
self.main_window.ui.start_install_text.setText("开始安装")
|
||||||
return
|
return
|
||||||
|
|
||||||
self.main_window.hash_msg_box = self.main_window.hash_manager.hash_pop_window(check_type="pre", is_offline=False)
|
# 显示文件检验窗口
|
||||||
|
self.main_window.hash_msg_box = self.main_window.hash_manager.hash_pop_window(check_type="pre")
|
||||||
|
|
||||||
|
# 获取安装路径
|
||||||
install_paths = self.get_install_paths()
|
install_paths = self.get_install_paths()
|
||||||
|
|
||||||
|
# 创建并启动哈希线程进行预检查
|
||||||
self.main_window.hash_thread = self.main_window.create_hash_thread("pre", install_paths)
|
self.main_window.hash_thread = self.main_window.create_hash_thread("pre", install_paths)
|
||||||
self.main_window.hash_thread.pre_finished.connect(
|
self.main_window.hash_thread.pre_finished.connect(
|
||||||
lambda updated_status: self.on_pre_hash_finished_with_dirs(updated_status, game_dirs)
|
lambda updated_status: self.on_pre_hash_finished_with_dirs(updated_status, game_dirs)
|
||||||
@@ -255,36 +248,8 @@ class DownloadManager:
|
|||||||
|
|
||||||
self.main_window.setEnabled(True)
|
self.main_window.setEnabled(True)
|
||||||
|
|
||||||
installable_games = []
|
# 使用patch_detector检测可安装的游戏
|
||||||
already_installed_games = []
|
already_installed_games, installable_games, disabled_patch_games = self.main_window.patch_detector.detect_installable_games(game_dirs)
|
||||||
disabled_patch_games = [] # 存储检测到禁用补丁的游戏
|
|
||||||
|
|
||||||
for game_version, game_dir in game_dirs.items():
|
|
||||||
# 首先通过文件检查确认补丁是否已安装
|
|
||||||
is_patch_installed = self.main_window.patch_manager.check_patch_installed(game_dir, game_version)
|
|
||||||
# 同时考虑哈希检查结果
|
|
||||||
hash_check_passed = self.main_window.installed_status.get(game_version, False)
|
|
||||||
|
|
||||||
# 如果补丁文件存在或哈希检查通过,认为已安装
|
|
||||||
if is_patch_installed or hash_check_passed:
|
|
||||||
if debug_mode:
|
|
||||||
logger.info(f"DEBUG: {game_version} 已安装补丁,不需要再次安装")
|
|
||||||
logger.info(f"DEBUG: 文件检查结果: {is_patch_installed}, 哈希检查结果: {hash_check_passed}")
|
|
||||||
already_installed_games.append(game_version)
|
|
||||||
# 更新安装状态
|
|
||||||
self.main_window.installed_status[game_version] = True
|
|
||||||
else:
|
|
||||||
# 检查是否存在被禁用的补丁
|
|
||||||
is_disabled, disabled_path = self.main_window.patch_manager.check_patch_disabled(game_dir, game_version)
|
|
||||||
if is_disabled:
|
|
||||||
if debug_mode:
|
|
||||||
logger.info(f"DEBUG: {game_version} 存在被禁用的补丁: {disabled_path}")
|
|
||||||
disabled_patch_games.append(game_version)
|
|
||||||
else:
|
|
||||||
if debug_mode:
|
|
||||||
logger.info(f"DEBUG: {game_version} 未安装补丁,可以安装")
|
|
||||||
logger.info(f"DEBUG: 文件检查结果: {is_patch_installed}, 哈希检查结果: {hash_check_passed}")
|
|
||||||
installable_games.append(game_version)
|
|
||||||
|
|
||||||
status_message = ""
|
status_message = ""
|
||||||
if already_installed_games:
|
if already_installed_games:
|
||||||
@@ -423,7 +388,11 @@ class DownloadManager:
|
|||||||
self._fill_download_queue(config, selected_game_dirs)
|
self._fill_download_queue(config, selected_game_dirs)
|
||||||
|
|
||||||
if not self.download_queue:
|
if not self.download_queue:
|
||||||
self.main_window.after_hash_compare()
|
# 所有下载任务都已完成,进行后检查
|
||||||
|
if debug_mode:
|
||||||
|
logger.debug("DEBUG: 所有下载任务完成,进行后检查")
|
||||||
|
# 使用patch_detector进行安装后哈希比较
|
||||||
|
self.main_window.patch_detector.after_hash_compare()
|
||||||
return
|
return
|
||||||
|
|
||||||
# 如果是离线模式,直接开始下一个下载任务
|
# 如果是离线模式,直接开始下一个下载任务
|
||||||
@@ -544,29 +513,17 @@ class DownloadManager:
|
|||||||
"""显示Cloudflare加速选择对话框"""
|
"""显示Cloudflare加速选择对话框"""
|
||||||
if self.download_queue:
|
if self.download_queue:
|
||||||
first_url = self.download_queue[0][0]
|
first_url = self.download_queue[0][0]
|
||||||
hostname = urlparse(first_url).hostname
|
|
||||||
|
|
||||||
if hostname:
|
# 直接检查是否本次会话已执行过优选
|
||||||
existing_ips = self.cloudflare_optimizer.hosts_manager.get_hostname_entries(hostname)
|
if self.cloudflare_optimizer.has_optimized_in_session:
|
||||||
|
logger.info("本次会话已执行过优选,跳过询问直接使用")
|
||||||
|
|
||||||
if existing_ips:
|
self.cloudflare_optimizer.optimization_done = True
|
||||||
logger.info(f"发现hosts文件中已有域名 {hostname} 的优选IP记录,跳过询问直接使用")
|
self.cloudflare_optimizer.countdown_finished = True
|
||||||
|
|
||||||
self.cloudflare_optimizer.optimization_done = True
|
self.main_window.current_url = first_url
|
||||||
self.cloudflare_optimizer.countdown_finished = True
|
self.next_download_task()
|
||||||
|
return
|
||||||
ipv4_entries = [ip for ip in existing_ips if ':' not in ip]
|
|
||||||
ipv6_entries = [ip for ip in existing_ips if ':' in ip]
|
|
||||||
|
|
||||||
if ipv4_entries:
|
|
||||||
self.cloudflare_optimizer.optimized_ip = ipv4_entries[0]
|
|
||||||
if ipv6_entries:
|
|
||||||
self.cloudflare_optimizer.optimized_ipv6 = ipv6_entries[0]
|
|
||||||
|
|
||||||
self.main_window.current_url = first_url
|
|
||||||
|
|
||||||
self.next_download_task()
|
|
||||||
return
|
|
||||||
|
|
||||||
self.main_window.setEnabled(True)
|
self.main_window.setEnabled(True)
|
||||||
|
|
||||||
@@ -619,7 +576,11 @@ class DownloadManager:
|
|||||||
def next_download_task(self):
|
def next_download_task(self):
|
||||||
"""处理下载队列中的下一个任务"""
|
"""处理下载队列中的下一个任务"""
|
||||||
if not self.download_queue:
|
if not self.download_queue:
|
||||||
self.main_window.after_hash_compare()
|
# 所有下载任务都已完成,进行后检查
|
||||||
|
if debug_mode:
|
||||||
|
logger.debug("DEBUG: 所有下载任务完成,进行后检查")
|
||||||
|
# 使用patch_detector进行安装后哈希比较
|
||||||
|
self.main_window.patch_detector.after_hash_compare()
|
||||||
return
|
return
|
||||||
|
|
||||||
if self.download_task_manager.current_download_thread and self.download_task_manager.current_download_thread.isRunning():
|
if self.download_task_manager.current_download_thread and self.download_task_manager.current_download_thread.isRunning():
|
||||||
@@ -735,21 +696,18 @@ class DownloadManager:
|
|||||||
self.download_task_manager.start_download(url, _7z_path, game_version, game_folder, plugin_path)
|
self.download_task_manager.start_download(url, _7z_path, game_version, game_folder, plugin_path)
|
||||||
|
|
||||||
def on_download_finished(self, success, error, url, game_folder, game_version, _7z_path, plugin_path):
|
def on_download_finished(self, success, error, url, game_folder, game_version, _7z_path, plugin_path):
|
||||||
"""下载完成后的处理
|
"""下载完成后的回调函数
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
success: 是否下载成功
|
success: 是否下载成功
|
||||||
error: 错误信息
|
error: 错误信息
|
||||||
url: 下载URL
|
url: 下载URL
|
||||||
game_folder: 游戏文件夹路径
|
game_folder: 游戏文件夹路径
|
||||||
game_version: 游戏版本名称
|
game_version: 游戏版本
|
||||||
_7z_path: 7z文件保存路径
|
_7z_path: 7z文件保存路径
|
||||||
plugin_path: 插件路径
|
plugin_path: 插件保存路径
|
||||||
"""
|
"""
|
||||||
if self.main_window.progress_window and self.main_window.progress_window.isVisible():
|
# 如果下载失败,显示错误并询问是否重试
|
||||||
self.main_window.progress_window.reject()
|
|
||||||
self.main_window.progress_window = None
|
|
||||||
|
|
||||||
if not success:
|
if not success:
|
||||||
logger.error(f"--- Download Failed: {game_version} ---")
|
logger.error(f"--- Download Failed: {game_version} ---")
|
||||||
logger.error(error)
|
logger.error(error)
|
||||||
@@ -805,7 +763,6 @@ class DownloadManager:
|
|||||||
return
|
return
|
||||||
|
|
||||||
self.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.extraction_handler.extraction_finished.connect(self.on_extraction_finished)
|
|
||||||
|
|
||||||
def on_extraction_finished(self, continue_download):
|
def on_extraction_finished(self, continue_download):
|
||||||
"""解压完成后的回调,决定是否继续下载队列
|
"""解压完成后的回调,决定是否继续下载队列
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ class OfflineModeManager:
|
|||||||
self.app_name = main_window.APP_NAME if hasattr(main_window, 'APP_NAME') else ""
|
self.app_name = main_window.APP_NAME if hasattr(main_window, 'APP_NAME') else ""
|
||||||
self.offline_patches = {} # 存储离线补丁信息 {补丁名称: 文件路径}
|
self.offline_patches = {} # 存储离线补丁信息 {补丁名称: 文件路径}
|
||||||
self.is_offline_mode = False
|
self.is_offline_mode = False
|
||||||
|
self.installed_games = [] # 跟踪本次实际安装的游戏
|
||||||
|
|
||||||
def _is_debug_mode(self):
|
def _is_debug_mode(self):
|
||||||
"""检查是否处于调试模式
|
"""检查是否处于调试模式
|
||||||
@@ -208,7 +209,7 @@ class OfflineModeManager:
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
def verify_patch_hash(self, game_version, file_path):
|
def verify_patch_hash(self, game_version, file_path):
|
||||||
"""验证补丁文件的哈希值
|
"""验证补丁文件的哈希值,使用patch_detector模块
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
game_version: 游戏版本名称
|
game_version: 游戏版本名称
|
||||||
@@ -217,164 +218,8 @@ class OfflineModeManager:
|
|||||||
Returns:
|
Returns:
|
||||||
bool: 哈希值是否匹配
|
bool: 哈希值是否匹配
|
||||||
"""
|
"""
|
||||||
# 获取预期的哈希值
|
# 使用patch_detector模块验证哈希值
|
||||||
expected_hash = None
|
return self.main_window.patch_detector.verify_patch_hash(game_version, file_path)
|
||||||
|
|
||||||
if "Vol.1" in game_version:
|
|
||||||
expected_hash = PLUGIN_HASH.get("vol1", "")
|
|
||||||
elif "Vol.2" in game_version:
|
|
||||||
expected_hash = PLUGIN_HASH.get("vol2", "")
|
|
||||||
elif "Vol.3" in game_version:
|
|
||||||
expected_hash = PLUGIN_HASH.get("vol3", "")
|
|
||||||
elif "Vol.4" in game_version:
|
|
||||||
expected_hash = PLUGIN_HASH.get("vol4", "")
|
|
||||||
elif "After" in game_version:
|
|
||||||
expected_hash = PLUGIN_HASH.get("after", "")
|
|
||||||
|
|
||||||
if not expected_hash:
|
|
||||||
logger.warning(f"DEBUG: 未找到 {game_version} 的预期哈希值")
|
|
||||||
return False
|
|
||||||
|
|
||||||
debug_mode = self._is_debug_mode()
|
|
||||||
|
|
||||||
if debug_mode:
|
|
||||||
logger.debug(f"DEBUG: 开始验证离线补丁文件: {file_path}")
|
|
||||||
logger.debug(f"DEBUG: 游戏版本: {game_version}")
|
|
||||||
logger.debug(f"DEBUG: 预期哈希值: {expected_hash}")
|
|
||||||
|
|
||||||
try:
|
|
||||||
# 检查文件是否存在
|
|
||||||
if not os.path.exists(file_path):
|
|
||||||
if debug_mode:
|
|
||||||
logger.warning(f"DEBUG: 补丁文件不存在: {file_path}")
|
|
||||||
return False
|
|
||||||
|
|
||||||
# 检查文件大小
|
|
||||||
file_size = os.path.getsize(file_path)
|
|
||||||
if debug_mode:
|
|
||||||
logger.debug(f"DEBUG: 补丁文件大小: {file_size} 字节")
|
|
||||||
|
|
||||||
if file_size == 0:
|
|
||||||
if debug_mode:
|
|
||||||
logger.warning(f"DEBUG: 补丁文件大小为0,无效文件")
|
|
||||||
return False
|
|
||||||
|
|
||||||
# 创建临时目录用于解压文件
|
|
||||||
with tempfile.TemporaryDirectory() as temp_dir:
|
|
||||||
if debug_mode:
|
|
||||||
logger.debug(f"DEBUG: 创建临时目录: {temp_dir}")
|
|
||||||
|
|
||||||
# 解压补丁文件
|
|
||||||
try:
|
|
||||||
if debug_mode:
|
|
||||||
logger.debug(f"DEBUG: 开始解压文件: {file_path}")
|
|
||||||
|
|
||||||
with py7zr.SevenZipFile(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()}")
|
|
||||||
return False
|
|
||||||
|
|
||||||
# 获取补丁文件路径
|
|
||||||
patch_file = None
|
|
||||||
if "Vol.1" in game_version:
|
|
||||||
patch_file = os.path.join(temp_dir, "vol.1", "adultsonly.xp3")
|
|
||||||
elif "Vol.2" in game_version:
|
|
||||||
patch_file = os.path.join(temp_dir, "vol.2", "adultsonly.xp3")
|
|
||||||
elif "Vol.3" in game_version:
|
|
||||||
patch_file = os.path.join(temp_dir, "vol.3", "update00.int")
|
|
||||||
elif "Vol.4" in game_version:
|
|
||||||
patch_file = os.path.join(temp_dir, "vol.4", "vol4adult.xp3")
|
|
||||||
elif "After" in 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}")
|
|
||||||
return False
|
|
||||||
|
|
||||||
if debug_mode:
|
|
||||||
logger.debug(f"DEBUG: 找到解压后的补丁文件: {patch_file}")
|
|
||||||
|
|
||||||
# 计算补丁文件哈希值
|
|
||||||
try:
|
|
||||||
with open(patch_file, "rb") as f:
|
|
||||||
file_hash = hashlib.sha256(f.read()).hexdigest()
|
|
||||||
|
|
||||||
# 比较哈希值
|
|
||||||
result = file_hash.lower() == expected_hash.lower()
|
|
||||||
|
|
||||||
if debug_mode:
|
|
||||||
logger.debug(f"DEBUG: 补丁文件 {patch_file} 哈希值验证: {'成功' if result else '失败'}")
|
|
||||||
logger.debug(f"DEBUG: 预期哈希值: {expected_hash}")
|
|
||||||
logger.debug(f"DEBUG: 实际哈希值: {file_hash}")
|
|
||||||
|
|
||||||
return result
|
|
||||||
except Exception as e:
|
|
||||||
if debug_mode:
|
|
||||||
logger.error(f"DEBUG: 计算补丁文件哈希值失败: {e}")
|
|
||||||
logger.error(f"DEBUG: 错误类型: {type(e).__name__}")
|
|
||||||
return False
|
|
||||||
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()}")
|
|
||||||
return False
|
|
||||||
|
|
||||||
def is_offline_mode_available(self):
|
|
||||||
"""检查是否可以使用离线模式
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
bool: 是否可以使用离线模式
|
|
||||||
"""
|
|
||||||
# 在调试模式下始终允许离线模式
|
|
||||||
if self._is_debug_mode():
|
|
||||||
return True
|
|
||||||
|
|
||||||
# 检查是否有离线补丁文件
|
|
||||||
return self.has_offline_patches()
|
|
||||||
|
|
||||||
def is_in_offline_mode(self):
|
|
||||||
"""检查当前是否处于离线模式
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
bool: 是否处于离线模式
|
|
||||||
"""
|
|
||||||
return self.is_offline_mode
|
|
||||||
|
|
||||||
def install_offline_patches(self, selected_games):
|
def install_offline_patches(self, selected_games):
|
||||||
"""直接安装离线补丁,完全绕过下载模块
|
"""直接安装离线补丁,完全绕过下载模块
|
||||||
@@ -419,78 +264,29 @@ class OfflineModeManager:
|
|||||||
logger.warning("DEBUG: 未识别到任何游戏目录")
|
logger.warning("DEBUG: 未识别到任何游戏目录")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# 显示文件检验窗口
|
self.main_window.setEnabled(False)
|
||||||
self.main_window.hash_msg_box = self.main_window.hash_manager.hash_pop_window(check_type="pre", is_offline=True)
|
|
||||||
|
|
||||||
# 获取安装路径
|
# 重置已安装游戏列表
|
||||||
install_paths = self.main_window.download_manager.get_install_paths()
|
self.installed_games = []
|
||||||
|
|
||||||
# 创建并启动哈希线程进行预检查
|
# 设置到主窗口,供结果显示使用
|
||||||
self.main_window.hash_thread = self.main_window.create_hash_thread("pre", install_paths)
|
self.main_window.download_queue_history = selected_games
|
||||||
self.main_window.hash_thread.pre_finished.connect(
|
|
||||||
lambda updated_status: self.on_offline_pre_hash_finished(updated_status, game_dirs, selected_games)
|
|
||||||
)
|
|
||||||
self.main_window.hash_thread.start()
|
|
||||||
|
|
||||||
return True
|
|
||||||
|
|
||||||
def on_offline_pre_hash_finished(self, updated_status, game_dirs, selected_games):
|
|
||||||
"""离线模式下的哈希预检查完成处理
|
|
||||||
|
|
||||||
Args:
|
|
||||||
updated_status: 更新后的安装状态
|
|
||||||
game_dirs: 识别到的游戏目录
|
|
||||||
selected_games: 用户选择安装的游戏列表
|
|
||||||
"""
|
|
||||||
debug_mode = self._is_debug_mode()
|
|
||||||
|
|
||||||
# 更新安装状态
|
|
||||||
self.main_window.installed_status = updated_status
|
|
||||||
|
|
||||||
# 关闭哈希检查窗口
|
|
||||||
if self.main_window.hash_msg_box and self.main_window.hash_msg_box.isVisible():
|
|
||||||
self.main_window.hash_msg_box.accept()
|
|
||||||
self.main_window.hash_msg_box = None
|
|
||||||
|
|
||||||
# 重新启用主窗口
|
|
||||||
self.main_window.setEnabled(True)
|
|
||||||
|
|
||||||
# 过滤出需要安装的游戏
|
|
||||||
installable_games = []
|
|
||||||
for game_version in selected_games:
|
|
||||||
if game_version in game_dirs and not self.main_window.installed_status.get(game_version, False):
|
|
||||||
# 检查是否有对应的离线补丁
|
|
||||||
if self.get_offline_patch_path(game_version):
|
|
||||||
installable_games.append(game_version)
|
|
||||||
elif debug_mode:
|
|
||||||
logger.warning(f"DEBUG: 未找到 {game_version} 的离线补丁文件,跳过")
|
|
||||||
|
|
||||||
if not installable_games:
|
|
||||||
if debug_mode:
|
|
||||||
logger.info("DEBUG: 没有需要安装的游戏或未找到对应的离线补丁")
|
|
||||||
msgbox_frame(
|
|
||||||
f"离线安装信息 - {self.app_name}",
|
|
||||||
"\n没有需要安装的游戏或未找到对应的离线补丁文件。\n",
|
|
||||||
QMessageBox.StandardButton.Ok
|
|
||||||
).exec()
|
|
||||||
self.main_window.ui.start_install_text.setText("开始安装")
|
|
||||||
return
|
|
||||||
|
|
||||||
# 开始安装流程
|
|
||||||
if debug_mode:
|
|
||||||
logger.info(f"DEBUG: 开始离线安装流程,安装游戏: {installable_games}")
|
|
||||||
|
|
||||||
# 创建安装任务列表
|
# 创建安装任务列表
|
||||||
install_tasks = []
|
install_tasks = []
|
||||||
for game_version in installable_games:
|
for game_version in selected_games:
|
||||||
# 获取离线补丁文件路径
|
# 获取离线补丁文件路径
|
||||||
patch_file = self.get_offline_patch_path(game_version)
|
patch_file = self.get_offline_patch_path(game_version)
|
||||||
if not patch_file:
|
if not patch_file:
|
||||||
|
if debug_mode:
|
||||||
|
logger.warning(f"DEBUG: 未找到 {game_version} 的离线补丁文件,跳过")
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# 获取游戏目录
|
# 获取游戏目录
|
||||||
game_folder = game_dirs.get(game_version)
|
game_folder = game_dirs.get(game_version)
|
||||||
if not game_folder:
|
if not game_folder:
|
||||||
|
if debug_mode:
|
||||||
|
logger.warning(f"DEBUG: 未找到 {game_version} 的游戏目录,跳过")
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# 获取目标路径
|
# 获取目标路径
|
||||||
@@ -510,6 +306,8 @@ class OfflineModeManager:
|
|||||||
_7z_path = os.path.join(PLUGIN, "after.7z")
|
_7z_path = os.path.join(PLUGIN, "after.7z")
|
||||||
plugin_path = os.path.join(PLUGIN, GAME_INFO[game_version]["plugin_path"])
|
plugin_path = os.path.join(PLUGIN, GAME_INFO[game_version]["plugin_path"])
|
||||||
else:
|
else:
|
||||||
|
if debug_mode:
|
||||||
|
logger.warning(f"DEBUG: {game_version} 不是支持的游戏版本,跳过")
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# 添加到安装任务列表
|
# 添加到安装任务列表
|
||||||
@@ -517,10 +315,22 @@ class OfflineModeManager:
|
|||||||
|
|
||||||
# 开始执行第一个安装任务
|
# 开始执行第一个安装任务
|
||||||
if install_tasks:
|
if install_tasks:
|
||||||
|
if debug_mode:
|
||||||
|
logger.info(f"DEBUG: 开始离线安装流程,安装游戏数量: {len(install_tasks)}")
|
||||||
self.process_next_offline_install_task(install_tasks)
|
self.process_next_offline_install_task(install_tasks)
|
||||||
else:
|
else:
|
||||||
|
if debug_mode:
|
||||||
|
logger.warning("DEBUG: 没有可安装的游戏,安装流程结束")
|
||||||
|
msgbox_frame(
|
||||||
|
f"离线安装信息 - {self.app_name}",
|
||||||
|
"\n没有可安装的游戏或未找到对应的离线补丁文件。\n",
|
||||||
|
QMessageBox.StandardButton.Ok
|
||||||
|
).exec()
|
||||||
|
self.main_window.setEnabled(True)
|
||||||
self.main_window.ui.start_install_text.setText("开始安装")
|
self.main_window.ui.start_install_text.setText("开始安装")
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
def process_next_offline_install_task(self, install_tasks):
|
def process_next_offline_install_task(self, install_tasks):
|
||||||
"""处理下一个离线安装任务
|
"""处理下一个离线安装任务
|
||||||
|
|
||||||
@@ -533,7 +343,9 @@ class OfflineModeManager:
|
|||||||
# 所有任务完成,进行后检查
|
# 所有任务完成,进行后检查
|
||||||
if debug_mode:
|
if debug_mode:
|
||||||
logger.info("DEBUG: 所有离线安装任务完成,进行后检查")
|
logger.info("DEBUG: 所有离线安装任务完成,进行后检查")
|
||||||
self.main_window.after_hash_compare()
|
|
||||||
|
# 使用patch_detector进行安装后哈希比较
|
||||||
|
self.main_window.patch_detector.after_hash_compare()
|
||||||
return
|
return
|
||||||
|
|
||||||
# 获取下一个任务
|
# 获取下一个任务
|
||||||
@@ -555,22 +367,6 @@ class OfflineModeManager:
|
|||||||
logger.debug(f"DEBUG: 已复制补丁文件到缓存目录: {_7z_path}")
|
logger.debug(f"DEBUG: 已复制补丁文件到缓存目录: {_7z_path}")
|
||||||
logger.debug(f"DEBUG: 开始验证补丁文件哈希值")
|
logger.debug(f"DEBUG: 开始验证补丁文件哈希值")
|
||||||
|
|
||||||
# 获取预期的哈希值
|
|
||||||
expected_hash = None
|
|
||||||
if "Vol.1" in game_version:
|
|
||||||
expected_hash = PLUGIN_HASH.get("vol1", "")
|
|
||||||
elif "Vol.2" in game_version:
|
|
||||||
expected_hash = PLUGIN_HASH.get("vol2", "")
|
|
||||||
elif "Vol.3" in game_version:
|
|
||||||
expected_hash = PLUGIN_HASH.get("vol3", "")
|
|
||||||
elif "Vol.4" in game_version:
|
|
||||||
expected_hash = PLUGIN_HASH.get("vol4", "")
|
|
||||||
elif "After" in game_version:
|
|
||||||
expected_hash = PLUGIN_HASH.get("after", "")
|
|
||||||
|
|
||||||
if debug_mode and expected_hash:
|
|
||||||
logger.debug(f"DEBUG: 预期哈希值: {expected_hash}")
|
|
||||||
|
|
||||||
# 显示哈希验证窗口 - 使用离线特定消息
|
# 显示哈希验证窗口 - 使用离线特定消息
|
||||||
self.main_window.hash_msg_box = self.main_window.hash_manager.hash_pop_window(check_type="offline_verify", is_offline=True)
|
self.main_window.hash_msg_box = self.main_window.hash_manager.hash_pop_window(check_type="offline_verify", is_offline=True)
|
||||||
|
|
||||||
@@ -684,19 +480,30 @@ class OfflineModeManager:
|
|||||||
# 更新安装状态
|
# 更新安装状态
|
||||||
self.main_window.installed_status[game_version] = True
|
self.main_window.installed_status[game_version] = True
|
||||||
|
|
||||||
|
# 添加到已安装游戏列表
|
||||||
|
if game_version not in self.installed_games:
|
||||||
|
self.installed_games.append(game_version)
|
||||||
|
|
||||||
# 处理下一个任务
|
# 处理下一个任务
|
||||||
self.process_next_offline_install_task(remaining_tasks)
|
self.process_next_offline_install_task(remaining_tasks)
|
||||||
|
|
||||||
def on_offline_extraction_finished(self, remaining_tasks):
|
def is_offline_mode_available(self):
|
||||||
"""离线模式下的解压完成处理(旧方法,保留兼容性)
|
"""检查是否可以使用离线模式
|
||||||
|
|
||||||
Args:
|
Returns:
|
||||||
remaining_tasks: 剩余的安装任务列表
|
bool: 是否可以使用离线模式
|
||||||
"""
|
"""
|
||||||
debug_mode = self._is_debug_mode()
|
# 在调试模式下始终允许离线模式
|
||||||
|
if self._is_debug_mode():
|
||||||
|
return True
|
||||||
|
|
||||||
if debug_mode:
|
# 检查是否有离线补丁文件
|
||||||
logger.debug("DEBUG: 离线解压完成,继续处理下一个任务")
|
return self.has_offline_patches()
|
||||||
|
|
||||||
# 处理下一个任务
|
def is_in_offline_mode(self):
|
||||||
self.process_next_offline_install_task(remaining_tasks)
|
"""检查当前是否处于离线模式
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
bool: 是否处于离线模式
|
||||||
|
"""
|
||||||
|
return self.is_offline_mode
|
||||||
599
source/core/patch_detector.py
Normal file
599
source/core/patch_detector.py
Normal file
@@ -0,0 +1,599 @@
|
|||||||
|
import os
|
||||||
|
import hashlib
|
||||||
|
import tempfile
|
||||||
|
import py7zr
|
||||||
|
import traceback
|
||||||
|
from utils.logger import setup_logger
|
||||||
|
from PySide6.QtWidgets import QMessageBox
|
||||||
|
from PySide6.QtCore import QTimer
|
||||||
|
from data.config import PLUGIN_HASH, APP_NAME
|
||||||
|
from workers.hash_thread import HashThread
|
||||||
|
|
||||||
|
# 初始化logger
|
||||||
|
logger = setup_logger("patch_detector")
|
||||||
|
|
||||||
|
class PatchDetector:
|
||||||
|
"""补丁检测与校验模块,用于统一处理在线和离线模式下的补丁检测和校验"""
|
||||||
|
|
||||||
|
def __init__(self, main_window):
|
||||||
|
"""初始化补丁检测器
|
||||||
|
|
||||||
|
Args:
|
||||||
|
main_window: 主窗口实例,用于访问UI和状态
|
||||||
|
"""
|
||||||
|
self.main_window = main_window
|
||||||
|
self.app_name = main_window.APP_NAME if hasattr(main_window, 'APP_NAME') else ""
|
||||||
|
self.game_info = {}
|
||||||
|
self.plugin_hash = {}
|
||||||
|
|
||||||
|
# 从配置中加载游戏信息和补丁哈希值
|
||||||
|
self._load_game_info()
|
||||||
|
|
||||||
|
def _load_game_info(self):
|
||||||
|
"""从配置中加载游戏信息和补丁哈希值"""
|
||||||
|
try:
|
||||||
|
from data.config import GAME_INFO, PLUGIN_HASH
|
||||||
|
self.game_info = GAME_INFO
|
||||||
|
self.plugin_hash = PLUGIN_HASH
|
||||||
|
except ImportError:
|
||||||
|
logger.error("无法加载游戏信息或补丁哈希值配置")
|
||||||
|
|
||||||
|
def _is_debug_mode(self):
|
||||||
|
"""检查是否处于调试模式
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
bool: 是否处于调试模式
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
if hasattr(self.main_window, 'debug_manager') and self.main_window.debug_manager:
|
||||||
|
if hasattr(self.main_window.debug_manager, '_is_debug_mode'):
|
||||||
|
# 尝试直接从debug_manager获取状态
|
||||||
|
return self.main_window.debug_manager._is_debug_mode()
|
||||||
|
elif hasattr(self.main_window, 'config'):
|
||||||
|
# 如果debug_manager还没准备好,尝试从配置中获取
|
||||||
|
return self.main_window.config.get('debug_mode', False)
|
||||||
|
# 如果以上都不可行,返回False
|
||||||
|
return False
|
||||||
|
except Exception:
|
||||||
|
# 捕获任何异常,默认返回False
|
||||||
|
return False
|
||||||
|
|
||||||
|
def check_patch_installed(self, game_dir, game_version):
|
||||||
|
"""检查游戏是否已安装补丁
|
||||||
|
|
||||||
|
Args:
|
||||||
|
game_dir: 游戏目录路径
|
||||||
|
game_version: 游戏版本
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
bool: 如果已安装补丁或有被禁用的补丁文件返回True,否则返回False
|
||||||
|
"""
|
||||||
|
debug_mode = self._is_debug_mode()
|
||||||
|
|
||||||
|
if game_version not in self.game_info:
|
||||||
|
return False
|
||||||
|
|
||||||
|
# 获取可能的补丁文件路径
|
||||||
|
install_path_base = os.path.basename(self.game_info[game_version]["install_path"])
|
||||||
|
patch_file_path = os.path.join(game_dir, install_path_base)
|
||||||
|
|
||||||
|
# 尝试查找补丁文件,支持不同大小写
|
||||||
|
patch_files_to_check = [
|
||||||
|
patch_file_path,
|
||||||
|
patch_file_path.lower(),
|
||||||
|
patch_file_path.upper(),
|
||||||
|
patch_file_path.replace("_", ""),
|
||||||
|
patch_file_path.replace("_", "-"),
|
||||||
|
]
|
||||||
|
|
||||||
|
# 查找补丁文件
|
||||||
|
for patch_path in patch_files_to_check:
|
||||||
|
if os.path.exists(patch_path):
|
||||||
|
if debug_mode:
|
||||||
|
logger.debug(f"找到补丁文件: {patch_path}")
|
||||||
|
return True
|
||||||
|
# 检查是否存在被禁用的补丁文件(带.fain后缀)
|
||||||
|
disabled_path = f"{patch_path}.fain"
|
||||||
|
if os.path.exists(disabled_path):
|
||||||
|
if debug_mode:
|
||||||
|
logger.debug(f"找到被禁用的补丁文件: {disabled_path}")
|
||||||
|
return True
|
||||||
|
|
||||||
|
# 检查是否有补丁文件夹
|
||||||
|
patch_folders_to_check = [
|
||||||
|
os.path.join(game_dir, "patch"),
|
||||||
|
os.path.join(game_dir, "Patch"),
|
||||||
|
os.path.join(game_dir, "PATCH"),
|
||||||
|
]
|
||||||
|
|
||||||
|
for patch_folder in patch_folders_to_check:
|
||||||
|
if os.path.exists(patch_folder):
|
||||||
|
if debug_mode:
|
||||||
|
logger.debug(f"找到补丁文件夹: {patch_folder}")
|
||||||
|
return True
|
||||||
|
|
||||||
|
# 检查game/patch文件夹
|
||||||
|
game_folders = ["game", "Game", "GAME"]
|
||||||
|
patch_folders = ["patch", "Patch", "PATCH"]
|
||||||
|
|
||||||
|
for game_folder in game_folders:
|
||||||
|
for patch_folder in patch_folders:
|
||||||
|
game_patch_folder = os.path.join(game_dir, game_folder, patch_folder)
|
||||||
|
if os.path.exists(game_patch_folder):
|
||||||
|
if debug_mode:
|
||||||
|
logger.debug(f"找到game/patch文件夹: {game_patch_folder}")
|
||||||
|
return True
|
||||||
|
|
||||||
|
# 检查配置文件
|
||||||
|
config_files = ["config.json", "Config.json", "CONFIG.JSON"]
|
||||||
|
script_files = ["scripts.json", "Scripts.json", "SCRIPTS.JSON"]
|
||||||
|
|
||||||
|
for game_folder in game_folders:
|
||||||
|
game_path = os.path.join(game_dir, game_folder)
|
||||||
|
if os.path.exists(game_path):
|
||||||
|
# 检查配置文件
|
||||||
|
for config_file in config_files:
|
||||||
|
config_path = os.path.join(game_path, config_file)
|
||||||
|
if os.path.exists(config_path):
|
||||||
|
if debug_mode:
|
||||||
|
logger.debug(f"找到配置文件: {config_path}")
|
||||||
|
return True
|
||||||
|
|
||||||
|
# 检查脚本文件
|
||||||
|
for script_file in script_files:
|
||||||
|
script_path = os.path.join(game_path, script_file)
|
||||||
|
if os.path.exists(script_path):
|
||||||
|
if debug_mode:
|
||||||
|
logger.debug(f"找到脚本文件: {script_path}")
|
||||||
|
return True
|
||||||
|
|
||||||
|
# 没有找到补丁文件或文件夹
|
||||||
|
if debug_mode:
|
||||||
|
logger.debug(f"{game_version} 在 {game_dir} 中没有安装补丁")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def check_patch_disabled(self, game_dir, game_version):
|
||||||
|
"""检查游戏的补丁是否已被禁用
|
||||||
|
|
||||||
|
Args:
|
||||||
|
game_dir: 游戏目录路径
|
||||||
|
game_version: 游戏版本
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
bool: 如果补丁被禁用返回True,否则返回False
|
||||||
|
str: 禁用的补丁文件路径,如果没有禁用返回None
|
||||||
|
"""
|
||||||
|
debug_mode = self._is_debug_mode()
|
||||||
|
|
||||||
|
if game_version not in self.game_info:
|
||||||
|
return False, None
|
||||||
|
|
||||||
|
# 获取可能的补丁文件路径
|
||||||
|
install_path_base = os.path.basename(self.game_info[game_version]["install_path"])
|
||||||
|
patch_file_path = os.path.join(game_dir, install_path_base)
|
||||||
|
|
||||||
|
# 检查是否存在禁用的补丁文件(.fain后缀)
|
||||||
|
disabled_patch_files = [
|
||||||
|
f"{patch_file_path}.fain",
|
||||||
|
f"{patch_file_path.lower()}.fain",
|
||||||
|
f"{patch_file_path.upper()}.fain",
|
||||||
|
f"{patch_file_path.replace('_', '')}.fain",
|
||||||
|
f"{patch_file_path.replace('_', '-')}.fain",
|
||||||
|
]
|
||||||
|
|
||||||
|
# 检查是否有禁用的补丁文件
|
||||||
|
for disabled_path in disabled_patch_files:
|
||||||
|
if os.path.exists(disabled_path):
|
||||||
|
if debug_mode:
|
||||||
|
logger.debug(f"找到禁用的补丁文件: {disabled_path}")
|
||||||
|
return True, disabled_path
|
||||||
|
|
||||||
|
if debug_mode:
|
||||||
|
logger.debug(f"{game_version} 在 {game_dir} 的补丁未被禁用")
|
||||||
|
|
||||||
|
return False, None
|
||||||
|
|
||||||
|
def detect_installable_games(self, game_dirs):
|
||||||
|
"""检测可安装补丁的游戏
|
||||||
|
|
||||||
|
Args:
|
||||||
|
game_dirs: 游戏版本到游戏目录的映射字典
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
tuple: (已安装补丁的游戏列表, 可安装补丁的游戏列表, 禁用补丁的游戏列表)
|
||||||
|
"""
|
||||||
|
debug_mode = self._is_debug_mode()
|
||||||
|
|
||||||
|
if debug_mode:
|
||||||
|
logger.debug(f"开始检测可安装补丁的游戏,游戏目录: {game_dirs}")
|
||||||
|
|
||||||
|
already_installed_games = []
|
||||||
|
installable_games = []
|
||||||
|
disabled_patch_games = []
|
||||||
|
|
||||||
|
for game_version, game_dir in game_dirs.items():
|
||||||
|
# 首先通过文件检查确认补丁是否已安装
|
||||||
|
is_patch_installed = self.check_patch_installed(game_dir, game_version)
|
||||||
|
# 同时考虑哈希检查结果
|
||||||
|
hash_check_passed = self.main_window.installed_status.get(game_version, False)
|
||||||
|
|
||||||
|
# 如果补丁文件存在或哈希检查通过,认为已安装
|
||||||
|
if is_patch_installed or hash_check_passed:
|
||||||
|
if debug_mode:
|
||||||
|
logger.info(f"DEBUG: {game_version} 已安装补丁,不需要再次安装")
|
||||||
|
logger.info(f"DEBUG: 文件检查结果: {is_patch_installed}, 哈希检查结果: {hash_check_passed}")
|
||||||
|
already_installed_games.append(game_version)
|
||||||
|
# 更新安装状态
|
||||||
|
self.main_window.installed_status[game_version] = True
|
||||||
|
else:
|
||||||
|
# 检查是否存在被禁用的补丁
|
||||||
|
is_disabled, disabled_path = self.check_patch_disabled(game_dir, game_version)
|
||||||
|
if is_disabled:
|
||||||
|
if debug_mode:
|
||||||
|
logger.info(f"DEBUG: {game_version} 存在被禁用的补丁: {disabled_path}")
|
||||||
|
disabled_patch_games.append(game_version)
|
||||||
|
else:
|
||||||
|
if debug_mode:
|
||||||
|
logger.info(f"DEBUG: {game_version} 未安装补丁,可以安装")
|
||||||
|
logger.info(f"DEBUG: 文件检查结果: {is_patch_installed}, 哈希检查结果: {hash_check_passed}")
|
||||||
|
installable_games.append(game_version)
|
||||||
|
|
||||||
|
if debug_mode:
|
||||||
|
logger.debug(f"检测结果 - 已安装补丁: {already_installed_games}")
|
||||||
|
logger.debug(f"检测结果 - 可安装补丁: {installable_games}")
|
||||||
|
logger.debug(f"检测结果 - 禁用补丁: {disabled_patch_games}")
|
||||||
|
|
||||||
|
return already_installed_games, installable_games, disabled_patch_games
|
||||||
|
|
||||||
|
def verify_patch_hash(self, game_version, file_path):
|
||||||
|
"""验证补丁文件的哈希值
|
||||||
|
|
||||||
|
Args:
|
||||||
|
game_version: 游戏版本名称
|
||||||
|
file_path: 补丁压缩包文件路径
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
bool: 哈希值是否匹配
|
||||||
|
"""
|
||||||
|
# 获取预期的哈希值
|
||||||
|
expected_hash = None
|
||||||
|
|
||||||
|
# 直接使用完整游戏名称作为键
|
||||||
|
expected_hash = self.plugin_hash.get(game_version, "")
|
||||||
|
|
||||||
|
if not expected_hash:
|
||||||
|
logger.warning(f"DEBUG: 未找到 {game_version} 的预期哈希值")
|
||||||
|
return False
|
||||||
|
|
||||||
|
debug_mode = self._is_debug_mode()
|
||||||
|
|
||||||
|
if debug_mode:
|
||||||
|
logger.debug(f"DEBUG: 开始验证补丁文件: {file_path}")
|
||||||
|
logger.debug(f"DEBUG: 游戏版本: {game_version}")
|
||||||
|
logger.debug(f"DEBUG: 预期哈希值: {expected_hash}")
|
||||||
|
|
||||||
|
try:
|
||||||
|
# 检查文件是否存在
|
||||||
|
if not os.path.exists(file_path):
|
||||||
|
if debug_mode:
|
||||||
|
logger.warning(f"DEBUG: 补丁文件不存在: {file_path}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
# 检查文件大小
|
||||||
|
file_size = os.path.getsize(file_path)
|
||||||
|
if debug_mode:
|
||||||
|
logger.debug(f"DEBUG: 补丁文件大小: {file_size} 字节")
|
||||||
|
|
||||||
|
if file_size == 0:
|
||||||
|
if debug_mode:
|
||||||
|
logger.warning(f"DEBUG: 补丁文件大小为0,无效文件")
|
||||||
|
return False
|
||||||
|
|
||||||
|
# 创建临时目录用于解压文件
|
||||||
|
with tempfile.TemporaryDirectory() as temp_dir:
|
||||||
|
if debug_mode:
|
||||||
|
logger.debug(f"DEBUG: 创建临时目录: {temp_dir}")
|
||||||
|
|
||||||
|
# 解压补丁文件
|
||||||
|
try:
|
||||||
|
if debug_mode:
|
||||||
|
logger.debug(f"DEBUG: 开始解压文件: {file_path}")
|
||||||
|
|
||||||
|
with py7zr.SevenZipFile(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()}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
# 获取补丁文件路径
|
||||||
|
patch_file = None
|
||||||
|
if "Vol.1" in game_version:
|
||||||
|
patch_file = os.path.join(temp_dir, "vol.1", "adultsonly.xp3")
|
||||||
|
elif "Vol.2" in game_version:
|
||||||
|
patch_file = os.path.join(temp_dir, "vol.2", "adultsonly.xp3")
|
||||||
|
elif "Vol.3" in game_version:
|
||||||
|
patch_file = os.path.join(temp_dir, "vol.3", "update00.int")
|
||||||
|
elif "Vol.4" in game_version:
|
||||||
|
patch_file = os.path.join(temp_dir, "vol.4", "vol4adult.xp3")
|
||||||
|
elif "After" in 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}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
if debug_mode:
|
||||||
|
logger.debug(f"DEBUG: 找到解压后的补丁文件: {patch_file}")
|
||||||
|
|
||||||
|
# 计算补丁文件哈希值
|
||||||
|
try:
|
||||||
|
with open(patch_file, "rb") as f:
|
||||||
|
file_hash = hashlib.sha256(f.read()).hexdigest()
|
||||||
|
|
||||||
|
# 比较哈希值
|
||||||
|
result = file_hash.lower() == expected_hash.lower()
|
||||||
|
|
||||||
|
if debug_mode:
|
||||||
|
logger.debug(f"DEBUG: 补丁文件 {patch_file} 哈希值验证: {'成功' if result else '失败'}")
|
||||||
|
logger.debug(f"DEBUG: 预期哈希值: {expected_hash}")
|
||||||
|
logger.debug(f"DEBUG: 实际哈希值: {file_hash}")
|
||||||
|
|
||||||
|
return result
|
||||||
|
except Exception as e:
|
||||||
|
if debug_mode:
|
||||||
|
logger.error(f"DEBUG: 计算补丁文件哈希值失败: {e}")
|
||||||
|
logger.error(f"DEBUG: 错误类型: {type(e).__name__}")
|
||||||
|
return False
|
||||||
|
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()}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def create_hash_thread(self, mode, install_paths):
|
||||||
|
"""创建哈希检查线程
|
||||||
|
|
||||||
|
Args:
|
||||||
|
mode: 检查模式,"pre"或"after"
|
||||||
|
install_paths: 安装路径字典
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
HashThread: 哈希检查线程实例
|
||||||
|
"""
|
||||||
|
return HashThread(mode, install_paths, PLUGIN_HASH, self.main_window.installed_status, self.main_window)
|
||||||
|
|
||||||
|
def after_hash_compare(self):
|
||||||
|
"""进行安装后哈希比较"""
|
||||||
|
# 禁用窗口已在安装流程开始时完成
|
||||||
|
|
||||||
|
# 检查是否处于离线模式
|
||||||
|
is_offline = False
|
||||||
|
if hasattr(self.main_window, 'offline_mode_manager'):
|
||||||
|
is_offline = self.main_window.offline_mode_manager.is_in_offline_mode()
|
||||||
|
|
||||||
|
self.main_window.hash_msg_box = self.main_window.hash_manager.hash_pop_window(check_type="after", is_offline=is_offline)
|
||||||
|
|
||||||
|
install_paths = self.main_window.download_manager.get_install_paths()
|
||||||
|
|
||||||
|
self.main_window.hash_thread = self.create_hash_thread("after", install_paths)
|
||||||
|
self.main_window.hash_thread.after_finished.connect(self.on_after_hash_finished)
|
||||||
|
self.main_window.hash_thread.start()
|
||||||
|
|
||||||
|
def on_after_hash_finished(self, result):
|
||||||
|
"""哈希比较完成后的处理
|
||||||
|
|
||||||
|
Args:
|
||||||
|
result: 哈希比较结果
|
||||||
|
"""
|
||||||
|
# 确保哈希检查窗口关闭,无论是否还在显示
|
||||||
|
if self.main_window.hash_msg_box:
|
||||||
|
try:
|
||||||
|
if self.main_window.hash_msg_box.isVisible():
|
||||||
|
self.main_window.hash_msg_box.close()
|
||||||
|
else:
|
||||||
|
# 如果窗口已经不可见但没有关闭,也要尝试关闭
|
||||||
|
self.main_window.hash_msg_box.close()
|
||||||
|
except:
|
||||||
|
pass # 忽略任何关闭窗口时的错误
|
||||||
|
self.main_window.hash_msg_box = None
|
||||||
|
|
||||||
|
if not result["passed"]:
|
||||||
|
# 启用窗口以显示错误消息
|
||||||
|
self.main_window.setEnabled(True)
|
||||||
|
|
||||||
|
game = result.get("game", "未知游戏")
|
||||||
|
message = result.get("message", "发生未知错误。")
|
||||||
|
msg_box = QMessageBox.critical(
|
||||||
|
self.main_window,
|
||||||
|
f"文件校验失败 - {APP_NAME}",
|
||||||
|
message,
|
||||||
|
QMessageBox.StandardButton.Ok,
|
||||||
|
)
|
||||||
|
|
||||||
|
# 恢复窗口状态
|
||||||
|
self.main_window.setEnabled(True)
|
||||||
|
self.main_window.ui.start_install_text.setText("开始安装")
|
||||||
|
|
||||||
|
# 添加短暂延迟确保UI更新
|
||||||
|
QTimer.singleShot(100, self.main_window.show_result)
|
||||||
|
|
||||||
|
def on_offline_pre_hash_finished(self, updated_status, game_dirs):
|
||||||
|
"""离线模式下的哈希预检查完成处理
|
||||||
|
|
||||||
|
Args:
|
||||||
|
updated_status: 更新后的安装状态
|
||||||
|
game_dirs: 识别到的游戏目录
|
||||||
|
"""
|
||||||
|
# 更新安装状态
|
||||||
|
self.main_window.installed_status = updated_status
|
||||||
|
|
||||||
|
# 关闭哈希检查窗口
|
||||||
|
if self.main_window.hash_msg_box and self.main_window.hash_msg_box.isVisible():
|
||||||
|
self.main_window.hash_msg_box.accept()
|
||||||
|
self.main_window.hash_msg_box = None
|
||||||
|
|
||||||
|
# 重新启用主窗口
|
||||||
|
self.main_window.setEnabled(True)
|
||||||
|
|
||||||
|
# 使用patch_detector检测可安装的游戏
|
||||||
|
already_installed_games, installable_games, disabled_patch_games = self.detect_installable_games(game_dirs)
|
||||||
|
|
||||||
|
debug_mode = self._is_debug_mode()
|
||||||
|
|
||||||
|
status_message = ""
|
||||||
|
if already_installed_games:
|
||||||
|
status_message += f"已安装补丁的游戏:\n{chr(10).join(already_installed_games)}\n\n"
|
||||||
|
|
||||||
|
# 处理禁用补丁的情况
|
||||||
|
if disabled_patch_games:
|
||||||
|
# 构建提示消息
|
||||||
|
disabled_msg = f"检测到以下游戏的补丁已被禁用:\n{chr(10).join(disabled_patch_games)}\n\n是否要启用这些补丁?"
|
||||||
|
|
||||||
|
from PySide6 import QtWidgets
|
||||||
|
reply = QtWidgets.QMessageBox.question(
|
||||||
|
self.main_window,
|
||||||
|
f"检测到禁用补丁 - {APP_NAME}",
|
||||||
|
disabled_msg,
|
||||||
|
QtWidgets.QMessageBox.StandardButton.Yes | QtWidgets.QMessageBox.StandardButton.No
|
||||||
|
)
|
||||||
|
|
||||||
|
if reply == QtWidgets.QMessageBox.StandardButton.Yes:
|
||||||
|
# 用户选择启用补丁
|
||||||
|
if debug_mode:
|
||||||
|
logger.debug(f"DEBUG: 用户选择启用被禁用的补丁")
|
||||||
|
|
||||||
|
# 为每个禁用的游戏创建目录映射
|
||||||
|
disabled_game_dirs = {game: game_dirs[game] for game in disabled_patch_games}
|
||||||
|
|
||||||
|
# 批量启用补丁
|
||||||
|
success_count, fail_count, results = self.main_window.patch_manager.batch_toggle_patches(
|
||||||
|
disabled_game_dirs,
|
||||||
|
operation="enable"
|
||||||
|
)
|
||||||
|
|
||||||
|
# 显示启用结果
|
||||||
|
self.main_window.patch_manager.show_toggle_result(success_count, fail_count, results)
|
||||||
|
|
||||||
|
# 更新安装状态
|
||||||
|
for game_version in disabled_patch_games:
|
||||||
|
self.main_window.installed_status[game_version] = True
|
||||||
|
if game_version in installable_games:
|
||||||
|
installable_games.remove(game_version)
|
||||||
|
if game_version not in already_installed_games:
|
||||||
|
already_installed_games.append(game_version)
|
||||||
|
else:
|
||||||
|
if debug_mode:
|
||||||
|
logger.info(f"DEBUG: 用户选择不启用被禁用的补丁,这些游戏将被添加到可安装列表")
|
||||||
|
# 用户选择不启用,将这些游戏视为可以安装补丁
|
||||||
|
installable_games.extend(disabled_patch_games)
|
||||||
|
|
||||||
|
# 更新status_message
|
||||||
|
if disabled_patch_games:
|
||||||
|
status_message += f"禁用补丁的游戏:\n{chr(10).join(disabled_patch_games)}\n\n"
|
||||||
|
|
||||||
|
if not installable_games:
|
||||||
|
# 没有可安装的游戏,显示信息并重置UI
|
||||||
|
if already_installed_games:
|
||||||
|
# 有已安装的游戏,显示已安装信息
|
||||||
|
QMessageBox.information(
|
||||||
|
self.main_window,
|
||||||
|
f"信息 - {APP_NAME}",
|
||||||
|
f"\n所有游戏已安装补丁,无需重复安装。\n\n{status_message}",
|
||||||
|
QMessageBox.StandardButton.Ok,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
# 没有已安装的游戏,可能是未检测到游戏
|
||||||
|
QMessageBox.warning(
|
||||||
|
self.main_window,
|
||||||
|
f"警告 - {APP_NAME}",
|
||||||
|
"\n未检测到任何需要安装补丁的游戏。\n\n请确保游戏文件夹位于选择的目录中。\n",
|
||||||
|
QMessageBox.StandardButton.Ok,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.main_window.ui.start_install_text.setText("开始安装")
|
||||||
|
return
|
||||||
|
|
||||||
|
# 显示游戏选择对话框
|
||||||
|
from PySide6 import QtWidgets
|
||||||
|
dialog = QtWidgets.QDialog(self.main_window)
|
||||||
|
dialog.setWindowTitle(f"选择要安装的游戏 - {APP_NAME}")
|
||||||
|
dialog.setMinimumWidth(300)
|
||||||
|
|
||||||
|
layout = QtWidgets.QVBoxLayout()
|
||||||
|
|
||||||
|
# 添加说明标签
|
||||||
|
label = QtWidgets.QLabel("请选择要安装补丁的游戏:")
|
||||||
|
layout.addWidget(label)
|
||||||
|
|
||||||
|
# 添加游戏列表
|
||||||
|
list_widget = QtWidgets.QListWidget()
|
||||||
|
list_widget.setSelectionMode(QtWidgets.QAbstractItemView.SelectionMode.MultiSelection)
|
||||||
|
|
||||||
|
for game in installable_games:
|
||||||
|
item = QtWidgets.QListWidgetItem(game)
|
||||||
|
list_widget.addItem(item)
|
||||||
|
item.setSelected(True) # 默认全选
|
||||||
|
|
||||||
|
layout.addWidget(list_widget)
|
||||||
|
|
||||||
|
# 添加按钮
|
||||||
|
button_box = QtWidgets.QDialogButtonBox(
|
||||||
|
QtWidgets.QDialogButtonBox.StandardButton.Ok |
|
||||||
|
QtWidgets.QDialogButtonBox.StandardButton.Cancel
|
||||||
|
)
|
||||||
|
button_box.accepted.connect(dialog.accept)
|
||||||
|
button_box.rejected.connect(dialog.reject)
|
||||||
|
layout.addWidget(button_box)
|
||||||
|
|
||||||
|
dialog.setLayout(layout)
|
||||||
|
|
||||||
|
# 显示对话框
|
||||||
|
result = dialog.exec()
|
||||||
|
if result != QtWidgets.QDialog.DialogCode.Accepted or list_widget.selectedItems() == []:
|
||||||
|
self.main_window.ui.start_install_text.setText("开始安装")
|
||||||
|
return
|
||||||
|
|
||||||
|
# 获取用户选择的游戏
|
||||||
|
selected_games = [item.text() for item in list_widget.selectedItems()]
|
||||||
|
|
||||||
|
# 开始安装
|
||||||
|
if debug_mode:
|
||||||
|
logger.debug(f"DEBUG: 用户选择了以下游戏进行安装: {selected_games}")
|
||||||
|
|
||||||
|
# 调用离线模式管理器安装补丁
|
||||||
|
self.main_window.offline_mode_manager.install_offline_patches(selected_games)
|
||||||
@@ -3,23 +3,36 @@ import shutil
|
|||||||
import traceback
|
import traceback
|
||||||
from PySide6.QtWidgets import QMessageBox
|
from PySide6.QtWidgets import QMessageBox
|
||||||
from utils.logger import setup_logger
|
from utils.logger import setup_logger
|
||||||
|
from data.config import APP_NAME
|
||||||
|
from utils import msgbox_frame
|
||||||
|
|
||||||
class PatchManager:
|
class PatchManager:
|
||||||
"""补丁管理器,用于处理补丁的安装和卸载"""
|
"""补丁管理器,用于处理补丁的安装和卸载"""
|
||||||
|
|
||||||
def __init__(self, app_name, game_info, debug_manager=None):
|
def __init__(self, app_name, game_info, debug_manager=None, main_window=None):
|
||||||
"""初始化补丁管理器
|
"""初始化补丁管理器
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
app_name: 应用程序名称,用于显示消息框标题
|
app_name: 应用程序名称,用于显示消息框标题
|
||||||
game_info: 游戏信息字典,包含各版本的安装路径和可执行文件名
|
game_info: 游戏信息字典,包含各版本的安装路径和可执行文件名
|
||||||
debug_manager: 调试管理器实例,用于输出调试信息
|
debug_manager: 调试管理器实例,用于输出调试信息
|
||||||
|
main_window: 主窗口实例,用于访问UI和状态
|
||||||
"""
|
"""
|
||||||
self.app_name = app_name
|
self.app_name = app_name
|
||||||
self.game_info = game_info
|
self.game_info = game_info
|
||||||
self.debug_manager = debug_manager
|
self.debug_manager = debug_manager
|
||||||
|
self.main_window = main_window # 添加main_window属性
|
||||||
self.installed_status = {} # 游戏版本的安装状态
|
self.installed_status = {} # 游戏版本的安装状态
|
||||||
self.logger = setup_logger("patch_manager")
|
self.logger = setup_logger("patch_manager")
|
||||||
|
self.patch_detector = None # 将在main_window初始化后设置
|
||||||
|
|
||||||
|
def set_patch_detector(self, patch_detector):
|
||||||
|
"""设置补丁检测器实例
|
||||||
|
|
||||||
|
Args:
|
||||||
|
patch_detector: 补丁检测器实例
|
||||||
|
"""
|
||||||
|
self.patch_detector = patch_detector
|
||||||
|
|
||||||
def _is_debug_mode(self):
|
def _is_debug_mode(self):
|
||||||
"""检查是否处于调试模式
|
"""检查是否处于调试模式
|
||||||
@@ -331,7 +344,7 @@ class PatchManager:
|
|||||||
)
|
)
|
||||||
|
|
||||||
def check_patch_installed(self, game_dir, game_version):
|
def check_patch_installed(self, game_dir, game_version):
|
||||||
"""检查游戏是否已安装补丁
|
"""检查游戏是否已安装补丁(调用patch_detector)
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
game_dir: 游戏目录路径
|
game_dir: 游戏目录路径
|
||||||
@@ -340,6 +353,10 @@ class PatchManager:
|
|||||||
Returns:
|
Returns:
|
||||||
bool: 如果已安装补丁或有被禁用的补丁文件返回True,否则返回False
|
bool: 如果已安装补丁或有被禁用的补丁文件返回True,否则返回False
|
||||||
"""
|
"""
|
||||||
|
if self.patch_detector:
|
||||||
|
return self.patch_detector.check_patch_installed(game_dir, game_version)
|
||||||
|
|
||||||
|
# 如果patch_detector未设置,使用原始逻辑(应该不会执行到这里)
|
||||||
debug_mode = self._is_debug_mode()
|
debug_mode = self._is_debug_mode()
|
||||||
|
|
||||||
if game_version not in self.game_info:
|
if game_version not in self.game_info:
|
||||||
@@ -425,7 +442,7 @@ class PatchManager:
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
def check_patch_disabled(self, game_dir, game_version):
|
def check_patch_disabled(self, game_dir, game_version):
|
||||||
"""检查游戏的补丁是否已被禁用
|
"""检查游戏的补丁是否已被禁用(调用patch_detector)
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
game_dir: 游戏目录路径
|
game_dir: 游戏目录路径
|
||||||
@@ -435,6 +452,10 @@ class PatchManager:
|
|||||||
bool: 如果补丁被禁用返回True,否则返回False
|
bool: 如果补丁被禁用返回True,否则返回False
|
||||||
str: 禁用的补丁文件路径,如果没有禁用返回None
|
str: 禁用的补丁文件路径,如果没有禁用返回None
|
||||||
"""
|
"""
|
||||||
|
if self.patch_detector:
|
||||||
|
return self.patch_detector.check_patch_disabled(game_dir, game_version)
|
||||||
|
|
||||||
|
# 如果patch_detector未设置,使用原始逻辑(应该不会执行到这里)
|
||||||
debug_mode = self._is_debug_mode()
|
debug_mode = self._is_debug_mode()
|
||||||
|
|
||||||
if game_version not in self.game_info:
|
if game_version not in self.game_info:
|
||||||
@@ -744,3 +765,85 @@ class PatchManager:
|
|||||||
result_text,
|
result_text,
|
||||||
QMessageBox.StandardButton.Ok,
|
QMessageBox.StandardButton.Ok,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def show_result(self):
|
||||||
|
"""显示安装结果,区分不同情况"""
|
||||||
|
# 获取当前安装状态
|
||||||
|
installed_versions = [] # 成功安装的版本
|
||||||
|
skipped_versions = [] # 已有补丁跳过的版本
|
||||||
|
failed_versions = [] # 安装失败的版本
|
||||||
|
not_found_versions = [] # 未找到的版本
|
||||||
|
|
||||||
|
# 获取所有游戏版本路径
|
||||||
|
install_paths = self.main_window.download_manager.get_install_paths() if hasattr(self.main_window.download_manager, "get_install_paths") else {}
|
||||||
|
|
||||||
|
# 检查是否处于离线模式
|
||||||
|
is_offline_mode = False
|
||||||
|
if hasattr(self.main_window, 'offline_mode_manager'):
|
||||||
|
is_offline_mode = self.main_window.offline_mode_manager.is_in_offline_mode()
|
||||||
|
|
||||||
|
# 获取本次实际安装的游戏列表
|
||||||
|
installed_games = []
|
||||||
|
|
||||||
|
# 在线模式下使用download_queue_history
|
||||||
|
if hasattr(self.main_window, 'download_queue_history') and self.main_window.download_queue_history:
|
||||||
|
installed_games = self.main_window.download_queue_history
|
||||||
|
|
||||||
|
# 离线模式下使用offline_mode_manager.installed_games
|
||||||
|
if is_offline_mode and hasattr(self.main_window.offline_mode_manager, 'installed_games'):
|
||||||
|
installed_games = self.main_window.offline_mode_manager.installed_games
|
||||||
|
|
||||||
|
debug_mode = self._is_debug_mode()
|
||||||
|
|
||||||
|
if debug_mode:
|
||||||
|
self.logger.debug(f"DEBUG: 显示安装结果,离线模式: {is_offline_mode}")
|
||||||
|
self.logger.debug(f"DEBUG: 本次安装的游戏: {installed_games}")
|
||||||
|
|
||||||
|
for game_version, is_installed in self.main_window.installed_status.items():
|
||||||
|
# 只处理install_paths中存在的游戏版本
|
||||||
|
if game_version in install_paths:
|
||||||
|
path = install_paths[game_version]
|
||||||
|
|
||||||
|
# 检查游戏是否存在但未通过本次安装补丁
|
||||||
|
if is_installed:
|
||||||
|
# 游戏已安装补丁
|
||||||
|
if game_version in installed_games:
|
||||||
|
# 本次成功安装
|
||||||
|
installed_versions.append(game_version)
|
||||||
|
else:
|
||||||
|
# 已有补丁,被跳过下载
|
||||||
|
skipped_versions.append(game_version)
|
||||||
|
else:
|
||||||
|
# 游戏未安装补丁
|
||||||
|
if os.path.exists(path):
|
||||||
|
# 游戏文件夹存在,但安装失败
|
||||||
|
failed_versions.append(game_version)
|
||||||
|
else:
|
||||||
|
# 游戏文件夹不存在
|
||||||
|
not_found_versions.append(game_version)
|
||||||
|
|
||||||
|
# 构建结果信息
|
||||||
|
result_text = f"\n安装结果:\n"
|
||||||
|
|
||||||
|
# 总数统计 - 只显示本次实际安装的数量
|
||||||
|
total_installed = len(installed_versions)
|
||||||
|
total_failed = len(failed_versions)
|
||||||
|
|
||||||
|
result_text += f"安装成功:{total_installed} 个 安装失败:{total_failed} 个\n\n"
|
||||||
|
|
||||||
|
# 详细列表
|
||||||
|
if installed_versions:
|
||||||
|
result_text += f"【成功安装】:\n{chr(10).join(installed_versions)}\n\n"
|
||||||
|
|
||||||
|
if failed_versions:
|
||||||
|
result_text += f"【安装失败】:\n{chr(10).join(failed_versions)}\n\n"
|
||||||
|
|
||||||
|
if not_found_versions:
|
||||||
|
# 只有在真正检测到了游戏但未安装补丁时才显示
|
||||||
|
result_text += f"【尚未安装补丁的游戏】:\n{chr(10).join(not_found_versions)}\n"
|
||||||
|
|
||||||
|
QMessageBox.information(
|
||||||
|
self.main_window,
|
||||||
|
f"安装完成 - {APP_NAME}",
|
||||||
|
result_text
|
||||||
|
)
|
||||||
@@ -720,7 +720,7 @@ class UIManager:
|
|||||||
log_datetime = "-".join(os.path.basename(latest_log)[4:-4].split("-")[:2])
|
log_datetime = "-".join(os.path.basename(latest_log)[4:-4].split("-")[:2])
|
||||||
log_date = log_datetime.split("-")[0]
|
log_date = log_datetime.split("-")[0]
|
||||||
log_time = log_datetime.split("-")[1] if "-" in log_datetime else "未知时间"
|
log_time = log_datetime.split("-")[1] if "-" in log_datetime else "未知时间"
|
||||||
date_info = f"日期: {log_date[:4]}-{log_date[4:6]}-{log_date[6:]} "
|
date_info = f"日期: {log_date[:4]}-{log_date[4:6]}-{log_date[6:]}"
|
||||||
time_info = f"时间: {log_time[:2]}:{log_time[2:4]}:{log_time[4:]}"
|
time_info = f"时间: {log_time[:2]}:{log_time[2:4]}:{log_time[4:]}"
|
||||||
except:
|
except:
|
||||||
date_info = "日期未知 "
|
date_info = "日期未知 "
|
||||||
@@ -780,8 +780,8 @@ class UIManager:
|
|||||||
"""手动删除软件添加的hosts条目"""
|
"""手动删除软件添加的hosts条目"""
|
||||||
if hasattr(self.main_window, 'download_manager') and hasattr(self.main_window.download_manager, 'hosts_manager'):
|
if hasattr(self.main_window, 'download_manager') and hasattr(self.main_window.download_manager, 'hosts_manager'):
|
||||||
try:
|
try:
|
||||||
# 调用清理hosts条目的方法
|
# 调用清理hosts条目的方法,强制清理即使禁用了自动还原
|
||||||
result = self.main_window.download_manager.hosts_manager.check_and_clean_all_entries()
|
result = self.main_window.download_manager.hosts_manager.check_and_clean_all_entries(force_clean=True)
|
||||||
|
|
||||||
if result:
|
if result:
|
||||||
msg_box = self._create_message_box("成功", "\n已成功清理软件添加的hosts条目。\n")
|
msg_box = self._create_message_box("成功", "\n已成功清理软件添加的hosts条目。\n")
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ from PySide6.QtCore import QTimer, Qt, QPoint, QRect, QSize
|
|||||||
from PySide6.QtWidgets import QMainWindow, QMessageBox, QGraphicsOpacityEffect, QGraphicsColorizeEffect
|
from PySide6.QtWidgets import QMainWindow, QMessageBox, QGraphicsOpacityEffect, QGraphicsColorizeEffect
|
||||||
from PySide6.QtGui import QPalette, QColor, QPainterPath, QRegion, QFont
|
from PySide6.QtGui import QPalette, QColor, QPainterPath, QRegion, QFont
|
||||||
from PySide6.QtGui import QAction # Added for menu actions
|
from PySide6.QtGui import QAction # Added for menu actions
|
||||||
|
from PySide6.QtWidgets import QDialog, QVBoxLayout, QHBoxLayout, QProgressBar, QLabel # Added for progress window
|
||||||
|
|
||||||
from ui.Ui_install import Ui_MainWindows
|
from ui.Ui_install import Ui_MainWindows
|
||||||
from data.config import (
|
from data.config import (
|
||||||
@@ -26,7 +27,7 @@ from workers import (
|
|||||||
)
|
)
|
||||||
from core import (
|
from core import (
|
||||||
MultiStageAnimations, UIManager, DownloadManager, DebugManager,
|
MultiStageAnimations, UIManager, DownloadManager, DebugManager,
|
||||||
WindowManager, GameDetector, PatchManager, ConfigManager
|
WindowManager, GameDetector, PatchManager, ConfigManager, PatchDetector
|
||||||
)
|
)
|
||||||
from core.ipv6_manager import IPv6Manager
|
from core.ipv6_manager import IPv6Manager
|
||||||
from handlers import PatchToggleHandler, UninstallHandler
|
from handlers import PatchToggleHandler, UninstallHandler
|
||||||
@@ -79,16 +80,22 @@ class MainWindow(QMainWindow):
|
|||||||
# 5. 初始化其他管理器
|
# 5. 初始化其他管理器
|
||||||
self.config_manager = ConfigManager(APP_NAME, CONFIG_URL, UA, self.debug_manager)
|
self.config_manager = ConfigManager(APP_NAME, CONFIG_URL, UA, self.debug_manager)
|
||||||
self.game_detector = GameDetector(GAME_INFO, self.debug_manager)
|
self.game_detector = GameDetector(GAME_INFO, self.debug_manager)
|
||||||
self.patch_manager = PatchManager(APP_NAME, GAME_INFO, self.debug_manager)
|
self.patch_manager = PatchManager(APP_NAME, GAME_INFO, self.debug_manager, self)
|
||||||
|
|
||||||
# 6. 初始化离线模式管理器
|
# 6. 初始化补丁检测模块
|
||||||
|
self.patch_detector = PatchDetector(self)
|
||||||
|
|
||||||
|
# 7. 设置补丁检测器到补丁管理器
|
||||||
|
self.patch_manager.set_patch_detector(self.patch_detector)
|
||||||
|
|
||||||
|
# 8. 初始化离线模式管理器
|
||||||
from core.offline_mode_manager import OfflineModeManager
|
from core.offline_mode_manager import OfflineModeManager
|
||||||
self.offline_mode_manager = OfflineModeManager(self)
|
self.offline_mode_manager = OfflineModeManager(self)
|
||||||
|
|
||||||
# 7. 初始化下载管理器 - 放在最后,因为它可能依赖于其他管理器
|
# 9. 初始化下载管理器 - 放在最后,因为它可能依赖于其他管理器
|
||||||
self.download_manager = DownloadManager(self)
|
self.download_manager = DownloadManager(self)
|
||||||
|
|
||||||
# 8. 初始化功能处理程序
|
# 10. 初始化功能处理程序
|
||||||
self.uninstall_handler = UninstallHandler(self)
|
self.uninstall_handler = UninstallHandler(self)
|
||||||
self.patch_toggle_handler = PatchToggleHandler(self)
|
self.patch_toggle_handler = PatchToggleHandler(self)
|
||||||
|
|
||||||
@@ -325,33 +332,40 @@ class MainWindow(QMainWindow):
|
|||||||
Args:
|
Args:
|
||||||
url: 下载URL
|
url: 下载URL
|
||||||
_7z_path: 7z文件保存路径
|
_7z_path: 7z文件保存路径
|
||||||
game_version: 游戏版本名称
|
game_version: 游戏版本
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
DownloadThread: 下载线程实例
|
DownloadThread: 下载线程实例
|
||||||
"""
|
"""
|
||||||
from workers import DownloadThread
|
return DownloadThread(url, _7z_path, game_version, self)
|
||||||
return DownloadThread(url, _7z_path, game_version, parent=self)
|
|
||||||
|
|
||||||
def create_progress_window(self):
|
def create_progress_window(self):
|
||||||
"""创建下载进度窗口
|
"""创建进度窗口
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
ProgressWindow: 进度窗口实例
|
QDialog: 进度窗口实例
|
||||||
"""
|
"""
|
||||||
return ProgressWindow(self)
|
progress_window = QDialog(self)
|
||||||
|
progress_window.setWindowTitle(f"下载进度 - {APP_NAME}")
|
||||||
|
progress_window.setFixedSize(400, 150)
|
||||||
|
|
||||||
def create_hash_thread(self, mode, install_paths):
|
layout = QVBoxLayout()
|
||||||
"""创建哈希检查线程
|
|
||||||
|
|
||||||
Args:
|
# 添加进度条
|
||||||
mode: 检查模式,"pre"或"after"
|
progress_bar = QProgressBar()
|
||||||
install_paths: 安装路径字典
|
progress_bar.setRange(0, 100)
|
||||||
|
progress_bar.setValue(0)
|
||||||
|
layout.addWidget(progress_bar)
|
||||||
|
|
||||||
Returns:
|
# 添加标签
|
||||||
HashThread: 哈希检查线程实例
|
status_label = QLabel("准备下载...")
|
||||||
"""
|
layout.addWidget(status_label)
|
||||||
return HashThread(mode, install_paths, PLUGIN_HASH, self.installed_status, self)
|
|
||||||
|
progress_window.setLayout(layout)
|
||||||
|
progress_window.progress_bar = progress_bar
|
||||||
|
progress_window.status_label = status_label
|
||||||
|
|
||||||
|
return progress_window
|
||||||
|
|
||||||
def create_extraction_thread(self, _7z_path, game_folder, plugin_path, game_version):
|
def create_extraction_thread(self, _7z_path, game_folder, plugin_path, game_version):
|
||||||
"""创建解压线程
|
"""创建解压线程
|
||||||
@@ -367,120 +381,9 @@ class MainWindow(QMainWindow):
|
|||||||
"""
|
"""
|
||||||
return ExtractionThread(_7z_path, game_folder, plugin_path, game_version, self)
|
return ExtractionThread(_7z_path, game_folder, plugin_path, game_version, self)
|
||||||
|
|
||||||
def after_hash_compare(self):
|
|
||||||
"""进行安装后哈希比较"""
|
|
||||||
# 禁用窗口已在安装流程开始时完成
|
|
||||||
|
|
||||||
# 检查是否处于离线模式
|
|
||||||
is_offline = False
|
|
||||||
if hasattr(self, 'offline_mode_manager'):
|
|
||||||
is_offline = self.offline_mode_manager.is_in_offline_mode()
|
|
||||||
|
|
||||||
self.hash_msg_box = self.hash_manager.hash_pop_window(check_type="after", is_offline=is_offline)
|
|
||||||
|
|
||||||
install_paths = self.download_manager.get_install_paths()
|
|
||||||
|
|
||||||
self.hash_thread = self.create_hash_thread("after", install_paths)
|
|
||||||
self.hash_thread.after_finished.connect(self.on_after_hash_finished)
|
|
||||||
self.hash_thread.start()
|
|
||||||
|
|
||||||
def on_after_hash_finished(self, result):
|
|
||||||
"""哈希比较完成后的处理
|
|
||||||
|
|
||||||
Args:
|
|
||||||
result: 哈希比较结果
|
|
||||||
"""
|
|
||||||
# 确保哈希检查窗口关闭,无论是否还在显示
|
|
||||||
if self.hash_msg_box:
|
|
||||||
try:
|
|
||||||
if self.hash_msg_box.isVisible():
|
|
||||||
self.hash_msg_box.close()
|
|
||||||
else:
|
|
||||||
# 如果窗口已经不可见但没有关闭,也要尝试关闭
|
|
||||||
self.hash_msg_box.close()
|
|
||||||
except:
|
|
||||||
pass # 忽略任何关闭窗口时的错误
|
|
||||||
self.hash_msg_box = None
|
|
||||||
|
|
||||||
if not result["passed"]:
|
|
||||||
# 启用窗口以显示错误消息
|
|
||||||
self.setEnabled(True)
|
|
||||||
|
|
||||||
game = result.get("game", "未知游戏")
|
|
||||||
message = result.get("message", "发生未知错误。")
|
|
||||||
msg_box = msgbox_frame(
|
|
||||||
f"文件校验失败 - {APP_NAME}",
|
|
||||||
message,
|
|
||||||
QMessageBox.StandardButton.Ok,
|
|
||||||
)
|
|
||||||
msg_box.exec()
|
|
||||||
|
|
||||||
# 恢复窗口状态
|
|
||||||
self.setEnabled(True)
|
|
||||||
self.ui.start_install_text.setText("开始安装")
|
|
||||||
|
|
||||||
# 添加短暂延迟确保UI更新
|
|
||||||
QTimer.singleShot(100, self.show_result)
|
|
||||||
|
|
||||||
def show_result(self):
|
def show_result(self):
|
||||||
"""显示安装结果,区分不同情况"""
|
"""显示安装结果,调用patch_manager的show_result方法"""
|
||||||
# 获取当前安装状态
|
self.patch_manager.show_result()
|
||||||
installed_versions = [] # 成功安装的版本
|
|
||||||
skipped_versions = [] # 已有补丁跳过的版本
|
|
||||||
failed_versions = [] # 安装失败的版本
|
|
||||||
not_found_versions = [] # 未找到的版本
|
|
||||||
|
|
||||||
# 获取所有游戏版本路径
|
|
||||||
install_paths = self.download_manager.get_install_paths() if hasattr(self.download_manager, "get_install_paths") else {}
|
|
||||||
|
|
||||||
for game_version, is_installed in self.installed_status.items():
|
|
||||||
# 只处理install_paths中存在的游戏版本
|
|
||||||
if game_version in install_paths:
|
|
||||||
path = install_paths[game_version]
|
|
||||||
|
|
||||||
# 检查游戏是否存在但未通过本次安装补丁
|
|
||||||
if is_installed:
|
|
||||||
# 游戏已安装补丁
|
|
||||||
if hasattr(self, 'download_queue_history') and game_version not in self.download_queue_history:
|
|
||||||
# 已有补丁,被跳过下载
|
|
||||||
skipped_versions.append(game_version)
|
|
||||||
else:
|
|
||||||
# 本次成功安装
|
|
||||||
installed_versions.append(game_version)
|
|
||||||
else:
|
|
||||||
# 游戏未安装补丁
|
|
||||||
if os.path.exists(path):
|
|
||||||
# 游戏文件夹存在,但安装失败
|
|
||||||
failed_versions.append(game_version)
|
|
||||||
else:
|
|
||||||
# 游戏文件夹不存在
|
|
||||||
not_found_versions.append(game_version)
|
|
||||||
|
|
||||||
# 构建结果信息
|
|
||||||
result_text = f"\n安装结果:\n"
|
|
||||||
|
|
||||||
# 总数统计 - 不再显示已跳过的数量
|
|
||||||
total_installed = len(installed_versions)
|
|
||||||
total_failed = len(failed_versions)
|
|
||||||
|
|
||||||
result_text += f"安装成功:{total_installed} 个 安装失败:{total_failed} 个\n\n"
|
|
||||||
|
|
||||||
# 详细列表
|
|
||||||
if installed_versions:
|
|
||||||
result_text += f"【成功安装】:\n{chr(10).join(installed_versions)}\n\n"
|
|
||||||
|
|
||||||
if failed_versions:
|
|
||||||
result_text += f"【安装失败】:\n{chr(10).join(failed_versions)}\n\n"
|
|
||||||
|
|
||||||
if not_found_versions:
|
|
||||||
# 只有在真正检测到了游戏但未安装补丁时才显示
|
|
||||||
result_text += f"【尚未安装补丁的游戏】:\n{chr(10).join(not_found_versions)}\n"
|
|
||||||
|
|
||||||
QMessageBox.information(
|
|
||||||
self,
|
|
||||||
f"安装完成 - {APP_NAME}",
|
|
||||||
result_text
|
|
||||||
)
|
|
||||||
|
|
||||||
def closeEvent(self, event):
|
def closeEvent(self, event):
|
||||||
"""窗口关闭事件处理
|
"""窗口关闭事件处理
|
||||||
@@ -651,64 +554,18 @@ class MainWindow(QMainWindow):
|
|||||||
self.ui.start_install_text.setText("开始安装")
|
self.ui.start_install_text.setText("开始安装")
|
||||||
return
|
return
|
||||||
|
|
||||||
# 显示游戏选择对话框
|
# 显示文件检验窗口
|
||||||
dialog = QtWidgets.QDialog(self)
|
self.hash_msg_box = self.hash_manager.hash_pop_window(check_type="pre", is_offline=True)
|
||||||
dialog.setWindowTitle("选择要安装的游戏")
|
|
||||||
dialog.resize(400, 300)
|
|
||||||
|
|
||||||
layout = QtWidgets.QVBoxLayout(dialog)
|
# 获取安装路径
|
||||||
|
install_paths = self.download_manager.get_install_paths()
|
||||||
|
|
||||||
# 添加"选择要安装的游戏"标签
|
# 创建并启动哈希线程进行预检查
|
||||||
title_label = QtWidgets.QLabel("选择要安装的游戏", dialog)
|
self.hash_thread = self.patch_detector.create_hash_thread("pre", install_paths)
|
||||||
title_label.setFont(QFont(title_label.font().family(), title_label.font().pointSize(), QFont.Bold))
|
self.hash_thread.pre_finished.connect(
|
||||||
layout.addWidget(title_label)
|
lambda updated_status: self.patch_detector.on_offline_pre_hash_finished(updated_status, game_dirs)
|
||||||
|
)
|
||||||
# 添加游戏列表控件
|
self.hash_thread.start()
|
||||||
list_widget = QtWidgets.QListWidget(dialog)
|
|
||||||
list_widget.setSelectionMode(QtWidgets.QAbstractItemView.SelectionMode.ExtendedSelection) # 允许多选
|
|
||||||
for game_version in game_dirs.keys():
|
|
||||||
list_widget.addItem(game_version)
|
|
||||||
# 默认选中所有项目
|
|
||||||
list_widget.item(list_widget.count() - 1).setSelected(True)
|
|
||||||
layout.addWidget(list_widget)
|
|
||||||
|
|
||||||
# 添加全选按钮
|
|
||||||
select_all_btn = QtWidgets.QPushButton("全选", dialog)
|
|
||||||
select_all_btn.clicked.connect(lambda: list_widget.selectAll())
|
|
||||||
layout.addWidget(select_all_btn)
|
|
||||||
|
|
||||||
# 添加确定和取消按钮
|
|
||||||
buttons_layout = QtWidgets.QHBoxLayout()
|
|
||||||
ok_button = QtWidgets.QPushButton("确定", dialog)
|
|
||||||
cancel_button = QtWidgets.QPushButton("取消", dialog)
|
|
||||||
buttons_layout.addWidget(ok_button)
|
|
||||||
buttons_layout.addWidget(cancel_button)
|
|
||||||
layout.addLayout(buttons_layout)
|
|
||||||
|
|
||||||
# 连接按钮事件
|
|
||||||
ok_button.clicked.connect(dialog.accept)
|
|
||||||
cancel_button.clicked.connect(dialog.reject)
|
|
||||||
|
|
||||||
# 显示对话框
|
|
||||||
if dialog.exec() == QtWidgets.QDialog.DialogCode.Accepted:
|
|
||||||
# 获取选择的游戏
|
|
||||||
selected_games = [item.text() for item in list_widget.selectedItems()]
|
|
||||||
|
|
||||||
if selected_games:
|
|
||||||
# 使用离线模式管理器进行安装
|
|
||||||
self.offline_mode_manager.install_offline_patches(selected_games)
|
|
||||||
else:
|
|
||||||
QtWidgets.QMessageBox.information(
|
|
||||||
self,
|
|
||||||
f"通知 - {APP_NAME}",
|
|
||||||
"\n未选择任何游戏,安装已取消。\n"
|
|
||||||
)
|
|
||||||
self.setEnabled(True)
|
|
||||||
self.ui.start_install_text.setText("开始安装")
|
|
||||||
else:
|
|
||||||
# 用户取消了选择
|
|
||||||
self.setEnabled(True)
|
|
||||||
self.ui.start_install_text.setText("开始安装")
|
|
||||||
else:
|
else:
|
||||||
# 在线模式下,检查版本是否过低
|
# 在线模式下,检查版本是否过低
|
||||||
if hasattr(self, 'version_warning') and self.version_warning:
|
if hasattr(self, 'version_warning') and self.version_warning:
|
||||||
@@ -723,6 +580,8 @@ class MainWindow(QMainWindow):
|
|||||||
# 版本正常,使用原有的下载流程
|
# 版本正常,使用原有的下载流程
|
||||||
self.download_manager.file_dialog()
|
self.download_manager.file_dialog()
|
||||||
|
|
||||||
|
# 移除on_offline_pre_hash_finished方法
|
||||||
|
|
||||||
def check_and_set_offline_mode(self):
|
def check_and_set_offline_mode(self):
|
||||||
"""检查是否有离线补丁文件,如果有则自动启用离线模式
|
"""检查是否有离线补丁文件,如果有则自动启用离线模式
|
||||||
|
|
||||||
|
|||||||
@@ -567,14 +567,17 @@ class HostsManager:
|
|||||||
self.auto_restore_disabled = auto_restore_disabled
|
self.auto_restore_disabled = auto_restore_disabled
|
||||||
return auto_restore_disabled
|
return auto_restore_disabled
|
||||||
|
|
||||||
def check_and_clean_all_entries(self):
|
def check_and_clean_all_entries(self, force_clean=False):
|
||||||
"""检查并清理所有由本应用程序添加的hosts记录
|
"""检查并清理所有由本应用程序添加的hosts记录
|
||||||
|
|
||||||
|
Args:
|
||||||
|
force_clean: 是否强制清理,即使禁用了自动还原
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
bool: 清理是否成功
|
bool: 清理是否成功
|
||||||
"""
|
"""
|
||||||
# 如果禁用了自动还原,则不执行清理操作
|
# 如果禁用了自动还原,且不是强制清理,则不执行清理操作
|
||||||
if self.is_auto_restore_disabled():
|
if self.is_auto_restore_disabled() and not force_clean:
|
||||||
logger.info("已禁用自动还原hosts,跳过清理操作")
|
logger.info("已禁用自动还原hosts,跳过清理操作")
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ def censor_url(text):
|
|||||||
return text # 直接返回原始文本,不做任何隐藏
|
return text # 直接返回原始文本,不做任何隐藏
|
||||||
|
|
||||||
# 以下是原始代码,现在被注释掉
|
# 以下是原始代码,现在被注释掉
|
||||||
'''
|
r'''
|
||||||
# 匹配URL并替换为固定文本
|
# 匹配URL并替换为固定文本
|
||||||
url_pattern = re.compile(r'https?://[^\s/$.?#].[^\s]*')
|
url_pattern = re.compile(r'https?://[^\s/$.?#].[^\s]*')
|
||||||
censored = url_pattern.sub('***URL protection***', text)
|
censored = url_pattern.sub('***URL protection***', text)
|
||||||
|
|||||||
Reference in New Issue
Block a user