From 6d3fda50d35ac7e63d560b61edc9200874bdb2ec Mon Sep 17 00:00:00 2001 From: DLmaster361 Date: Sat, 12 Jul 2025 01:27:27 +0800 Subject: [PATCH 1/2] =?UTF-8?q?feat(security):=20=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E9=87=8D=E7=BD=AE=E7=AE=A1=E7=90=86=E5=AF=86=E9=92=A5=E5=8A=9F?= =?UTF-8?q?=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/core/config.py | 111 +++++++++++++++++++++------------------ app/services/security.py | 34 ++++++++---- app/ui/main_window.py | 2 +- app/ui/member_manager.py | 18 +++---- app/ui/setting.py | 74 ++++++++++++++++++++++++-- requirements.txt | 2 +- resources/version.json | 10 +++- 7 files changed, 173 insertions(+), 78 deletions(-) diff --git a/app/core/config.py b/app/core/config.py index 42749e6..1fb5cfa 100644 --- a/app/core/config.py +++ b/app/core/config.py @@ -706,7 +706,7 @@ class GeneralSubConfig(LQConfig): class AppConfig(GlobalConfig): - VERSION = "4.4.0.3" + VERSION = "4.4.0.4" stage_refreshed = Signal() PASSWORD_refreshed = Signal() @@ -1081,57 +1081,66 @@ class AppConfig(GlobalConfig): logger.info("数据文件版本更新:v1.6-->v1.7") if_streaming = True - for MaaConfig in (self.app_path / "config/MaaConfig").iterdir(): - if MaaConfig.is_dir(): - for user in (MaaConfig / "UserData").iterdir(): - if user.is_dir(): - if (user / "config.json").exists(): - with (user / "config.json").open( - encoding="utf-8" - ) as f: - user_config = json.load(f) - user_config["Info"]["Stage"] = user_config["Info"][ - "GameId" - ] - user_config["Info"]["StageMode"] = user_config[ - "Info" - ]["GameIdMode"] - user_config["Info"]["Stage_1"] = user_config[ - "Info" - ]["GameId_1"] - user_config["Info"]["Stage_2"] = user_config[ - "Info" - ]["GameId_2"] - user_config["Info"]["Stage_Remain"] = user_config[ - "Info" - ]["GameId_Remain"] - with (user / "config.json").open( - "w", encoding="utf-8" - ) as f: - json.dump( - user_config, f, ensure_ascii=False, indent=4 - ) - for MaaPlanConfig in (self.app_path / "config/MaaPlanConfig").iterdir(): - if ( - MaaPlanConfig.is_dir() - and (MaaPlanConfig / "config.json").exists() - ): - with (MaaPlanConfig / "config.json").open( - encoding="utf-8" - ) as f: - plan_config = json.load(f) + if (self.app_path / "config/MaaConfig").exists(): - for k in self.stage_dict.keys(): - plan_config[k]["Stage"] = plan_config[k]["GameId"] - plan_config[k]["Stage_1"] = plan_config[k]["GameId_1"] - plan_config[k]["Stage_2"] = plan_config[k]["GameId_2"] - plan_config[k]["Stage_Remain"] = plan_config[k][ - "GameId_Remain" - ] - with (MaaPlanConfig / "config.json").open( - "w", encoding="utf-8" - ) as f: - json.dump(plan_config, f, ensure_ascii=False, indent=4) + for MaaConfig in (self.app_path / "config/MaaConfig").iterdir(): + if MaaConfig.is_dir(): + for user in (MaaConfig / "UserData").iterdir(): + if user.is_dir(): + if (user / "config.json").exists(): + with (user / "config.json").open( + encoding="utf-8" + ) as f: + user_config = json.load(f) + user_config["Info"]["Stage"] = user_config[ + "Info" + ]["GameId"] + user_config["Info"]["StageMode"] = user_config[ + "Info" + ]["GameIdMode"] + user_config["Info"]["Stage_1"] = user_config[ + "Info" + ]["GameId_1"] + user_config["Info"]["Stage_2"] = user_config[ + "Info" + ]["GameId_2"] + user_config["Info"]["Stage_Remain"] = ( + user_config["Info"]["GameId_Remain"] + ) + with (user / "config.json").open( + "w", encoding="utf-8" + ) as f: + json.dump( + user_config, + f, + ensure_ascii=False, + indent=4, + ) + + if (self.app_path / "config/MaaPlanConfig").exists(): + for MaaPlanConfig in ( + self.app_path / "config/MaaPlanConfig" + ).iterdir(): + if ( + MaaPlanConfig.is_dir() + and (MaaPlanConfig / "config.json").exists() + ): + with (MaaPlanConfig / "config.json").open( + encoding="utf-8" + ) as f: + plan_config = json.load(f) + + for k in self.stage_dict.keys(): + plan_config[k]["Stage"] = plan_config[k]["GameId"] + plan_config[k]["Stage_1"] = plan_config[k]["GameId_1"] + plan_config[k]["Stage_2"] = plan_config[k]["GameId_2"] + plan_config[k]["Stage_Remain"] = plan_config[k][ + "GameId_Remain" + ] + with (MaaPlanConfig / "config.json").open( + "w", encoding="utf-8" + ) as f: + json.dump(plan_config, f, ensure_ascii=False, indent=4) cur.execute("DELETE FROM version WHERE v = ?", ("v1.6",)) cur.execute("INSERT INTO version VALUES(?)", ("v1.7",)) diff --git a/app/services/security.py b/app/services/security.py index dda8dc1..b7bcc1a 100644 --- a/app/services/security.py +++ b/app/services/security.py @@ -147,22 +147,36 @@ class CryptoHandler: for member in Config.member_dict.values(): # 使用旧管理密钥解密 - for user in member["UserData"].values(): - user["Password"] = self.AUTO_decryptor( - user["Config"].get(user["Config"].Info_Password), PASSWORD_old - ) + if member["Type"] == "Maa": + for user in member["UserData"].values(): + user["Password"] = self.AUTO_decryptor( + user["Config"].get(user["Config"].Info_Password), PASSWORD_old + ) self.get_PASSWORD(PASSWORD_new) for member in Config.member_dict.values(): # 使用新管理密钥重新加密 - for user in member["UserData"].values(): - user["Config"].set( - user["Config"].Info_Password, self.AUTO_encryptor(user["Password"]) - ) - user["Password"] = None - del user["Password"] + if member["Type"] == "Maa": + for user in member["UserData"].values(): + user["Config"].set( + user["Config"].Info_Password, + self.AUTO_encryptor(user["Password"]), + ) + user["Password"] = None + del user["Password"] + + def reset_PASSWORD(self, PASSWORD_new: str) -> None: + """重置管理密钥""" + + self.get_PASSWORD(PASSWORD_new) + + for member in Config.member_dict.values(): + + if member["Type"] == "Maa": + for user in member["UserData"].values(): + user["Config"].set(user["Config"].Info_Password, "") def win_encryptor( self, note: str, description: str = None, entropy: bytes = None diff --git a/app/ui/main_window.py b/app/ui/main_window.py index f95fce0..595c751 100644 --- a/app/ui/main_window.py +++ b/app/ui/main_window.py @@ -469,7 +469,7 @@ class AUTO_MAA(MSFluentWindow): logger.warning("启动主任务失败:未找到有效的主任务配置文件") MainInfoBar.push_info_bar( - "warning", "启动主任务失败", "“调度队列_1”与“脚本_1”均不存在", -1 + "warning", "启动主任务失败", "「调度队列_1」与「脚本_1」均不存在", -1 ) def __currentChanged(self, index: int) -> None: diff --git a/app/ui/member_manager.py b/app/ui/member_manager.py index 62a6e61..406c883 100644 --- a/app/ui/member_manager.py +++ b/app/ui/member_manager.py @@ -804,7 +804,7 @@ class MemberManager(QWidget): self.card_TaskTransitionMethod = ComboBoxSettingCard( icon=FluentIcon.PAGE_RIGHT, title="任务切换方式", - content="相邻两个任务间的切换方式,使用“详细”配置的用户固定为“重启模拟器”", + content="相邻两个任务间的切换方式,使用「详细」配置的用户固定为「重启模拟器」", texts=["直接切换账号", "重启明日方舟", "重启模拟器"], qconfig=self.config, configItem=self.config.RunSet_TaskTransitionMethod, @@ -813,7 +813,7 @@ class MemberManager(QWidget): self.card_ProxyTimesLimit = SpinBoxSettingCard( icon=FluentIcon.PAGE_RIGHT, title="用户单日代理次数上限", - content="当用户本日代理成功次数达到该阈值时跳过代理,阈值为“0”时视为无代理次数上限", + content="当用户本日代理成功次数达到该阈值时跳过代理,阈值为「0」时视为无代理次数上限", range=(0, 1024), qconfig=self.config, configItem=self.config.RunSet_ProxyTimesLimit, @@ -2187,7 +2187,7 @@ class MemberManager(QWidget): self.card_ServerChanChannel = LineEditSettingCard( icon=FluentIcon.PAGE_RIGHT, title="用户ServerChanChannel代码", - content="留空则默认,多个请使用“|”隔开", + content="留空则默认,多个请使用「|」隔开", text="请输入Channel代码,仅SCT生效", qconfig=self.config, configItem=self.config.Notify_ServerChanChannel, @@ -2196,7 +2196,7 @@ class MemberManager(QWidget): self.card_ServerChanTag = LineEditSettingCard( icon=FluentIcon.PAGE_RIGHT, title="用户Tag内容", - content="留空则默认,多个请使用“|”隔开", + content="留空则默认,多个请使用「|」隔开", text="请输入加入推送的Tag,仅SC3生效", qconfig=self.config, configItem=self.config.Notify_ServerChanTag, @@ -2404,7 +2404,7 @@ class MemberManager(QWidget): self.card_SuccessLog = LineEditSettingCard( icon=FluentIcon.PAGE_RIGHT, title="脚本成功日志", - content="任务成功完成时出现的日志,多条请使用“|”隔开", + content="任务成功完成时出现的日志,多条请使用「|」隔开", text="请输入脚本成功日志内容", qconfig=self.config, configItem=self.config.Script_SuccessLog, @@ -2413,7 +2413,7 @@ class MemberManager(QWidget): self.card_ErrorLog = LineEditSettingCard( icon=FluentIcon.PAGE_RIGHT, title="脚本异常日志 - [必填]", - content="脚本运行异常时的日志内容,多条请使用“|”隔开", + content="脚本运行异常时的日志内容,多条请使用「|」隔开", text="请输入脚本异常日志内容", qconfig=self.config, configItem=self.config.Script_ErrorLog, @@ -2580,7 +2580,7 @@ class MemberManager(QWidget): self.card_ProxyTimesLimit = SpinBoxSettingCard( icon=FluentIcon.PAGE_RIGHT, title="子配置单日代理次数上限", - content="当子配置本日代理成功次数达到该阈值时跳过代理,阈值为“0”时视为无代理次数上限", + content="当子配置本日代理成功次数达到该阈值时跳过代理,阈值为「0」时视为无代理次数上限", range=(0, 1024), qconfig=self.config, configItem=self.config.Run_ProxyTimesLimit, @@ -3339,7 +3339,7 @@ class MemberManager(QWidget): self.card_ServerChanChannel = LineEditSettingCard( icon=FluentIcon.PAGE_RIGHT, title="用户ServerChanChannel代码", - content="留空则默认,多个请使用“|”隔开", + content="留空则默认,多个请使用「|」隔开", text="请输入Channel代码,仅SCT生效", qconfig=self.config, configItem=self.config.Notify_ServerChanChannel, @@ -3348,7 +3348,7 @@ class MemberManager(QWidget): self.card_ServerChanTag = LineEditSettingCard( icon=FluentIcon.PAGE_RIGHT, title="用户Tag内容", - content="留空则默认,多个请使用“|”隔开", + content="留空则默认,多个请使用「|」隔开", text="请输入加入推送的Tag,仅SC3生效", qconfig=self.config, configItem=self.config.Notify_ServerChanTag, diff --git a/app/ui/setting.py b/app/ui/setting.py index b1b0808..68d049e 100644 --- a/app/ui/setting.py +++ b/app/ui/setting.py @@ -86,6 +86,7 @@ class Setting(QWidget): ) self.start.card_IfSelfStart.checkedChanged.connect(System.set_SelfStart) self.security.card_changePASSWORD.clicked.connect(self.change_PASSWORD) + self.security.card_resetPASSWORD.clicked.connect(self.reset_PASSWORD) self.updater.card_CheckUpdate.clicked.connect( lambda: self.check_update(if_show=True) ) @@ -119,7 +120,7 @@ class Setting(QWidget): choice = MessageBox( "授权声明", - "开启“托管bilibili游戏隐私政策”功能,即代表您已完整阅读并同意《哔哩哔哩弹幕网用户使用协议》、《哔哩哔哩隐私政策》和《哔哩哔哩游戏中心用户协议》,并授权AUTO_MAA在其认定需要时以其认定合适的方法替您处理相关弹窗\n\n是否同意授权?", + "开启「托管bilibili游戏隐私政策」功能,即代表您已完整阅读并同意《哔哩哔哩弹幕网用户使用协议》、《哔哩哔哩隐私政策》和《哔哩哔哩游戏中心用户协议》,并授权AUTO_MAA在其认定需要时以其认定合适的方法替您处理相关弹窗\n\n是否同意授权?", self.window(), ) if choice.exec(): @@ -147,7 +148,7 @@ class Setting(QWidget): choice = MessageBox( "风险声明", - "开启“跳过MuMu启动广告”功能,即代表您已安装MuMu模拟器-12且允许AUTO_MAA以其认定合适的方法屏蔽MuMu启动广告,并接受此操作带来的风险\n\n此功能即时生效,是否仍要开启此功能?", + "开启「跳过MuMu启动广告」功能,即代表您已安装MuMu模拟器-12且允许AUTO_MAA以其认定合适的方法屏蔽MuMu启动广告,并接受此操作带来的风险\n\n此功能即时生效,是否仍要开启此功能?", self.window(), ) if choice.exec(): @@ -259,6 +260,61 @@ class Setting(QWidget): if choice.exec(): break + def reset_PASSWORD(self) -> None: + """重置管理密钥""" + + choice = MessageBox( + "确认", + "重置管理密钥将清空所有使用管理密钥加密的数据,您确认要重置管理密钥吗?", + self.window(), + ) + if choice.exec(): + choice = LineEditMessageBox( + self.window(), + "请输入文本提示框内的验证信息", + "AUTO_MAA绝赞DeBug中!", + "明文", + ) + + if choice.exec() and choice.input.text() in [ + "AUTO_MAA绝赞DeBug中!", + "AUTO_MAA绝赞DeBug中!", + ]: + + # 获取新的管理密钥 + while True: + + choice = LineEditMessageBox( + self.window(), "请输入新的管理密钥", "新管理密钥", "密码" + ) + if choice.exec() and choice.input.text() != "": + + # 重置管理密钥 + Crypto.reset_PASSWORD(choice.input.text()) + MainInfoBar.push_info_bar( + "success", "操作成功", "管理密钥重置成功", 3000 + ) + break + + else: + + choice = MessageBox( + "确认", + "您没有输入新的管理密钥,是否取消修改管理密钥?", + self.window(), + ) + if choice.exec(): + break + + else: + + MainInfoBar.push_info_bar( + "info", + "验证未通过", + "请输入「AUTO_MAA绝赞DeBug中!」后单击确认键", + 3000, + ) + def check_update(self, if_show: bool = False, if_first: bool = False) -> None: """检查版本更新,调起文件下载进程""" @@ -670,7 +726,7 @@ class FunctionSettingCard(HeaderCardWidget): self.card_BossKey = LineEditSettingCard( icon=FluentIcon.PAGE_RIGHT, title="模拟器老板键", - content="请输入对应的模拟器老板键,请直接输入文字,多个键位之间请用“+”隔开。如:“Alt+Q”", + content="请输入对应的模拟器老板键,请直接输入文字,多个键位之间请用「+」隔开。如:「Alt+Q」", text="请以文字形式输入模拟器老板快捷键", qconfig=Config, configItem=Config.function_BossKey, @@ -981,7 +1037,7 @@ class NotifySettingCard(HeaderCardWidget): self.card_ServerChanChannel = LineEditSettingCard( icon=FluentIcon.PAGE_RIGHT, title="ServerChanChannel代码", - content="可以留空,留空则默认。可以多个,请使用“|”隔开", + content="可以留空,留空则默认。可以多个,请使用「|」隔开", text="请输入需要推送的Channel代码(SCT生效)", qconfig=Config, configItem=Config.notify_ServerChanChannel, @@ -990,7 +1046,7 @@ class NotifySettingCard(HeaderCardWidget): self.card_ServerChanTag = LineEditSettingCard( icon=FluentIcon.PAGE_RIGHT, title="Tag内容", - content="可以留空,留空则默认。可以多个,请使用“|”隔开", + content="可以留空,留空则默认。可以多个,请使用「|」隔开", text="请输入加入推送的Tag(SC3生效)", qconfig=Config, configItem=Config.notify_ServerChanTag, @@ -1056,9 +1112,17 @@ class SecuritySettingCard(HeaderCardWidget): content="修改用于解密用户密码的管理密钥", parent=self, ) + self.card_resetPASSWORD = PushSettingCard( + text="重置", + icon=FluentIcon.VPN, + title="重置管理密钥", + content="重置用于解密用户密码的管理密钥", + parent=self, + ) Layout = QVBoxLayout() Layout.addWidget(self.card_changePASSWORD) + Layout.addWidget(self.card_resetPASSWORD) self.viewLayout.addLayout(Layout) diff --git a/requirements.txt b/requirements.txt index f2d6ee6..9448234 100644 --- a/requirements.txt +++ b/requirements.txt @@ -10,5 +10,5 @@ certifi==2025.4.26 requests==2.32.4 markdown==3.8.2 Jinja2==3.1.6 -nuitka==2.7.11 +nuitka==2.7.12 pillow==11.3.0 \ No newline at end of file diff --git a/resources/version.json b/resources/version.json index 4c7f7b3..211e42d 100644 --- a/resources/version.json +++ b/resources/version.json @@ -1,6 +1,14 @@ { - "main_version": "4.4.0.3", + "main_version": "4.4.0.4", "version_info": { + "4.4.0.4": { + "新增功能": [ + "添加重置管理密钥功能" + ], + "修复BUG": [ + "修复无计划表时数据系统无法正常升级到v1.7的问题" + ] + }, "4.4.0.3": { "修复BUG": [ "适配 MAA 备选关卡字段修改", From 4efbafc174504bd2fbd5b22075e3b5429406691b Mon Sep 17 00:00:00 2001 From: DLmaster361 Date: Sat, 12 Jul 2025 16:08:07 +0800 Subject: [PATCH 2/2] =?UTF-8?q?fix(system):=20=E4=BF=AE=E5=A4=8D=E5=BC=80?= =?UTF-8?q?=E6=9C=BA=E8=87=AA=E5=90=AF=E7=9B=B8=E5=85=B3=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/core/config.py | 4 +- app/services/system.py | 126 ++++++++++++++++++++++++++++++----------- resources/version.json | 7 ++- 3 files changed, 100 insertions(+), 37 deletions(-) diff --git a/app/core/config.py b/app/core/config.py index 1fb5cfa..3642ff6 100644 --- a/app/core/config.py +++ b/app/core/config.py @@ -706,7 +706,7 @@ class GeneralSubConfig(LQConfig): class AppConfig(GlobalConfig): - VERSION = "4.4.0.4" + VERSION = "4.4.0.5" stage_refreshed = Signal() PASSWORD_refreshed = Signal() @@ -717,7 +717,7 @@ class AppConfig(GlobalConfig): super().__init__() self.app_path = Path(sys.argv[0]).resolve().parent # 获取软件根目录 - self.app_path_sys = str(Path(sys.argv[0]).resolve()) # 获取软件自身的路径 + self.app_path_sys = Path(sys.argv[0]).resolve() # 获取软件自身的路径 self.log_path = self.app_path / "debug/AUTO_MAA.log" self.database_path = self.app_path / "data/data.db" diff --git a/app/services/system.py b/app/services/system.py index 4600c59..e856061 100644 --- a/app/services/system.py +++ b/app/services/system.py @@ -31,9 +31,11 @@ import sys import ctypes import win32gui import win32process -import winreg import psutil import subprocess +import tempfile +import getpass +from datetime import datetime from pathlib import Path from app.core import Config @@ -61,44 +63,103 @@ class _SystemHandler: # 恢复系统电源状态 ctypes.windll.kernel32.SetThreadExecutionState(self.ES_CONTINUOUS) - def set_SelfStart(self) -> bool: + def set_SelfStart(self) -> None: """同步开机自启""" if Config.get(Config.start_IfSelfStart) and not self.is_startup(): + # 创建任务计划 try: - # 创建任务计划 - result = subprocess.run( - [ - "schtasks", - "/create", - "/tn", - "AUTO_MAA_AutoStart", - "/tr", - Config.app_path_sys, - "/sc", - "onlogon", - "/rl", - "highest", # 以最高权限运行 - "/f", # 强制创建(覆盖现有任务) - ], - creationflags=subprocess.CREATE_NO_WINDOW, - stdin=subprocess.DEVNULL, - capture_output=True, - text=True, - ) + # 获取当前用户和时间 + current_user = getpass.getuser() + current_time = datetime.now().strftime("%Y-%m-%dT%H:%M:%S") - if result.returncode == 0: - logger.info(f"任务计划程序自启动已创建: {Config.app_path_sys}") - return True - else: - logger.error(f"创建任务计划失败: {result.stderr}") - return False + # XML 模板 + xml_content = f""" + + + {current_time} + {current_user} + AUTO_MAA自启动服务 + \\AUTO_MAA_AutoStart + + + + {current_time} + true + + + + + InteractiveToken + HighestAvailable + + + + IgnoreNew + false + false + false + true + false + + false + false + + true + true + false + false + false + PT0S + 7 + + + + "{Config.app_path_sys}" + + + """ + + # 创建临时 XML 文件并执行 + with tempfile.NamedTemporaryFile( + mode="w", suffix=".xml", delete=False, encoding="utf-16" + ) as f: + f.write(xml_content) + xml_file = f.name + + try: + result = subprocess.run( + [ + "schtasks", + "/create", + "/tn", + "AUTO_MAA_AutoStart", + "/xml", + xml_file, + "/f", + ], + creationflags=subprocess.CREATE_NO_WINDOW, + stdin=subprocess.DEVNULL, + capture_output=True, + text=True, + ) + + if result.returncode == 0: + logger.info(f"任务计划程序自启动已创建: {Config.app_path_sys}") + else: + logger.error(f"创建任务计划失败: {result.stderr}") + + finally: + # 删除临时文件 + try: + Path(xml_file).unlink() + except: + pass except Exception as e: - logger.error(f"设置任务计划程序自启动失败: {e}") - return False + logger.exception(f"设置任务计划程序自启动失败: {e}") elif not Config.get(Config.start_IfSelfStart) and self.is_startup(): @@ -114,14 +175,11 @@ class _SystemHandler: if result.returncode == 0: logger.info("任务计划程序自启动已删除") - return True else: logger.error(f"删除任务计划失败: {result.stderr}") - return False except Exception as e: - logger.error(f"删除任务计划程序自启动失败: {e}") - return False + logger.exception(f"删除任务计划程序自启动失败: {e}") def set_power(self, mode) -> None: diff --git a/resources/version.json b/resources/version.json index 211e42d..3d58ed5 100644 --- a/resources/version.json +++ b/resources/version.json @@ -1,6 +1,11 @@ { - "main_version": "4.4.0.4", + "main_version": "4.4.0.5", "version_info": { + "4.4.0.5": { + "修复BUG": [ + "修复开机自启相关功能" + ] + }, "4.4.0.4": { "新增功能": [ "添加重置管理密钥功能"