Update source -> V4.9.0.17411

This commit is contained in:
Yanam1Anna 2025-03-07 17:10:56 +08:00
parent 9f94b3e8a5
commit c3e91bda94
2 changed files with 413 additions and 312 deletions

View File

@ -215,7 +215,7 @@ class Ui_mainwin(object):
def retranslateUi(self, mainwin):
mainwin.setWindowTitle(
QCoreApplication.translate(
"mainwin", "FRAISEMOE Addons Installer V4.8.6.17218", None
"mainwin", "FRAISEMOE Addons Installer V4.9.0.17411", None
)
)
self.mainbg.setText("")

View File

@ -7,9 +7,11 @@ import sys
import base64
import psutil
import ctypes
import base64
import concurrent.futures
from PySide6 import QtWidgets, QtCore
from PySide6.QtCore import Qt, QByteArray, Signal
from PySide6.QtCore import Qt, QByteArray, QThread, Signal
from PySide6.QtWidgets import (
QApplication,
QWidget,
@ -17,285 +19,401 @@ from PySide6.QtWidgets import (
QProgressBar,
QVBoxLayout,
QLabel,
QDialog,
)
from PySide6.QtGui import QIcon, QPixmap
from collections import deque
from pic_data import img_data
from GUI import Ui_mainwin
APP_VERSION = "@FRAISEMOE Addons Installer V4.8.6.17218"
TEMP = os.getenv("TEMP")
CACHE = os.path.join(TEMP, "FRAISEMOE")
PLUGIN = os.path.join(CACHE, "PLUGIN")
CONFIG_URL = "https://archive.ovofish.com/api/widget/nekopara/download_url.json"
UA = "Mozilla/5.0 (Linux debian12 Python-Accept) Gecko/20100101 Firefox/114.0"
SRC_HASHES = {
"NEKOPARA Vol.1": "04b48b231a7f34431431e5027fcc7b27affaa951b8169c541709156acf754f3e",
"NEKOPARA Vol.2": "b9c00a2b113a1e768bf78400e4f9075ceb7b35349cdeca09be62eb014f0d4b42",
"NEKOPARA Vol.3": "2ce7b223c84592e1ebc3b72079dee1e5e8d064ade15723328a64dee58833b9d5",
"NEKOPARA Vol.4": "4a4a9ae5a75a18aacbe3ab0774d7f93f99c046afe3a777ee0363e8932b90f36a",
# 配置信息
app_data = {
"APP_NAME": "@FRAISEMOE Addons Installer",
"TEMP": "TEMP",
"CACHE": "FRAISEMOE",
"PLUGIN": "PLUGIN",
"CONFIG_URL": "aHR0cHM6Ly9hcmNoaXZlLm92b2Zpc2guY29tL2FwaS93aWRnZXQvbmVrb3BhcmEvZG93bmxvYWRfdXJsLmpzb24=",
"UA": "TW96aWxsYS81LjAgKExpbnV4IGRlYmlhbjEyIFB5dGhvbi1BY2NlcHQpIEdlY2tvLzIwMTAwMTAxIEZpcmVmb3gvMTE0LjA=",
"game_info": {
"NEKOPARA Vol.1": {
"exe": "nekopara_vol1.exe",
"hash": "04b48b231a7f34431431e5027fcc7b27affaa951b8169c541709156acf754f3e",
"install_path": "NEKOPARA Vol. 1/adultsonly.xp3",
"plugin_path": "vol.1/adultsonly.xp3",
},
"NEKOPARA Vol.2": {
"exe": "nekopara_vol2.exe",
"hash": "b9c00a2b113a1e768bf78400e4f9075ceb7b35349cdeca09be62eb014f0d4b42",
"install_path": "NEKOPARA Vol. 2/adultsonly.xp3",
"plugin_path": "vol.2/adultsonly.xp3",
},
"NEKOPARA Vol.3": {
"exe": "NEKOPARAvol3.exe",
"hash": "2ce7b223c84592e1ebc3b72079dee1e5e8d064ade15723328a64dee58833b9d5",
"install_path": "NEKOPARA Vol. 3/update00.int",
"plugin_path": "vol.3/update00.int",
},
"NEKOPARA Vol.4": {
"exe": "nekopara_vol4.exe",
"hash": "4a4a9ae5a75a18aacbe3ab0774d7f93f99c046afe3a777ee0363e8932b90f36a",
"install_path": "NEKOPARA Vol. 4/vol4adult.xp3",
"plugin_path": "vol.4/vol4adult.xp3",
},
},
}
def admin_status():
try:
return ctypes.windll.shell32.IsUserAnAdmin()
except:
return False
# Base64解码
def decode_base64(encoded_str):
return base64.b64decode(encoded_str).decode("utf-8")
def run_as_admin():
script = os.path.abspath(sys.argv[0])
params = " ".join([script] + sys.argv[1:])
ctypes.windll.shell32.ShellExecuteW(None, "runas", sys.executable, params, None, 1)
APP_NAME = app_data["APP_NAME"]
TEMP = os.getenv(app_data["TEMP"])
CACHE = os.path.join(TEMP, app_data["CACHE"])
PLUGIN = os.path.join(CACHE, app_data["PLUGIN"])
CONFIG_URL = decode_base64(app_data["CONFIG_URL"])
UA = decode_base64(app_data["UA"])
GAME_INFO = app_data["game_info"]
BLOCK_SIZE = 64 * 1024
HASH_SIZE = 128 * 1024
PLUGIN_HASH = {game: info["hash"] for game, info in GAME_INFO.items()}
PROCESS_INFO = {info["exe"]: game for game, info in GAME_INFO.items()}
class DownloadThread(QtCore.QThread):
progress = Signal(int)
finished = Signal(bool, str)
# 弹窗框架
def msgbox_frame(title, text, buttons=QMessageBox.StandardButton.NoButton):
msg_box = QMessageBox()
msg_box.setWindowTitle(title)
pixmap = QPixmap()
pixmap.loadFromData(QByteArray(base64.b64decode(img_data["icon"])))
icon = QIcon(pixmap)
msg_box.setWindowIcon(icon)
msg_box.setText(text)
msg_box.setStandardButtons(buttons)
return msg_box
# 哈希值计算类
class HashManager:
def __init__(self, HASH_SIZE):
self.HASH_SIZE = HASH_SIZE
# 哈希值计算
def hash_calculate(self, file_path):
sha256_hash = hashlib.sha256()
with open(file_path, "rb") as f:
for byte_block in iter(lambda: f.read(self.HASH_SIZE), b""):
sha256_hash.update(byte_block)
return sha256_hash.hexdigest()
# 使用多线程优化哈希值计算
def calculate_hashes_in_parallel(self, file_paths):
with concurrent.futures.ThreadPoolExecutor() as executor:
future_to_file = {
executor.submit(self.hash_calculate, path): path for path in file_paths
}
results = {}
for future in concurrent.futures.as_completed(future_to_file):
file_path = future_to_file[future]
try:
results[file_path] = future.result()
except Exception as e:
results[file_path] = None
msg_box = msgbox_frame(
f"错误 {APP_NAME}",
f"\n文件哈希值计算失败\n\n【错误信息】:{e}\n",
QMessageBox.StandardButton.Ok,
)
msg_box.exec()
return results
# 哈希值计算时的窗口
def hash_pop_window(self):
msg_box = msgbox_frame(f"通知 {APP_NAME}", "\n正在检验文件状态...\n")
msg_box.show()
QApplication.processEvents()
return msg_box
# 下载前比对已有文件哈希值
def cfg_pre_hash_compare(
self, install_path, game_version, plugin_hash, installed_status
):
if not os.path.exists(install_path):
installed_status[game_version] = False
return
file_hash = self.hash_calculate(install_path)
if file_hash == plugin_hash[game_version]:
installed_status[game_version] = True
else:
reply = msgbox_frame(
f"文件校验 {APP_NAME}",
f"\n检测到 {game_version} 的文件哈希值不匹配,是否重新安装?\n",
QMessageBox.Yes | QMessageBox.No,
).exec()
if reply == QMessageBox.Yes:
installed_status[game_version] = False
else:
installed_status[game_version] = True
# 下载完成后比对哈希值
def cfg_after_hash_compare(self, install_paths, plugin_hash, installed_status):
passed = True
file_paths = [
install_paths[game] for game in plugin_hash if installed_status.get(game)
]
hash_results = self.calculate_hashes_in_parallel(file_paths)
for game, hash_value in plugin_hash.items():
if installed_status.get(game):
file_hash = hash_results.get(install_paths[game])
if file_hash != hash_value:
msg_box = msgbox_frame(
f"文件校验 {APP_NAME}",
f"\n检测到 {game} 的文件哈希值不匹配\n",
QMessageBox.StandardButton.Ok,
)
msg_box.exec()
installed_status[game] = False
passed = False
break
return passed
# 管理员权限检查类
class AdminPrivileges:
# 进程列表
def __init__(self):
self.required_exes = [
"nekopara_vol1.exe",
"nekopara_vol2.exe",
"NEKOPARAvol3.exe",
"nekopara_vol4.exe",
]
# 检查管理员权限
def is_admin(self):
try:
return ctypes.windll.shell32.IsUserAnAdmin()
except:
return False
# 请求管理员权限
def request_admin_privileges(self):
if not self.is_admin():
msg_box = msgbox_frame(
f"权限检测 {APP_NAME}",
"\n需要管理员权限运行此程序\n",
QMessageBox.Yes | QMessageBox.No,
)
reply = msg_box.exec()
if reply == QMessageBox.Yes:
try:
ctypes.windll.shell32.ShellExecuteW(
None, "runas", sys.executable, " ".join(sys.argv), None, 1
)
except Exception as e:
msg_box = msgbox_frame(
f"错误 {APP_NAME}",
f"\n请求管理员权限失败\n\n【错误信息】:{e}\n",
QMessageBox.StandardButton.Ok,
)
msg_box.exec()
sys.exit(1)
else:
msg_box = msgbox_frame(
f"权限检测 {APP_NAME}",
"\n无法获取管理员权限,程序将退出\n",
QMessageBox.StandardButton.Ok,
)
msg_box.exec()
sys.exit(1)
# 检查并终止进程
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",
QMessageBox.Yes | QMessageBox.No,
)
reply = msg_box.exec()
if reply == QMessageBox.Yes:
try:
proc.terminate()
proc.wait(timeout=3)
except psutil.AccessDenied:
msg_box = msgbox_frame(
f"错误 {APP_NAME}",
f"\n无法关闭游戏: {proc.info['name']} \n\n请手动关闭后重启应用\n",
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",
QMessageBox.StandardButton.Ok,
)
msg_box.exec()
sys.exit(1)
# 下载线程类
class DownloadThread(QThread):
progress = Signal(int) # 进度信号
finished = Signal(bool, str) # 完成信号
def __init__(self, url, _7z_path, parent=None):
super().__init__(parent)
self.url = url
self._7z_path = _7z_path
self.url = url # 下载地址
self._7z_path = _7z_path # 7z文件路径
# 下载线程运行
def run(self):
try:
headers = {"User-Agent": UA}
r = requests.get(self.url, headers=headers, stream=True, timeout=10)
r.raise_for_status()
block_size = 64 * 1024
total_size = int(r.headers.get("content-length", 0))
with open(self._7z_path, "wb") as f:
for chunk in r.iter_content(chunk_size=block_size):
for chunk in r.iter_content(chunk_size=BLOCK_SIZE):
f.write(chunk)
self.progress.emit(f.tell() * 100 // total_size)
self.finished.emit(True, "")
self.progress.emit(f.tell() * 100 // total_size) # 发送进度信号
self.finished.emit(True, "") # 发送完成信号
except requests.exceptions.RequestException as e:
self.finished.emit(False, f"\n网络请求错误\n\n【错误信息】: {e}\n")
except Exception as e:
self.finished.emit(False, f"\n未知错误\n\n【错误信息】: {e}\n")
def game_process_status(process_name):
for proc in psutil.process_iter(["pid", "name"]):
try:
if process_name.lower() in proc.info["name"].lower():
return proc.info["pid"]
except (psutil.NoSuchProcess, psutil.AccessDenied, psutil.ZombieProcess):
pass
return None
def kill_process(pid):
try:
process = psutil.Process(pid)
process.terminate()
process.wait(timeout=5)
except (psutil.NoSuchProcess, psutil.AccessDenied, psutil.ZombieProcess):
pass
# 下载进度窗口类
class ProgressWindow(QDialog):
def __init__(self, parent=None):
super(ProgressWindow, self).__init__(parent)
self.setWindowTitle(f"下载进度 {APP_NAME}")
self.resize(400, 100)
self.progress_bar_max = 100
self.setWindowFlags(
self.windowFlags() & ~Qt.WindowCloseButtonHint
) # 禁用关闭按钮
self.setWindowFlags(
self.windowFlags() & ~Qt.WindowSystemMenuHint
) # 禁用系统菜单
layout = QVBoxLayout()
self.progress_bar = QProgressBar()
self.progress_bar.setValue(0)
self.label = QLabel("\n正在下载...\n")
layout.addWidget(self.label)
layout.addWidget(self.progress_bar)
self.setLayout(layout)
# 设置进度条最大值
def setmaxvalue(self, value):
self.progress_bar_max = value
self.progress_bar.setMaximum(value)
# 设置进度条值
def setprogressbarval(self, value):
self.progress_bar.setValue(value)
if value == self.progress_bar_max: # 下载完成后关闭窗口
QtCore.QTimer.singleShot(2000, self.close)
# 主窗口类
class MyWindow(QWidget, Ui_mainwin):
def __init__(self):
super().__init__()
self.setupUi(self)
self.selected_folder = ""
self.installed_status = {
"NEKOPARA Vol.1": False,
"NEKOPARA Vol.2": False,
"NEKOPARA Vol.3": False,
"NEKOPARA Vol.4": False,
}
self.installed_status = {f"NEKOPARA Vol.{i}": False for i in range(1, 5)}
self.download_queue = deque()
self.current_download_thread = None
self.hash_manager = HashManager(BLOCK_SIZE)
game_process_info = {
"nekopara_vol1.exe": "NEKOPARA Vol.1",
"nekopara_vol2.exe": "NEKOPARA Vol.2",
"NEKOPARAvol3.exe": "NEKOPARA Vol.3",
"nekopara_vol4.exe": "NEKOPARA Vol.4",
}
for process_name, game_version in game_process_info.items():
pid = game_process_status(process_name)
if pid:
msg_box = QMessageBox()
msg_box.setWindowTitle(f"进程检测 {APP_VERSION}")
pixmap = QPixmap()
pixmap.loadFromData(QByteArray(base64.b64decode(img_data["icon"])))
icon = QIcon(pixmap)
msg_box.setWindowIcon(icon)
msg_box.setText(f"\n检测到 {game_version} 正在运行,是否关闭?\n")
yes_button = msg_box.addButton(
"确定", QMessageBox.ButtonRole.AcceptRole
)
no_button = msg_box.addButton("取消", QMessageBox.ButtonRole.RejectRole)
msg_box.setDefaultButton(no_button)
msg_box.exec()
if msg_box.clickedButton() == yes_button:
kill_process(pid)
else:
QMessageBox.warning(
self,
f"警告 {APP_VERSION}",
f"\n请关闭 {game_version} 后再运行本程序。\n",
)
self.close()
sys.exit()
# 检查管理员权限和进程
admin_privileges = AdminPrivileges()
admin_privileges.request_admin_privileges()
admin_privileges.check_and_terminate_processes()
# 创建缓存目录
if not os.path.exists(PLUGIN):
os.makedirs(PLUGIN)
if not os.path.exists(PLUGIN):
try:
os.makedirs(PLUGIN)
except OSError as e:
QMessageBox.critical(
self,
f"错误 {APP_VERSION}",
"\n无法创建缓存位置\n\n使用管理员身份运行或检查文件读写权限\n",
f"错误 {APP_NAME}",
f"\n无法创建缓存位置\n\n使用管理员身份运行或检查文件读写权限\n\n【错误信息】:{e}\n",
)
self.close()
sys.exit()
sys.exit(1)
# 连接信号 & UI按钮
self.startbtn.clicked.connect(self.file_dialog)
self.exitbtn.clicked.connect(self.shutdown_app)
# 获取游戏安装路径
def get_install_paths(self):
return {
"NEKOPARA Vol.1": os.path.join(
self.selected_folder, "NEKOPARA Vol. 1", "adultsonly.xp3"
),
"NEKOPARA Vol.2": os.path.join(
self.selected_folder, "NEKOPARA Vol. 2", "adultsonly.xp3"
),
"NEKOPARA Vol.3": os.path.join(
self.selected_folder, "NEKOPARA Vol. 3", "update00.int"
),
"NEKOPARA Vol.4": os.path.join(
self.selected_folder, "NEKOPARA Vol. 4", "vol4adult.xp3"
),
game: os.path.join(self.selected_folder, info["install_path"])
for game, info in GAME_INFO.items()
}
# 获取游戏目录
def file_dialog(self):
self.selected_folder = QtWidgets.QFileDialog.getExistingDirectory(
self, f"选择游戏所在【上级目录】 {APP_VERSION}"
self, f"选择游戏所在【上级目录】 {APP_NAME}"
)
if not self.selected_folder:
QMessageBox.warning(
self, f"通知 {APP_VERSION}", "\n未选择任何目录,请重新选择\n"
self, f"通知 {APP_NAME}", "\n未选择任何目录,请重新选择\n"
)
return
self.download_action()
def hash_calculate(self, file_path):
sha256_hash = hashlib.sha256()
with open(file_path, "rb") as f:
for byte_block in iter(lambda: f.read(4096), b""):
sha256_hash.update(byte_block)
return sha256_hash.hexdigest()
def hash_pop_window(self):
msg_box = QMessageBox()
msg_box.setWindowTitle(f"通知 {APP_VERSION}")
pixmap = QPixmap()
pixmap.loadFromData(QByteArray(base64.b64decode(img_data["icon"])))
icon = QIcon(pixmap)
msg_box.setWindowIcon(icon)
msg_box.setText("\n正在检验文件状态...\n")
msg_box.setStandardButtons(QMessageBox.StandardButton.NoButton)
msg_box.show()
QApplication.processEvents()
return msg_box
def pre_hash_compare(self, install_path, game_version, SRC_HASHES):
if not os.path.exists(install_path):
self.installed_status[game_version] = False
return
msg_box = self.hash_pop_window()
file_hash = self.hash_calculate(install_path)
msg_box.close()
if file_hash != SRC_HASHES[game_version]:
msg_box = QMessageBox(self)
msg_box.setWindowTitle(f"文件校验 {APP_VERSION}")
msg_box.setText(
f"\n【 当前版本已安装旧版本补丁 -> {game_version}\n\n是否重新安装?\n----->取消安装前应确认补丁是否可用<-----\n"
)
yes_button = msg_box.addButton("确定", QMessageBox.ButtonRole.AcceptRole)
no_button = msg_box.addButton("取消", QMessageBox.ButtonRole.RejectRole)
msg_box.setDefaultButton(no_button)
msg_box.exec()
if msg_box.clickedButton() == yes_button:
self.installed_status[game_version] = False
return
else:
self.installed_status[game_version] = True
return
else:
self.installed_status[game_version] = True
return
def late_hash_compare(self, SRC_HASHES):
install_paths = self.get_install_paths()
passed = True
for game, hash_value in SRC_HASHES.items():
if self.installed_status.get(game):
msg_box = self.hash_pop_window()
file_hash = self.hash_calculate(install_paths[game])
msg_box.close()
if file_hash != hash_value:
passed = False
break
return passed
def download_config(self) -> dict:
# 获取下载配置文件
def get_download_url(self) -> dict:
try:
headers = {"User-Agent": UA}
response = requests.get(CONFIG_URL, headers=headers, timeout=10)
response.raise_for_status()
response = response.json()
return {f"vol{i+1}": response[f"vol.{i+1}.data"]["url"] for i in range(4)}
except requests.exceptions.RequestException as e:
config_data = response.json()
if not all(f"vol.{i+1}.data" in config_data for i in range(4)):
raise ValueError("配置文件数据异常")
return {
f"vol{i+1}": config_data[f"vol.{i+1}.data"]["url"] for i in range(4)
}
except (requests.exceptions.RequestException, ValueError) as e:
QMessageBox.critical(
self,
f"错误 {APP_VERSION}",
f"\n下载配置获取失败\n\n网络状态异常或服务器故障\n\n【错误信息】:{e}\n",
f"错误 {APP_NAME}",
f"\n下载配置获取失败\n\n【错误信息】:{e}\n",
)
except Exception as e:
QMessageBox.critical(
self,
f"错误 {APP_VERSION}",
f"\n下载配置获取失败\n\n未知错误\n\n【错误信息】:{e}\n",
)
return {}
return {}
# 下载参数设置
def download_setting(self, url, game_folder, game_version, _7z_path, plugin_path):
game_exe = {
"NEKOPARA Vol.1": os.path.join(
self.selected_folder, "NEKOPARA Vol. 1", "nekopara_vol1.exe"
),
"NEKOPARA Vol.2": os.path.join(
self.selected_folder, "NEKOPARA Vol. 2", "nekopara_vol2.exe"
),
"NEKOPARA Vol.3": os.path.join(
self.selected_folder, "NEKOPARA Vol. 3", "NEKOPARAvol3.exe"
),
"NEKOPARA Vol.4": os.path.join(
self.selected_folder, "NEKOPARA Vol. 4", "nekopara_vol4.exe"
),
game: os.path.join(
self.selected_folder, info["install_path"].split("/")[0], info["exe"]
)
for game, info in GAME_INFO.items()
}
# 判断游戏是否存在,不存在则跳过
if (
game_version not in game_exe
or not os.path.exists(game_exe[game_version])
or self.installed_status[game_version]
):
self.next_download_task()
self.installed_status[game_version] = False
self.show_result()
return
# 下载时显示进度窗口
progress_window = ProgressWindow(self)
progress_window.show()
# 启用下载线程
self.current_download_thread = DownloadThread(url, _7z_path, self)
self.current_download_thread.progress.connect(progress_window.setprogressbarval)
self.current_download_thread.finished.connect(
@ -311,6 +429,7 @@ class MyWindow(QWidget, Ui_mainwin):
)
self.current_download_thread.start()
# 安装设置
def install_setting(
self,
success,
@ -324,32 +443,19 @@ class MyWindow(QWidget, Ui_mainwin):
progress_window.close()
if success:
try:
msg_box = self.hash_pop_window()
msg_box = self.hash_manager.hash_pop_window()
QApplication.processEvents()
with py7zr.SevenZipFile(_7z_path, mode="r") as archive:
archive.extractall(path=PLUGIN)
shutil.copy(plugin_path, game_folder)
self.installed_status[game_version] = True
QMessageBox.information(
self, f"通知 {APP_VERSION}", f"\n{game_version} 补丁已安装\n"
self, f"通知 {APP_NAME}", f"\n{game_version} 补丁已安装\n"
)
except py7zr.Bad7zFile as e:
except (py7zr.Bad7zFile, FileNotFoundError, Exception) as e:
QMessageBox.critical(
self,
f"错误 {APP_VERSION}",
f"\n文件损坏\n\n【错误信息】:{e}\n",
)
except FileNotFoundError as e:
QMessageBox.critical(
self,
f"错误 {APP_VERSION}",
f"\n文件不存在\n\n【错误信息】:{e}\n",
)
except Exception as e:
QMessageBox.critical(
self,
f"错误 {APP_VERSION}",
f"错误 {APP_NAME}",
f"\n文件操作失败,请重试\n\n【错误信息】:{e}\n",
)
finally:
@ -357,56 +463,68 @@ class MyWindow(QWidget, Ui_mainwin):
else:
QMessageBox.critical(
self,
f"错误 {APP_VERSION}",
f"错误 {APP_NAME}",
f"\n文件获取失败\n网络状态异常或服务器故障\n\n【错误信息】:{error}\n",
)
self.next_download_task()
# 下载前比对已有文件哈希值
def pre_hash_compare(self, install_path, game_version, plugin_hash):
msg_box = self.hash_manager.hash_pop_window()
self.hash_manager.cfg_pre_hash_compare(
install_path, game_version, plugin_hash, self.installed_status
)
msg_box.close()
# 开始下载文件
def download_action(self):
install_paths = self.get_install_paths()
for game_version, install_path in install_paths.items():
self.pre_hash_compare(install_path, game_version, SRC_HASHES)
if self.late_hash_compare(SRC_HASHES):
config = self.download_config()
if not config:
QMessageBox.critical(
self, f"错误 {APP_VERSION}", "\n网络状态异常或服务器故障,请重试\n"
)
return
for i in range(1, 5):
game_version = f"NEKOPARA Vol.{i}"
if self.installed_status[game_version] == False:
url = config[f"vol{i}"]
game_folder = os.path.join(
self.selected_folder, f"NEKOPARA Vol. {i}"
)
_7z_path = os.path.join(PLUGIN, f"vol.{i}.7z")
plugin_path = os.path.join(
PLUGIN,
f"vol.{i}",
[
"adultsonly.xp3",
"adultsonly.xp3",
"update00.int",
"vol4adult.xp3",
][i - 1],
)
self.download_queue.append(
(url, game_folder, game_version, _7z_path, plugin_path)
)
self.next_download_task()
self.pre_hash_compare(install_path, game_version, PLUGIN_HASH)
def next_download_task(self):
if not self.download_queue:
self.show_result()
config = self.get_download_url()
if not config:
QMessageBox.critical(
self, f"错误 {APP_NAME}", "\n网络状态异常或服务器故障,请重试\n"
)
return
for i in range(1, 5):
game_version = f"NEKOPARA Vol.{i}"
if not self.installed_status[game_version]:
url = config[f"vol{i}"]
game_folder = os.path.join(self.selected_folder, f"NEKOPARA Vol. {i}")
_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.next_download_task()
# 开始下载队列中的下一个任务
def next_download_task(self):
if not self.download_queue:
self.after_hash_compare(PLUGIN_HASH)
return
url, game_folder, game_version, _7z_path, plugin_path = (
self.download_queue.popleft()
)
self.download_setting(url, game_folder, game_version, _7z_path, plugin_path)
# 下载完成后比对哈希值
def after_hash_compare(self, plugin_hash):
msg_box = self.hash_manager.hash_pop_window()
result = self.hash_manager.cfg_after_hash_compare(
self.get_install_paths(), plugin_hash, self.installed_status
)
msg_box.close()
self.show_result()
return result
# 显示最终安装结果
def show_result(self):
installed_version = "\n".join(
[i for i in self.installed_status if self.installed_status[i]]
@ -414,83 +532,66 @@ class MyWindow(QWidget, Ui_mainwin):
failed_ver = "\n".join(
[i for i in self.installed_status if not self.installed_status[i]]
)
QMessageBox.information(
self,
f"完成 {APP_VERSION}",
f"\n安装结果:\n"
f"安装成功数:{len(installed_version.splitlines())} 安装失败数:{len(failed_ver.splitlines())}\n\n"
f"安装成功的版本:\n"
f"{installed_version}\n"
f"尚未持有的版本:\n"
f"{failed_ver}\n",
f"完成 {APP_NAME}",
f"\n安装结果:\n安装成功数:{len(installed_version.splitlines())} 安装失败数:{len(failed_ver.splitlines())}\n"
f"安装成功的版本:\n{installed_version}\n尚未持有或未使用本工具安装补丁的版本:\n{failed_ver}\n",
)
def shutdown_app(self):
msg_box = QMessageBox(self)
msg_box.setWindowTitle("退出程序")
msg_box.setText("\n是否确定退出?\n")
yes_button = msg_box.addButton("确定", QMessageBox.ButtonRole.AcceptRole)
no_button = msg_box.addButton("取消", QMessageBox.ButtonRole.RejectRole)
msg_box.setDefaultButton(no_button)
msg_box.exec()
# 关闭程序-窗口
def closeEvent(self, event):
self.shutdown_app(event)
if msg_box.clickedButton() == yes_button:
# 关闭程序-按钮
def shutdown_app(self, event=None):
reply = QMessageBox.question(
self,
"退出程序",
"\n是否确定退出?\n",
QMessageBox.Yes | QMessageBox.No,
QMessageBox.No,
)
if reply == QMessageBox.Yes:
if (
self.current_download_thread
and self.current_download_thread.isRunning()
):
QMessageBox.critical(
self,
f"错误 {APP_VERSION}",
"\n当前有下载任务正在进行,完成后再试。\n",
f"错误 {APP_NAME}",
"\n当前有下载任务正在进行,完成后再试\n",
)
if event:
event.ignore()
return
if os.path.exists(PLUGIN):
try:
shutil.rmtree(PLUGIN)
except Exception as e:
QMessageBox.critical(
self,
f"错误 {APP_VERSION}",
f"\n清理缓存失败\n\n【错误信息】:{e}\n",
)
sys.exit()
class ProgressWindow(QtWidgets.QDialog):
def __init__(self, parent=None):
super(ProgressWindow, self).__init__(parent)
self.setWindowTitle(f"下载进度 {APP_VERSION}")
self.resize(400, 100)
self.progress_bar_max = 100
self.setWindowFlags(self.windowFlags() & ~Qt.WindowCloseButtonHint)
self.setWindowFlags(self.windowFlags() & ~Qt.WindowSystemMenuHint)
layout = QVBoxLayout()
self.progress_bar = QProgressBar()
self.progress_bar.setValue(0)
self.label = QLabel("\n正在下载...\n")
layout.addWidget(self.label)
layout.addWidget(self.progress_bar)
self.setLayout(layout)
def setmaxvalue(self, value):
self.progress_bar_max = value
self.progress_bar.setMaximum(value)
def setprogressbarval(self, value):
self.progress_bar.setValue(value)
if value == self.progress_bar_max:
QtCore.QTimer.singleShot(2000, self.close)
for attempt in range(3):
try:
shutil.rmtree(PLUGIN)
break
except Exception as e:
if attempt == 2:
QMessageBox.critical(
self,
f"错误 {APP_NAME}",
f"\n清理缓存失败\n\n【错误信息】:{e}\n",
)
if event:
event.accept()
sys.exit(1)
if event:
event.accept()
else:
sys.exit(0)
else:
if event:
event.ignore()
if __name__ == "__main__":
if not admin_status():
run_as_admin()
sys.exit()
app = QApplication([])
window = MyWindow()
window.show()