import ctypes import os import threading import time from dataclasses import dataclass from model.custom_struct import GameConfig, AccountInfo from model.global_manager import GM from scripts.script import Script from tools.dm_operate import Dm from tools.emulator.emulator import Emulator from tools.emulator.ld_operate import LD from tools.emulator.ys_operate import YS from tools.upload_log import LogInfo from tools.utils import Utils from tools.log import logger @dataclass class ScriptHelper: dm: Dm = None # 大漠对象 bind_handle: int = None # 绑定句柄 top_handle: int = None # 顶层句柄 emulator_index: int = None # 模拟器下标 timeout: int = None # 超时时间 game_id: str = None # 游戏id game_type: str = None # 游戏类型 account: str = None # 账号 password: str = None # 密码 retained: int = None # 新增留存 log_uuid: str = None # 日志uuid log_port: int = None # 日志端口 emu: Emulator = None # 模拟器对象 class MyHelper: """模拟器任务助手""" _lock = threading.Lock() def __init__(self, window_id: int, game_config: GameConfig, account_info: AccountInfo): self.window_id = window_id self.game_config = game_config self.account_info = account_info self.emulator_type = self.game_config.emulator_type self.emu = self._initialize_emulator_instance() self.emulator_index = self._get_emulator() self.dm = Dm() self.emulator_info = None self.wy_ip = GM.wy_ip self.log_port = GM.script_log_port self.script = Script(self.game_config.script) self.log_uuid = f'{time.time()}_{self.account_info.account}' self.upload_log = LogInfo(self.log_uuid) self.upload_account_log(GM.device_info.pc_name, GM.device_info.officer, GM.device_mac) def _initialize_emulator_instance(self): emulator_mapping = { '雷电模拟器4-32位': lambda: LD(4), '雷电模拟器4-64位': lambda: LD(64), '雷电模拟器9': lambda: LD(9), '夜神模拟器7-32位': lambda: YS(7), '夜神模拟器7-64位': lambda: YS(8), '夜神模拟器9': lambda: YS(9) } # 仅当查找到类型时才会实例化,否则返回默认的 LD(9) return emulator_mapping.get(self.emulator_type, lambda: LD(9))() def _get_emulator(self): with MyHelper._lock: index = self.window_id while GM.get_global_control(): if self.emu.is_exists(index): if GM.get_emulator_status(f'{self.emulator_type}-{index}') == 0: GM.set_emulator_status(f'{self.emulator_type}-{index}', 1) return index raise Exception(f'模拟器-{index}状态异常') else: # 模拟器不存在,添加新的模拟器 self.emu.add(f'new-{index}') time.sleep(3) def get_emulator_index(self): return self.emulator_index def set_emulator_index(self, index): self.emulator_index = index def restore_emulator(self): try: self.emu.restore(self.emulator_index, os.path.join(GM.image_path, self.game_config.image)) time.sleep(1) self.emu.rename(self.emulator_index, f'{self.game_config.task_id}-{self.emulator_index}') return True, '还原模拟器成功' except Exception as e: return False, str(e) def modify_emulator(self): try: self.emu.modify(self.emulator_index, cpu=self.game_config.cpu, memory=self.game_config.memory, resolution=self.game_config.resolution, manufacturer=self.account_info.manufacturer, model=self.account_info.model, pnumber=self.account_info.pnumber, imei=self.account_info.imei, imsi=self.account_info.imsi, simserial=self.account_info.simserial, androidid=self.account_info.androidid, mac=self.account_info.mac if self.emu.emulator_type == 'ld' else self.account_info.mac_colon ) return True, '修改模拟器信息成功' except Exception as e: return False, str(e) def switch_emulator_area(self): if self.wy_ip is None: return False, "未初始化无忧IP" self.wy_ip.set_wy_token() if len(self.account_info.number_operatorTypes.split('_')) < 1: return False, "number_operatorTypes参数错误" return self.wy_ip.switch_emulator_area(self.emulator_index, self.account_info.number_operatorTypes.split('_')[1], self.account_info.number_operatorTypes.split('_')[0]) def confirm_emulator_area(self): result, message = self.wy_ip.queryProcessProxyRegion(self.emulator_info.vbox_pid) if not result: return result, message if message != self.account_info.number_operatorTypes.split('_')[0]: return False, f'当前模拟器切换地区失败,目标地区:{self.account_info.number_operatorTypes.split("_")[0]},当前地区:{message}' return True, '当前模拟器切换地区成功' def start_emulator_and_set_position(self): """启动模拟器并设置窗口位置""" try: # 获取共享路径 share_path = GM.get_share_path() emulator_share_dir = os.path.join(share_path, str(self.window_id)) # 如果目录不存在则创建 if not os.path.exists(emulator_share_dir): os.makedirs(emulator_share_dir) # 设置共享目录 self.emu.set_share_dir(self.emulator_index, emulator_share_dir) # 启动模拟器并确认启动状态 result, message = self.emu.start_and_confirm(self.emulator_index) # if result: # self.emulator_info = message # # 设置模拟器窗口位置 # self.emu.set_emulator_position(self.emulator_info.top_handle, self.window_id) # time.sleep(1) # return True, f"模拟器编号:{self.emulator_index},启动成功" # return False, f"模拟器编号:{self.emulator_index},,启动失败,错误信息:{message}" except Exception as e: return False, f"模拟器编号:{self.emulator_index},启动失败,错误信息:{str(e)}" def close_emulator_and_confirm(self): """关闭模拟器并确保所有相关进程被终止。""" # 检查模拟器是否正在运行 if self.emu.is_running(self.emulator_index): self.emu.close(self.emulator_index) time.sleep(3) # 等待模拟器关闭的缓冲时间 # 获取模拟器信息 emu_info = self.emu.get_info(self.emulator_index) if emu_info: # 确认主进程 pid 有效 if emu_info.pid > 0: Utils.kill_process(emu_info.pid) # 确认 VBox 进程 pid 有效 if emu_info.vbox_pid > 0: Utils.kill_process(emu_info.vbox_pid) def bind_emulator(self): try: if self.game_config.script.endswith('.dll'): result = self.script.execute('dm_bind', self.emulator_index, self.emulator_info.bind_handle, timeout=10 ) return True, result elif self.game_config.script.endswith('.py'): script_helper = ScriptHelper(dm=self.dm, emu=self.emu, emulator_index=self.emulator_index, top_handle=self.emulator_info.top_handle, bind_handle=self.emulator_info.bind_handle, game_type=self.game_config.game_type, game_id=self.game_config.task_id, account=self.account_info.account, password=self.account_info.password, retained=self.account_info.retained, log_uuid=self.log_uuid, log_port=self.log_port, timeout=self.game_config.timeout * 60 ) result = self.script.execute('dm_bind', script_helper, timeout=10) return True, result else: return False, '脚本类型错误' except Exception as e: return False, str(e) def start_game(self): """启动游戏并处理可能的异常。 Returns: tuple: 成功与否的标志和结果或错误信息。 """ logger.info(f"进游戏了1",100) try: if self.game_config.script.endswith('.dll'): logger.info(f"进游戏了3", 100) # 获取字符串的字节数据 task_id_bytes = self.game_config.task_id.encode('gbk') account_bytes = self.account_info.account.encode('gbk') script_path_bytes = self.script.script_path.encode('gbk') log_uuid_bytes = self.log_uuid.encode('gbk') # 创建 c_char_p 指针 task_id_ptr = ctypes.c_char_p(task_id_bytes) account_ptr = ctypes.c_char_p(account_bytes) script_path_ptr = ctypes.c_char_p(script_path_bytes) log_uuid_ptr = ctypes.c_char_p(log_uuid_bytes) result = self.script.execute( 'script_start_game', self.emulator_index, task_id_ptr, self.emulator_info.bind_handle, account_ptr, self.game_config.timeout * 60 * 1000, script_path_ptr, GM.script_log_port, log_uuid_ptr, timeout=30 * 60 ) return True, result elif self.game_config.script.endswith('.py'): logger.info(f"进游戏了2", 100) script_helper = ScriptHelper(dm=self.dm, emu=self.emu, emulator_index=self.emulator_index, top_handle=self.emulator_info.top_handle, bind_handle=self.emulator_info.bind_handle, game_id=self.game_config.task_id, game_type=self.game_config.game_type, account=self.account_info.account, password=self.account_info.password, retained=self.account_info.retained, log_uuid=self.log_uuid, log_port=self.log_port, timeout=self.game_config.timeout * 60 ) result = self.script.execute('script_start_game',script_helper, timeout=30*60) logger.info(f"进游戏了4", 100) return True, result else: return False, "脚本文件格式错误" except Exception as e: return False, f"脚本_启动游戏,错误: {e}" def login_game(self): """登录游戏并处理可能的异常。 Returns: tuple: 成功与否的标志和结果或错误信息。 """ try: if self.game_config.script.endswith('.dll'): # 获取字符串的字节数据 account_bytes = self.account_info.account.encode('gbk') password_bytes = self.account_info.password.encode('gbk') wechat_order_id_bytes = self.account_info.wechat_order_id.encode('gbk') task_id_bytes = self.game_config.task_id.encode('gbk') script_path_bytes = self.script.script_path.encode('gbk') channel_id_bytes = self.game_config.channel_id.encode('gbk') # 创建 c_char_p 指针 account_ptr = ctypes.c_char_p(account_bytes) password_ptr = ctypes.c_char_p(password_bytes) wechat_order_id_ptr = ctypes.c_char_p(wechat_order_id_bytes) task_id_ptr = ctypes.c_char_p(task_id_bytes) script_path_ptr = ctypes.c_char_p(script_path_bytes) channel_id_ptr = ctypes.c_char_p(channel_id_bytes) result = self.script.execute( 'script_login_game', self.emulator_index, # 窗口下标 self.game_config.timeout * 60 * 1000, # 超时时间 self.emulator_info.bind_handle, account_ptr, password_ptr, self.account_info.retained, wechat_order_id_ptr, 1, task_id_ptr, script_path_ptr, GM.script_log_port, channel_id_ptr, timeout=30 * 60 ) return True, result elif self.game_config.script.endswith('.py'): script_helper = ScriptHelper(dm=self.dm, emu=self.emu, emulator_index=self.emulator_index, top_handle=self.emulator_info.top_handle, bind_handle=self.emulator_info.bind_handle, game_id=self.game_config.task_id, game_type=self.game_config.game_type, account=self.account_info.account, password=self.account_info.password, retained=self.account_info.retained, log_uuid=self.log_uuid, log_port=self.log_port, timeout=self.game_config.timeout * 60 ) result = self.script.execute('script_login_game',script_helper, timeout=30 * 60) return True, result else: return False, "脚本文件格式错误" except Exception as e: return False, f'脚本_登陆游戏,错误: {e}' def main_task(self): """执行主线任务并处理可能的异常。 Returns: tuple: 成功与否的标志和结果或错误信息。 """ try: if self.game_config.script.endswith('.dll'): # 获取字符串的字节数据 account_bytes = self.account_info.account.encode('gbk') task_id_bytes = self.game_config.task_id.encode('gbk') script_path_bytes = self.script.script_path.encode('gbk') # 创建 c_char_p 指针 account_ptr = ctypes.c_char_p(account_bytes) task_id_ptr = ctypes.c_char_p(task_id_bytes) script_path_ptr = ctypes.c_char_p(script_path_bytes) result = self.script.execute( 'script_main_task', self.emulator_index, self.game_config.timeout * 60 * 1000, account_ptr, # 直接传递数据地址 0, self.emulator_info.bind_handle, task_id_ptr, # 直接传递数据地址 script_path_ptr, # 直接传递数据地址 GM.script_log_port, timeout=60 * 60 ) return True, result elif self.game_config.script.endswith('.py'): script_helper = ScriptHelper(dm=self.dm, emu=self.emu, emulator_index=self.emulator_index, top_handle=self.emulator_info.top_handle, bind_handle=self.emulator_info.bind_handle, game_id=self.game_config.task_id, game_type=self.game_config.game_type, account=self.account_info.account, password=self.account_info.password, retained=self.account_info.retained, log_uuid=self.log_uuid, log_port=self.log_port, timeout=self.game_config.timeout * 60 ) result = self.script.execute('script_main_task', script_helper, timeout=60 * 60) return True, result else: return False, "脚本文件格式错误" except Exception as e: return False, f'脚本_教程主线,错误: {e}' def upload_account_log(self, pc_code: str, operator: str, pc_mac: str): self.upload_log.set_account_info(game_id=int(self.game_config.task_id), account_type=self.account_info.game_type, pwd=self.account_info.password, account=self.account_info.account, task_type=self.account_info.retained) self.upload_log.set_pc_info(pc_code=pc_code, operator=operator, pc_mac=pc_mac, pc_ip='') self.upload_log.upload_pull_account_log(1) def upload_device_log(self, status: int): try: self.upload_log.set_device_info(device_id=self.emu.get_android_id(self.emulator_index), device_manufacturer=self.emu.get_manufacturer(self.emulator_index), device_model=self.emu.get_model(self.emulator_index), device_imei=self.emu.get_IMEI(self.emulator_index), device_sdk=self.emu.get_android_version(self.emulator_index), device_mac=self.emu.get_mac(self.emulator_index), device_number=self.emu.get_phone_number(self.emulator_index), script_device_id='') self.upload_log.set_simulator_info(simulator_code=str(self.emulator_index), simulator_mac='', simulator_ip_city='', simulator_ip=self.emu.get_net_ip(self.emulator_index) ) ret = self.upload_log.upload_start_simulator_log(status=status) return ret except Exception as e: return False, f'脚本_设备信息,错误: {e}'