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