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 2dec22e..749a90b 100644 Binary files a/__pycache__/Ui_install.cpython-310.pyc and b/__pycache__/Ui_install.cpython-310.pyc differ diff --git a/__pycache__/animations.cpython-310.pyc b/__pycache__/animations.cpython-310.pyc index e07e120..547b273 100644 Binary files a/__pycache__/animations.cpython-310.pyc and b/__pycache__/animations.cpython-310.pyc differ diff --git a/__pycache__/core.cpython-310.pyc b/__pycache__/core.cpython-310.pyc new file mode 100644 index 0000000..11d6b45 Binary files /dev/null and b/__pycache__/core.cpython-310.pyc differ diff --git a/__pycache__/main_window.cpython-310.pyc b/__pycache__/main_window.cpython-310.pyc index 871fcd3..eb99d0b 100644 Binary files a/__pycache__/main_window.cpython-310.pyc and b/__pycache__/main_window.cpython-310.pyc differ 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