update:测试
This commit is contained in:
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB |
Binary file not shown.
Binary file not shown.
BIN
__pycache__/core.cpython-310.pyc
Normal file
BIN
__pycache__/core.cpython-310.pyc
Normal file
Binary file not shown.
Binary file not shown.
254
core.py
Normal file
254
core.py
Normal file
@@ -0,0 +1,254 @@
|
||||
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()
|
||||
@@ -212,7 +212,7 @@
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset>
|
||||
<normaloff>IMG/BTH/start_install.bmp</normaloff>IMG/BTH/start_install.bmp</iconset>
|
||||
<normaloff>IMG/BTN/start_install.bmp</normaloff>IMG/BTN/start_install.bmp</iconset>
|
||||
</property>
|
||||
<property name="iconSize">
|
||||
<size>
|
||||
@@ -253,7 +253,7 @@
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset>
|
||||
<normaloff>IMG/BTH/exit.bmp</normaloff>IMG/BTH/exit.bmp</iconset>
|
||||
<normaloff>IMG/BTN/exit.bmp</normaloff>IMG/BTN/exit.bmp</iconset>
|
||||
</property>
|
||||
<property name="iconSize">
|
||||
<size>
|
||||
|
||||
Reference in New Issue
Block a user