102 lines
		
	
	
		
			4.3 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			102 lines
		
	
	
		
			4.3 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| """
 | |
| 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
 | |
| 
 | |
| 
 | |
| 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)
 | |
| 
 | |
| 
 | |
| 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)
 | |
| 
 | |
|         # Register handler
 | |
|         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()
 | |
| 
 | |
|         # 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)
 | |
| 
 | |
|         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, ...]]]
 | |
|     """The system(s) that the game this client is for runs on"""
 | |
| 
 | |
|     game: ClassVar[str]
 | |
|     """The game this client is for"""
 | |
| 
 | |
|     patch_suffix: ClassVar[Optional[Union[str, Tuple[str, ...]]]]
 | |
|     """The file extension(s) this client is meant to open and patch (e.g. ".apz3")"""
 | |
| 
 | |
|     @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
 | 
