From 4ccc93fdd18786adca3859be2785e096d473922d Mon Sep 17 00:00:00 2001 From: Akatsuki-Misaki Date: Thu, 3 Jul 2025 23:39:31 +0800 Subject: [PATCH] =?UTF-8?q?update:=E6=B5=8B=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- IMG/{BTH => BTN}/exit.bmp | Bin IMG/{BTH => BTN}/start_install.bmp | Bin __pycache__/Ui_install.cpython-310.pyc | Bin 4614 -> 4614 bytes __pycache__/animations.cpython-310.pyc | Bin 5103 -> 5103 bytes __pycache__/core.cpython-310.pyc | Bin 0 -> 8120 bytes __pycache__/main_window.cpython-310.pyc | Bin 1157 -> 1157 bytes core.py | 254 ++++++++++++++++++++++++ install.ui | 4 +- 8 files changed, 256 insertions(+), 2 deletions(-) rename IMG/{BTH => BTN}/exit.bmp (100%) rename IMG/{BTH => BTN}/start_install.bmp (100%) create mode 100644 __pycache__/core.cpython-310.pyc create mode 100644 core.py diff --git a/IMG/BTH/exit.bmp b/IMG/BTN/exit.bmp similarity index 100% rename from IMG/BTH/exit.bmp rename to IMG/BTN/exit.bmp diff --git a/IMG/BTH/start_install.bmp b/IMG/BTN/start_install.bmp similarity index 100% rename from IMG/BTH/start_install.bmp rename to IMG/BTN/start_install.bmp diff --git a/__pycache__/Ui_install.cpython-310.pyc b/__pycache__/Ui_install.cpython-310.pyc index 2dec22e7b623f2ad918236548695e3a1bdd3a934..749a90b55aab7b023b0ece795909b0eaf4bc0ebc 100644 GIT binary patch delta 19 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 T>$`hR`w#!`^oQHEj{E-?1-_w` literal 0 HcmV?d00001 diff --git a/__pycache__/main_window.cpython-310.pyc b/__pycache__/main_window.cpython-310.pyc index 871fcd34b8be9036dd3c34f9841ceac583f28f38..eb99d0b69d216f1ee72678f166e343675ea15f62 100644 GIT binary patch delta 19 ZcmZqWY~|$2=jG*M0D`lNH*!_8001a01Y`gJ delta 19 YcmZqWY~|$2=jG*M0D{K4ja=0%041yg^8f$< diff --git a/core.py b/core.py new file mode 100644 index 0000000..96d8881 --- /dev/null +++ b/core.py @@ -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() \ No newline at end of file diff --git a/install.ui b/install.ui index bdfb08d..400f680 100644 --- a/install.ui +++ b/install.ui @@ -212,7 +212,7 @@ - IMG/BTH/start_install.bmpIMG/BTH/start_install.bmp + IMG/BTN/start_install.bmpIMG/BTN/start_install.bmp @@ -253,7 +253,7 @@ - IMG/BTH/exit.bmpIMG/BTH/exit.bmp + IMG/BTN/exit.bmpIMG/BTN/exit.bmp