Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/dev' into winapiSp
Browse files Browse the repository at this point in the history
  • Loading branch information
PPPlatelet committed Jul 28, 2024
2 parents b3d7fa4 + 85f19a2 commit c96132e
Show file tree
Hide file tree
Showing 6 changed files with 102 additions and 75 deletions.
1 change: 1 addition & 0 deletions alas.py
Original file line number Diff line number Diff line change
Expand Up @@ -563,6 +563,7 @@ def loop(self):
# Reboot emulator
if not self.device.emulator_check():
self.emurestart(task)
self.device.config = self.config
# Skip first restart
if self.is_first_task and task == 'Restart':
logger.info('Skip task `Restart` at scheduler start')
Expand Down
38 changes: 12 additions & 26 deletions module/device/platform/api_windows.py
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@ def execute(command: str, silentstart: bool, start: bool) -> tuple:
'"E:\\Program Files\\Netease\\MuMu Player 12\\shell\\MuMuPlayer.exe" -v 1'
Returns:
process: tuple(processhandle, threadhandle, processid, mainthreadid),
process: PROCESS_INFORMATION,
focusedwindow: tuple(hwnd, WINDOWPLACEMENT)
Raises:
Expand Down Expand Up @@ -206,9 +206,9 @@ def execute(command: str, silentstart: bool, start: bool) -> tuple:
lpStartupInfo.cb = sizeof(STARTUPINFOW)
lpStartupInfo.dwFlags = STARTF_USESHOWWINDOW
if start:
lpStartupInfo.wShowWindow = SW_HIDE if silentstart else SW_MINIMIZE
lpStartupInfo.wShowWindow = SW_FORCEMINIMIZE if silentstart else SW_MINIMIZE
else:
lpStartupInfo.wShowWindow = SW_HIDE
lpStartupInfo.wShowWindow = SW_FORCEMINIMIZE
lpProcessInformation = PROCESS_INFORMATION()

success = CreateProcessW(
Expand All @@ -231,7 +231,7 @@ def execute(command: str, silentstart: bool, start: bool) -> tuple:
return lpProcessInformation, focusedwindow
else:
closehandle(lpProcessInformation.hProcess, lpProcessInformation.hThread)
return (), focusedwindow
return None, focusedwindow


def terminate_process(pid: int) -> bool:
Expand Down Expand Up @@ -342,9 +342,8 @@ def kill_process_by_regex(regex: str) -> int:
"""
count = 0

processes = _enum_processes()
try:
for lppe32 in processes:
for lppe32 in _enum_processes():
pid = lppe32.th32ProcessID
cmdline = get_cmdline(lppe32.th32ProcessID)
if not re.search(regex, cmdline):
Expand All @@ -353,7 +352,6 @@ def kill_process_by_regex(regex: str) -> int:
terminate_process(pid)
count += 1
except IterationFinished:
processes.close()
return count


Expand Down Expand Up @@ -430,9 +428,8 @@ def get_thread(pid: int) -> int:
"""
mainthreadid = 0
minstarttime = MAXULONGLONG
threads = _enum_threads()
try:
for lpte32 in threads:
for lpte32 in _enum_threads():
if lpte32.th32OwnerProcessID != pid:
continue

Expand All @@ -444,11 +441,10 @@ def get_thread(pid: int) -> int:
minstarttime = threadstarttime
mainthreadid = lpte32.th32ThreadID
except IterationFinished:
threads.close()
return mainthreadid


def _get_process(pid: int) -> tuple:
def _get_process(pid: int) -> PROCESS_INFORMATION:
"""
Get emulator's handle.
Expand All @@ -470,12 +466,12 @@ def _get_process(pid: int) -> tuple:
CloseHandle(hProcess)
report("OpenThread failed.", level=30)

return hProcess, hThread, pid, tid
return PROCESS_INFORMATION(hProcess, hThread, pid, tid)
except Exception as e:
logger.warning(f"Failed to get process and thread handles: {e}")
return None, None, pid, tid
return PROCESS_INFORMATION(None, None, pid, tid)

def get_process(instance: EmulatorInstance) -> tuple:
def get_process(instance: EmulatorInstance) -> PROCESS_INFORMATION:
"""
Get emulator's process.
Expand All @@ -491,26 +487,22 @@ def get_process(instance: EmulatorInstance) -> tuple:
OSError if any winapi failed.
IterationFinished if enumeration completed.
"""
processes = _enum_processes()
for lppe32 in processes:
for lppe32 in _enum_processes():
pid = lppe32.th32ProcessID
cmdline = get_cmdline(pid)
if not instance.path in cmdline:
continue
if instance == Emulator.MuMuPlayer12:
match = re.search(r'-v\s*(\d+)', cmdline)
if match and int(match.group(1)) == instance.MuMuPlayer12_id:
processes.close()
return _get_process(pid)
elif instance == Emulator.LDPlayerFamily:
match = re.search(r'index=\s*(\d+)', cmdline)
if match and int(match.group(1)) == instance.LDPlayer_id:
processes.close()
return _get_process(pid)
else:
matchname = re.search(fr'{instance.name}(\s+|$)', cmdline)
if matchname and matchname.group(0).strip() == instance.name:
processes.close()
return _get_process(pid)


Expand All @@ -526,13 +518,7 @@ def switch_window(hwnds: list, arg: int = SW_SHOWNORMAL) -> bool:
bool:
"""
for hwnd in hwnds:
if not IsWindow(hwnd):
continue
if GetParent(hwnd):
continue
rect = RECT()
GetWindowRect(hwnd, byref(rect))
if {rect.left, rect.top, rect.right, rect.bottom} == {0}:
if not GetWindow(hwnd, GW_CHILD):
continue
ShowWindow(hwnd, arg)
return True
22 changes: 11 additions & 11 deletions module/device/platform/platform_windows.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ class EmulatorUnknown(Exception):

class PlatformWindows(PlatformBase, EmulatorManager):
# Quadruple, contains the kernel process object, kernel thread object, process ID and thread ID.
process: tuple = ()
process = None
# Window handles of the target process.
hwnds: list = []
# Pair, contains the hwnd of the focused window and a WINDOWPLACEMENT object.
Expand All @@ -31,7 +31,7 @@ def __execute(self, command: str, start: bool) -> bool:
if self.process:
if not all(self.process[:2]):
api_windows.closehandle(*self.process[:2])
self.process = ()
self.process = None

if self.hwnds:
self.hwnds = []
Expand Down Expand Up @@ -62,7 +62,7 @@ def get_hwnds(pid: int) -> list:
return api_windows.get_hwnds(pid)

@staticmethod
def get_process(instance: api_windows.t.Optional[EmulatorInstance]) -> tuple:
def get_process(instance: api_windows.t.Optional[EmulatorInstance]) -> api_windows.PROCESS_INFORMATION:
return api_windows.get_process(instance)

@staticmethod
Expand Down Expand Up @@ -102,8 +102,8 @@ def _emulator_start(self, instance: EmulatorInstance):
logger.warning(f'Cannot get MuMu instance index from name {instance.name}')
self._start(f'"{exe}" -v {instance.MuMuPlayer12_id}')
elif instance == Emulator.LDPlayerFamily:
# LDPlayer.exe index=0
self._start(f'"{exe}" index={instance.LDPlayer_id}')
# ldconsole.exe launch --index 0
self._start(f'"{Emulator.single_to_console(exe)}" launch --index {instance.LDPlayer_id}')
elif instance == Emulator.NoxPlayerFamily:
# Nox.exe -clone:Nox_1
self._start(f'"{exe}" -clone:{instance.name}')
Expand Down Expand Up @@ -153,12 +153,12 @@ def _emulator_stop(self, instance: EmulatorInstance):
rf')'
)
elif instance == Emulator.MuMuPlayer12:
# E:\Program Files\Netease\MuMu Player 12\shell\MuMuManager.exe api -v 1 shutdown_player
# MuMuManager.exe api -v 1 shutdown_player
if instance.MuMuPlayer12_id is None:
logger.warning(f'Cannot get MuMu instance index from name {instance.name}')
self._stop(f'"{Emulator.single_to_console(exe)}" api -v {instance.MuMuPlayer12_id} shutdown_player')
elif instance == Emulator.LDPlayerFamily:
# E:\Program Files\leidian\LDPlayer9\dnconsole.exe quit --index 0
# ldconsole.exe quit --index 0
self._stop(f'"{Emulator.single_to_console(exe)}" quit --index {instance.LDPlayer_id}')
elif instance == Emulator.NoxPlayerFamily:
# Nox.exe -clone:Nox_1 -quit
Expand All @@ -173,10 +173,10 @@ def _emulator_stop(self, instance: EmulatorInstance):
rf')'
)
elif instance == Emulator.BlueStacks4:
# E:\Program Files (x86)\BluestacksCN\bsconsole.exe quit --name Android
# bsconsole.exe quit --name Android
self._stop(f'"{Emulator.single_to_console(exe)}" quit --name {instance.name}')
elif instance == Emulator.MEmuPlayer:
# F:\Program Files\Microvirt\MEmu\memuc.exe stop -n MEmu_0
# memuc.exe stop -n MEmu_0
self._stop(f'"{Emulator.single_to_console(exe)}" stop -n {instance.name}')
else:
raise EmulatorUnknown(f'Cannot stop an unknown emulator instance: {instance}')
Expand Down Expand Up @@ -241,7 +241,7 @@ def show_package(m):
logger.info(f'Found azurlane packages: {m}')

interval = Timer(0.5).start()
timeout = Timer(300).start()
timeout = Timer(180).start()
while 1:
interval.wait()
interval.reset()
Expand Down Expand Up @@ -340,7 +340,7 @@ def emulator_check(self) -> bool:
else:
if not all(self.process[:2]):
api_windows.closehandle(*self.process[:2])
self.process = ()
self.process = None
raise ProcessLookupError
except api_windows.IterationFinished:
return False
Expand Down
8 changes: 8 additions & 0 deletions module/device/platform/winapi/const_windows.py
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,14 @@
SW_FORCEMINIMIZE = 11
SW_MAX = 11

GW_HWNDFIRST = 0
GW_HWNDLAST = 1
GW_HWNDNEXT = 2
GW_HWNDPREV = 3
GW_OWNER = 4
GW_CHILD = 5
GW_ENABLEDPOPUP = 6

# winbase.h line 377
DEBUG_PROCESS = 0x00000001
DEBUG_ONLY_THIS_PROCESS = 0x00000002
Expand Down
99 changes: 65 additions & 34 deletions module/device/platform/winapi/functions_windows.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
from abc import ABCMeta, abstractmethod
import typing as t
import threading
import time
from functools import wraps

from ctypes import POINTER, WINFUNCTYPE, WinDLL, c_size_t
from ctypes.wintypes import \
Expand All @@ -10,7 +13,9 @@
from module.device.platform.winapi.structures_windows import \
SECURITY_ATTRIBUTES, STARTUPINFOW, WINDOWPLACEMENT, \
PROCESS_INFORMATION, PROCESSENTRY32W, THREADENTRY32, \
FILETIME, RECT
FILETIME

from module.logger import logger

user32 = WinDLL(name='user32', use_last_error=True)
kernel32 = WinDLL(name='kernel32', use_last_error=True)
Expand Down Expand Up @@ -53,15 +58,9 @@
ShowWindow.argtypes = [HWND, INT]
ShowWindow.restype = BOOL

IsWindow = user32.IsWindow
IsWindow.argtypes = [HWND]
IsWindow.restype = BOOL
GetParent = user32.GetParent
GetParent.argtypes = [HWND]
GetParent.restype = HWND
GetWindowRect = user32.GetWindowRect
GetWindowRect.argtypes = [HWND, POINTER(RECT)]
GetWindowRect.restype = BOOL
GetWindow = user32.GetWindow
GetWindow.argtypes = [HWND, UINT]
GetWindow.restype = HWND

EnumWindowsProc = WINFUNCTYPE(BOOL, HWND, LPARAM, use_last_error=True)
EnumWindows = user32.EnumWindows
Expand Down Expand Up @@ -173,7 +172,7 @@ class ProcessHandle(Handle):
_func = OpenProcess
_exitfunc = CloseHandle

def __get_init_args__(self, access, pid) -> tuple:
def __get_init_args__(self, access, pid, uselog, raiseexcept) -> tuple:
return access, False, pid

def _is_invalid_handle(self) -> bool:
Expand All @@ -183,7 +182,7 @@ class ThreadHandle(Handle):
_func = OpenThread
_exitfunc = CloseHandle

def __get_init_args__(self, access, pid) -> tuple:
def __get_init_args__(self, access, pid, uselog, raiseexcept) -> tuple:
return access, False, pid

def _is_invalid_handle(self) -> bool:
Expand All @@ -193,8 +192,8 @@ class CreateSnapshot(Handle):
_func = CreateToolhelp32Snapshot
_exitfunc = CloseHandle

def __get_init_args__(self, arg) -> tuple:
return arg, DWORD(0)
def __get_init_args__(self, access) -> tuple:
return access, DWORD(0)

def _is_invalid_handle(self) -> bool:
from module.device.platform.winapi.const_windows import INVALID_HANDLE_VALUE
Expand Down Expand Up @@ -259,28 +258,60 @@ def open_thread(access, tid, uselog=False, raiseexcept=True) -> ThreadHandle:
def create_snapshot(arg) -> CreateSnapshot:
return CreateSnapshot(arg)

def time_it(func):
from time import time
from functools import wraps
from module.logger import logger
import logging
def get_func_path(func):
module = func.__module__
if hasattr(func, '__qualname__'):
qualname = func.__qualname__
else:
qualname = func.__name__
return f"{module}::{qualname.replace('.', '::')}"

@wraps(func)
def wrapper(*args, **kwargs):
original_level = logger.level
logger.setLevel(logging.DEBUG)
class LogLevelManager:
def __init__(self, new_level):
self.new_level = new_level
self.original_level = logger.level

logger.debug(f"Entering {func.__name__}")
start_time = time()
def __enter__(self):
logger.setLevel(self.new_level)

try:
result = func(*args, **kwargs)
finally:
end_time = time()
logger.debug(f"Exiting {func.__name__}")
logger.debug(f"{func.__name__} executed in {end_time - start_time:.4f} seconds")
def __exit__(self, exc_type, exc_val, exc_tb):
logger.setLevel(self.original_level)

logger.setLevel(original_level)
def Timer(timeout=1):
import logging

return result
return wrapper
def decorator(func):
if not callable(func):
raise TypeError(f"Expected a callable, but got {type(func).__name__}")

@wraps(func)
def wrapper(self, *args, **kwargs):
func_path = get_func_path(func)
result = [TimeoutError(f"Function '{func_path}' timed out after {timeout} seconds")]
stop_event = threading.Event()

with LogLevelManager(logging.DEBUG):
logger.debug(f"Entering {func_path}")
start_time = time.time()

def target():
try:
result[0] = func(self, *args, **kwargs)
except Exception as e:
result[0] = e
finally:
stop_event.set()

thread = threading.Thread(target=target, name=f"Thread-{func_path}")
thread.start()
if not stop_event.wait(timeout):
raise TimeoutError(f"Function '{func_path}' timed out after {timeout} seconds")

end_time = time.time()
if isinstance(result[0], Exception):
raise result[0]
logger.debug(f"Exiting {func_path}")
logger.debug(f"{func_path} executed in {end_time - start_time:.4f} seconds")
return result[0]
return wrapper
return decorator
Loading

0 comments on commit c96132e

Please sign in to comment.