feat(core): 优化游戏目录识别和补丁卸载流程

- 改进游戏目录识别算法,支持大小写不敏感和特殊字符处理
- 增加递归搜索可执行文件的功能,提高识别准确率
- 优化补丁卸载流程,支持更灵活的文件路径和名称
- 增加调试模式下的日志输出,便于问题排查
- 重构部分代码结构,提高可维护性和可扩展性
This commit is contained in:
hyb-oyqq
2025-07-28 11:54:52 +08:00
parent f6a57215c2
commit 41aab89669
3 changed files with 665 additions and 125 deletions

View File

@@ -3,6 +3,7 @@ import requests
import json import json
from collections import deque from collections import deque
from urllib.parse import urlparse from urllib.parse import urlparse
import re # Added for recursive search
from PySide6 import QtWidgets, QtCore from PySide6 import QtWidgets, QtCore
from PySide6.QtCore import Qt from PySide6.QtCore import Qt
@@ -42,10 +43,29 @@ class DownloadManager:
def get_install_paths(self): def get_install_paths(self):
"""获取所有游戏版本的安装路径""" """获取所有游戏版本的安装路径"""
return { # 使用改进的目录识别功能
game: os.path.join(self.selected_folder, info["install_path"]) game_dirs = self.main_window.identify_game_directories_improved(self.selected_folder)
for game, info in GAME_INFO.items() install_paths = {}
}
debug_mode = self.is_debug_mode()
for game, info in GAME_INFO.items():
if game in game_dirs:
# 如果找到了游戏目录,使用它
game_dir = game_dirs[game]
install_path = os.path.join(game_dir, os.path.basename(info["install_path"]))
install_paths[game] = install_path
if debug_mode:
print(f"DEBUG: 使用识别到的游戏目录 {game}: {game_dir}")
print(f"DEBUG: 安装路径设置为: {install_path}")
else:
# 回退到原始路径计算方式
install_path = os.path.join(self.selected_folder, info["install_path"])
install_paths[game] = install_path
if debug_mode:
print(f"DEBUG: 未识别到游戏目录 {game}, 使用默认路径: {install_path}")
return install_paths
def is_debug_mode(self): def is_debug_mode(self):
"""检查是否处于调试模式""" """检查是否处于调试模式"""
@@ -145,6 +165,26 @@ class DownloadManager:
# 清空下载历史记录 # 清空下载历史记录
self.main_window.download_queue_history = [] self.main_window.download_queue_history = []
# 使用改进的目录识别功能
game_dirs = self.main_window.identify_game_directories_improved(self.selected_folder)
debug_mode = self.is_debug_mode()
if debug_mode:
print(f"DEBUG: 开始下载流程, 识别到 {len(game_dirs)} 个游戏目录")
# 检查是否找到任何游戏目录
if not game_dirs:
if debug_mode:
print("DEBUG: 未识别到任何游戏目录,设置目录未找到错误")
# 设置特定的错误类型,以便在按钮点击处理中区分处理
self.main_window.last_error_message = "directory_not_found"
QtWidgets.QMessageBox.warning(
self.main_window,
f"目录错误 - {APP_NAME}",
"\n未能识别到任何游戏目录。\n\n请确认您选择的是游戏的上级目录并且该目录中包含NEKOPARA系列游戏文件夹。\n"
)
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")
@@ -227,6 +267,13 @@ class DownloadManager:
# 创建下载历史记录列表,用于跟踪本次安装的游戏 # 创建下载历史记录列表,用于跟踪本次安装的游戏
if not hasattr(self.main_window, 'download_queue_history'): if not hasattr(self.main_window, 'download_queue_history'):
self.main_window.download_queue_history = [] self.main_window.download_queue_history = []
# 获取所有识别到的游戏目录
game_dirs = self.main_window.identify_game_directories_improved(self.selected_folder)
debug_mode = self.is_debug_mode()
if debug_mode:
print(f"DEBUG: 填充下载队列, 识别到的游戏目录: {game_dirs}")
# 添加nekopara 1-4 # 添加nekopara 1-4
for i in range(1, 5): for i in range(1, 5):
@@ -234,7 +281,18 @@ class DownloadManager:
if not self.main_window.installed_status.get(game_version, False): if not self.main_window.installed_status.get(game_version, False):
url = config.get(f"vol{i}") url = config.get(f"vol{i}")
if not url: continue if not url: continue
game_folder = os.path.join(self.selected_folder, f"NEKOPARA Vol. {i}")
# 确定游戏文件夹路径
if game_version in game_dirs:
game_folder = game_dirs[game_version]
if debug_mode:
print(f"DEBUG: 使用识别到的游戏目录 {game_version}: {game_folder}")
else:
# 回退到传统方式
game_folder = os.path.join(self.selected_folder, f"NEKOPARA Vol. {i}")
if debug_mode:
print(f"DEBUG: 使用默认游戏目录 {game_version}: {game_folder}")
_7z_path = os.path.join(PLUGIN, f"vol.{i}.7z") _7z_path = os.path.join(PLUGIN, f"vol.{i}.7z")
plugin_path = os.path.join(PLUGIN, GAME_INFO[game_version]["plugin_path"]) plugin_path = os.path.join(PLUGIN, GAME_INFO[game_version]["plugin_path"])
self.download_queue.append((url, game_folder, game_version, _7z_path, plugin_path)) self.download_queue.append((url, game_folder, game_version, _7z_path, plugin_path))
@@ -246,7 +304,16 @@ class DownloadManager:
if not self.main_window.installed_status.get(game_version, False): if not self.main_window.installed_status.get(game_version, False):
url = config.get("after") url = config.get("after")
if url: if url:
game_folder = os.path.join(self.selected_folder, "NEKOPARA After") # 确定After的游戏文件夹路径
if game_version in game_dirs:
game_folder = game_dirs[game_version]
if debug_mode:
print(f"DEBUG: 使用识别到的游戏目录 {game_version}: {game_folder}")
else:
game_folder = os.path.join(self.selected_folder, "NEKOPARA After")
if debug_mode:
print(f"DEBUG: 使用默认游戏目录 {game_version}: {game_folder}")
_7z_path = os.path.join(PLUGIN, "after.7z") _7z_path = os.path.join(PLUGIN, "after.7z")
plugin_path = os.path.join(PLUGIN, GAME_INFO[game_version]["plugin_path"]) plugin_path = os.path.join(PLUGIN, GAME_INFO[game_version]["plugin_path"])
self.download_queue.append((url, game_folder, game_version, _7z_path, plugin_path)) self.download_queue.append((url, game_folder, game_version, _7z_path, plugin_path))
@@ -402,21 +469,109 @@ class DownloadManager:
_7z_path: 7z文件保存路径 _7z_path: 7z文件保存路径
plugin_path: 插件路径 plugin_path: 插件路径
""" """
game_exe = { # 使用改进的目录识别获取安装路径
game: os.path.join( install_paths = self.get_install_paths()
self.selected_folder, info["install_path"].split("/")[0], info["exe"]
debug_mode = self.is_debug_mode()
if debug_mode:
print(f"DEBUG: 准备下载游戏 {game_version}")
print(f"DEBUG: 游戏文件夹: {game_folder}")
# 获取游戏可执行文件路径
game_dirs = self.main_window.identify_game_directories_improved(self.selected_folder)
game_exe_exists = False
if game_version in game_dirs:
game_dir = game_dirs[game_version]
# 游戏目录已经通过可执行文件验证了,可以直接认为存在
game_exe_exists = True
if debug_mode:
print(f"DEBUG: 游戏目录已验证: {game_dir}")
print(f"DEBUG: 游戏可执行文件存在: {game_exe_exists}")
else:
# 回退到传统方法检查游戏是否存在
# 尝试多种可能的文件名格式
expected_exe = GAME_INFO[game_version]["exe"]
traditional_folder = os.path.join(
self.selected_folder,
GAME_INFO[game_version]["install_path"].split("/")[0]
) )
for game, info in GAME_INFO.items()
} # 定义多种可能的可执行文件变体
exe_variants = [
expected_exe, # 标准文件名
expected_exe + ".nocrack", # Steam加密版本
expected_exe.replace(".exe", ""), # 无扩展名版本
expected_exe.replace("NEKOPARA", "nekopara").lower(), # 全小写变体
expected_exe.lower(), # 小写变体
expected_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(traditional_folder, exe_variant)
if os.path.exists(exe_path):
game_exe_exists = True
if debug_mode:
print(f"DEBUG: 找到游戏可执行文件: {exe_path}")
break
# 如果仍未找到,尝试递归搜索
if not game_exe_exists and os.path.exists(traditional_folder):
# 提取卷号或检查是否是After
vol_match = re.search(r"Vol\.(\d+)", game_version)
vol_num = None
if vol_match:
vol_num = vol_match.group(1)
is_after = "After" in game_version
# 遍历游戏目录及其子目录
for root, dirs, files in os.walk(traditional_folder):
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)):
game_exe_exists = True
if debug_mode:
print(f"DEBUG: 通过递归搜索找到游戏可执行文件: {os.path.join(root, file)}")
break
if game_exe_exists:
break
if debug_mode:
print(f"DEBUG: 使用传统方法检查游戏目录: {traditional_folder}")
print(f"DEBUG: 游戏可执行文件存在: {game_exe_exists}")
# 检查游戏是否已安装 # 检查游戏是否已安装
if ( if (
game_version not in game_exe not game_exe_exists
or not os.path.exists(game_exe[game_version])
or self.main_window.installed_status[game_version] or self.main_window.installed_status[game_version]
): ):
self.main_window.installed_status[game_version] = False if debug_mode:
self.main_window.show_result() print(f"DEBUG: 跳过下载游戏 {game_version}")
print(f"DEBUG: 游戏存在: {game_exe_exists}")
print(f"DEBUG: 已安装补丁: {self.main_window.installed_status[game_version]}")
self.main_window.installed_status[game_version] = False if not game_exe_exists else True
self.next_download_task()
return return
# 创建进度窗口并开始下载 # 创建进度窗口并开始下载

View File

@@ -283,13 +283,16 @@ class MainWindow(QMainWindow):
data: 获取到的配置数据 data: 获取到的配置数据
error_message: 错误信息,如果有 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
if error_message: if error_message:
# 标记配置无效 # 标记配置无效
self.config_valid = False self.config_valid = False
# 记录错误信息,用于按钮点击时显示
self.last_error_message = error_message
# 记录错误信息,用于按钮点击时显示
if error_message == "update_required": if error_message == "update_required":
self.last_error_message = "update_required"
msg_box = msgbox_frame( msg_box = msgbox_frame(
f"更新提示 - {APP_NAME}", f"更新提示 - {APP_NAME}",
"\n当前版本过低,请及时更新。\n", "\n当前版本过低,请及时更新。\n",
@@ -302,6 +305,7 @@ class MainWindow(QMainWindow):
self.shutdown_app(force_exit=True) self.shutdown_app(force_exit=True)
return return
elif "missing_keys" in error_message: elif "missing_keys" in error_message:
self.last_error_message = "missing_keys"
missing_versions = error_message.split(":")[1] missing_versions = error_message.split(":")[1]
msg_box = msgbox_frame( msg_box = msgbox_frame(
f"配置缺失 - {APP_NAME}", f"配置缺失 - {APP_NAME}",
@@ -312,8 +316,10 @@ class MainWindow(QMainWindow):
# 对于部分缺失,仍然允许使用,因为可能只影响部分游戏版本 # 对于部分缺失,仍然允许使用,因为可能只影响部分游戏版本
self.config_valid = True self.config_valid = True
else: else:
# 设置网络错误标记
self.last_error_message = "network_error"
# 显示通用错误消息只在debug模式下显示详细错误 # 显示通用错误消息只在debug模式下显示详细错误
debug_mode = self.ui_manager.debug_action.isChecked() if self.ui_manager.debug_action else False
error_msg = "访问云端配置失败,请检查网络状况或稍后再试。" error_msg = "访问云端配置失败,请检查网络状况或稍后再试。"
if debug_mode and "详细错误:" in error_message: if debug_mode and "详细错误:" in error_message:
msg_box = msgbox_frame( msg_box = msgbox_frame(
@@ -337,7 +343,6 @@ class MainWindow(QMainWindow):
# 清除错误信息 # 清除错误信息
self.last_error_message = "" self.last_error_message = ""
debug_mode = self.ui_manager.debug_action.isChecked() if self.ui_manager.debug_action else False
if debug_mode: if debug_mode:
print("--- Cloud config fetched successfully ---") print("--- Cloud config fetched successfully ---")
print(json.dumps(data, indent=2)) print(json.dumps(data, indent=2))
@@ -585,7 +590,7 @@ class MainWindow(QMainWindow):
根据按钮当前状态决定是显示错误还是执行安装 根据按钮当前状态决定是显示错误还是执行安装
""" """
if not self.install_button_enabled: if not self.install_button_enabled:
# 按钮处于"无法安装"状态,显示上次错误消息 # 按钮处于"无法安装"状态
if self.last_error_message == "update_required": if self.last_error_message == "update_required":
msg_box = msgbox_frame( msg_box = msgbox_frame(
f"更新提示 - {APP_NAME}", f"更新提示 - {APP_NAME}",
@@ -593,17 +598,31 @@ class MainWindow(QMainWindow):
QMessageBox.StandardButton.Ok, QMessageBox.StandardButton.Ok,
) )
msg_box.exec() msg_box.exec()
else: elif self.last_error_message == "directory_not_found":
# 默认显示网络错误 # 目录识别失败的特定错误信息
msg_box = msgbox_frame( reply = msgbox_frame(
f"错误 - {APP_NAME}", f"目录错误 - {APP_NAME}",
"\n访问云端配置失败,请检查网络状况或稍后再试。\n", "\n未能识别游戏目录请确认选择的是游戏的上级目录并且目录中包含Nekopara游戏文件夹。\n\n是否重新选择目录?\n",
QMessageBox.StandardButton.Ok, QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No,
) )
msg_box.exec() if reply.exec() == QMessageBox.StandardButton.Yes:
# 重新启用按钮并允许用户选择目录
self.set_start_button_enabled(True)
# 直接调用文件对话框
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()
else: else:
# 按钮处于"开始安装"状态,正常执行安装流程 # 按钮处于"开始安装"状态,正常执行安装流程
self.download_manager.file_dialog() self.download_manager.file_dialog()
def handle_uninstall_button_click(self): def handle_uninstall_button_click(self):
"""处理卸载补丁按钮点击事件 """处理卸载补丁按钮点击事件
@@ -612,33 +631,81 @@ class MainWindow(QMainWindow):
# 获取游戏目录 # 获取游戏目录
from PySide6.QtWidgets import QFileDialog 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
game_dir = QFileDialog.getExistingDirectory(
self,
"选择游戏目录",
""
)
if not game_dir or game_dir == "": # 提示用户选择目录
file_dialog_info = "选择游戏上级目录" if debug_mode else "选择游戏目录"
selected_folder = QFileDialog.getExistingDirectory(self, file_dialog_info, "")
if not selected_folder or selected_folder == "":
return # 用户取消了选择 return # 用户取消了选择
# 验证所选目录是否为有效的游戏目录
game_version = self.identify_game_version(game_dir)
if not game_version: if debug_mode:
msg_box = msgbox_frame( print(f"DEBUG: 卸载功能 - 用户选择了目录: {selected_folder}")
f"错误 - {APP_NAME}",
"\n所选目录不是有效的NEKOPARA游戏目录。\n请选择包含游戏可执行文件的目录。\n", # 首先尝试将选择的目录视为上级目录,使用增强的目录识别功能
QMessageBox.StandardButton.Ok, game_dirs = self.identify_game_directories_improved(selected_folder)
)
msg_box.exec() if game_dirs and len(game_dirs) > 0:
return # 找到了游戏目录,显示选择对话框
if debug_mode:
print(f"DEBUG: 卸载功能 - 在上级目录中找到以下游戏: {list(game_dirs.keys())}")
# 如果只有一个游戏,直接选择它
if len(game_dirs) == 1:
game_version = list(game_dirs.keys())[0]
game_dir = game_dirs[game_version]
self._confirm_and_uninstall(game_dir, game_version)
else:
# 有多个游戏,让用户选择
from PySide6.QtWidgets import QInputDialog
game_versions = list(game_dirs.keys())
selected_game, ok = QInputDialog.getItem(
self, "选择游戏", "选择要卸载补丁的游戏:",
game_versions, 0, False
)
if ok and selected_game:
game_version = selected_game
game_dir = game_dirs[game_version]
self._confirm_and_uninstall(game_dir, game_version)
else:
# 未找到游戏目录,尝试将选择的目录作为游戏目录
if debug_mode:
print(f"DEBUG: 卸载功能 - 未在上级目录找到游戏,尝试将选择的目录视为游戏目录")
game_version = self.identify_game_version(selected_folder)
if game_version:
if debug_mode:
print(f"DEBUG: 卸载功能 - 识别为游戏: {game_version}")
self._confirm_and_uninstall(selected_folder, game_version)
else:
# 两种方式都未识别到游戏
if debug_mode:
print(f"DEBUG: 卸载功能 - 无法识别游戏")
msg_box = msgbox_frame(
f"错误 - {APP_NAME}",
"\n所选目录不是有效的NEKOPARA游戏目录。\n请选择包含游戏可执行文件的目录或其上级目录。\n",
QMessageBox.StandardButton.Ok,
)
msg_box.exec()
def _confirm_and_uninstall(self, game_dir, game_version):
"""确认并卸载补丁
Args:
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
# 确认卸载 # 确认卸载
reply = QMessageBox.question( reply = QMessageBox.question(
self, self,
f"确认卸载 - {APP_NAME}", f"确认卸载 - {APP_NAME}",
f"\n确定要卸载 {game_version} 的补丁吗?\n", f"\n确定要卸载 {game_version} 的补丁吗?\n游戏目录: {game_dir}\n",
QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No, QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No,
QMessageBox.StandardButton.No QMessageBox.StandardButton.No
) )
@@ -648,7 +715,7 @@ class MainWindow(QMainWindow):
# 开始卸载补丁 # 开始卸载补丁
self.uninstall_patch(game_dir, game_version) self.uninstall_patch(game_dir, game_version)
def identify_game_version(self, game_dir): def identify_game_version(self, game_dir):
"""识别游戏版本 """识别游戏版本
@@ -659,31 +726,279 @@ class MainWindow(QMainWindow):
str: 游戏版本名称如果不是有效的游戏目录则返回None str: 游戏版本名称如果不是有效的游戏目录则返回None
""" """
import os 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游戏目录 # 检查是否为NEKOPARA游戏目录
# 通过检查游戏可执行文件来识别游戏版本 # 通过检查游戏可执行文件来识别游戏版本
for game_version, info in GAME_INFO.items(): for game_version, info in GAME_INFO.items():
exe_path = os.path.join(game_dir, info["exe"]) # 尝试多种可能的可执行文件名变体
if os.path.exists(exe_path): exe_variants = [
return game_version info["exe"], # 标准文件名
info["exe"] + ".nocrack", # Steam加密版本
# 如果没有直接匹配,尝试通过目录名称推断 info["exe"].replace(".exe", ""), # 无扩展名版本
dir_name = os.path.basename(game_dir).lower() 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 "vol" in dir_name or "vol." in dir_name: # 如果没有直接匹配,尝试递归搜索
# 尝试提取卷号 if potential_version:
if "vol 1" in dir_name or "vol.1" in dir_name or "vol1" in dir_name: # 从预测的版本中获取卷号或确认是否是After
return "NEKOPARA Vol.1" is_after = "After" in potential_version
elif "vol 2" in dir_name or "vol.2" in dir_name or "vol2" in dir_name: if not vol_num and not is_after:
return "NEKOPARA Vol.2" vol_match = re.search(r"Vol\.(\d+)", potential_version)
elif "vol 3" in dir_name or "vol.3" in dir_name or "vol3" in dir_name: if vol_match:
return "NEKOPARA Vol.3" vol_num = vol_match.group(1)
elif "vol 4" in dir_name or "vol.4" in dir_name or "vol4" in dir_name:
return "NEKOPARA Vol.4" # 递归搜索可执行文件
elif "after" in dir_name: for root, dirs, files in os.walk(game_dir):
return "NEKOPARA After" 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 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): def uninstall_patch(self, game_dir, game_version):
"""卸载补丁 """卸载补丁
@@ -695,6 +1010,8 @@ class MainWindow(QMainWindow):
import os import os
import shutil 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: if game_version not in GAME_INFO:
QMessageBox.critical( QMessageBox.critical(
self, self,
@@ -703,54 +1020,110 @@ class MainWindow(QMainWindow):
QMessageBox.StandardButton.Ok, QMessageBox.StandardButton.Ok,
) )
return return
if debug_mode:
print(f"DEBUG: 开始卸载 {game_version} 补丁,目录: {game_dir}")
try: try:
# 获取补丁文件路径 files_removed = 0
patch_file_path = os.path.join(game_dir, os.path.basename(GAME_INFO[game_version]["install_path"]))
# 检查补丁文件是否存在 # 获取可能的补丁文件路径
if os.path.exists(patch_file_path): install_path_base = os.path.basename(GAME_INFO[game_version]["install_path"])
os.remove(patch_file_path) patch_file_path = os.path.join(game_dir, install_path_base)
print(f"已删除补丁文件: {patch_file_path}")
# 尝试查找补丁文件,支持不同大小写
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) # 检查是否有额外的签名文件 (.sig)
if game_version == "NEKOPARA After": if game_version == "NEKOPARA After":
sig_file_path = f"{patch_file_path}.sig" for patch_path in patch_files_to_check:
sig_file_path = f"{patch_path}.sig"
if os.path.exists(sig_file_path): if os.path.exists(sig_file_path):
os.remove(sig_file_path) os.remove(sig_file_path)
print(f"已删除签名文件: {sig_file_path}") files_removed += 1
if debug_mode:
# 删除patch文件夹 print(f"DEBUG: 已删除签名文件: {sig_file_path}")
patch_folder = os.path.join(game_dir, "patch")
# 删除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): if os.path.exists(patch_folder):
shutil.rmtree(patch_folder) shutil.rmtree(patch_folder)
print(f"已删除补丁文件夹: {patch_folder}") files_removed += 1
if debug_mode:
# 删除game/patch文件夹 print(f"DEBUG: 已删除补丁文件夹: {patch_folder}")
game_patch_folder = os.path.join(game_dir, "game", "patch")
if os.path.exists(game_patch_folder): # 删除game/patch文件夹
shutil.rmtree(game_patch_folder) game_folders = ["game", "Game", "GAME"]
print(f"已删除game/patch文件夹: {game_patch_folder}") patch_folders = ["patch", "Patch", "PATCH"]
# 删除配置文件 for game_folder in game_folders:
config_file = os.path.join(game_dir, "game", "config.json") for patch_folder in patch_folders:
if os.path.exists(config_file): game_patch_folder = os.path.join(game_dir, game_folder, patch_folder)
os.remove(config_file) if os.path.exists(game_patch_folder):
print(f"已删除配置文件: {config_file}") 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}")
scripts_file = os.path.join(game_dir, "game", "scripts.json") # 删除脚本文件
if os.path.exists(scripts_file): for script_file in script_files:
os.remove(scripts_file) script_path = os.path.join(game_path, script_file)
print(f"已删除脚本文件: {scripts_file}") if os.path.exists(script_path):
os.remove(script_path)
# 更新安装状态 files_removed += 1
self.installed_status[game_version] = False if debug_mode:
print(f"DEBUG: 已删除脚本文件: {script_path}")
# 显示卸载成功消息
# 更新安装状态
self.installed_status[game_version] = False
# 显示卸载成功消息
if files_removed > 0:
QMessageBox.information( QMessageBox.information(
self, self,
f"卸载完成 - {APP_NAME}", f"卸载完成 - {APP_NAME}",
f"\n{game_version} 补丁卸载成功!\n", f"\n{game_version} 补丁卸载成功!\n共删除 {files_removed} 个文件/文件夹。\n",
QMessageBox.StandardButton.Ok, QMessageBox.StandardButton.Ok,
) )
else: else:
@@ -763,10 +1136,14 @@ class MainWindow(QMainWindow):
except Exception as e: except Exception as e:
# 显示卸载失败消息 # 显示卸载失败消息
error_message = f"\n卸载 {game_version} 补丁时出错:\n\n{str(e)}\n"
if debug_mode:
print(f"DEBUG: 卸载错误 - {str(e)}")
QMessageBox.critical( QMessageBox.critical(
self, self,
f"卸载失败 - {APP_NAME}", f"卸载失败 - {APP_NAME}",
f"\n卸载 {game_version} 补丁时出错:\n\n{str(e)}\n", error_message,
QMessageBox.StandardButton.Ok, QMessageBox.StandardButton.Ok,
) )

View File

@@ -188,6 +188,7 @@ class AdminPrivileges:
"nekopara_vol1.exe", "nekopara_vol1.exe",
"nekopara_vol2.exe", "nekopara_vol2.exe",
"NEKOPARAvol3.exe", "NEKOPARAvol3.exe",
"NEKOPARAvol3.exe.nocrack",
"nekopara_vol4.exe", "nekopara_vol4.exe",
"nekopara_after.exe", "nekopara_after.exe",
] ]
@@ -230,33 +231,40 @@ class AdminPrivileges:
def check_and_terminate_processes(self): def check_and_terminate_processes(self):
for proc in psutil.process_iter(["pid", "name"]): for proc in psutil.process_iter(["pid", "name"]):
if proc.info["name"] in self.required_exes: proc_name = proc.info["name"].lower() if proc.info["name"] else ""
msg_box = msgbox_frame(
f"进程检测 - {APP_NAME}", # 检查进程名是否匹配任何需要终止的游戏进程
f"\n检测到游戏正在运行: {proc.info['name']} \n\n是否终止?\n", for exe in self.required_exes:
QtWidgets.QMessageBox.StandardButton.Yes | QtWidgets.QMessageBox.StandardButton.No, if exe.lower() == proc_name:
) # 获取不带.nocrack的游戏名称用于显示
reply = msg_box.exec() display_name = exe.replace(".nocrack", "")
if reply == QtWidgets.QMessageBox.StandardButton.Yes:
try: msg_box = msgbox_frame(
proc.terminate() f"进程检测 - {APP_NAME}",
proc.wait(timeout=3) f"\n检测到游戏正在运行: {display_name} \n\n是否终止?\n",
except psutil.AccessDenied: QtWidgets.QMessageBox.StandardButton.Yes | QtWidgets.QMessageBox.StandardButton.No,
)
reply = msg_box.exec()
if reply == QtWidgets.QMessageBox.StandardButton.Yes:
try:
proc.terminate()
proc.wait(timeout=3)
except psutil.AccessDenied:
msg_box = msgbox_frame(
f"错误 - {APP_NAME}",
f"\n无法关闭游戏: {display_name} \n\n请手动关闭后重启应用\n",
QtWidgets.QMessageBox.StandardButton.Ok,
)
msg_box.exec()
sys.exit(1)
else:
msg_box = msgbox_frame( msg_box = msgbox_frame(
f"错误 - {APP_NAME}", f"进程检测 - {APP_NAME}",
f"\n无法关闭游戏: {proc.info['name']} \n\n请手动关闭后重启应用\n", f"\n关闭游戏: {display_name} \n\n请手动关闭后重启应用\n",
QtWidgets.QMessageBox.StandardButton.Ok, QtWidgets.QMessageBox.StandardButton.Ok,
) )
msg_box.exec() msg_box.exec()
sys.exit(1) sys.exit(1)
else:
msg_box = msgbox_frame(
f"进程检测 - {APP_NAME}",
f"\n未关闭的游戏: {proc.info['name']} \n\n请手动关闭后重启应用\n",
QtWidgets.QMessageBox.StandardButton.Ok,
)
msg_box.exec()
sys.exit(1)
class HostsManager: class HostsManager:
def __init__(self): def __init__(self):