diff --git a/.gitignore b/.gitignore index 684938c..ebf719b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ tools/ test.spec jsoneditor.spec +BAAH_UPDATE.spec BAAH.spec compact_log.txt /*.png @@ -10,6 +11,8 @@ compact_log.txt /BAAH.exe /BAAH_GUI.exe /platform-tools-latest.zip +/DATA/update/ +/BAAH*_update.zip # Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] diff --git a/BAAH.py b/BAAH.py index bbafd3b..f75fcfe 100644 --- a/BAAH.py +++ b/BAAH.py @@ -85,7 +85,8 @@ def BAAH_start_emulator(): try: # 以列表形式传命令行参数 logging.info({"zh_CN": "启动模拟器", "en_US": "Starting the emulator"}) - emulator_process = subprocess_run(config.userconfigdict['TARGET_EMULATOR_PATH'].split(" "), isasync=True) + # 不能用shell,否则得到的是shell的pid + emulator_process = subprocess_run(config.userconfigdict['TARGET_EMULATOR_PATH'], isasync=True) logging.info({"zh_CN": "模拟器pid: " + str(emulator_process.pid), "en_US": "The emulator pid: " + str(emulator_process.pid)}) time.sleep(5) diff --git a/BAAH_CONFIGS/example.json b/BAAH_CONFIGS/example.json index 559ee04..24d7c6d 100644 --- a/BAAH_CONFIGS/example.json +++ b/BAAH_CONFIGS/example.json @@ -255,5 +255,8 @@ "POST_COMMAND": "", "SHOP_NORMAL_SWITCH": true, "SHOP_CONTEST_SWITCH": true, - "USER_DEF_TASKS": "" + "USER_DEF_TASKS": "", + "PUSH_NORMAL_USE_SIMPLE": false, + "PUSH_HARD_USE_SIMPLE": false, + "CRAFT_TIMES": 1 } \ No newline at end of file diff --git a/DATA/assets/kayoko.ico b/DATA/assets/kayoko.ico new file mode 100644 index 0000000..80845bc Binary files /dev/null and b/DATA/assets/kayoko.ico differ diff --git a/DATA/i18n/en_US.json b/DATA/i18n/en_US.json index 94fa8d4..c883898 100644 --- a/DATA/i18n/en_US.json +++ b/DATA/i18n/en_US.json @@ -21,6 +21,7 @@ "push_main_story":"Explore Eposide", "task_assault": "Assault", "task_user_def_task": "User Defined Task", + "task_craft":"Craft", "setting_emulator": "Emulator Configuration", "setting_server": "Server Configuration", @@ -47,6 +48,7 @@ "button_save_and_run_gui":"Save and execute (GUI, experimental)", "button_save_and_run_terminal":"Save and execute (Terminal)", "button_check_version": "Check for Updates and Download(Make sure BAAH is not running)", + "button_update_advance":"One Click to update to the latest version (experimental)", "button_select_all": "Select All", "button_select_none": "Deselect All", "button_enable": "Enable", @@ -132,6 +134,9 @@ "config_buy_ap_add_times":"Number of times to buy AP", "config_pre_command":"Command before task start, leave blank to ignore", "config_post_command":"Command after task end, leave blank to ignore", + "config_use_simple_explore":"Use simple explore", + "config_craft_desc":"Currently, the craft task only supports one node and does not support the priority of specified items. After the Chinese server implements one-click manufacturing, it will be supported through one-click manufacturing", + "config_craft_max_times":"Maximum times of craft", "config_server_jp": "Japanese Server", "config_server_cn": "Chinese Official Server", diff --git a/DATA/i18n/jp_JP.json b/DATA/i18n/jp_JP.json index 0d1cad1..e2138d1 100644 --- a/DATA/i18n/jp_JP.json +++ b/DATA/i18n/jp_JP.json @@ -21,6 +21,7 @@ "push_main_story": "メインストーリーの進行", "task_assault": "総力戦", "task_user_def_task": "ユーザー定義タスク", + "task_craft":"クラフト", "setting_emulator": "エミュレータ設定", "setting_server": "サーバー設定", @@ -47,6 +48,7 @@ "button_save_and_run_gui": "保存して実行(GUI、実験的)", "button_save_and_run_terminal": "保存して実行(ターミナル)", "button_check_version": "更新を確認してダウンロードする(BAAHが実行されていないことを確認してください)", + "button_update_advance":"最新バージョンに一括更新(実験的)", "button_select_all": "すべて選択", "button_select_none": "選択解除", "button_enable": "有効にする", @@ -132,6 +134,9 @@ "config_buy_ap_add_times":"APを購入する回数", "config_pre_command":"タスク開始前に実行するコマンド", "config_post_command":"タスク終了後に実行するコマンド", + "config_use_simple_explore":"簡易攻略を使用する", + "config_craft_desc":"現在、クラフトタスクは1つのノードのみをサポートし、指定されたアイテムの優先順位をサポートしていません。国内サーバーがワンクリック製造を実装した後、ワンクリック製造を使用してサポートする予定です", + "config_craft_max_times":"最大製造回数", "config_server_jp": "日本サーバー", "config_server_cn": "中国公式サーバー", diff --git a/DATA/i18n/zh_CN.json b/DATA/i18n/zh_CN.json index 3dd7002..854dba6 100644 --- a/DATA/i18n/zh_CN.json +++ b/DATA/i18n/zh_CN.json @@ -21,6 +21,7 @@ "push_main_story":"推主线剧情", "task_assault":"总力战", "task_user_def_task": "自定义任务", + "task_craft":"制造", "setting_emulator":"模拟器配置", "setting_server":"服务器配置", @@ -47,6 +48,7 @@ "button_save_and_run_gui":"保存并执行(GUI,实验性功能)", "button_save_and_run_terminal":"保存并执行(终端)", "button_check_version":"检查更新并下载(确保BAAH不在运行),下载完成后请关闭BAAH手动解压更新补丁", + "button_update_advance":"一键更新到最新版本(实验性功能)", "button_select_all":"全选", "button_select_none":"全不选", "button_enable":"启用", @@ -132,6 +134,9 @@ "config_buy_ap_add_times":"购买体力组数", "config_pre_command":"任务开始前执行命令, 留空忽略", "config_post_command":"任务结束后执行命令, 留空忽略", + "config_use_simple_explore":"使用简易攻略", + "config_craft_desc":"制造任务目前只能一个节点,且不支持指定物品的优先级,等国服实装一键制造后会通过一键制造来支持", + "config_craft_max_times":"尝试制造次数", "config_server_jp":"日服", diff --git a/gui/__init__.py b/gui/__init__.py index 34f7d35..634b2da 100644 --- a/gui/__init__.py +++ b/gui/__init__.py @@ -5,6 +5,7 @@ import os from gui.components.run_baah_in_gui import run_baah_task from gui.pages.Setting_BAAH import set_BAAH +from gui.pages.Setting_Craft import set_craft from gui.pages.Setting_cafe import set_cafe from gui.pages.Setting_emulator import set_emulator from gui.pages.Setting_event import set_event @@ -43,6 +44,7 @@ def show_GUI(load_jsonname, config, shared_softwareconfig): "咖啡馆只摸头":config.get_text("task_cafe_deprecated"), # 为了兼容以前的配置里的咖啡馆只摸头,这里只改显示名 "课程表":config.get_text("task_timetable"), "社团":config.get_text("task_club"), + "制造":config.get_text("task_craft"), "商店":config.get_text("task_shop"), "购买AP":config.get_text("task_buy_ap"), "悬赏通缉":config.get_text("task_wanted"), @@ -77,6 +79,7 @@ def show_GUI(load_jsonname, config, shared_softwareconfig): # ui.link(config.get_text("setting_next_config"), '#NEXT_CONFIG') ui.link(config.get_text("task_cafe"), '#CAFE') ui.link(config.get_text("task_timetable"), '#TIME_TABLE') + ui.link(config.get_text("task_craft"), '#CRAFT') ui.link(config.get_text("task_shop"), '#SHOP_NORMAL') ui.link(config.get_text("task_buy_ap"), '#BUY_AP') ui.link(config.get_text("task_wanted"), '#WANTED') @@ -114,6 +117,9 @@ def show_GUI(load_jsonname, config, shared_softwareconfig): # 课程表 set_timetable(config) + + # 制造 + set_craft(config) # 商店 set_shop(config) diff --git a/gui/pages/Setting_BAAH.py b/gui/pages/Setting_BAAH.py index e198510..a9d0050 100644 --- a/gui/pages/Setting_BAAH.py +++ b/gui/pages/Setting_BAAH.py @@ -1,4 +1,5 @@ -from nicegui import ui +import subprocess +from nicegui import ui, app from gui.components.check_update import get_newest_version def set_BAAH(config, shared_softwareconfig): @@ -22,8 +23,18 @@ def select_language(value): ui.label(config.get_text("BAAH_get_version")) + # 下载更新包 ui.button(config.get_text("button_check_version"), on_click=lambda e, c=config:get_newest_version(c)) + # 一键更新,唤起更新程序,结束gui进程 + def update_advance(): + try: + subprocess.Popen(["BAAH_UPDATE.exe"], creationflags=subprocess.CREATE_NEW_CONSOLE, close_fds=True) + app.shutdown() + except Exception as e: + ui.notify(f"Failed to start BAAH_UPDATE.exe: {e}", type="warning") + ui.button(config.get_text("button_update_advance"), on_click=update_advance) + web_url = { "github": "https://github.com/sanmusen214/BAAH", "bilibili":"https://space.bilibili.com/7331920" diff --git a/gui/pages/Setting_Craft.py b/gui/pages/Setting_Craft.py new file mode 100644 index 0000000..444ac28 --- /dev/null +++ b/gui/pages/Setting_Craft.py @@ -0,0 +1,17 @@ +from nicegui import ui + +def set_craft(config): + with ui.row(): + ui.link_target("CRAFT") + ui.label(config.get_text("task_craft")).style('font-size: x-large') + + + ui.label(config.get_text("config_craft_desc")) + + ui.number(config.get_text("config_craft_max_times"), + step=1, + precision=0, + min=1, + max=3 + ).bind_value(config.userconfigdict, 'CRAFT_TIMES', forward=lambda v: int(v), backward=lambda v:int(v)).style('width: 400px') + \ No newline at end of file diff --git a/gui/pages/Setting_hard.py b/gui/pages/Setting_hard.py index fed2c1c..f342c92 100644 --- a/gui/pages/Setting_hard.py +++ b/gui/pages/Setting_hard.py @@ -29,6 +29,7 @@ def set_hard(config): ui.label(config.get_text("config_explore_attention")) with ui.card(): + ui.checkbox(config.get_text("config_use_simple_explore")).bind_value(config.userconfigdict, "PUSH_HARD_USE_SIMPLE") ui.checkbox(config.get_text("config_rainbow_teams_desc")).bind_value(config.userconfigdict, "EXPLORE_RAINBOW_TEAMS") ui.number(config.get_text("config_push_hard_desc"), min=1, precision=0, step=1).bind_value(config.userconfigdict, "PUSH_HARD_QUEST", forward=lambda x: int(x)).style("width: 300px") ui.number(config.get_text("config_level"), min=1, precision=0, step=1).bind_value(config.userconfigdict, "PUSH_HARD_QUEST_LEVEL", forward=lambda x:int(x)).style("width: 300px") diff --git a/gui/pages/Setting_normal.py b/gui/pages/Setting_normal.py index c3495b3..0dadc41 100644 --- a/gui/pages/Setting_normal.py +++ b/gui/pages/Setting_normal.py @@ -29,6 +29,7 @@ def set_normal(config): ui.label(config.get_text("config_explore_attention")) with ui.card(): + ui.checkbox(config.get_text("config_use_simple_explore")).bind_value(config.userconfigdict, "PUSH_NORMAL_USE_SIMPLE") ui.checkbox(config.get_text("config_rainbow_teams_desc")).bind_value(config.userconfigdict, "EXPLORE_RAINBOW_TEAMS") ui.number(config.get_text("config_push_normal_desc"), min=4, precision=0, step=1).bind_value(config.userconfigdict, "PUSH_NORMAL_QUEST", forward=lambda x: int(x)).style("width: 300px") ui.number(config.get_text("config_level"), min=1, precision=0, step=1).bind_value(config.userconfigdict, "PUSH_NORMAL_QUEST_LEVEL", forward=lambda x:int(x)).style("width: 300px") diff --git a/gui/pages/Setting_server.py b/gui/pages/Setting_server.py index 30c2fdf..2815c2a 100644 --- a/gui/pages/Setting_server.py +++ b/gui/pages/Setting_server.py @@ -14,7 +14,7 @@ def set_server(config): "CN_BILI":config.get_text("config_server_cn_b")}, value=config.userconfigdict['SERVER_TYPE'], on_change=lambda a:set_server_info(a.value)).props('inline') - fanhexie = ui.checkbox("如果游戏开了反和谐请勾选此项").bind_value(config.userconfigdict, "FANHEXIE").bind_visibility_from(config.userconfigdict, "SERVER_TYPE", lambda x: x in ["CN", "CN_BILI"]) + fanhexie = ui.checkbox('如果游戏开了反和谐请勾选此项(手机显示“momotalk”勾选,手机显示“桃信”不要勾)').bind_value(config.userconfigdict, "FANHEXIE").bind_visibility_from(config.userconfigdict, "SERVER_TYPE", lambda x: x in ["CN", "CN_BILI"]) def set_server_info(servername): config.userconfigdict['SERVER_TYPE'] = servername diff --git a/modules/AllTask/InCraft/InCraft.py b/modules/AllTask/InCraft/InCraft.py index 9f510e0..95435ad 100644 --- a/modules/AllTask/InCraft/InCraft.py +++ b/modules/AllTask/InCraft/InCraft.py @@ -59,6 +59,8 @@ def dealing_with_craft(self): CN: "点击制造失败", EN: "Failed to click craft button" })) + # 没制造材料,跳过之后的制造任务 + self.status=Task.STATUS_SKIP return # 点击开放按钮 click(self.BUTTON_CRAFT) @@ -114,6 +116,7 @@ def on_run(self) -> None: # 获取当前制造状态 status_list = self.getNowCraftStatus() logging.info(status_list) + now_craft_number = 0 for ind, item in enumerate(status_list): # 处理每一个位置 if item == self.STATUS_CRAFT_DOING: @@ -134,6 +137,19 @@ def on_run(self) -> None: lambda: not Page.is_page(PageName.PAGE_CRAFT) ) self.dealing_with_craft() + if self.status == Task.STATUS_SKIP: + logging.warn(istr({ + CN: "制造材料不足,跳过之后的制造任务", + EN: "Insufficient crafting materials, skip subsequent crafting tasks" + })) + return + now_craft_number += 1 + if now_craft_number >= config.userconfigdict["CRAFT_TIMES"]: + logging.info(istr({ + CN: f"制造次数已达上限 {config.userconfigdict['CRAFT_TIMES']}", + EN: f"The number of crafting has reached the upper limit {config.userconfigdict['CRAFT_TIMES']}" + })) + return diff --git a/modules/AllTask/InEvent/InEvent.py b/modules/AllTask/InEvent/InEvent.py index 8871f92..859cb88 100644 --- a/modules/AllTask/InEvent/InEvent.py +++ b/modules/AllTask/InEvent/InEvent.py @@ -225,8 +225,8 @@ def on_run(self) -> None: logging.info({"zh_CN": "成功进入Event页面", "en_US": "Successfully entered the Event page"}) today = time.localtime().tm_mday - # 检测并跳过剧情 - if config.userconfigdict["AUTO_EVENT_STORY_PUSH"]: + # 检测并跳过剧情,如果已经进入过活动一次了,就不用再跳过剧情了 + if config.userconfigdict["AUTO_EVENT_STORY_PUSH"] and not config.sessiondict["HAS_ENTER_EVENT"]: EventStory().run() # 推图任务,如果已经进入过活动一次了,就不用再推图了 if config.userconfigdict["AUTO_PUSH_EVENT_QUEST"] and not config.sessiondict["HAS_ENTER_EVENT"]: diff --git a/modules/AllTask/InQuest/PushQuest.py b/modules/AllTask/InQuest/PushQuest.py index a5a39f9..596d68a 100644 --- a/modules/AllTask/InQuest/PushQuest.py +++ b/modules/AllTask/InQuest/PushQuest.py @@ -81,7 +81,6 @@ def on_run(self) -> None: # 此时应该看到扫荡弹窗 # 判断是否有简易攻略tab - has_easy_tab = quest_has_easy_tab() # 向右翻self.level_ind次 logging.info({"zh_CN": "尝试翻到关卡 {}".format(self.level_ind + 1), "en_US": "Try flipping to level {}".format(self.level_ind + 1)}) @@ -92,6 +91,8 @@ def on_run(self) -> None: if match_pixel(Page.MAGICPOINT, Page.COLOR_WHITE): logging.info({"zh_CN": "关卡弹窗消失,结束此任务","en_US": "Level popup disappears, end this task"}) return + # 此时判断是否有简易攻略tab + has_easy_tab = quest_has_easy_tab() # 当前关卡就是这次需要推图的关卡 # 国服弹窗往右偏移了50 offsetx = 0 @@ -144,21 +145,25 @@ def on_run(self) -> None: # ===========正式开始推图=================== # 看到弹窗,ocr是否有S ocr_s = ocr_area((327 + offsetx, 257 + offsety), (370 + offsetx, 288 + offsety)) - # 如果有简易攻略 + # 如果有简易攻略tab,有简易攻略tab存在,这个图肯定默认是格子图 if has_easy_tab: - if self.is_normal: - logging.info({"zh_CN": "使用简易攻略", "en_US": "Easy to use guide"}) + if (self.is_normal and config.userconfigdict["PUSH_NORMAL_USE_SIMPLE"]) or (not self.is_normal and config.userconfigdict["PUSH_HARD_USE_SIMPLE"]): + logging.info({"zh_CN": "使用简易攻略", "en_US": "Use simple explore"}) click(easy_tab_pos_R) click(easy_tab_pos_R) - ocr_s = "easy" + # 简易攻略的话,把ocr_s设为_,让后面不要走格子(no "S") + ocr_s = ["_", 1.0] else: - logging.info({"zh_CN": "困难图,走格子拿钻石", - "en_US": "Difficulty diagram, walk the grid to get the diamond"}) + logging.info({"zh_CN": "不使用简易攻略", + "en_US": "Do not use simple explore"}) click(center_tab_pos_L) click(center_tab_pos_L) + # 切换到集中指挥tab后,才会出现s标签,没有必要再次识别 + ocr_s = ["S", 1.0] + walk_grid = None - logging.info(ocr_s[0].upper()) + logging.info("OCR: "+ocr_s[0].upper()) if "S" not in ocr_s[0].upper(): logging.info({"zh_CN": "未识别到S等级,判断为普通战斗", "en_US": "S grade not recognized, judged as normal battle"}) diff --git a/modules/AllTask/SubTask/FightQuest.py b/modules/AllTask/SubTask/FightQuest.py index 60ddb2c..84bf484 100644 --- a/modules/AllTask/SubTask/FightQuest.py +++ b/modules/AllTask/SubTask/FightQuest.py @@ -102,16 +102,21 @@ def on_run(self) -> None: sleeptime=2 ) # 1. 如果是白色UI,进入战斗 - if match_pixel((1250, 32), Page.COLOR_BUTTON_WHITE): + if match_pixel((1250, 32), Page.COLOR_BUTTON_WHITE, printit=True): # 战斗中 logging.info({"zh_CN": "战斗中...", "en_US": "Fighting"}) break + else: + logging.info({"zh_CN": "无法匹配右上暂停", "en_US": "Cannot match the upper right fight pause"}) + logging.warn({"zh_CN": "请确认游戏设置:战斗时上下黑边 为 关", "en_US": "Please confirm the game settings: Black edges during battle are off"}) # 2. 如果是剧情,跳过剧情 if match(button_pic(ButtonName.BUTTON_STORY_MENU)): logging.info({"zh_CN": "剧情中...", "en_US": "In the plot..."}) SkipStory(pre_times=3).run() # 跳过剧情后,重新判断是否进入了战斗 continue + else: + logging.info({"zh_CN": "无法匹配剧情按钮", "en_US": "Cannot match the story pause button"}) # 切换AUTO logging.info({"zh_CN": "切换AUTO...", "en_US": "Toggle Auto..."}) self.run_until( @@ -151,9 +156,11 @@ def on_run(self) -> None: self.run_until( lambda: click(Page.MAGICPOINT), lambda: match(button_pic(ButtonName.BUTTON_FIGHT_RESULT_CONFIRMB)) or match( - button_pic(ButtonName.BUTTON_CONFIRMY) or self.backtopic(), threshold=0.8), - times=90, - sleeptime=2 + button_pic(ButtonName.BUTTON_CONFIRMY), + threshold=0.8 + ) or self.backtopic(), + times=90, + sleeptime=2 ) if self.backtopic(): # 此处返回到backtopic,意味着错误进入了战斗 diff --git a/modules/configs/MyConfig.py b/modules/configs/MyConfig.py index 7fba82f..c4704e7 100644 --- a/modules/configs/MyConfig.py +++ b/modules/configs/MyConfig.py @@ -10,7 +10,7 @@ class MyConfigger: """ 维护config字典,包含软件config,用户任务config,语言包 """ - NOWVERSION="1.5.5" + NOWVERSION="1.6.0" USER_CONFIG_FOLDER="./BAAH_CONFIGS" SOFTWARE_CONFIG_FOLDER="./DATA/CONFIGS" LANGUAGE_PACKAGE_FOLDER="./DATA/i18n" @@ -199,24 +199,35 @@ def save_software_config(self): with open(file_path, 'w', encoding="utf8") as f: json.dump(self.softwareconfigdict, f, indent=4, ensure_ascii=False) - def get_one_version_num(self, versionstr="nothing"): + def get_one_version_num(self, versionstr=None): """ 将版本号字符串转换成数字 + + 如 1.4.10 -> 10410 """ - if versionstr == "nothing": - versionstr = self.NOWVERSION - versionlist = versionstr.split(".") - if len(versionlist) != 3: + + try: + if not versionstr: + versionstr = self.NOWVERSION + versionlist = versionstr.split(".") + return int(versionlist[0])*10000+int(versionlist[1])*100+int(versionlist[2]) + except Exception as e: + print(e) return -1 - return int(versionlist[0])*10000+int(versionlist[1])*100+int(versionlist[2]) - def get_version_str(self, versionnum=-1): + def get_version_str(self, versionnum=None): """ 将版本号数字转换成字符串 """ - if versionnum == -1: - versionnum = self.get_one_version_num() - return f"{int(versionnum/10000)}.{int(versionnum%10000/100)}.{versionnum%100}" + if versionnum is None: + return self.NOWVERSION + try: + assert isinstance(versionnum, int) + assert versionnum > 0 + return f"{int(versionnum/10000)}.{int(versionnum%10000/100)}.{versionnum%100}" + except Exception as e: + print(e) + return "0.0.0" diff --git a/modules/configs/defaultSettings.py b/modules/configs/defaultSettings.py index b02351f..af2e071 100644 --- a/modules/configs/defaultSettings.py +++ b/modules/configs/defaultSettings.py @@ -24,8 +24,10 @@ "TASK_ORDER": {"d":["登录游戏"]}, "SHOP_NORMAL": {"d":[]}, "SHOP_CONTEST": {"d":[]}, + "PUSH_NORMAL_USE_SIMPLE": {"d":False}, "PUSH_NORMAL_QUEST": {"d":0}, "PUSH_NORMAL_QUEST_LEVEL": {"d":1}, + "PUSH_HARD_USE_SIMPLE": {"d":False}, "PUSH_HARD_QUEST": {"d":0}, "PUSH_HARD_QUEST_LEVEL": {"d":1}, "TASK_ACTIVATE": {"d":[True]}, @@ -153,6 +155,8 @@ # 自定义任务 "USER_DEF_TASKS":{"d":""}, + + "CRAFT_TIMES":{"d":1}, } # 软件的config里的默认值 @@ -190,5 +194,5 @@ "INFO_DICT":{"d":{}}, "INFO_LIST":{"d":[]}, # 截图文件读取失败的次数 - "SCREENSHOT_READ_FAIL_TIMES":{"d":0}, + "SCREENSHOT_READ_FAIL_TIMES":{"d":0} } \ No newline at end of file diff --git a/modules/utils/__init__.py b/modules/utils/__init__.py index 5114ada..1e31d11 100644 --- a/modules/utils/__init__.py +++ b/modules/utils/__init__.py @@ -229,8 +229,10 @@ def check_connect(): logging.info({"zh_CN":"adb与模拟器连接正常" , "en_US":"The connection between adb and the emulator is normal"}) # 检查图片长和宽 img = cv2.imread(f"./{get_config_screenshot_name()}") + if img is None: + logging.error({"zh_CN": "图片读取失败,多次出现请尝试重启模拟器", "en_US":"Image read failed, try restart emulator if encountered multiple times"}) # 第一维度是高,第二维度是宽 - if img.shape[0] == 720 and img.shape[1] == 1280: + elif img.shape[0] == 720 and img.shape[1] == 1280: logging.info({"zh_CN": "图片分辨率为1280*720", "en_US":"The resolution is 1280*720"}) return True elif img.shape[0] == 1280 and img.shape[1] == 720: diff --git a/modules/utils/adb_utils.py b/modules/utils/adb_utils.py index 0ef9d80..7efb013 100644 --- a/modules/utils/adb_utils.py +++ b/modules/utils/adb_utils.py @@ -112,7 +112,7 @@ def get_now_running_app(use_config=None): # 找到当前运行的app那行 output = sentence if "null" in output: - logging.warn({"zh_CN": "MUMU模拟器需要设置里关闭保活!", + logging.warn({"zh_CN": ">>> MUMU模拟器需要设置里关闭保活! <<<", "en_US": "If you are using MUMU emulator, please turn off the keep alive in the settings!"}) break # 截取app activity diff --git a/modules/utils/subprocess_helper.py b/modules/utils/subprocess_helper.py index b7a18eb..f6f3345 100644 --- a/modules/utils/subprocess_helper.py +++ b/modules/utils/subprocess_helper.py @@ -6,24 +6,26 @@ logging.getLogger("subprocess").setLevel(logging.WARNING) -def subprocess_run(cmd: Tuple[str], isasync=False, stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding = "utf-8"): +def subprocess_run(cmd: Tuple[str]|str, isasync=False, stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding = "utf-8", shell=False): """ Run a command in a subprocess and return the instance. Parameters ========== - cmd: list + cmd: list|str The command to run. Returns ======= pipeline """ + # https://www.cnblogs.com/superbaby11/p/16195273.html + # shell 为True时,cmd可以是字符串,否则是列表。列表的第一个元素是命令,后面的元素是传递给shell1的参数。 if isasync: # 异步非阻塞执行 - return subprocess.Popen(cmd, stdout=stdout, stderr=stderr, encoding=encoding) + return subprocess.Popen(cmd, stdout=stdout, stderr=stderr, encoding=encoding, shell=shell) else: # 同步阻塞执行 - return subprocess.run(cmd, stdout=stdout, stderr=stderr, encoding=encoding) + return subprocess.run(cmd, stdout=stdout, stderr=stderr, encoding=encoding, shell=shell) \ No newline at end of file diff --git a/package.py b/package.py index 508a589..49b7ed0 100644 --- a/package.py +++ b/package.py @@ -112,6 +112,15 @@ def package_remove_folder(path): ] subprocess.call(guicmd) +# 打包update.py,名字为BAAH_UPDATE +updatecmd = [ + 'pyinstaller', + 'update.py', + '-n', 'BAAH_UPDATE', + '--icon', './DATA/assets/kayoko.ico', + '-y' +] +subprocess.call(updatecmd) # 当前目录 print("当前目录:", os.getcwd()) @@ -152,6 +161,7 @@ def package_remove_folder(path): package_copyfolder("./DATA/assets_global_en", "./dist/BAAH/DATA/assets_global_en") package_copyfolder("./DATA/grid_solution", "./dist/BAAH/DATA/grid_solution") package_copyfile("./dist/jsoneditor/jsoneditor.exe", "./dist/BAAH/jsoneditor.exe") +package_copyfile("./dist/BAAH_UPDATE/BAAH_UPDATE.exe", "./dist/BAAH/BAAH_UPDATE.exe") time.sleep(2) diff --git a/update.py b/update.py new file mode 100644 index 0000000..b4a3a70 --- /dev/null +++ b/update.py @@ -0,0 +1,232 @@ +import hashlib +import json +import shutil +import subprocess +import traceback +import requests +import os +import zipfile +import time + +# 本文件内容不要频繁变更 +print("This Updator Version: 0.1.0") + +def get_one_version_num(versionstr=None): + """ + 将版本号字符串转换成数字 + + 如 1.4.10 -> 10410 + """ + + try: + if not versionstr: + versionstr = self.NOWVERSION + versionlist = versionstr.split(".") + return int(versionlist[0])*10000+int(versionlist[1])*100+int(versionlist[2]) + except Exception as e: + print(e) + return -1 + +class VersionInfo: + def __init__(self): + self.has_new_version = False + self.msg = "No new version" + self.version_str = "" + self.update_zip_url = "" + + +def file_checksum(file_path): + """计算文件的 SHA256 哈希值""" + sha256 = hashlib.sha256() + with open(file_path, 'rb') as f: + for block in iter(lambda: f.read(4096), b''): + sha256.update(block) + return sha256.hexdigest() + +def zip_file_checksum(zip_file, file_in_zip): + """计算压缩包内文件的 SHA256 哈希值""" + sha256 = hashlib.sha256() + with zip_file.open(file_in_zip) as f: + for block in iter(lambda: f.read(4096), b''): + sha256.update(block) + return sha256.hexdigest() + +def whether_has_new_version(): + """ + 检查是否有新版本 + """ + urls = { + "gitee": "https://gitee.com/api/v5/repos/sammusen/BAAH/releases/latest", + "github": "https://api.github.com/repos/sanmusen214/BAAH/releases/latest" + } + + eachtime = {} + eachnewesttag = {} # 存放各个平台上最新的版本号 如 1.5.3 + eachdownloadurl = {} # 存放各个平台上最新的下载链接 + print("Checking for new version...") + for key in urls: + nowtime = time.time() + try: + print(f"Checking: {key}...") + response = requests.get(urls[key], timeout=5) + if response.status_code == 200: + eachtime[key] = time.time() - nowtime + data = response.json() + eachnewesttag[key] = data["tag_name"].replace("BAAH", "") + eachdownloadurl[key] = [each["browser_download_url"] for each in data["assets"]] + except Exception as e: + print(f"Error accessing {key}: {e}") + continue + + if not eachtime: + print("Failed to check time spent for accessing github nor gitee.") + vi = VersionInfo() + vi.msg = "Failed to check time spent for accessing github nor gitee." + return vi + print(eachtime) + print(eachnewesttag) + fastestkey = min(eachtime, key=eachtime.get) + newest_tag = eachnewesttag[fastestkey] + + # 这里读取software_config.json实际存储的字符串 + # DATA/CONFIGS/software_config.json里的NOWVERSION字段 + with open(os.path.join("DATA", "CONFIGS", "software_config.json"), "r") as f: + confile = json.load(f) + current_version_num = get_one_version_num(confile["NOWVERSION"]) + new_version_num = get_one_version_num(newest_tag) + + if new_version_num > current_version_num: + vi = VersionInfo() + vi.has_new_version = True + vi.msg = f"New version available: {newest_tag} ({fastestkey})" + vi.version_str = newest_tag + vi.update_zip_url = next((url for url in eachdownloadurl[fastestkey] if url.endswith("_update.zip")), "") + # 如果没有以update末尾的文件,说明没有更新文件 + if vi.update_zip_url == "": + vi.msg += "\nFailed to get the download URL." + vi.has_new_version = False + return vi + else: + vi = VersionInfo() + vi.msg = "No new version" + return vi + +def check_and_update(): + # 判断路径下是否有BAAH.exe,如果没有说明运行目录不对 + if not os.path.exists("BAAH.exe"): + print("Please run this script in the same directory as BAAH.exe.") + return + + version_info = whether_has_new_version() + # 如果没有新版本,直接返回 + if not version_info.has_new_version: + print("No new version available.") + print(version_info.msg) + return + # 根据update.zip结尾的url下载文件 + target_url = version_info.update_zip_url + targetfilename = os.path.basename(target_url) + # 不存在zip文件则下载 + if not os.path.exists(targetfilename): + try: + response = requests.get(target_url, timeout=10) + if response.status_code == 200: + with open(targetfilename, "wb") as f: + f.write(response.content) + print("Downloading update zip: Success") + else: + print("Downloading update zip: Failed") + return + except Exception as e: + print("Downloading new version: Failed") + print(f"Error downloading file: {e}") + raise Exception("Failed to download the ZIP file.") + else: + print(f"Update ZIP file: {targetfilename} already exists.") + + # Check and terminate BAAH.exe and BAAH_GUI.exe processes + # 中断已有的BAAH进程 + processes_to_terminate = ["BAAH.exe", "BAAH_GUI.exe"] + for process in processes_to_terminate: + try: + #! only for Windows now + # Windows,未来多平台可以考虑使用psutil库 + subprocess.run(f'taskkill /f /im {process}', shell=True, check=True) + print(f"Terminated process: {process}") + except subprocess.CalledProcessError as e: + # but thats fine, maybe it is not running + print(f"Failed to terminate process {process}: {e}") + # 之后重新启动GUI + global open_GUI_again + open_GUI_again = True + # Extract the downloaded ZIP file + # 解压下载下来的zip文件 + try: + # zip第一层是一个BAAH1.5.4这样的大文件夹,跳过 + total_sub_files_extracted = 0 + with zipfile.ZipFile(targetfilename, 'r') as zip_ref: + all_files = zip_ref.namelist() + # 把BAAH_UPDATE.exe放到最后 + file_updateexe_name = next((file for file in all_files if file.endswith("BAAH_UPDATE.exe")), None) + if file_updateexe_name: + all_files.remove(file_updateexe_name) + all_files.append(file_updateexe_name) + for file in all_files: + if file.endswith("/"): + # 文件夹不作为文件处理 + continue + if file.startswith(f"BAAH{version_info.version_str}/"): + # 去除第一层文件夹 + relative_path = os.path.relpath(file, f"BAAH{version_info.version_str}/") + # 如果有深层文件夹,创建深层文件夹 + os.makedirs(os.path.dirname(relative_path), exist_ok=True) if os.path.dirname(relative_path) else None + # 判断文件是否存在 + if os.path.exists(relative_path): + # 如果存在,检查hash是否一致 + src_hash = zip_file_checksum(zip_ref, file) + dst_hash = file_checksum(relative_path) + # print(f"src_hash: {src_hash}, dst_hash: {dst_hash}") + if src_hash == dst_hash: + continue + else: + print(f"detected file change: {relative_path}") + else: + print(f"file not exists: {relative_path}, write it.") + print(f" Extracting {file} to {relative_path}") + # 解压文件到relative_path,覆盖 + with zip_ref.open(file) as zf, open(relative_path, "wb") as f: + shutil.copyfileobj(zf, f) + total_sub_files_extracted += 1 + else: + print(f"Skipped {file}, this is not start with BAAH{version_info.version_str}/ in zip") + print(f"\nUpdate successful, {total_sub_files_extracted} files extracted.\n") + except zipfile.BadZipFile: + raise Exception("Failed to extract the ZIP file. Bad ZIP file.") + + + # 删除下载的zip文件 + os.remove(targetfilename) + print(f"Deleted the downloaded ZIP file: {targetfilename}.") + +open_GUI_again = False +if __name__ == "__main__": + try: + check_and_update() + print("========== [UPDATE SUCCESS] =========") + except Exception as e: + traceback.print_exc() + print("========== ERROR! =========") + if "BAAH_UPDATE.exe" in str(e) and "Permission denied" in str(e): + print(">>> You can not use this script to replace itself. Please unpack the zip manually. <<<") + + # 重新启动BAAH_GUI.exe + # 注意这里CREATE_NEW_CONSOLE即使把本文件命令行窗口关了,也不会影响BAAH_GUI.exe的运行 + if open_GUI_again: + try: + # Windows only + subprocess.Popen(["BAAH_GUI.exe"], creationflags=subprocess.CREATE_NEW_CONSOLE, close_fds=True) + print("BAAH_GUI.exe started.") + except Exception as e: + print(f"Failed to start BAAH_GUI.exe: {e}") + + input("Press Enter to exit the updater.") \ No newline at end of file