| 
									
										
										
										
											2022-10-25 13:54:43 -04:00
										 |  |  | 
 | 
					
						
							|  |  |  | from __future__ import annotations | 
					
						
							|  |  |  | import abc | 
					
						
							| 
									
										
										
										
											2024-03-07 03:18:22 -06:00
										 |  |  | from typing import TYPE_CHECKING, ClassVar, Dict, Iterable, Tuple, Any, Optional, Union | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | from typing_extensions import TypeGuard | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | from worlds.LauncherComponents import Component, SuffixIdentifier, Type, components | 
					
						
							| 
									
										
										
										
											2022-10-25 13:54:43 -04:00
										 |  |  | 
 | 
					
						
							|  |  |  | if TYPE_CHECKING: | 
					
						
							|  |  |  |     from SNIClient import SNIContext | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-03-07 03:18:22 -06:00
										 |  |  | component = Component('SNI Client', 'SNIClient', component_type=Type.CLIENT, file_identifier=SuffixIdentifier(".apsoe")) | 
					
						
							|  |  |  | components.append(component) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def valid_patch_suffix(obj: object) -> TypeGuard[Union[str, Iterable[str]]]: | 
					
						
							|  |  |  |     """ make sure this is a valid value for the class variable `patch_suffix` """ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def valid_individual(one: object) -> TypeGuard[str]: | 
					
						
							|  |  |  |         """ check an individual suffix """ | 
					
						
							|  |  |  |         # TODO: decide:                 len(one) > 3 and one.startswith(".ap") ? | 
					
						
							|  |  |  |         # or keep it more general? | 
					
						
							|  |  |  |         return isinstance(one, str) and len(one) > 1 and one.startswith(".") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if isinstance(obj, str): | 
					
						
							|  |  |  |         return valid_individual(obj) | 
					
						
							|  |  |  |     if not isinstance(obj, Iterable): | 
					
						
							|  |  |  |         return False | 
					
						
							|  |  |  |     obj_it: Iterable[object] = obj | 
					
						
							|  |  |  |     return all(valid_individual(each) for each in obj_it) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-10-25 13:54:43 -04:00
										 |  |  | 
 | 
					
						
							|  |  |  | class AutoSNIClientRegister(abc.ABCMeta): | 
					
						
							|  |  |  |     game_handlers: ClassVar[Dict[str, SNIClient]] = {} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def __new__(cls, name: str, bases: Tuple[type, ...], dct: Dict[str, Any]) -> AutoSNIClientRegister: | 
					
						
							|  |  |  |         # construct class | 
					
						
							|  |  |  |         new_class = super().__new__(cls, name, bases, dct) | 
					
						
							|  |  |  |         if "game" in dct: | 
					
						
							|  |  |  |             AutoSNIClientRegister.game_handlers[dct["game"]] = new_class() | 
					
						
							| 
									
										
										
										
											2024-03-07 03:18:22 -06:00
										 |  |  | 
 | 
					
						
							|  |  |  |         if "patch_suffix" in dct: | 
					
						
							|  |  |  |             patch_suffix = dct["patch_suffix"] | 
					
						
							|  |  |  |             assert valid_patch_suffix(patch_suffix), f"class {name} defining invalid {patch_suffix=}" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             existing_identifier = component.file_identifier | 
					
						
							|  |  |  |             assert isinstance(existing_identifier, SuffixIdentifier), f"{existing_identifier=}" | 
					
						
							|  |  |  |             new_suffixes = [*existing_identifier.suffixes] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             if isinstance(patch_suffix, str): | 
					
						
							|  |  |  |                 new_suffixes.append(patch_suffix) | 
					
						
							|  |  |  |             else: | 
					
						
							|  |  |  |                 new_suffixes.extend(patch_suffix) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             component.file_identifier = SuffixIdentifier(*new_suffixes) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-10-25 13:54:43 -04:00
										 |  |  |         return new_class | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     @staticmethod | 
					
						
							|  |  |  |     async def get_handler(ctx: SNIContext) -> Optional[SNIClient]: | 
					
						
							|  |  |  |         for _game, handler in AutoSNIClientRegister.game_handlers.items(): | 
					
						
							|  |  |  |             if await handler.validate_rom(ctx): | 
					
						
							|  |  |  |                 return handler | 
					
						
							|  |  |  |         return None | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class SNIClient(abc.ABC, metaclass=AutoSNIClientRegister): | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-03-07 03:18:22 -06:00
										 |  |  |     patch_suffix: ClassVar[Union[str, Iterable[str]]] = () | 
					
						
							|  |  |  |     """The file extension(s) this client is meant to open and patch (e.g. ".aplttp")""" | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-10-25 13:54:43 -04:00
										 |  |  |     @abc.abstractmethod | 
					
						
							|  |  |  |     async def validate_rom(self, ctx: SNIContext) -> bool: | 
					
						
							|  |  |  |         """ TODO: interface documentation here """ | 
					
						
							|  |  |  |         ... | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     @abc.abstractmethod | 
					
						
							|  |  |  |     async def game_watcher(self, ctx: SNIContext) -> None: | 
					
						
							|  |  |  |         """ TODO: interface documentation here """ | 
					
						
							|  |  |  |         ... | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     async def deathlink_kill_player(self, ctx: SNIContext) -> None: | 
					
						
							|  |  |  |         """ override this with implementation to kill player """ | 
					
						
							|  |  |  |         pass |