async def main(): multiprocessing.freeze_support() parser = get_base_parser() parser.add_argument('apz5_file', default="", type=str, nargs="?", help='Path to an APZ5 file') args = parser.parse_args() if args.apz5_file: logger.info("APZ5 file supplied, beginning patching process...") asyncio.create_task(patch_and_run_game(args.apz5_file)) ctx = OoTContext(args.connect, args.password) ctx.server_task = asyncio.create_task(server_loop(ctx), name="Server Loop") if gui_enabled: ctx.run_gui() ctx.run_cli() ctx.n64_sync_task = asyncio.create_task(n64_sync_task(ctx), name="N64 Sync") await ctx.exit_event.wait() ctx.server_address = None await ctx.shutdown() if ctx.n64_sync_task: await ctx.n64_sync_task
def on_print_json(self, args: dict): if not self.found_items and args.get("type", None) == "ItemSend" and args["receiving"] == args["sending"]: pass # don't want info on other player's local pickups. copy_data = copy.deepcopy(args["data"]) # jsontotextparser is destructive currently logger.info(self.jsontotextparser(args["data"])) if self.rcon_client: cleaned_text = self.raw_json_text_parser(copy_data).replace('"', '') self.rcon_client.send_command(f"/sc game.print(\"Archipelago: {cleaned_text}\")")
async def server_auth(self, password_requested: bool = False): if password_requested and not self.password: await super(FF1Context, self).server_auth(password_requested) if not self.auth: self.awaiting_rom = True logger.info('Awaiting connection to NES to get Player information') return await self.send_connect()
async def factorio_spinup_server(ctx: FactorioContext) -> bool: savegame_name = os.path.abspath("Archipelago.zip") if not os.path.exists(savegame_name): logger.info(f"Creating savegame {savegame_name}") subprocess.run(( executable, "--create", savegame_name )) factorio_process = subprocess.Popen( (executable, "--start-server", savegame_name, *(str(elem) for elem in server_args)), stderr=subprocess.PIPE, stdout=subprocess.PIPE, stdin=subprocess.DEVNULL, encoding="utf-8") factorio_server_logger.info("Started Information Exchange Factorio Server") factorio_queue = Queue() stream_factorio_output(factorio_process.stdout, factorio_queue, factorio_process) stream_factorio_output(factorio_process.stderr, factorio_queue, factorio_process) rcon_client = None try: while not ctx.auth: while not factorio_queue.empty(): msg = factorio_queue.get() factorio_server_logger.info(msg) if "Loading mod AP-" in msg and msg.endswith("(data.lua)"): parts = msg.split() ctx.mod_version = Utils.Version(*(int(number) for number in parts[-2].split("."))) elif "Write data path: " in msg: ctx.write_data_path = Utils.get_text_between(msg, "Write data path: ", " [") if "AppData" in ctx.write_data_path: logger.warning("It appears your mods are loaded from Appdata, " "this can lead to problems with multiple Factorio instances. " "If this is the case, you will get a file locked error running Factorio.") if not rcon_client and "Starting RCON interface at IP ADDR:" in msg: rcon_client = factorio_rcon.RCONClient("localhost", rcon_port, rcon_password) if ctx.mod_version == ctx.__class__.mod_version: raise Exception("No Archipelago mod was loaded. Aborting.") await get_info(ctx, rcon_client) await asyncio.sleep(0.01) except Exception as e: logger.exception(e, extra={"compact_gui": True}) msg = "Aborted Factorio Server Bridge" logger.error(msg) ctx.gui_error(msg, e) ctx.exit_event.set() else: logger.info( f"Got World Information from AP Mod {tuple(ctx.mod_version)} for seed {ctx.seed_name} in slot {ctx.auth}") return True finally: factorio_process.terminate() factorio_process.wait(5) return False
def _cmd_toggle_msgs(self): """Toggle displaying messages in bizhawk""" global DISPLAY_MSGS DISPLAY_MSGS = not DISPLAY_MSGS logger.info( f"Messages are now {'enabled' if DISPLAY_MSGS else 'disabled'}")
def _cmd_nes(self): """Check NES Connection State""" if isinstance(self.ctx, FF1Context): logger.info(f"NES Status: {self.ctx.nes_status}")
async def nes_sync_task(ctx: FF1Context): logger.info("Starting nes connector. Use /nes for status information") while not ctx.exit_event.is_set(): error_status = None if ctx.nes_streams: (reader, writer) = ctx.nes_streams msg = get_payload(ctx).encode() writer.write(msg) writer.write(b'\n') try: await asyncio.wait_for(writer.drain(), timeout=1.5) try: # Data will return a dict with up to two fields: # 1. A keepalive response of the Players Name (always) # 2. An array representing the memory values of the locations area (if in game) data = await asyncio.wait_for(reader.readline(), timeout=5) data_decoded = json.loads(data.decode()) # print(data_decoded) if ctx.game is not None and 'locations' in data_decoded: # Not just a keep alive ping, parse asyncio.create_task( parse_locations(data_decoded['locations'], ctx, False)) if not ctx.auth: ctx.auth = ''.join([ chr(i) for i in data_decoded['playerName'] if i != 0 ]) if ctx.auth == '': logger.info( "Invalid ROM detected. No player name built into the ROM. Please regenerate" "the ROM using the same link but adding your slot name" ) if ctx.awaiting_rom: await ctx.server_auth(False) except asyncio.TimeoutError: logger.debug("Read Timed Out, Reconnecting") error_status = CONNECTION_TIMING_OUT_STATUS writer.close() ctx.nes_streams = None except ConnectionResetError as e: logger.debug( "Read failed due to Connection Lost, Reconnecting") error_status = CONNECTION_RESET_STATUS writer.close() ctx.nes_streams = None except TimeoutError: logger.debug("Connection Timed Out, Reconnecting") error_status = CONNECTION_TIMING_OUT_STATUS writer.close() ctx.nes_streams = None except ConnectionResetError: logger.debug("Connection Lost, Reconnecting") error_status = CONNECTION_RESET_STATUS writer.close() ctx.nes_streams = None if ctx.nes_status == CONNECTION_TENTATIVE_STATUS: if not error_status: logger.info("Successfully Connected to NES") ctx.nes_status = CONNECTION_CONNECTED_STATUS else: ctx.nes_status = f"Was tentatively connected but error occured: {error_status}" elif error_status: ctx.nes_status = error_status logger.info( "Lost connection to nes and attempting to reconnect. Use /nes for status updates" ) else: try: logger.debug("Attempting to connect to NES") ctx.nes_streams = await asyncio.wait_for( asyncio.open_connection("localhost", 52980), timeout=10) ctx.nes_status = CONNECTION_TENTATIVE_STATUS except TimeoutError: logger.debug("Connection Timed Out, Trying Again") ctx.nes_status = CONNECTION_TIMING_OUT_STATUS continue except ConnectionRefusedError: logger.debug("Connection Refused, Trying Again") ctx.nes_status = CONNECTION_REFUSED_STATUS continue
def on_print(self, args: dict): logger.info(args["text"]) if self.rcon_client: cleaned_text = args['text'].replace('"', '') self.rcon_client.send_command(f"/sc game.print(\"Archipelago: {cleaned_text}\")")
async def factorio_server_watcher(ctx: FactorioContext): savegame_name = os.path.abspath(ctx.savegame_name) if not os.path.exists(savegame_name): logger.info(f"Creating savegame {savegame_name}") subprocess.run(( executable, "--create", savegame_name, "--preset", "archipelago" )) factorio_process = subprocess.Popen((executable, "--start-server", ctx.savegame_name, *(str(elem) for elem in server_args)), stderr=subprocess.PIPE, stdout=subprocess.PIPE, stdin=subprocess.DEVNULL, encoding="utf-8") factorio_server_logger.info("Started Factorio Server") factorio_queue = Queue() stream_factorio_output(factorio_process.stdout, factorio_queue, factorio_process) stream_factorio_output(factorio_process.stderr, factorio_queue, factorio_process) try: while not ctx.exit_event.is_set(): if factorio_process.poll(): factorio_server_logger.info("Factorio server has exited.") ctx.exit_event.set() while not factorio_queue.empty(): msg = factorio_queue.get() factorio_queue.task_done() if not ctx.rcon_client and "Starting RCON interface at IP ADDR:" in msg: ctx.rcon_client = factorio_rcon.RCONClient("localhost", rcon_port, rcon_password) if not ctx.server: logger.info("Established bridge to Factorio Server. " "Ready to connect to Archipelago via /connect") if not ctx.awaiting_bridge and "Archipelago Bridge Data available for game tick " in msg: ctx.awaiting_bridge = True factorio_server_logger.debug(msg) else: factorio_server_logger.info(msg) if ctx.rcon_client: commands = {} while ctx.send_index < len(ctx.items_received): transfer_item: NetworkItem = ctx.items_received[ctx.send_index] item_id = transfer_item.item player_name = ctx.player_names[transfer_item.player] if item_id not in Factorio.item_id_to_name: factorio_server_logger.error(f"Cannot send unknown item ID: {item_id}") else: item_name = Factorio.item_id_to_name[item_id] factorio_server_logger.info(f"Sending {item_name} to Nauvis from {player_name}.") commands[ctx.send_index] = f'/ap-get-technology {item_name}\t{ctx.send_index}\t{player_name}' ctx.send_index += 1 if commands: ctx.rcon_client.send_commands(commands) await asyncio.sleep(0.1) except Exception as e: logging.exception(e) logging.error("Aborted Factorio Server Bridge") ctx.rcon_client = None ctx.exit_event.set() finally: factorio_process.terminate() factorio_process.wait(5)
def _cmd_n64(self): """Check N64 Connection State""" if isinstance(self.ctx, OoTContext): logger.info(f"N64 Status: {self.ctx.n64_status}")
async def n64_sync_task(ctx: OoTContext): logger.info("Starting n64 connector. Use /n64 for status information.") while not ctx.exit_event.is_set(): error_status = None if ctx.n64_streams: (reader, writer) = ctx.n64_streams msg = get_payload(ctx).encode() writer.write(msg) writer.write(b'\n') try: await asyncio.wait_for(writer.drain(), timeout=1.5) try: # Data will return a dict with up to six fields: # 1. str: player name (always) # 2. int: script version (always) # 3. bool: deathlink active (always) # 4. dict[str, bool]: checked locations # 5. bool: whether Link is currently at 0 HP # 6. bool: whether the game currently registers as complete data = await asyncio.wait_for(reader.readline(), timeout=10) data_decoded = json.loads(data.decode()) reported_version = data_decoded.get('scriptVersion', 0) if reported_version == script_version: if ctx.game is not None and 'locations' in data_decoded: # Not just a keep alive ping, parse asyncio.create_task( parse_payload(data_decoded, ctx, False)) if not ctx.auth: ctx.auth = data_decoded['playerName'] if ctx.awaiting_rom: await ctx.server_auth(False) else: if not ctx.version_warning: logger.warning( f"Your Lua script is version {reported_version}, expected {script_version}. " "Please update to the latest version. " "Your connection to the Archipelago server will not be accepted." ) ctx.version_warning = True except asyncio.TimeoutError: logger.debug("Read Timed Out, Reconnecting") error_status = CONNECTION_TIMING_OUT_STATUS writer.close() ctx.n64_streams = None except ConnectionResetError as e: logger.debug( "Read failed due to Connection Lost, Reconnecting") error_status = CONNECTION_RESET_STATUS writer.close() ctx.n64_streams = None except TimeoutError: logger.debug("Connection Timed Out, Reconnecting") error_status = CONNECTION_TIMING_OUT_STATUS writer.close() ctx.n64_streams = None except ConnectionResetError: logger.debug("Connection Lost, Reconnecting") error_status = CONNECTION_RESET_STATUS writer.close() ctx.n64_streams = None if ctx.n64_status == CONNECTION_TENTATIVE_STATUS: if not error_status: logger.info("Successfully Connected to N64") ctx.n64_status = CONNECTION_CONNECTED_STATUS else: ctx.n64_status = f"Was tentatively connected but error occured: {error_status}" elif error_status: ctx.n64_status = error_status logger.info( "Lost connection to N64 and attempting to reconnect. Use /n64 for status updates" ) else: try: logger.debug("Attempting to connect to N64") ctx.n64_streams = await asyncio.wait_for( asyncio.open_connection("localhost", 28921), timeout=10) ctx.n64_status = CONNECTION_TENTATIVE_STATUS except TimeoutError: logger.debug("Connection Timed Out, Trying Again") ctx.n64_status = CONNECTION_TIMING_OUT_STATUS continue except ConnectionRefusedError: logger.debug("Connection Refused, Trying Again") ctx.n64_status = CONNECTION_REFUSED_STATUS continue