13 Commits
1.1.2 ... 1.2.0

Author SHA1 Message Date
hyb-oyqq
331f7a25d2 refactor(ui): 重构 UI 相关代码并移除冗余资源
- 从 ui_manager.py 和 install.ui 中移除了使用 base64 图片数据的方式
- 采用直接加载图片文件的方法,提高了代码的可读性和维护性
- 删除了未使用的 popup.ui 文件
- 优化了资源路径的获取方式,使用 resource_path 函数统一处理
2025-07-28 15:22:31 +08:00
hyb-oyqq
642b2ec17f feat(main_window): 添加批量卸载游戏补丁功能
- 在卸载补丁界面添加"全部卸载"选项
- 实现批量卸载所有游戏补丁的功能
- 优化单个游戏卸载流程,统一卸载成功和失败的返回值
- 调整卸载消息提示,适应批量卸载和单个卸载的不同场景
2025-07-28 13:09:43 +08:00
hyb-oyqq
41aab89669 feat(core): 优化游戏目录识别和补丁卸载流程
- 改进游戏目录识别算法,支持大小写不敏感和特殊字符处理
- 增加递归搜索可执行文件的功能,提高识别准确率
- 优化补丁卸载流程,支持更灵活的文件路径和名称
- 增加调试模式下的日志输出,便于问题排查
- 重构部分代码结构,提高可维护性和可扩展性
2025-07-28 11:54:52 +08:00
hyb-oyqq
f6a57215c2 refactor(source): 优化窗口大小和背景图显示
- 设置窗口最小和最大尺寸
- 优化背景图加载和显示,使用setScaledContents简化处理
- 调整标题栏和菜单区域的大小和位置
- 重构部分UI代码,提高可读性和维护性
2025-07-25 17:21:30 +08:00
hyb-oyqq
38549e098e feat(core): 添加卸载补丁功能并优化用户界面
- 在主界面添加卸载补丁按钮,实现卸载功能
- 优化菜单区域,使用按钮替代传统菜单栏
- 更新主题颜色,调整按钮布局和样式
- 优化帮助和设置菜单,提升用户体验
2025-07-25 17:00:55 +08:00
hyb-oyqq
286270a819 feat(core): 优化窗口动画和布局
- 新增按钮点击动画效果
- 动态调整窗口大小和布局
- 实现圆角窗口和拖动功能
- 优化安装按钮状态管理
- 更新菜单动画逻辑
- 为2.0新窗口做预实现
2025-07-25 15:18:05 +08:00
hyb-oyqq
0f9c91b59a docs(README): 更新文档状态说明
- 中英文 README 文件中的文档状态说明已更新
2025-07-24 18:24:58 +08:00
hyb-oyqq
3753375bed feat(core): 重构按钮显示和动画
- 更新动画逻辑,使用新的按钮容器
- 优化按钮点击区域和视觉效果
- 添加了得意黑作为字体和样式
2025-07-24 17:43:11 +08:00
hyb-oyqq
dab2ba2dc5 feat(core): 优化安装结果显示和下载管理
- 优化 Cloudflare 加速提示信息格式
- 增加下载历史记录列表,跟踪本次安装的游戏
- 改进安装结果显示,区分不同情况
- 优化哈希检查提示信息
2025-07-24 16:57:16 +08:00
hyb-oyqq
f86cb7aa7e refactor(core): 优化消息框显示和下载流程
- 修改 hash_pop_window 函数,增加检查类型参数
- 优化 Cloudflare 优选失败和成功消息框显示
- 调整下载前后检查的消息框内容
- 改进解压缩后的文件检查流程
2025-07-24 16:29:30 +08:00
hyb-oyqq
0cf9f5e6c2 feat(core): 优化 Cloudflare IP 加速功能
- 在消息框中添加 Cloudflare 图标
- 更新应用版本号至 1.1.3
- 优化配置获取流程,增加错误处理
- 移除未使用的资源文件
- 调整资源路径获取逻辑
2025-07-24 15:14:29 +08:00
hyb-oyqq
f9715f91f7 build: 更新应用版本号 2025-07-24 11:21:52 +08:00
hyb-oyqq
98e51d443e perf(ip_optimizer): 优化 IP 优选逻辑
- 修改 speedtest-cli 命令参数,避免写入结果文件
- 修复最优 IP 查找逻辑,确保只保存第一个匹配的 IP
- 移除不必要的循环退出条件,简化代码逻辑
2025-07-24 11:20:56 +08:00
43 changed files with 1910 additions and 572 deletions

View File

@@ -1,7 +1,7 @@
# 🍓FRAISEMOE-Addons-Installer-NEXT🍓
```
🔊 Note: This repository is still under active development, and most of the documentation is not yet available. We appreciate your understanding.
🔊 Note: This repository's documentation updates have stabilized. If there are any missing parts, please promptly raise an issue. Thank you all for your support!
The English version is not updated in real-time! Please check the Simplified Chinese version for more updates! Thank you for your support!
```

View File

@@ -1,7 +1,7 @@
# 🍓FRAISEMOE-Addons-Installer-NEXT🍓
```
🔊 注意:本库仍然努力更新中,大部分文档不可用,敬请谅解。
🔊 注意:本库文档更新已趋于稳定如有遗漏部分请及时提出issue感谢各位支持
```
<!-- PROJECT SHIELDS -->

BIN
source/IMG/BG/title_bg1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

BIN
source/IMG/BG/title_bg2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

BIN
source/IMG/BTN/Button.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

View File

@@ -1,15 +1,24 @@
import sys
from PySide6.QtCore import (QObject, QPropertyAnimation, QParallelAnimationGroup,
QPoint, QEasingCurve, QTimer, Signal)
from PySide6.QtWidgets import QGraphicsOpacityEffect
QPoint, QEasingCurve, QTimer, Signal, QRect)
from PySide6.QtWidgets import QGraphicsOpacityEffect, QPushButton
from PySide6.QtGui import QColor
class MultiStageAnimations(QObject):
animation_finished = Signal()
def __init__(self, ui, parent=None):
super().__init__(parent)
self.ui = ui
# 获取画布尺寸
self.canvas_width = ui.centralwidget.width()
self.canvas_height = ui.centralwidget.height()
self.parent = parent # 保存父窗口引用以获取当前尺寸
# 获取画布尺寸 - 动态从父窗口获取
if parent:
self.canvas_width = parent.width()
self.canvas_height = parent.height()
else:
# 默认尺寸
self.canvas_width = 1280
self.canvas_height = 720
# 动画时序配置
self.animation_config = {
@@ -18,29 +27,141 @@ class MultiStageAnimations(QObject):
},
"mainbg": {
"delay_after": 500
},
"button_click": {
"scale_duration": 100,
"scale_min": 0.95,
"scale_max": 1.0
}
}
# 第一阶段Logo动画配置
# 第一阶段Logo动画配置根据新布局调整Y坐标
self.logo_widgets = [
{"widget": ui.vol1bg, "delay": 0, "duration": 500, "end_pos": QPoint(0, 120)},
{"widget": ui.vol2bg, "delay": 80, "duration": 500, "end_pos": QPoint(0, 180)},
{"widget": ui.vol3bg, "delay": 160, "duration": 500, "end_pos": QPoint(0, 240)},
{"widget": ui.vol4bg, "delay": 240, "duration": 500, "end_pos": QPoint(0, 300)},
{"widget": ui.afterbg, "delay": 320, "duration": 500, "end_pos": QPoint(0, 360)}
{"widget": ui.vol1bg, "delay": 0, "duration": 500, "end_pos": QPoint(0, 150)},
{"widget": ui.vol2bg, "delay": 80, "duration": 500, "end_pos": QPoint(0, 210)},
{"widget": ui.vol3bg, "delay": 160, "duration": 500, "end_pos": QPoint(0, 270)},
{"widget": ui.vol4bg, "delay": 240, "duration": 500, "end_pos": QPoint(0, 330)},
{"widget": ui.afterbg, "delay": 320, "duration": 500, "end_pos": QPoint(0, 390)}
]
# 第二阶段:菜单元素
# 第二阶段:菜单元素,位置会在开始动画时动态计算
self.menu_widgets = [
{"widget": ui.menubg, "end_pos": QPoint(710, 0), "duration": 600},
{"widget": ui.start_install_btn, "end_pos": QPoint(780, 250), "duration": 600},
{"widget": ui.exit_btn, "end_pos": QPoint(780, 340), "duration": 600}
# 移除菜单背景动画
# {"widget": ui.menubg, "end_pos": QPoint(720, 55), "duration": 600},
{"widget": ui.button_container, "end_pos": None, "duration": 600},
{"widget": ui.uninstall_container, "end_pos": None, "duration": 600}, # 添加卸载补丁按钮
{"widget": ui.exit_container, "end_pos": None, "duration": 600}
]
self.animations = []
self.timers = []
# 设置按钮点击动画
self.setup_button_click_animations()
def setup_button_click_animations(self):
"""设置按钮点击动画"""
# 为开始安装按钮添加点击动画
self.ui.start_install_btn.pressed.connect(
lambda: self.start_button_click_animation(self.ui.button_container)
)
self.ui.start_install_btn.released.connect(
lambda: self.end_button_click_animation(self.ui.button_container)
)
# 为卸载补丁按钮添加点击动画
self.ui.uninstall_btn.pressed.connect(
lambda: self.start_button_click_animation(self.ui.uninstall_container)
)
self.ui.uninstall_btn.released.connect(
lambda: self.end_button_click_animation(self.ui.uninstall_container)
)
# 为退出按钮添加点击动画
self.ui.exit_btn.pressed.connect(
lambda: self.start_button_click_animation(self.ui.exit_container)
)
self.ui.exit_btn.released.connect(
lambda: self.end_button_click_animation(self.ui.exit_container)
)
def start_button_click_animation(self, button_container):
"""开始按钮点击动画"""
# 创建缩放动画
scale_anim = QPropertyAnimation(button_container.children()[0], b"geometry") # 只对按钮背景应用动画
scale_anim.setDuration(self.animation_config["button_click"]["scale_duration"])
# 获取当前几何形状
current_geometry = button_container.children()[0].geometry()
# 计算缩放后的几何形状(保持中心点不变)
scale_factor = self.animation_config["button_click"]["scale_min"]
width_diff = current_geometry.width() * (1 - scale_factor) / 2
height_diff = current_geometry.height() * (1 - scale_factor) / 2
new_geometry = QRect(
current_geometry.x() + width_diff,
current_geometry.y() + height_diff,
current_geometry.width() * scale_factor,
current_geometry.height() * scale_factor
)
scale_anim.setEndValue(new_geometry)
scale_anim.setEasingCurve(QEasingCurve.Type.OutQuad)
# 启动动画
scale_anim.start()
self.animations.append(scale_anim)
# 对文本标签也应用同样的动画
text_anim = QPropertyAnimation(button_container.children()[1], b"geometry")
text_anim.setDuration(self.animation_config["button_click"]["scale_duration"])
text_geometry = button_container.children()[1].geometry()
new_text_geometry = QRect(
text_geometry.x() + width_diff,
text_geometry.y() + height_diff,
text_geometry.width() * scale_factor,
text_geometry.height() * scale_factor
)
text_anim.setEndValue(new_text_geometry)
text_anim.setEasingCurve(QEasingCurve.Type.OutQuad)
text_anim.start()
self.animations.append(text_anim)
def end_button_click_animation(self, button_container):
"""结束按钮点击动画,恢复正常外观"""
# 创建恢复动画 - 对背景
scale_anim = QPropertyAnimation(button_container.children()[0], b"geometry")
scale_anim.setDuration(self.animation_config["button_click"]["scale_duration"])
# 恢复到原始大小 (10,10,191,91)
original_geometry = QRect(10, 10, 191, 91)
scale_anim.setEndValue(original_geometry)
scale_anim.setEasingCurve(QEasingCurve.Type.OutElastic)
# 启动动画
scale_anim.start()
self.animations.append(scale_anim)
# 恢复文本标签
text_anim = QPropertyAnimation(button_container.children()[1], b"geometry")
text_anim.setDuration(self.animation_config["button_click"]["scale_duration"])
# 恢复文本到原始大小 (10,7,191,91)
text_anim.setEndValue(QRect(10, 7, 191, 91))
text_anim.setEasingCurve(QEasingCurve.Type.OutElastic)
text_anim.start()
self.animations.append(text_anim)
def initialize(self):
"""初始化所有组件状态"""
# 更新画布尺寸
if self.parent:
self.canvas_width = self.parent.width()
self.canvas_height = self.parent.height()
# 设置Mainbg初始状态
effect = QGraphicsOpacityEffect(self.ui.Mainbg)
effect.setOpacity(0)
@@ -125,6 +246,12 @@ class MultiStageAnimations(QObject):
self.animations.append(main_anim)
def start_menu_animations(self):
"""启动菜单动画(从下往上)"""
# 更新按钮最终位置
self._update_button_positions()
# 跟踪最后一个动画用于连接finished信号
last_anim = None
for item in self.menu_widgets:
anim_group = QParallelAnimationGroup()
@@ -144,11 +271,69 @@ class MultiStageAnimations(QObject):
anim_group.addAnimation(pos_anim)
anim_group.addAnimation(opacity_anim)
if item["widget"] == self.ui.exit_btn:
anim_group.finished.connect(self.animation_finished.emit)
# 记录最后一个按钮的动画
if item["widget"] == self.ui.exit_container:
last_anim = anim_group
anim_group.start()
self.animations.append(anim_group)
# 在最后一个动画完成时发出信号
if last_anim:
last_anim.finished.connect(self.animation_finished.emit)
def _update_button_positions(self):
"""更新按钮最终位置"""
# 根据当前窗口大小动态计算按钮位置
if self.parent:
width = self.parent.width()
height = self.parent.height()
# 计算按钮位置
right_margin = 20 # 减小右边距,使按钮更靠右
# 开始安装按钮
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中保持一致
# 更新动画目标位置
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, '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中保持一致
# 更新动画目标位置
for item in self.menu_widgets:
if item["widget"] == self.ui.uninstall_container:
item["end_pos"] = QPoint(x_pos, y_pos)
# 退出按钮
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中保持一致
# 更新动画目标位置
for item in self.menu_widgets:
if item["widget"] == self.ui.exit_container:
item["end_pos"] = QPoint(x_pos, y_pos)
else:
# 默认位置
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:
item["end_pos"] = QPoint(1050, 310)
elif item["widget"] == self.ui.exit_container:
item["end_pos"] = QPoint(1050, 420)
def start_animations(self):
"""启动完整动画序列"""
self.clear_animations()

View File

@@ -3,11 +3,13 @@ import requests
import json
from collections import deque
from urllib.parse import urlparse
import re # Added for recursive search
from PySide6 import QtWidgets
from PySide6 import QtWidgets, QtCore
from PySide6.QtCore import Qt
from PySide6.QtGui import QIcon, QPixmap
from utils import msgbox_frame, HostsManager
from utils import msgbox_frame, HostsManager, resource_path
from data.config import APP_NAME, PLUGIN, GAME_INFO, UA, CONFIG_URL
from workers import IpOptimizerThread
@@ -41,10 +43,29 @@ class DownloadManager:
def get_install_paths(self):
"""获取所有游戏版本的安装路径"""
return {
game: os.path.join(self.selected_folder, info["install_path"])
for game, info in GAME_INFO.items()
}
# 使用改进的目录识别功能
game_dirs = self.main_window.identify_game_directories_improved(self.selected_folder)
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):
"""检查是否处于调试模式"""
@@ -139,10 +160,33 @@ class DownloadManager:
def download_action(self):
"""开始下载流程"""
# 禁用开始安装按钮
self.main_window.ui.start_install_btn.setEnabled(False)
self.main_window.set_start_button_enabled(False)
# 清空下载历史记录
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()
self.main_window.hash_msg_box = self.main_window.hash_manager.hash_pop_window(check_type="pre")
# 执行预检查
install_paths = self.get_install_paths()
@@ -169,7 +213,7 @@ class DownloadManager:
self.main_window, f"错误 - {APP_NAME}", "\n网络状态异常或服务器故障,请重试\n"
)
# 重新启用开始安装按钮
self.main_window.ui.start_install_btn.setEnabled(True)
self.main_window.set_start_button_enabled(True)
return
# 填充下载队列
@@ -184,8 +228,18 @@ class DownloadManager:
# 询问用户是否使用Cloudflare加速
msg_box = QtWidgets.QMessageBox(self.main_window)
msg_box.setWindowTitle(f"下载优化 - {APP_NAME}")
msg_box.setText("是否愿意通过Cloudflare加速来优化下载速度\n\n这将临时修改系统的hosts文件并需要管理员权限。")
msg_box.setIcon(QtWidgets.QMessageBox.Icon.Question)
msg_box.setText("是否愿意通过Cloudflare加速来优化下载速度\n\n这将临时修改系统的hosts文件并需要管理员权限。\n如您的杀毒软件提醒有软件正在修改hosts文件请注意放行。")
# 设置Cloudflare图标
cf_icon_path = resource_path("IMG/ICO/cloudflare_logo_icon.ico")
if os.path.exists(cf_icon_path):
cf_pixmap = QPixmap(cf_icon_path)
if not cf_pixmap.isNull():
msg_box.setWindowIcon(QIcon(cf_pixmap))
msg_box.setIconPixmap(cf_pixmap.scaled(64, 64, Qt.AspectRatioMode.KeepAspectRatio,
Qt.TransformationMode.SmoothTransformation))
else:
msg_box.setIcon(QtWidgets.QMessageBox.Icon.Question)
yes_button = msg_box.addButton("是,开启加速", QtWidgets.QMessageBox.ButtonRole.YesRole)
no_button = msg_box.addButton("否,直接下载", QtWidgets.QMessageBox.ButtonRole.NoRole)
@@ -210,26 +264,61 @@ class DownloadManager:
# 清空现有队列
self.download_queue.clear()
# 创建下载历史记录列表,用于跟踪本次安装的游戏
if not hasattr(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
for i in range(1, 5):
game_version = f"NEKOPARA Vol.{i}"
if not self.main_window.installed_status.get(game_version, False):
url = config.get(f"vol{i}")
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")
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.main_window.download_queue_history.append(game_version)
# 添加nekopara after
game_version = "NEKOPARA After"
if not self.main_window.installed_status.get(game_version, False):
url = config.get("after")
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")
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.main_window.download_queue_history.append(game_version)
def _start_ip_optimization(self, url):
"""开始IP优化过程
@@ -240,10 +329,21 @@ class DownloadManager:
# 禁用退出按钮
self.main_window.ui.exit_btn.setEnabled(False)
# 使用Cloudflare图标创建消息框
self.optimizing_msg_box = msgbox_frame(
f"通知 - {APP_NAME}",
"\n正在优选Cloudflare IP请稍候...\n\n这可能需要5-10分钟请耐心等待喵~"
)
# 设置Cloudflare图标
cf_icon_path = resource_path("IMG/ICO/cloudflare_logo_icon.ico")
if os.path.exists(cf_icon_path):
cf_pixmap = QPixmap(cf_icon_path)
if not cf_pixmap.isNull():
self.optimizing_msg_box.setWindowIcon(QIcon(cf_pixmap))
self.optimizing_msg_box.setIconPixmap(cf_pixmap.scaled(64, 64, Qt.AspectRatioMode.KeepAspectRatio,
Qt.TransformationMode.SmoothTransformation))
# 我们不再提供"跳过"按钮
self.optimizing_msg_box.setStandardButtons(QtWidgets.QMessageBox.StandardButton.NoButton)
self.optimizing_msg_box.setWindowModality(Qt.WindowModality.ApplicationModal)
@@ -271,11 +371,33 @@ class DownloadManager:
# 显示优选结果
if not ip:
QtWidgets.QMessageBox.warning(
self.main_window,
f"优选失败 - {APP_NAME}",
"\n未能找到合适的Cloudflare IP将使用默认网络进行下载。\n"
)
msg_box = QtWidgets.QMessageBox(self.main_window)
msg_box.setWindowTitle(f"优选失败 - {APP_NAME}")
msg_box.setText("\n未能找到合适的Cloudflare IP将使用默认网络进行下载。\n\n10秒后自动继续...")
msg_box.setIcon(QtWidgets.QMessageBox.Icon.Warning)
ok_button = msg_box.addButton("确定 (10)", QtWidgets.QMessageBox.ButtonRole.AcceptRole)
# 创建计时器实现倒计时
countdown = 10
timer = QtCore.QTimer(self.main_window)
def update_countdown():
nonlocal countdown
countdown -= 1
ok_button.setText(f"确定 ({countdown})")
if countdown <= 0:
timer.stop()
if msg_box.isVisible():
msg_box.accept()
timer.timeout.connect(update_countdown)
timer.start(1000) # 每秒更新一次
# 显示对话框,但不阻塞主线程
msg_box.open()
# 连接关闭信号以停止计时器
msg_box.finished.connect(timer.stop)
else:
# 应用优选IP到hosts文件
if self.download_queue:
@@ -286,11 +408,33 @@ class DownloadManager:
self.hosts_manager.clean_hostname_entries(hostname)
if self.hosts_manager.apply_ip(hostname, ip):
QtWidgets.QMessageBox.information(
self.main_window,
f"成功 - {APP_NAME}",
f"\n已将优选IP ({ip}) 应用到hosts文件。\n"
)
msg_box = QtWidgets.QMessageBox(self.main_window)
msg_box.setWindowTitle(f"成功 - {APP_NAME}")
msg_box.setText(f"\n已将优选IP ({ip}) 应用到hosts文件。\n\n10秒后自动继续...")
msg_box.setIcon(QtWidgets.QMessageBox.Icon.Information)
ok_button = msg_box.addButton("确定 (10)", QtWidgets.QMessageBox.ButtonRole.AcceptRole)
# 创建计时器实现倒计时
countdown = 10
timer = QtCore.QTimer(self.main_window)
def update_countdown():
nonlocal countdown
countdown -= 1
ok_button.setText(f"确定 ({countdown})")
if countdown <= 0:
timer.stop()
if msg_box.isVisible():
msg_box.accept()
timer.timeout.connect(update_countdown)
timer.start(1000) # 每秒更新一次
# 显示对话框,但不阻塞主线程
msg_box.open()
# 连接关闭信号以停止计时器
msg_box.finished.connect(timer.stop)
else:
QtWidgets.QMessageBox.critical(
self.main_window,
@@ -298,8 +442,8 @@ class DownloadManager:
"\n修改hosts文件失败请检查程序是否以管理员权限运行。\n"
)
# 开始下载
self.next_download_task()
# 计时器结束或用户点击确定时,继续下载
QtCore.QTimer.singleShot(10000, self.next_download_task)
def next_download_task(self):
"""处理下载队列中的下一个任务"""
@@ -325,21 +469,109 @@ class DownloadManager:
_7z_path: 7z文件保存路径
plugin_path: 插件路径
"""
game_exe = {
game: os.path.join(
self.selected_folder, info["install_path"].split("/")[0], info["exe"]
# 使用改进的目录识别获取安装路径
install_paths = self.get_install_paths()
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 (
game_version not in game_exe
or not os.path.exists(game_exe[game_version])
not game_exe_exists
or self.main_window.installed_status[game_version]
):
self.main_window.installed_status[game_version] = False
self.main_window.show_result()
if debug_mode:
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
# 创建进度窗口并开始下载
@@ -428,7 +660,7 @@ class DownloadManager:
return
# 下载成功,开始解压缩
self.main_window.hash_msg_box = self.main_window.hash_manager.hash_pop_window()
self.main_window.hash_msg_box = self.main_window.hash_manager.hash_pop_window(check_type="extraction")
# 创建并启动解压线程
self.main_window.extraction_thread = self.main_window.create_extraction_thread(
@@ -488,6 +720,6 @@ class DownloadManager:
# 重新启用退出按钮和开始安装按钮
self.main_window.ui.exit_btn.setEnabled(True)
self.main_window.ui.start_install_btn.setEnabled(True)
self.main_window.set_start_button_enabled(True)
self.main_window.show_result()

View File

@@ -1,11 +1,10 @@
from PySide6.QtGui import QIcon, QAction
from PySide6.QtGui import QIcon, QAction, QFont
from PySide6.QtWidgets import QMessageBox, QMainWindow
from PySide6.QtCore import Qt
import webbrowser
from utils import load_base64_image, msgbox_frame
from data.config import APP_NAME, APP_VERSION
from data.pic_data import img_data
class UIManager:
def __init__(self, main_window):
@@ -22,10 +21,11 @@ class UIManager:
def setup_ui(self):
"""设置UI元素包括窗口图标、标题和菜单"""
# 设置窗口图标
icon_data = img_data.get("icon")
if icon_data:
pixmap = load_base64_image(icon_data)
self.main_window.setWindowIcon(QIcon(pixmap))
import os
from utils import resource_path
icon_path = resource_path(os.path.join("IMG", "ICO", "icon.png"))
if os.path.exists(icon_path):
self.main_window.setWindowIcon(QIcon(icon_path))
# 设置窗口标题
self.main_window.setWindowTitle(f"{APP_NAME} v{APP_VERSION}")
@@ -39,20 +39,28 @@ class UIManager:
if not self.ui or not hasattr(self.ui, 'menu_2'):
return
# 创建菜单项
project_home_action = QAction("项目主页", self.main_window)
project_home_action.triggered.connect(self.open_project_home_page)
about_action = QAction("关于", self.main_window)
about_action.triggered.connect(self.show_about_dialog)
# 添加到菜单
self.ui.menu_2.addAction(project_home_action)
self.ui.menu_2.addAction(about_action)
# 连接按钮点击事件,如果使用按钮式菜单
if hasattr(self.ui, 'help_btn'):
# 按钮已经连接到显示菜单,不需要额外处理
pass
def _setup_settings_menu(self):
"""设置"设置"菜单"""
if not self.ui or not hasattr(self.ui, 'menu'):
return
# 创建菜单项
self.debug_action = QAction("Debug模式", self.main_window, checkable=True)
# 安全地获取config属性
@@ -67,13 +75,22 @@ class UIManager:
if hasattr(self.main_window, 'toggle_debug_mode'):
self.debug_action.triggered.connect(self.main_window.toggle_debug_mode)
# 添加到菜单
self.ui.menu.addAction(self.debug_action)
# 为未来功能预留的"切换下载源"按钮
self.switch_source_action = QAction("切换下载源", self.main_window)
self.switch_source_action.setEnabled(False) # 暂时禁用
self.ui.menu.addAction(self.switch_source_action)
# 添加分隔符
self.ui.menu.addSeparator()
# 连接按钮点击事件,如果使用按钮式菜单
if hasattr(self.ui, 'settings_btn'):
# 按钮已经连接到显示菜单,不需要额外处理
pass
def open_project_home_page(self):
"""打开项目主页"""
webbrowser.open("https://github.com/hyb-oyqq/FRAISEMOE-Addons-Installer-NEXT")

View File

@@ -3,7 +3,7 @@ import base64
# 配置信息
app_data = {
"APP_VERSION": "1.1.2",
"APP_VERSION": "1.1.3",
"APP_NAME": "FRAISEMOE Addons Installer NEXT",
"TEMP": "TEMP",
"CACHE": "FRAISEMOE",

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

Before

Width:  |  Height:  |  Size: 264 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 162 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 179 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 571 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 250 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 229 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 264 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 177 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 327 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 166 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 184 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 197 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 166 KiB

View File

@@ -1,4 +1,3 @@
from data.pic_data import img_data
from PySide6.QtGui import QPixmap
import base64
from PySide6.QtCore import (QCoreApplication, QDate, QDateTime, QLocale,
@@ -8,105 +7,459 @@ from PySide6.QtGui import (QAction, QBrush, QColor, QConicalGradient,
QCursor, QFont, QFontDatabase, QGradient,
QIcon, QImage, QKeySequence, QLinearGradient,
QPainter, QPalette, QPixmap, QRadialGradient,
QTransform)
QTransform, QPainterPath, QRegion)
from PySide6.QtWidgets import (QApplication, QLabel, QMainWindow, QMenu,
QMenuBar, QPushButton, QSizePolicy, QWidget)
def load_base64_image(base64_str):
pixmap = QPixmap()
pixmap.loadFromData(base64.b64decode(base64_str))
return pixmap
QMenuBar, QPushButton, QSizePolicy, QWidget, QHBoxLayout)
import os
# 导入配置常量
from data.config import APP_NAME, APP_VERSION
from utils import load_image_from_file
class Ui_MainWindows(object):
def setupUi(self, MainWindows):
if not MainWindows.objectName():
MainWindows.setObjectName(u"MainWindows")
MainWindows.setEnabled(True)
MainWindows.resize(1024, 576)
MainWindows.setMinimumSize(QSize(1024, 576))
MainWindows.setMaximumSize(QSize(1024, 576))
# 调整窗口默认大小为1280x720以匹配背景图片
MainWindows.resize(1280, 720)
# 锁定窗口比例为16:9确保不会变形同时限制最大尺寸不超过背景图片
MainWindows.setMinimumSize(QSize(1024, 576)) # 16:9最小尺寸
MainWindows.setMaximumSize(QSize(1280, 720)) # 将最大尺寸限制为1280x720
# 设置固定纵横比
self.aspect_ratio = 16/9
MainWindows.setMouseTracking(False)
MainWindows.setTabletTracking(False)
MainWindows.setAcceptDrops(True)
MainWindows.setAutoFillBackground(True)
MainWindows.setAutoFillBackground(False)
MainWindows.setToolButtonStyle(Qt.ToolButtonStyle.ToolButtonIconOnly)
MainWindows.setAnimated(True)
MainWindows.setDocumentMode(False)
MainWindows.setDockNestingEnabled(False)
# 加载自定义字体
font_id = QFontDatabase.addApplicationFont(os.path.join(os.path.dirname(os.path.dirname(__file__)), "fonts", "SmileySans-Oblique.ttf"))
font_family = QFontDatabase.applicationFontFamilies(font_id)[0] if font_id != -1 else "Arial"
self.custom_font = QFont(font_family, 16) # 创建字体对象大小为16
self.custom_font.setWeight(QFont.Weight.Medium) # 设置为中等粗细,不要太粗
self.centralwidget = QWidget(MainWindows)
self.centralwidget.setObjectName(u"centralwidget")
self.centralwidget.setAutoFillBackground(True)
self.loadbg = QLabel(self.centralwidget)
self.centralwidget.setAutoFillBackground(False) # 修改为False以支持透明背景
self.centralwidget.setStyleSheet("""
QWidget#centralwidget {
background-color: transparent;
}
""")
# 圆角背景容器
self.main_container = QWidget(self.centralwidget)
self.main_container.setObjectName(u"main_container")
self.main_container.setGeometry(QRect(0, 0, 1280, 720))
self.main_container.setStyleSheet("""
QWidget#main_container {
background-color: #E96948;
border-radius: 20px;
border: 1px solid #E96948;
}
""")
# 内容容器 - 用于限制内容在圆角范围内
self.content_container = QWidget(self.main_container)
self.content_container.setObjectName(u"content_container")
self.content_container.setGeometry(QRect(0, 0, 1280, 720))
self.content_container.setStyleSheet("""
QWidget#content_container {
background-color: transparent;
border-radius: 20px;
}
""")
# 添加圆角裁剪,确保内容在圆角范围内
rect = QRect(0, 0, 1280, 720)
path = QPainterPath()
path.addRoundedRect(rect, 20, 20)
region = QRegion(path.toFillPolygon().toPolygon())
self.content_container.setMask(region)
# 标题栏
self.title_bar = QWidget(self.content_container)
self.title_bar.setObjectName(u"title_bar")
self.title_bar.setGeometry(QRect(0, 0, 1280, 35)) # 减小高度从40到35
self.title_bar.setStyleSheet("""
QWidget#title_bar {
background-color: #E96948;
border-top-left-radius: 20px;
border-top-right-radius: 20px;
border-bottom: 1px solid #F47A5B;
}
""")
# 标题栏布局
self.title_layout = QHBoxLayout(self.title_bar)
self.title_layout.setSpacing(10)
self.title_layout.setContentsMargins(10, 0, 10, 0)
# 添加最小化和关闭按钮到标题栏
self.minimize_btn = QPushButton(self.title_bar)
self.minimize_btn.setObjectName(u"minimize_btn")
self.minimize_btn.setMinimumSize(QSize(24, 24))
self.minimize_btn.setMaximumSize(QSize(24, 24))
self.minimize_btn.setCursor(QCursor(Qt.CursorShape.PointingHandCursor))
self.minimize_btn.setStyleSheet("""
QPushButton {
background-color: #FFC107;
border-radius: 12px;
border: none;
}
QPushButton:hover {
background-color: #FFD54F;
}
QPushButton:pressed {
background-color: #FFA000;
}
""")
self.minimize_btn.setText("")
self.minimize_btn.setFont(QFont(font_family, 10))
self.close_btn = QPushButton(self.title_bar)
self.close_btn.setObjectName(u"close_btn")
self.close_btn.setMinimumSize(QSize(24, 24))
self.close_btn.setMaximumSize(QSize(24, 24))
self.close_btn.setCursor(QCursor(Qt.CursorShape.PointingHandCursor))
self.close_btn.setStyleSheet("""
QPushButton {
background-color: #F44336;
border-radius: 12px;
border: none;
color: white;
font-weight: bold;
}
QPushButton:hover {
background-color: #EF5350;
}
QPushButton:pressed {
background-color: #D32F2F;
}
""")
self.close_btn.setText("×")
self.close_btn.setFont(QFont(font_family, 14))
# 标题文本
self.title_label = QLabel(self.title_bar)
self.title_label.setObjectName(u"title_label")
# 直接使用APP_NAME并添加版本号
self.title_label.setText(f"{APP_NAME} v{APP_VERSION}")
title_font = QFont(font_family, 14) # 减小字体从16到14
title_font.setBold(True)
self.title_label.setFont(title_font)
self.title_label.setStyleSheet("color: #333333; padding-left: 10px;")
self.title_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
# 添加按钮到标题栏布局
self.title_layout.addWidget(self.title_label)
self.title_layout.addStretch(1)
self.title_layout.addWidget(self.minimize_btn)
self.title_layout.addSpacing(5)
self.title_layout.addWidget(self.close_btn)
# 修改菜单区域 - 确保足够宽以容纳更多菜单项
self.menu_area = QWidget(self.content_container)
self.menu_area.setObjectName(u"menu_area")
self.menu_area.setGeometry(QRect(0, 35, 1280, 30)) # 调整位置从40到35高度从35到30
self.menu_area.setStyleSheet("""
QWidget#menu_area {
background-color: #E96948;
}
""")
# 不再使用菜单栏,改用普通按钮
# 创建菜单按钮字体
menu_font = QFont(font_family, 14) # 进一步减小字体大小到14
menu_font.setBold(True)
# 设置按钮
self.settings_btn = QPushButton("设置", self.menu_area)
self.settings_btn.setObjectName(u"settings_btn")
self.settings_btn.setGeometry(QRect(20, 1, 80, 28)) # 调整高度和Y位置
self.settings_btn.setFont(menu_font)
self.settings_btn.setCursor(QCursor(Qt.CursorShape.PointingHandCursor))
self.settings_btn.setStyleSheet("""
QPushButton {
background-color: transparent;
color: white;
border: none;
text-align: left;
padding-left: 10px;
}
QPushButton:hover {
background-color: #F47A5B;
border-radius: 4px;
}
QPushButton:pressed {
background-color: #D25A3C;
border-radius: 4px;
}
""")
# 帮助按钮
self.help_btn = QPushButton("帮助", self.menu_area)
self.help_btn.setObjectName(u"help_btn")
self.help_btn.setGeometry(QRect(120, 1, 80, 28)) # 调整高度和Y位置
self.help_btn.setFont(menu_font)
self.help_btn.setCursor(QCursor(Qt.CursorShape.PointingHandCursor))
self.help_btn.setStyleSheet(self.settings_btn.styleSheet())
# 将原来的菜单项移到全局,方便访问
self.menu = QMenu(self.content_container)
self.menu.setObjectName(u"menu")
self.menu.setTitle("设置")
self.menu.setFont(menu_font)
self.menu.setStyleSheet("""
QMenu {
background-color: #E96948;
color: white;
font-size: 16px;
font-weight: bold;
border: 1px solid #F47A5B;
padding: 8px;
border-radius: 6px;
margin-top: 2px;
}
QMenu::item {
padding: 6px 20px 6px 15px;
background-color: transparent;
min-width: 120px;
color: white;
}
QMenu::item:selected {
background-color: #F47A5B;
border-radius: 4px;
}
QMenu::separator {
height: 1px;
background-color: #F47A5B;
margin: 5px 15px;
}
""")
self.menu_2 = QMenu(self.content_container)
self.menu_2.setObjectName(u"menu_2")
self.menu_2.setTitle("帮助")
self.menu_2.setFont(menu_font)
self.menu_2.setStyleSheet(self.menu.styleSheet())
# 连接按钮点击事件到显示对应菜单
self.settings_btn.clicked.connect(lambda: self.show_menu(self.menu, self.settings_btn))
self.help_btn.clicked.connect(lambda: self.show_menu(self.menu_2, self.help_btn))
# 预留位置给未来可能的第三个按钮
# 第三个按钮可以这样添加:
# self.third_btn = QPushButton("第三项", self.menu_area)
# self.third_btn.setObjectName(u"third_btn")
# self.third_btn.setGeometry(QRect(320, 0, 120, 35))
# self.third_btn.setFont(menu_font)
# self.third_btn.setCursor(QCursor(Qt.CursorShape.PointingHandCursor))
# self.third_btn.setStyleSheet(self.settings_btn.styleSheet())
# self.third_btn.clicked.connect(lambda: self.show_menu(self.menu_3, self.third_btn))
# 内容子容器
self.inner_content = QWidget(self.content_container)
self.inner_content.setObjectName(u"inner_content")
# 确保宽度足够大,保证右侧元素完全显示
self.inner_content.setGeometry(QRect(0, 65, 1280, 655)) # 调整Y位置从75到65高度从645到655
self.inner_content.setStyleSheet("""
QWidget#inner_content {
background-color: transparent;
border-bottom-left-radius: 20px;
border-bottom-right-radius: 20px;
}
""")
# 添加底部圆角裁剪
inner_rect = QRect(0, 0, 1280, 665)
inner_path = QPainterPath()
inner_path.addRoundedRect(inner_rect, 20, 20)
inner_region = QRegion(inner_path.toFillPolygon().toPolygon())
self.inner_content.setMask(inner_region)
# 在主容器中添加背景和内容元素
# 修改loadbg使用title_bg1.png作为整个背景
# 原来的loadbg保持不变
self.loadbg = QLabel(self.inner_content)
self.loadbg.setObjectName(u"loadbg")
self.loadbg.setGeometry(QRect(0, 0, 1031, 561))
self.loadbg.setPixmap(load_base64_image(img_data["loadbg"]))
self.loadbg.setGeometry(QRect(0, 0, 1280, 655))
# 加载背景图并允许拉伸
bg_path = os.path.join(os.path.dirname(os.path.dirname(__file__)), "IMG", "BG", "bg1.jpg")
bg_pixmap = QPixmap(bg_path)
self.loadbg.setPixmap(bg_pixmap)
self.loadbg.setScaledContents(True)
self.vol1bg = QLabel(self.centralwidget)
self.vol1bg = QLabel(self.inner_content)
self.vol1bg.setObjectName(u"vol1bg")
self.vol1bg.setGeometry(QRect(0, 120, 93, 64))
self.vol1bg.setPixmap(load_base64_image(img_data["vol1"]))
self.vol1bg.setGeometry(QRect(0, 150, 93, 64))
# 直接加载图片文件
vol1_path = os.path.join(os.path.dirname(os.path.dirname(__file__)), "IMG", "LOGO", "vo01_logo.png")
self.vol1bg.setPixmap(QPixmap(vol1_path))
self.vol1bg.setScaledContents(True)
self.vol2bg = QLabel(self.centralwidget)
self.vol2bg = QLabel(self.inner_content)
self.vol2bg.setObjectName(u"vol2bg")
self.vol2bg.setGeometry(QRect(0, 180, 93, 64))
self.vol2bg.setPixmap(load_base64_image(img_data["vol2"]))
self.vol2bg.setGeometry(QRect(0, 210, 93, 64))
# 直接加载图片文件
vol2_path = os.path.join(os.path.dirname(os.path.dirname(__file__)), "IMG", "LOGO", "vo02_logo.png")
self.vol2bg.setPixmap(QPixmap(vol2_path))
self.vol2bg.setScaledContents(True)
self.vol3bg = QLabel(self.centralwidget)
self.vol3bg = QLabel(self.inner_content)
self.vol3bg.setObjectName(u"vol3bg")
self.vol3bg.setGeometry(QRect(0, 240, 93, 64))
self.vol3bg.setPixmap(load_base64_image(img_data["vol3"]))
self.vol3bg.setGeometry(QRect(0, 270, 93, 64))
# 直接加载图片文件
vol3_path = os.path.join(os.path.dirname(os.path.dirname(__file__)), "IMG", "LOGO", "vo03_logo.png")
self.vol3bg.setPixmap(QPixmap(vol3_path))
self.vol3bg.setScaledContents(True)
self.vol4bg = QLabel(self.centralwidget)
self.vol4bg = QLabel(self.inner_content)
self.vol4bg.setObjectName(u"vol4bg")
self.vol4bg.setGeometry(QRect(0, 300, 93, 64))
self.vol4bg.setPixmap(load_base64_image(img_data["vol4"]))
self.vol4bg.setGeometry(QRect(0, 330, 93, 64))
# 直接加载图片文件
vol4_path = os.path.join(os.path.dirname(os.path.dirname(__file__)), "IMG", "LOGO", "vo04_logo.png")
self.vol4bg.setPixmap(QPixmap(vol4_path))
self.vol4bg.setScaledContents(True)
self.afterbg = QLabel(self.centralwidget)
self.afterbg = QLabel(self.inner_content)
self.afterbg.setObjectName(u"afterbg")
self.afterbg.setGeometry(QRect(0, 360, 93, 64))
self.afterbg.setPixmap(load_base64_image(img_data["after"]))
self.afterbg.setGeometry(QRect(0, 390, 93, 64))
# 直接加载图片文件
after_path = os.path.join(os.path.dirname(os.path.dirname(__file__)), "IMG", "LOGO", "voaf_logo.png")
self.afterbg.setPixmap(QPixmap(after_path))
self.afterbg.setScaledContents(True)
self.Mainbg = QLabel(self.centralwidget)
# 修复Mainbg位置并使用title_bg1.png作为背景图片
self.Mainbg = QLabel(self.inner_content)
self.Mainbg.setObjectName(u"Mainbg")
self.Mainbg.setGeometry(QRect(0, 0, 1031, 561))
self.Mainbg.setPixmap(load_base64_image(img_data["Mainbg"]))
self.Mainbg.setScaledContents(True)
self.start_install_btn = QPushButton(self.centralwidget)
self.Mainbg.setGeometry(QRect(0, 0, 1280, 655))
# 允许拉伸以填满整个区域
main_bg_pixmap = load_image_from_file(os.path.join(os.path.dirname(os.path.dirname(__file__)), "IMG", "BG", "title_bg1.png"))
# 如果加载的图片不是空的,则设置,并允许拉伸填满
if not main_bg_pixmap.isNull():
self.Mainbg.setPixmap(main_bg_pixmap)
self.Mainbg.setScaledContents(True)
self.Mainbg.setAlignment(Qt.AlignmentFlag.AlignCenter)
# 使用新的按钮图片
button_pixmap = load_image_from_file(os.path.join(os.path.dirname(os.path.dirname(__file__)), "IMG", "BTN", "Button.png"))
# 创建文本标签布局的按钮
# 开始安装按钮 - 基于背景图片和标签组合
# 调整开始安装按钮的位置
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.start_install_bg = QLabel(self.button_container)
self.start_install_bg.setObjectName(u"start_install_bg")
self.start_install_bg.setGeometry(QRect(10, 10, 191, 91)) # 居中放置在扩大的容器中
self.start_install_bg.setPixmap(button_pixmap)
self.start_install_bg.setScaledContents(True)
self.start_install_text = QLabel(self.button_container)
self.start_install_text.setObjectName(u"start_install_text")
self.start_install_text.setGeometry(QRect(10, 7, 191, 91)) # 居中放置在扩大的容器中
self.start_install_text.setText("开始安装")
self.start_install_text.setFont(self.custom_font)
self.start_install_text.setAlignment(Qt.AlignmentFlag.AlignCenter)
self.start_install_text.setStyleSheet("letter-spacing: 1px;")
# 点击区域透明按钮
self.start_install_btn = QPushButton(self.button_container)
self.start_install_btn.setObjectName(u"start_install_btn")
self.start_install_btn.setEnabled(True)
self.start_install_btn.setGeometry(QRect(780, 250, 191, 91))
self.start_install_btn.setAutoFillBackground(False)
start_install_icon = QIcon()
start_install_pixmap = load_base64_image(img_data["start_install_btn"])
if not start_install_pixmap.isNull():
start_install_icon.addPixmap(start_install_pixmap)
self.start_install_btn.setIcon(start_install_icon)
self.start_install_btn.setIcon(start_install_icon)
self.start_install_btn.setIconSize(QSize(189, 110))
self.start_install_btn.setCheckable(False)
self.start_install_btn.setAutoRepeat(False)
self.start_install_btn.setAutoDefault(False)
self.start_install_btn.setGeometry(QRect(10, 10, 191, 91)) # 居中放置在扩大的容器中
self.start_install_btn.setCursor(QCursor(Qt.CursorShape.PointingHandCursor)) # 设置鼠标悬停时为手形光标
self.start_install_btn.setFlat(True)
self.exit_btn = QPushButton(self.centralwidget)
self.start_install_btn.raise_() # 确保按钮在最上层
self.start_install_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_bg = QLabel(self.uninstall_container)
self.uninstall_bg.setObjectName(u"uninstall_bg")
self.uninstall_bg.setGeometry(QRect(10, 10, 191, 91)) # 居中放置在扩大的容器中
self.uninstall_bg.setPixmap(button_pixmap)
self.uninstall_bg.setScaledContents(True)
self.uninstall_text = QLabel(self.uninstall_container)
self.uninstall_text.setObjectName(u"uninstall_text")
self.uninstall_text.setGeometry(QRect(10, 7, 191, 91)) # 居中放置在扩大的容器中
self.uninstall_text.setText("卸载补丁")
self.uninstall_text.setFont(self.custom_font)
self.uninstall_text.setAlignment(Qt.AlignmentFlag.AlignCenter)
self.uninstall_text.setStyleSheet("letter-spacing: 1px;")
# 点击区域透明按钮
self.uninstall_btn = QPushButton(self.uninstall_container)
self.uninstall_btn.setObjectName(u"uninstall_btn")
self.uninstall_btn.setGeometry(QRect(10, 10, 191, 91)) # 居中放置在扩大的容器中
self.uninstall_btn.setCursor(QCursor(Qt.CursorShape.PointingHandCursor)) # 设置鼠标悬停时为手形光标
self.uninstall_btn.setFlat(True)
self.uninstall_btn.raise_() # 确保按钮在最上层
self.uninstall_btn.setStyleSheet("""
QPushButton {
background-color: transparent;
border: none;
}
""")
# 退出按钮 - 基于背景图片和标签组合,调整位置
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_bg = QLabel(self.exit_container)
self.exit_bg.setObjectName(u"exit_bg")
self.exit_bg.setGeometry(QRect(10, 10, 191, 91)) # 居中放置在扩大的容器中
self.exit_bg.setPixmap(button_pixmap)
self.exit_bg.setScaledContents(True)
self.exit_text = QLabel(self.exit_container)
self.exit_text.setObjectName(u"exit_text")
self.exit_text.setGeometry(QRect(10, 7, 191, 91)) # 居中放置在扩大的容器中
self.exit_text.setText("退出程序")
self.exit_text.setFont(self.custom_font)
self.exit_text.setAlignment(Qt.AlignmentFlag.AlignCenter)
self.exit_text.setStyleSheet("letter-spacing: 1px;")
# 点击区域透明按钮
self.exit_btn = QPushButton(self.exit_container)
self.exit_btn.setObjectName(u"exit_btn")
self.exit_btn.setEnabled(True)
self.exit_btn.setGeometry(QRect(780, 340, 191, 91))
self.exit_btn.setAutoFillBackground(False)
exit_icon = QIcon()
exit_pixmap = load_base64_image(img_data["exit_btn"])
if not exit_pixmap.isNull():
exit_icon.addPixmap(exit_pixmap)
self.exit_btn.setIcon(exit_icon)
self.exit_btn.setIcon(exit_icon)
self.exit_btn.setIconSize(QSize(189, 110))
self.exit_btn.setCheckable(False)
self.exit_btn.setGeometry(QRect(10, 10, 191, 91)) # 居中放置在扩大的容器中
self.exit_btn.setCursor(QCursor(Qt.CursorShape.PointingHandCursor)) # 设置鼠标悬停时为手形光标
self.exit_btn.setFlat(True)
self.menubg = QLabel(self.centralwidget)
self.menubg.setObjectName(u"menubg")
self.menubg.setGeometry(QRect(710, 0, 321, 561))
self.menubg.setPixmap(load_base64_image(img_data["menubg"]))
self.menubg.setScaledContents(True)
self.exit_btn.raise_() # 确保按钮在最上层
self.exit_btn.setStyleSheet("""
QPushButton {
background-color: transparent;
border: none;
}
""")
MainWindows.setCentralWidget(self.centralwidget)
# 调整层级顺序
self.loadbg.raise_()
self.vol1bg.raise_()
self.vol2bg.raise_()
@@ -114,28 +467,22 @@ class Ui_MainWindows(object):
self.vol4bg.raise_()
self.afterbg.raise_()
self.Mainbg.raise_()
self.menubg.raise_()
self.start_install_btn.raise_()
self.exit_btn.raise_()
self.menubar = QMenuBar(MainWindows)
self.menubar.setObjectName(u"menubar")
self.menubar.setGeometry(QRect(0, 0, 1024, 21))
self.menu = QMenu(self.menubar)
self.menu.setObjectName(u"menu")
self.menu_2 = QMenu(self.menubar)
self.menu_2.setObjectName(u"menu_2")
MainWindows.setMenuBar(self.menubar)
self.menubar.addAction(self.menu.menuAction())
self.menubar.addAction(self.menu_2.menuAction())
self.menu.addSeparator()
self.button_container.raise_()
self.uninstall_container.raise_() # 添加新按钮到层级顺序
self.exit_container.raise_()
self.menu_area.raise_() # 确保菜单区域在背景之上
# self.menubar.raise_() # 不再需要菜单栏
self.settings_btn.raise_() # 确保设置按钮在上层
self.help_btn.raise_() # 确保帮助按钮在上层
self.title_bar.raise_() # 确保标题栏在最上层
self.retranslateUi(MainWindows)
QMetaObject.connectSlotsByName(MainWindows)
# setupUi
def retranslateUi(self, MainWindows):
MainWindows.setWindowTitle(QCoreApplication.translate("MainWindows", u" UI Test", None))
MainWindows.setWindowTitle(QCoreApplication.translate("MainWindows", f"{APP_NAME} v{APP_VERSION}", None))
self.loadbg.setText("")
self.vol1bg.setText("")
self.vol2bg.setText("")
@@ -146,10 +493,18 @@ class Ui_MainWindows(object):
#if QT_CONFIG(accessibility)
self.start_install_btn.setAccessibleDescription("")
#endif // QT_CONFIG(accessibility)
self.start_install_btn.setText("")
self.exit_btn.setText("")
self.menubg.setText("")
self.menu.setTitle(QCoreApplication.translate("MainWindows", u"\u8bbe\u7f6e", None))
self.menu_2.setTitle(QCoreApplication.translate("MainWindows", u"\u5e2e\u52a9", None))
self.menu.setTitle(QCoreApplication.translate("MainWindows", u"设置", None))
self.menu_2.setTitle(QCoreApplication.translate("MainWindows", u"帮助", None))
# retranslateUi
def show_menu(self, menu, button):
"""显示菜单
Args:
menu: 要显示的菜单
button: 触发菜单的按钮
"""
# 计算菜单显示位置
pos = button.mapToGlobal(button.rect().bottomLeft())
menu.exec(pos)

View File

@@ -1,333 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>MainWindows</class>
<widget class="QMainWindow" name="MainWindows">
<property name="enabled">
<bool>true</bool>
</property>
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>1024</width>
<height>576</height>
</rect>
</property>
<property name="minimumSize">
<size>
<width>1024</width>
<height>576</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>1024</width>
<height>576</height>
</size>
</property>
<property name="mouseTracking">
<bool>false</bool>
</property>
<property name="tabletTracking">
<bool>false</bool>
</property>
<property name="acceptDrops">
<bool>true</bool>
</property>
<property name="windowTitle">
<string> UI Test</string>
</property>
<property name="autoFillBackground">
<bool>true</bool>
</property>
<property name="toolButtonStyle">
<enum>Qt::ToolButtonStyle::ToolButtonIconOnly</enum>
</property>
<property name="animated">
<bool>true</bool>
</property>
<property name="documentMode">
<bool>false</bool>
</property>
<property name="dockNestingEnabled">
<bool>false</bool>
</property>
<widget class="QWidget" name="centralwidget">
<property name="autoFillBackground">
<bool>true</bool>
</property>
<widget class="QLabel" name="loadbg">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>1031</width>
<height>561</height>
</rect>
</property>
<property name="text">
<string/>
</property>
<property name="pixmap">
<pixmap>IMG/BG/bg2.jpg</pixmap>
</property>
<property name="scaledContents">
<bool>true</bool>
</property>
</widget>
<widget class="QLabel" name="vol1bg">
<property name="geometry">
<rect>
<x>0</x>
<y>120</y>
<width>93</width>
<height>64</height>
</rect>
</property>
<property name="text">
<string/>
</property>
<property name="pixmap">
<pixmap>IMG/LOGO/vo01_logo.png</pixmap>
</property>
<property name="scaledContents">
<bool>true</bool>
</property>
</widget>
<widget class="QLabel" name="vol2bg">
<property name="geometry">
<rect>
<x>0</x>
<y>180</y>
<width>93</width>
<height>64</height>
</rect>
</property>
<property name="text">
<string/>
</property>
<property name="pixmap">
<pixmap>IMG/LOGO/vo02_logo.png</pixmap>
</property>
<property name="scaledContents">
<bool>true</bool>
</property>
</widget>
<widget class="QLabel" name="vol3bg">
<property name="geometry">
<rect>
<x>0</x>
<y>240</y>
<width>93</width>
<height>64</height>
</rect>
</property>
<property name="text">
<string/>
</property>
<property name="pixmap">
<pixmap>IMG/LOGO/vo03_logo.png</pixmap>
</property>
<property name="scaledContents">
<bool>true</bool>
</property>
</widget>
<widget class="QLabel" name="vol4bg">
<property name="geometry">
<rect>
<x>0</x>
<y>300</y>
<width>93</width>
<height>64</height>
</rect>
</property>
<property name="text">
<string/>
</property>
<property name="pixmap">
<pixmap>IMG/LOGO/vo04_logo.png</pixmap>
</property>
<property name="scaledContents">
<bool>true</bool>
</property>
</widget>
<widget class="QLabel" name="afterbg">
<property name="geometry">
<rect>
<x>0</x>
<y>360</y>
<width>93</width>
<height>64</height>
</rect>
</property>
<property name="text">
<string/>
</property>
<property name="pixmap">
<pixmap>IMG/LOGO/voaf_logo.png</pixmap>
</property>
<property name="scaledContents">
<bool>true</bool>
</property>
</widget>
<widget class="QLabel" name="Mainbg">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>1031</width>
<height>561</height>
</rect>
</property>
<property name="text">
<string/>
</property>
<property name="pixmap">
<pixmap>IMG/BG/bg3.jpg</pixmap>
</property>
<property name="scaledContents">
<bool>true</bool>
</property>
</widget>
<widget class="QPushButton" name="start_install_btn">
<property name="enabled">
<bool>true</bool>
</property>
<property name="geometry">
<rect>
<x>780</x>
<y>250</y>
<width>191</width>
<height>91</height>
</rect>
</property>
<property name="accessibleDescription">
<string/>
</property>
<property name="autoFillBackground">
<bool>false</bool>
</property>
<property name="text">
<string/>
</property>
<property name="icon">
<iconset>
<normaloff>IMG/BTN/start_install.bmp</normaloff>IMG/BTN/start_install.bmp</iconset>
</property>
<property name="iconSize">
<size>
<width>189</width>
<height>110</height>
</size>
</property>
<property name="checkable">
<bool>false</bool>
</property>
<property name="autoRepeat">
<bool>false</bool>
</property>
<property name="autoDefault">
<bool>false</bool>
</property>
<property name="flat">
<bool>true</bool>
</property>
</widget>
<widget class="QPushButton" name="exit_btn">
<property name="enabled">
<bool>true</bool>
</property>
<property name="geometry">
<rect>
<x>780</x>
<y>340</y>
<width>191</width>
<height>91</height>
</rect>
</property>
<property name="autoFillBackground">
<bool>false</bool>
</property>
<property name="text">
<string/>
</property>
<property name="icon">
<iconset>
<normaloff>IMG/BTN/exit.bmp</normaloff>IMG/BTN/exit.bmp</iconset>
</property>
<property name="iconSize">
<size>
<width>189</width>
<height>110</height>
</size>
</property>
<property name="checkable">
<bool>false</bool>
</property>
<property name="flat">
<bool>true</bool>
</property>
</widget>
<widget class="QLabel" name="menubg">
<property name="geometry">
<rect>
<x>710</x>
<y>0</y>
<width>321</width>
<height>561</height>
</rect>
</property>
<property name="text">
<string/>
</property>
<property name="pixmap">
<pixmap>IMG/BG/menubg.jpg</pixmap>
</property>
<property name="scaledContents">
<bool>true</bool>
</property>
</widget>
<zorder>loadbg</zorder>
<zorder>vol1bg</zorder>
<zorder>vol2bg</zorder>
<zorder>vol3bg</zorder>
<zorder>vol4bg</zorder>
<zorder>afterbg</zorder>
<zorder>Mainbg</zorder>
<zorder>menubg</zorder>
<zorder>start_install_btn</zorder>
<zorder>exit_btn</zorder>
</widget>
<widget class="QMenuBar" name="menubar">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>1024</width>
<height>21</height>
</rect>
</property>
<widget class="QMenu" name="menu">
<property name="title">
<string>设置</string>
</property>
<addaction name="separator"/>
<addaction name="action_2"/>
</widget>
<widget class="QMenu" name="menu_2">
<property name="title">
<string>关于</string>
</property>
</widget>
<addaction name="menu"/>
<addaction name="menu_2"/>
</widget>
<action name="action_2">
<property name="text">
<string>update - sd</string>
</property>
</action>
</widget>
<resources/>
<connections/>
</ui>

View File

@@ -1,19 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>Form</class>
<widget class="QWidget" name="Form">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>480</width>
<height>270</height>
</rect>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
</widget>
<resources/>
<connections/>
</ui>

View File

@@ -1,12 +1,14 @@
from .logger import Logger
from .helpers import (
load_base64_image, HashManager, AdminPrivileges, msgbox_frame,
load_config, save_config, HostsManager, censor_url, resource_path
load_config, save_config, HostsManager, censor_url, resource_path,
load_image_from_file
)
__all__ = [
'Logger',
'load_base64_image',
'load_image_from_file',
'HashManager',
'AdminPrivileges',
'msgbox_frame',

View File

@@ -9,7 +9,6 @@ import psutil
from PySide6 import QtCore, QtWidgets
import re
from PySide6.QtGui import QIcon, QPixmap
from data.pic_data import img_data
from data.config import APP_NAME, CONFIG_FILE
def resource_path(relative_path):
@@ -28,7 +27,7 @@ def resource_path(relative_path):
if relative_path in ("aria2c.exe", "cfst.exe"):
return os.path.join(base_path, 'bin', relative_path)
elif relative_path in ("ip.txt", "ipv6.txt"):
return os.path.join(base_path, 'resources', 'data', relative_path)
return os.path.join(base_path, 'data', relative_path)
return os.path.join(base_path, relative_path)
@@ -37,14 +36,28 @@ def load_base64_image(base64_str):
pixmap.loadFromData(base64.b64decode(base64_str))
return pixmap
def load_image_from_file(file_path):
"""加载图像文件到QPixmap
Args:
file_path: 图像文件路径
Returns:
QPixmap: 加载的图像
"""
if os.path.exists(file_path):
return QPixmap(file_path)
return QPixmap()
def msgbox_frame(title, text, buttons=QtWidgets.QMessageBox.StandardButton.NoButton):
msg_box = QtWidgets.QMessageBox()
msg_box.setWindowTitle(title)
msg_box.setWindowModality(QtCore.Qt.WindowModality.WindowModal)
icon_data = img_data.get("icon")
if icon_data:
pixmap = load_base64_image(icon_data)
# 直接加载图标文件
icon_path = resource_path(os.path.join("IMG", "ICO", "icon.png"))
if os.path.exists(icon_path):
pixmap = QPixmap(icon_path)
if not pixmap.isNull():
msg_box.setWindowIcon(QIcon(pixmap))
msg_box.setIconPixmap(pixmap.scaled(64, 64, QtCore.Qt.AspectRatioMode.KeepAspectRatio, QtCore.Qt.TransformationMode.SmoothTransformation))
@@ -99,8 +112,25 @@ class HashManager:
print(f"Error calculating hash for {file_path}: {e}")
return results
def hash_pop_window(self):
msg_box = msgbox_frame(f"通知 - {APP_NAME}", "\n正在检验文件状态...\n")
def hash_pop_window(self, check_type="default"):
"""显示文件检验窗口
Args:
check_type: 检查类型,可以是 'pre'(预检查), 'after'(后检查), 'extraction'(解压后检查)
Returns:
QMessageBox: 消息框实例
"""
message = "\n正在检验文件状态...\n"
if check_type == "pre":
message = "\n正在检查游戏文件以确定需要安装的补丁...\n"
elif check_type == "after":
message = "\n正在检验本地文件完整性...\n"
elif check_type == "extraction":
message = "\n正在验证下载的解压文件完整性...\n"
msg_box = msgbox_frame(f"通知 - {APP_NAME}", message)
msg_box.open()
QtWidgets.QApplication.processEvents()
return msg_box
@@ -158,6 +188,7 @@ class AdminPrivileges:
"nekopara_vol1.exe",
"nekopara_vol2.exe",
"NEKOPARAvol3.exe",
"NEKOPARAvol3.exe.nocrack",
"nekopara_vol4.exe",
"nekopara_after.exe",
]
@@ -200,33 +231,40 @@ class AdminPrivileges:
def check_and_terminate_processes(self):
for proc in psutil.process_iter(["pid", "name"]):
if proc.info["name"] in self.required_exes:
msg_box = msgbox_frame(
f"进程检测 - {APP_NAME}",
f"\n检测到游戏正在运行: {proc.info['name']} \n\n是否终止?\n",
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:
proc_name = proc.info["name"].lower() if proc.info["name"] else ""
# 检查进程名是否匹配任何需要终止的游戏进程
for exe in self.required_exes:
if exe.lower() == proc_name:
# 获取不带.nocrack的游戏名称用于显示
display_name = exe.replace(".nocrack", "")
msg_box = msgbox_frame(
f"进程检测 - {APP_NAME}",
f"\n检测到游戏正在运行: {display_name} \n\n是否终止?\n",
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(
f"错误 - {APP_NAME}",
f"\n无法关闭游戏: {proc.info['name']} \n\n请手动关闭后重启应用\n",
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(
f"进程检测 - {APP_NAME}",
f"\n未关闭的游戏: {proc.info['name']} \n\n请手动关闭后重启应用\n",
QtWidgets.QMessageBox.StandardButton.Ok,
)
msg_box.exec()
sys.exit(1)
class HostsManager:
def __init__(self):

View File

@@ -1,6 +1,9 @@
import json
import requests
import webbrowser
from PySide6.QtCore import QThread, Signal
from PySide6.QtWidgets import QMessageBox
import sys
class ConfigFetchThread(QThread):
finished = Signal(object, str) # data, error_message
@@ -30,8 +33,12 @@ class ConfigFetchThread(QThread):
# 首先总是尝试解析JSON
config_data = response.json()
# 检查是否是要求更新的错误信息
if config_data.get("message") == "请使用最新版本的FRAISEMOE Addons Installer NEXT进行下载安装":
# 检查是否是要求更新的错误信息 - 使用Unicode编码的更新提示文本
update_required_msg = "\u8bf7\u4f7f\u7528\u6700\u65b0\u7248\u672c\u7684FraiseMoe2-Next\u8fdb\u884c\u4e0b\u8f7d"
if isinstance(config_data, str) and config_data == update_required_msg:
self.finished.emit(None, "update_required")
return
elif isinstance(config_data, dict) and config_data.get("message") == update_required_msg:
self.finished.emit(None, "update_required")
return
@@ -44,9 +51,15 @@ class ConfigFetchThread(QThread):
self.finished.emit(config_data, "")
except requests.exceptions.RequestException as e:
self.finished.emit(None, f"网络请求失败: {e}")
error_msg = "访问云端配置失败,请检查网络状况或稍后再试。"
if self.debug_mode:
error_msg += f"\n详细错误: {e}"
self.finished.emit(None, error_msg)
except (ValueError, json.JSONDecodeError) as e:
self.finished.emit(None, f"JSON解析失败: {e}")
error_msg = "访问云端配置失败,请检查网络状况或稍后再试。"
if self.debug_mode:
error_msg += f"\nJSON解析失败: {e}"
self.finished.emit(None, error_msg)
finally:
if self.debug_mode:
print("--- Finished fetching cloud config ---")

View File

@@ -38,7 +38,7 @@ class IpOptimizer:
"-url", url, # 指定测速地址
"-f", ip_txt_path, # IP文件
"-dd", # 禁用下载测速,按延迟排序
"-o",
"-o","" # 不写入结果文件
]
creation_flags = subprocess.CREATE_NO_WINDOW if sys.platform == 'win32' else 0
@@ -119,10 +119,8 @@ class IpOptimizer:
if match and not optimal_ip: # 只保存第一个匹配的IP最优IP
optimal_ip = match.group(1)
print(f"找到最优 IP: {optimal_ip}")
# 如果已经看到完成标记,可以退出了
if found_completion:
break
# 找到最优IP后立即退出循环不等待完成标记
break
except Exception as e:
print(f"读取输出时发生错误: {e}")