StarRailCopilot/module/device/connection_attr.py

267 lines
9.6 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import os
import re
import adbutils
import uiautomator2 as u2
from adbutils import AdbClient, AdbDevice
from module.base.decorator import cached_property
from module.config.config import AzurLaneConfig
from module.config.utils import deep_iter
from module.exception import RequestHumanTakeover
from module.logger import logger
class ConnectionAttr:
config: AzurLaneConfig
serial: str
adb_binary_list = [
'./bin/adb/adb.exe',
'./toolkit/Lib/site-packages/adbutils/binaries/adb.exe',
'/usr/bin/adb'
]
def __init__(self, config):
"""
Args:
config (AzurLaneConfig, str): Name of the user config under ./config
"""
logger.hr('Device', level=1)
if isinstance(config, str):
self.config = AzurLaneConfig(config, task=None)
else:
self.config = config
# Init adb client
logger.attr('AdbBinary', self.adb_binary)
# Monkey patch to custom adb
adbutils.adb_path = lambda: self.adb_binary
# Remove global proxies, or uiautomator2 will go through it
for k in list(os.environ.keys()):
if k.lower().endswith('_proxy'):
del os.environ[k]
# Cache adb_client
_ = self.adb_client
# Parse custom serial
self.serial = str(self.config.Emulator_Serial)
self.serial_check()
self.config.DEVICE_OVER_HTTP = self.is_over_http
def serial_check(self):
"""
serial check
"""
# Chinese colon
if '' in self.serial:
self.serial = self.serial.replace('', ':')
logger.warning(f'Serial {self.config.Emulator_Serial} is revised to {self.serial}')
self.config.Emulator_Serial = self.serial
if self.is_bluestacks4_hyperv:
self.serial = self.find_bluestacks4_hyperv(self.serial)
if self.is_bluestacks5_hyperv:
self.serial = self.find_bluestacks5_hyperv(self.serial)
if "127.0.0.1:58526" in self.serial:
logger.warning('Serial 127.0.0.1:58526 seems to be WSA, '
'please use "wsa-0" or others instead')
raise RequestHumanTakeover
if self.is_wsa:
self.serial = '127.0.0.1:58526'
if self.config.Emulator_ScreenshotMethod != 'uiautomator2' \
or self.config.Emulator_ControlMethod != 'uiautomator2':
with self.config.multi_set():
self.config.Emulator_ScreenshotMethod = 'uiautomator2'
self.config.Emulator_ControlMethod = 'uiautomator2'
if self.is_over_http:
if self.config.Emulator_ScreenshotMethod not in ["ADB", "uiautomator2", "aScreenCap"] \
or self.config.Emulator_ControlMethod not in ["ADB", "uiautomator2", "minitouch"]:
logger.warning(
f'When connecting to a device over http: {self.serial} '
f'ScreenshotMethod can only use ["ADB", "uiautomator2", "aScreenCap"], '
f'ControlMethod can only use ["ADB", "uiautomator2", "minitouch"]'
)
raise RequestHumanTakeover
@cached_property
def is_bluestacks4_hyperv(self):
return "bluestacks4-hyperv" in self.serial
@cached_property
def is_bluestacks5_hyperv(self):
return "bluestacks5-hyperv" in self.serial
@cached_property
def is_bluestacks_hyperv(self):
return self.is_bluestacks4_hyperv or self.is_bluestacks5_hyperv
@cached_property
def is_wsa(self):
return bool(re.match(r'^wsa', self.serial))
@cached_property
def is_mumu_family(self):
# 127.0.0.1:7555
# 127.0.0.1:16384 + 32*n
return self.serial == '127.0.0.1:7555' or self.serial.startswith('127.0.0.1:16')
@cached_property
def is_emulator(self):
return self.serial.startswith('emulator-') or self.serial.startswith('127.0.0.1:')
@cached_property
def is_network_device(self):
return bool(re.match(r'\d+\.\d+\.\d+\.\d+:\d+', self.serial))
@cached_property
def is_over_http(self):
return bool(re.match(r"^https?://", self.serial))
@cached_property
def is_chinac_phone_cloud(self):
# Phone cloud with public ADB connection
# Serial like xxx.xxx.xxx.xxx:301
return bool(re.search(r":30[0-9]$", self.serial))
@staticmethod
def find_bluestacks4_hyperv(serial):
"""
Find dynamic serial of BlueStacks4 Hyper-V Beta.
Args:
serial (str): 'bluestacks4-hyperv', 'bluestacks4-hyperv-2' for multi instance, and so on.
Returns:
str: 127.0.0.1:{port}
"""
from winreg import HKEY_LOCAL_MACHINE, OpenKey, QueryValueEx
logger.info("Use BlueStacks4 Hyper-V Beta")
logger.info("Reading Realtime adb port")
if serial == "bluestacks4-hyperv":
folder_name = "Android"
else:
folder_name = f"Android_{serial[19:]}"
try:
with OpenKey(HKEY_LOCAL_MACHINE,
rf"SOFTWARE\BlueStacks_bgp64_hyperv\Guests\{folder_name}\Config") as key:
port = QueryValueEx(key, "BstAdbPort")[0]
except FileNotFoundError:
logger.error(rf'Unable to find registry HKEY_LOCAL_MACHINE\SOFTWARE\BlueStacks_bgp64_hyperv\Guests\{folder_name}\Config')
logger.error('Please confirm that your are using BlueStack 4 hyper-v and not regular BlueStacks 4')
logger.error(r'Please check if there is any other emulator instances under '
r'registry HKEY_LOCAL_MACHINE\SOFTWARE\BlueStacks_bgp64_hyperv\Guests')
raise RequestHumanTakeover
logger.info(f"New adb port: {port}")
return f"127.0.0.1:{port}"
@staticmethod
def find_bluestacks5_hyperv(serial):
"""
Find dynamic serial of BlueStacks5 Hyper-V.
Args:
serial (str): 'bluestacks5-hyperv', 'bluestacks5-hyperv-1' for multi instance, and so on.
Returns:
str: 127.0.0.1:{port}
"""
from winreg import HKEY_LOCAL_MACHINE, OpenKey, QueryValueEx
logger.info("Use BlueStacks5 Hyper-V")
logger.info("Reading Realtime adb port")
if serial == "bluestacks5-hyperv":
parameter_name = r"bst\.instance\.(Nougat64|Pie64|Rvc64)\.status\.adb_port"
else:
parameter_name = rf"bst\.instance\.(Nougat64|Pie64|Rvc64)_{serial[19:]}\.status.adb_port"
try:
with OpenKey(HKEY_LOCAL_MACHINE, r"SOFTWARE\BlueStacks_nxt") as key:
directory = QueryValueEx(key, 'UserDefinedDir')[0]
except FileNotFoundError:
try:
with OpenKey(HKEY_LOCAL_MACHINE, r"SOFTWARE\BlueStacks_nxt_cn") as key:
directory = QueryValueEx(key, 'UserDefinedDir')[0]
except FileNotFoundError:
logger.error('Unable to find registry HKEY_LOCAL_MACHINE\SOFTWARE\BlueStacks_nxt '
'or HKEY_LOCAL_MACHINE\SOFTWARE\BlueStacks_nxt_cn')
logger.error('Please confirm that you are using BlueStacks 5 hyper-v and not regular BlueStacks 5')
raise RequestHumanTakeover
logger.info(f"Configuration file directory: {directory}")
with open(os.path.join(directory, 'bluestacks.conf'), encoding='utf-8') as f:
content = f.read()
port = re.search(rf'{parameter_name}="(\d+)"', content)
if port is None:
logger.warning(f"Did not match the result: {serial}.")
raise RequestHumanTakeover
port = port.group(2)
logger.info(f"Match to dynamic port: {port}")
return f"127.0.0.1:{port}"
@cached_property
def adb_binary(self):
# Try adb in deploy.yaml
from module.webui.setting import State
file = State.deploy_config.AdbExecutable
file = file.replace('\\', '/')
if os.path.exists(file):
return os.path.abspath(file)
# Try existing adb.exe
for file in self.adb_binary_list:
if os.path.exists(file):
return os.path.abspath(file)
# Try adb in python environment
import sys
file = os.path.join(sys.executable, '../Lib/site-packages/adbutils/binaries/adb.exe')
file = os.path.abspath(file).replace('\\', '/')
if os.path.exists(file):
return file
# Use adb in system PATH
file = 'adb'
return file
@cached_property
def adb_client(self) -> AdbClient:
host = '127.0.0.1'
port = 5037
# Trying to get adb port from env
env = os.environ.get('ANDROID_ADB_SERVER_PORT', None)
if env is not None:
try:
port = int(env)
except ValueError:
logger.warning(f'Invalid environ variable ANDROID_ADB_SERVER_PORT={port}, using default port')
logger.attr('AdbClient', f'AdbClient({host}, {port})')
return AdbClient(host, port)
@cached_property
def adb(self) -> AdbDevice:
return AdbDevice(self.adb_client, self.serial)
@cached_property
def u2(self) -> u2.Device:
if self.is_over_http:
# Using uiautomator2_http
device = u2.connect(self.serial)
else:
# Normal uiautomator2
if self.serial.startswith('emulator-') or self.serial.startswith('127.0.0.1:'):
device = u2.connect_usb(self.serial)
else:
device = u2.connect(self.serial)
# Stay alive
device.set_new_command_timeout(604800)
logger.attr('u2.Device', f'Device(atx_agent_url={device._get_atx_agent_url()})')
return device