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
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
class EmulatorInstanceBase:
# Serial for adb connection
@ -205,6 +220,14 @@ class EmulatorBase:
class EmulatorManagerBase:
@staticmethod
def iter_running_emulator():
"""
Yields:
str: Path to emulator executables, may contains duplicate values
"""
return
@cached_property
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_windows.py
# 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
@ -70,7 +71,7 @@ class Emulator(EmulatorBase):
def path_to_type(cls, path: str) -> str:
"""
Args:
path: Path to .exe file
path: Path to .exe file, case insensitive
Returns:
str: Emulator type, such as Emulator.NoxPlayer
@ -78,46 +79,49 @@ class Emulator(EmulatorBase):
folder, exe = os.path.split(path)
folder, dir1 = os.path.split(folder)
folder, dir2 = os.path.split(folder)
if exe == 'Nox.exe':
if dir2 == 'Nox':
exe = exe.lower()
dir1 = dir1.lower()
dir2 = dir2.lower()
if exe == 'nox.exe':
if dir2 == 'nox':
return cls.NoxPlayer
elif dir2 == 'Nox64':
elif dir2 == 'nox64':
return cls.NoxPlayer64
else:
return cls.NoxPlayer
if exe == 'Bluestacks.exe':
if dir1 in ['BlueStacks', 'BlueStacks_cn']:
if exe == 'bluestacks.exe':
if dir1 in ['bluestacks', 'bluestacks_cn']:
return cls.BlueStacks4
elif dir1 in ['BlueStacks_nxt', 'BlueStacks_nxt_cn']:
elif dir1 in ['bluestacks_nxt', 'bluestacks_nxt_cn']:
return cls.BlueStacks5
else:
return cls.BlueStacks4
if exe == 'HD-Player.exe':
if dir1 in ['BlueStacks', 'BlueStacks_cn']:
if exe == 'hd-player.exe':
if dir1 in ['bluestacks', 'bluestacks_cn']:
return cls.BlueStacks4
elif dir1 in ['BlueStacks_nxt', 'BlueStacks_nxt_cn']:
elif dir1 in ['bluestacks_nxt', 'bluestacks_nxt_cn']:
return cls.BlueStacks5
else:
return cls.BlueStacks5
if exe == 'dnplayer.exe':
if dir1 == 'LDPlayer':
if dir1 == 'ldplayer':
return cls.LDPlayer3
elif dir1 == 'LDPlayer4':
elif dir1 == 'ldplayer4':
return cls.LDPlayer4
elif dir1 == 'LDPlayer9':
elif dir1 == 'ldplayer9':
return cls.LDPlayer9
else:
return cls.LDPlayer3
if exe == 'NemuPlayer.exe':
if exe == 'nemuplayer.exe':
if dir2 == 'nemu':
return cls.MuMuPlayer
elif dir2 == 'nemu9':
return cls.MuMuPlayerX
else:
return cls.MuMuPlayer
if exe == 'MuMuPlayer.exe':
if exe == 'mumuplayer.exe':
return cls.MuMuPlayer12
if exe == 'MEmu.exe':
if exe == 'memu.exe':
return cls.MEmuPlayer
return ''
@ -143,7 +147,9 @@ class Emulator(EmulatorBase):
elif 'NemuMultiPlayer.exe' in exe:
yield exe.replace('NemuMultiPlayer.exe', 'NemuPlayer.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:
yield exe.replace('MEmuConsole.exe', 'MEmu.exe')
else:
@ -316,7 +322,7 @@ class EmulatorManager(EmulatorManagerBase):
Get recently executed programs in UserAssist
https://github.com/forensicmatt/MonitorUserAssist
Returns:
Yields:
str: Path to emulator executables, may contains duplicate values
"""
path = r'Software\Microsoft\Windows\CurrentVersion\Explorer\UserAssist'
@ -447,6 +453,31 @@ class EmulatorManager(EmulatorManagerBase):
uninstall = res.group(1) if res else 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
def all_emulators(self) -> t.List[Emulator]:
"""
@ -474,7 +505,7 @@ class EmulatorManager(EmulatorManagerBase):
exe.add(ld)
# Uninstall registry
for uninstall in self.iter_uninstall_registry():
for uninstall in EmulatorManager.iter_uninstall_registry():
# Find emulator executable from uninstaller
for file in iter_folder(abspath(os.path.dirname(uninstall)), ext='.exe'):
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):
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 = sorted(set(exe))
exe = [Emulator(path) for path in exe]
exe = [Emulator(path) for path in remove_duplicated_path(exe)]
return exe
@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.utils import SelectedGrids
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
@ -47,8 +48,20 @@ class PlatformBase(Connection, EmulatorManagerBase):
@cached_property
def emulator_info(self) -> EmulatorInfo:
emulator = self.config.EmulatorInfo_Emulator
name = str(self.config.EmulatorInfo_name).strip().replace('\n', '')
path = str(self.config.EmulatorInfo_path).strip().replace('\n', '')
if emulator == 'auto':
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(
emulator=emulator,
@ -68,8 +81,14 @@ class PlatformBase(Connection, EmulatorManagerBase):
path=data.path,
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(
serial=str(self.config.Emulator_Serial).strip(),
serial=serial,
name=data.name,
path=data.path,
emulator=data.emulator,
@ -117,7 +136,7 @@ class PlatformBase(Connection, EmulatorManagerBase):
# Search by serial
select = instances.select(**search_args)
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
if select.count == 1:
instance = select[0]
@ -130,9 +149,9 @@ class PlatformBase(Connection, EmulatorManagerBase):
search_args['name'] = name
select = instances.select(**search_args)
if select.count == 0:
logger.warning(f'No emulator instances with {search_args}')
return None
if select.count == 1:
logger.warning(f'No emulator instances with {search_args}, name invalid')
search_args.pop('name')
elif select.count == 1:
instance = select[0]
logger.hr('Emulator instance', level=2)
logger.info(f'Found emulator instance: {instance}')
@ -143,9 +162,9 @@ class PlatformBase(Connection, EmulatorManagerBase):
search_args['path'] = path
select = instances.select(**search_args)
if select.count == 0:
logger.warning(f'No emulator instances with {search_args}')
return None
if select.count == 1:
logger.warning(f'No emulator instances with {search_args}, path invalid')
search_args.pop('path')
elif select.count == 1:
instance = select[0]
logger.hr('Emulator instance', level=2)
logger.info(f'Found emulator instance: {instance}')
@ -156,9 +175,28 @@ class PlatformBase(Connection, EmulatorManagerBase):
search_args['type'] = emulator
select = instances.select(**search_args)
if select.count == 0:
logger.warning(f'No emulator instances with {search_args}')
return None
if select.count == 1:
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')
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]
logger.hr('Emulator instance', level=2)
logger.info(f'Found emulator instance: {instance}')
@ -167,9 +205,3 @@ class PlatformBase(Connection, EmulatorManagerBase):
# Still too many instances
logger.warning(f'Found multiple emulator instances with {search_args}')
return None
if __name__ == '__main__':
self = PlatformBase('alas')
d = self.emulator_instance
print(d)