mirror of
https://github.com/hyb-oyqq/FRAISEMOE-Addons-Installer-NEXT.git
synced 2025-12-16 20:10:28 +00:00
feat(core): 增强离线模式支持和版本管理
- 在主窗口中添加离线模式管理器,支持自动切换到离线模式。 - 更新下载管理器以处理离线模式下的下载逻辑,确保用户体验流畅。 - 添加版本警告机制,提示用户在版本过低时的操作选项。 - 优化配置管理器,确保在离线模式下仍可使用相关功能。 - 更新UI管理器以反映当前工作模式,提升用户界面友好性。
This commit is contained in:
7
.gitignore
vendored
7
.gitignore
vendored
@@ -173,4 +173,9 @@ cython_debug/
|
||||
nuitka-crash-report.xml
|
||||
build.bat
|
||||
log.txt
|
||||
result.csv
|
||||
result.csv
|
||||
after.7z
|
||||
vol.1.7z
|
||||
vol.2.7z
|
||||
vol.3.7z
|
||||
vol.4.7z
|
||||
|
||||
@@ -85,16 +85,34 @@ class ConfigManager:
|
||||
# 记录错误信息,用于按钮点击时显示
|
||||
if error_message == "update_required":
|
||||
self.last_error_message = "update_required"
|
||||
msg_box = msgbox_frame(
|
||||
f"更新提示 - {self.app_name}",
|
||||
"\n当前版本过低,请及时更新。\n",
|
||||
QMessageBox.StandardButton.Ok,
|
||||
)
|
||||
msg_box.exec()
|
||||
# 在浏览器中打开项目主页
|
||||
webbrowser.open("https://github.com/hyb-oyqq/FRAISEMOE-Addons-Installer-NEXT/")
|
||||
# 版本过低,应当显示"无法安装"
|
||||
return {"action": "disable_button", "then": "exit"}
|
||||
|
||||
# 检查是否处于离线模式
|
||||
is_offline_mode = False
|
||||
if hasattr(self.debug_manager, 'main_window') and hasattr(self.debug_manager.main_window, 'offline_mode_manager'):
|
||||
is_offline_mode = self.debug_manager.main_window.offline_mode_manager.is_in_offline_mode()
|
||||
|
||||
if is_offline_mode:
|
||||
# 离线模式下只显示提示,不禁用开始安装按钮
|
||||
msg_box = msgbox_frame(
|
||||
f"更新提示 - {self.app_name}",
|
||||
"\n当前版本过低,请及时更新。\n在离线模式下,您仍可使用禁用/启用补丁、卸载补丁和离线安装功能。\n",
|
||||
QMessageBox.StandardButton.Ok,
|
||||
)
|
||||
msg_box.exec()
|
||||
# 移除在浏览器中打开项目主页的代码
|
||||
# 离线模式下版本过低,仍然允许使用安装按钮
|
||||
return {"action": "enable_button"}
|
||||
else:
|
||||
# 在线模式下显示强制更新提示
|
||||
msg_box = msgbox_frame(
|
||||
f"更新提示 - {self.app_name}",
|
||||
"\n当前版本过低,请及时更新。\n如需联网下载补丁,请更新到最新版,否则无法下载。\n",
|
||||
QMessageBox.StandardButton.Ok,
|
||||
)
|
||||
msg_box.exec()
|
||||
# 移除在浏览器中打开项目主页的代码
|
||||
# 在线模式下版本过低,但不直接禁用按钮,而是在点击时提示
|
||||
return {"action": "enable_button", "version_warning": True}
|
||||
|
||||
elif "missing_keys" in error_message:
|
||||
self.last_error_message = "missing_keys"
|
||||
@@ -128,8 +146,8 @@ class ConfigManager:
|
||||
)
|
||||
msg_box.exec()
|
||||
|
||||
# 网络错误时应当显示"无法安装"
|
||||
return {"action": "disable_button"}
|
||||
# 网络错误时仍然允许使用按钮,用户可以尝试离线模式
|
||||
return {"action": "enable_button"}
|
||||
else:
|
||||
self.cloud_config = data
|
||||
# 标记配置有效
|
||||
@@ -139,10 +157,36 @@ class ConfigManager:
|
||||
|
||||
if debug_mode:
|
||||
print("--- Cloud config fetched successfully ---")
|
||||
print(json.dumps(data, indent=2))
|
||||
# 创建一个数据副本,隐藏敏感URL
|
||||
safe_data = self._create_safe_config_for_logging(data)
|
||||
print(json.dumps(safe_data, indent=2))
|
||||
|
||||
# 获取配置成功,允许安装
|
||||
return {"action": "enable_button"}
|
||||
|
||||
def _create_safe_config_for_logging(self, config_data):
|
||||
"""创建用于日志记录的安全配置副本,隐藏敏感URL
|
||||
|
||||
Args:
|
||||
config_data: 原始配置数据
|
||||
|
||||
Returns:
|
||||
dict: 安全的配置数据副本
|
||||
"""
|
||||
if not config_data or not isinstance(config_data, dict):
|
||||
return config_data
|
||||
|
||||
# 创建深拷贝,避免修改原始数据
|
||||
import copy
|
||||
safe_config = copy.deepcopy(config_data)
|
||||
|
||||
# 隐藏敏感URL
|
||||
for key in safe_config:
|
||||
if isinstance(safe_config[key], dict) and "url" in safe_config[key]:
|
||||
# 完全隐藏URL
|
||||
safe_config[key]["url"] = "***URL protection***"
|
||||
|
||||
return safe_config
|
||||
|
||||
def is_config_valid(self):
|
||||
"""检查配置是否有效
|
||||
|
||||
@@ -31,9 +31,20 @@ class DebugManager:
|
||||
Returns:
|
||||
bool: 是否处于调试模式
|
||||
"""
|
||||
if hasattr(self, 'ui_manager') and hasattr(self.ui_manager, 'debug_action'):
|
||||
return self.ui_manager.debug_action.isChecked()
|
||||
return False
|
||||
try:
|
||||
# 首先尝试从UI管理器获取状态
|
||||
if hasattr(self, 'ui_manager') and self.ui_manager and hasattr(self.ui_manager, 'debug_action') and self.ui_manager.debug_action:
|
||||
return self.ui_manager.debug_action.isChecked()
|
||||
|
||||
# 如果UI管理器还没准备好,尝试从配置中获取
|
||||
if hasattr(self.main_window, 'config') and isinstance(self.main_window.config, dict):
|
||||
return self.main_window.config.get('debug_mode', False)
|
||||
|
||||
# 如果以上都不可行,返回False
|
||||
return False
|
||||
except Exception:
|
||||
# 捕获任何异常,默认返回False
|
||||
return False
|
||||
|
||||
def toggle_debug_mode(self, checked):
|
||||
"""切换调试模式
|
||||
@@ -51,6 +62,21 @@ class DebugManager:
|
||||
|
||||
if checked:
|
||||
self.start_logging()
|
||||
|
||||
# 如果启用了调试模式,检查是否需要强制启用离线模式
|
||||
if hasattr(self.main_window, 'offline_mode_manager'):
|
||||
# 检查配置中是否已设置离线模式
|
||||
offline_mode_enabled = self.main_window.config.get("offline_mode", False)
|
||||
|
||||
# 如果配置中已设置离线模式,则在调试模式下强制启用
|
||||
if offline_mode_enabled:
|
||||
print("DEBUG: 调试模式下强制启用离线模式")
|
||||
self.main_window.offline_mode_manager.set_offline_mode(True)
|
||||
|
||||
# 更新UI中的离线模式选项
|
||||
if hasattr(self.ui_manager, 'offline_mode_action') and self.ui_manager.offline_mode_action:
|
||||
self.ui_manager.offline_mode_action.setChecked(True)
|
||||
self.ui_manager.online_mode_action.setChecked(False)
|
||||
else:
|
||||
self.stop_logging()
|
||||
|
||||
|
||||
@@ -100,7 +100,9 @@ class DownloadManager:
|
||||
raise ValueError("未能获取或解析配置数据")
|
||||
|
||||
if self.is_debug_mode():
|
||||
print(f"DEBUG: Parsed JSON data: {json.dumps(config_data, indent=2)}")
|
||||
# 创建安全版本的配置数据用于调试输出
|
||||
safe_config = self._create_safe_config_for_logging(config_data)
|
||||
print(f"DEBUG: Parsed JSON data: {json.dumps(safe_config, indent=2)}")
|
||||
|
||||
urls = {}
|
||||
for i in range(4):
|
||||
@@ -125,7 +127,18 @@ class DownloadManager:
|
||||
raise ValueError(f"配置文件缺少必要的键: {', '.join(missing_original_keys)}")
|
||||
|
||||
if self.is_debug_mode():
|
||||
print(f"DEBUG: Extracted URLs: {urls}")
|
||||
# 创建安全版本的URL字典用于调试输出
|
||||
safe_urls = {}
|
||||
for key, url in urls.items():
|
||||
# 保留域名部分,隐藏路径
|
||||
import re
|
||||
domain_match = re.match(r'(https?://[^/]+)/.*', url)
|
||||
if domain_match:
|
||||
domain = domain_match.group(1)
|
||||
safe_urls[key] = f"{domain}/***隐藏URL路径***"
|
||||
else:
|
||||
safe_urls[key] = "***隐藏URL***"
|
||||
print(f"DEBUG: Extracted URLs: {safe_urls}")
|
||||
print("--- Finished getting download URL successfully ---")
|
||||
return urls
|
||||
|
||||
@@ -158,11 +171,41 @@ class DownloadManager:
|
||||
f"\n配置文件格式异常\n\n【错误信息】:{e}\n",
|
||||
)
|
||||
return {}
|
||||
|
||||
def _create_safe_config_for_logging(self, config_data):
|
||||
"""创建用于日志记录的安全配置副本,隐藏敏感URL
|
||||
|
||||
Args:
|
||||
config_data: 原始配置数据
|
||||
|
||||
Returns:
|
||||
dict: 安全的配置数据副本
|
||||
"""
|
||||
if not config_data or not isinstance(config_data, dict):
|
||||
return config_data
|
||||
|
||||
# 创建深拷贝,避免修改原始数据
|
||||
import copy
|
||||
safe_config = copy.deepcopy(config_data)
|
||||
|
||||
# 隐藏敏感URL
|
||||
for key in safe_config:
|
||||
if isinstance(safe_config[key], dict) and "url" in safe_config[key]:
|
||||
# 完全隐藏URL
|
||||
safe_config[key]["url"] = "***URL protection***"
|
||||
|
||||
return safe_config
|
||||
|
||||
def download_action(self):
|
||||
"""开始下载流程"""
|
||||
self.main_window.download_queue_history = []
|
||||
|
||||
# 清除游戏检测器的目录缓存,确保获取最新的目录状态
|
||||
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()
|
||||
if self.is_debug_mode():
|
||||
print("DEBUG: 已清除游戏目录缓存,确保获取最新状态")
|
||||
|
||||
game_dirs = self.main_window.game_detector.identify_game_directories_improved(self.selected_folder)
|
||||
|
||||
debug_mode = self.is_debug_mode()
|
||||
@@ -182,7 +225,7 @@ class DownloadManager:
|
||||
self.main_window.ui.start_install_text.setText("开始安装")
|
||||
return
|
||||
|
||||
self.main_window.hash_msg_box = self.main_window.hash_manager.hash_pop_window(check_type="pre")
|
||||
self.main_window.hash_msg_box = self.main_window.hash_manager.hash_pop_window(check_type="pre", is_offline=False)
|
||||
|
||||
install_paths = self.get_install_paths()
|
||||
|
||||
@@ -345,22 +388,38 @@ class DownloadManager:
|
||||
|
||||
self.main_window.setEnabled(False)
|
||||
|
||||
config = self.get_download_url()
|
||||
if not config:
|
||||
QtWidgets.QMessageBox.critical(
|
||||
self.main_window, f"错误 - {APP_NAME}", "\n网络状态异常或服务器故障,请重试\n"
|
||||
)
|
||||
self.main_window.setEnabled(True)
|
||||
self.main_window.ui.start_install_text.setText("开始安装")
|
||||
return
|
||||
# 检查是否处于离线模式
|
||||
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()
|
||||
|
||||
if is_offline_mode:
|
||||
if debug_mode:
|
||||
print("DEBUG: 使用离线模式,跳过网络配置获取")
|
||||
self._fill_offline_download_queue(selected_game_dirs)
|
||||
else:
|
||||
config = self.get_download_url()
|
||||
if not config:
|
||||
QtWidgets.QMessageBox.critical(
|
||||
self.main_window, f"错误 - {APP_NAME}", "\n网络状态异常或服务器故障,请重试\n"
|
||||
)
|
||||
self.main_window.setEnabled(True)
|
||||
self.main_window.ui.start_install_text.setText("开始安装")
|
||||
return
|
||||
|
||||
self._fill_download_queue(config, selected_game_dirs)
|
||||
self._fill_download_queue(config, selected_game_dirs)
|
||||
|
||||
if not self.download_queue:
|
||||
self.main_window.after_hash_compare()
|
||||
return
|
||||
|
||||
self._show_cloudflare_option()
|
||||
# 如果是离线模式,直接开始下一个下载任务
|
||||
if is_offline_mode:
|
||||
if debug_mode:
|
||||
print("DEBUG: 离线模式,跳过Cloudflare优化")
|
||||
self.next_download_task()
|
||||
else:
|
||||
self._show_cloudflare_option()
|
||||
|
||||
def _fill_download_queue(self, config, game_dirs):
|
||||
"""填充下载队列
|
||||
@@ -406,6 +465,68 @@ class DownloadManager:
|
||||
self.download_queue.append((url, game_folder, game_version, _7z_path, plugin_path))
|
||||
self.main_window.download_queue_history.append(game_version)
|
||||
|
||||
def _fill_offline_download_queue(self, game_dirs):
|
||||
"""填充离线模式下的下载队列
|
||||
|
||||
Args:
|
||||
game_dirs: 包含游戏文件夹路径的字典
|
||||
"""
|
||||
self.download_queue.clear()
|
||||
|
||||
if not hasattr(self.main_window, 'download_queue_history'):
|
||||
self.main_window.download_queue_history = []
|
||||
|
||||
debug_mode = self.is_debug_mode()
|
||||
if debug_mode:
|
||||
print(f"DEBUG: 填充离线下载队列, 游戏目录: {game_dirs}")
|
||||
|
||||
# 检查是否有离线模式管理器
|
||||
if not hasattr(self.main_window, 'offline_mode_manager'):
|
||||
if debug_mode:
|
||||
print("DEBUG: 离线模式管理器未初始化,无法使用离线模式")
|
||||
return
|
||||
|
||||
for i in range(1, 5):
|
||||
game_version = f"NEKOPARA Vol.{i}"
|
||||
if game_version in game_dirs and not self.main_window.installed_status.get(game_version, False):
|
||||
# 获取离线补丁文件路径
|
||||
offline_patch_path = self.main_window.offline_mode_manager.get_offline_patch_path(game_version)
|
||||
if not offline_patch_path:
|
||||
if debug_mode:
|
||||
print(f"DEBUG: 未找到 {game_version} 的离线补丁文件,跳过")
|
||||
continue
|
||||
|
||||
game_folder = game_dirs[game_version]
|
||||
if debug_mode:
|
||||
print(f"DEBUG: 使用识别到的游戏目录 {game_version}: {game_folder}")
|
||||
print(f"DEBUG: 使用离线补丁文件: {offline_patch_path}")
|
||||
|
||||
_7z_path = os.path.join(PLUGIN, f"vol.{i}.7z")
|
||||
plugin_path = os.path.join(PLUGIN, GAME_INFO[game_version]["plugin_path"])
|
||||
|
||||
# 将本地文件路径作为URL添加到下载队列
|
||||
self.download_queue.append((offline_patch_path, game_folder, game_version, _7z_path, plugin_path))
|
||||
self.main_window.download_queue_history.append(game_version)
|
||||
|
||||
game_version = "NEKOPARA After"
|
||||
if game_version in game_dirs and not self.main_window.installed_status.get(game_version, False):
|
||||
# 获取离线补丁文件路径
|
||||
offline_patch_path = self.main_window.offline_mode_manager.get_offline_patch_path(game_version)
|
||||
if offline_patch_path:
|
||||
game_folder = game_dirs[game_version]
|
||||
if debug_mode:
|
||||
print(f"DEBUG: 使用识别到的游戏目录 {game_version}: {game_folder}")
|
||||
print(f"DEBUG: 使用离线补丁文件: {offline_patch_path}")
|
||||
|
||||
_7z_path = os.path.join(PLUGIN, "after.7z")
|
||||
plugin_path = os.path.join(PLUGIN, GAME_INFO[game_version]["plugin_path"])
|
||||
|
||||
# 将本地文件路径作为URL添加到下载队列
|
||||
self.download_queue.append((offline_patch_path, game_folder, game_version, _7z_path, plugin_path))
|
||||
self.main_window.download_queue_history.append(game_version)
|
||||
elif debug_mode:
|
||||
print(f"DEBUG: 未找到 {game_version} 的离线补丁文件,跳过")
|
||||
|
||||
def _show_cloudflare_option(self):
|
||||
"""显示Cloudflare加速选择对话框"""
|
||||
if self.download_queue:
|
||||
@@ -498,7 +619,7 @@ class DownloadManager:
|
||||
"""准备下载特定游戏版本
|
||||
|
||||
Args:
|
||||
url: 下载URL
|
||||
url: 下载URL或本地文件路径
|
||||
game_folder: 游戏文件夹路径
|
||||
game_version: 游戏版本名称
|
||||
_7z_path: 7z文件保存路径
|
||||
@@ -511,6 +632,10 @@ class DownloadManager:
|
||||
print(f"DEBUG: 准备下载游戏 {game_version}")
|
||||
print(f"DEBUG: 游戏文件夹: {game_folder}")
|
||||
|
||||
# 隐藏敏感URL
|
||||
safe_url = "***URL protection***" # 完全隐藏URL
|
||||
print(f"DEBUG: 下载URL: {safe_url}")
|
||||
|
||||
game_exe_exists = True
|
||||
|
||||
if (
|
||||
@@ -525,15 +650,76 @@ class DownloadManager:
|
||||
self.next_download_task()
|
||||
return
|
||||
|
||||
self.main_window.progress_window = self.main_window.create_progress_window()
|
||||
# 检查是否处于离线模式
|
||||
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()
|
||||
|
||||
self.optimized_ip = self.cloudflare_optimizer.get_optimized_ip()
|
||||
if self.optimized_ip:
|
||||
print(f"已为 {game_version} 获取到优选IP: {self.optimized_ip}")
|
||||
# 如果是离线模式且URL是本地文件路径
|
||||
if is_offline_mode and os.path.isfile(url):
|
||||
if debug_mode:
|
||||
print(f"DEBUG: 离线模式,复制本地补丁文件 {url} 到 {_7z_path}")
|
||||
|
||||
try:
|
||||
# 确保目标目录存在
|
||||
os.makedirs(os.path.dirname(_7z_path), exist_ok=True)
|
||||
|
||||
# 复制文件
|
||||
import shutil
|
||||
shutil.copy2(url, _7z_path)
|
||||
|
||||
# 验证文件哈希
|
||||
hash_valid = False
|
||||
if hasattr(self.main_window, 'offline_mode_manager'):
|
||||
if debug_mode:
|
||||
print(f"DEBUG: 开始验证补丁文件哈希: {_7z_path}")
|
||||
hash_valid = self.main_window.offline_mode_manager.verify_patch_hash(game_version, _7z_path)
|
||||
if debug_mode:
|
||||
print(f"DEBUG: 补丁文件哈希验证结果: {'成功' if hash_valid else '失败'}")
|
||||
else:
|
||||
if debug_mode:
|
||||
print("DEBUG: 离线模式管理器不可用,跳过哈希验证")
|
||||
hash_valid = True # 如果没有离线模式管理器,假设验证成功
|
||||
|
||||
if hash_valid:
|
||||
if debug_mode:
|
||||
print(f"DEBUG: 成功复制并验证补丁文件 {_7z_path}")
|
||||
# 直接进入解压阶段
|
||||
self.main_window.extraction_handler.start_extraction(_7z_path, game_folder, plugin_path, game_version)
|
||||
self.main_window.extraction_handler.extraction_finished.connect(self.on_extraction_finished)
|
||||
else:
|
||||
if debug_mode:
|
||||
print(f"DEBUG: 补丁文件哈希验证失败")
|
||||
# 显示错误消息
|
||||
QtWidgets.QMessageBox.critical(
|
||||
self.main_window,
|
||||
f"错误 - {APP_NAME}",
|
||||
f"\n补丁文件校验失败: {game_version}\n\n文件可能已损坏或被篡改,请重新获取补丁文件。\n"
|
||||
)
|
||||
# 继续下一个任务
|
||||
self.next_download_task()
|
||||
except Exception as e:
|
||||
if debug_mode:
|
||||
print(f"DEBUG: 复制补丁文件失败: {e}")
|
||||
# 显示错误消息
|
||||
QtWidgets.QMessageBox.critical(
|
||||
self.main_window,
|
||||
f"错误 - {APP_NAME}",
|
||||
f"\n复制补丁文件失败: {game_version}\n错误: {e}\n"
|
||||
)
|
||||
# 继续下一个任务
|
||||
self.next_download_task()
|
||||
else:
|
||||
print(f"未能为 {game_version} 获取优选IP,将使用默认线路。")
|
||||
# 在线模式,正常下载
|
||||
self.main_window.progress_window = self.main_window.create_progress_window()
|
||||
|
||||
self.optimized_ip = self.cloudflare_optimizer.get_optimized_ip()
|
||||
if self.optimized_ip:
|
||||
print(f"已为 {game_version} 获取到优选IP: {self.optimized_ip}")
|
||||
else:
|
||||
print(f"未能为 {game_version} 获取优选IP,将使用默认线路。")
|
||||
|
||||
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):
|
||||
"""下载完成后的处理
|
||||
|
||||
@@ -24,8 +24,16 @@ class ExtractionHandler:
|
||||
plugin_path: 插件路径
|
||||
game_version: 游戏版本名称
|
||||
"""
|
||||
# 检查是否处于离线模式
|
||||
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="extraction")
|
||||
self.main_window.hash_msg_box = self.main_window.hash_manager.hash_pop_window(
|
||||
check_type="offline_extraction" if is_offline else "extraction",
|
||||
is_offline=is_offline
|
||||
)
|
||||
|
||||
# 创建并启动解压线程
|
||||
self.main_window.extraction_thread = self.main_window.create_extraction_thread(
|
||||
|
||||
@@ -13,6 +13,7 @@ class GameDetector:
|
||||
"""
|
||||
self.game_info = game_info
|
||||
self.debug_manager = debug_manager
|
||||
self.directory_cache = {} # 添加目录缓存
|
||||
|
||||
def _is_debug_mode(self):
|
||||
"""检查是否处于调试模式
|
||||
@@ -135,6 +136,12 @@ class GameDetector:
|
||||
"""
|
||||
debug_mode = self._is_debug_mode()
|
||||
|
||||
# 检查缓存中是否已有该目录的识别结果
|
||||
if selected_folder in self.directory_cache:
|
||||
if debug_mode:
|
||||
print(f"DEBUG: 使用缓存的目录识别结果: {selected_folder}")
|
||||
return self.directory_cache[selected_folder]
|
||||
|
||||
if debug_mode:
|
||||
print(f"--- 开始识别目录: {selected_folder} ---")
|
||||
|
||||
@@ -307,5 +314,14 @@ class GameDetector:
|
||||
if debug_mode:
|
||||
print(f"DEBUG: 最终识别的游戏目录: {game_paths}")
|
||||
print(f"--- 目录识别结束 ---")
|
||||
|
||||
# 将识别结果存入缓存
|
||||
self.directory_cache[selected_folder] = game_paths
|
||||
|
||||
return game_paths
|
||||
return game_paths
|
||||
|
||||
def clear_directory_cache(self):
|
||||
"""清除目录缓存"""
|
||||
self.directory_cache = {}
|
||||
if self._is_debug_mode():
|
||||
print("DEBUG: 已清除目录缓存")
|
||||
@@ -291,28 +291,6 @@ class IPv6Manager:
|
||||
"""
|
||||
print(f"Toggle IPv6 support: {enabled}")
|
||||
|
||||
# 如果用户尝试启用IPv6,检查系统是否支持IPv6并发出警告
|
||||
if enabled:
|
||||
# 先显示警告提示
|
||||
warning_msg_box = self._create_message_box(
|
||||
"警告",
|
||||
"\n目前IPv6支持功能仍在测试阶段,可能会发生意料之外的bug!\n\n您确定需要启用吗?\n",
|
||||
QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No
|
||||
)
|
||||
response = warning_msg_box.exec()
|
||||
|
||||
# 如果用户选择不启用,直接返回
|
||||
if response != QMessageBox.StandardButton.Yes:
|
||||
return False
|
||||
|
||||
# 用户确认启用后,继续检查IPv6可用性
|
||||
ipv6_available = self.check_ipv6_availability()
|
||||
|
||||
if not ipv6_available:
|
||||
msg_box = self._create_message_box("错误", "\n未检测到可用的IPv6连接,无法启用IPv6支持。\n\n请确保您的网络环境支持IPv6且已正确配置。\n")
|
||||
msg_box.exec()
|
||||
return False
|
||||
|
||||
# 保存设置到配置
|
||||
if self.config is not None:
|
||||
self.config["ipv6_enabled"] = enabled
|
||||
|
||||
692
source/core/offline_mode_manager.py
Normal file
692
source/core/offline_mode_manager.py
Normal file
@@ -0,0 +1,692 @@
|
||||
import os
|
||||
import hashlib
|
||||
import shutil
|
||||
import tempfile
|
||||
import py7zr
|
||||
from PySide6.QtWidgets import QMessageBox
|
||||
|
||||
from data.config import PLUGIN, PLUGIN_HASH, GAME_INFO
|
||||
from utils import msgbox_frame
|
||||
|
||||
class OfflineModeManager:
|
||||
"""离线模式管理器,用于管理离线模式下的补丁安装和检测"""
|
||||
|
||||
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.offline_patches = {} # 存储离线补丁信息 {补丁名称: 文件路径}
|
||||
self.is_offline_mode = False
|
||||
|
||||
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 scan_for_offline_patches(self, directory=None):
|
||||
"""扫描指定目录(默认为软件所在目录)查找离线补丁文件
|
||||
|
||||
Args:
|
||||
directory: 要扫描的目录,如果为None则使用软件所在目录
|
||||
|
||||
Returns:
|
||||
dict: 找到的补丁文件 {补丁名称: 文件路径}
|
||||
"""
|
||||
if directory is None:
|
||||
# 获取软件所在目录
|
||||
directory = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
|
||||
debug_mode = self._is_debug_mode()
|
||||
if debug_mode:
|
||||
print(f"DEBUG: 扫描离线补丁文件,目录: {directory}")
|
||||
|
||||
# 要查找的补丁文件名
|
||||
patch_files = ["vol.1.7z", "vol.2.7z", "vol.3.7z", "vol.4.7z", "after.7z"]
|
||||
|
||||
found_patches = {}
|
||||
|
||||
# 扫描目录中的文件
|
||||
for file in os.listdir(directory):
|
||||
if file.lower() in patch_files:
|
||||
file_path = os.path.join(directory, file)
|
||||
if os.path.isfile(file_path):
|
||||
patch_name = file.lower()
|
||||
found_patches[patch_name] = file_path
|
||||
if debug_mode:
|
||||
print(f"DEBUG: 找到离线补丁文件: {patch_name} 路径: {file_path}")
|
||||
|
||||
self.offline_patches = found_patches
|
||||
return found_patches
|
||||
|
||||
def has_offline_patches(self):
|
||||
"""检查是否有可用的离线补丁文件
|
||||
|
||||
Returns:
|
||||
bool: 是否有可用的离线补丁
|
||||
"""
|
||||
if not self.offline_patches:
|
||||
self.scan_for_offline_patches()
|
||||
|
||||
return len(self.offline_patches) > 0
|
||||
|
||||
def set_offline_mode(self, enabled):
|
||||
"""设置离线模式状态
|
||||
|
||||
Args:
|
||||
enabled: 是否启用离线模式
|
||||
|
||||
Returns:
|
||||
bool: 是否成功设置离线模式
|
||||
"""
|
||||
debug_mode = self._is_debug_mode()
|
||||
|
||||
if enabled:
|
||||
# 检查是否有离线补丁文件
|
||||
if not self.has_offline_patches() and not debug_mode:
|
||||
msgbox_frame(
|
||||
f"离线模式错误 - {self.app_name}",
|
||||
"\n未找到任何离线补丁文件,无法启用离线模式。\n\n请将补丁文件放置在软件所在目录后再尝试。\n",
|
||||
QMessageBox.StandardButton.Ok
|
||||
).exec()
|
||||
return False
|
||||
|
||||
if debug_mode:
|
||||
print("DEBUG: 已启用离线模式(调试模式下允许强制启用)")
|
||||
|
||||
self.is_offline_mode = enabled
|
||||
|
||||
# 更新窗口标题
|
||||
if hasattr(self.main_window, 'setWindowTitle'):
|
||||
from data.config import APP_NAME, APP_VERSION
|
||||
mode_indicator = "[离线模式]" if enabled else "[在线模式]"
|
||||
self.main_window.setWindowTitle(f"{APP_NAME} v{APP_VERSION} {mode_indicator}")
|
||||
|
||||
# 同时更新UI中的标题标签
|
||||
if hasattr(self.main_window, 'ui') and hasattr(self.main_window.ui, 'title_label'):
|
||||
self.main_window.ui.title_label.setText(f"{APP_NAME} v{APP_VERSION} {mode_indicator}")
|
||||
|
||||
if debug_mode:
|
||||
print(f"DEBUG: 离线模式已{'启用' if enabled else '禁用'}")
|
||||
|
||||
return True
|
||||
|
||||
def get_offline_patch_path(self, game_version):
|
||||
"""根据游戏版本获取对应的离线补丁文件路径
|
||||
|
||||
Args:
|
||||
game_version: 游戏版本名称,如"NEKOPARA Vol.1"
|
||||
|
||||
Returns:
|
||||
str: 离线补丁文件路径,如果没有找到则返回None
|
||||
"""
|
||||
# 确保已扫描过补丁文件
|
||||
if not self.offline_patches:
|
||||
self.scan_for_offline_patches()
|
||||
|
||||
# 根据游戏版本获取对应的补丁文件名
|
||||
patch_file = None
|
||||
|
||||
if "Vol.1" in game_version:
|
||||
patch_file = "vol.1.7z"
|
||||
elif "Vol.2" in game_version:
|
||||
patch_file = "vol.2.7z"
|
||||
elif "Vol.3" in game_version:
|
||||
patch_file = "vol.3.7z"
|
||||
elif "Vol.4" in game_version:
|
||||
patch_file = "vol.4.7z"
|
||||
elif "After" in game_version:
|
||||
patch_file = "after.7z"
|
||||
|
||||
# 检查是否有对应的补丁文件
|
||||
if patch_file and patch_file in self.offline_patches:
|
||||
return self.offline_patches[patch_file]
|
||||
|
||||
return None
|
||||
|
||||
def prepare_offline_patch(self, game_version, target_path):
|
||||
"""准备离线补丁文件,复制到缓存目录
|
||||
|
||||
Args:
|
||||
game_version: 游戏版本名称
|
||||
target_path: 目标路径(通常是缓存目录中的路径)
|
||||
|
||||
Returns:
|
||||
bool: 是否成功准备补丁文件
|
||||
"""
|
||||
source_path = self.get_offline_patch_path(game_version)
|
||||
|
||||
if not source_path:
|
||||
return False
|
||||
|
||||
debug_mode = self._is_debug_mode()
|
||||
|
||||
try:
|
||||
# 确保目标目录存在
|
||||
os.makedirs(os.path.dirname(target_path), exist_ok=True)
|
||||
|
||||
# 复制文件
|
||||
shutil.copy2(source_path, target_path)
|
||||
|
||||
if debug_mode:
|
||||
print(f"DEBUG: 已复制离线补丁文件 {source_path} 到 {target_path}")
|
||||
|
||||
return True
|
||||
except Exception as e:
|
||||
if debug_mode:
|
||||
print(f"DEBUG: 复制离线补丁文件失败: {e}")
|
||||
return False
|
||||
|
||||
def verify_patch_hash(self, game_version, file_path):
|
||||
"""验证补丁文件的哈希值
|
||||
|
||||
Args:
|
||||
game_version: 游戏版本名称
|
||||
file_path: 补丁压缩包文件路径
|
||||
|
||||
Returns:
|
||||
bool: 哈希值是否匹配
|
||||
"""
|
||||
# 获取预期的哈希值
|
||||
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 not expected_hash:
|
||||
print(f"DEBUG: 未找到 {game_version} 的预期哈希值")
|
||||
return False
|
||||
|
||||
debug_mode = self._is_debug_mode()
|
||||
|
||||
if debug_mode:
|
||||
print(f"DEBUG: 开始验证离线补丁文件: {file_path}")
|
||||
print(f"DEBUG: 游戏版本: {game_version}")
|
||||
print(f"DEBUG: 预期哈希值: {expected_hash}")
|
||||
|
||||
try:
|
||||
# 检查文件是否存在
|
||||
if not os.path.exists(file_path):
|
||||
if debug_mode:
|
||||
print(f"DEBUG: 补丁文件不存在: {file_path}")
|
||||
return False
|
||||
|
||||
# 检查文件大小
|
||||
file_size = os.path.getsize(file_path)
|
||||
if debug_mode:
|
||||
print(f"DEBUG: 补丁文件大小: {file_size} 字节")
|
||||
|
||||
if file_size == 0:
|
||||
if debug_mode:
|
||||
print(f"DEBUG: 补丁文件大小为0,无效文件")
|
||||
return False
|
||||
|
||||
# 创建临时目录用于解压文件
|
||||
with tempfile.TemporaryDirectory() as temp_dir:
|
||||
if debug_mode:
|
||||
print(f"DEBUG: 创建临时目录: {temp_dir}")
|
||||
|
||||
# 解压补丁文件
|
||||
try:
|
||||
if debug_mode:
|
||||
print(f"DEBUG: 开始解压文件: {file_path}")
|
||||
|
||||
with py7zr.SevenZipFile(file_path, mode="r") as archive:
|
||||
# 获取压缩包内文件列表
|
||||
file_list = archive.getnames()
|
||||
if debug_mode:
|
||||
print(f"DEBUG: 压缩包内文件列表: {file_list}")
|
||||
|
||||
# 解压所有文件
|
||||
archive.extractall(path=temp_dir)
|
||||
|
||||
if debug_mode:
|
||||
print(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))
|
||||
print(f"DEBUG: 解压后的文件列表: {extracted_files}")
|
||||
except Exception as e:
|
||||
if debug_mode:
|
||||
print(f"DEBUG: 解压补丁文件失败: {e}")
|
||||
print(f"DEBUG: 错误类型: {type(e).__name__}")
|
||||
import traceback
|
||||
print(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:
|
||||
print(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:
|
||||
print(f"DEBUG: 找到可能的替代文件: {alternative_files}")
|
||||
|
||||
# 检查解压目录结构
|
||||
print(f"DEBUG: 检查解压目录结构:")
|
||||
for root, dirs, files in os.walk(temp_dir):
|
||||
print(f"DEBUG: 目录: {root}")
|
||||
print(f"DEBUG: 子目录: {dirs}")
|
||||
print(f"DEBUG: 文件: {files}")
|
||||
return False
|
||||
|
||||
if debug_mode:
|
||||
print(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:
|
||||
print(f"DEBUG: 补丁文件 {patch_file} 哈希值验证: {'成功' if result else '失败'}")
|
||||
print(f"DEBUG: 预期哈希值: {expected_hash}")
|
||||
print(f"DEBUG: 实际哈希值: {file_hash}")
|
||||
|
||||
return result
|
||||
except Exception as e:
|
||||
if debug_mode:
|
||||
print(f"DEBUG: 计算补丁文件哈希值失败: {e}")
|
||||
print(f"DEBUG: 错误类型: {type(e).__name__}")
|
||||
return False
|
||||
except Exception as e:
|
||||
if debug_mode:
|
||||
print(f"DEBUG: 验证补丁哈希值失败: {e}")
|
||||
print(f"DEBUG: 错误类型: {type(e).__name__}")
|
||||
import traceback
|
||||
print(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):
|
||||
"""直接安装离线补丁,完全绕过下载模块
|
||||
|
||||
Args:
|
||||
selected_games: 用户选择安装的游戏列表
|
||||
|
||||
Returns:
|
||||
bool: 是否成功启动安装流程
|
||||
"""
|
||||
debug_mode = self._is_debug_mode()
|
||||
|
||||
if debug_mode:
|
||||
print(f"DEBUG: 开始离线安装流程,选择的游戏: {selected_games}")
|
||||
|
||||
if not self.is_in_offline_mode():
|
||||
if debug_mode:
|
||||
print("DEBUG: 当前不是离线模式,无法使用离线安装")
|
||||
return False
|
||||
|
||||
# 确保已扫描过补丁文件
|
||||
if not self.offline_patches:
|
||||
self.scan_for_offline_patches()
|
||||
|
||||
if not self.offline_patches:
|
||||
if debug_mode:
|
||||
print("DEBUG: 未找到任何离线补丁文件")
|
||||
msgbox_frame(
|
||||
f"离线安装错误 - {self.app_name}",
|
||||
"\n未找到任何离线补丁文件,无法进行离线安装。\n\n请将补丁文件放置在软件所在目录后再尝试。\n",
|
||||
QMessageBox.StandardButton.Ok
|
||||
).exec()
|
||||
return False
|
||||
|
||||
# 获取游戏目录
|
||||
game_dirs = self.main_window.game_detector.identify_game_directories_improved(
|
||||
self.main_window.download_manager.selected_folder
|
||||
)
|
||||
|
||||
if not game_dirs:
|
||||
if debug_mode:
|
||||
print("DEBUG: 未识别到任何游戏目录")
|
||||
return 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.main_window.hash_thread = self.main_window.create_hash_thread("pre", install_paths)
|
||||
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:
|
||||
print(f"DEBUG: 未找到 {game_version} 的离线补丁文件,跳过")
|
||||
|
||||
if not installable_games:
|
||||
if debug_mode:
|
||||
print("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:
|
||||
print(f"DEBUG: 开始离线安装流程,安装游戏: {installable_games}")
|
||||
|
||||
# 创建安装任务列表
|
||||
install_tasks = []
|
||||
for game_version in installable_games:
|
||||
# 获取离线补丁文件路径
|
||||
patch_file = self.get_offline_patch_path(game_version)
|
||||
if not patch_file:
|
||||
continue
|
||||
|
||||
# 获取游戏目录
|
||||
game_folder = game_dirs.get(game_version)
|
||||
if not game_folder:
|
||||
continue
|
||||
|
||||
# 获取目标路径
|
||||
if "Vol.1" in game_version:
|
||||
_7z_path = os.path.join(PLUGIN, "vol.1.7z")
|
||||
plugin_path = os.path.join(PLUGIN, GAME_INFO[game_version]["plugin_path"])
|
||||
elif "Vol.2" in game_version:
|
||||
_7z_path = os.path.join(PLUGIN, "vol.2.7z")
|
||||
plugin_path = os.path.join(PLUGIN, GAME_INFO[game_version]["plugin_path"])
|
||||
elif "Vol.3" in game_version:
|
||||
_7z_path = os.path.join(PLUGIN, "vol.3.7z")
|
||||
plugin_path = os.path.join(PLUGIN, GAME_INFO[game_version]["plugin_path"])
|
||||
elif "Vol.4" in game_version:
|
||||
_7z_path = os.path.join(PLUGIN, "vol.4.7z")
|
||||
plugin_path = os.path.join(PLUGIN, GAME_INFO[game_version]["plugin_path"])
|
||||
elif "After" in game_version:
|
||||
_7z_path = os.path.join(PLUGIN, "after.7z")
|
||||
plugin_path = os.path.join(PLUGIN, GAME_INFO[game_version]["plugin_path"])
|
||||
else:
|
||||
continue
|
||||
|
||||
# 添加到安装任务列表
|
||||
install_tasks.append((patch_file, game_folder, game_version, _7z_path, plugin_path))
|
||||
|
||||
# 开始执行第一个安装任务
|
||||
if install_tasks:
|
||||
self.process_next_offline_install_task(install_tasks)
|
||||
else:
|
||||
self.main_window.ui.start_install_text.setText("开始安装")
|
||||
|
||||
def process_next_offline_install_task(self, install_tasks):
|
||||
"""处理下一个离线安装任务
|
||||
|
||||
Args:
|
||||
install_tasks: 安装任务列表,每个任务是一个元组 (patch_file, game_folder, game_version, _7z_path, plugin_path)
|
||||
"""
|
||||
debug_mode = self._is_debug_mode()
|
||||
|
||||
if not install_tasks:
|
||||
# 所有任务完成,进行后检查
|
||||
if debug_mode:
|
||||
print("DEBUG: 所有离线安装任务完成,进行后检查")
|
||||
self.main_window.after_hash_compare()
|
||||
return
|
||||
|
||||
# 获取下一个任务
|
||||
patch_file, game_folder, game_version, _7z_path, plugin_path = install_tasks.pop(0)
|
||||
|
||||
if debug_mode:
|
||||
print(f"DEBUG: 处理离线安装任务: {game_version}")
|
||||
print(f"DEBUG: 补丁文件: {patch_file}")
|
||||
print(f"DEBUG: 游戏目录: {game_folder}")
|
||||
|
||||
# 确保目标目录存在
|
||||
os.makedirs(os.path.dirname(_7z_path), exist_ok=True)
|
||||
|
||||
try:
|
||||
# 复制补丁文件到缓存目录
|
||||
shutil.copy2(patch_file, _7z_path)
|
||||
|
||||
if debug_mode:
|
||||
print(f"DEBUG: 已复制补丁文件到缓存目录: {_7z_path}")
|
||||
print(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:
|
||||
print(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)
|
||||
|
||||
# 验证补丁文件哈希
|
||||
hash_valid = self.verify_patch_hash(game_version, _7z_path)
|
||||
|
||||
# 关闭哈希验证窗口
|
||||
if self.main_window.hash_msg_box and self.main_window.hash_msg_box.isVisible():
|
||||
self.main_window.hash_msg_box.close()
|
||||
self.main_window.hash_msg_box = None
|
||||
|
||||
if hash_valid:
|
||||
if debug_mode:
|
||||
print(f"DEBUG: 补丁文件哈希验证成功,开始解压")
|
||||
|
||||
# 显示解压窗口 - 使用离线特定消息
|
||||
self.main_window.hash_msg_box = self.main_window.hash_manager.hash_pop_window(check_type="offline_extraction", is_offline=True)
|
||||
|
||||
try:
|
||||
# 创建解压线程
|
||||
extraction_thread = self.main_window.create_extraction_thread(
|
||||
_7z_path, game_folder, plugin_path, game_version
|
||||
)
|
||||
|
||||
# 正确连接信号
|
||||
extraction_thread.finished.connect(
|
||||
lambda success, error, game_ver: self.on_extraction_thread_finished(
|
||||
success, error, game_ver, install_tasks
|
||||
)
|
||||
)
|
||||
|
||||
# 启动解压线程
|
||||
extraction_thread.start()
|
||||
except Exception as e:
|
||||
if debug_mode:
|
||||
print(f"DEBUG: 创建或启动解压线程失败: {e}")
|
||||
|
||||
# 关闭解压窗口
|
||||
if self.main_window.hash_msg_box and self.main_window.hash_msg_box.isVisible():
|
||||
self.main_window.hash_msg_box.close()
|
||||
self.main_window.hash_msg_box = None
|
||||
|
||||
# 显示错误消息
|
||||
msgbox_frame(
|
||||
f"解压错误 - {self.app_name}",
|
||||
f"\n{game_version} 的解压过程中发生错误: {str(e)}\n\n跳过此游戏的安装。\n",
|
||||
QMessageBox.StandardButton.Ok
|
||||
).exec()
|
||||
|
||||
# 继续下一个任务
|
||||
self.process_next_offline_install_task(install_tasks)
|
||||
else:
|
||||
if debug_mode:
|
||||
print(f"DEBUG: 补丁文件哈希验证失败")
|
||||
|
||||
# 显示错误消息
|
||||
msgbox_frame(
|
||||
f"哈希验证失败 - {self.app_name}",
|
||||
f"\n{game_version} 的补丁文件哈希验证失败,可能已损坏或被篡改。\n\n跳过此游戏的安装。\n",
|
||||
QMessageBox.StandardButton.Ok
|
||||
).exec()
|
||||
|
||||
# 继续下一个任务
|
||||
self.process_next_offline_install_task(install_tasks)
|
||||
except Exception as e:
|
||||
if debug_mode:
|
||||
print(f"DEBUG: 离线安装任务处理失败: {e}")
|
||||
|
||||
# 显示错误消息
|
||||
msgbox_frame(
|
||||
f"安装错误 - {self.app_name}",
|
||||
f"\n{game_version} 的安装过程中发生错误: {str(e)}\n\n跳过此游戏的安装。\n",
|
||||
QMessageBox.StandardButton.Ok
|
||||
).exec()
|
||||
|
||||
# 继续下一个任务
|
||||
self.process_next_offline_install_task(install_tasks)
|
||||
|
||||
def on_extraction_thread_finished(self, success, error_message, game_version, remaining_tasks):
|
||||
"""解压线程完成后的处理
|
||||
|
||||
Args:
|
||||
success: 是否解压成功
|
||||
error_message: 错误信息
|
||||
game_version: 游戏版本
|
||||
remaining_tasks: 剩余的安装任务列表
|
||||
"""
|
||||
debug_mode = self._is_debug_mode()
|
||||
|
||||
# 关闭解压窗口
|
||||
if self.main_window.hash_msg_box and self.main_window.hash_msg_box.isVisible():
|
||||
self.main_window.hash_msg_box.close()
|
||||
self.main_window.hash_msg_box = None
|
||||
|
||||
if debug_mode:
|
||||
print(f"DEBUG: 离线解压完成,状态: {'成功' if success else '失败'}")
|
||||
if not success:
|
||||
print(f"DEBUG: 错误信息: {error_message}")
|
||||
|
||||
if not success:
|
||||
# 显示错误消息
|
||||
msgbox_frame(
|
||||
f"解压失败 - {self.app_name}",
|
||||
f"\n{game_version} 的补丁解压失败。\n\n错误信息: {error_message}\n\n跳过此游戏的安装。\n",
|
||||
QMessageBox.StandardButton.Ok
|
||||
).exec()
|
||||
|
||||
# 更新安装状态
|
||||
self.main_window.installed_status[game_version] = False
|
||||
else:
|
||||
# 更新安装状态
|
||||
self.main_window.installed_status[game_version] = True
|
||||
|
||||
# 处理下一个任务
|
||||
self.process_next_offline_install_task(remaining_tasks)
|
||||
|
||||
def on_offline_extraction_finished(self, remaining_tasks):
|
||||
"""离线模式下的解压完成处理(旧方法,保留兼容性)
|
||||
|
||||
Args:
|
||||
remaining_tasks: 剩余的安装任务列表
|
||||
"""
|
||||
debug_mode = self._is_debug_mode()
|
||||
|
||||
if debug_mode:
|
||||
print("DEBUG: 离线解压完成,继续处理下一个任务")
|
||||
|
||||
# 处理下一个任务
|
||||
self.process_next_offline_install_task(remaining_tasks)
|
||||
@@ -1,4 +1,4 @@
|
||||
from PySide6.QtGui import QIcon, QAction, QFont, QCursor
|
||||
from PySide6.QtGui import QIcon, QAction, QFont, QCursor, QActionGroup
|
||||
from PySide6.QtWidgets import QMessageBox, QMainWindow, QMenu, QPushButton
|
||||
from PySide6.QtCore import Qt, QRect
|
||||
import webbrowser
|
||||
@@ -37,8 +37,18 @@ class UIManager:
|
||||
if os.path.exists(icon_path):
|
||||
self.main_window.setWindowIcon(QIcon(icon_path))
|
||||
|
||||
# 设置窗口标题
|
||||
self.main_window.setWindowTitle(f"{APP_NAME} v{APP_VERSION}")
|
||||
# 获取当前离线模式状态
|
||||
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()
|
||||
|
||||
# 设置窗口标题和UI标题标签
|
||||
mode_indicator = "[离线模式]" if is_offline_mode else "[在线模式]"
|
||||
self.main_window.setWindowTitle(f"{APP_NAME} v{APP_VERSION} {mode_indicator}")
|
||||
|
||||
# 更新UI中的标题标签
|
||||
if hasattr(self.main_window, 'ui') and hasattr(self.main_window.ui, 'title_label'):
|
||||
self.main_window.ui.title_label.setText(f"{APP_NAME} v{APP_VERSION} {mode_indicator}")
|
||||
|
||||
# 创建关于按钮
|
||||
self._create_about_button()
|
||||
@@ -265,6 +275,34 @@ class UIManager:
|
||||
menu_font = self._get_menu_font()
|
||||
font_family = menu_font.family()
|
||||
|
||||
# 创建工作模式子菜单
|
||||
self.work_mode_menu = QMenu("工作模式", self.main_window)
|
||||
self.work_mode_menu.setFont(menu_font)
|
||||
self.work_mode_menu.setStyleSheet(self._get_menu_style(font_family))
|
||||
|
||||
# 创建在线模式和离线模式选项
|
||||
self.online_mode_action = QAction("在线模式", self.main_window, checkable=True)
|
||||
self.online_mode_action.setFont(menu_font)
|
||||
self.online_mode_action.setChecked(True) # 默认选中在线模式
|
||||
|
||||
self.offline_mode_action = QAction("离线模式", self.main_window, checkable=True)
|
||||
self.offline_mode_action.setFont(menu_font)
|
||||
self.offline_mode_action.setChecked(False)
|
||||
|
||||
# 将两个模式选项添加到同一个互斥组
|
||||
mode_group = QActionGroup(self.main_window)
|
||||
mode_group.addAction(self.online_mode_action)
|
||||
mode_group.addAction(self.offline_mode_action)
|
||||
mode_group.setExclusive(True) # 确保只能选择一个模式
|
||||
|
||||
# 连接切换事件
|
||||
self.online_mode_action.triggered.connect(lambda: self.switch_work_mode("online"))
|
||||
self.offline_mode_action.triggered.connect(lambda: self.switch_work_mode("offline"))
|
||||
|
||||
# 添加到工作模式子菜单
|
||||
self.work_mode_menu.addAction(self.online_mode_action)
|
||||
self.work_mode_menu.addAction(self.offline_mode_action)
|
||||
|
||||
# 创建开发者选项子菜单
|
||||
self.dev_menu = QMenu("开发者选项", self.main_window)
|
||||
self.dev_menu.setFont(menu_font) # 设置与UI_install.py中相同的字体
|
||||
@@ -300,28 +338,11 @@ class UIManager:
|
||||
self.ipv6_submenu.setFont(menu_font)
|
||||
self.ipv6_submenu.setStyleSheet(menu_style)
|
||||
|
||||
# 检查IPv6是否可用
|
||||
ipv6_available = False
|
||||
if self.ipv6_manager:
|
||||
ipv6_available = self.ipv6_manager.check_ipv6_availability()
|
||||
|
||||
if not ipv6_available:
|
||||
self.ipv6_action.setText("启用IPv6支持 (不可用)")
|
||||
self.ipv6_action.setEnabled(False)
|
||||
self.ipv6_action.setToolTip("未检测到可用的IPv6连接")
|
||||
|
||||
# 检查配置中是否已启用IPv6
|
||||
config = getattr(self.main_window, 'config', {})
|
||||
ipv6_enabled = False
|
||||
if isinstance(config, dict):
|
||||
ipv6_enabled = config.get("ipv6_enabled", False)
|
||||
# 如果配置中启用了IPv6但实际不可用,则强制禁用
|
||||
if ipv6_enabled and not ipv6_available:
|
||||
config["ipv6_enabled"] = False
|
||||
ipv6_enabled = False
|
||||
# 使用utils.save_config直接保存配置
|
||||
from utils import save_config
|
||||
save_config(config)
|
||||
|
||||
self.ipv6_action.setChecked(ipv6_enabled)
|
||||
|
||||
@@ -416,6 +437,7 @@ class UIManager:
|
||||
self.download_settings_menu.addAction(self.thread_settings_action)
|
||||
|
||||
# 添加到主菜单
|
||||
self.ui.menu.addMenu(self.work_mode_menu) # 添加工作模式子菜单
|
||||
self.ui.menu.addMenu(self.download_settings_menu) # 添加下载设置子菜单
|
||||
self.ui.menu.addSeparator()
|
||||
self.ui.menu.addMenu(self.dev_menu) # 添加开发者选项子菜单
|
||||
@@ -438,7 +460,47 @@ class UIManager:
|
||||
# 恢复复选框状态
|
||||
self.ipv6_action.setChecked(not enabled)
|
||||
return
|
||||
|
||||
if enabled:
|
||||
# 先显示警告提示
|
||||
warning_msg_box = self._create_message_box(
|
||||
"警告",
|
||||
"\n目前IPv6支持功能仍在测试阶段,可能会发生意料之外的bug!\n\n您确定需要启用吗?\n",
|
||||
QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No
|
||||
)
|
||||
response = warning_msg_box.exec()
|
||||
|
||||
# 如果用户选择不启用,直接返回
|
||||
if response != QMessageBox.StandardButton.Yes:
|
||||
# 恢复复选框状态
|
||||
self.ipv6_action.setChecked(False)
|
||||
return
|
||||
|
||||
# 显示正在校验IPv6的提示
|
||||
msg_box = self._create_message_box("IPv6检测", "\n正在校验是否支持IPv6,请稍候...\n")
|
||||
msg_box.open() # 使用open而不是exec,这样不会阻塞UI
|
||||
|
||||
# 处理消息队列,确保对话框显示
|
||||
from PySide6.QtCore import QCoreApplication
|
||||
QCoreApplication.processEvents()
|
||||
|
||||
# 检查IPv6是否可用
|
||||
ipv6_available = self.ipv6_manager.check_ipv6_availability()
|
||||
|
||||
# 关闭提示对话框
|
||||
msg_box.accept()
|
||||
|
||||
if not ipv6_available:
|
||||
# 显示IPv6不可用的提示
|
||||
error_msg_box = self._create_message_box(
|
||||
"IPv6不可用",
|
||||
"\n未检测到可用的IPv6连接,无法启用IPv6支持。\n\n请确保您的网络环境支持IPv6且已正确配置。\n"
|
||||
)
|
||||
error_msg_box.exec()
|
||||
# 恢复复选框状态
|
||||
self.ipv6_action.setChecked(False)
|
||||
return False
|
||||
|
||||
# 使用IPv6Manager处理切换
|
||||
success = self.ipv6_manager.toggle_ipv6_support(enabled)
|
||||
# 如果切换失败,恢复复选框状态
|
||||
@@ -732,4 +794,72 @@ class UIManager:
|
||||
def show_ipv6_manager_not_ready(self):
|
||||
"""显示IPv6管理器未准备好的提示"""
|
||||
msg_box = self._create_message_box("错误", "\nIPv6管理器尚未初始化,请稍后再试。\n")
|
||||
msg_box.exec()
|
||||
msg_box.exec()
|
||||
|
||||
def switch_work_mode(self, mode):
|
||||
"""切换工作模式
|
||||
|
||||
Args:
|
||||
mode: 要切换的模式,"online"或"offline"
|
||||
"""
|
||||
# 检查主窗口是否有离线模式管理器
|
||||
if not hasattr(self.main_window, 'offline_mode_manager'):
|
||||
# 如果没有离线模式管理器,创建提示
|
||||
msg_box = self._create_message_box(
|
||||
"错误",
|
||||
"\n离线模式管理器未初始化,无法切换工作模式。\n"
|
||||
)
|
||||
msg_box.exec()
|
||||
|
||||
# 恢复选择状态
|
||||
self.online_mode_action.setChecked(True)
|
||||
self.offline_mode_action.setChecked(False)
|
||||
return
|
||||
|
||||
if mode == "offline":
|
||||
# 尝试切换到离线模式
|
||||
success = self.main_window.offline_mode_manager.set_offline_mode(True)
|
||||
if not success:
|
||||
# 如果切换失败,恢复选择状态
|
||||
self.online_mode_action.setChecked(True)
|
||||
self.offline_mode_action.setChecked(False)
|
||||
return
|
||||
|
||||
# 更新配置
|
||||
self.main_window.config["offline_mode"] = True
|
||||
self.main_window.save_config(self.main_window.config)
|
||||
|
||||
# 在离线模式下始终启用开始安装按钮
|
||||
if hasattr(self.main_window, 'set_start_button_enabled'):
|
||||
self.main_window.set_start_button_enabled(True)
|
||||
|
||||
# 清除版本警告标志
|
||||
if hasattr(self.main_window, 'version_warning'):
|
||||
self.main_window.version_warning = False
|
||||
|
||||
# 显示提示
|
||||
msg_box = self._create_message_box(
|
||||
"模式已切换",
|
||||
"\n已切换到离线模式。\n\n将使用本地补丁文件进行安装,不会从网络下载补丁。\n"
|
||||
)
|
||||
msg_box.exec()
|
||||
else:
|
||||
# 切换到在线模式
|
||||
self.main_window.offline_mode_manager.set_offline_mode(False)
|
||||
|
||||
# 更新配置
|
||||
self.main_window.config["offline_mode"] = False
|
||||
self.main_window.save_config(self.main_window.config)
|
||||
|
||||
# 如果当前版本过低,设置版本警告标志
|
||||
if hasattr(self.main_window, 'last_error_message') and self.main_window.last_error_message == "update_required":
|
||||
# 设置版本警告标志
|
||||
if hasattr(self.main_window, 'version_warning'):
|
||||
self.main_window.version_warning = True
|
||||
|
||||
# 显示提示
|
||||
msg_box = self._create_message_box(
|
||||
"模式已切换",
|
||||
"\n已切换到在线模式。\n\n将从网络下载补丁进行安装。\n"
|
||||
)
|
||||
msg_box.exec()
|
||||
@@ -3,7 +3,7 @@ import base64
|
||||
|
||||
# 配置信息
|
||||
app_data = {
|
||||
"APP_VERSION": "1.3.2",
|
||||
"APP_VERSION": "1.4.0",
|
||||
"APP_NAME": "FRAISEMOE Addons Installer NEXT",
|
||||
"TEMP": "TEMP",
|
||||
"CACHE": "FRAISEMOE",
|
||||
@@ -62,7 +62,13 @@ UA = app_data["UA_TEMPLATE"].format(APP_VERSION)
|
||||
GAME_INFO = app_data["game_info"]
|
||||
BLOCK_SIZE = 67108864
|
||||
HASH_SIZE = 134217728
|
||||
PLUGIN_HASH = {game: info["hash"] for game, info in GAME_INFO.items()}
|
||||
PLUGIN_HASH = {
|
||||
"vol1": GAME_INFO["NEKOPARA Vol.1"]["hash"],
|
||||
"vol2": GAME_INFO["NEKOPARA Vol.2"]["hash"],
|
||||
"vol3": GAME_INFO["NEKOPARA Vol.3"]["hash"],
|
||||
"vol4": GAME_INFO["NEKOPARA Vol.4"]["hash"],
|
||||
"after": GAME_INFO["NEKOPARA After"]["hash"]
|
||||
}
|
||||
PROCESS_INFO = {info["exe"]: game for game, info in GAME_INFO.items()}
|
||||
|
||||
# 下载线程档位设置
|
||||
|
||||
@@ -15,7 +15,7 @@ from ui.Ui_install import Ui_MainWindows
|
||||
from data.config import (
|
||||
APP_NAME, PLUGIN, GAME_INFO, BLOCK_SIZE,
|
||||
PLUGIN_HASH, UA, CONFIG_URL, LOG_FILE,
|
||||
DOWNLOAD_THREADS, DEFAULT_DOWNLOAD_THREAD_LEVEL # 添加下载线程常量
|
||||
DOWNLOAD_THREADS, DEFAULT_DOWNLOAD_THREAD_LEVEL, APP_VERSION # 添加APP_VERSION导入
|
||||
)
|
||||
from utils import (
|
||||
load_config, save_config, HashManager, AdminPrivileges, msgbox_frame, load_image_from_file
|
||||
@@ -57,7 +57,7 @@ class MainWindow(QMainWindow):
|
||||
self.hash_manager = HashManager(BLOCK_SIZE)
|
||||
self.admin_privileges = AdminPrivileges()
|
||||
|
||||
# 初始化各种管理器
|
||||
# 初始化各种管理器 - 调整初始化顺序,避免循环依赖
|
||||
# 1. 首先创建必要的基础管理器
|
||||
self.animator = MultiStageAnimations(self.ui, self)
|
||||
self.window_manager = WindowManager(self)
|
||||
@@ -72,18 +72,19 @@ class MainWindow(QMainWindow):
|
||||
# 4. 为debug_manager设置ui_manager引用
|
||||
self.debug_manager.set_ui_manager(self.ui_manager)
|
||||
|
||||
# 设置UI - 确保debug_action已初始化
|
||||
self.ui_manager.setup_ui()
|
||||
|
||||
# 5. 初始化其他管理器
|
||||
self.config_manager = ConfigManager(APP_NAME, CONFIG_URL, UA, self.debug_manager)
|
||||
self.game_detector = GameDetector(GAME_INFO, self.debug_manager)
|
||||
self.patch_manager = PatchManager(APP_NAME, GAME_INFO, self.debug_manager)
|
||||
|
||||
# 6. 初始化下载管理器 - 放在最后,因为它可能依赖于其他管理器
|
||||
# 6. 初始化离线模式管理器
|
||||
from core.offline_mode_manager import OfflineModeManager
|
||||
self.offline_mode_manager = OfflineModeManager(self)
|
||||
|
||||
# 7. 初始化下载管理器 - 放在最后,因为它可能依赖于其他管理器
|
||||
self.download_manager = DownloadManager(self)
|
||||
|
||||
# 7. 初始化功能处理程序
|
||||
# 8. 初始化功能处理程序
|
||||
self.uninstall_handler = UninstallHandler(self)
|
||||
self.patch_toggle_handler = PatchToggleHandler(self)
|
||||
|
||||
@@ -97,6 +98,9 @@ class MainWindow(QMainWindow):
|
||||
self.patch_manager.initialize_status()
|
||||
self.installed_status = self.patch_manager.get_status() # 获取初始化后的状态
|
||||
self.hash_msg_box = None
|
||||
self.last_error_message = "" # 添加错误信息记录
|
||||
self.version_warning = False # 添加版本警告标志
|
||||
self.install_button_enabled = True # 默认启用安装按钮
|
||||
self.progress_window = None
|
||||
|
||||
# 设置关闭按钮事件连接
|
||||
@@ -140,11 +144,17 @@ class MainWindow(QMainWindow):
|
||||
if self.ui_manager.debug_action.isChecked():
|
||||
self.debug_manager.start_logging()
|
||||
|
||||
# 在窗口显示前设置初始状态
|
||||
self.animator.initialize()
|
||||
# 设置UI,包括窗口图标和菜单
|
||||
self.ui_manager.setup_ui()
|
||||
|
||||
# 窗口显示后延迟100ms启动动画
|
||||
QTimer.singleShot(100, self.start_animations)
|
||||
# 检查是否有离线补丁文件,如果有则自动切换到离线模式
|
||||
self.check_and_set_offline_mode()
|
||||
|
||||
# 获取云端配置
|
||||
self.fetch_cloud_config()
|
||||
|
||||
# 启动动画
|
||||
self.start_animations()
|
||||
|
||||
# 窗口事件处理 - 委托给WindowManager
|
||||
def mousePressEvent(self, event):
|
||||
@@ -170,10 +180,14 @@ class MainWindow(QMainWindow):
|
||||
# 但确保开始安装按钮仍然处于禁用状态
|
||||
self.set_start_button_enabled(False)
|
||||
|
||||
# 在动画开始前初始化
|
||||
self.animator.initialize()
|
||||
|
||||
# 连接动画完成信号
|
||||
self.animator.animation_finished.connect(self.on_animations_finished)
|
||||
|
||||
# 启动动画
|
||||
self.animator.start_animations()
|
||||
# 在动画开始时获取云端配置
|
||||
self.fetch_cloud_config()
|
||||
|
||||
def on_animations_finished(self):
|
||||
"""动画完成后启用按钮"""
|
||||
@@ -185,8 +199,16 @@ class MainWindow(QMainWindow):
|
||||
self.ui.toggle_patch_btn.setEnabled(True) # 启用禁/启用补丁按钮
|
||||
self.ui.exit_btn.setEnabled(True)
|
||||
|
||||
# 只有在配置有效时才启用开始安装按钮
|
||||
if self.config_valid:
|
||||
# 检查是否处于离线模式
|
||||
is_offline_mode = False
|
||||
if hasattr(self, 'offline_mode_manager'):
|
||||
is_offline_mode = self.offline_mode_manager.is_in_offline_mode()
|
||||
|
||||
# 如果是离线模式,始终启用开始安装按钮
|
||||
if is_offline_mode:
|
||||
self.set_start_button_enabled(True)
|
||||
# 否则,只有在配置有效时才启用开始安装按钮
|
||||
elif self.config_valid:
|
||||
self.set_start_button_enabled(True)
|
||||
else:
|
||||
self.set_start_button_enabled(False)
|
||||
@@ -237,14 +259,14 @@ class MainWindow(QMainWindow):
|
||||
elif result["action"] == "disable_button":
|
||||
# 禁用开始安装按钮
|
||||
self.set_start_button_enabled(False)
|
||||
|
||||
# 检查是否有后续操作
|
||||
if "then" in result and result["then"] == "exit":
|
||||
# 强制关闭程序
|
||||
self.shutdown_app(force_exit=True)
|
||||
elif result["action"] == "enable_button":
|
||||
# 启用开始安装按钮
|
||||
self.set_start_button_enabled(True)
|
||||
# 检查是否需要记录版本警告
|
||||
if "version_warning" in result and result["version_warning"]:
|
||||
self.version_warning = True
|
||||
else:
|
||||
self.version_warning = False
|
||||
|
||||
# 同步状态
|
||||
self.cloud_config = self.config_manager.get_cloud_config()
|
||||
@@ -318,7 +340,12 @@ class MainWindow(QMainWindow):
|
||||
"""进行安装后哈希比较"""
|
||||
# 禁用窗口已在安装流程开始时完成
|
||||
|
||||
self.hash_msg_box = self.hash_manager.hash_pop_window(check_type="after")
|
||||
# 检查是否处于离线模式
|
||||
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()
|
||||
|
||||
@@ -495,16 +522,28 @@ class MainWindow(QMainWindow):
|
||||
"""处理安装按钮点击事件
|
||||
根据按钮当前状态决定是显示错误还是执行安装
|
||||
"""
|
||||
# 检查是否处于离线模式
|
||||
is_offline_mode = False
|
||||
if hasattr(self, 'offline_mode_manager'):
|
||||
is_offline_mode = self.offline_mode_manager.is_in_offline_mode()
|
||||
|
||||
# 如果版本过低且在在线模式下,提示用户更新
|
||||
if self.last_error_message == "update_required" and not is_offline_mode:
|
||||
# 在线模式下提示用户更新软件
|
||||
msg_box = msgbox_frame(
|
||||
f"更新提示 - {APP_NAME}",
|
||||
"\n当前版本过低,请及时更新。\n如需联网下载补丁,请更新到最新版,否则无法下载。\n\n是否切换到离线模式继续使用?\n",
|
||||
QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No,
|
||||
)
|
||||
if msg_box.exec() == QMessageBox.StandardButton.Yes:
|
||||
# 切换到离线模式
|
||||
if self.ui_manager and hasattr(self.ui_manager, 'switch_work_mode'):
|
||||
self.ui_manager.switch_work_mode("offline")
|
||||
return
|
||||
|
||||
if not self.install_button_enabled:
|
||||
# 按钮处于"无法安装"状态
|
||||
if self.last_error_message == "update_required":
|
||||
msg_box = msgbox_frame(
|
||||
f"更新提示 - {APP_NAME}",
|
||||
"\n当前版本过低,请及时更新。\n",
|
||||
QMessageBox.StandardButton.Ok,
|
||||
)
|
||||
msg_box.exec()
|
||||
elif self.last_error_message == "directory_not_found":
|
||||
if self.last_error_message == "directory_not_found":
|
||||
# 目录识别失败的特定错误信息
|
||||
reply = msgbox_frame(
|
||||
f"目录错误 - {APP_NAME}",
|
||||
@@ -517,18 +556,299 @@ class MainWindow(QMainWindow):
|
||||
# 直接调用文件对话框
|
||||
self.download_manager.file_dialog()
|
||||
else:
|
||||
# 网络错误或其他错误
|
||||
reply = msgbox_frame(
|
||||
f"错误 - {APP_NAME}",
|
||||
"\n访问云端配置失败,请检查网络状况或稍后再试。\n\n是否重新尝试连接?\n",
|
||||
QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No,
|
||||
)
|
||||
if reply.exec() == QMessageBox.StandardButton.Yes:
|
||||
# 重试获取配置
|
||||
self.fetch_cloud_config()
|
||||
# 检查是否处于离线模式
|
||||
if is_offline_mode and self.last_error_message == "network_error":
|
||||
# 如果是离线模式且错误是网络相关的,提示切换到在线模式
|
||||
reply = msgbox_frame(
|
||||
f"离线模式提示 - {APP_NAME}",
|
||||
"\n当前处于离线模式,但本地补丁文件不完整。\n\n是否切换到在线模式尝试下载?\n",
|
||||
QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No,
|
||||
)
|
||||
if reply.exec() == QMessageBox.StandardButton.Yes:
|
||||
# 切换到在线模式
|
||||
if self.ui_manager and hasattr(self.ui_manager, 'switch_work_mode'):
|
||||
self.ui_manager.switch_work_mode("online")
|
||||
# 重试获取配置
|
||||
self.fetch_cloud_config()
|
||||
else:
|
||||
# 网络错误或其他错误
|
||||
reply = msgbox_frame(
|
||||
f"错误 - {APP_NAME}",
|
||||
"\n访问云端配置失败,请检查网络状况或稍后再试。\n\n是否重新尝试连接?\n",
|
||||
QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No,
|
||||
)
|
||||
if reply.exec() == QMessageBox.StandardButton.Yes:
|
||||
# 重试获取配置
|
||||
self.fetch_cloud_config()
|
||||
else:
|
||||
# 按钮处于"开始安装"状态,正常执行安装流程
|
||||
self.download_manager.file_dialog()
|
||||
# 检查是否处于离线模式
|
||||
if is_offline_mode:
|
||||
# 如果是离线模式,使用离线安装流程
|
||||
# 先选择游戏目录
|
||||
self.selected_folder = QtWidgets.QFileDialog.getExistingDirectory(
|
||||
self, f"选择游戏所在【上级目录】 {APP_NAME}"
|
||||
)
|
||||
if not self.selected_folder:
|
||||
QtWidgets.QMessageBox.warning(
|
||||
self, f"通知 - {APP_NAME}", "\n未选择任何目录,请重新选择\n"
|
||||
)
|
||||
return
|
||||
|
||||
# 保存选择的目录到下载管理器
|
||||
self.download_manager.selected_folder = self.selected_folder
|
||||
|
||||
# 设置按钮状态
|
||||
self.ui.start_install_text.setText("正在安装")
|
||||
self.setEnabled(False)
|
||||
|
||||
# 清除游戏检测器的目录缓存
|
||||
if hasattr(self, 'game_detector') and hasattr(self.game_detector, 'clear_directory_cache'):
|
||||
self.game_detector.clear_directory_cache()
|
||||
|
||||
# 识别游戏目录
|
||||
game_dirs = self.game_detector.identify_game_directories_improved(self.selected_folder)
|
||||
|
||||
if not game_dirs:
|
||||
self.last_error_message = "directory_not_found"
|
||||
QtWidgets.QMessageBox.warning(
|
||||
self,
|
||||
f"目录错误 - {APP_NAME}",
|
||||
"\n未能识别到任何游戏目录。\n\n请确认您选择的是游戏的上级目录,并且该目录中包含NEKOPARA系列游戏文件夹。\n"
|
||||
)
|
||||
self.setEnabled(True)
|
||||
self.ui.start_install_text.setText("开始安装")
|
||||
return
|
||||
|
||||
# 显示游戏选择对话框
|
||||
dialog = QtWidgets.QDialog(self)
|
||||
dialog.setWindowTitle("选择要安装的游戏")
|
||||
dialog.resize(400, 300)
|
||||
|
||||
# 设置对话框样式
|
||||
dialog.setStyleSheet("""
|
||||
QDialog {
|
||||
background-color: #2D2D30;
|
||||
color: #FFFFFF;
|
||||
}
|
||||
QCheckBox {
|
||||
color: #FFFFFF;
|
||||
font-size: 14px;
|
||||
padding: 5px;
|
||||
margin: 5px;
|
||||
}
|
||||
QCheckBox:hover {
|
||||
background-color: #3E3E42;
|
||||
border-radius: 4px;
|
||||
}
|
||||
QCheckBox:checked {
|
||||
color: #F47A5B;
|
||||
}
|
||||
QPushButton {
|
||||
background-color: #3E3E42;
|
||||
color: #FFFFFF;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
padding: 8px 16px;
|
||||
font-size: 14px;
|
||||
min-width: 100px;
|
||||
}
|
||||
QPushButton:hover {
|
||||
background-color: #F47A5B;
|
||||
}
|
||||
QPushButton:pressed {
|
||||
background-color: #E06A4B;
|
||||
}
|
||||
""")
|
||||
|
||||
layout = QtWidgets.QVBoxLayout(dialog)
|
||||
layout.setContentsMargins(20, 20, 20, 20)
|
||||
layout.setSpacing(10)
|
||||
|
||||
# 添加标题标签
|
||||
title_label = QtWidgets.QLabel("选择要安装的游戏", dialog)
|
||||
title_label.setStyleSheet("font-size: 16px; font-weight: bold; color: #F47A5B; margin-bottom: 10px;")
|
||||
layout.addWidget(title_label)
|
||||
|
||||
# 添加分隔线
|
||||
line = QtWidgets.QFrame(dialog)
|
||||
line.setFrameShape(QtWidgets.QFrame.Shape.HLine)
|
||||
line.setFrameShadow(QtWidgets.QFrame.Shadow.Sunken)
|
||||
line.setStyleSheet("background-color: #3E3E42; margin: 10px 0px;")
|
||||
layout.addWidget(line)
|
||||
|
||||
# 添加游戏选择框
|
||||
game_checkboxes = {}
|
||||
scroll_area = QtWidgets.QScrollArea(dialog)
|
||||
scroll_area.setWidgetResizable(True)
|
||||
scroll_area.setStyleSheet("border: none; background-color: transparent;")
|
||||
|
||||
scroll_content = QtWidgets.QWidget(scroll_area)
|
||||
scroll_layout = QtWidgets.QVBoxLayout(scroll_content)
|
||||
scroll_layout.setContentsMargins(5, 5, 5, 5)
|
||||
scroll_layout.setSpacing(8)
|
||||
scroll_layout.setAlignment(Qt.AlignmentFlag.AlignTop)
|
||||
|
||||
for game_version in game_dirs.keys():
|
||||
checkbox = QtWidgets.QCheckBox(game_version, scroll_content)
|
||||
checkbox.setChecked(True) # 默认选中
|
||||
scroll_layout.addWidget(checkbox)
|
||||
game_checkboxes[game_version] = checkbox
|
||||
|
||||
scroll_content.setLayout(scroll_layout)
|
||||
scroll_area.setWidget(scroll_content)
|
||||
layout.addWidget(scroll_area)
|
||||
|
||||
# 添加按钮
|
||||
button_layout = QtWidgets.QHBoxLayout()
|
||||
button_layout.setSpacing(15)
|
||||
|
||||
# 全选按钮
|
||||
select_all_btn = QtWidgets.QPushButton("全选", dialog)
|
||||
select_all_btn.clicked.connect(lambda: self.select_all_games(game_checkboxes, True))
|
||||
|
||||
# 全不选按钮
|
||||
deselect_all_btn = QtWidgets.QPushButton("全不选", dialog)
|
||||
deselect_all_btn.clicked.connect(lambda: self.select_all_games(game_checkboxes, False))
|
||||
|
||||
# 确定和取消按钮
|
||||
ok_button = QtWidgets.QPushButton("确定", dialog)
|
||||
ok_button.setStyleSheet(ok_button.styleSheet() + "background-color: #007ACC;")
|
||||
|
||||
cancel_button = QtWidgets.QPushButton("取消", dialog)
|
||||
|
||||
# 添加按钮到布局
|
||||
button_layout.addWidget(select_all_btn)
|
||||
button_layout.addWidget(deselect_all_btn)
|
||||
button_layout.addStretch()
|
||||
button_layout.addWidget(ok_button)
|
||||
button_layout.addWidget(cancel_button)
|
||||
|
||||
layout.addLayout(button_layout)
|
||||
|
||||
# 连接信号
|
||||
ok_button.clicked.connect(dialog.accept)
|
||||
cancel_button.clicked.connect(dialog.reject)
|
||||
|
||||
# 显示对话框
|
||||
if dialog.exec() == QtWidgets.QDialog.DialogCode.Accepted:
|
||||
# 获取选择的游戏
|
||||
selected_games = []
|
||||
for game_version, checkbox in game_checkboxes.items():
|
||||
if checkbox.isChecked():
|
||||
selected_games.append(game_version)
|
||||
|
||||
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:
|
||||
# 在线模式下,检查版本是否过低
|
||||
if hasattr(self, 'version_warning') and self.version_warning:
|
||||
# 版本过低,提示用户更新
|
||||
msg_box = msgbox_frame(
|
||||
f"版本过低 - {APP_NAME}",
|
||||
"\n当前版本过低,无法使用在线下载功能。\n\n请更新到最新版本或切换到离线模式。\n",
|
||||
QMessageBox.StandardButton.Ok
|
||||
)
|
||||
msg_box.exec()
|
||||
else:
|
||||
# 版本正常,使用原有的下载流程
|
||||
self.download_manager.file_dialog()
|
||||
|
||||
def check_and_set_offline_mode(self):
|
||||
"""检查是否有离线补丁文件,如果有则自动切换到离线模式"""
|
||||
try:
|
||||
# 检查是否有离线补丁文件
|
||||
has_offline_patches = self.offline_mode_manager.has_offline_patches()
|
||||
|
||||
# 获取调试模式状态
|
||||
debug_mode = False
|
||||
if hasattr(self.debug_manager, '_is_debug_mode'):
|
||||
debug_mode = self.debug_manager._is_debug_mode()
|
||||
|
||||
# 检查配置中是否已设置离线模式
|
||||
offline_mode_enabled = False
|
||||
if isinstance(self.config, dict):
|
||||
offline_mode_enabled = self.config.get("offline_mode", False)
|
||||
|
||||
# 如果有离线补丁文件或者调试模式下强制启用离线模式
|
||||
if has_offline_patches or (debug_mode and offline_mode_enabled):
|
||||
# 设置离线模式
|
||||
self.offline_mode_manager.set_offline_mode(True)
|
||||
|
||||
# 更新UI中的离线模式选项
|
||||
if hasattr(self.ui_manager, 'offline_mode_action') and self.ui_manager.offline_mode_action:
|
||||
self.ui_manager.offline_mode_action.setChecked(True)
|
||||
self.ui_manager.online_mode_action.setChecked(False)
|
||||
|
||||
# 更新配置
|
||||
self.config["offline_mode"] = True
|
||||
self.save_config(self.config)
|
||||
|
||||
# 在离线模式下始终启用开始安装按钮
|
||||
self.set_start_button_enabled(True)
|
||||
|
||||
# 清除版本警告标志
|
||||
self.version_warning = False
|
||||
|
||||
if debug_mode:
|
||||
print(f"DEBUG: 已自动切换到离线模式,找到离线补丁文件: {list(self.offline_mode_manager.offline_patches.keys())}")
|
||||
print(f"DEBUG: 离线模式下启用开始安装按钮")
|
||||
else:
|
||||
# 如果没有离线补丁文件,确保使用在线模式
|
||||
self.offline_mode_manager.set_offline_mode(False)
|
||||
|
||||
# 更新UI中的在线模式选项
|
||||
if hasattr(self.ui_manager, 'online_mode_action') and self.ui_manager.online_mode_action:
|
||||
self.ui_manager.online_mode_action.setChecked(True)
|
||||
self.ui_manager.offline_mode_action.setChecked(False)
|
||||
|
||||
# 更新配置
|
||||
self.config["offline_mode"] = False
|
||||
self.save_config(self.config)
|
||||
|
||||
# 如果当前版本过低,设置版本警告标志
|
||||
if hasattr(self, 'last_error_message') and self.last_error_message == "update_required":
|
||||
# 设置版本警告标志
|
||||
self.version_warning = True
|
||||
|
||||
if debug_mode:
|
||||
print("DEBUG: 未找到离线补丁文件,使用在线模式")
|
||||
|
||||
# 确保标题标签显示正确的模式
|
||||
if hasattr(self, 'ui') and hasattr(self.ui, 'title_label'):
|
||||
from data.config import APP_NAME, APP_VERSION
|
||||
mode_indicator = "[离线模式]" if self.offline_mode_manager.is_in_offline_mode() else "[在线模式]"
|
||||
self.ui.title_label.setText(f"{APP_NAME} v{APP_VERSION} {mode_indicator}")
|
||||
|
||||
except Exception as e:
|
||||
# 捕获任何异常,确保程序不会崩溃
|
||||
print(f"错误: 检查离线模式时发生异常: {e}")
|
||||
# 默认使用在线模式
|
||||
if hasattr(self, 'offline_mode_manager'):
|
||||
self.offline_mode_manager.is_offline_mode = False
|
||||
|
||||
def select_all_games(self, game_checkboxes, checked):
|
||||
"""选择或取消选择所有游戏
|
||||
|
||||
Args:
|
||||
game_checkboxes: 游戏复选框字典
|
||||
checked: 是否选中
|
||||
"""
|
||||
for checkbox in game_checkboxes.values():
|
||||
checkbox.setChecked(checked)
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -542,7 +542,6 @@ class Ui_MainWindows(object):
|
||||
# setupUi
|
||||
|
||||
def retranslateUi(self, MainWindows):
|
||||
MainWindows.setWindowTitle(QCoreApplication.translate("MainWindows", f"{APP_NAME} v{APP_VERSION}", None))
|
||||
self.loadbg.setText("")
|
||||
self.vol1bg.setText("")
|
||||
self.vol2bg.setText("")
|
||||
|
||||
@@ -112,23 +112,38 @@ class HashManager:
|
||||
print(f"Error calculating hash for {file_path}: {e}")
|
||||
return results
|
||||
|
||||
def hash_pop_window(self, check_type="default"):
|
||||
def hash_pop_window(self, check_type="default", is_offline=False):
|
||||
"""显示文件检验窗口
|
||||
|
||||
Args:
|
||||
check_type: 检查类型,可以是 'pre'(预检查), 'after'(后检查), 'extraction'(解压后检查)
|
||||
|
||||
check_type: 检查类型,可以是 'pre'(预检查), 'after'(后检查), 'extraction'(解压后检查), 'offline_extraction'(离线解压), 'offline_verify'(离线验证)
|
||||
is_offline: 是否处于离线模式
|
||||
|
||||
Returns:
|
||||
QMessageBox: 消息框实例
|
||||
"""
|
||||
message = "\n正在检验文件状态...\n"
|
||||
|
||||
if check_type == "pre":
|
||||
message = "\n正在检查游戏文件以确定需要安装的补丁...\n"
|
||||
elif check_type == "after":
|
||||
message = "\n正在检验本地文件完整性...\n"
|
||||
elif check_type == "extraction":
|
||||
message = "\n正在验证下载的解压文件完整性...\n"
|
||||
if is_offline:
|
||||
# 离线模式的消息
|
||||
if check_type == "pre":
|
||||
message = "\n正在检查游戏文件以确定需要安装的补丁...\n"
|
||||
elif check_type == "after":
|
||||
message = "\n正在检验本地文件完整性...\n"
|
||||
elif check_type == "offline_verify":
|
||||
message = "\n正在验证本地补丁压缩文件完整性...\n"
|
||||
elif check_type == "offline_extraction":
|
||||
message = "\n正在解压安装补丁文件...\n"
|
||||
else:
|
||||
message = "\n正在处理离线补丁文件...\n"
|
||||
else:
|
||||
# 在线模式的消息
|
||||
if check_type == "pre":
|
||||
message = "\n正在检查游戏文件以确定需要安装的补丁...\n"
|
||||
elif check_type == "after":
|
||||
message = "\n正在检验本地文件完整性...\n"
|
||||
elif check_type == "extraction":
|
||||
message = "\n正在验证下载的解压文件完整性...\n"
|
||||
|
||||
msg_box = msgbox_frame(f"通知 - {APP_NAME}", message)
|
||||
msg_box.open()
|
||||
@@ -137,49 +152,94 @@ class HashManager:
|
||||
|
||||
def cfg_pre_hash_compare(self, install_paths, plugin_hash, installed_status):
|
||||
status_copy = installed_status.copy()
|
||||
debug_mode = False
|
||||
|
||||
# 尝试检测是否处于调试模式
|
||||
try:
|
||||
from data.config import CACHE
|
||||
debug_file = os.path.join(os.path.dirname(CACHE), "debug_mode.txt")
|
||||
debug_mode = os.path.exists(debug_file)
|
||||
except:
|
||||
pass
|
||||
|
||||
for game_version, install_path in install_paths.items():
|
||||
if not os.path.exists(install_path):
|
||||
status_copy[game_version] = False
|
||||
if debug_mode:
|
||||
print(f"DEBUG: 哈希预检查 - {game_version} 补丁文件不存在: {install_path}")
|
||||
continue
|
||||
|
||||
try:
|
||||
expected_hash = plugin_hash.get(game_version, "")
|
||||
file_hash = self.hash_calculate(install_path)
|
||||
if file_hash == plugin_hash.get(game_version):
|
||||
|
||||
if debug_mode:
|
||||
print(f"DEBUG: 哈希预检查 - {game_version}")
|
||||
print(f"DEBUG: 文件路径: {install_path}")
|
||||
print(f"DEBUG: 预期哈希值: {expected_hash}")
|
||||
print(f"DEBUG: 实际哈希值: {file_hash}")
|
||||
print(f"DEBUG: 哈希匹配: {file_hash == expected_hash}")
|
||||
|
||||
if file_hash == expected_hash:
|
||||
status_copy[game_version] = True
|
||||
else:
|
||||
status_copy[game_version] = False
|
||||
except Exception:
|
||||
except Exception as e:
|
||||
status_copy[game_version] = False
|
||||
if debug_mode:
|
||||
print(f"DEBUG: 哈希预检查异常 - {game_version}: {str(e)}")
|
||||
|
||||
return status_copy
|
||||
|
||||
def cfg_after_hash_compare(self, install_paths, plugin_hash, installed_status):
|
||||
debug_mode = False
|
||||
|
||||
# 尝试检测是否处于调试模式
|
||||
try:
|
||||
from data.config import CACHE
|
||||
debug_file = os.path.join(os.path.dirname(CACHE), "debug_mode.txt")
|
||||
debug_mode = os.path.exists(debug_file)
|
||||
except:
|
||||
pass
|
||||
|
||||
file_paths = [
|
||||
install_paths[game] for game in plugin_hash if installed_status.get(game)
|
||||
]
|
||||
hash_results = self.calculate_hashes_in_parallel(file_paths)
|
||||
|
||||
for game, hash_value in plugin_hash.items():
|
||||
for game, expected_hash in plugin_hash.items():
|
||||
if installed_status.get(game):
|
||||
file_path = install_paths[game]
|
||||
file_hash = hash_results.get(file_path)
|
||||
|
||||
if debug_mode:
|
||||
print(f"DEBUG: 哈希后检查 - {game}")
|
||||
print(f"DEBUG: 文件路径: {file_path}")
|
||||
print(f"DEBUG: 预期哈希值: {expected_hash}")
|
||||
print(f"DEBUG: 实际哈希值: {file_hash if file_hash else '计算失败'}")
|
||||
|
||||
if file_hash is None:
|
||||
installed_status[game] = False
|
||||
if debug_mode:
|
||||
print(f"DEBUG: 哈希后检查失败 - 无法计算文件哈希值: {game}")
|
||||
return {
|
||||
"passed": False,
|
||||
"game": game,
|
||||
"message": f"\n无法计算 {game} 的文件哈希值,文件可能已损坏或被占用。\n"
|
||||
}
|
||||
|
||||
if file_hash != hash_value:
|
||||
if file_hash != expected_hash:
|
||||
installed_status[game] = False
|
||||
if debug_mode:
|
||||
print(f"DEBUG: 哈希后检查失败 - 哈希值不匹配: {game}")
|
||||
return {
|
||||
"passed": False,
|
||||
"game": game,
|
||||
"message": f"\n检测到 {game} 的文件哈希值不匹配。\n"
|
||||
}
|
||||
|
||||
if debug_mode:
|
||||
print(f"DEBUG: 哈希后检查通过 - 所有文件哈希值匹配")
|
||||
return {"passed": True}
|
||||
|
||||
class AdminPrivileges:
|
||||
@@ -580,8 +640,17 @@ class HostsManager:
|
||||
return False
|
||||
|
||||
def censor_url(text):
|
||||
"""Censors URLs in a given text string."""
|
||||
"""Censors URLs in a given text string, replacing them with a protection message.
|
||||
|
||||
Args:
|
||||
text: 要处理的文本
|
||||
|
||||
Returns:
|
||||
str: 处理后的文本,URL被完全隐藏
|
||||
"""
|
||||
if not isinstance(text, str):
|
||||
text = str(text)
|
||||
|
||||
# 匹配URL并替换为固定文本
|
||||
url_pattern = re.compile(r'https?://[^\s/$.?#].[^\s]*')
|
||||
return url_pattern.sub('***URL HIDDEN***', text)
|
||||
return url_pattern.sub('***URL protection***', text)
|
||||
@@ -3,6 +3,15 @@ import logging
|
||||
import os
|
||||
from data.config import CACHE
|
||||
|
||||
class URLCensorFormatter(logging.Formatter):
|
||||
"""自定义的日志格式化器,用于隐藏日志消息中的URL"""
|
||||
|
||||
def format(self, record):
|
||||
# 先使用原始的format方法格式化日志
|
||||
formatted_message = super().format(record)
|
||||
# 然后对格式化后的消息进行URL审查
|
||||
return censor_url(formatted_message)
|
||||
|
||||
class Logger:
|
||||
def __init__(self, filename, stream):
|
||||
self.terminal = stream
|
||||
@@ -53,7 +62,7 @@ def setup_logger(name):
|
||||
console_handler.setLevel(logging.INFO)
|
||||
|
||||
# 创建格式器并添加到处理器
|
||||
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
|
||||
formatter = URLCensorFormatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
|
||||
file_handler.setFormatter(formatter)
|
||||
console_handler.setFormatter(formatter)
|
||||
|
||||
|
||||
@@ -18,7 +18,8 @@ class ConfigFetchThread(QThread):
|
||||
try:
|
||||
if self.debug_mode:
|
||||
print("--- Starting to fetch cloud config ---")
|
||||
print(f"DEBUG: Requesting URL: {self.url}")
|
||||
# 完全隐藏URL
|
||||
print(f"DEBUG: Requesting URL: ***URL protection***")
|
||||
print(f"DEBUG: Using Headers: {self.headers}")
|
||||
|
||||
response = requests.get(self.url, headers=self.headers, timeout=10)
|
||||
@@ -26,7 +27,18 @@ class ConfigFetchThread(QThread):
|
||||
if self.debug_mode:
|
||||
print(f"DEBUG: Response Status Code: {response.status_code}")
|
||||
print(f"DEBUG: Response Headers: {response.headers}")
|
||||
print(f"DEBUG: Response Text: {response.text}")
|
||||
|
||||
# 解析并隐藏响应中的敏感URL
|
||||
try:
|
||||
response_data = response.json()
|
||||
# 创建安全版本用于日志输出
|
||||
safe_response = self._create_safe_config_for_logging(response_data)
|
||||
print(f"DEBUG: Response Text: {json.dumps(safe_response, indent=2)}")
|
||||
except:
|
||||
# 如果不是JSON,直接打印文本
|
||||
from utils.helpers import censor_url
|
||||
censored_text = censor_url(response.text)
|
||||
print(f"DEBUG: Response Text: {censored_text}")
|
||||
|
||||
response.raise_for_status()
|
||||
|
||||
@@ -62,4 +74,28 @@ class ConfigFetchThread(QThread):
|
||||
self.finished.emit(None, error_msg)
|
||||
finally:
|
||||
if self.debug_mode:
|
||||
print("--- Finished fetching cloud config ---")
|
||||
print("--- Finished fetching cloud config ---")
|
||||
|
||||
def _create_safe_config_for_logging(self, config_data):
|
||||
"""创建用于日志记录的安全配置副本,隐藏敏感URL
|
||||
|
||||
Args:
|
||||
config_data: 原始配置数据
|
||||
|
||||
Returns:
|
||||
dict: 安全的配置数据副本
|
||||
"""
|
||||
if not config_data or not isinstance(config_data, dict):
|
||||
return config_data
|
||||
|
||||
# 创建深拷贝,避免修改原始数据
|
||||
import copy
|
||||
safe_config = copy.deepcopy(config_data)
|
||||
|
||||
# 隐藏敏感URL
|
||||
for key in safe_config:
|
||||
if isinstance(safe_config[key], dict) and "url" in safe_config[key]:
|
||||
# 完全隐藏URL
|
||||
safe_config[key]["url"] = "***URL protection***"
|
||||
|
||||
return safe_config
|
||||
@@ -200,7 +200,15 @@ class DownloadThread(QThread):
|
||||
|
||||
command.append(self.url)
|
||||
|
||||
print(f"即将执行的 Aria2c 命令: {' '.join(command)}")
|
||||
# 创建一个安全的命令副本,隐藏URL
|
||||
safe_command = command.copy()
|
||||
if len(safe_command) > 0:
|
||||
# 替换最后一个参数(URL)为安全版本
|
||||
url = safe_command[-1]
|
||||
if isinstance(url, str) and url.startswith("http"):
|
||||
safe_command[-1] = "***URL protection***"
|
||||
|
||||
print(f"即将执行的 Aria2c 命令: {' '.join(safe_command)}")
|
||||
|
||||
creation_flags = subprocess.CREATE_NO_WINDOW if sys.platform == 'win32' else 0
|
||||
self.process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True, encoding='utf-8', errors='replace', creationflags=creation_flags)
|
||||
@@ -220,8 +228,11 @@ class DownloadThread(QThread):
|
||||
else:
|
||||
break
|
||||
|
||||
full_output.append(line)
|
||||
print(line.strip())
|
||||
# 处理输出行,隐藏可能包含的URL
|
||||
from utils.helpers import censor_url
|
||||
censored_line = censor_url(line)
|
||||
full_output.append(censored_line)
|
||||
print(censored_line.strip())
|
||||
|
||||
match = progress_pattern.search(line)
|
||||
if match:
|
||||
|
||||
@@ -30,6 +30,9 @@ class IpOptimizer:
|
||||
|
||||
ip_txt_path = resource_path("ip.txt")
|
||||
|
||||
# 隐藏敏感URL
|
||||
safe_url = "***URL protection***"
|
||||
|
||||
command = [
|
||||
cst_path,
|
||||
"-n", "1000", # 延迟测速线程数
|
||||
@@ -39,10 +42,17 @@ class IpOptimizer:
|
||||
"-dd", # 禁用下载测速
|
||||
"-o"," " # 不写入结果文件
|
||||
]
|
||||
|
||||
# 创建用于显示的安全命令副本
|
||||
safe_command = command.copy()
|
||||
for i, arg in enumerate(safe_command):
|
||||
if arg == url:
|
||||
safe_command[i] = safe_url
|
||||
|
||||
creation_flags = subprocess.CREATE_NO_WINDOW if sys.platform == 'win32' else 0
|
||||
|
||||
print("--- CloudflareSpeedTest 开始执行 ---")
|
||||
print(f"执行命令: {' '.join(safe_command)}")
|
||||
|
||||
self.process = subprocess.Popen(
|
||||
command,
|
||||
@@ -91,7 +101,9 @@ class IpOptimizer:
|
||||
|
||||
timeout_counter = 0
|
||||
|
||||
cleaned_line = line.strip()
|
||||
# 处理输出行,隐藏可能包含的URL
|
||||
from utils.helpers import censor_url
|
||||
cleaned_line = censor_url(line.strip())
|
||||
if cleaned_line:
|
||||
print(cleaned_line)
|
||||
|
||||
@@ -157,6 +169,9 @@ class IpOptimizer:
|
||||
print(f"错误: ipv6.txt 未在资源路径中找到。")
|
||||
return None
|
||||
|
||||
# 隐藏敏感URL
|
||||
safe_url = "***URL protection***"
|
||||
|
||||
command = [
|
||||
cst_path,
|
||||
"-n", "1000", # 延迟测速线程数
|
||||
@@ -166,10 +181,17 @@ class IpOptimizer:
|
||||
"-dd", # 禁用下载测速
|
||||
"-o", " " # 不写入结果文件
|
||||
]
|
||||
|
||||
# 创建用于显示的安全命令副本
|
||||
safe_command = command.copy()
|
||||
for i, arg in enumerate(safe_command):
|
||||
if arg == url:
|
||||
safe_command[i] = safe_url
|
||||
|
||||
creation_flags = subprocess.CREATE_NO_WINDOW if sys.platform == 'win32' else 0
|
||||
|
||||
print("--- CloudflareSpeedTest IPv6 开始执行 ---")
|
||||
print(f"执行命令: {' '.join(safe_command)}")
|
||||
|
||||
self.process = subprocess.Popen(
|
||||
command,
|
||||
@@ -218,7 +240,9 @@ class IpOptimizer:
|
||||
|
||||
timeout_counter = 0
|
||||
|
||||
cleaned_line = line.strip()
|
||||
# 处理输出行,隐藏可能包含的URL
|
||||
from utils.helpers import censor_url
|
||||
cleaned_line = censor_url(line.strip())
|
||||
if cleaned_line:
|
||||
print(cleaned_line)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user