254 lines
9.2 KiB
Python
254 lines
9.2 KiB
Python
import os
|
|
import shutil
|
|
import requests
|
|
import py7zr
|
|
import hashlib
|
|
import psutil
|
|
import ctypes
|
|
import base64
|
|
from PySide6.QtWidgets import (QMessageBox, QFileDialog, QProgressDialog)
|
|
from PySide6.QtCore import QThread, Signal
|
|
|
|
# 配置信息(完全保持不变)
|
|
app_data = {
|
|
"APP_VERSION": "4.10.0.17496",
|
|
"APP_NAME": "@FRAISEMOE Addons Installer",
|
|
"TEMP": "TEMP",
|
|
"CACHE": "FRAISEMOE",
|
|
"PLUGIN": "PLUGIN",
|
|
"CONFIG_URL": "aHR0cHM6Ly9hcmNoaXZlLm92b2Zpc2guY29tL2FwaS93aWRnZXQvbmVrb3BhcmEvZG93bmxvYWRfdXJsLmpzb24=",
|
|
"UA": "TW96aWxsYS81LjAgKExpbnV4IGRlYmlhbjEyIEZyYWlzZU1vZS1BY2NlcHQpIEdlY2tvLzIwMTAwMTAxIEZpcmVmb3gvMTE0LjA=",
|
|
"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 decode_base64(encoded_str):
|
|
"""Base64解码函数"""
|
|
return base64.b64decode(encoded_str).decode("utf-8")
|
|
|
|
class DownloadThread(QThread):
|
|
progress_updated = Signal(int)
|
|
download_finished = Signal(bool, str)
|
|
|
|
def __init__(self, url, save_path, headers):
|
|
super().__init__()
|
|
self.url = url
|
|
self.save_path = save_path
|
|
self.headers = headers
|
|
|
|
def run(self):
|
|
try:
|
|
response = requests.get(self.url, headers=self.headers, stream=True)
|
|
response.raise_for_status()
|
|
|
|
total_size = int(response.headers.get('content-length', 0))
|
|
downloaded = 0
|
|
|
|
with open(self.save_path, 'wb') as f:
|
|
for chunk in response.iter_content(chunk_size=8192):
|
|
if chunk:
|
|
f.write(chunk)
|
|
downloaded += len(chunk)
|
|
progress = int((downloaded / total_size) * 100)
|
|
self.progress_updated.emit(progress)
|
|
|
|
self.download_finished.emit(True, "")
|
|
except Exception as e:
|
|
self.download_finished.emit(False, str(e))
|
|
|
|
class AppCore:
|
|
def __init__(self, ui):
|
|
self.ui = ui
|
|
self.install_dir = ""
|
|
self.temp_dir = os.path.join(os.getenv(app_data["TEMP"]), app_data["CACHE"])
|
|
self.plugin_dir = os.path.join(self.temp_dir, app_data["PLUGIN"])
|
|
self._create_dirs() # 确保这个方法被正确定义
|
|
|
|
# 解码配置
|
|
self.CONFIG_URL = decode_base64(app_data["CONFIG_URL"])
|
|
self.UA = decode_base64(app_data["UA"]) + f" FraiseMoe/{app_data['APP_VERSION']}"
|
|
self.GAME_INFO = app_data["game_info"]
|
|
self.current_download_thread = None
|
|
|
|
def _create_dirs(self):
|
|
"""创建必要的目录"""
|
|
os.makedirs(self.plugin_dir, exist_ok=True)
|
|
|
|
def start_installation(self):
|
|
"""开始安装流程"""
|
|
if not self._select_install_dir():
|
|
return
|
|
|
|
if not self._check_processes():
|
|
return
|
|
|
|
self._download_and_install()
|
|
|
|
def _select_install_dir(self):
|
|
"""选择安装目录"""
|
|
dir_path = QFileDialog.getExistingDirectory(
|
|
self.ui, "选择游戏安装目录"
|
|
)
|
|
if not dir_path:
|
|
QMessageBox.warning(self.ui, "警告", "必须选择安装目录")
|
|
return False
|
|
self.install_dir = dir_path
|
|
return True
|
|
|
|
def _check_processes(self):
|
|
"""检查游戏进程"""
|
|
for proc in psutil.process_iter(['name']):
|
|
if proc.info['name'] in [info['exe'] for info in self.GAME_INFO.values()]:
|
|
reply = QMessageBox.question(
|
|
self.ui, "警告",
|
|
"检测到游戏正在运行,需要关闭才能继续安装",
|
|
QMessageBox.Yes | QMessageBox.No
|
|
)
|
|
if reply == QMessageBox.Yes:
|
|
try:
|
|
proc.kill()
|
|
except:
|
|
QMessageBox.critical(
|
|
self.ui, "错误",
|
|
"无法关闭游戏进程,请手动关闭"
|
|
)
|
|
return False
|
|
else:
|
|
return False
|
|
return True
|
|
|
|
def _get_download_config(self):
|
|
"""获取下载配置"""
|
|
try:
|
|
headers = {"User-Agent": self.UA}
|
|
response = requests.get(self.CONFIG_URL, headers=headers, timeout=10)
|
|
response.raise_for_status()
|
|
return response.json()
|
|
except Exception as e:
|
|
QMessageBox.critical(
|
|
self.ui, "错误",
|
|
f"获取下载配置失败: {str(e)}"
|
|
)
|
|
return None
|
|
|
|
def _download_and_install(self):
|
|
"""下载并安装文件"""
|
|
config = self._get_download_config()
|
|
if not config:
|
|
return
|
|
|
|
# 创建进度对话框
|
|
progress = QProgressDialog("下载补丁文件中...", "取消", 0, 100, self.ui)
|
|
progress.setWindowTitle("安装进度")
|
|
progress.setAutoClose(True)
|
|
|
|
# 为每个游戏版本下载补丁
|
|
for game_name, game_info in self.GAME_INFO.items():
|
|
vol_key = f"vol.{game_name.split()[-1]}.data"
|
|
if vol_key not in config:
|
|
continue
|
|
|
|
download_url = config[vol_key]["url"]
|
|
temp_file = os.path.join(self.plugin_dir, f"{vol_key}.7z")
|
|
|
|
# 启动下载线程
|
|
self.current_download_thread = DownloadThread(
|
|
download_url,
|
|
temp_file,
|
|
{"User-Agent": self.UA}
|
|
)
|
|
self.current_download_thread.progress_updated.connect(progress.setValue)
|
|
self.current_download_thread.download_finished.connect(
|
|
lambda success, err, f=temp_file, g=game_name, i=game_info:
|
|
self._handle_download_result(success, err, f, g, i)
|
|
)
|
|
self.current_download_thread.start()
|
|
|
|
progress.exec_()
|
|
|
|
def _handle_download_result(self, success, error, temp_file, game_name, game_info):
|
|
"""处理下载结果"""
|
|
if not success:
|
|
QMessageBox.critical(self.ui, "下载失败", f"错误: {error}")
|
|
return
|
|
|
|
try:
|
|
# 解压文件
|
|
with py7zr.SevenZipFile(temp_file, mode='r') as archive:
|
|
archive.extractall(path=self.plugin_dir)
|
|
|
|
# 复制到游戏目录
|
|
plugin_path = os.path.join(self.plugin_dir, game_info["plugin_path"])
|
|
install_path = os.path.join(self.install_dir, game_info["install_path"])
|
|
|
|
os.makedirs(os.path.dirname(install_path), exist_ok=True)
|
|
shutil.copy(plugin_path, install_path)
|
|
|
|
# 验证哈希
|
|
if self._verify_hash(install_path, game_info["hash"]):
|
|
QMessageBox.information(
|
|
self.ui, "安装完成",
|
|
f"{game_name} 补丁已成功安装!"
|
|
)
|
|
else:
|
|
QMessageBox.warning(
|
|
self.ui, "警告",
|
|
f"{game_name} 文件校验失败,安装可能不完整"
|
|
)
|
|
except Exception as e:
|
|
QMessageBox.critical(
|
|
self.ui, "安装失败",
|
|
f"安装过程中出错: {str(e)}"
|
|
)
|
|
finally:
|
|
# 清理临时文件
|
|
if os.path.exists(temp_file):
|
|
os.remove(temp_file)
|
|
|
|
def _verify_hash(self, file_path, expected_hash):
|
|
"""验证文件哈希"""
|
|
if not os.path.exists(file_path):
|
|
return False
|
|
|
|
sha256_hash = hashlib.sha256()
|
|
with open(file_path, "rb") as f:
|
|
for byte_block in iter(lambda: f.read(8192), b""):
|
|
sha256_hash.update(byte_block)
|
|
return sha256_hash.hexdigest() == expected_hash
|
|
|
|
def shutdown_app(self):
|
|
"""关闭应用程序"""
|
|
reply = QMessageBox.question(
|
|
self.ui, "退出",
|
|
"确定要退出安装程序吗?",
|
|
QMessageBox.Yes | QMessageBox.No
|
|
)
|
|
if reply == QMessageBox.Yes:
|
|
# 清理临时目录
|
|
if os.path.exists(self.temp_dir):
|
|
shutil.rmtree(self.temp_dir)
|
|
self.ui.close() |