From 0c31b2c4ec5aa2c9c5f5b6e62f5ecf1389ff0179 Mon Sep 17 00:00:00 2001 From: guoh064 <50830808+guoh064@users.noreply.github.com> Date: Sat, 28 Mar 2026 17:49:29 +0800 Subject: [PATCH 1/7] Fix: _reward_mission_claim_click stucked at MISSION_UNFINISH (#5573) --- module/reward/reward.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/module/reward/reward.py b/module/reward/reward.py index 9a839bf6b..b713691f8 100644 --- a/module/reward/reward.py +++ b/module/reward/reward.py @@ -89,6 +89,8 @@ class Reward(UI): click_interval.reset() clicked = True continue + if self.appear(MISSION_UNFINISH, offset=(20, 20)): + return clicked def _reward_mission_claim_receive(self): """ From e45fd05341de6300b7d8a35e868b21d7c12342d3 Mon Sep 17 00:00:00 2001 From: LmeSzinc <37934724+LmeSzinc@users.noreply.github.com> Date: Sat, 28 Mar 2026 18:12:31 +0800 Subject: [PATCH 2/7] Add: Add Students with Level >= X Only --- config/template.json | 3 ++- module/config/argument/args.json | 4 ++++ module/config/argument/argument.yaml | 1 + module/config/i18n/en-US.json | 4 ++++ module/config/i18n/ja-JP.json | 4 ++++ module/config/i18n/zh-CN.json | 4 ++++ module/config/i18n/zh-TW.json | 4 ++++ module/tactical/tactical_class.py | 23 +++++++++++++++-------- 8 files changed, 38 insertions(+), 9 deletions(-) diff --git a/config/template.json b/config/template.json index 7ec2175f7..41b2a2628 100644 --- a/config/template.json +++ b/config/template.json @@ -1253,7 +1253,8 @@ }, "AddNewStudent": { "Enable": false, - "Favorite": true + "Favorite": true, + "MinLevel": 50 }, "Storage": { "Storage": {} diff --git a/module/config/argument/args.json b/module/config/argument/args.json index 531741908..40f6f579a 100644 --- a/module/config/argument/args.json +++ b/module/config/argument/args.json @@ -6685,6 +6685,10 @@ "Favorite": { "type": "checkbox", "value": true + }, + "MinLevel": { + "type": "input", + "value": 50 } }, "Storage": { diff --git a/module/config/argument/argument.yaml b/module/config/argument/argument.yaml index 77fc24e65..d27cdf4c4 100644 --- a/module/config/argument/argument.yaml +++ b/module/config/argument/argument.yaml @@ -362,6 +362,7 @@ ControlExpOverflow: AddNewStudent: Enable: false Favorite: true + MinLevel: 50 Research: UseCube: value: only_05_hour diff --git a/module/config/i18n/en-US.json b/module/config/i18n/en-US.json index 51352e6ad..d9cfe5d8e 100644 --- a/module/config/i18n/en-US.json +++ b/module/config/i18n/en-US.json @@ -1417,6 +1417,10 @@ "Favorite": { "name": "Use Favorite Filter", "help": "During student selection apply the favorite filter\nThereby, giving priority to certain ship girls\nApply the favorite status to a ship girl in-game" + }, + "MinLevel": { + "name": "Add Students with Level >= X Only", + "help": "" } }, "Research": { diff --git a/module/config/i18n/ja-JP.json b/module/config/i18n/ja-JP.json index b4daf33c2..686a40d84 100644 --- a/module/config/i18n/ja-JP.json +++ b/module/config/i18n/ja-JP.json @@ -1417,6 +1417,10 @@ "Favorite": { "name": "好ましい選択", "help": "" + }, + "MinLevel": { + "name": "レベルがX以上のキャラクターのみを追加してください。", + "help": "" } }, "Research": { diff --git a/module/config/i18n/zh-CN.json b/module/config/i18n/zh-CN.json index a4de36172..91f022671 100644 --- a/module/config/i18n/zh-CN.json +++ b/module/config/i18n/zh-CN.json @@ -1417,6 +1417,10 @@ "Favorite": { "name": "优先选择常用", "help": "" + }, + "MinLevel": { + "name": "仅添加等级 >= X 的角色", + "help": "" } }, "Research": { diff --git a/module/config/i18n/zh-TW.json b/module/config/i18n/zh-TW.json index 69294ecab..edee19175 100644 --- a/module/config/i18n/zh-TW.json +++ b/module/config/i18n/zh-TW.json @@ -1417,6 +1417,10 @@ "Favorite": { "name": "優先選擇常用", "help": "" + }, + "MinLevel": { + "name": "僅新增等級 >= X 的角色", + "help": "" } }, "Research": { diff --git a/module/tactical/tactical_class.py b/module/tactical/tactical_class.py index 3c2e9f022..b8c9f5b9d 100644 --- a/module/tactical/tactical_class.py +++ b/module/tactical/tactical_class.py @@ -643,27 +643,34 @@ class RewardTacticalClass(Dock): # Wait until they turn into # [120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120] level_ocr = LevelOcr(CARD_LEVEL_GRIDS.buttons, name='DOCK_LEVEL_OCR', threshold=64) - timeout = Timer(1, count=1).start() - while 1: + list_level = [] + for _ in self.loop(timeout=1): list_level = level_ocr.ocr(self.device.image) first_ship = next((i for i, x in enumerate(list_level) if x > 0), len(list_level)) first_empty = next((i for i, x in enumerate(list_level) if x == 0), len(list_level)) - if timeout.reached(): - logger.warning('Wait ship cards timeout') - break if first_empty >= first_ship: break - self.device.screenshot() + else: + logger.warning('Wait ship cards timeout') + + try: + min_level = int(self.config.AddNewStudent_MinLevel) + if min_level < 1: + min_level = 1 + except (ValueError, TypeError) as e: + logger.warning(f'Invalid AddNewStudent_MinLevel: {self.config.AddNewStudent_MinLevel}, {e}') + min_level = 1 + logger.attr('AddNewStudent_MinLevel', min_level) should_select_button = None for button, level in list(zip(CARD_GRIDS.buttons, list_level))[self.dock_select_index:]: # Select ship LV > 1 only - if level > 1: + if level >= min_level: should_select_button = button break if should_select_button is None: - logger.info('No ships with level > 1 in dock') + logger.info(f'No ships with level >= {min_level} in dock') return False # select a ship From 0c4f46180ea4b8c3ab88fa562ea60bf40513810b Mon Sep 17 00:00:00 2001 From: LmeSzinc <37934724+LmeSzinc@users.noreply.github.com> Date: Sat, 28 Mar 2026 18:17:42 +0800 Subject: [PATCH 3/7] Fix: [ALAS] Skip permission errors when replacing adb --- deploy/Windows/emulator.py | 11 +++++++---- deploy/emulator.py | 21 ++++++++++++--------- 2 files changed, 19 insertions(+), 13 deletions(-) diff --git a/deploy/Windows/emulator.py b/deploy/Windows/emulator.py index 3dfc74f15..11def046a 100644 --- a/deploy/Windows/emulator.py +++ b/deploy/Windows/emulator.py @@ -142,10 +142,13 @@ class EmulatorManager(AlasManager): for adb in replace: logger.info(f'Replacing {adb}') bak = self.adb_path_to_backup(adb, new_backup=True) - logger.info(f'{adb} -----> {bak}') - shutil.move(adb, bak) - logger.info(f'{self.adb} -----> {adb}') - shutil.copy(self.adb, adb) + try: + logger.info(f'{adb} -----> {bak}') + shutil.move(adb, bak) + logger.info(f'{self.adb} -----> {adb}') + shutil.copy(self.adb, adb) + except OSError as e: + logger.warning(f'Failed to replace {adb}, {e}') def adb_recover(self): """ diff --git a/deploy/emulator.py b/deploy/emulator.py index 2ccbd4ec4..b16069d1b 100644 --- a/deploy/emulator.py +++ b/deploy/emulator.py @@ -137,16 +137,19 @@ class VirtualBoxEmulator: """ for ori, bak in zip(self.adb_binary, self.adb_backup): logger.info(f'Replacing {ori}') - if os.path.exists(ori): - if filecmp.cmp(adb, ori, shallow=True): - logger.info(f'{adb} is same as {ori}, skip') + try: + if os.path.exists(ori): + if filecmp.cmp(adb, ori, shallow=True): + logger.info(f'{adb} is same as {ori}, skip') + else: + logger.info(f'{ori} -----> {bak}') + shutil.move(ori, bak) + logger.info(f'{adb} -----> {ori}') + shutil.copy(adb, ori) else: - logger.info(f'{ori} -----> {bak}') - shutil.move(ori, bak) - logger.info(f'{adb} -----> {ori}') - shutil.copy(adb, ori) - else: - logger.info(f'{ori} not exists, skip') + logger.info(f'{ori} not exists, skip') + except OSError as e: + logger.warning(f'Failed to replace {ori}, {e}') def adb_recover(self): """ Revert adb replacement """ From b703cb10eaaa203df6b55ba4809784e418a9462b Mon Sep 17 00:00:00 2001 From: LmeSzinc <37934724+LmeSzinc@users.noreply.github.com> Date: Sat, 28 Mar 2026 18:32:55 +0800 Subject: [PATCH 4/7] Fix: Uninstall registry not closed --- deploy/emulator.py | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/deploy/emulator.py b/deploy/emulator.py index b16069d1b..7eb076a75 100644 --- a/deploy/emulator.py +++ b/deploy/emulator.py @@ -50,10 +50,11 @@ class VirtualBoxEmulator: return root try: - reg = winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, f'{self.UNINSTALL_REG}\\{self.name}', 0) + with winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, f'{self.UNINSTALL_REG}\\{self.name}', 0) as reg: + res = winreg.QueryValueEx(reg, 'UninstallString')[0] except FileNotFoundError: - reg = winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, f'{self.UNINSTALL_REG_2}\\{self.name}', 0) - res = winreg.QueryValueEx(reg, 'UninstallString')[0] + with winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, f'{self.UNINSTALL_REG_2}\\{self.name}', 0) as reg: + res = winreg.QueryValueEx(reg, 'UninstallString')[0] file = re.search('"(.*?)"', res) file = file.group(1) if file else res @@ -70,17 +71,17 @@ class VirtualBoxEmulator: str: Installation dir or None """ try: - reg = winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, path, 0) - root = winreg.QueryValueEx(reg, key)[0] - if os.path.exists(root): - return root + with winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, path, 0) as reg: + root = winreg.QueryValueEx(reg, key)[0] + if os.path.exists(root): + return root except FileNotFoundError: pass try: - reg = winreg.OpenKey(winreg.HKEY_CURRENT_USER, path, 0) - root = winreg.QueryValueEx(reg, key)[0] - if os.path.exists(root): - return root + with winreg.OpenKey(winreg.HKEY_CURRENT_USER, path, 0) as reg: + root = winreg.QueryValueEx(reg, key)[0] + if os.path.exists(root): + return root except FileNotFoundError: pass From 960db91be9effd21dd854bea9195169670256b6b Mon Sep 17 00:00:00 2001 From: LmeSzinc <37934724+LmeSzinc@users.noreply.github.com> Date: Sat, 28 Mar 2026 18:46:36 +0800 Subject: [PATCH 5/7] Add: [ALAS] Support LDPlayer14 --- module/config/argument/args.json | 1 + module/config/argument/argument.yaml | 1 + module/config/config_generated.py | 3 ++- module/config/i18n/en-US.json | 1 + module/config/i18n/ja-JP.json | 1 + module/config/i18n/zh-CN.json | 1 + module/config/i18n/zh-TW.json | 1 + module/device/method/ldopengl.py | 2 +- module/device/platform/emulator_base.py | 3 ++- module/device/platform/emulator_windows.py | 9 +++++++-- 10 files changed, 18 insertions(+), 5 deletions(-) diff --git a/module/config/argument/args.json b/module/config/argument/args.json index 40f6f579a..1cec1d669 100644 --- a/module/config/argument/args.json +++ b/module/config/argument/args.json @@ -161,6 +161,7 @@ "LDPlayer3", "LDPlayer4", "LDPlayer9", + "LDPlayer14", "MuMuPlayer", "MuMuPlayerX", "MuMuPlayer12", diff --git a/module/config/argument/argument.yaml b/module/config/argument/argument.yaml index d27cdf4c4..bfc910a0f 100644 --- a/module/config/argument/argument.yaml +++ b/module/config/argument/argument.yaml @@ -69,6 +69,7 @@ EmulatorInfo: LDPlayer3, LDPlayer4, LDPlayer9, + LDPlayer14, MuMuPlayer, MuMuPlayerX, MuMuPlayer12, diff --git a/module/config/config_generated.py b/module/config/config_generated.py index e698e9877..8b0b1115d 100644 --- a/module/config/config_generated.py +++ b/module/config/config_generated.py @@ -27,7 +27,7 @@ class GeneratedConfig: Emulator_AdbRestart = False # Group `EmulatorInfo` - EmulatorInfo_Emulator = 'auto' # auto, NoxPlayer, NoxPlayer64, BlueStacks4, BlueStacks5, BlueStacks4HyperV, BlueStacks5HyperV, LDPlayer3, LDPlayer4, LDPlayer9, MuMuPlayer, MuMuPlayerX, MuMuPlayer12, MEmuPlayer + EmulatorInfo_Emulator = 'auto' # auto, NoxPlayer, NoxPlayer64, BlueStacks4, BlueStacks5, BlueStacks4HyperV, BlueStacks5HyperV, LDPlayer3, LDPlayer4, LDPlayer9, LDPlayer14, MuMuPlayer, MuMuPlayerX, MuMuPlayer12, MEmuPlayer EmulatorInfo_name = None EmulatorInfo_path = None @@ -202,6 +202,7 @@ class GeneratedConfig: # Group `AddNewStudent` AddNewStudent_Enable = False AddNewStudent_Favorite = True + AddNewStudent_MinLevel = 50 # Group `Research` Research_UseCube = 'only_05_hour' # always_use, only_05_hour, only_no_project, do_not_use diff --git a/module/config/i18n/en-US.json b/module/config/i18n/en-US.json index d9cfe5d8e..5b01b62cd 100644 --- a/module/config/i18n/en-US.json +++ b/module/config/i18n/en-US.json @@ -470,6 +470,7 @@ "LDPlayer3": "LD Player 3", "LDPlayer4": "LD Player 4", "LDPlayer9": "LD Player 9", + "LDPlayer14": "LDPlayer 14", "MuMuPlayer": "MuMu Player", "MuMuPlayerX": "MuMu Player X", "MuMuPlayer12": "MuMu Player 12", diff --git a/module/config/i18n/ja-JP.json b/module/config/i18n/ja-JP.json index 686a40d84..2005c4584 100644 --- a/module/config/i18n/ja-JP.json +++ b/module/config/i18n/ja-JP.json @@ -470,6 +470,7 @@ "LDPlayer3": "LDPlayer3", "LDPlayer4": "LDPlayer4", "LDPlayer9": "LDPlayer9", + "LDPlayer14": "LDPlayer14", "MuMuPlayer": "MuMuPlayer", "MuMuPlayerX": "MuMuPlayerX", "MuMuPlayer12": "MuMuPlayer12", diff --git a/module/config/i18n/zh-CN.json b/module/config/i18n/zh-CN.json index 91f022671..f76b0b2f8 100644 --- a/module/config/i18n/zh-CN.json +++ b/module/config/i18n/zh-CN.json @@ -470,6 +470,7 @@ "LDPlayer3": "雷电模拟器3", "LDPlayer4": "雷电模拟器4", "LDPlayer9": "雷电模拟器9", + "LDPlayer14": "雷电模拟器14", "MuMuPlayer": "MuMu模拟器", "MuMuPlayerX": "MuMu模拟器X", "MuMuPlayer12": "MuMu模拟器12", diff --git a/module/config/i18n/zh-TW.json b/module/config/i18n/zh-TW.json index edee19175..f98b049e1 100644 --- a/module/config/i18n/zh-TW.json +++ b/module/config/i18n/zh-TW.json @@ -470,6 +470,7 @@ "LDPlayer3": "雷電模擬器3", "LDPlayer4": "雷電模擬器4", "LDPlayer9": "雷電模擬器9", + "LDPlayer14": "雷電模擬器14", "MuMuPlayer": "MuMu模擬器", "MuMuPlayerX": "MuMu模擬器X", "MuMuPlayer12": "MuMu模擬器12", diff --git a/module/device/method/ldopengl.py b/module/device/method/ldopengl.py index fbd6009ca..b4e6c38f0 100644 --- a/module/device/method/ldopengl.py +++ b/module/device/method/ldopengl.py @@ -332,7 +332,7 @@ class LDOpenGL(Platform): if not self.is_ldplayer_bluestacks_family: return False logger.attr('EmulatorInfo_Emulator', self.config.EmulatorInfo_Emulator) - if self.config.EmulatorInfo_Emulator not in ['LDPlayer9']: + if self.config.EmulatorInfo_Emulator not in ['LDPlayer9', 'LDPlayer14']: return False try: diff --git a/module/device/platform/emulator_base.py b/module/device/platform/emulator_base.py index 19943fd95..522c6b51d 100644 --- a/module/device/platform/emulator_base.py +++ b/module/device/platform/emulator_base.py @@ -155,7 +155,8 @@ class EmulatorBase: LDPlayer3 = 'LDPlayer3' LDPlayer4 = 'LDPlayer4' LDPlayer9 = 'LDPlayer9' - LDPlayerFamily = [LDPlayer3, LDPlayer4, LDPlayer9] + LDPlayer14 = 'LDPlayer14' + LDPlayerFamily = [LDPlayer3, LDPlayer4, LDPlayer9, LDPlayer14] MuMuPlayer = 'MuMuPlayer' MuMuPlayerX = 'MuMuPlayerX' MuMuPlayer12 = 'MuMuPlayer12' diff --git a/module/device/platform/emulator_windows.py b/module/device/platform/emulator_windows.py index 78a5b6b0c..c48a0922a 100644 --- a/module/device/platform/emulator_windows.py +++ b/module/device/platform/emulator_windows.py @@ -110,6 +110,8 @@ class Emulator(EmulatorBase): return cls.LDPlayer4 elif dir1 == 'ldplayer9': return cls.LDPlayer9 + elif dir1 == 'ldplayer14': + return cls.LDPlayer14 else: return cls.LDPlayer3 if exe == 'nemuplayer.exe': @@ -538,8 +540,11 @@ class EmulatorManager(EmulatorManagerBase): exe.add(file) # LDPlayer install path - for path in [r'SOFTWARE\leidian\ldplayer', - r'SOFTWARE\leidian\ldplayer9']: + for path in [ + r'SOFTWARE\leidian\ldplayer', + r'SOFTWARE\leidian\ldplayer9', + r'SOFTWARE\leidian\ldplayer14', + ]: ld = self.get_install_dir_from_reg(path, 'InstallDir') if ld: ld = abspath(os.path.join(ld, './dnplayer.exe')) From 5c9b955d24be7af5a7d304ed3909c5fc65582e26 Mon Sep 17 00:00:00 2001 From: LmeSzinc <37934724+LmeSzinc@users.noreply.github.com> Date: Sat, 28 Mar 2026 19:07:47 +0800 Subject: [PATCH 6/7] Fix: [ALAS] search by emulator type first, which is more trustworthy --- module/device/platform/platform_base.py | 27 +++++++++++++------------ 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/module/device/platform/platform_base.py b/module/device/platform/platform_base.py index 9d5c61b9c..944f7a8ff 100644 --- a/module/device/platform/platform_base.py +++ b/module/device/platform/platform_base.py @@ -181,6 +181,20 @@ class PlatformBase(Connection, EmulatorManagerBase): logger.info(f'Found emulator instance: {instance}') return instance + # search by emulator type first, which is the easiest setting for user to setup, so more trustworthy + # Multiple instances in given serial, name and path, search by emulator + if emulator: + search_args['type'] = emulator + select = instances.select(**search_args) + if select.count == 0: + logger.warning(f'No emulator instances with {search_args}, type invalid') + search_args.pop('type') + elif select.count == 1: + instance = select[0] + logger.hr('Emulator instance', level=2) + logger.info(f'Found emulator instance: {instance}') + return instance + # Multiple instances in given serial, search by name if name: search_args['name'] = name @@ -207,19 +221,6 @@ class PlatformBase(Connection, EmulatorManagerBase): logger.info(f'Found emulator instance: {instance}') return instance - # Multiple instances in given serial, name and path, search by emulator - if emulator: - search_args['type'] = emulator - select = instances.select(**search_args) - if select.count == 0: - logger.warning(f'No emulator instances with {search_args}, type invalid') - search_args.pop('type') - elif select.count == 1: - instance = select[0] - logger.hr('Emulator instance', level=2) - logger.info(f'Found emulator instance: {instance}') - return instance - # Still too many instances, search from running emulators running = remove_duplicated_path(list(self.iter_running_emulator())) logger.info('Running emulators') From c60c343c687c90224c3414901b7b8c66f06c0ce4 Mon Sep 17 00:00:00 2001 From: LmeSzinc <37934724+LmeSzinc@users.noreply.github.com> Date: Sat, 28 Mar 2026 19:09:14 +0800 Subject: [PATCH 7/7] Fix: [ALAS] Limit Android SDK version when benchmarking Droidcast --- module/daemon/benchmark.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/module/daemon/benchmark.py b/module/daemon/benchmark.py index 9bc3d6c0b..19fcf7f07 100644 --- a/module/daemon/benchmark.py +++ b/module/daemon/benchmark.py @@ -39,7 +39,7 @@ class Benchmark(DaemonBase, CampaignUI): record = [] for n in range(1, self.TEST_TOTAL + 1): - start = time.time() + start = time.perf_counter() try: func(*args, **kwargs) @@ -52,7 +52,7 @@ class Benchmark(DaemonBase, CampaignUI): logger.warning(f'Benchmark tests failed on func: {func.__name__}') return 'Failed' - cost = time.time() - start + cost = time.perf_counter() - start logger.attr( f'{str(n).rjust(2, "0")}/{self.TEST_TOTAL}', f'{float2str(cost)}' @@ -194,6 +194,10 @@ class Benchmark(DaemonBase, CampaignUI): if device == 'android_phone_vmos': screenshot = ['ADB', 'aScreenCap', 'DroidCast', 'DroidCast_raw'] click = ['ADB', 'Hermit', 'MaaTouch'] + # Droidcast on SDK 23 (Android 6.0) to SDK 32 (Android 12) + if not (23 <= sdk <= 32): + screenshot = remove('DroidCast', 'DroidCast_raw') + if self.device.nemu_ipc_available(): screenshot.append('nemu_ipc') if self.device.ldopengl_available():