2023-10-03 00:44:19 +00:00
|
|
|
"""
|
|
|
|
A module containing the BizHawkClient base class and metaclass
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
|
|
from __future__ import annotations
|
|
|
|
|
|
|
|
import abc
|
|
|
|
from typing import TYPE_CHECKING, Any, ClassVar, Dict, Optional, Tuple, Union
|
|
|
|
|
|
|
|
from worlds.LauncherComponents import Component, SuffixIdentifier, Type, components, launch_subprocess
|
|
|
|
|
|
|
|
if TYPE_CHECKING:
|
|
|
|
from .context import BizHawkClientContext
|
|
|
|
else:
|
|
|
|
BizHawkClientContext = object
|
|
|
|
|
|
|
|
|
2023-10-27 04:14:25 +00:00
|
|
|
def launch_client(*args) -> None:
|
|
|
|
from .context import launch
|
|
|
|
launch_subprocess(launch, name="BizHawkClient")
|
|
|
|
|
|
|
|
component = Component("BizHawk Client", "BizHawkClient", component_type=Type.CLIENT, func=launch_client,
|
|
|
|
file_identifier=SuffixIdentifier())
|
|
|
|
components.append(component)
|
|
|
|
|
|
|
|
|
2023-10-03 00:44:19 +00:00
|
|
|
class AutoBizHawkClientRegister(abc.ABCMeta):
|
|
|
|
game_handlers: ClassVar[Dict[Tuple[str, ...], Dict[str, BizHawkClient]]] = {}
|
|
|
|
|
|
|
|
def __new__(cls, name: str, bases: Tuple[type, ...], namespace: Dict[str, Any]) -> AutoBizHawkClientRegister:
|
|
|
|
new_class = super().__new__(cls, name, bases, namespace)
|
|
|
|
|
2023-10-27 04:14:25 +00:00
|
|
|
# Register handler
|
2023-10-03 00:44:19 +00:00
|
|
|
if "system" in namespace:
|
|
|
|
systems = (namespace["system"],) if type(namespace["system"]) is str else tuple(sorted(namespace["system"]))
|
|
|
|
if systems not in AutoBizHawkClientRegister.game_handlers:
|
|
|
|
AutoBizHawkClientRegister.game_handlers[systems] = {}
|
|
|
|
|
|
|
|
if "game" in namespace:
|
|
|
|
AutoBizHawkClientRegister.game_handlers[systems][namespace["game"]] = new_class()
|
|
|
|
|
2023-10-27 04:14:25 +00:00
|
|
|
# Update launcher component's suffixes
|
|
|
|
if "patch_suffix" in namespace:
|
|
|
|
if namespace["patch_suffix"] is not None:
|
|
|
|
existing_identifier: SuffixIdentifier = component.file_identifier
|
|
|
|
new_suffixes = [*existing_identifier.suffixes]
|
|
|
|
|
|
|
|
if type(namespace["patch_suffix"]) is str:
|
|
|
|
new_suffixes.append(namespace["patch_suffix"])
|
|
|
|
else:
|
|
|
|
new_suffixes.extend(namespace["patch_suffix"])
|
|
|
|
|
|
|
|
component.file_identifier = SuffixIdentifier(*new_suffixes)
|
|
|
|
|
2023-10-03 00:44:19 +00:00
|
|
|
return new_class
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
async def get_handler(ctx: BizHawkClientContext, system: str) -> Optional[BizHawkClient]:
|
|
|
|
for systems, handlers in AutoBizHawkClientRegister.game_handlers.items():
|
|
|
|
if system in systems:
|
|
|
|
for handler in handlers.values():
|
|
|
|
if await handler.validate_rom(ctx):
|
|
|
|
return handler
|
|
|
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
|
|
class BizHawkClient(abc.ABC, metaclass=AutoBizHawkClientRegister):
|
|
|
|
system: ClassVar[Union[str, Tuple[str, ...]]]
|
2023-10-27 04:14:25 +00:00
|
|
|
"""The system(s) that the game this client is for runs on"""
|
2023-10-03 00:44:19 +00:00
|
|
|
|
|
|
|
game: ClassVar[str]
|
|
|
|
"""The game this client is for"""
|
|
|
|
|
2023-10-27 04:14:25 +00:00
|
|
|
patch_suffix: ClassVar[Optional[Union[str, Tuple[str, ...]]]]
|
|
|
|
"""The file extension(s) this client is meant to open and patch (e.g. ".apz3")"""
|
|
|
|
|
2023-10-03 00:44:19 +00:00
|
|
|
@abc.abstractmethod
|
|
|
|
async def validate_rom(self, ctx: BizHawkClientContext) -> bool:
|
|
|
|
"""Should return whether the currently loaded ROM should be handled by this client. You might read the game name
|
|
|
|
from the ROM header, for example. This function will only be asked to validate ROMs from the system set by the
|
|
|
|
client class, so you do not need to check the system yourself.
|
|
|
|
|
|
|
|
Once this function has determined that the ROM should be handled by this client, it should also modify `ctx`
|
|
|
|
as necessary (such as setting `ctx.game = self.game`, modifying `ctx.items_handling`, etc...)."""
|
|
|
|
...
|
|
|
|
|
|
|
|
async def set_auth(self, ctx: BizHawkClientContext) -> None:
|
|
|
|
"""Should set ctx.auth in anticipation of sending a `Connected` packet. You may override this if you store slot
|
|
|
|
name in your patched ROM. If ctx.auth is not set after calling, the player will be prompted to enter their
|
|
|
|
username."""
|
|
|
|
pass
|
|
|
|
|
|
|
|
@abc.abstractmethod
|
|
|
|
async def game_watcher(self, ctx: BizHawkClientContext) -> None:
|
|
|
|
"""Runs on a loop with the approximate interval `ctx.watcher_timeout`. The currently loaded ROM is guaranteed
|
|
|
|
to have passed your validator when this function is called, and the emulator is very likely to be connected."""
|
|
|
|
...
|
|
|
|
|
|
|
|
def on_package(self, ctx: BizHawkClientContext, cmd: str, args: dict) -> None:
|
|
|
|
"""For handling packages from the server. Called from `BizHawkClientContext.on_package`."""
|
|
|
|
pass
|