SNIClient: dynamically generate patch file identifier (#2870)
Co-authored-by: beauxq <beauxq@yahoo.com>
This commit is contained in:
parent
862d77820d
commit
b4bb88fcf7
|
@ -1,11 +1,35 @@
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
import abc
|
import abc
|
||||||
from typing import TYPE_CHECKING, ClassVar, Dict, Tuple, Any, Optional
|
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
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from SNIClient import SNIContext
|
from SNIClient import SNIContext
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
|
||||||
class AutoSNIClientRegister(abc.ABCMeta):
|
class AutoSNIClientRegister(abc.ABCMeta):
|
||||||
game_handlers: ClassVar[Dict[str, SNIClient]] = {}
|
game_handlers: ClassVar[Dict[str, SNIClient]] = {}
|
||||||
|
@ -15,6 +39,22 @@ class AutoSNIClientRegister(abc.ABCMeta):
|
||||||
new_class = super().__new__(cls, name, bases, dct)
|
new_class = super().__new__(cls, name, bases, dct)
|
||||||
if "game" in dct:
|
if "game" in dct:
|
||||||
AutoSNIClientRegister.game_handlers[dct["game"]] = new_class()
|
AutoSNIClientRegister.game_handlers[dct["game"]] = new_class()
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
return new_class
|
return new_class
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
@ -27,6 +67,9 @@ class AutoSNIClientRegister(abc.ABCMeta):
|
||||||
|
|
||||||
class SNIClient(abc.ABC, metaclass=AutoSNIClientRegister):
|
class SNIClient(abc.ABC, metaclass=AutoSNIClientRegister):
|
||||||
|
|
||||||
|
patch_suffix: ClassVar[Union[str, Iterable[str]]] = ()
|
||||||
|
"""The file extension(s) this client is meant to open and patch (e.g. ".aplttp")"""
|
||||||
|
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
async def validate_rom(self, ctx: SNIContext) -> bool:
|
async def validate_rom(self, ctx: SNIContext) -> bool:
|
||||||
""" TODO: interface documentation here """
|
""" TODO: interface documentation here """
|
||||||
|
|
|
@ -85,10 +85,6 @@ components: List[Component] = [
|
||||||
file_identifier=SuffixIdentifier('.archipelago', '.zip')),
|
file_identifier=SuffixIdentifier('.archipelago', '.zip')),
|
||||||
Component('Generate', 'Generate', cli=True),
|
Component('Generate', 'Generate', cli=True),
|
||||||
Component('Text Client', 'CommonClient', 'ArchipelagoTextClient', func=launch_textclient),
|
Component('Text Client', 'CommonClient', 'ArchipelagoTextClient', func=launch_textclient),
|
||||||
# SNI
|
|
||||||
Component('SNI Client', 'SNIClient',
|
|
||||||
file_identifier=SuffixIdentifier('.apz3', '.apm3', '.apsoe', '.aplttp', '.apsm', '.apsmz3', '.apdkc3',
|
|
||||||
'.apsmw', '.apl2ac', '.apkdl3')),
|
|
||||||
Component('Links Awakening DX Client', 'LinksAwakeningClient',
|
Component('Links Awakening DX Client', 'LinksAwakeningClient',
|
||||||
file_identifier=SuffixIdentifier('.apladx')),
|
file_identifier=SuffixIdentifier('.apladx')),
|
||||||
Component('LttP Adjuster', 'LttPAdjuster'),
|
Component('LttP Adjuster', 'LttPAdjuster'),
|
||||||
|
|
|
@ -471,6 +471,7 @@ async def track_locations(ctx, roomid, roomdata) -> bool:
|
||||||
|
|
||||||
class ALTTPSNIClient(SNIClient):
|
class ALTTPSNIClient(SNIClient):
|
||||||
game = "A Link to the Past"
|
game = "A Link to the Past"
|
||||||
|
patch_suffix = [".aplttp", ".apz3"]
|
||||||
|
|
||||||
async def deathlink_kill_player(self, ctx):
|
async def deathlink_kill_player(self, ctx):
|
||||||
from SNIClient import DeathState, snes_read, snes_buffered_write, snes_flush_writes
|
from SNIClient import DeathState, snes_read, snes_buffered_write, snes_flush_writes
|
||||||
|
|
|
@ -24,6 +24,7 @@ DEATH_LINK_ACTIVE_ADDR = DKC3_ROMNAME_START + 0x15 # DKC3_TODO: Find a perma
|
||||||
|
|
||||||
class DKC3SNIClient(SNIClient):
|
class DKC3SNIClient(SNIClient):
|
||||||
game = "Donkey Kong Country 3"
|
game = "Donkey Kong Country 3"
|
||||||
|
patch_suffix = ".apdkc3"
|
||||||
|
|
||||||
async def deathlink_kill_player(self, ctx):
|
async def deathlink_kill_player(self, ctx):
|
||||||
pass
|
pass
|
||||||
|
|
|
@ -30,6 +30,7 @@ L2AC_RX_ADDR: int = SRAM_START + 0x2800
|
||||||
|
|
||||||
class L2ACSNIClient(SNIClient):
|
class L2ACSNIClient(SNIClient):
|
||||||
game: str = "Lufia II Ancient Cave"
|
game: str = "Lufia II Ancient Cave"
|
||||||
|
patch_suffix = ".apl2ac"
|
||||||
|
|
||||||
async def validate_rom(self, ctx: SNIContext) -> bool:
|
async def validate_rom(self, ctx: SNIContext) -> bool:
|
||||||
from SNIClient import snes_read
|
from SNIClient import snes_read
|
||||||
|
|
|
@ -37,6 +37,7 @@ SM_REMOTE_ITEM_FLAG_ADDR = ROM_START + 0x277F06 # 1 byte
|
||||||
|
|
||||||
class SMSNIClient(SNIClient):
|
class SMSNIClient(SNIClient):
|
||||||
game = "Super Metroid"
|
game = "Super Metroid"
|
||||||
|
patch_suffix = [".apsm", ".apm3"]
|
||||||
|
|
||||||
async def deathlink_kill_player(self, ctx):
|
async def deathlink_kill_player(self, ctx):
|
||||||
from SNIClient import DeathState, snes_buffered_write, snes_flush_writes, snes_read
|
from SNIClient import DeathState, snes_buffered_write, snes_flush_writes, snes_read
|
||||||
|
|
|
@ -57,6 +57,7 @@ SMW_UNCOLLECTABLE_LEVELS = [0x25, 0x07, 0x0B, 0x40, 0x0E, 0x1F, 0x20, 0x1B, 0x1A
|
||||||
|
|
||||||
class SMWSNIClient(SNIClient):
|
class SMWSNIClient(SNIClient):
|
||||||
game = "Super Mario World"
|
game = "Super Mario World"
|
||||||
|
patch_suffix = ".apsmw"
|
||||||
|
|
||||||
async def deathlink_kill_player(self, ctx):
|
async def deathlink_kill_player(self, ctx):
|
||||||
from SNIClient import DeathState, snes_buffered_write, snes_flush_writes, snes_read
|
from SNIClient import DeathState, snes_buffered_write, snes_flush_writes, snes_read
|
||||||
|
|
|
@ -32,6 +32,7 @@ SMZ3_RECV_ITEM_PLAYER_ADDR = SAVEDATA_START + 0x4D3 # 1 byte
|
||||||
|
|
||||||
class SMZ3SNIClient(SNIClient):
|
class SMZ3SNIClient(SNIClient):
|
||||||
game = "SMZ3"
|
game = "SMZ3"
|
||||||
|
patch_suffix = ".apsmz3"
|
||||||
|
|
||||||
async def validate_rom(self, ctx):
|
async def validate_rom(self, ctx):
|
||||||
from SNIClient import snes_buffered_write, snes_flush_writes, snes_read
|
from SNIClient import snes_buffered_write, snes_flush_writes, snes_read
|
||||||
|
|
Loading…
Reference in New Issue