feat(core): 重构核心模块并添加新功能

- 重构 __init__.py,引入新模块:WindowManager, GameDetector, PatchManager, ConfigManager
- 更新 DebugManager,添加 set_ui_manager 方法
- 改进 DownloadManager,优化下载流程和错误处理
- 重构 MainWindow 类,移除冗余代码,委托部分功能给新模块
- 更新 UI 组件,简化界面逻辑
- 优化导入结构,提高代码可维护性
This commit is contained in:
hyb-oyqq
2025-07-30 17:18:57 +08:00
parent 331f7a25d2
commit a411461f63
11 changed files with 999 additions and 668 deletions

View File

@@ -23,7 +23,8 @@ from workers import (
HashThread, ExtractionThread, ConfigFetchThread
)
from core import (
MultiStageAnimations, UIManager, DownloadManager, DebugManager
MultiStageAnimations, UIManager, DownloadManager, DebugManager,
WindowManager, GameDetector, PatchManager, ConfigManager
)
class MainWindow(QMainWindow):
@@ -41,31 +42,13 @@ class MainWindow(QMainWindow):
self.setMinimumSize(QSize(1024, 576))
self.setMaximumSize(QSize(1280, 720))
# 窗口比例 (16:9)
self.aspect_ratio = 16 / 9
# 拖动窗口相关变量
self._drag_position = QPoint()
self._is_dragging = False
# 初始化UI (从Ui_install.py导入)
self.ui = Ui_MainWindows()
self.ui.setupUi(self)
# 设置圆角窗口
self.setRoundedCorners()
# 初始化配置
self.config = load_config()
# 初始化状态变量
self.cloud_config = None
self.config_valid = False # 添加配置有效标志
self.installed_status = {f"NEKOPARA Vol.{i}": False for i in range(1, 5)}
self.installed_status["NEKOPARA After"] = False # 添加After的状态
self.hash_msg_box = None
self.progress_window = None
# 初始化工具类
self.hash_manager = HashManager(BLOCK_SIZE)
self.admin_privileges = AdminPrivileges()
@@ -77,9 +60,26 @@ class MainWindow(QMainWindow):
# 首先设置UI - 确保debug_action已初始化
self.ui_manager.setup_ui()
# 初始化新的管理器类
self.window_manager = WindowManager(self)
self.debug_manager = DebugManager(self)
# 为debug_manager设置ui_manager引用
self.debug_manager.set_ui_manager(self.ui_manager)
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)
# 初始化下载管理器 - 应该放在其他管理器之后,因为它可能依赖于它们
self.download_manager = DownloadManager(self)
# 初始化状态变量
self.cloud_config = None
self.config_valid = False # 添加配置有效标志
self.patch_manager.initialize_status()
self.installed_status = self.patch_manager.get_status() # 获取初始化后的状态
self.hash_msg_box = None
self.progress_window = None
# 设置关闭按钮事件连接
if hasattr(self.ui, 'close_btn'):
self.ui.close_btn.clicked.connect(self.close)
@@ -126,105 +126,18 @@ class MainWindow(QMainWindow):
# 窗口显示后延迟100ms启动动画
QTimer.singleShot(100, self.start_animations)
def setRoundedCorners(self):
"""设置窗口圆角"""
# 实现圆角窗口
path = QPainterPath()
path.addRoundedRect(self.rect(), 20, 20)
mask = QRegion(path.toFillPolygon().toPolygon())
self.setMask(mask)
# 更新resize事件时更新圆角
self.updateRoundedCorners = True
# 添加鼠标事件处理,实现窗口拖动
# 窗口事件处理 - 委托给WindowManager
def mousePressEvent(self, event):
if event.button() == Qt.MouseButton.LeftButton:
# 只有当鼠标在标题栏区域时才可以拖动
if hasattr(self.ui, 'title_bar') and self.ui.title_bar.geometry().contains(event.position().toPoint()):
self._is_dragging = True
self._drag_position = event.globalPosition().toPoint() - self.frameGeometry().topLeft()
event.accept()
self.window_manager.handle_mouse_press(event)
def mouseMoveEvent(self, event):
if event.buttons() & Qt.MouseButton.LeftButton and self._is_dragging:
self.move(event.globalPosition().toPoint() - self._drag_position)
event.accept()
self.window_manager.handle_mouse_move(event)
def mouseReleaseEvent(self, event):
if event.button() == Qt.MouseButton.LeftButton:
self._is_dragging = False
event.accept()
self.window_manager.handle_mouse_release(event)
def resizeEvent(self, event):
"""当窗口大小改变时更新圆角和维持纵横比"""
# 计算基于当前宽度的合适高度以维持16:9比例
new_width = event.size().width()
new_height = int(new_width / self.aspect_ratio)
if new_height != event.size().height():
# 阻止变形,保持比例
self.resize(new_width, new_height)
# 更新主容器大小
if hasattr(self.ui, 'main_container'):
self.ui.main_container.setGeometry(0, 0, new_width, new_height)
# 更新内容容器大小
if hasattr(self.ui, 'content_container'):
self.ui.content_container.setGeometry(0, 0, new_width, new_height)
# 更新标题栏宽度和高度
if hasattr(self.ui, 'title_bar'):
self.ui.title_bar.setGeometry(0, 0, new_width, 35)
# 更新菜单区域
if hasattr(self.ui, 'menu_area'):
self.ui.menu_area.setGeometry(0, 35, new_width, 30)
# 更新内容区域大小
if hasattr(self.ui, 'inner_content'):
self.ui.inner_content.setGeometry(0, 65, new_width, new_height - 65)
# 更新背景图大小 - 使用setScaledContents简化处理
if hasattr(self.ui, 'Mainbg'):
self.ui.Mainbg.setGeometry(0, 0, new_width, new_height - 65)
# 使用setScaledContents=True不需要手动缩放
if hasattr(self.ui, 'loadbg'):
self.ui.loadbg.setGeometry(0, 0, new_width, new_height - 65)
# 调整按钮位置 - 固定在右侧
right_margin = 20 # 减小右边距,使按钮更靠右
if hasattr(self.ui, 'button_container'):
btn_width = 211 # 扩大后的容器宽度
btn_height = 111 # 扩大后的容器高度
x_pos = new_width - btn_width - right_margin
y_pos = int((new_height - 65) * 0.28) - 10 # 调整为更靠上的位置
self.ui.button_container.setGeometry(x_pos, y_pos, btn_width, btn_height)
# 添加卸载补丁按钮容器的位置调整
if hasattr(self.ui, 'uninstall_container'):
btn_width = 211 # 扩大后的容器宽度
btn_height = 111 # 扩大后的容器高度
x_pos = new_width - btn_width - right_margin
y_pos = int((new_height - 65) * 0.46) - 10 # 调整为中间位置
self.ui.uninstall_container.setGeometry(x_pos, y_pos, btn_width, btn_height)
if hasattr(self.ui, 'exit_container'):
btn_width = 211 # 扩大后的容器宽度
btn_height = 111 # 扩大后的容器高度
x_pos = new_width - btn_width - right_margin
y_pos = int((new_height - 65) * 0.64) - 10 # 调整为更靠下的位置
self.ui.exit_container.setGeometry(x_pos, y_pos, btn_width, btn_height)
# 更新圆角
if hasattr(self, 'updateRoundedCorners') and self.updateRoundedCorners:
path = QPainterPath()
path.addRoundedRect(self.rect(), 20, 20)
mask = QRegion(path.toFillPolygon().toPolygon())
self.setMask(mask)
self.window_manager.handle_resize(event)
super().resizeEvent(event)
def start_animations(self):
@@ -251,30 +164,37 @@ class MainWindow(QMainWindow):
else:
self.set_start_button_enabled(False)
def set_start_button_enabled(self, enabled):
def set_start_button_enabled(self, enabled, installing=False):
"""设置开始安装按钮的启用状态和视觉效果
Args:
enabled: 是否启用按钮
installing: 是否正在安装中
"""
self.ui.start_install_btn.setEnabled(True) # 始终启用按钮,以便捕获点击事件
# 根据状态修改文本内容,但不再修改颜色样式
if enabled:
self.ui.start_install_text.setText("开始安装")
if installing:
# 安装中状态 - 按钮被禁用但显示"正在安装"
self.ui.start_install_btn.setEnabled(False)
self.ui.start_install_text.setText("正在安装")
self.install_button_enabled = False
else:
self.ui.start_install_text.setText("!无法安装!")
# 正常状态 - 按钮可点击但根据enabled决定是否显示"无法安装"
self.ui.start_install_btn.setEnabled(True) # 始终启用按钮,以便捕获点击事件
# 记录当前按钮状态,用于点击事件处理
self.install_button_enabled = enabled
# 根据状态修改文本内容
if enabled:
self.ui.start_install_text.setText("开始安装")
else:
self.ui.start_install_text.setText("!无法安装!")
# 记录当前按钮状态,用于点击事件处理
self.install_button_enabled = enabled
def fetch_cloud_config(self):
"""获取云端配置"""
headers = {"User-Agent": UA}
debug_mode = self.ui_manager.debug_action.isChecked() if self.ui_manager.debug_action else False
self.config_fetch_thread = ConfigFetchThread(CONFIG_URL, headers, debug_mode, self)
self.config_fetch_thread.finished.connect(self.on_config_fetched)
self.config_fetch_thread.start()
self.config_manager.fetch_cloud_config(
lambda url, headers, debug_mode, parent=None: ConfigFetchThread(url, headers, debug_mode, self),
self.on_config_fetched
)
def on_config_fetched(self, data, error_message):
"""云端配置获取完成的回调处理
@@ -283,71 +203,30 @@ class MainWindow(QMainWindow):
data: 获取到的配置数据
error_message: 错误信息,如果有
"""
# 定义debug_mode变量在方法开头
debug_mode = self.ui_manager.debug_action.isChecked() if hasattr(self.ui_manager, 'debug_action') and self.ui_manager.debug_action else False
# 处理返回结果
result = self.config_manager.on_config_fetched(data, error_message)
if error_message:
# 标记配置无效
self.config_valid = False
# 记录错误信息,用于按钮点击时显示
if error_message == "update_required":
self.last_error_message = "update_required"
msg_box = msgbox_frame(
f"更新提示 - {APP_NAME}",
"\n当前版本过低,请及时更新。\n",
QMessageBox.StandardButton.Ok,
)
msg_box.exec()
# 在浏览器中打开项目主页
webbrowser.open("https://github.com/hyb-oyqq/FRAISEMOE-Addons-Installer-NEXT/")
# 根据返回的操作执行相应动作
if result and "action" in result:
if result["action"] == "exit":
# 强制关闭程序
self.shutdown_app(force_exit=True)
return
elif "missing_keys" in error_message:
self.last_error_message = "missing_keys"
missing_versions = error_message.split(":")[1]
msg_box = msgbox_frame(
f"配置缺失 - {APP_NAME}",
f'\n云端缺失下载链接,可能云服务器正在维护,不影响其他版本下载。\n当前缺失版本:"{missing_versions}"\n',
QMessageBox.StandardButton.Ok,
)
msg_box.exec()
# 对于部分缺失,仍然允许使用,因为可能只影响部分游戏版本
self.config_valid = True
else:
# 设置网络错误标记
self.last_error_message = "network_error"
# 显示通用错误消息只在debug模式下显示详细错误
error_msg = "访问云端配置失败,请检查网络状况或稍后再试。"
if debug_mode and "详细错误:" in error_message:
msg_box = msgbox_frame(
f"错误 - {APP_NAME}",
f"\n{error_message}\n",
QMessageBox.StandardButton.Ok,
)
else:
msg_box = msgbox_frame(
f"错误 - {APP_NAME}",
f"\n{error_msg}\n",
QMessageBox.StandardButton.Ok,
)
msg_box.exec()
# 在无法连接到云端时禁用开始安装按钮
elif result["action"] == "disable_button":
# 禁用开始安装按钮
self.set_start_button_enabled(False)
else:
self.cloud_config = data
# 标记配置有效
self.config_valid = True
# 清除错误信息
self.last_error_message = ""
if debug_mode:
print("--- Cloud config fetched successfully ---")
print(json.dumps(data, indent=2))
# 确保按钮在成功获取配置时启用
self.set_start_button_enabled(True)
# 检查是否有后续操作
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)
# 同步状态
self.cloud_config = self.config_manager.get_cloud_config()
self.config_valid = self.config_manager.is_config_valid()
self.last_error_message = self.config_manager.get_last_error()
def toggle_debug_mode(self, checked):
"""切换调试模式
@@ -359,7 +238,7 @@ class MainWindow(QMainWindow):
def save_config(self, config):
"""保存配置的便捷方法"""
save_config(config)
self.config_manager.save_config(config)
def create_download_thread(self, url, _7z_path, game_version):
"""创建下载线程
@@ -631,7 +510,7 @@ class MainWindow(QMainWindow):
# 获取游戏目录
from PySide6.QtWidgets import QFileDialog
debug_mode = self.ui_manager.debug_action.isChecked() if hasattr(self.ui_manager, 'debug_action') and self.ui_manager.debug_action else False
debug_mode = self.debug_manager._is_debug_mode()
# 提示用户选择目录
file_dialog_info = "选择游戏上级目录" if debug_mode else "选择游戏目录"
@@ -644,7 +523,7 @@ class MainWindow(QMainWindow):
print(f"DEBUG: 卸载功能 - 用户选择了目录: {selected_folder}")
# 首先尝试将选择的目录视为上级目录,使用增强的目录识别功能
game_dirs = self.identify_game_directories_improved(selected_folder)
game_dirs = self.game_detector.identify_game_directories_improved(selected_folder)
if game_dirs and len(game_dirs) > 0:
# 找到了游戏目录,显示选择对话框
@@ -680,26 +559,9 @@ class MainWindow(QMainWindow):
)
if reply == QMessageBox.StandardButton.Yes:
success_count = 0
fail_count = 0
for version, path in game_dirs.items():
try:
if self.uninstall_patch(path, version):
success_count += 1
else:
fail_count += 1
except Exception as e:
if debug_mode:
print(f"DEBUG: 卸载 {version} 时出错: {str(e)}")
fail_count += 1
# 显示批量卸载结果
QMessageBox.information(
self,
f"批量卸载完成 - {APP_NAME}",
f"\n批量卸载完成!\n成功: {success_count}\n失败: {fail_count}\n",
QMessageBox.StandardButton.Ok,
)
# 使用批量卸载方法
success_count, fail_count = self.patch_manager.batch_uninstall_patches(game_dirs)
self.patch_manager.show_uninstall_result(success_count, fail_count)
else:
# 卸载选中的单个游戏
game_version = selected_game
@@ -710,7 +572,7 @@ class MainWindow(QMainWindow):
if debug_mode:
print(f"DEBUG: 卸载功能 - 未在上级目录找到游戏,尝试将选择的目录视为游戏目录")
game_version = self.identify_game_version(selected_folder)
game_version = self.game_detector.identify_game_version(selected_folder)
if game_version:
if debug_mode:
@@ -735,7 +597,7 @@ class MainWindow(QMainWindow):
game_dir: 游戏目录
game_version: 游戏版本
"""
debug_mode = self.ui_manager.debug_action.isChecked() if hasattr(self.ui_manager, 'debug_action') and self.ui_manager.debug_action else False
debug_mode = self.debug_manager._is_debug_mode()
# 确认卸载
reply = QMessageBox.question(
@@ -750,450 +612,6 @@ class MainWindow(QMainWindow):
return
# 开始卸载补丁
self.uninstall_patch(game_dir, game_version)
def identify_game_version(self, game_dir):
"""识别游戏版本
Args:
game_dir: 游戏目录路径
Returns:
str: 游戏版本名称如果不是有效的游戏目录则返回None
"""
import os
import re
# 调试模式
debug_mode = self.ui_manager.debug_action.isChecked() if hasattr(self.ui_manager, 'debug_action') and self.ui_manager.debug_action else False
if debug_mode:
print(f"DEBUG: 尝试识别游戏版本: {game_dir}")
# 先通过目录名称进行初步推测(这将作为递归搜索的提示)
dir_name = os.path.basename(game_dir).lower()
potential_version = None
vol_num = None
# 提取卷号或判断是否是After
if "vol" in dir_name or "vol." in dir_name:
vol_match = re.search(r"vol(?:\.|\s*)?(\d+)", dir_name)
if vol_match:
vol_num = vol_match.group(1)
potential_version = f"NEKOPARA Vol.{vol_num}"
if debug_mode:
print(f"DEBUG: 从目录名推测游戏版本: {potential_version}, 卷号: {vol_num}")
elif "after" in dir_name:
potential_version = "NEKOPARA After"
if debug_mode:
print(f"DEBUG: 从目录名推测游戏版本: NEKOPARA After")
# 检查是否为NEKOPARA游戏目录
# 通过检查游戏可执行文件来识别游戏版本
for game_version, info in GAME_INFO.items():
# 尝试多种可能的可执行文件名变体
exe_variants = [
info["exe"], # 标准文件名
info["exe"] + ".nocrack", # Steam加密版本
info["exe"].replace(".exe", ""), # 无扩展名版本
info["exe"].replace("NEKOPARA", "nekopara").lower(), # 全小写变体
info["exe"].lower(), # 小写变体
info["exe"].lower() + ".nocrack", # 小写变体的Steam加密版本
]
# 对于Vol.3可能有特殊名称
if "Vol.3" in game_version:
# 增加可能的卷3特定的变体
exe_variants.extend([
"NEKOPARAVol3.exe",
"NEKOPARAVol3.exe.nocrack",
"nekoparavol3.exe",
"nekoparavol3.exe.nocrack",
"nekopara_vol3.exe",
"nekopara_vol3.exe.nocrack",
"vol3.exe",
"vol3.exe.nocrack"
])
for exe_variant in exe_variants:
exe_path = os.path.join(game_dir, exe_variant)
if os.path.exists(exe_path):
if debug_mode:
print(f"DEBUG: 通过可执行文件确认游戏版本: {game_version}, 文件: {exe_variant}")
return game_version
# 如果没有直接匹配,尝试递归搜索
if potential_version:
# 从预测的版本中获取卷号或确认是否是After
is_after = "After" in potential_version
if not vol_num and not is_after:
vol_match = re.search(r"Vol\.(\d+)", potential_version)
if vol_match:
vol_num = vol_match.group(1)
# 递归搜索可执行文件
for root, dirs, files in os.walk(game_dir):
for file in files:
file_lower = file.lower()
if file.endswith('.exe') or file.endswith('.exe.nocrack'):
# 检查文件名中是否包含卷号或关键词
if ((vol_num and (f"vol{vol_num}" in file_lower or
f"vol.{vol_num}" in file_lower or
f"vol {vol_num}" in file_lower)) or
(is_after and "after" in file_lower)):
if debug_mode:
print(f"DEBUG: 通过递归搜索确认游戏版本: {potential_version}, 文件: {file}")
return potential_version
# 如果仍然没有找到,基于目录名的推测返回结果
if potential_version:
if debug_mode:
print(f"DEBUG: 基于目录名返回推测的游戏版本: {potential_version}")
return potential_version
if debug_mode:
print(f"DEBUG: 无法识别游戏版本: {game_dir}")
return None
def identify_game_directories_improved(self, selected_folder):
"""改进的游戏目录识别,支持大小写不敏感和特殊字符处理
Args:
selected_folder: 选择的上级目录
Returns:
dict: 游戏版本到游戏目录的映射
"""
import os
import re
# 添加debug日志
debug_mode = self.ui_manager.debug_action.isChecked() if hasattr(self.ui_manager, 'debug_action') and self.ui_manager.debug_action else False
if debug_mode:
print(f"--- 开始识别目录: {selected_folder} ---")
game_paths = {}
# 获取上级目录中的所有文件夹
try:
all_dirs = [d for d in os.listdir(selected_folder) if os.path.isdir(os.path.join(selected_folder, d))]
if debug_mode:
print(f"DEBUG: 找到以下子目录: {all_dirs}")
except Exception as e:
if debug_mode:
print(f"DEBUG: 无法读取目录 {selected_folder}: {str(e)}")
return {}
for game, info in GAME_INFO.items():
expected_dir = info["install_path"].split("/")[0] # 例如 "NEKOPARA Vol. 1"
expected_exe = info["exe"] # 标准可执行文件名
if debug_mode:
print(f"DEBUG: 搜索游戏 {game}, 预期目录: {expected_dir}, 预期可执行文件: {expected_exe}")
# 尝试不同的匹配方法
found_dir = None
# 1. 精确匹配
if expected_dir in all_dirs:
found_dir = expected_dir
if debug_mode:
print(f"DEBUG: 精确匹配成功: {expected_dir}")
# 2. 大小写不敏感匹配
if not found_dir:
for dir_name in all_dirs:
if expected_dir.lower() == dir_name.lower():
found_dir = dir_name
if debug_mode:
print(f"DEBUG: 大小写不敏感匹配成功: {dir_name}")
break
# 3. 更模糊的匹配(允许特殊字符差异)
if not found_dir:
# 准备用于模糊匹配的正则表达式模式
# 替换空格为可选空格或连字符,替换点为可选点
pattern_text = expected_dir.replace(" ", "[ -]?").replace(".", "\\.?")
pattern = re.compile(f"^{pattern_text}$", re.IGNORECASE)
for dir_name in all_dirs:
if pattern.match(dir_name):
found_dir = dir_name
if debug_mode:
print(f"DEBUG: 模糊匹配成功: {dir_name} 匹配模式 {pattern_text}")
break
# 4. 如果还是没找到,尝试更宽松的匹配
if not found_dir:
vol_match = re.search(r"vol(?:\.|\s*)?(\d+)", expected_dir, re.IGNORECASE)
vol_num = None
if vol_match:
vol_num = vol_match.group(1)
if debug_mode:
print(f"DEBUG: 提取卷号: {vol_num}")
is_after = "after" in expected_dir.lower()
for dir_name in all_dirs:
dir_lower = dir_name.lower()
# 对于After特殊处理
if is_after and "after" in dir_lower:
found_dir = dir_name
if debug_mode:
print(f"DEBUG: After特殊匹配成功: {dir_name}")
break
# 对于Vol特殊处理
if vol_num:
# 查找目录名中的卷号
dir_vol_match = re.search(r"vol(?:\.|\s*)?(\d+)", dir_lower)
if dir_vol_match and dir_vol_match.group(1) == vol_num:
found_dir = dir_name
if debug_mode:
print(f"DEBUG: 卷号匹配成功: {dir_name} 卷号 {vol_num}")
break
# 如果找到匹配的目录验证exe文件是否存在
if found_dir:
potential_path = os.path.join(selected_folder, found_dir)
# 尝试多种可能的可执行文件名变体
# 包括Steam加密版本和其他可能的变体
exe_variants = [
expected_exe, # 标准文件名
expected_exe + ".nocrack", # Steam加密版本
expected_exe.replace(".exe", ""),# 无扩展名版本
# Vol.3的特殊变体,因为它的文件名可能不一样
expected_exe.replace("NEKOPARA", "nekopara").lower(), # 全小写变体
expected_exe.lower(), # 小写变体
expected_exe.lower() + ".nocrack", # 小写变体的Steam加密版本
]
# 对于Vol.3可能有特殊名称
if "Vol.3" in game:
# 增加可能的卷3特定的变体
exe_variants.extend([
"NEKOPARAVol3.exe",
"NEKOPARAVol3.exe.nocrack",
"nekoparavol3.exe",
"nekoparavol3.exe.nocrack",
"nekopara_vol3.exe",
"nekopara_vol3.exe.nocrack",
"vol3.exe",
"vol3.exe.nocrack"
])
exe_exists = False
found_exe = None
# 尝试所有可能的变体
for exe_variant in exe_variants:
exe_path = os.path.join(potential_path, exe_variant)
if os.path.exists(exe_path):
exe_exists = True
found_exe = exe_variant
if debug_mode:
print(f"DEBUG: 验证成功,找到游戏可执行文件: {exe_variant}")
break
# 如果没有直接找到,尝试递归搜索当前目录下的所有可执行文件
if not exe_exists:
# 遍历当前目录下的所有文件和文件夹
for root, dirs, files in os.walk(potential_path):
for file in files:
file_lower = file.lower()
# 检查是否是游戏可执行文件(根据关键字)
if file.endswith('.exe') or file.endswith('.exe.nocrack'):
# 检查文件名中是否包含卷号或关键词
if (f"vol{vol_num}" in file_lower or
f"vol.{vol_num}" in file_lower or
f"vol {vol_num}" in file_lower or
(is_after and "after" in file_lower)):
exe_path = os.path.join(root, file)
exe_exists = True
found_exe = os.path.relpath(exe_path, potential_path)
if debug_mode:
print(f"DEBUG: 通过递归搜索找到游戏可执行文件: {found_exe}")
break
if exe_exists:
break
# 如果找到了可执行文件,将该目录添加到游戏目录列表
if exe_exists:
game_paths[game] = potential_path
if debug_mode:
print(f"DEBUG: 验证成功,将 {potential_path} 添加为 {game} 的目录")
else:
if debug_mode:
print(f"DEBUG: 未找到任何可执行文件变体,游戏 {game}{potential_path} 未找到")
if debug_mode:
print(f"DEBUG: 最终识别的游戏目录: {game_paths}")
print(f"--- 目录识别结束 ---")
return game_paths
def uninstall_patch(self, game_dir, game_version):
"""卸载补丁
Args:
game_dir: 游戏目录路径
game_version: 游戏版本
Returns:
bool: 卸载成功返回True失败返回False
"""
import os
import shutil
debug_mode = self.ui_manager.debug_action.isChecked() if hasattr(self.ui_manager, 'debug_action') and self.ui_manager.debug_action else False
if game_version not in GAME_INFO:
QMessageBox.critical(
self,
f"错误 - {APP_NAME}",
f"\n无法识别游戏版本: {game_version}\n",
QMessageBox.StandardButton.Ok,
)
return False
if debug_mode:
print(f"DEBUG: 开始卸载 {game_version} 补丁,目录: {game_dir}")
try:
files_removed = 0
# 获取可能的补丁文件路径
install_path_base = os.path.basename(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("_", "-"),
]
# 查找并删除补丁文件
patch_file_found = False
for patch_path in patch_files_to_check:
if os.path.exists(patch_path):
patch_file_found = True
os.remove(patch_path)
files_removed += 1
if debug_mode:
print(f"DEBUG: 已删除补丁文件: {patch_path}")
if not patch_file_found and debug_mode:
print(f"DEBUG: 未找到补丁文件,检查了以下路径: {patch_files_to_check}")
# 检查是否有额外的签名文件 (.sig)
if game_version == "NEKOPARA After":
for patch_path in patch_files_to_check:
sig_file_path = f"{patch_path}.sig"
if os.path.exists(sig_file_path):
os.remove(sig_file_path)
files_removed += 1
if debug_mode:
print(f"DEBUG: 已删除签名文件: {sig_file_path}")
# 删除patch文件夹
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):
shutil.rmtree(patch_folder)
files_removed += 1
if debug_mode:
print(f"DEBUG: 已删除补丁文件夹: {patch_folder}")
# 删除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):
shutil.rmtree(game_patch_folder)
files_removed += 1
if debug_mode:
print(f"DEBUG: 已删除game/patch文件夹: {game_patch_folder}")
# 删除配置文件
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):
os.remove(config_path)
files_removed += 1
if debug_mode:
print(f"DEBUG: 已删除配置文件: {config_path}")
# 删除脚本文件
for script_file in script_files:
script_path = os.path.join(game_path, script_file)
if os.path.exists(script_path):
os.remove(script_path)
files_removed += 1
if debug_mode:
print(f"DEBUG: 已删除脚本文件: {script_path}")
# 更新安装状态
self.installed_status[game_version] = False
# 在非批量卸载模式下显示卸载成功消息
if game_version != "all":
# 显示卸载成功消息
if files_removed > 0:
QMessageBox.information(
self,
f"卸载完成 - {APP_NAME}",
f"\n{game_version} 补丁卸载成功!\n共删除 {files_removed} 个文件/文件夹。\n",
QMessageBox.StandardButton.Ok,
)
else:
QMessageBox.warning(
self,
f"警告 - {APP_NAME}",
f"\n未找到 {game_version} 的补丁文件,可能未安装补丁或已被移除。\n",
QMessageBox.StandardButton.Ok,
)
# 卸载成功
return True
except Exception as e:
# 在非批量卸载模式下显示卸载失败消息
if game_version != "all":
# 显示卸载失败消息
error_message = f"\n卸载 {game_version} 补丁时出错:\n\n{str(e)}\n"
if debug_mode:
print(f"DEBUG: 卸载错误 - {str(e)}")
QMessageBox.critical(
self,
f"卸载失败 - {APP_NAME}",
error_message,
QMessageBox.StandardButton.Ok,
)
# 卸载失败
return False
self.patch_manager.uninstall_patch(game_dir, game_version)