mirror of
https://github.com/Yanam1Anna/FRAISEMOE-Addons-Installer.git
synced 2025-06-04 14:43:02 +00:00
Update source -> V4.9.0.17411
This commit is contained in:
parent
9f94b3e8a5
commit
c3e91bda94
@ -215,7 +215,7 @@ class Ui_mainwin(object):
|
|||||||
def retranslateUi(self, mainwin):
|
def retranslateUi(self, mainwin):
|
||||||
mainwin.setWindowTitle(
|
mainwin.setWindowTitle(
|
||||||
QCoreApplication.translate(
|
QCoreApplication.translate(
|
||||||
"mainwin", "FRAISEMOE Addons Installer V4.8.6.17218", None
|
"mainwin", "FRAISEMOE Addons Installer V4.9.0.17411", None
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
self.mainbg.setText("")
|
self.mainbg.setText("")
|
||||||
|
723
source/Main.py
723
source/Main.py
@ -7,9 +7,11 @@ import sys
|
|||||||
import base64
|
import base64
|
||||||
import psutil
|
import psutil
|
||||||
import ctypes
|
import ctypes
|
||||||
|
import base64
|
||||||
|
import concurrent.futures
|
||||||
|
|
||||||
from PySide6 import QtWidgets, QtCore
|
from PySide6 import QtWidgets, QtCore
|
||||||
from PySide6.QtCore import Qt, QByteArray, Signal
|
from PySide6.QtCore import Qt, QByteArray, QThread, Signal
|
||||||
from PySide6.QtWidgets import (
|
from PySide6.QtWidgets import (
|
||||||
QApplication,
|
QApplication,
|
||||||
QWidget,
|
QWidget,
|
||||||
@ -17,285 +19,401 @@ from PySide6.QtWidgets import (
|
|||||||
QProgressBar,
|
QProgressBar,
|
||||||
QVBoxLayout,
|
QVBoxLayout,
|
||||||
QLabel,
|
QLabel,
|
||||||
|
QDialog,
|
||||||
)
|
)
|
||||||
from PySide6.QtGui import QIcon, QPixmap
|
from PySide6.QtGui import QIcon, QPixmap
|
||||||
from collections import deque
|
from collections import deque
|
||||||
from pic_data import img_data
|
from pic_data import img_data
|
||||||
from GUI import Ui_mainwin
|
from GUI import Ui_mainwin
|
||||||
|
|
||||||
APP_VERSION = "@FRAISEMOE Addons Installer V4.8.6.17218"
|
# 配置信息
|
||||||
TEMP = os.getenv("TEMP")
|
app_data = {
|
||||||
CACHE = os.path.join(TEMP, "FRAISEMOE")
|
"APP_NAME": "@FRAISEMOE Addons Installer",
|
||||||
PLUGIN = os.path.join(CACHE, "PLUGIN")
|
"TEMP": "TEMP",
|
||||||
CONFIG_URL = "https://archive.ovofish.com/api/widget/nekopara/download_url.json"
|
"CACHE": "FRAISEMOE",
|
||||||
UA = "Mozilla/5.0 (Linux debian12 Python-Accept) Gecko/20100101 Firefox/114.0"
|
"PLUGIN": "PLUGIN",
|
||||||
SRC_HASHES = {
|
"CONFIG_URL": "aHR0cHM6Ly9hcmNoaXZlLm92b2Zpc2guY29tL2FwaS93aWRnZXQvbmVrb3BhcmEvZG93bmxvYWRfdXJsLmpzb24=",
|
||||||
"NEKOPARA Vol.1": "04b48b231a7f34431431e5027fcc7b27affaa951b8169c541709156acf754f3e",
|
"UA": "TW96aWxsYS81LjAgKExpbnV4IGRlYmlhbjEyIFB5dGhvbi1BY2NlcHQpIEdlY2tvLzIwMTAwMTAxIEZpcmVmb3gvMTE0LjA=",
|
||||||
"NEKOPARA Vol.2": "b9c00a2b113a1e768bf78400e4f9075ceb7b35349cdeca09be62eb014f0d4b42",
|
"game_info": {
|
||||||
"NEKOPARA Vol.3": "2ce7b223c84592e1ebc3b72079dee1e5e8d064ade15723328a64dee58833b9d5",
|
"NEKOPARA Vol.1": {
|
||||||
"NEKOPARA Vol.4": "4a4a9ae5a75a18aacbe3ab0774d7f93f99c046afe3a777ee0363e8932b90f36a",
|
"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 admin_status():
|
# Base64解码
|
||||||
try:
|
def decode_base64(encoded_str):
|
||||||
return ctypes.windll.shell32.IsUserAnAdmin()
|
return base64.b64decode(encoded_str).decode("utf-8")
|
||||||
except:
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
def run_as_admin():
|
APP_NAME = app_data["APP_NAME"]
|
||||||
script = os.path.abspath(sys.argv[0])
|
TEMP = os.getenv(app_data["TEMP"])
|
||||||
params = " ".join([script] + sys.argv[1:])
|
CACHE = os.path.join(TEMP, app_data["CACHE"])
|
||||||
ctypes.windll.shell32.ShellExecuteW(None, "runas", sys.executable, params, None, 1)
|
PLUGIN = os.path.join(CACHE, app_data["PLUGIN"])
|
||||||
|
CONFIG_URL = decode_base64(app_data["CONFIG_URL"])
|
||||||
|
UA = decode_base64(app_data["UA"])
|
||||||
|
GAME_INFO = app_data["game_info"]
|
||||||
|
BLOCK_SIZE = 64 * 1024
|
||||||
|
HASH_SIZE = 128 * 1024
|
||||||
|
PLUGIN_HASH = {game: info["hash"] for game, info in GAME_INFO.items()}
|
||||||
|
PROCESS_INFO = {info["exe"]: game for game, info in GAME_INFO.items()}
|
||||||
|
|
||||||
|
|
||||||
class DownloadThread(QtCore.QThread):
|
# 弹窗框架
|
||||||
progress = Signal(int)
|
def msgbox_frame(title, text, buttons=QMessageBox.StandardButton.NoButton):
|
||||||
finished = Signal(bool, str)
|
msg_box = QMessageBox()
|
||||||
|
msg_box.setWindowTitle(title)
|
||||||
|
pixmap = QPixmap()
|
||||||
|
pixmap.loadFromData(QByteArray(base64.b64decode(img_data["icon"])))
|
||||||
|
icon = QIcon(pixmap)
|
||||||
|
msg_box.setWindowIcon(icon)
|
||||||
|
msg_box.setText(text)
|
||||||
|
msg_box.setStandardButtons(buttons)
|
||||||
|
return msg_box
|
||||||
|
|
||||||
|
|
||||||
|
# 哈希值计算类
|
||||||
|
class HashManager:
|
||||||
|
def __init__(self, HASH_SIZE):
|
||||||
|
self.HASH_SIZE = HASH_SIZE
|
||||||
|
|
||||||
|
# 哈希值计算
|
||||||
|
def hash_calculate(self, file_path):
|
||||||
|
sha256_hash = hashlib.sha256()
|
||||||
|
with open(file_path, "rb") as f:
|
||||||
|
for byte_block in iter(lambda: f.read(self.HASH_SIZE), b""):
|
||||||
|
sha256_hash.update(byte_block)
|
||||||
|
return sha256_hash.hexdigest()
|
||||||
|
|
||||||
|
# 使用多线程优化哈希值计算
|
||||||
|
def calculate_hashes_in_parallel(self, file_paths):
|
||||||
|
with concurrent.futures.ThreadPoolExecutor() as executor:
|
||||||
|
future_to_file = {
|
||||||
|
executor.submit(self.hash_calculate, path): path for path in file_paths
|
||||||
|
}
|
||||||
|
results = {}
|
||||||
|
for future in concurrent.futures.as_completed(future_to_file):
|
||||||
|
file_path = future_to_file[future]
|
||||||
|
try:
|
||||||
|
results[file_path] = future.result()
|
||||||
|
except Exception as e:
|
||||||
|
results[file_path] = None
|
||||||
|
msg_box = msgbox_frame(
|
||||||
|
f"错误 {APP_NAME}",
|
||||||
|
f"\n文件哈希值计算失败\n\n【错误信息】:{e}\n",
|
||||||
|
QMessageBox.StandardButton.Ok,
|
||||||
|
)
|
||||||
|
msg_box.exec()
|
||||||
|
return results
|
||||||
|
|
||||||
|
# 哈希值计算时的窗口
|
||||||
|
def hash_pop_window(self):
|
||||||
|
msg_box = msgbox_frame(f"通知 {APP_NAME}", "\n正在检验文件状态...\n")
|
||||||
|
msg_box.show()
|
||||||
|
QApplication.processEvents()
|
||||||
|
return msg_box
|
||||||
|
|
||||||
|
# 下载前比对已有文件哈希值
|
||||||
|
def cfg_pre_hash_compare(
|
||||||
|
self, install_path, game_version, plugin_hash, installed_status
|
||||||
|
):
|
||||||
|
if not os.path.exists(install_path):
|
||||||
|
installed_status[game_version] = False
|
||||||
|
return
|
||||||
|
file_hash = self.hash_calculate(install_path)
|
||||||
|
if file_hash == plugin_hash[game_version]:
|
||||||
|
installed_status[game_version] = True
|
||||||
|
else:
|
||||||
|
reply = msgbox_frame(
|
||||||
|
f"文件校验 {APP_NAME}",
|
||||||
|
f"\n检测到 {game_version} 的文件哈希值不匹配,是否重新安装?\n",
|
||||||
|
QMessageBox.Yes | QMessageBox.No,
|
||||||
|
).exec()
|
||||||
|
if reply == QMessageBox.Yes:
|
||||||
|
installed_status[game_version] = False
|
||||||
|
else:
|
||||||
|
installed_status[game_version] = True
|
||||||
|
|
||||||
|
# 下载完成后比对哈希值
|
||||||
|
def cfg_after_hash_compare(self, install_paths, plugin_hash, installed_status):
|
||||||
|
passed = True
|
||||||
|
file_paths = [
|
||||||
|
install_paths[game] for game in plugin_hash if installed_status.get(game)
|
||||||
|
]
|
||||||
|
hash_results = self.calculate_hashes_in_parallel(file_paths)
|
||||||
|
|
||||||
|
for game, hash_value in plugin_hash.items():
|
||||||
|
if installed_status.get(game):
|
||||||
|
file_hash = hash_results.get(install_paths[game])
|
||||||
|
if file_hash != hash_value:
|
||||||
|
msg_box = msgbox_frame(
|
||||||
|
f"文件校验 {APP_NAME}",
|
||||||
|
f"\n检测到 {game} 的文件哈希值不匹配\n",
|
||||||
|
QMessageBox.StandardButton.Ok,
|
||||||
|
)
|
||||||
|
msg_box.exec()
|
||||||
|
installed_status[game] = False
|
||||||
|
passed = False
|
||||||
|
break
|
||||||
|
return passed
|
||||||
|
|
||||||
|
|
||||||
|
# 管理员权限检查类
|
||||||
|
class AdminPrivileges:
|
||||||
|
# 进程列表
|
||||||
|
def __init__(self):
|
||||||
|
self.required_exes = [
|
||||||
|
"nekopara_vol1.exe",
|
||||||
|
"nekopara_vol2.exe",
|
||||||
|
"NEKOPARAvol3.exe",
|
||||||
|
"nekopara_vol4.exe",
|
||||||
|
]
|
||||||
|
|
||||||
|
# 检查管理员权限
|
||||||
|
def is_admin(self):
|
||||||
|
try:
|
||||||
|
return ctypes.windll.shell32.IsUserAnAdmin()
|
||||||
|
except:
|
||||||
|
return False
|
||||||
|
|
||||||
|
# 请求管理员权限
|
||||||
|
def request_admin_privileges(self):
|
||||||
|
if not self.is_admin():
|
||||||
|
msg_box = msgbox_frame(
|
||||||
|
f"权限检测 {APP_NAME}",
|
||||||
|
"\n需要管理员权限运行此程序\n",
|
||||||
|
QMessageBox.Yes | QMessageBox.No,
|
||||||
|
)
|
||||||
|
reply = msg_box.exec()
|
||||||
|
if reply == QMessageBox.Yes:
|
||||||
|
try:
|
||||||
|
ctypes.windll.shell32.ShellExecuteW(
|
||||||
|
None, "runas", sys.executable, " ".join(sys.argv), None, 1
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
msg_box = msgbox_frame(
|
||||||
|
f"错误 {APP_NAME}",
|
||||||
|
f"\n请求管理员权限失败\n\n【错误信息】:{e}\n",
|
||||||
|
QMessageBox.StandardButton.Ok,
|
||||||
|
)
|
||||||
|
msg_box.exec()
|
||||||
|
sys.exit(1)
|
||||||
|
else:
|
||||||
|
msg_box = msgbox_frame(
|
||||||
|
f"权限检测 {APP_NAME}",
|
||||||
|
"\n无法获取管理员权限,程序将退出\n",
|
||||||
|
QMessageBox.StandardButton.Ok,
|
||||||
|
)
|
||||||
|
msg_box.exec()
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
# 检查并终止进程
|
||||||
|
def check_and_terminate_processes(self):
|
||||||
|
for proc in psutil.process_iter(["pid", "name"]):
|
||||||
|
if proc.info["name"] in self.required_exes:
|
||||||
|
msg_box = msgbox_frame(
|
||||||
|
f"进程检测 {APP_NAME}",
|
||||||
|
f"\n检测到游戏正在运行: {proc.info['name']} \n\n是否终止?\n",
|
||||||
|
QMessageBox.Yes | QMessageBox.No,
|
||||||
|
)
|
||||||
|
reply = msg_box.exec()
|
||||||
|
if reply == QMessageBox.Yes:
|
||||||
|
try:
|
||||||
|
proc.terminate()
|
||||||
|
proc.wait(timeout=3)
|
||||||
|
except psutil.AccessDenied:
|
||||||
|
msg_box = msgbox_frame(
|
||||||
|
f"错误 {APP_NAME}",
|
||||||
|
f"\n无法关闭游戏: {proc.info['name']} \n\n请手动关闭后重启应用\n",
|
||||||
|
QMessageBox.StandardButton.Ok,
|
||||||
|
)
|
||||||
|
msg_box.exec()
|
||||||
|
sys.exit(1)
|
||||||
|
else:
|
||||||
|
msg_box = msgbox_frame(
|
||||||
|
f"进程检测 {APP_NAME}",
|
||||||
|
f"\n未关闭的游戏: {proc.info['name']} \n\n请手动关闭后重启应用\n",
|
||||||
|
QMessageBox.StandardButton.Ok,
|
||||||
|
)
|
||||||
|
msg_box.exec()
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
# 下载线程类
|
||||||
|
class DownloadThread(QThread):
|
||||||
|
progress = Signal(int) # 进度信号
|
||||||
|
finished = Signal(bool, str) # 完成信号
|
||||||
|
|
||||||
def __init__(self, url, _7z_path, parent=None):
|
def __init__(self, url, _7z_path, parent=None):
|
||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
self.url = url
|
self.url = url # 下载地址
|
||||||
self._7z_path = _7z_path
|
self._7z_path = _7z_path # 7z文件路径
|
||||||
|
|
||||||
|
# 下载线程运行
|
||||||
def run(self):
|
def run(self):
|
||||||
try:
|
try:
|
||||||
headers = {"User-Agent": UA}
|
headers = {"User-Agent": UA}
|
||||||
r = requests.get(self.url, headers=headers, stream=True, timeout=10)
|
r = requests.get(self.url, headers=headers, stream=True, timeout=10)
|
||||||
r.raise_for_status()
|
r.raise_for_status()
|
||||||
block_size = 64 * 1024
|
|
||||||
total_size = int(r.headers.get("content-length", 0))
|
total_size = int(r.headers.get("content-length", 0))
|
||||||
with open(self._7z_path, "wb") as f:
|
with open(self._7z_path, "wb") as f:
|
||||||
for chunk in r.iter_content(chunk_size=block_size):
|
for chunk in r.iter_content(chunk_size=BLOCK_SIZE):
|
||||||
f.write(chunk)
|
f.write(chunk)
|
||||||
self.progress.emit(f.tell() * 100 // total_size)
|
self.progress.emit(f.tell() * 100 // total_size) # 发送进度信号
|
||||||
self.finished.emit(True, "")
|
self.finished.emit(True, "") # 发送完成信号
|
||||||
except requests.exceptions.RequestException as e:
|
except requests.exceptions.RequestException as e:
|
||||||
self.finished.emit(False, f"\n网络请求错误\n\n【错误信息】: {e}\n")
|
self.finished.emit(False, f"\n网络请求错误\n\n【错误信息】: {e}\n")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.finished.emit(False, f"\n未知错误\n\n【错误信息】: {e}\n")
|
self.finished.emit(False, f"\n未知错误\n\n【错误信息】: {e}\n")
|
||||||
|
|
||||||
|
|
||||||
def game_process_status(process_name):
|
# 下载进度窗口类
|
||||||
for proc in psutil.process_iter(["pid", "name"]):
|
class ProgressWindow(QDialog):
|
||||||
try:
|
def __init__(self, parent=None):
|
||||||
if process_name.lower() in proc.info["name"].lower():
|
super(ProgressWindow, self).__init__(parent)
|
||||||
return proc.info["pid"]
|
self.setWindowTitle(f"下载进度 {APP_NAME}")
|
||||||
except (psutil.NoSuchProcess, psutil.AccessDenied, psutil.ZombieProcess):
|
self.resize(400, 100)
|
||||||
pass
|
self.progress_bar_max = 100
|
||||||
return None
|
self.setWindowFlags(
|
||||||
|
self.windowFlags() & ~Qt.WindowCloseButtonHint
|
||||||
|
) # 禁用关闭按钮
|
||||||
def kill_process(pid):
|
self.setWindowFlags(
|
||||||
try:
|
self.windowFlags() & ~Qt.WindowSystemMenuHint
|
||||||
process = psutil.Process(pid)
|
) # 禁用系统菜单
|
||||||
process.terminate()
|
|
||||||
process.wait(timeout=5)
|
layout = QVBoxLayout()
|
||||||
except (psutil.NoSuchProcess, psutil.AccessDenied, psutil.ZombieProcess):
|
self.progress_bar = QProgressBar()
|
||||||
pass
|
self.progress_bar.setValue(0)
|
||||||
|
self.label = QLabel("\n正在下载...\n")
|
||||||
|
layout.addWidget(self.label)
|
||||||
|
layout.addWidget(self.progress_bar)
|
||||||
|
self.setLayout(layout)
|
||||||
|
|
||||||
|
# 设置进度条最大值
|
||||||
|
def setmaxvalue(self, value):
|
||||||
|
self.progress_bar_max = value
|
||||||
|
self.progress_bar.setMaximum(value)
|
||||||
|
|
||||||
|
# 设置进度条值
|
||||||
|
def setprogressbarval(self, value):
|
||||||
|
self.progress_bar.setValue(value)
|
||||||
|
if value == self.progress_bar_max: # 下载完成后关闭窗口
|
||||||
|
QtCore.QTimer.singleShot(2000, self.close)
|
||||||
|
|
||||||
|
|
||||||
|
# 主窗口类
|
||||||
class MyWindow(QWidget, Ui_mainwin):
|
class MyWindow(QWidget, Ui_mainwin):
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.setupUi(self)
|
self.setupUi(self)
|
||||||
self.selected_folder = ""
|
self.selected_folder = ""
|
||||||
self.installed_status = {
|
self.installed_status = {f"NEKOPARA Vol.{i}": False for i in range(1, 5)}
|
||||||
"NEKOPARA Vol.1": False,
|
|
||||||
"NEKOPARA Vol.2": False,
|
|
||||||
"NEKOPARA Vol.3": False,
|
|
||||||
"NEKOPARA Vol.4": False,
|
|
||||||
}
|
|
||||||
self.download_queue = deque()
|
self.download_queue = deque()
|
||||||
self.current_download_thread = None
|
self.current_download_thread = None
|
||||||
|
self.hash_manager = HashManager(BLOCK_SIZE)
|
||||||
|
|
||||||
game_process_info = {
|
# 检查管理员权限和进程
|
||||||
"nekopara_vol1.exe": "NEKOPARA Vol.1",
|
admin_privileges = AdminPrivileges()
|
||||||
"nekopara_vol2.exe": "NEKOPARA Vol.2",
|
admin_privileges.request_admin_privileges()
|
||||||
"NEKOPARAvol3.exe": "NEKOPARA Vol.3",
|
admin_privileges.check_and_terminate_processes()
|
||||||
"nekopara_vol4.exe": "NEKOPARA Vol.4",
|
# 创建缓存目录
|
||||||
}
|
|
||||||
|
|
||||||
for process_name, game_version in game_process_info.items():
|
|
||||||
pid = game_process_status(process_name)
|
|
||||||
if pid:
|
|
||||||
msg_box = QMessageBox()
|
|
||||||
msg_box.setWindowTitle(f"进程检测 {APP_VERSION}")
|
|
||||||
pixmap = QPixmap()
|
|
||||||
pixmap.loadFromData(QByteArray(base64.b64decode(img_data["icon"])))
|
|
||||||
icon = QIcon(pixmap)
|
|
||||||
msg_box.setWindowIcon(icon)
|
|
||||||
msg_box.setText(f"\n检测到 {game_version} 正在运行,是否关闭?\n")
|
|
||||||
yes_button = msg_box.addButton(
|
|
||||||
"确定", QMessageBox.ButtonRole.AcceptRole
|
|
||||||
)
|
|
||||||
no_button = msg_box.addButton("取消", QMessageBox.ButtonRole.RejectRole)
|
|
||||||
msg_box.setDefaultButton(no_button)
|
|
||||||
msg_box.exec()
|
|
||||||
|
|
||||||
if msg_box.clickedButton() == yes_button:
|
|
||||||
kill_process(pid)
|
|
||||||
else:
|
|
||||||
QMessageBox.warning(
|
|
||||||
self,
|
|
||||||
f"警告 {APP_VERSION}",
|
|
||||||
f"\n请关闭 {game_version} 后再运行本程序。\n",
|
|
||||||
)
|
|
||||||
self.close()
|
|
||||||
sys.exit()
|
|
||||||
|
|
||||||
if not os.path.exists(PLUGIN):
|
if not os.path.exists(PLUGIN):
|
||||||
os.makedirs(PLUGIN)
|
try:
|
||||||
if not os.path.exists(PLUGIN):
|
os.makedirs(PLUGIN)
|
||||||
|
except OSError as e:
|
||||||
QMessageBox.critical(
|
QMessageBox.critical(
|
||||||
self,
|
self,
|
||||||
f"错误 {APP_VERSION}",
|
f"错误 {APP_NAME}",
|
||||||
"\n无法创建缓存位置\n\n使用管理员身份运行或检查文件读写权限\n",
|
f"\n无法创建缓存位置\n\n使用管理员身份运行或检查文件读写权限\n\n【错误信息】:{e}\n",
|
||||||
)
|
)
|
||||||
self.close()
|
sys.exit(1)
|
||||||
sys.exit()
|
# 连接信号 & UI按钮
|
||||||
|
|
||||||
self.startbtn.clicked.connect(self.file_dialog)
|
self.startbtn.clicked.connect(self.file_dialog)
|
||||||
self.exitbtn.clicked.connect(self.shutdown_app)
|
self.exitbtn.clicked.connect(self.shutdown_app)
|
||||||
|
|
||||||
|
# 获取游戏安装路径
|
||||||
def get_install_paths(self):
|
def get_install_paths(self):
|
||||||
return {
|
return {
|
||||||
"NEKOPARA Vol.1": os.path.join(
|
game: os.path.join(self.selected_folder, info["install_path"])
|
||||||
self.selected_folder, "NEKOPARA Vol. 1", "adultsonly.xp3"
|
for game, info in GAME_INFO.items()
|
||||||
),
|
|
||||||
"NEKOPARA Vol.2": os.path.join(
|
|
||||||
self.selected_folder, "NEKOPARA Vol. 2", "adultsonly.xp3"
|
|
||||||
),
|
|
||||||
"NEKOPARA Vol.3": os.path.join(
|
|
||||||
self.selected_folder, "NEKOPARA Vol. 3", "update00.int"
|
|
||||||
),
|
|
||||||
"NEKOPARA Vol.4": os.path.join(
|
|
||||||
self.selected_folder, "NEKOPARA Vol. 4", "vol4adult.xp3"
|
|
||||||
),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# 获取游戏目录
|
||||||
def file_dialog(self):
|
def file_dialog(self):
|
||||||
self.selected_folder = QtWidgets.QFileDialog.getExistingDirectory(
|
self.selected_folder = QtWidgets.QFileDialog.getExistingDirectory(
|
||||||
self, f"选择游戏所在【上级目录】 {APP_VERSION}"
|
self, f"选择游戏所在【上级目录】 {APP_NAME}"
|
||||||
)
|
)
|
||||||
if not self.selected_folder:
|
if not self.selected_folder:
|
||||||
QMessageBox.warning(
|
QMessageBox.warning(
|
||||||
self, f"通知 {APP_VERSION}", "\n未选择任何目录,请重新选择\n"
|
self, f"通知 {APP_NAME}", "\n未选择任何目录,请重新选择\n"
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
self.download_action()
|
self.download_action()
|
||||||
|
|
||||||
def hash_calculate(self, file_path):
|
# 获取下载配置文件
|
||||||
sha256_hash = hashlib.sha256()
|
def get_download_url(self) -> dict:
|
||||||
with open(file_path, "rb") as f:
|
|
||||||
for byte_block in iter(lambda: f.read(4096), b""):
|
|
||||||
sha256_hash.update(byte_block)
|
|
||||||
return sha256_hash.hexdigest()
|
|
||||||
|
|
||||||
def hash_pop_window(self):
|
|
||||||
msg_box = QMessageBox()
|
|
||||||
msg_box.setWindowTitle(f"通知 {APP_VERSION}")
|
|
||||||
pixmap = QPixmap()
|
|
||||||
pixmap.loadFromData(QByteArray(base64.b64decode(img_data["icon"])))
|
|
||||||
icon = QIcon(pixmap)
|
|
||||||
msg_box.setWindowIcon(icon)
|
|
||||||
msg_box.setText("\n正在检验文件状态...\n")
|
|
||||||
msg_box.setStandardButtons(QMessageBox.StandardButton.NoButton)
|
|
||||||
msg_box.show()
|
|
||||||
QApplication.processEvents()
|
|
||||||
return msg_box
|
|
||||||
|
|
||||||
def pre_hash_compare(self, install_path, game_version, SRC_HASHES):
|
|
||||||
if not os.path.exists(install_path):
|
|
||||||
self.installed_status[game_version] = False
|
|
||||||
return
|
|
||||||
|
|
||||||
msg_box = self.hash_pop_window()
|
|
||||||
file_hash = self.hash_calculate(install_path)
|
|
||||||
msg_box.close()
|
|
||||||
|
|
||||||
if file_hash != SRC_HASHES[game_version]:
|
|
||||||
msg_box = QMessageBox(self)
|
|
||||||
msg_box.setWindowTitle(f"文件校验 {APP_VERSION}")
|
|
||||||
msg_box.setText(
|
|
||||||
f"\n【 当前版本已安装旧版本补丁 -> {game_version} 】\n\n是否重新安装?\n----->取消安装前应确认补丁是否可用<-----\n"
|
|
||||||
)
|
|
||||||
yes_button = msg_box.addButton("确定", QMessageBox.ButtonRole.AcceptRole)
|
|
||||||
no_button = msg_box.addButton("取消", QMessageBox.ButtonRole.RejectRole)
|
|
||||||
msg_box.setDefaultButton(no_button)
|
|
||||||
msg_box.exec()
|
|
||||||
if msg_box.clickedButton() == yes_button:
|
|
||||||
self.installed_status[game_version] = False
|
|
||||||
return
|
|
||||||
else:
|
|
||||||
self.installed_status[game_version] = True
|
|
||||||
return
|
|
||||||
else:
|
|
||||||
self.installed_status[game_version] = True
|
|
||||||
return
|
|
||||||
|
|
||||||
def late_hash_compare(self, SRC_HASHES):
|
|
||||||
install_paths = self.get_install_paths()
|
|
||||||
passed = True
|
|
||||||
for game, hash_value in SRC_HASHES.items():
|
|
||||||
if self.installed_status.get(game):
|
|
||||||
msg_box = self.hash_pop_window()
|
|
||||||
file_hash = self.hash_calculate(install_paths[game])
|
|
||||||
msg_box.close()
|
|
||||||
if file_hash != hash_value:
|
|
||||||
passed = False
|
|
||||||
break
|
|
||||||
return passed
|
|
||||||
|
|
||||||
def download_config(self) -> dict:
|
|
||||||
try:
|
try:
|
||||||
headers = {"User-Agent": UA}
|
headers = {"User-Agent": UA}
|
||||||
response = requests.get(CONFIG_URL, headers=headers, timeout=10)
|
response = requests.get(CONFIG_URL, headers=headers, timeout=10)
|
||||||
response.raise_for_status()
|
response.raise_for_status()
|
||||||
response = response.json()
|
config_data = response.json()
|
||||||
return {f"vol{i+1}": response[f"vol.{i+1}.data"]["url"] for i in range(4)}
|
if not all(f"vol.{i+1}.data" in config_data for i in range(4)):
|
||||||
except requests.exceptions.RequestException as e:
|
raise ValueError("配置文件数据异常")
|
||||||
|
return {
|
||||||
|
f"vol{i+1}": config_data[f"vol.{i+1}.data"]["url"] for i in range(4)
|
||||||
|
}
|
||||||
|
except (requests.exceptions.RequestException, ValueError) as e:
|
||||||
QMessageBox.critical(
|
QMessageBox.critical(
|
||||||
self,
|
self,
|
||||||
f"错误 {APP_VERSION}",
|
f"错误 {APP_NAME}",
|
||||||
f"\n下载配置获取失败\n\n网络状态异常或服务器故障\n\n【错误信息】:{e}\n",
|
f"\n下载配置获取失败\n\n【错误信息】:{e}\n",
|
||||||
)
|
)
|
||||||
except Exception as e:
|
return {}
|
||||||
QMessageBox.critical(
|
|
||||||
self,
|
|
||||||
f"错误 {APP_VERSION}",
|
|
||||||
f"\n下载配置获取失败\n\n未知错误\n\n【错误信息】:{e}\n",
|
|
||||||
)
|
|
||||||
return {}
|
|
||||||
|
|
||||||
|
# 下载参数设置
|
||||||
def download_setting(self, url, game_folder, game_version, _7z_path, plugin_path):
|
def download_setting(self, url, game_folder, game_version, _7z_path, plugin_path):
|
||||||
game_exe = {
|
game_exe = {
|
||||||
"NEKOPARA Vol.1": os.path.join(
|
game: os.path.join(
|
||||||
self.selected_folder, "NEKOPARA Vol. 1", "nekopara_vol1.exe"
|
self.selected_folder, info["install_path"].split("/")[0], info["exe"]
|
||||||
),
|
)
|
||||||
"NEKOPARA Vol.2": os.path.join(
|
for game, info in GAME_INFO.items()
|
||||||
self.selected_folder, "NEKOPARA Vol. 2", "nekopara_vol2.exe"
|
|
||||||
),
|
|
||||||
"NEKOPARA Vol.3": os.path.join(
|
|
||||||
self.selected_folder, "NEKOPARA Vol. 3", "NEKOPARAvol3.exe"
|
|
||||||
),
|
|
||||||
"NEKOPARA Vol.4": os.path.join(
|
|
||||||
self.selected_folder, "NEKOPARA Vol. 4", "nekopara_vol4.exe"
|
|
||||||
),
|
|
||||||
}
|
}
|
||||||
|
# 判断游戏是否存在,不存在则跳过
|
||||||
if (
|
if (
|
||||||
game_version not in game_exe
|
game_version not in game_exe
|
||||||
or not os.path.exists(game_exe[game_version])
|
or not os.path.exists(game_exe[game_version])
|
||||||
or self.installed_status[game_version]
|
or self.installed_status[game_version]
|
||||||
):
|
):
|
||||||
self.next_download_task()
|
self.installed_status[game_version] = False
|
||||||
|
self.show_result()
|
||||||
return
|
return
|
||||||
|
# 下载时显示进度窗口
|
||||||
progress_window = ProgressWindow(self)
|
progress_window = ProgressWindow(self)
|
||||||
progress_window.show()
|
progress_window.show()
|
||||||
|
# 启用下载线程
|
||||||
self.current_download_thread = DownloadThread(url, _7z_path, self)
|
self.current_download_thread = DownloadThread(url, _7z_path, self)
|
||||||
self.current_download_thread.progress.connect(progress_window.setprogressbarval)
|
self.current_download_thread.progress.connect(progress_window.setprogressbarval)
|
||||||
self.current_download_thread.finished.connect(
|
self.current_download_thread.finished.connect(
|
||||||
@ -311,6 +429,7 @@ class MyWindow(QWidget, Ui_mainwin):
|
|||||||
)
|
)
|
||||||
self.current_download_thread.start()
|
self.current_download_thread.start()
|
||||||
|
|
||||||
|
# 安装设置
|
||||||
def install_setting(
|
def install_setting(
|
||||||
self,
|
self,
|
||||||
success,
|
success,
|
||||||
@ -324,32 +443,19 @@ class MyWindow(QWidget, Ui_mainwin):
|
|||||||
progress_window.close()
|
progress_window.close()
|
||||||
if success:
|
if success:
|
||||||
try:
|
try:
|
||||||
msg_box = self.hash_pop_window()
|
msg_box = self.hash_manager.hash_pop_window()
|
||||||
QApplication.processEvents()
|
QApplication.processEvents()
|
||||||
|
|
||||||
with py7zr.SevenZipFile(_7z_path, mode="r") as archive:
|
with py7zr.SevenZipFile(_7z_path, mode="r") as archive:
|
||||||
archive.extractall(path=PLUGIN)
|
archive.extractall(path=PLUGIN)
|
||||||
shutil.copy(plugin_path, game_folder)
|
shutil.copy(plugin_path, game_folder)
|
||||||
self.installed_status[game_version] = True
|
self.installed_status[game_version] = True
|
||||||
QMessageBox.information(
|
QMessageBox.information(
|
||||||
self, f"通知 {APP_VERSION}", f"\n{game_version} 补丁已安装\n"
|
self, f"通知 {APP_NAME}", f"\n{game_version} 补丁已安装\n"
|
||||||
)
|
)
|
||||||
except py7zr.Bad7zFile as e:
|
except (py7zr.Bad7zFile, FileNotFoundError, Exception) as e:
|
||||||
QMessageBox.critical(
|
QMessageBox.critical(
|
||||||
self,
|
self,
|
||||||
f"错误 {APP_VERSION}",
|
f"错误 {APP_NAME}",
|
||||||
f"\n文件损坏\n\n【错误信息】:{e}\n",
|
|
||||||
)
|
|
||||||
except FileNotFoundError as e:
|
|
||||||
QMessageBox.critical(
|
|
||||||
self,
|
|
||||||
f"错误 {APP_VERSION}",
|
|
||||||
f"\n文件不存在\n\n【错误信息】:{e}\n",
|
|
||||||
)
|
|
||||||
except Exception as e:
|
|
||||||
QMessageBox.critical(
|
|
||||||
self,
|
|
||||||
f"错误 {APP_VERSION}",
|
|
||||||
f"\n文件操作失败,请重试\n\n【错误信息】:{e}\n",
|
f"\n文件操作失败,请重试\n\n【错误信息】:{e}\n",
|
||||||
)
|
)
|
||||||
finally:
|
finally:
|
||||||
@ -357,56 +463,68 @@ class MyWindow(QWidget, Ui_mainwin):
|
|||||||
else:
|
else:
|
||||||
QMessageBox.critical(
|
QMessageBox.critical(
|
||||||
self,
|
self,
|
||||||
f"错误 {APP_VERSION}",
|
f"错误 {APP_NAME}",
|
||||||
f"\n文件获取失败\n网络状态异常或服务器故障\n\n【错误信息】:{error}\n",
|
f"\n文件获取失败\n网络状态异常或服务器故障\n\n【错误信息】:{error}\n",
|
||||||
)
|
)
|
||||||
|
|
||||||
self.next_download_task()
|
self.next_download_task()
|
||||||
|
|
||||||
|
# 下载前比对已有文件哈希值
|
||||||
|
def pre_hash_compare(self, install_path, game_version, plugin_hash):
|
||||||
|
msg_box = self.hash_manager.hash_pop_window()
|
||||||
|
self.hash_manager.cfg_pre_hash_compare(
|
||||||
|
install_path, game_version, plugin_hash, self.installed_status
|
||||||
|
)
|
||||||
|
msg_box.close()
|
||||||
|
|
||||||
|
# 开始下载文件
|
||||||
def download_action(self):
|
def download_action(self):
|
||||||
install_paths = self.get_install_paths()
|
install_paths = self.get_install_paths()
|
||||||
for game_version, install_path in install_paths.items():
|
for game_version, install_path in install_paths.items():
|
||||||
self.pre_hash_compare(install_path, game_version, SRC_HASHES)
|
self.pre_hash_compare(install_path, game_version, PLUGIN_HASH)
|
||||||
if self.late_hash_compare(SRC_HASHES):
|
|
||||||
config = self.download_config()
|
|
||||||
if not config:
|
|
||||||
QMessageBox.critical(
|
|
||||||
self, f"错误 {APP_VERSION}", "\n网络状态异常或服务器故障,请重试\n"
|
|
||||||
)
|
|
||||||
return
|
|
||||||
for i in range(1, 5):
|
|
||||||
game_version = f"NEKOPARA Vol.{i}"
|
|
||||||
if self.installed_status[game_version] == False:
|
|
||||||
url = config[f"vol{i}"]
|
|
||||||
game_folder = os.path.join(
|
|
||||||
self.selected_folder, f"NEKOPARA Vol. {i}"
|
|
||||||
)
|
|
||||||
_7z_path = os.path.join(PLUGIN, f"vol.{i}.7z")
|
|
||||||
plugin_path = os.path.join(
|
|
||||||
PLUGIN,
|
|
||||||
f"vol.{i}",
|
|
||||||
[
|
|
||||||
"adultsonly.xp3",
|
|
||||||
"adultsonly.xp3",
|
|
||||||
"update00.int",
|
|
||||||
"vol4adult.xp3",
|
|
||||||
][i - 1],
|
|
||||||
)
|
|
||||||
self.download_queue.append(
|
|
||||||
(url, game_folder, game_version, _7z_path, plugin_path)
|
|
||||||
)
|
|
||||||
self.next_download_task()
|
|
||||||
|
|
||||||
def next_download_task(self):
|
config = self.get_download_url()
|
||||||
if not self.download_queue:
|
if not config:
|
||||||
self.show_result()
|
QMessageBox.critical(
|
||||||
|
self, f"错误 {APP_NAME}", "\n网络状态异常或服务器故障,请重试\n"
|
||||||
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
for i in range(1, 5):
|
||||||
|
game_version = f"NEKOPARA Vol.{i}"
|
||||||
|
if not self.installed_status[game_version]:
|
||||||
|
url = config[f"vol{i}"]
|
||||||
|
game_folder = os.path.join(self.selected_folder, f"NEKOPARA Vol. {i}")
|
||||||
|
_7z_path = os.path.join(PLUGIN, f"vol.{i}.7z")
|
||||||
|
plugin_path = os.path.join(
|
||||||
|
PLUGIN, GAME_INFO[game_version]["plugin_path"]
|
||||||
|
)
|
||||||
|
self.download_queue.append(
|
||||||
|
(url, game_folder, game_version, _7z_path, plugin_path)
|
||||||
|
)
|
||||||
|
|
||||||
|
self.next_download_task()
|
||||||
|
|
||||||
|
# 开始下载队列中的下一个任务
|
||||||
|
def next_download_task(self):
|
||||||
|
if not self.download_queue:
|
||||||
|
self.after_hash_compare(PLUGIN_HASH)
|
||||||
|
return
|
||||||
url, game_folder, game_version, _7z_path, plugin_path = (
|
url, game_folder, game_version, _7z_path, plugin_path = (
|
||||||
self.download_queue.popleft()
|
self.download_queue.popleft()
|
||||||
)
|
)
|
||||||
self.download_setting(url, game_folder, game_version, _7z_path, plugin_path)
|
self.download_setting(url, game_folder, game_version, _7z_path, plugin_path)
|
||||||
|
|
||||||
|
# 下载完成后比对哈希值
|
||||||
|
def after_hash_compare(self, plugin_hash):
|
||||||
|
msg_box = self.hash_manager.hash_pop_window()
|
||||||
|
result = self.hash_manager.cfg_after_hash_compare(
|
||||||
|
self.get_install_paths(), plugin_hash, self.installed_status
|
||||||
|
)
|
||||||
|
msg_box.close()
|
||||||
|
self.show_result()
|
||||||
|
return result
|
||||||
|
|
||||||
|
# 显示最终安装结果
|
||||||
def show_result(self):
|
def show_result(self):
|
||||||
installed_version = "\n".join(
|
installed_version = "\n".join(
|
||||||
[i for i in self.installed_status if self.installed_status[i]]
|
[i for i in self.installed_status if self.installed_status[i]]
|
||||||
@ -414,83 +532,66 @@ class MyWindow(QWidget, Ui_mainwin):
|
|||||||
failed_ver = "\n".join(
|
failed_ver = "\n".join(
|
||||||
[i for i in self.installed_status if not self.installed_status[i]]
|
[i for i in self.installed_status if not self.installed_status[i]]
|
||||||
)
|
)
|
||||||
|
|
||||||
QMessageBox.information(
|
QMessageBox.information(
|
||||||
self,
|
self,
|
||||||
f"完成 {APP_VERSION}",
|
f"完成 {APP_NAME}",
|
||||||
f"\n安装结果:\n"
|
f"\n安装结果:\n安装成功数:{len(installed_version.splitlines())} 安装失败数:{len(failed_ver.splitlines())}\n"
|
||||||
f"安装成功数:{len(installed_version.splitlines())} 安装失败数:{len(failed_ver.splitlines())}\n\n"
|
f"安装成功的版本:\n{installed_version}\n尚未持有或未使用本工具安装补丁的版本:\n{failed_ver}\n",
|
||||||
f"安装成功的版本:\n"
|
|
||||||
f"{installed_version}\n"
|
|
||||||
f"尚未持有的版本:\n"
|
|
||||||
f"{failed_ver}\n",
|
|
||||||
)
|
)
|
||||||
|
|
||||||
def shutdown_app(self):
|
# 关闭程序-窗口
|
||||||
msg_box = QMessageBox(self)
|
def closeEvent(self, event):
|
||||||
msg_box.setWindowTitle("退出程序")
|
self.shutdown_app(event)
|
||||||
msg_box.setText("\n是否确定退出?\n")
|
|
||||||
yes_button = msg_box.addButton("确定", QMessageBox.ButtonRole.AcceptRole)
|
|
||||||
no_button = msg_box.addButton("取消", QMessageBox.ButtonRole.RejectRole)
|
|
||||||
msg_box.setDefaultButton(no_button)
|
|
||||||
msg_box.exec()
|
|
||||||
|
|
||||||
if msg_box.clickedButton() == yes_button:
|
# 关闭程序-按钮
|
||||||
|
def shutdown_app(self, event=None):
|
||||||
|
reply = QMessageBox.question(
|
||||||
|
self,
|
||||||
|
"退出程序",
|
||||||
|
"\n是否确定退出?\n",
|
||||||
|
QMessageBox.Yes | QMessageBox.No,
|
||||||
|
QMessageBox.No,
|
||||||
|
)
|
||||||
|
|
||||||
|
if reply == QMessageBox.Yes:
|
||||||
if (
|
if (
|
||||||
self.current_download_thread
|
self.current_download_thread
|
||||||
and self.current_download_thread.isRunning()
|
and self.current_download_thread.isRunning()
|
||||||
):
|
):
|
||||||
QMessageBox.critical(
|
QMessageBox.critical(
|
||||||
self,
|
self,
|
||||||
f"错误 {APP_VERSION}",
|
f"错误 {APP_NAME}",
|
||||||
"\n当前有下载任务正在进行,完成后再试。\n",
|
"\n当前有下载任务正在进行,完成后再试\n",
|
||||||
)
|
)
|
||||||
|
if event:
|
||||||
|
event.ignore()
|
||||||
return
|
return
|
||||||
|
|
||||||
if os.path.exists(PLUGIN):
|
if os.path.exists(PLUGIN):
|
||||||
try:
|
for attempt in range(3):
|
||||||
shutil.rmtree(PLUGIN)
|
try:
|
||||||
except Exception as e:
|
shutil.rmtree(PLUGIN)
|
||||||
QMessageBox.critical(
|
break
|
||||||
self,
|
except Exception as e:
|
||||||
f"错误 {APP_VERSION}",
|
if attempt == 2:
|
||||||
f"\n清理缓存失败\n\n【错误信息】:{e}\n",
|
QMessageBox.critical(
|
||||||
)
|
self,
|
||||||
sys.exit()
|
f"错误 {APP_NAME}",
|
||||||
|
f"\n清理缓存失败\n\n【错误信息】:{e}\n",
|
||||||
|
)
|
||||||
class ProgressWindow(QtWidgets.QDialog):
|
if event:
|
||||||
|
event.accept()
|
||||||
def __init__(self, parent=None):
|
sys.exit(1)
|
||||||
super(ProgressWindow, self).__init__(parent)
|
if event:
|
||||||
self.setWindowTitle(f"下载进度 {APP_VERSION}")
|
event.accept()
|
||||||
self.resize(400, 100)
|
else:
|
||||||
self.progress_bar_max = 100
|
sys.exit(0)
|
||||||
self.setWindowFlags(self.windowFlags() & ~Qt.WindowCloseButtonHint)
|
else:
|
||||||
self.setWindowFlags(self.windowFlags() & ~Qt.WindowSystemMenuHint)
|
if event:
|
||||||
|
event.ignore()
|
||||||
layout = QVBoxLayout()
|
|
||||||
self.progress_bar = QProgressBar()
|
|
||||||
self.progress_bar.setValue(0)
|
|
||||||
self.label = QLabel("\n正在下载...\n")
|
|
||||||
layout.addWidget(self.label)
|
|
||||||
layout.addWidget(self.progress_bar)
|
|
||||||
self.setLayout(layout)
|
|
||||||
|
|
||||||
def setmaxvalue(self, value):
|
|
||||||
self.progress_bar_max = value
|
|
||||||
self.progress_bar.setMaximum(value)
|
|
||||||
|
|
||||||
def setprogressbarval(self, value):
|
|
||||||
self.progress_bar.setValue(value)
|
|
||||||
if value == self.progress_bar_max:
|
|
||||||
QtCore.QTimer.singleShot(2000, self.close)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
if not admin_status():
|
|
||||||
run_as_admin()
|
|
||||||
sys.exit()
|
|
||||||
app = QApplication([])
|
app = QApplication([])
|
||||||
window = MyWindow()
|
window = MyWindow()
|
||||||
window.show()
|
window.show()
|
||||||
|
Loading…
x
Reference in New Issue
Block a user