| 
									
										
										
										
											2023-10-02 17:44:19 -07: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 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-10-26 21:14:25 -07:00
										 |  |  | def launch_client(*args) -> None: | 
					
						
							|  |  |  |     from .context import launch | 
					
						
							| 
									
										
										
										
											2024-09-18 11:42:22 -07:00
										 |  |  |     launch_subprocess(launch, name="BizHawkClient", args=args) | 
					
						
							| 
									
										
										
										
											2023-10-26 21:14:25 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-05-22 17:03:42 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-10-26 21:14:25 -07:00
										 |  |  | component = Component("BizHawk Client", "BizHawkClient", component_type=Type.CLIENT, func=launch_client, | 
					
						
							|  |  |  |                       file_identifier=SuffixIdentifier()) | 
					
						
							|  |  |  | components.append(component) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-10-02 17:44:19 -07: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-26 21:14:25 -07:00
										 |  |  |         # Register handler | 
					
						
							| 
									
										
										
										
											2023-10-02 17:44:19 -07: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-26 21:14:25 -07: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-02 17:44:19 -07:00
										 |  |  |         return new_class | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     @staticmethod | 
					
						
							| 
									
										
										
										
											2024-05-22 17:03:42 -07:00
										 |  |  |     async def get_handler(ctx: "BizHawkClientContext", system: str) -> Optional[BizHawkClient]: | 
					
						
							| 
									
										
										
										
											2023-10-02 17:44:19 -07:00
										 |  |  |         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-26 21:14:25 -07:00
										 |  |  |     """The system(s) that the game this client is for runs on""" | 
					
						
							| 
									
										
										
										
											2023-10-02 17:44:19 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  |     game: ClassVar[str] | 
					
						
							|  |  |  |     """The game this client is for""" | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-10-26 21:14:25 -07: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-02 17:44:19 -07:00
										 |  |  |     @abc.abstractmethod | 
					
						
							| 
									
										
										
										
											2024-05-22 17:03:42 -07:00
										 |  |  |     async def validate_rom(self, ctx: "BizHawkClientContext") -> bool: | 
					
						
							| 
									
										
										
										
											2023-10-02 17:44:19 -07:00
										 |  |  |         """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...)."""
 | 
					
						
							|  |  |  |         ... | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-05-22 17:03:42 -07:00
										 |  |  |     async def set_auth(self, ctx: "BizHawkClientContext") -> None: | 
					
						
							| 
									
										
										
										
											2023-10-02 17:44:19 -07:00
										 |  |  |         """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 | 
					
						
							| 
									
										
										
										
											2024-05-22 17:03:42 -07:00
										 |  |  |     async def game_watcher(self, ctx: "BizHawkClientContext") -> None: | 
					
						
							| 
									
										
										
										
											2023-10-02 17:44:19 -07:00
										 |  |  |         """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."""
 | 
					
						
							|  |  |  |         ... | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-05-22 17:03:42 -07:00
										 |  |  |     def on_package(self, ctx: "BizHawkClientContext", cmd: str, args: dict) -> None: | 
					
						
							| 
									
										
										
										
											2023-10-02 17:44:19 -07:00
										 |  |  |         """For handling packages from the server. Called from `BizHawkClientContext.on_package`.""" | 
					
						
							|  |  |  |         pass |