LADX: Client Fixes (#1934)
This commit is contained in:
parent
736945658a
commit
36474c3ccc
|
@ -9,16 +9,19 @@ if __name__ == "__main__":
|
||||||
import asyncio
|
import asyncio
|
||||||
import base64
|
import base64
|
||||||
import binascii
|
import binascii
|
||||||
|
import colorama
|
||||||
import io
|
import io
|
||||||
import logging
|
import os
|
||||||
|
import re
|
||||||
import select
|
import select
|
||||||
|
import shlex
|
||||||
import socket
|
import socket
|
||||||
|
import struct
|
||||||
|
import sys
|
||||||
|
import subprocess
|
||||||
import time
|
import time
|
||||||
import typing
|
import typing
|
||||||
import urllib
|
|
||||||
|
|
||||||
import colorama
|
|
||||||
import struct
|
|
||||||
|
|
||||||
from CommonClient import (CommonContext, get_base_parser, gui_enabled, logger,
|
from CommonClient import (CommonContext, get_base_parser, gui_enabled, logger,
|
||||||
server_loop)
|
server_loop)
|
||||||
|
@ -30,6 +33,7 @@ from worlds.ladx.LADXR.checkMetadata import checkMetadataTable
|
||||||
from worlds.ladx.Locations import get_locations_to_id, meta_to_name
|
from worlds.ladx.Locations import get_locations_to_id, meta_to_name
|
||||||
from worlds.ladx.Tracker import LocationTracker, MagpieBridge
|
from worlds.ladx.Tracker import LocationTracker, MagpieBridge
|
||||||
|
|
||||||
|
|
||||||
class GameboyException(Exception):
|
class GameboyException(Exception):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@ -115,17 +119,17 @@ class RAGameboy():
|
||||||
assert (self.socket)
|
assert (self.socket)
|
||||||
self.socket.setblocking(False)
|
self.socket.setblocking(False)
|
||||||
|
|
||||||
def get_retroarch_version(self):
|
async def send_command(self, command, timeout=1.0):
|
||||||
self.send(b'VERSION\n')
|
self.send(f'{command}\n')
|
||||||
select.select([self.socket], [], [])
|
response_str = await self.async_recv()
|
||||||
response_str, addr = self.socket.recvfrom(16)
|
self.check_command_response(command, response_str)
|
||||||
return response_str.rstrip()
|
return response_str.rstrip()
|
||||||
|
|
||||||
def get_retroarch_status(self, timeout):
|
async def get_retroarch_version(self):
|
||||||
self.send(b'GET_STATUS\n')
|
return await self.send_command("VERSION")
|
||||||
select.select([self.socket], [], [], timeout)
|
|
||||||
response_str, addr = self.socket.recvfrom(1000, )
|
async def get_retroarch_status(self):
|
||||||
return response_str.rstrip()
|
return await self.send_command("GET_STATUS")
|
||||||
|
|
||||||
def set_cache_limits(self, cache_start, cache_size):
|
def set_cache_limits(self, cache_start, cache_size):
|
||||||
self.cache_start = cache_start
|
self.cache_start = cache_start
|
||||||
|
@ -141,8 +145,8 @@ class RAGameboy():
|
||||||
response, _ = self.socket.recvfrom(4096)
|
response, _ = self.socket.recvfrom(4096)
|
||||||
return response
|
return response
|
||||||
|
|
||||||
async def async_recv(self):
|
async def async_recv(self, timeout=1.0):
|
||||||
response = await asyncio.get_event_loop().sock_recv(self.socket, 4096)
|
response = await asyncio.wait_for(asyncio.get_event_loop().sock_recv(self.socket, 4096), timeout)
|
||||||
return response
|
return response
|
||||||
|
|
||||||
async def check_safe_gameplay(self, throw=True):
|
async def check_safe_gameplay(self, throw=True):
|
||||||
|
@ -169,6 +173,8 @@ class RAGameboy():
|
||||||
raise InvalidEmulatorStateError()
|
raise InvalidEmulatorStateError()
|
||||||
return False
|
return False
|
||||||
if not await check_wram():
|
if not await check_wram():
|
||||||
|
if throw:
|
||||||
|
raise InvalidEmulatorStateError()
|
||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
@ -227,20 +233,30 @@ class RAGameboy():
|
||||||
|
|
||||||
return r
|
return r
|
||||||
|
|
||||||
|
def check_command_response(self, command: str, response: bytes):
|
||||||
|
if command == "VERSION":
|
||||||
|
ok = re.match("\d+\.\d+\.\d+", response.decode('ascii')) is not None
|
||||||
|
else:
|
||||||
|
ok = response.startswith(command.encode())
|
||||||
|
if not ok:
|
||||||
|
logger.warning(f"Bad response to command {command} - {response}")
|
||||||
|
raise BadRetroArchResponse()
|
||||||
|
|
||||||
def read_memory(self, address, size=1):
|
def read_memory(self, address, size=1):
|
||||||
command = "READ_CORE_MEMORY"
|
command = "READ_CORE_MEMORY"
|
||||||
|
|
||||||
self.send(f'{command} {hex(address)} {size}\n')
|
self.send(f'{command} {hex(address)} {size}\n')
|
||||||
response = self.recv()
|
response = self.recv()
|
||||||
|
|
||||||
|
self.check_command_response(command, response)
|
||||||
|
|
||||||
splits = response.decode().split(" ", 2)
|
splits = response.decode().split(" ", 2)
|
||||||
|
|
||||||
assert (splits[0] == command)
|
|
||||||
# Ignore the address for now
|
# Ignore the address for now
|
||||||
|
if splits[2][:2] == "-1":
|
||||||
# TODO: transform to bytes
|
|
||||||
if splits[2][:2] == "-1" or splits[0] != "READ_CORE_MEMORY":
|
|
||||||
raise BadRetroArchResponse()
|
raise BadRetroArchResponse()
|
||||||
|
|
||||||
|
# TODO: check response address, check hex behavior between RA and BH
|
||||||
|
|
||||||
return bytearray.fromhex(splits[2])
|
return bytearray.fromhex(splits[2])
|
||||||
|
|
||||||
async def async_read_memory(self, address, size=1):
|
async def async_read_memory(self, address, size=1):
|
||||||
|
@ -248,14 +264,21 @@ class RAGameboy():
|
||||||
|
|
||||||
self.send(f'{command} {hex(address)} {size}\n')
|
self.send(f'{command} {hex(address)} {size}\n')
|
||||||
response = await self.async_recv()
|
response = await self.async_recv()
|
||||||
|
self.check_command_response(command, response)
|
||||||
response = response[:-1]
|
response = response[:-1]
|
||||||
splits = response.decode().split(" ", 2)
|
splits = response.decode().split(" ", 2)
|
||||||
|
try:
|
||||||
|
response_addr = int(splits[1], 16)
|
||||||
|
except ValueError:
|
||||||
|
raise BadRetroArchResponse()
|
||||||
|
|
||||||
assert (splits[0] == command)
|
if response_addr != address:
|
||||||
# Ignore the address for now
|
raise BadRetroArchResponse()
|
||||||
|
|
||||||
# TODO: transform to bytes
|
ret = bytearray.fromhex(splits[2])
|
||||||
return bytearray.fromhex(splits[2])
|
if len(ret) > size:
|
||||||
|
raise BadRetroArchResponse()
|
||||||
|
return ret
|
||||||
|
|
||||||
def write_memory(self, address, bytes):
|
def write_memory(self, address, bytes):
|
||||||
command = "WRITE_CORE_MEMORY"
|
command = "WRITE_CORE_MEMORY"
|
||||||
|
@ -263,7 +286,7 @@ class RAGameboy():
|
||||||
self.send(f'{command} {hex(address)} {" ".join(hex(b) for b in bytes)}')
|
self.send(f'{command} {hex(address)} {" ".join(hex(b) for b in bytes)}')
|
||||||
select.select([self.socket], [], [])
|
select.select([self.socket], [], [])
|
||||||
response, _ = self.socket.recvfrom(4096)
|
response, _ = self.socket.recvfrom(4096)
|
||||||
|
self.check_command_response(command, response)
|
||||||
splits = response.decode().split(" ", 3)
|
splits = response.decode().split(" ", 3)
|
||||||
|
|
||||||
assert (splits[0] == command)
|
assert (splits[0] == command)
|
||||||
|
@ -281,6 +304,9 @@ class LinksAwakeningClient():
|
||||||
pending_deathlink = False
|
pending_deathlink = False
|
||||||
deathlink_debounce = True
|
deathlink_debounce = True
|
||||||
recvd_checks = {}
|
recvd_checks = {}
|
||||||
|
retroarch_address = None
|
||||||
|
retroarch_port = None
|
||||||
|
gameboy = None
|
||||||
|
|
||||||
def msg(self, m):
|
def msg(self, m):
|
||||||
logger.info(m)
|
logger.info(m)
|
||||||
|
@ -288,50 +314,47 @@ class LinksAwakeningClient():
|
||||||
self.gameboy.send(s)
|
self.gameboy.send(s)
|
||||||
|
|
||||||
def __init__(self, retroarch_address="127.0.0.1", retroarch_port=55355):
|
def __init__(self, retroarch_address="127.0.0.1", retroarch_port=55355):
|
||||||
self.gameboy = RAGameboy(retroarch_address, retroarch_port)
|
self.retroarch_address = retroarch_address
|
||||||
|
self.retroarch_port = retroarch_port
|
||||||
|
pass
|
||||||
|
|
||||||
|
stop_bizhawk_spam = False
|
||||||
async def wait_for_retroarch_connection(self):
|
async def wait_for_retroarch_connection(self):
|
||||||
logger.info("Waiting on connection to Retroarch...")
|
if not self.stop_bizhawk_spam:
|
||||||
|
logger.info("Waiting on connection to Retroarch...")
|
||||||
|
self.stop_bizhawk_spam = True
|
||||||
|
self.gameboy = RAGameboy(self.retroarch_address, self.retroarch_port)
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
try:
|
try:
|
||||||
version = self.gameboy.get_retroarch_version()
|
version = await self.gameboy.get_retroarch_version()
|
||||||
NO_CONTENT = b"GET_STATUS CONTENTLESS"
|
NO_CONTENT = b"GET_STATUS CONTENTLESS"
|
||||||
status = NO_CONTENT
|
status = NO_CONTENT
|
||||||
core_type = None
|
core_type = None
|
||||||
GAME_BOY = b"game_boy"
|
GAME_BOY = b"game_boy"
|
||||||
while status == NO_CONTENT or core_type != GAME_BOY:
|
while status == NO_CONTENT or core_type != GAME_BOY:
|
||||||
try:
|
status = await self.gameboy.get_retroarch_status()
|
||||||
status = self.gameboy.get_retroarch_status(0.1)
|
if status.count(b" ") < 2:
|
||||||
if status.count(b" ") < 2:
|
await asyncio.sleep(1.0)
|
||||||
await asyncio.sleep(1.0)
|
continue
|
||||||
continue
|
GET_STATUS, PLAYING, info = status.split(b" ", 2)
|
||||||
|
if status.count(b",") < 2:
|
||||||
GET_STATUS, PLAYING, info = status.split(b" ", 2)
|
await asyncio.sleep(1.0)
|
||||||
if status.count(b",") < 2:
|
continue
|
||||||
await asyncio.sleep(1.0)
|
core_type, rom_name, self.game_crc = info.split(b",", 2)
|
||||||
continue
|
if core_type != GAME_BOY:
|
||||||
core_type, rom_name, self.game_crc = info.split(b",", 2)
|
logger.info(
|
||||||
if core_type != GAME_BOY:
|
f"Core type should be '{GAME_BOY}', found {core_type} instead - wrong type of ROM?")
|
||||||
logger.info(
|
await asyncio.sleep(1.0)
|
||||||
f"Core type should be '{GAME_BOY}', found {core_type} instead - wrong type of ROM?")
|
continue
|
||||||
await asyncio.sleep(1.0)
|
logger.info(f"Connected to Retroarch {version.decode('ascii')} running {rom_name.decode('ascii')}")
|
||||||
continue
|
|
||||||
except (BlockingIOError, TimeoutError) as e:
|
|
||||||
await asyncio.sleep(0.1)
|
|
||||||
pass
|
|
||||||
logger.info(f"Connected to Retroarch {version} {info}")
|
|
||||||
self.gameboy.read_memory(0x1000)
|
|
||||||
return
|
return
|
||||||
except ConnectionResetError:
|
except (BlockingIOError, TimeoutError, ConnectionResetError):
|
||||||
await asyncio.sleep(1.0)
|
await asyncio.sleep(1.0)
|
||||||
pass
|
pass
|
||||||
|
self.stop_bizhawk_spam = False
|
||||||
def reset_auth(self):
|
async def reset_auth(self):
|
||||||
auth = binascii.hexlify(self.gameboy.read_memory(0x0134, 12)).decode()
|
auth = binascii.hexlify(await self.gameboy.async_read_memory(0x0134, 12)).decode()
|
||||||
|
|
||||||
if self.auth:
|
|
||||||
assert (auth == self.auth)
|
|
||||||
|
|
||||||
self.auth = auth
|
self.auth = auth
|
||||||
|
|
||||||
async def wait_and_init_tracker(self):
|
async def wait_and_init_tracker(self):
|
||||||
|
@ -367,11 +390,14 @@ class LinksAwakeningClient():
|
||||||
status = self.gameboy.write_memory(LAClientConstants.wLinkStatusBits, [status])
|
status = self.gameboy.write_memory(LAClientConstants.wLinkStatusBits, [status])
|
||||||
self.gameboy.write_memory(LAClientConstants.wRecvIndex, struct.pack(">H", next_index))
|
self.gameboy.write_memory(LAClientConstants.wRecvIndex, struct.pack(">H", next_index))
|
||||||
|
|
||||||
|
should_reset_auth = False
|
||||||
async def wait_for_game_ready(self):
|
async def wait_for_game_ready(self):
|
||||||
logger.info("Waiting on game to be in valid state...")
|
logger.info("Waiting on game to be in valid state...")
|
||||||
while not await self.gameboy.check_safe_gameplay(throw=False):
|
while not await self.gameboy.check_safe_gameplay(throw=False):
|
||||||
pass
|
if self.should_reset_auth:
|
||||||
logger.info("Ready!")
|
self.should_reset_auth = False
|
||||||
|
raise GameboyException("Resetting due to wrong archipelago server")
|
||||||
|
logger.info("Game connection ready!")
|
||||||
|
|
||||||
async def is_victory(self):
|
async def is_victory(self):
|
||||||
return (await self.gameboy.read_memory_cache([LAClientConstants.wGameplayType]))[LAClientConstants.wGameplayType] == 1
|
return (await self.gameboy.read_memory_cache([LAClientConstants.wGameplayType]))[LAClientConstants.wGameplayType] == 1
|
||||||
|
@ -398,7 +424,7 @@ class LinksAwakeningClient():
|
||||||
if await self.is_victory():
|
if await self.is_victory():
|
||||||
await win_cb()
|
await win_cb()
|
||||||
|
|
||||||
recv_index = struct.unpack(">H", self.gameboy.read_memory(LAClientConstants.wRecvIndex, 2))[0]
|
recv_index = struct.unpack(">H", await self.gameboy.async_read_memory(LAClientConstants.wRecvIndex, 2))[0]
|
||||||
|
|
||||||
# Play back one at a time
|
# Play back one at a time
|
||||||
if recv_index in self.recvd_checks:
|
if recv_index in self.recvd_checks:
|
||||||
|
@ -480,6 +506,15 @@ class LinksAwakeningContext(CommonContext):
|
||||||
message = [{"cmd": 'LocationChecks', "locations": self.found_checks}]
|
message = [{"cmd": 'LocationChecks', "locations": self.found_checks}]
|
||||||
await self.send_msgs(message)
|
await self.send_msgs(message)
|
||||||
|
|
||||||
|
had_invalid_slot_data = None
|
||||||
|
def event_invalid_slot(self):
|
||||||
|
# The next time we try to connect, reset the game loop for new auth
|
||||||
|
self.had_invalid_slot_data = True
|
||||||
|
self.auth = None
|
||||||
|
# Don't try to autoreconnect, it will just fail
|
||||||
|
self.disconnected_intentionally = True
|
||||||
|
CommonContext.event_invalid_slot(self)
|
||||||
|
|
||||||
ENABLE_DEATHLINK = False
|
ENABLE_DEATHLINK = False
|
||||||
async def send_deathlink(self):
|
async def send_deathlink(self):
|
||||||
if self.ENABLE_DEATHLINK:
|
if self.ENABLE_DEATHLINK:
|
||||||
|
@ -511,8 +546,17 @@ class LinksAwakeningContext(CommonContext):
|
||||||
async def server_auth(self, password_requested: bool = False):
|
async def server_auth(self, password_requested: bool = False):
|
||||||
if password_requested and not self.password:
|
if password_requested and not self.password:
|
||||||
await super(LinksAwakeningContext, self).server_auth(password_requested)
|
await super(LinksAwakeningContext, self).server_auth(password_requested)
|
||||||
|
|
||||||
|
if self.had_invalid_slot_data:
|
||||||
|
# We are connecting when previously we had the wrong ROM or server - just in case
|
||||||
|
# re-read the ROM so that if the user had the correct address but wrong ROM, we
|
||||||
|
# allow a successful reconnect
|
||||||
|
self.client.should_reset_auth = True
|
||||||
|
self.had_invalid_slot_data = False
|
||||||
|
|
||||||
|
while self.client.auth == None:
|
||||||
|
await asyncio.sleep(0.1)
|
||||||
self.auth = self.client.auth
|
self.auth = self.client.auth
|
||||||
await self.get_username()
|
|
||||||
await self.send_connect()
|
await self.send_connect()
|
||||||
|
|
||||||
def on_package(self, cmd: str, args: dict):
|
def on_package(self, cmd: str, args: dict):
|
||||||
|
@ -520,9 +564,13 @@ class LinksAwakeningContext(CommonContext):
|
||||||
self.game = self.slot_info[self.slot].game
|
self.game = self.slot_info[self.slot].game
|
||||||
# TODO - use watcher_event
|
# TODO - use watcher_event
|
||||||
if cmd == "ReceivedItems":
|
if cmd == "ReceivedItems":
|
||||||
for index, item in enumerate(args["items"], args["index"]):
|
for index, item in enumerate(args["items"], start=args["index"]):
|
||||||
self.client.recvd_checks[index] = item
|
self.client.recvd_checks[index] = item
|
||||||
|
|
||||||
|
async def sync(self):
|
||||||
|
sync_msg = [{'cmd': 'Sync'}]
|
||||||
|
await self.send_msgs(sync_msg)
|
||||||
|
|
||||||
item_id_lookup = get_locations_to_id()
|
item_id_lookup = get_locations_to_id()
|
||||||
|
|
||||||
async def run_game_loop(self):
|
async def run_game_loop(self):
|
||||||
|
@ -539,17 +587,31 @@ class LinksAwakeningContext(CommonContext):
|
||||||
|
|
||||||
if self.magpie_enabled:
|
if self.magpie_enabled:
|
||||||
self.magpie_task = asyncio.create_task(self.magpie.serve())
|
self.magpie_task = asyncio.create_task(self.magpie.serve())
|
||||||
|
|
||||||
# yield to allow UI to start
|
# yield to allow UI to start
|
||||||
await asyncio.sleep(0)
|
await asyncio.sleep(0)
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
try:
|
try:
|
||||||
# TODO: cancel all client tasks
|
# TODO: cancel all client tasks
|
||||||
logger.info("(Re)Starting game loop")
|
if not self.client.stop_bizhawk_spam:
|
||||||
|
logger.info("(Re)Starting game loop")
|
||||||
self.found_checks.clear()
|
self.found_checks.clear()
|
||||||
|
# On restart of game loop, clear all checks, just in case we swapped ROMs
|
||||||
|
# this isn't totally neccessary, but is extra safety against cross-ROM contamination
|
||||||
|
self.client.recvd_checks.clear()
|
||||||
await self.client.wait_for_retroarch_connection()
|
await self.client.wait_for_retroarch_connection()
|
||||||
self.client.reset_auth()
|
await self.client.reset_auth()
|
||||||
|
# If we find ourselves with new auth after the reset, reconnect
|
||||||
|
if self.auth and self.client.auth != self.auth:
|
||||||
|
# It would be neat to reconnect here, but connection needs this loop to be running
|
||||||
|
logger.info("Detected new ROM, disconnecting...")
|
||||||
|
await self.disconnect()
|
||||||
|
continue
|
||||||
|
|
||||||
|
if not self.client.recvd_checks:
|
||||||
|
await self.sync()
|
||||||
|
|
||||||
await self.client.wait_and_init_tracker()
|
await self.client.wait_and_init_tracker()
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
|
@ -560,39 +622,59 @@ class LinksAwakeningContext(CommonContext):
|
||||||
self.last_resend = now
|
self.last_resend = now
|
||||||
await self.send_checks()
|
await self.send_checks()
|
||||||
if self.magpie_enabled:
|
if self.magpie_enabled:
|
||||||
self.magpie.set_checks(self.client.tracker.all_checks)
|
try:
|
||||||
await self.magpie.set_item_tracker(self.client.item_tracker)
|
self.magpie.set_checks(self.client.tracker.all_checks)
|
||||||
await self.magpie.send_gps(self.client.gps_tracker)
|
await self.magpie.set_item_tracker(self.client.item_tracker)
|
||||||
|
await self.magpie.send_gps(self.client.gps_tracker)
|
||||||
|
except Exception:
|
||||||
|
# Don't let magpie errors take out the client
|
||||||
|
pass
|
||||||
|
if self.client.should_reset_auth:
|
||||||
|
self.client.should_reset_auth = False
|
||||||
|
raise GameboyException("Resetting due to wrong archipelago server")
|
||||||
|
except (GameboyException, asyncio.TimeoutError, TimeoutError, ConnectionResetError):
|
||||||
|
await asyncio.sleep(1.0)
|
||||||
|
|
||||||
except GameboyException:
|
def run_game(romfile: str) -> None:
|
||||||
time.sleep(1.0)
|
auto_start = typing.cast(typing.Union[bool, str],
|
||||||
pass
|
Utils.get_options()["ladx_options"].get("rom_start", True))
|
||||||
|
if auto_start is True:
|
||||||
|
import webbrowser
|
||||||
|
webbrowser.open(romfile)
|
||||||
|
elif isinstance(auto_start, str):
|
||||||
|
args = shlex.split(auto_start)
|
||||||
|
# Specify full path to ROM as we are going to cd in popen
|
||||||
|
full_rom_path = os.path.realpath(romfile)
|
||||||
|
args.append(full_rom_path)
|
||||||
|
try:
|
||||||
|
# set cwd so that paths to lua scripts are always relative to our client
|
||||||
|
if getattr(sys, 'frozen', False):
|
||||||
|
# The application is frozen
|
||||||
|
script_dir = os.path.dirname(sys.executable)
|
||||||
|
else:
|
||||||
|
script_dir = os.path.dirname(os.path.realpath(__file__))
|
||||||
|
|
||||||
|
subprocess.Popen(args, stdin=subprocess.DEVNULL, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, cwd=script_dir)
|
||||||
|
except FileNotFoundError:
|
||||||
|
logger.error(f"Couldn't launch ROM, {args[0]} is missing")
|
||||||
|
|
||||||
async def main():
|
async def main():
|
||||||
parser = get_base_parser(description="Link's Awakening Client.")
|
parser = get_base_parser(description="Link's Awakening Client.")
|
||||||
parser.add_argument("--url", help="Archipelago connection url")
|
parser.add_argument("--url", help="Archipelago connection url")
|
||||||
parser.add_argument("--no-magpie", dest='magpie', default=True, action='store_false', help="Disable magpie bridge")
|
parser.add_argument("--no-magpie", dest='magpie', default=True, action='store_false', help="Disable magpie bridge")
|
||||||
|
|
||||||
parser.add_argument('diff_file', default="", type=str, nargs="?",
|
parser.add_argument('diff_file', default="", type=str, nargs="?",
|
||||||
help='Path to a .apladx Archipelago Binary Patch file')
|
help='Path to a .apladx Archipelago Binary Patch file')
|
||||||
|
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
logger.info(args)
|
|
||||||
|
|
||||||
if args.diff_file:
|
if args.diff_file:
|
||||||
import Patch
|
import Patch
|
||||||
logger.info("patch file was supplied - creating rom...")
|
logger.info("patch file was supplied - creating rom...")
|
||||||
meta, rom_file = Patch.create_rom_file(args.diff_file)
|
meta, rom_file = Patch.create_rom_file(args.diff_file)
|
||||||
if "server" in meta:
|
if "server" in meta and not args.connect:
|
||||||
args.url = meta["server"]
|
args.connect = meta["server"]
|
||||||
logger.info(f"wrote rom file to {rom_file}")
|
logger.info(f"wrote rom file to {rom_file}")
|
||||||
|
|
||||||
if args.url:
|
|
||||||
url = urllib.parse.urlparse(args.url)
|
|
||||||
args.connect = url.netloc
|
|
||||||
if url.password:
|
|
||||||
args.password = urllib.parse.unquote(url.password)
|
|
||||||
|
|
||||||
ctx = LinksAwakeningContext(args.connect, args.password, args.magpie)
|
ctx = LinksAwakeningContext(args.connect, args.password, args.magpie)
|
||||||
|
|
||||||
|
@ -604,6 +686,10 @@ async def main():
|
||||||
ctx.run_gui()
|
ctx.run_gui()
|
||||||
ctx.run_cli()
|
ctx.run_cli()
|
||||||
|
|
||||||
|
# Down below run_gui so that we get errors out of the process
|
||||||
|
if args.diff_file:
|
||||||
|
run_game(rom_file)
|
||||||
|
|
||||||
await ctx.exit_event.wait()
|
await ctx.exit_event.wait()
|
||||||
await ctx.shutdown()
|
await ctx.shutdown()
|
||||||
|
|
||||||
|
|
|
@ -46,10 +46,10 @@ function get_socket_path()
|
||||||
local pwd = (io.popen and io.popen("cd"):read'*l') or "."
|
local pwd = (io.popen and io.popen("cd"):read'*l') or "."
|
||||||
return pwd .. "/" .. arch .. "/socket-" .. the_os .. "-" .. get_lua_version() .. "." .. ext
|
return pwd .. "/" .. arch .. "/socket-" .. the_os .. "-" .. get_lua_version() .. "." .. ext
|
||||||
end
|
end
|
||||||
|
local lua_version = get_lua_version()
|
||||||
local socket_path = get_socket_path()
|
local socket_path = get_socket_path()
|
||||||
local socket = assert(package.loadlib(socket_path, "luaopen_socket_core"))()
|
local socket = assert(package.loadlib(socket_path, "luaopen_socket_core"))()
|
||||||
|
local event = event
|
||||||
-- http://lua-users.org/wiki/ModulesTutorial
|
-- http://lua-users.org/wiki/ModulesTutorial
|
||||||
local M = {}
|
local M = {}
|
||||||
if setfenv then
|
if setfenv then
|
||||||
|
@ -59,6 +59,20 @@ else
|
||||||
end
|
end
|
||||||
|
|
||||||
M.socket = socket
|
M.socket = socket
|
||||||
|
-- Bizhawk <= 2.8 has an issue where resetting the lua doesn't close the socket
|
||||||
|
-- ...to get around this, we register an exit handler to close the socket first
|
||||||
|
if lua_version == '5-1' then
|
||||||
|
local old_udp = socket.udp
|
||||||
|
function udp(self)
|
||||||
|
s = old_udp(self)
|
||||||
|
function close_socket(self)
|
||||||
|
s:close()
|
||||||
|
end
|
||||||
|
event.onexit(close_socket)
|
||||||
|
return s
|
||||||
|
end
|
||||||
|
socket.udp = udp
|
||||||
|
end
|
||||||
|
|
||||||
-----------------------------------------------------------------------------
|
-----------------------------------------------------------------------------
|
||||||
-- Exported auxiliar functions
|
-- Exported auxiliar functions
|
||||||
|
|
|
@ -39,8 +39,23 @@ class LinksAwakeningSettings(settings.Group):
|
||||||
description = "LADX ROM File"
|
description = "LADX ROM File"
|
||||||
md5s = [LADXDeltaPatch.hash]
|
md5s = [LADXDeltaPatch.hash]
|
||||||
|
|
||||||
rom_file: RomFile = RomFile(RomFile.copy_to)
|
class RomStart(str):
|
||||||
|
"""
|
||||||
|
Set this to false to never autostart a rom (such as after patching)
|
||||||
|
true for operating system default program
|
||||||
|
Alternatively, a path to a program to open the .gbc file with
|
||||||
|
Examples:
|
||||||
|
Retroarch:
|
||||||
|
rom_start: "C:/RetroArch-Win64/retroarch.exe -L sameboy"
|
||||||
|
BizHawk:
|
||||||
|
rom_start: "C:/BizHawk-2.9-win-x64/EmuHawk.exe --lua=data/lua/connector_ladx_bizhawk.lua"
|
||||||
|
"""
|
||||||
|
|
||||||
|
class DisplayMsgs(settings.Bool):
|
||||||
|
"""Display message inside of Bizhawk"""
|
||||||
|
|
||||||
|
rom_file: RomFile = RomFile(RomFile.copy_to)
|
||||||
|
rom_start: typing.Union[RomStart, bool] = True
|
||||||
|
|
||||||
class LinksAwakeningWebWorld(WebWorld):
|
class LinksAwakeningWebWorld(WebWorld):
|
||||||
tutorials = [Tutorial(
|
tutorials = [Tutorial(
|
||||||
|
@ -451,12 +466,10 @@ class LinksAwakeningWorld(World):
|
||||||
# Kind of kludge, make it possible for the location to differentiate between local and remote items
|
# Kind of kludge, make it possible for the location to differentiate between local and remote items
|
||||||
loc.ladxr_item.location_owner = self.player
|
loc.ladxr_item.location_owner = self.player
|
||||||
|
|
||||||
rom_name = "Legend of Zelda, The - Link's Awakening DX (USA, Europe) (SGB Enhanced).gbc"
|
rom_name = Rom.get_base_rom_path()
|
||||||
out_name = f"AP-{self.multiworld.seed_name}-P{self.player}-{self.multiworld.player_name[self.player]}.gbc"
|
out_name = f"AP-{self.multiworld.seed_name}-P{self.player}-{self.multiworld.player_name[self.player]}.gbc"
|
||||||
out_path = os.path.join(output_directory, f"{self.multiworld.get_out_file_name_base(self.player)}.gbc")
|
out_path = os.path.join(output_directory, f"{self.multiworld.get_out_file_name_base(self.player)}.gbc")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
parser = get_parser()
|
parser = get_parser()
|
||||||
args = parser.parse_args([rom_name, "-o", out_name, "--dump"])
|
args = parser.parse_args([rom_name, "-o", out_name, "--dump"])
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue