Sync: [ALAS] Emulator manager

This commit is contained in:
LmeSzinc 2024-04-14 18:41:30 +08:00
parent 01116336f8
commit 96aa273f9b
3 changed files with 133 additions and 42 deletions

View File

@ -36,6 +36,21 @@ def get_serial_pair(serial):
return None, None return None, None
def remove_duplicated_path(paths):
"""
Args:
paths (list[str]):
Returns:
list[str]:
"""
paths = sorted(set(paths))
dic = {}
for path in paths:
dic.setdefault(path.lower(), path)
return list(dic.values())
@dataclass @dataclass
class EmulatorInstanceBase: class EmulatorInstanceBase:
# Serial for adb connection # Serial for adb connection
@ -205,6 +220,14 @@ class EmulatorBase:
class EmulatorManagerBase: class EmulatorManagerBase:
@staticmethod
def iter_running_emulator():
"""
Yields:
str: Path to emulator executables, may contains duplicate values
"""
return
@cached_property @cached_property
def all_emulators(self) -> t.List[EmulatorBase]: def all_emulators(self) -> t.List[EmulatorBase]:
""" """

View File

@ -8,7 +8,8 @@ from dataclasses import dataclass
# module/device/platform/emulator_base.py # module/device/platform/emulator_base.py
# module/device/platform/emulator_windows.py # module/device/platform/emulator_windows.py
# Will be used in Alas Easy Install, they shouldn't import any Alas modules. # Will be used in Alas Easy Install, they shouldn't import any Alas modules.
from module.device.platform.emulator_base import EmulatorBase, EmulatorInstanceBase, EmulatorManagerBase from module.device.platform.emulator_base import EmulatorBase, EmulatorInstanceBase, EmulatorManagerBase, \
remove_duplicated_path
from module.device.platform.utils import cached_property, iter_folder from module.device.platform.utils import cached_property, iter_folder
@ -70,7 +71,7 @@ class Emulator(EmulatorBase):
def path_to_type(cls, path: str) -> str: def path_to_type(cls, path: str) -> str:
""" """
Args: Args:
path: Path to .exe file path: Path to .exe file, case insensitive
Returns: Returns:
str: Emulator type, such as Emulator.NoxPlayer str: Emulator type, such as Emulator.NoxPlayer
@ -78,46 +79,49 @@ class Emulator(EmulatorBase):
folder, exe = os.path.split(path) folder, exe = os.path.split(path)
folder, dir1 = os.path.split(folder) folder, dir1 = os.path.split(folder)
folder, dir2 = os.path.split(folder) folder, dir2 = os.path.split(folder)
if exe == 'Nox.exe': exe = exe.lower()
if dir2 == 'Nox': dir1 = dir1.lower()
dir2 = dir2.lower()
if exe == 'nox.exe':
if dir2 == 'nox':
return cls.NoxPlayer return cls.NoxPlayer
elif dir2 == 'Nox64': elif dir2 == 'nox64':
return cls.NoxPlayer64 return cls.NoxPlayer64
else: else:
return cls.NoxPlayer return cls.NoxPlayer
if exe == 'Bluestacks.exe': if exe == 'bluestacks.exe':
if dir1 in ['BlueStacks', 'BlueStacks_cn']: if dir1 in ['bluestacks', 'bluestacks_cn']:
return cls.BlueStacks4 return cls.BlueStacks4
elif dir1 in ['BlueStacks_nxt', 'BlueStacks_nxt_cn']: elif dir1 in ['bluestacks_nxt', 'bluestacks_nxt_cn']:
return cls.BlueStacks5 return cls.BlueStacks5
else: else:
return cls.BlueStacks4 return cls.BlueStacks4
if exe == 'HD-Player.exe': if exe == 'hd-player.exe':
if dir1 in ['BlueStacks', 'BlueStacks_cn']: if dir1 in ['bluestacks', 'bluestacks_cn']:
return cls.BlueStacks4 return cls.BlueStacks4
elif dir1 in ['BlueStacks_nxt', 'BlueStacks_nxt_cn']: elif dir1 in ['bluestacks_nxt', 'bluestacks_nxt_cn']:
return cls.BlueStacks5 return cls.BlueStacks5
else: else:
return cls.BlueStacks5 return cls.BlueStacks5
if exe == 'dnplayer.exe': if exe == 'dnplayer.exe':
if dir1 == 'LDPlayer': if dir1 == 'ldplayer':
return cls.LDPlayer3 return cls.LDPlayer3
elif dir1 == 'LDPlayer4': elif dir1 == 'ldplayer4':
return cls.LDPlayer4 return cls.LDPlayer4
elif dir1 == 'LDPlayer9': elif dir1 == 'ldplayer9':
return cls.LDPlayer9 return cls.LDPlayer9
else: else:
return cls.LDPlayer3 return cls.LDPlayer3
if exe == 'NemuPlayer.exe': if exe == 'nemuplayer.exe':
if dir2 == 'nemu': if dir2 == 'nemu':
return cls.MuMuPlayer return cls.MuMuPlayer
elif dir2 == 'nemu9': elif dir2 == 'nemu9':
return cls.MuMuPlayerX return cls.MuMuPlayerX
else: else:
return cls.MuMuPlayer return cls.MuMuPlayer
if exe == 'MuMuPlayer.exe': if exe == 'mumuplayer.exe':
return cls.MuMuPlayer12 return cls.MuMuPlayer12
if exe == 'MEmu.exe': if exe == 'memu.exe':
return cls.MEmuPlayer return cls.MEmuPlayer
return '' return ''
@ -143,7 +147,9 @@ class Emulator(EmulatorBase):
elif 'NemuMultiPlayer.exe' in exe: elif 'NemuMultiPlayer.exe' in exe:
yield exe.replace('NemuMultiPlayer.exe', 'NemuPlayer.exe') yield exe.replace('NemuMultiPlayer.exe', 'NemuPlayer.exe')
elif 'MuMuMultiPlayer.exe' in exe: elif 'MuMuMultiPlayer.exe' in exe:
yield exe.replace('MuMuMultiPlayer.exe', 'MuMuManager.exe') yield exe.replace('MuMuMultiPlayer.exe', 'MuMuPlayer.exe')
elif 'MuMuManager.exe' in exe:
yield exe.replace('MuMuManager.exe', 'MuMuPlayer.exe')
elif 'MEmuConsole.exe' in exe: elif 'MEmuConsole.exe' in exe:
yield exe.replace('MEmuConsole.exe', 'MEmu.exe') yield exe.replace('MEmuConsole.exe', 'MEmu.exe')
else: else:
@ -316,7 +322,7 @@ class EmulatorManager(EmulatorManagerBase):
Get recently executed programs in UserAssist Get recently executed programs in UserAssist
https://github.com/forensicmatt/MonitorUserAssist https://github.com/forensicmatt/MonitorUserAssist
Returns: Yields:
str: Path to emulator executables, may contains duplicate values str: Path to emulator executables, may contains duplicate values
""" """
path = r'Software\Microsoft\Windows\CurrentVersion\Explorer\UserAssist' path = r'Software\Microsoft\Windows\CurrentVersion\Explorer\UserAssist'
@ -447,6 +453,31 @@ class EmulatorManager(EmulatorManagerBase):
uninstall = res.group(1) if res else uninstall uninstall = res.group(1) if res else uninstall
yield uninstall yield uninstall
@staticmethod
def iter_running_emulator():
"""
Yields:
str: Path to emulator executables, may contains duplicate values
"""
try:
import psutil
except ModuleNotFoundError:
return
# Since this is a one-time-usage, we access psutil._psplatform.Process directly
# to bypass the call of psutil.Process.is_running().
# This only costs about 0.017s.
for pid in psutil.pids():
proc = psutil._psplatform.Process(pid)
try:
exe = proc.cmdline()
exe = exe[0].replace(r'\\', '/').replace('\\', '/')
except (psutil.AccessDenied, IndexError):
# psutil.AccessDenied
continue
if Emulator.is_emulator(exe):
yield exe
@cached_property @cached_property
def all_emulators(self) -> t.List[Emulator]: def all_emulators(self) -> t.List[Emulator]:
""" """
@ -474,7 +505,7 @@ class EmulatorManager(EmulatorManagerBase):
exe.add(ld) exe.add(ld)
# Uninstall registry # Uninstall registry
for uninstall in self.iter_uninstall_registry(): for uninstall in EmulatorManager.iter_uninstall_registry():
# Find emulator executable from uninstaller # Find emulator executable from uninstaller
for file in iter_folder(abspath(os.path.dirname(uninstall)), ext='.exe'): for file in iter_folder(abspath(os.path.dirname(uninstall)), ext='.exe'):
if Emulator.is_emulator(file) and os.path.exists(file): if Emulator.is_emulator(file) and os.path.exists(file):
@ -488,9 +519,14 @@ class EmulatorManager(EmulatorManagerBase):
if Emulator.is_emulator(file) and os.path.exists(file): if Emulator.is_emulator(file) and os.path.exists(file):
exe.add(file) exe.add(file)
# Running
for file in EmulatorManager.iter_running_emulator():
if os.path.exists(file):
exe.add(file)
# De-redundancy
exe = [Emulator(path).path for path in exe if Emulator.is_emulator(path)] exe = [Emulator(path).path for path in exe if Emulator.is_emulator(path)]
exe = sorted(set(exe)) exe = [Emulator(path) for path in remove_duplicated_path(exe)]
exe = [Emulator(path) for path in exe]
return exe return exe
@cached_property @cached_property

View File

@ -6,7 +6,8 @@ from pydantic import BaseModel
from module.base.decorator import cached_property, del_cached_property from module.base.decorator import cached_property, del_cached_property
from module.base.utils import SelectedGrids from module.base.utils import SelectedGrids
from module.device.connection import Connection from module.device.connection import Connection
from module.device.platform.emulator_base import EmulatorInstanceBase, EmulatorManagerBase from module.device.method.utils import get_serial_pair
from module.device.platform.emulator_base import EmulatorInstanceBase, EmulatorManagerBase, remove_duplicated_path
from module.logger import logger from module.logger import logger
@ -47,8 +48,20 @@ class PlatformBase(Connection, EmulatorManagerBase):
@cached_property @cached_property
def emulator_info(self) -> EmulatorInfo: def emulator_info(self) -> EmulatorInfo:
emulator = self.config.EmulatorInfo_Emulator emulator = self.config.EmulatorInfo_Emulator
name = str(self.config.EmulatorInfo_name).strip().replace('\n', '') if emulator == 'auto':
path = str(self.config.EmulatorInfo_path).strip().replace('\n', '') emulator = ''
def parse_info(value):
if isinstance(value, str):
value = value.strip().replace('\n', '')
if value in ['None', 'False', 'True']:
value = ''
return value
else:
return ''
name = parse_info(self.config.EmulatorInfo_name)
path = parse_info(self.config.EmulatorInfo_path)
return EmulatorInfo( return EmulatorInfo(
emulator=emulator, emulator=emulator,
@ -68,8 +81,14 @@ class PlatformBase(Connection, EmulatorManagerBase):
path=data.path, path=data.path,
name=data.name, name=data.name,
) )
# Redirect emulator-5554 to 127.0.0.1:5555
serial = self.serial
port_serial, _ = get_serial_pair(self.serial)
if port_serial is not None:
serial = port_serial
instance = self.find_emulator_instance( instance = self.find_emulator_instance(
serial=str(self.config.Emulator_Serial).strip(), serial=serial,
name=data.name, name=data.name,
path=data.path, path=data.path,
emulator=data.emulator, emulator=data.emulator,
@ -117,7 +136,7 @@ class PlatformBase(Connection, EmulatorManagerBase):
# Search by serial # Search by serial
select = instances.select(**search_args) select = instances.select(**search_args)
if select.count == 0: if select.count == 0:
logger.warning(f'No emulator instance with {search_args}') logger.warning(f'No emulator instance with {search_args}, serial invalid')
return None return None
if select.count == 1: if select.count == 1:
instance = select[0] instance = select[0]
@ -130,9 +149,9 @@ class PlatformBase(Connection, EmulatorManagerBase):
search_args['name'] = name search_args['name'] = name
select = instances.select(**search_args) select = instances.select(**search_args)
if select.count == 0: if select.count == 0:
logger.warning(f'No emulator instances with {search_args}') logger.warning(f'No emulator instances with {search_args}, name invalid')
return None search_args.pop('name')
if select.count == 1: elif select.count == 1:
instance = select[0] instance = select[0]
logger.hr('Emulator instance', level=2) logger.hr('Emulator instance', level=2)
logger.info(f'Found emulator instance: {instance}') logger.info(f'Found emulator instance: {instance}')
@ -143,9 +162,9 @@ class PlatformBase(Connection, EmulatorManagerBase):
search_args['path'] = path search_args['path'] = path
select = instances.select(**search_args) select = instances.select(**search_args)
if select.count == 0: if select.count == 0:
logger.warning(f'No emulator instances with {search_args}') logger.warning(f'No emulator instances with {search_args}, path invalid')
return None search_args.pop('path')
if select.count == 1: elif select.count == 1:
instance = select[0] instance = select[0]
logger.hr('Emulator instance', level=2) logger.hr('Emulator instance', level=2)
logger.info(f'Found emulator instance: {instance}') logger.info(f'Found emulator instance: {instance}')
@ -156,9 +175,28 @@ class PlatformBase(Connection, EmulatorManagerBase):
search_args['type'] = emulator search_args['type'] = emulator
select = instances.select(**search_args) select = instances.select(**search_args)
if select.count == 0: if select.count == 0:
logger.warning(f'No emulator instances with {search_args}') logger.warning(f'No emulator instances with {search_args}, type invalid')
return None search_args.pop('type')
if select.count == 1: 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')
for exe in running:
logger.info(exe)
if len(running) == 1:
logger.info('Only one running emulator')
# Same as searching path
search_args['path'] = running[0]
select = instances.select(**search_args)
if select.count == 0:
logger.warning(f'No emulator instances with {search_args}, path invalid')
search_args.pop('path')
elif select.count == 1:
instance = select[0] instance = select[0]
logger.hr('Emulator instance', level=2) logger.hr('Emulator instance', level=2)
logger.info(f'Found emulator instance: {instance}') logger.info(f'Found emulator instance: {instance}')
@ -167,9 +205,3 @@ class PlatformBase(Connection, EmulatorManagerBase):
# Still too many instances # Still too many instances
logger.warning(f'Found multiple emulator instances with {search_args}') logger.warning(f'Found multiple emulator instances with {search_args}')
return None return None
if __name__ == '__main__':
self = PlatformBase('alas')
d = self.emulator_instance
print(d)