mirror of
https://github.com/hyb-oyqq/FRAISEMOE-Addons-Installer-NEXT.git
synced 2025-12-17 12:30:27 +00:00
- 在主窗口中添加优雅的线程清理逻辑,确保在退出时安全停止所有后台线程,避免潜在的资源泄漏。 - 更新离线模式管理器和哈希线程,增强对线程引用的管理,确保在操作完成后及时清理引用。 - 改进补丁检测器,支持在离线模式下的补丁状态检查,提升用户体验和系统稳定性。 - 增强日志记录,确保在关键操作中提供详细的调试信息,便于后续排查和用户反馈。
354 lines
16 KiB
Python
354 lines
16 KiB
Python
from PySide6.QtCore import QThread, Signal
|
||
import os
|
||
import re
|
||
from utils.logger import setup_logger
|
||
|
||
class GameDetectionThread(QThread):
|
||
"""用于在后台线程中执行游戏目录识别的线程"""
|
||
finished = Signal(dict)
|
||
|
||
def __init__(self, detector_func, selected_folder):
|
||
super().__init__()
|
||
self.detector_func = detector_func
|
||
self.selected_folder = selected_folder
|
||
|
||
def run(self):
|
||
result = self.detector_func(self.selected_folder)
|
||
self.finished.emit(result)
|
||
|
||
class GameDetector:
|
||
"""游戏检测器,用于识别游戏目录和版本"""
|
||
|
||
def __init__(self, game_info, debug_manager=None):
|
||
"""初始化游戏检测器
|
||
|
||
Args:
|
||
game_info: 游戏信息字典,包含各版本的安装路径和可执行文件名
|
||
debug_manager: 调试管理器实例,用于输出调试信息
|
||
"""
|
||
self.game_info = game_info
|
||
self.debug_manager = debug_manager
|
||
self.directory_cache = {} # 添加目录缓存
|
||
self.logger = setup_logger("game_detector")
|
||
self.detection_thread = None
|
||
|
||
def identify_game_directories_async(self, selected_folder, callback):
|
||
"""异步识别游戏目录"""
|
||
def on_finished(game_dirs):
|
||
callback(game_dirs)
|
||
self.detection_thread = None
|
||
|
||
self.detection_thread = GameDetectionThread(self.identify_game_directories_improved, selected_folder)
|
||
self.detection_thread.finished.connect(on_finished)
|
||
self.detection_thread.start()
|
||
|
||
def _is_debug_mode(self):
|
||
"""检查是否处于调试模式
|
||
|
||
Returns:
|
||
bool: 是否处于调试模式
|
||
"""
|
||
if hasattr(self.debug_manager, 'ui_manager') and hasattr(self.debug_manager.ui_manager, 'debug_action'):
|
||
return self.debug_manager.ui_manager.debug_action.isChecked()
|
||
return False
|
||
|
||
def identify_game_version(self, game_dir):
|
||
"""识别游戏版本
|
||
|
||
Args:
|
||
game_dir: 游戏目录路径
|
||
|
||
Returns:
|
||
str: 游戏版本名称,如果不是有效的游戏目录则返回None
|
||
"""
|
||
debug_mode = self._is_debug_mode()
|
||
|
||
if debug_mode:
|
||
self.logger.debug(f"尝试识别游戏版本: {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:
|
||
self.logger.debug(f"从目录名推测游戏版本: {potential_version}, 卷号: {vol_num}")
|
||
elif "after" in dir_name:
|
||
potential_version = "NEKOPARA After"
|
||
if debug_mode:
|
||
self.logger.debug(f"从目录名推测游戏版本: NEKOPARA After")
|
||
|
||
# 检查是否为NEKOPARA游戏目录
|
||
# 通过检查游戏可执行文件来识别游戏版本
|
||
for game_version, info in self.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:
|
||
self.logger.debug(f"通过可执行文件确认游戏版本: {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:
|
||
self.logger.debug(f"通过递归搜索确认游戏版本: {potential_version}, 文件: {file}")
|
||
return potential_version
|
||
|
||
# 如果仍然没有找到,基于目录名的推测返回结果
|
||
if potential_version:
|
||
if debug_mode:
|
||
self.logger.debug(f"基于目录名返回推测的游戏版本: {potential_version}")
|
||
return potential_version
|
||
|
||
if debug_mode:
|
||
self.logger.debug(f"无法识别游戏版本: {game_dir}")
|
||
|
||
return None
|
||
|
||
def identify_game_directories_improved(self, selected_folder):
|
||
"""改进的游戏目录识别,支持大小写不敏感和特殊字符处理
|
||
|
||
Args:
|
||
selected_folder: 选择的上级目录
|
||
|
||
Returns:
|
||
dict: 游戏版本到游戏目录的映射
|
||
"""
|
||
debug_mode = self._is_debug_mode()
|
||
|
||
# 检查缓存中是否已有该目录的识别结果
|
||
if selected_folder in self.directory_cache:
|
||
if debug_mode:
|
||
self.logger.debug(f"使用缓存的目录识别结果: {selected_folder}")
|
||
return self.directory_cache[selected_folder]
|
||
|
||
if debug_mode:
|
||
self.logger.debug(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:
|
||
self.logger.debug(f"找到以下子目录: {all_dirs}")
|
||
except Exception as e:
|
||
if debug_mode:
|
||
self.logger.debug(f"无法读取目录 {selected_folder}: {str(e)}")
|
||
return {}
|
||
|
||
for game, info in self.game_info.items():
|
||
expected_dir = info["install_path"].split("/")[0] # 例如 "NEKOPARA Vol. 1"
|
||
expected_exe = info["exe"] # 标准可执行文件名
|
||
|
||
if debug_mode:
|
||
self.logger.debug(f"搜索游戏 {game}, 预期目录: {expected_dir}, 预期可执行文件: {expected_exe}")
|
||
|
||
# 尝试不同的匹配方法
|
||
found_dir = None
|
||
|
||
# 1. 精确匹配
|
||
if expected_dir in all_dirs:
|
||
found_dir = expected_dir
|
||
if debug_mode:
|
||
self.logger.debug(f"精确匹配成功: {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:
|
||
self.logger.debug(f"大小写不敏感匹配成功: {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:
|
||
self.logger.debug(f"模糊匹配成功: {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:
|
||
self.logger.debug(f"提取卷号: {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:
|
||
self.logger.debug(f"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:
|
||
self.logger.debug(f"卷号匹配成功: {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:
|
||
self.logger.debug(f"验证成功,找到游戏可执行文件: {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 "Vol." in game:
|
||
vol_match = re.search(r"Vol\.(\d+)", game)
|
||
if vol_match:
|
||
vol_num = vol_match.group(1)
|
||
if (f"vol{vol_num}" in file_lower or
|
||
f"vol.{vol_num}" in file_lower or
|
||
f"vol {vol_num}" 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:
|
||
self.logger.debug(f"通过递归搜索找到游戏可执行文件: {found_exe}")
|
||
break
|
||
elif "After" in game 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:
|
||
self.logger.debug(f"通过递归搜索找到After游戏可执行文件: {found_exe}")
|
||
break
|
||
if exe_exists:
|
||
break
|
||
|
||
# 如果找到了可执行文件,将该目录添加到游戏目录列表
|
||
if exe_exists:
|
||
game_paths[game] = potential_path
|
||
if debug_mode:
|
||
self.logger.debug(f"验证成功,将 {potential_path} 添加为 {game} 的目录")
|
||
else:
|
||
if debug_mode:
|
||
self.logger.debug(f"未找到任何可执行文件变体,游戏 {game} 在 {potential_path} 未找到")
|
||
|
||
if debug_mode:
|
||
self.logger.debug(f"最终识别的游戏目录: {game_paths}")
|
||
self.logger.debug(f"--- 目录识别结束 ---")
|
||
|
||
# 将识别结果存入缓存
|
||
self.directory_cache[selected_folder] = game_paths
|
||
|
||
return game_paths
|
||
|
||
def clear_directory_cache(self):
|
||
"""清除目录缓存"""
|
||
self.directory_cache = {}
|
||
if self._is_debug_mode():
|
||
self.logger.debug("已清除目录缓存") |