diff --git a/source/core/animations.py b/source/core/animations.py index f6ffdee..9dd6b8c 100644 --- a/source/core/animations.py +++ b/source/core/animations.py @@ -49,6 +49,7 @@ class MultiStageAnimations(QObject): # 移除菜单背景动画 # {"widget": ui.menubg, "end_pos": QPoint(720, 55), "duration": 600}, {"widget": ui.button_container, "end_pos": None, "duration": 600}, + {"widget": ui.toggle_patch_container, "end_pos": None, "duration": 600}, # 添加禁/启用补丁按钮 {"widget": ui.uninstall_container, "end_pos": None, "duration": 600}, # 添加卸载补丁按钮 {"widget": ui.exit_container, "end_pos": None, "duration": 600} ] @@ -301,18 +302,29 @@ class MultiStageAnimations(QObject): if hasattr(self.ui, 'button_container'): btn_width = self.ui.button_container.width() x_pos = width - btn_width - right_margin - y_pos = int((height - 65) * 0.28) - 10 # 与resizeEvent中保持一致 + y_pos = int((height - 65) * 0.18) - 10 # 从0.28改为0.18,向上移动 # 更新动画目标位置 for item in self.menu_widgets: if item["widget"] == self.ui.button_container: item["end_pos"] = QPoint(x_pos, y_pos) + # 禁用补丁按钮 + if hasattr(self.ui, 'toggle_patch_container'): + btn_width = self.ui.toggle_patch_container.width() + x_pos = width - btn_width - right_margin + y_pos = int((height - 65) * 0.36) - 10 # 从0.46改为0.36,向上移动 + + # 更新动画目标位置 + for item in self.menu_widgets: + if item["widget"] == self.ui.toggle_patch_container: + item["end_pos"] = QPoint(x_pos, y_pos) + # 卸载补丁按钮 if hasattr(self.ui, 'uninstall_container'): btn_width = self.ui.uninstall_container.width() x_pos = width - btn_width - right_margin - y_pos = int((height - 65) * 0.46) - 10 # 与resizeEvent中保持一致 + y_pos = int((height - 65) * 0.54) - 10 # 从0.64改为0.54,向上移动 # 更新动画目标位置 for item in self.menu_widgets: @@ -323,7 +335,7 @@ class MultiStageAnimations(QObject): if hasattr(self.ui, 'exit_container'): btn_width = self.ui.exit_container.width() x_pos = width - btn_width - right_margin - y_pos = int((height - 65) * 0.64) - 10 # 与resizeEvent中保持一致 + y_pos = int((height - 65) * 0.72) - 10 # 从0.82改为0.72,向上移动 # 更新动画目标位置 for item in self.menu_widgets: @@ -334,10 +346,12 @@ class MultiStageAnimations(QObject): for item in self.menu_widgets: if item["widget"] == self.ui.button_container: item["end_pos"] = QPoint(1050, 200) - elif item["widget"] == self.ui.uninstall_container: + elif item["widget"] == self.ui.toggle_patch_container: item["end_pos"] = QPoint(1050, 310) - elif item["widget"] == self.ui.exit_container: + elif item["widget"] == self.ui.uninstall_container: item["end_pos"] = QPoint(1050, 420) + elif item["widget"] == self.ui.exit_container: + item["end_pos"] = QPoint(1050, 530) def start_animations(self): """启动完整动画序列""" diff --git a/source/core/download_manager.py b/source/core/download_manager.py index 143e54d..d807d38 100644 --- a/source/core/download_manager.py +++ b/source/core/download_manager.py @@ -209,20 +209,76 @@ class DownloadManager: installable_games = [] already_installed_games = [] + disabled_patch_games = [] # 存储检测到禁用补丁的游戏 + for game_version, game_dir in game_dirs.items(): + # 检查游戏是否已安装补丁 if self.main_window.installed_status.get(game_version, False): if debug_mode: print(f"DEBUG: {game_version} 已安装补丁,不需要再次安装") already_installed_games.append(game_version) else: - if debug_mode: - print(f"DEBUG: {game_version} 未安装补丁,可以安装") - installable_games.append(game_version) + # 检查是否存在被禁用的补丁 + is_disabled, disabled_path = self.main_window.patch_manager.check_patch_disabled(game_dir, game_version) + if is_disabled: + if debug_mode: + print(f"DEBUG: {game_version} 存在被禁用的补丁: {disabled_path}") + disabled_patch_games.append(game_version) + else: + if debug_mode: + print(f"DEBUG: {game_version} 未安装补丁,可以安装") + installable_games.append(game_version) status_message = "" if already_installed_games: status_message += f"已安装补丁的游戏:\n{chr(10).join(already_installed_games)}\n\n" + # 处理禁用补丁的情况 + if disabled_patch_games: + # 构建提示消息 + disabled_msg = f"检测到以下游戏的补丁已被禁用:\n{chr(10).join(disabled_patch_games)}\n\n是否要启用这些补丁?" + + reply = QtWidgets.QMessageBox.question( + self.main_window, + f"检测到禁用补丁 - {APP_NAME}", + disabled_msg, + QtWidgets.QMessageBox.StandardButton.Yes | QtWidgets.QMessageBox.StandardButton.No + ) + + if reply == QtWidgets.QMessageBox.StandardButton.Yes: + # 用户选择启用补丁 + if debug_mode: + print(f"DEBUG: 用户选择启用被禁用的补丁") + + # 为每个禁用的游戏创建目录映射 + disabled_game_dirs = {game: game_dirs[game] for game in disabled_patch_games} + + # 批量启用补丁 + success_count, fail_count, results = self.main_window.patch_manager.batch_toggle_patches( + disabled_game_dirs, + operation="enable" + ) + + # 显示启用结果 + self.main_window.patch_manager.show_toggle_result(success_count, fail_count, results) + + # 更新安装状态 + for game_version in disabled_patch_games: + self.main_window.installed_status[game_version] = True + if game_version in installable_games: + installable_games.remove(game_version) + if game_version not in already_installed_games: + already_installed_games.append(game_version) + else: + if debug_mode: + print(f"DEBUG: 用户选择不启用被禁用的补丁,这些游戏将被添加到可安装列表") + # 用户选择不启用,将这些游戏视为可以安装补丁 + installable_games.extend(disabled_patch_games) + + # 更新status_message + if already_installed_games: + status_message = f"已安装补丁的游戏:\n{chr(10).join(already_installed_games)}\n\n" + if not installable_games: QtWidgets.QMessageBox.information( self.main_window, @@ -233,28 +289,28 @@ class DownloadManager: self.main_window.ui.start_install_text.setText("开始安装") return - dialog = QDialog(self.main_window) + dialog = QtWidgets.QDialog(self.main_window) dialog.setWindowTitle("选择要安装的游戏") dialog.resize(400, 300) - layout = QVBoxLayout(dialog) + layout = QtWidgets.QVBoxLayout(dialog) if already_installed_games: - already_installed_label = QLabel("已安装补丁的游戏:", dialog) - already_installed_label.setFont(QFont(already_installed_label.font().family(), already_installed_label.font().pointSize(), QFont.Bold)) + already_installed_label = QtWidgets.QLabel("已安装补丁的游戏:", dialog) + already_installed_label.setFont(QFont(already_installed_label.font().family(), already_installed_label.font().pointSize(), QFont.Weight.Bold)) layout.addWidget(already_installed_label) - already_installed_list = QLabel(chr(10).join(already_installed_games), dialog) + already_installed_list = QtWidgets.QLabel(chr(10).join(already_installed_games), dialog) layout.addWidget(already_installed_list) layout.addSpacing(10) - info_label = QLabel("请选择你需要安装补丁的游戏:", dialog) - info_label.setFont(QFont(info_label.font().family(), info_label.font().pointSize(), QFont.Bold)) + info_label = QtWidgets.QLabel("请选择你需要安装补丁的游戏:", dialog) + info_label.setFont(QFont(info_label.font().family(), info_label.font().pointSize(), QFont.Weight.Bold)) layout.addWidget(info_label) - list_widget = QListWidget(dialog) - list_widget.setSelectionMode(QAbstractItemView.SelectionMode.ExtendedSelection) + list_widget = QtWidgets.QListWidget(dialog) + list_widget.setSelectionMode(QtWidgets.QAbstractItemView.SelectionMode.ExtendedSelection) for game in installable_games: list_widget.addItem(game) layout.addWidget(list_widget) diff --git a/source/core/patch_manager.py b/source/core/patch_manager.py index 6d41f42..67000e3 100644 --- a/source/core/patch_manager.py +++ b/source/core/patch_manager.py @@ -69,6 +69,9 @@ class PatchManager: """ debug_mode = self._is_debug_mode() + if debug_mode: + print(f"DEBUG: 开始卸载 {game_version} 补丁,目录: {game_dir}") + if game_version not in self.game_info: if not silent: QMessageBox.critical( @@ -79,9 +82,6 @@ class PatchManager: ) return False if not silent else {"success": False, "message": f"无法识别游戏版本: {game_version}", "files_removed": 0} - if debug_mode: - print(f"DEBUG: 开始卸载 {game_version} 补丁,目录: {game_dir}") - try: files_removed = 0 @@ -98,28 +98,51 @@ class PatchManager: patch_file_path.replace("_", "-"), ] - # 查找并删除补丁文件 + if debug_mode: + print(f"DEBUG: 查找以下可能的补丁文件路径: {patch_files_to_check}") + + # 查找并删除补丁文件,包括启用和禁用的 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}") + + # 检查被禁用的补丁文件(带.fain后缀) + disabled_path = f"{patch_path}.fain" + if os.path.exists(disabled_path): + patch_file_found = True + os.remove(disabled_path) + files_removed += 1 + if debug_mode: + print(f"DEBUG: 已删除被禁用的补丁文件: {disabled_path}") if not patch_file_found and debug_mode: print(f"DEBUG: 未找到补丁文件,检查了以下路径: {patch_files_to_check}") + print(f"DEBUG: 也检查了禁用的补丁文件(.fain后缀)") # 检查是否有额外的签名文件 (.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}") + + # 检查被禁用补丁的签名文件 + disabled_sig_path = f"{patch_path}.fain.sig" + if os.path.exists(disabled_sig_path): + os.remove(disabled_sig_path) + files_removed += 1 + if debug_mode: + print(f"DEBUG: 已删除被禁用补丁的签名文件: {disabled_sig_path}") # 删除patch文件夹 patch_folders_to_check = [ @@ -206,6 +229,8 @@ class PatchManager: error_message = f"\n卸载 {game_version} 补丁时出错:\n\n{str(e)}\n" if debug_mode: print(f"DEBUG: 卸载错误 - {str(e)}") + import traceback + print(f"DEBUG: 错误详情:\n{traceback.format_exc()}") QMessageBox.critical( None, @@ -310,7 +335,7 @@ class PatchManager: game_version: 游戏版本 Returns: - bool: 如果已安装补丁返回True,否则返回False + bool: 如果已安装补丁或有被禁用的补丁文件返回True,否则返回False """ debug_mode = self._is_debug_mode() @@ -336,6 +361,12 @@ class PatchManager: if debug_mode: print(f"DEBUG: 找到补丁文件: {patch_path}") return True + # 检查是否存在被禁用的补丁文件(带.fain后缀) + disabled_path = f"{patch_path}.fain" + if os.path.exists(disabled_path): + if debug_mode: + print(f"DEBUG: 找到被禁用的补丁文件: {disabled_path}") + return True # 检查是否有补丁文件夹 patch_folders_to_check = [ @@ -388,4 +419,325 @@ class PatchManager: # 没有找到补丁文件或文件夹 if debug_mode: print(f"DEBUG: {game_version} 在 {game_dir} 中没有安装补丁") - return False \ No newline at end of file + return False + + def check_patch_disabled(self, game_dir, game_version): + """检查游戏的补丁是否已被禁用 + + Args: + game_dir: 游戏目录路径 + game_version: 游戏版本 + + Returns: + bool: 如果补丁被禁用返回True,否则返回False + str: 禁用的补丁文件路径,如果没有禁用返回None + """ + debug_mode = self._is_debug_mode() + + if game_version not in self.game_info: + return False, None + + # 获取可能的补丁文件路径 + install_path_base = os.path.basename(self.game_info[game_version]["install_path"]) + patch_file_path = os.path.join(game_dir, install_path_base) + + # 检查是否存在禁用的补丁文件(.fain后缀) + disabled_patch_files = [ + f"{patch_file_path}.fain", + f"{patch_file_path.lower()}.fain", + f"{patch_file_path.upper()}.fain", + f"{patch_file_path.replace('_', '')}.fain", + f"{patch_file_path.replace('_', '-')}.fain", + ] + + # 检查是否有禁用的补丁文件 + for disabled_path in disabled_patch_files: + if os.path.exists(disabled_path): + if debug_mode: + print(f"DEBUG: 找到禁用的补丁文件: {disabled_path}") + return True, disabled_path + + if debug_mode: + print(f"DEBUG: {game_version} 在 {game_dir} 的补丁未被禁用") + + return False, None + + def toggle_patch(self, game_dir, game_version, operation=None, silent=False): + """切换补丁的禁用/启用状态 + + Args: + game_dir: 游戏目录路径 + game_version: 游戏版本 + operation: 指定操作,可以是"enable"、"disable"或None(None则自动切换当前状态) + silent: 是否静默模式(不显示弹窗) + + Returns: + dict: 包含操作结果信息的字典 + """ + debug_mode = self._is_debug_mode() + + if debug_mode: + print(f"DEBUG: 开始切换补丁状态 - 游戏版本: {game_version}, 游戏目录: {game_dir}, 操作: {operation}") + + if game_version not in self.game_info: + if debug_mode: + print(f"DEBUG: 无法识别游戏版本: {game_version}") + if not silent: + QMessageBox.critical( + None, + f"错误 - {self.app_name}", + f"\n无法识别游戏版本: {game_version}\n", + QMessageBox.StandardButton.Ok, + ) + return {"success": False, "message": f"无法识别游戏版本: {game_version}", "action": "none"} + + # 检查补丁是否已安装 + is_patch_installed = self.check_patch_installed(game_dir, game_version) + if debug_mode: + print(f"DEBUG: 补丁安装状态检查结果: {is_patch_installed}") + + if not is_patch_installed: + if debug_mode: + print(f"DEBUG: {game_version} 未安装补丁,无法进行禁用/启用操作") + if not silent: + QMessageBox.warning( + None, + f"提示 - {self.app_name}", + f"\n{game_version} 未安装补丁,无法进行禁用/启用操作。\n", + QMessageBox.StandardButton.Ok, + ) + return {"success": False, "message": f"{game_version} 未安装补丁", "action": "none"} + + try: + # 检查当前状态 + is_disabled, disabled_path = self.check_patch_disabled(game_dir, game_version) + if debug_mode: + print(f"DEBUG: 补丁禁用状态检查结果 - 是否禁用: {is_disabled}, 禁用路径: {disabled_path}") + + # 获取可能的补丁文件路径 + install_path_base = os.path.basename(self.game_info[game_version]["install_path"]) + patch_file_path = os.path.join(game_dir, install_path_base) + + # 尝试查找原始补丁文件,支持不同大小写 + patch_files_to_check = [ + patch_file_path, + patch_file_path.lower(), + patch_file_path.upper(), + patch_file_path.replace("_", ""), + patch_file_path.replace("_", "-"), + ] + + if debug_mode: + print(f"DEBUG: 将检查以下可能的补丁文件: {patch_files_to_check}") + + # 确定操作类型 + if operation: + if operation == "enable": + action_needed = is_disabled # 只有当前是禁用状态时才需要启用 + elif operation == "disable": + action_needed = not is_disabled # 只有当前是启用状态时才需要禁用 + else: + action_needed = True # 无效操作类型,强制进行操作 + else: + action_needed = True # 未指定操作类型,始终执行切换 + + if debug_mode: + print(f"DEBUG: 操作决策 - 操作类型: {operation}, 是否需要执行操作: {action_needed}") + + if not action_needed: + # 补丁已经是目标状态,无需操作 + if operation == "enable": + message = f"{game_version} 补丁已经是启用状态" + else: + message = f"{game_version} 补丁已经是禁用状态" + + if debug_mode: + print(f"DEBUG: {message}, 无需操作") + + if not silent: + QMessageBox.information( + None, + f"提示 - {self.app_name}", + f"\n{message}\n", + QMessageBox.StandardButton.Ok, + ) + return {"success": True, "message": message, "action": "none"} + + if is_disabled: + # 当前是禁用状态,需要启用 + if disabled_path and os.path.exists(disabled_path): + # 从禁用文件名去掉.fain后缀 + enabled_path = disabled_path[:-5] # 去掉.fain + if debug_mode: + print(f"DEBUG: 正在启用补丁 - 从 {disabled_path} 重命名为 {enabled_path}") + os.rename(disabled_path, enabled_path) + if debug_mode: + print(f"DEBUG: 已启用 {game_version} 的补丁,重命名文件成功") + action = "enable" + message = f"{game_version} 补丁已启用" + else: + # 未找到禁用的补丁文件,但状态是禁用 + message = f"未找到禁用的补丁文件: {disabled_path}" + if debug_mode: + print(f"DEBUG: {message}") + return {"success": False, "message": message, "action": "none"} + else: + # 当前是启用状态,需要禁用 + # 查找正在使用的补丁文件 + active_patch_file = None + for patch_path in patch_files_to_check: + if os.path.exists(patch_path): + active_patch_file = patch_path + if debug_mode: + print(f"DEBUG: 找到活跃的补丁文件: {active_patch_file}") + break + + if active_patch_file: + # 给补丁文件添加.fain后缀禁用它 + disabled_path = f"{active_patch_file}.fain" + if debug_mode: + print(f"DEBUG: 正在禁用补丁 - 从 {active_patch_file} 重命名为 {disabled_path}") + os.rename(active_patch_file, disabled_path) + if debug_mode: + print(f"DEBUG: 已禁用 {game_version} 的补丁,重命名文件成功") + action = "disable" + message = f"{game_version} 补丁已禁用" + else: + # 未找到活跃的补丁文件,但状态是启用 + message = f"未找到启用的补丁文件,请检查游戏目录: {game_dir}" + if debug_mode: + print(f"DEBUG: {message}") + return {"success": False, "message": message, "action": "none"} + + # 非静默模式下显示操作结果 + if not silent: + QMessageBox.information( + None, + f"操作成功 - {self.app_name}", + f"\n{message}\n", + QMessageBox.StandardButton.Ok, + ) + + if debug_mode: + print(f"DEBUG: 切换补丁状态操作完成 - 结果: 成功, 操作: {action}, 消息: {message}") + + return {"success": True, "message": message, "action": action} + + except Exception as e: + error_message = f"切换 {game_version} 补丁状态时出错: {str(e)}" + + if debug_mode: + print(f"DEBUG: {error_message}") + import traceback + print(f"DEBUG: 错误详情:\n{traceback.format_exc()}") + + if not silent: + QMessageBox.critical( + None, + f"操作失败 - {self.app_name}", + f"\n{error_message}\n", + QMessageBox.StandardButton.Ok, + ) + + return {"success": False, "message": error_message, "action": "none"} + + def batch_toggle_patches(self, game_dirs, operation=None): + """批量切换多个游戏补丁的禁用/启用状态 + + Args: + game_dirs: 游戏版本到游戏目录的映射字典 + operation: 指定操作,可以是"enable"、"disable"或None(None则自动切换当前状态) + + Returns: + tuple: (成功数量, 失败数量, 详细结果列表) + """ + success_count = 0 + fail_count = 0 + debug_mode = self._is_debug_mode() + results = [] + + if debug_mode: + print(f"DEBUG: 开始批量切换补丁状态 - 操作: {operation}, 游戏数量: {len(game_dirs)}") + print(f"DEBUG: 游戏列表: {list(game_dirs.keys())}") + + for version, path in game_dirs.items(): + try: + if debug_mode: + print(f"DEBUG: 处理游戏 {version}, 目录: {path}") + + # 在批量模式下使用静默操作 + result = self.toggle_patch(path, version, operation=operation, silent=True) + + if debug_mode: + print(f"DEBUG: 游戏 {version} 操作结果: {result}") + + if result["success"]: + success_count += 1 + if debug_mode: + print(f"DEBUG: 游戏 {version} 操作成功,操作类型: {result['action']}") + else: + fail_count += 1 + if debug_mode: + print(f"DEBUG: 游戏 {version} 操作失败,原因: {result['message']}") + + results.append({ + "version": version, + "success": result["success"], + "message": result["message"], + "action": result["action"] + }) + + except Exception as e: + if debug_mode: + print(f"DEBUG: 切换 {version} 补丁状态时出错: {str(e)}") + import traceback + print(f"DEBUG: 错误详情:\n{traceback.format_exc()}") + + fail_count += 1 + results.append({ + "version": version, + "success": False, + "message": f"操作出错: {str(e)}", + "action": "none" + }) + + if debug_mode: + print(f"DEBUG: 批量切换补丁状态完成 - 成功: {success_count}, 失败: {fail_count}") + + return success_count, fail_count, results + + def show_toggle_result(self, success_count, fail_count, results=None): + """显示批量切换补丁状态的结果 + + Args: + success_count: 成功操作的数量 + fail_count: 操作失败的数量 + results: 详细结果列表,如果提供,会显示更详细的信息 + """ + result_text = f"\n批量操作完成!\n成功: {success_count} 个\n失败: {fail_count} 个\n" + + # 如果有详细结果,添加到消息中 + if results: + enabled_list = [r["version"] for r in results if r["success"] and r["action"] == "enable"] + disabled_list = [r["version"] for r in results if r["success"] and r["action"] == "disable"] + skipped_list = [r["version"] for r in results if r["success"] and r["action"] == "none"] + fail_list = [r["version"] for r in results if not r["success"]] + + if enabled_list: + result_text += f"\n【已启用补丁】:\n{chr(10).join(enabled_list)}\n" + + if disabled_list: + result_text += f"\n【已禁用补丁】:\n{chr(10).join(disabled_list)}\n" + + if skipped_list: + result_text += f"\n【无需操作】:\n{chr(10).join(skipped_list)}\n" + + if fail_list: + result_text += f"\n【操作失败】:\n{chr(10).join(fail_list)}\n" + + QMessageBox.information( + None, + f"批量操作完成 - {self.app_name}", + result_text, + QMessageBox.StandardButton.Ok, + ) \ No newline at end of file diff --git a/source/core/window_manager.py b/source/core/window_manager.py index f447fb2..ae55bc9 100644 --- a/source/core/window_manager.py +++ b/source/core/window_manager.py @@ -115,22 +115,30 @@ class WindowManager: btn_width = 211 # 扩大后的容器宽度 btn_height = 111 # 扩大后的容器高度 x_pos = new_width - btn_width - right_margin - y_pos = int((new_height - 65) * 0.28) - 10 # 调整为更靠上的位置 + y_pos = int((new_height - 65) * 0.18) - 10 # 从0.28改为0.18,向上移动 self.ui.button_container.setGeometry(x_pos, y_pos, btn_width, btn_height) + # 添加禁/启用补丁按钮容器的位置调整 + if hasattr(self.ui, 'toggle_patch_container'): + btn_width = 211 # 扩大后的容器宽度 + btn_height = 111 # 扩大后的容器高度 + x_pos = new_width - btn_width - right_margin + y_pos = int((new_height - 65) * 0.36) - 10 # 从0.46改为0.36,向上移动 + self.ui.toggle_patch_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 # 调整为中间位置 + y_pos = int((new_height - 65) * 0.54) - 10 # 从0.64改为0.54,向上移动 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 # 调整为更靠下的位置 + y_pos = int((new_height - 65) * 0.72) - 10 # 从0.82改为0.72,向上移动 self.ui.exit_container.setGeometry(x_pos, y_pos, btn_width, btn_height) # 更新圆角 diff --git a/source/main_window.py b/source/main_window.py index 6a0072c..028097a 100644 --- a/source/main_window.py +++ b/source/main_window.py @@ -123,6 +123,7 @@ class MainWindow(QMainWindow): # 连接信号 - 绑定到新按钮 self.ui.start_install_btn.clicked.connect(self.handle_install_button_click) self.ui.uninstall_btn.clicked.connect(self.handle_uninstall_button_click) # 添加卸载补丁按钮事件连接 + self.ui.toggle_patch_btn.clicked.connect(self.handle_toggle_patch_button_click) # 添加禁/启用补丁按钮事件连接 self.ui.exit_btn.clicked.connect(self.shutdown_app) # 初始化按钮状态标记 @@ -176,6 +177,7 @@ class MainWindow(QMainWindow): # 启用所有菜单按钮 self.ui.start_install_btn.setEnabled(True) self.ui.uninstall_btn.setEnabled(True) + self.ui.toggle_patch_btn.setEnabled(True) # 启用禁/启用补丁按钮 self.ui.exit_btn.setEnabled(True) # 只有在配置有效时才启用开始安装按钮 @@ -697,6 +699,320 @@ class MainWindow(QMainWindow): ) msg_box.exec() + def handle_toggle_patch_button_click(self): + """处理禁/启用补丁按钮点击事件 + 打开文件选择对话框选择游戏目录,然后禁用或启用对应游戏的补丁 + """ + # 获取游戏目录 + from PySide6.QtWidgets import QFileDialog + + debug_mode = self.debug_manager._is_debug_mode() + + # 提示用户选择目录 + file_dialog_info = "选择游戏上级目录" if debug_mode else "选择游戏目录" + selected_folder = QFileDialog.getExistingDirectory(self, file_dialog_info, "") + + if not selected_folder or selected_folder == "": + return # 用户取消了选择 + + if debug_mode: + print(f"DEBUG: 禁/启用功能 - 用户选择了目录: {selected_folder}") + + # 首先尝试将选择的目录视为上级目录,使用增强的目录识别功能 + game_dirs = self.game_detector.identify_game_directories_improved(selected_folder) + + if game_dirs and len(game_dirs) > 0: + if debug_mode: + print(f"DEBUG: 禁/启用功能 - 在上级目录中找到以下游戏: {list(game_dirs.keys())}") + + # 查找已安装补丁的游戏,只处理那些已安装补丁的游戏 + games_with_patch = {} + for game_version, game_dir in game_dirs.items(): + if self.patch_manager.check_patch_installed(game_dir, game_version): + # 检查补丁当前状态(是否禁用) + is_disabled, disabled_path = self.patch_manager.check_patch_disabled(game_dir, game_version) + status = "已禁用" if is_disabled else "已启用" + games_with_patch[game_version] = { + "dir": game_dir, + "disabled": is_disabled, + "status": status + } + if debug_mode: + print(f"DEBUG: 禁/启用功能 - {game_version} 已安装补丁,当前状态: {status}") + + # 检查是否有已安装补丁的游戏 + if not games_with_patch: + QMessageBox.information( + self, + f"提示 - {APP_NAME}", + "\n未在选择的目录中找到已安装补丁的游戏。\n请确认您选择了正确的游戏目录,并且该目录中的游戏已安装过补丁。\n", + QMessageBox.StandardButton.Ok + ) + return + + # 创建自定义选择对话框,允许多选 + from PySide6.QtWidgets import QDialog, QVBoxLayout, QLabel, QListWidget, QPushButton, QHBoxLayout, QAbstractItemView, QRadioButton, QButtonGroup + + dialog = QDialog(self) + dialog.setWindowTitle("选择要操作的游戏补丁") + dialog.resize(400, 400) # 增加高度以适应新增的单选按钮 + + layout = QVBoxLayout(dialog) + + # 添加"已安装补丁的游戏"标签 + already_installed_label = QLabel("已安装补丁的游戏:", dialog) + already_installed_label.setFont(QFont(already_installed_label.font().family(), already_installed_label.font().pointSize(), QFont.Bold)) + layout.addWidget(already_installed_label) + + # 添加游戏列表和状态 + games_status_text = "" + for game, info in games_with_patch.items(): + games_status_text += f"{game} ({info['status']})\n" + games_status_label = QLabel(games_status_text.strip(), dialog) + layout.addWidget(games_status_label) + + # 添加一些间距 + layout.addSpacing(10) + + # 添加"请选择要操作的游戏"标签 + info_label = QLabel("请选择要操作的游戏:", dialog) + info_label.setFont(QFont(info_label.font().family(), info_label.font().pointSize(), QFont.Bold)) + layout.addWidget(info_label) + + # 添加列表控件,只显示已安装补丁的游戏 + list_widget = QListWidget(dialog) + list_widget.setSelectionMode(QAbstractItemView.SelectionMode.ExtendedSelection) # 允许多选 + for game, info in games_with_patch.items(): + list_widget.addItem(f"{game} ({info['status']})") + layout.addWidget(list_widget) + + # 添加全选按钮 + select_all_btn = QPushButton("全选", dialog) + select_all_btn.clicked.connect(lambda: list_widget.selectAll()) + layout.addWidget(select_all_btn) + + # 添加操作选择单选按钮 + operation_label = QLabel("请选择要执行的操作:", dialog) + operation_label.setFont(QFont(operation_label.font().family(), operation_label.font().pointSize(), QFont.Bold)) + layout.addWidget(operation_label) + + # 创建单选按钮组 + radio_button_group = QButtonGroup(dialog) + + # 添加"自动切换状态"单选按钮(默认选中) + auto_toggle_radio = QRadioButton("自动切换状态(禁用<->启用)", dialog) + auto_toggle_radio.setChecked(True) + radio_button_group.addButton(auto_toggle_radio, 0) + layout.addWidget(auto_toggle_radio) + + # 添加"全部禁用"单选按钮 + disable_all_radio = QRadioButton("禁用选中的补丁", dialog) + radio_button_group.addButton(disable_all_radio, 1) + layout.addWidget(disable_all_radio) + + # 添加"全部启用"单选按钮 + enable_all_radio = QRadioButton("启用选中的补丁", dialog) + radio_button_group.addButton(enable_all_radio, 2) + layout.addWidget(enable_all_radio) + + # 添加确定和取消按钮 + buttons_layout = QHBoxLayout() + ok_button = QPushButton("确定", dialog) + cancel_button = QPushButton("取消", dialog) + buttons_layout.addWidget(ok_button) + buttons_layout.addWidget(cancel_button) + layout.addLayout(buttons_layout) + + # 连接按钮事件 + ok_button.clicked.connect(dialog.accept) + cancel_button.clicked.connect(dialog.reject) + + # 显示对话框并等待用户选择 + result = dialog.exec() + + if result != QDialog.DialogCode.Accepted or list_widget.selectedItems() == []: + # 用户取消或未选择任何游戏 + return + + # 获取用户选择的游戏 + selected_items = [item.text() for item in list_widget.selectedItems()] + selected_games = [] + + # 从选中项文本中提取游戏名称 + for item in selected_items: + # 去除状态后缀 " (已启用)" 或 " (已禁用)" + game_name = item.split(" (")[0] + selected_games.append(game_name) + + if debug_mode: + print(f"DEBUG: 禁/启用功能 - 用户选择了以下游戏进行操作: {selected_games}") + + # 获取选中的操作类型 + operation = None + if radio_button_group.checkedId() == 1: # 禁用选中的补丁 + operation = "disable" + operation_text = "禁用" + elif radio_button_group.checkedId() == 2: # 启用选中的补丁 + operation = "enable" + operation_text = "启用" + else: # 自动切换状态 + operation = None + operation_text = "切换" + + # 过滤games_with_patch,只保留选中的游戏 + selected_game_dirs = {} + for game in selected_games: + if game in games_with_patch: + selected_game_dirs[game] = games_with_patch[game]["dir"] + + # 确认操作 + game_list = '\n'.join([f"{game} ({games_with_patch[game]['status']})" for game in selected_games]) + reply = QMessageBox.question( + self, + f"确认{operation_text}操作 - {APP_NAME}", + f"\n确定要{operation_text}以下游戏补丁吗?\n\n{game_list}\n", + QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No, + QMessageBox.StandardButton.No + ) + + if reply == QMessageBox.StandardButton.No: + return + + # 执行批量操作 + success_count = 0 + fail_count = 0 + results = [] + + for game_version, game_dir in selected_game_dirs.items(): + try: + # 使用静默模式进行操作 + result = self.patch_manager.toggle_patch(game_dir, game_version, operation=operation, silent=True) + + if result["success"]: + success_count += 1 + else: + fail_count += 1 + + results.append({ + "version": game_version, + "success": result["success"], + "message": result["message"], + "action": result["action"] + }) + + except Exception as e: + if debug_mode: + print(f"DEBUG: 切换 {game_version} 补丁状态时出错: {str(e)}") + fail_count += 1 + results.append({ + "version": game_version, + "success": False, + "message": f"操作出错: {str(e)}", + "action": "none" + }) + + # 显示操作结果 + self.patch_manager.show_toggle_result(success_count, fail_count, results) + + else: + # 未找到游戏目录,尝试将选择的目录作为游戏目录 + if debug_mode: + print(f"DEBUG: 禁/启用功能 - 未在上级目录找到游戏,尝试将选择的目录视为游戏目录") + + game_version = self.game_detector.identify_game_version(selected_folder) + + if game_version: + if debug_mode: + print(f"DEBUG: 禁/启用功能 - 识别为游戏: {game_version}") + + # 检查是否已安装补丁 + if self.patch_manager.check_patch_installed(selected_folder, game_version): + # 检查补丁当前状态 + is_disabled, disabled_path = self.patch_manager.check_patch_disabled(selected_folder, game_version) + current_status = "已禁用" if is_disabled else "已启用" + + # 创建简单对话框询问操作 + from PySide6.QtWidgets import QDialog, QVBoxLayout, QLabel, QRadioButton, QButtonGroup, QPushButton, QHBoxLayout + + dialog = QDialog(self) + dialog.setWindowTitle(f"{game_version} 补丁操作") + dialog.resize(300, 200) + + layout = QVBoxLayout(dialog) + + # 添加当前状态标签 + status_label = QLabel(f"当前补丁状态: {current_status}", dialog) + status_label.setFont(QFont(status_label.font().family(), status_label.font().pointSize(), QFont.Bold)) + layout.addWidget(status_label) + + # 添加操作选择单选按钮 + operation_label = QLabel("请选择要执行的操作:", dialog) + layout.addWidget(operation_label) + + # 创建单选按钮组 + radio_button_group = QButtonGroup(dialog) + + # 添加可选操作 + if is_disabled: + # 当前是禁用状态,显示启用选项 + enable_radio = QRadioButton("启用补丁", dialog) + enable_radio.setChecked(True) + radio_button_group.addButton(enable_radio, 0) + layout.addWidget(enable_radio) + else: + # 当前是启用状态,显示禁用选项 + disable_radio = QRadioButton("禁用补丁", dialog) + disable_radio.setChecked(True) + radio_button_group.addButton(disable_radio, 0) + layout.addWidget(disable_radio) + + # 添加确定和取消按钮 + buttons_layout = QHBoxLayout() + ok_button = QPushButton("确定", dialog) + cancel_button = QPushButton("取消", dialog) + buttons_layout.addWidget(ok_button) + buttons_layout.addWidget(cancel_button) + layout.addLayout(buttons_layout) + + # 连接按钮事件 + ok_button.clicked.connect(dialog.accept) + cancel_button.clicked.connect(dialog.reject) + + # 显示对话框并等待用户选择 + result = dialog.exec() + + if result != QDialog.DialogCode.Accepted: + # 用户取消 + return + + # 根据当前状态确定操作 + operation = "enable" if is_disabled else "disable" + + # 执行操作 + result = self.patch_manager.toggle_patch(selected_folder, game_version, operation=operation) + if not result["success"]: + # 操作失败的消息已在toggle_patch中显示 + pass + else: + # 没有安装补丁 + QMessageBox.information( + self, + f"提示 - {APP_NAME}", + f"\n未在 {game_version} 中找到已安装的补丁。\n请确认该游戏已经安装过补丁。\n", + QMessageBox.StandardButton.Ok + ) + else: + # 两种方式都未识别到游戏 + if debug_mode: + print(f"DEBUG: 禁/启用功能 - 无法识别游戏") + + msg_box = msgbox_frame( + f"错误 - {APP_NAME}", + "\n所选目录不是有效的NEKOPARA游戏目录。\n请选择包含游戏可执行文件的目录或其上级目录。\n", + QMessageBox.StandardButton.Ok, + ) + msg_box.exec() + \ No newline at end of file diff --git a/source/ui/Ui_install.py b/source/ui/Ui_install.py index 1586a4a..d6776f2 100644 --- a/source/ui/Ui_install.py +++ b/source/ui/Ui_install.py @@ -365,7 +365,7 @@ class Ui_MainWindows(object): # 调整开始安装按钮的位置 self.button_container = QWidget(self.inner_content) self.button_container.setObjectName(u"start_install_container") - self.button_container.setGeometry(QRect(1050, 200, 211, 111)) # 调整Y坐标,上移至200 + self.button_container.setGeometry(QRect(1045, 20, 211, 111)) # 调整坐标,Y设为20,X稍微左移 # 不要隐藏容器,让动画系统来控制它的可见性和位置 # 使用原来的按钮背景图片 @@ -397,10 +397,44 @@ class Ui_MainWindows(object): } """) + # 添加禁/启用补丁按钮 - 新增在开始安装和卸载补丁之间 + self.toggle_patch_container = QWidget(self.inner_content) + self.toggle_patch_container.setObjectName(u"toggle_patch_container") + self.toggle_patch_container.setGeometry(QRect(1050, 180, 211, 111)) # 调整Y坐标,设为180,增大与开始安装的间距 + + # 使用相同的按钮背景图片 + self.toggle_patch_bg = QLabel(self.toggle_patch_container) + self.toggle_patch_bg.setObjectName(u"toggle_patch_bg") + self.toggle_patch_bg.setGeometry(QRect(10, 10, 191, 91)) # 居中放置在扩大的容器中 + self.toggle_patch_bg.setPixmap(button_pixmap) + self.toggle_patch_bg.setScaledContents(True) + + self.toggle_patch_text = QLabel(self.toggle_patch_container) + self.toggle_patch_text.setObjectName(u"toggle_patch_text") + self.toggle_patch_text.setGeometry(QRect(10, 7, 191, 91)) # 居中放置在扩大的容器中 + self.toggle_patch_text.setText("禁/启用补丁") + self.toggle_patch_text.setFont(self.custom_font) + self.toggle_patch_text.setAlignment(Qt.AlignmentFlag.AlignCenter) + self.toggle_patch_text.setStyleSheet("letter-spacing: 1px;") + + # 点击区域透明按钮 + self.toggle_patch_btn = QPushButton(self.toggle_patch_container) + self.toggle_patch_btn.setObjectName(u"toggle_patch_btn") + self.toggle_patch_btn.setGeometry(QRect(10, 10, 191, 91)) # 居中放置在扩大的容器中 + self.toggle_patch_btn.setCursor(QCursor(Qt.CursorShape.PointingHandCursor)) # 设置鼠标悬停时为手形光标 + self.toggle_patch_btn.setFlat(True) + self.toggle_patch_btn.raise_() # 确保按钮在最上层 + self.toggle_patch_btn.setStyleSheet(""" + QPushButton { + background-color: transparent; + border: none; + } + """) + # 添加卸载补丁按钮 - 新增 self.uninstall_container = QWidget(self.inner_content) self.uninstall_container.setObjectName(u"uninstall_container") - self.uninstall_container.setGeometry(QRect(1050, 310, 211, 111)) # 调整Y坐标,位于310位置 + self.uninstall_container.setGeometry(QRect(1050, 320, 211, 111)) # 设置Y坐标为320 # 使用相同的按钮背景图片 self.uninstall_bg = QLabel(self.uninstall_container) @@ -434,7 +468,7 @@ class Ui_MainWindows(object): # 退出按钮 - 基于背景图片和标签组合,调整位置 self.exit_container = QWidget(self.inner_content) self.exit_container.setObjectName(u"exit_container") - self.exit_container.setGeometry(QRect(1050, 420, 211, 111)) # 调整Y坐标,下移至420 + self.exit_container.setGeometry(QRect(1050, 450, 211, 111)) # 调整Y坐标,设为450 # 不要隐藏容器,让动画系统来控制它的可见性和位置 # 使用原来的按钮背景图片 @@ -476,11 +510,28 @@ class Ui_MainWindows(object): self.vol4bg.raise_() self.afterbg.raise_() self.Mainbg.raise_() + # 显式单独抬升开始安装按钮的所有组件 self.button_container.raise_() - self.uninstall_container.raise_() # 添加新按钮到层级顺序 + self.start_install_bg.raise_() + self.start_install_text.raise_() + self.start_install_btn.raise_() + # 显式单独抬升禁/启用补丁按钮的所有组件 + self.toggle_patch_container.raise_() + self.toggle_patch_bg.raise_() + self.toggle_patch_text.raise_() + self.toggle_patch_btn.raise_() + # 显式单独抬升卸载补丁按钮的所有组件 + self.uninstall_container.raise_() + self.uninstall_bg.raise_() + self.uninstall_text.raise_() + self.uninstall_btn.raise_() + # 显式单独抬升退出按钮的所有组件 self.exit_container.raise_() + self.exit_bg.raise_() + self.exit_text.raise_() + self.exit_btn.raise_() + # 其他UI元素 self.menu_area.raise_() # 确保菜单区域在背景之上 - # self.menubar.raise_() # 不再需要菜单栏 self.settings_btn.raise_() # 确保设置按钮在上层 self.help_btn.raise_() # 确保帮助按钮在上层 self.title_bar.raise_() # 确保标题栏在最上层