From b4b9ff5d828ad269ff390fe07d6d8c51aba16d52 Mon Sep 17 00:00:00 2001 From: PoryGone <98504756+PoryGone@users.noreply.github.com> Date: Tue, 27 Sep 2022 07:26:33 -0400 Subject: [PATCH 01/18] Docs: Update snes9x Links (#1048) --- worlds/alttp/docs/multiworld_de.md | 4 ++-- worlds/alttp/docs/multiworld_en.md | 2 +- worlds/alttp/docs/multiworld_es.md | 6 +++--- worlds/alttp/docs/multiworld_fr.md | 6 +++--- worlds/dkc3/docs/setup_en.md | 5 ++--- worlds/sm/docs/multiworld_en.md | 5 ++--- worlds/smz3/docs/multiworld_en.md | 5 ++--- 7 files changed, 15 insertions(+), 18 deletions(-) diff --git a/worlds/alttp/docs/multiworld_de.md b/worlds/alttp/docs/multiworld_de.md index 877f4abe83..417bb8acff 100644 --- a/worlds/alttp/docs/multiworld_de.md +++ b/worlds/alttp/docs/multiworld_de.md @@ -6,7 +6,7 @@ - [SNI](https://github.com/alttpo/sni/releases) (Integriert in Archipelago) - Hardware oder Software zum Laden und Abspielen von SNES Rom-Dateien fähig zu einer Internetverbindung - Ein Emulator, der mit SNI verbinden kann - ([snes9x Multitroid](https://drive.google.com/drive/folders/1_ej-pwWtCAHYXIrvs5Hro16A1s9Hi3Jz), + ([snes9x rr](https://github.com/gocha/snes9x-rr/releases), [BizHawk](http://tasvideos.org/BizHawk.html)) - Ein SD2SNES, [FXPak Pro](https://krikzz.com/store/home/54-fxpak-pro.html), oder andere kompatible Hardware - Die Japanische Zelda 1.0 ROM-Datei, mit folgendem Namen: `Zelda no Densetsu - Kamigami no Triforce (Japan).sfc` @@ -93,7 +93,7 @@ Wenn der client den Emulator automatisch gestartet hat, wird SNI ebenfalls im Hi Mal ist, wird möglicherweise ein Fenster angezeigt, wo man bestätigen muss, dass das Programm durch die Windows Firewall kommunizieren darf. -##### snes9x Multitroid +##### snes9x-rr 1. Lade die Entsprechende ROM-Datei, wenn sie nicht schon automatisch geladen wurde. 2. Klicke auf den Reiter "File" oben im Menü und wähle **Lua Scripting** diff --git a/worlds/alttp/docs/multiworld_en.md b/worlds/alttp/docs/multiworld_en.md index 1e6b2d1044..e758edc22c 100644 --- a/worlds/alttp/docs/multiworld_en.md +++ b/worlds/alttp/docs/multiworld_en.md @@ -75,7 +75,7 @@ client, and will also create your ROM in the same place as your patch file. When the client launched automatically, SNI should have also automatically launched in the background. If this is its first time launching, you may be prompted to allow it to communicate through the Windows Firewall. -##### snes9x Multitroid +##### snes9x-rr 1. Load your ROM file if it hasn't already been loaded. 2. Click on the File menu and hover on **Lua Scripting** diff --git a/worlds/alttp/docs/multiworld_es.md b/worlds/alttp/docs/multiworld_es.md index da0b9d99bf..ca33f796c2 100644 --- a/worlds/alttp/docs/multiworld_es.md +++ b/worlds/alttp/docs/multiworld_es.md @@ -12,7 +12,7 @@ - [QUsb2Snes](https://github.com/Skarsnik/QUsb2snes/releases) (Incluido en Multiworld Utilities) - Hardware o software capaz de cargar y ejecutar archivos de ROM de SNES - Un emulador capaz de ejecutar scripts Lua - ([snes9x Multitroid](https://drive.google.com/drive/folders/1_ej-pwWtCAHYXIrvs5Hro16A1s9Hi3Jz), + ([snes9x rr](https://github.com/gocha/snes9x-rr/releases), [BizHawk](http://tasvideos.org/BizHawk.html), o [RetroArch](https://retroarch.com?page=platforms) 1.10.1 o más nuevo). O, - Un flashcart SD2SNES, [FXPak Pro](https://krikzz.com/store/home/54-fxpak-pro.html), o otro hardware compatible @@ -111,13 +111,13 @@ automáticamente el cliente, y ademas creara la rom en el mismo directorio donde Cuando el cliente se lance automáticamente, QUsb2Snes debería haberse ejecutado también. Si es la primera vez que lo ejecutas, puedes ser que el firewall de Windows te pregunte si le permites la comunicación. -##### snes9x Multitroid +##### snes9x-rr 1. Carga tu fichero de ROM, si no lo has hecho ya 2. Abre el menu "File" y situa el raton en **Lua Scripting** 3. Haz click en **New Lua Script Window...** 4. En la nueva ventana, haz click en **Browse...** -5. Navega hacia el directorio donde este situado snes9x Multitroid, entra en el directorio `lua`, y +5. Navega hacia el directorio donde este situado snes9x-rr, entra en el directorio `lua`, y escoge `multibridge.lua` 6. Observa que se ha asignado un nombre al dispositivo, y el cliente muestra "SNES Device: Connected", con el mismo nombre en la esquina superior izquierda. diff --git a/worlds/alttp/docs/multiworld_fr.md b/worlds/alttp/docs/multiworld_fr.md index 4a8ca5902e..380a010232 100644 --- a/worlds/alttp/docs/multiworld_fr.md +++ b/worlds/alttp/docs/multiworld_fr.md @@ -12,7 +12,7 @@ - [QUsb2Snes](https://github.com/Skarsnik/QUsb2snes/releases) (Inclus dans les utilitaires précédents) - Une solution logicielle ou matérielle capable de charger et de lancer des fichiers ROM de SNES - Un émulateur capable d'éxécuter des scripts Lua - ([snes9x Multitroid](https://drive.google.com/drive/folders/1_ej-pwWtCAHYXIrvs5Hro16A1s9Hi3Jz), + ([snes9x rr](https://github.com/gocha/snes9x-rr/releases), [BizHawk](http://tasvideos.org/BizHawk.html)) - Un SD2SNES, [FXPak Pro](https://krikzz.com/store/home/54-fxpak-pro.html), ou une autre solution matérielle compatible @@ -112,13 +112,13 @@ Quand le client se lance automatiquement, QUsb2Snes devrait se lancer automatiqu c'est la première fois qu'il démarre, il vous sera peut-être demandé de l'autoriser à communiquer à travers le pare-feu Windows. -##### snes9x Multitroid +##### snes9x-rr 1. Chargez votre ROM si ce n'est pas déjà fait. 2. Cliquez sur le menu "File" et survolez l'option **Lua Scripting** 3. Cliquez alors sur **New Lua Script Window...** 4. Dans la nouvelle fenêtre, sélectionnez **Browse...** -5. Dirigez vous vers le dossier où vous avez extrait snes9x Multitroid, allez dans le dossier `lua`, puis +5. Dirigez vous vers le dossier où vous avez extrait snes9x-rr, allez dans le dossier `lua`, puis choisissez `multibridge.lua` 6. Remarquez qu'un nom vous a été assigné, et que l'interface Web affiche "SNES Device: Connected", avec ce même nom dans le coin en haut à gauche. diff --git a/worlds/dkc3/docs/setup_en.md b/worlds/dkc3/docs/setup_en.md index 471248deb8..0ebe189a1e 100644 --- a/worlds/dkc3/docs/setup_en.md +++ b/worlds/dkc3/docs/setup_en.md @@ -7,8 +7,7 @@ - Hardware or software capable of loading and playing SNES ROM files - An emulator capable of connecting to SNI such as: - - snes9x Multitroid - from: [snes9x Multitroid Download](https://drive.google.com/drive/folders/1_ej-pwWtCAHYXIrvs5Hro16A1s9Hi3Jz), + - snes9x-rr from: [snes9x rr](https://github.com/gocha/snes9x-rr/releases), - BizHawk from: [BizHawk Website](http://tasvideos.org/BizHawk.html) - RetroArch 1.10.3 or newer from: [RetroArch Website](https://retroarch.com?page=platforms). Or, - An SD2SNES, FXPak Pro ([FXPak Pro Store Page](https://krikzz.com/store/home/54-fxpak-pro.html)), or other @@ -81,7 +80,7 @@ client, and will also create your ROM in the same place as your patch file. When the client launched automatically, SNI should have also automatically launched in the background. If this is its first time launching, you may be prompted to allow it to communicate through the Windows Firewall. -##### snes9x Multitroid +##### snes9x-rr 1. Load your ROM file if it hasn't already been loaded. 2. Click on the File menu and hover on **Lua Scripting** diff --git a/worlds/sm/docs/multiworld_en.md b/worlds/sm/docs/multiworld_en.md index 27e1d4a4ad..826be60188 100644 --- a/worlds/sm/docs/multiworld_en.md +++ b/worlds/sm/docs/multiworld_en.md @@ -7,8 +7,7 @@ - Hardware or software capable of loading and playing SNES ROM files - An emulator capable of connecting to SNI such as: - - snes9x Multitroid - from: [snes9x Multitroid Download](https://drive.google.com/drive/folders/1_ej-pwWtCAHYXIrvs5Hro16A1s9Hi3Jz), + - snes9x-rr from: [snes9x rr](https://github.com/gocha/snes9x-rr/releases), - BizHawk from: [BizHawk Website](http://tasvideos.org/BizHawk.html) - RetroArch 1.10.1 or newer from: [RetroArch Website](https://retroarch.com?page=platforms). Or, - An SD2SNES, FXPak Pro ([FXPak Pro Store Page](https://krikzz.com/store/home/54-fxpak-pro.html)), or other @@ -81,7 +80,7 @@ client, and will also create your ROM in the same place as your patch file. When the client launched automatically, SNI should have also automatically launched in the background. If this is its first time launching, you may be prompted to allow it to communicate through the Windows Firewall. -##### snes9x Multitroid +##### snes9x-rr 1. Load your ROM file if it hasn't already been loaded. 2. Click on the File menu and hover on **Lua Scripting** diff --git a/worlds/smz3/docs/multiworld_en.md b/worlds/smz3/docs/multiworld_en.md index 7457d6d0a7..735be9d519 100644 --- a/worlds/smz3/docs/multiworld_en.md +++ b/worlds/smz3/docs/multiworld_en.md @@ -8,8 +8,7 @@ `SNI Client - A Link to the Past Patch Setup` - Hardware or software capable of loading and playing SNES ROM files - An emulator capable of connecting to SNI such as: - - snes9x Multitroid - from: [snes9x Multitroid Download](https://drive.google.com/drive/folders/1_ej-pwWtCAHYXIrvs5Hro16A1s9Hi3Jz), + - snes9x-rr from: [snes9x rr](https://github.com/gocha/snes9x-rr/releases), - BizHawk from: [BizHawk Website](http://tasvideos.org/BizHawk.html), or - RetroArch 1.10.3 or newer from: [RetroArch Website](https://retroarch.com?page=platforms). Or, - An SD2SNES, FXPak Pro ([FXPak Pro Store Page](https://krikzz.com/store/home/54-fxpak-pro.html)), or other @@ -79,7 +78,7 @@ client, and will also create your ROM in the same place as your patch file. When the client launched automatically, SNI should have also automatically launched in the background. If this is its first time launching, you may be prompted to allow it to communicate through the Windows Firewall. -##### snes9x Multitroid +##### snes9x-rr 1. Load your ROM file if it hasn't already been loaded. 2. Click on the File menu and hover on **Lua Scripting** From 8bc8b412a384f9ac0f661e9cb746691b4c439dc2 Mon Sep 17 00:00:00 2001 From: alwaysintreble Date: Wed, 28 Sep 2022 16:02:42 -0500 Subject: [PATCH 02/18] Core: fix unweighted options for meta files (#1053) --- Generate.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Generate.py b/Generate.py index f048e54383..57d060d4d4 100644 --- a/Generate.py +++ b/Generate.py @@ -377,7 +377,7 @@ def roll_meta_option(option_key, game: str, category_dict: Dict) -> Any: if option_key in options: if options[option_key].supports_weighting: return get_choice(option_key, category_dict) - return options[option_key] + return category_dict[option_key] if game == "A Link to the Past": # TODO wow i hate this if option_key in {"glitches_required", "dark_room_logic", "entrance_shuffle", "goals", "triforce_pieces_mode", "triforce_pieces_percentage", "triforce_pieces_available", "triforce_pieces_extra", From c96b6d7b957a393e247b5b0804528629018dfb81 Mon Sep 17 00:00:00 2001 From: Doug Hoskisson Date: Wed, 28 Sep 2022 14:54:10 -0700 Subject: [PATCH 03/18] Core: some typing and docs in various parts of the interface (#1060) * some typing and docs in various parts of the interface * fix whitespace in docstring Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com> * suggested changes from discussion * remove redundant import * adjust type for json messages * for options module detection: module.lower().endswith("options") Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com> --- BaseClasses.py | 6 ++- CommonClient.py | 28 +++++----- NetUtils.py | 2 +- Options.py | 5 +- Patch.py | 89 +++++++++++++++----------------- Utils.py | 16 +++--- WebHostLib/options.py | 5 +- WebHostLib/upload.py | 2 +- docs/running from source.md | 4 ++ test/general/TestReachability.py | 2 +- worlds/AutoWorld.py | 15 +++--- worlds/generic/Rules.py | 6 +-- 12 files changed, 96 insertions(+), 84 deletions(-) diff --git a/BaseClasses.py b/BaseClasses.py index df8ac02071..478dd1cf9c 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -40,6 +40,7 @@ class MultiWorld(): plando_connections: List worlds: Dict[int, auto_world] groups: Dict[int, Group] + regions: List[Region] itempool: List[Item] is_race: bool = False precollected_items: Dict[int, List[Item]] @@ -50,6 +51,7 @@ class MultiWorld(): non_local_items: Dict[int, Options.NonLocalItems] progression_balancing: Dict[int, Options.ProgressionBalancing] completion_condition: Dict[int, Callable[[CollectionState], bool]] + exclude_locations: Dict[int, Options.ExcludeLocations] class AttributeProxy(): def __init__(self, rule): @@ -993,7 +995,7 @@ class Entrance: return False - def connect(self, region: Region, addresses=None, target=None): + def connect(self, region: Region, addresses: Any = None, target: Any = None) -> None: self.connected_region = region self.target = target self.addresses = addresses @@ -1081,7 +1083,7 @@ class Location: show_in_spoiler: bool = True progress_type: LocationProgressType = LocationProgressType.DEFAULT always_allow = staticmethod(lambda item, state: False) - access_rule = staticmethod(lambda state: True) + access_rule: Callable[[CollectionState], bool] = staticmethod(lambda state: True) item_rule = staticmethod(lambda item: True) item: Optional[Item] = None diff --git a/CommonClient.py b/CommonClient.py index 94d4359dd1..2940ceed31 100644 --- a/CommonClient.py +++ b/CommonClient.py @@ -132,12 +132,12 @@ class CommonContext: # defaults starting_reconnect_delay: int = 5 current_reconnect_delay: int = starting_reconnect_delay - command_processor: type(CommandProcessor) = ClientCommandProcessor + command_processor: typing.Type[CommandProcessor] = ClientCommandProcessor ui = None - ui_task: typing.Optional[asyncio.Task] = None - input_task: typing.Optional[asyncio.Task] = None - keep_alive_task: typing.Optional[asyncio.Task] = None - server_task: typing.Optional[asyncio.Task] = None + ui_task: typing.Optional["asyncio.Task[None]"] = None + input_task: typing.Optional["asyncio.Task[None]"] = None + keep_alive_task: typing.Optional["asyncio.Task[None]"] = None + server_task: typing.Optional["asyncio.Task[None]"] = None server: typing.Optional[Endpoint] = None server_version: Version = Version(0, 0, 0) current_energy_link_value: int = 0 # to display in UI, gets set by server @@ -146,7 +146,7 @@ class CommonContext: # remaining type info slot_info: typing.Dict[int, NetworkSlot] - server_address: str + server_address: typing.Optional[str] password: typing.Optional[str] hint_cost: typing.Optional[int] player_names: typing.Dict[int, str] @@ -154,6 +154,7 @@ class CommonContext: # locations locations_checked: typing.Set[int] # local state locations_scouted: typing.Set[int] + items_received: typing.List[NetworkItem] missing_locations: typing.Set[int] # server state checked_locations: typing.Set[int] # server state server_locations: typing.Set[int] # all locations the server knows of, missing_location | checked_locations @@ -163,7 +164,7 @@ class CommonContext: # current message box through kvui _messagebox = None - def __init__(self, server_address, password): + def __init__(self, server_address: typing.Optional[str], password: typing.Optional[str]) -> None: # server state self.server_address = server_address self.username = None @@ -243,7 +244,8 @@ class CommonContext: if self.server_task is not None: await self.server_task - async def send_msgs(self, msgs): + async def send_msgs(self, msgs: typing.List[typing.Any]) -> None: + """ `msgs` JSON serializable """ if not self.server or not self.server.socket.open or self.server.socket.closed: return await self.server.socket.send(encode(msgs)) @@ -271,7 +273,7 @@ class CommonContext: logger.info('Enter slot name:') self.auth = await self.console_input() - async def send_connect(self, **kwargs): + async def send_connect(self, **kwargs: typing.Any) -> None: payload = { 'cmd': 'Connect', 'password': self.password, 'name': self.auth, 'version': Utils.version_tuple, @@ -282,7 +284,7 @@ class CommonContext: payload.update(kwargs) await self.send_msgs([payload]) - async def console_input(self): + async def console_input(self) -> str: self.input_requests += 1 return await self.input_queue.get() @@ -390,7 +392,7 @@ class CommonContext: # DeathLink hooks - def on_deathlink(self, data: dict): + def on_deathlink(self, data: typing.Dict[str, typing.Any]) -> None: """Gets dispatched when a new DeathLink is triggered by another linked player.""" self.last_death_link = max(data["time"], self.last_death_link) text = data.get("cause", "") @@ -477,7 +479,7 @@ async def keep_alive(ctx: CommonContext, seconds_between_checks=100): seconds_elapsed = 0 -async def server_loop(ctx: CommonContext, address=None): +async def server_loop(ctx: CommonContext, address: typing.Optional[str] = None) -> None: if ctx.server and ctx.server.socket: logger.error('Already connected') return @@ -722,7 +724,7 @@ async def console_loop(ctx: CommonContext): logger.exception(e) -def get_base_parser(description=None): +def get_base_parser(description: typing.Optional[str] = None): import argparse parser = argparse.ArgumentParser(description=description) parser.add_argument('--connect', default=None, help='Address of the multiworld host.') diff --git a/NetUtils.py b/NetUtils.py index 1e7d66d824..513ab074fc 100644 --- a/NetUtils.py +++ b/NetUtils.py @@ -100,7 +100,7 @@ _encode = JSONEncoder( ).encode -def encode(obj): +def encode(obj: typing.Any) -> str: return _encode(_scan_for_TypedTuples(obj)) diff --git a/Options.py b/Options.py index 49f044d8cd..567ac8dbc6 100644 --- a/Options.py +++ b/Options.py @@ -165,6 +165,7 @@ class FreeText(Option): class NumericOption(Option[int], numbers.Integral): + default = 0 # note: some of the `typing.Any`` here is a result of unresolved issue in python standards # `int` is not a `numbers.Integral` according to the official typestubs # (even though isinstance(5, numbers.Integral) == True) @@ -628,7 +629,7 @@ class VerifyKeys: class OptionDict(Option[typing.Dict[str, typing.Any]], VerifyKeys): - default = {} + default: typing.Dict[str, typing.Any] = {} supports_weighting = False def __init__(self, value: typing.Dict[str, typing.Any]): @@ -659,7 +660,7 @@ class ItemDict(OptionDict): class OptionList(Option[typing.List[typing.Any]], VerifyKeys): - default = [] + default: typing.List[typing.Any] = [] supports_weighting = False def __init__(self, value: typing.List[typing.Any]): diff --git a/Patch.py b/Patch.py index aaa4fc2404..9b2c88a6b6 100644 --- a/Patch.py +++ b/Patch.py @@ -2,7 +2,7 @@ from __future__ import annotations import shutil import json -import bsdiff4 +import bsdiff4 # type: ignore import yaml import os import lzma @@ -10,7 +10,7 @@ import threading import concurrent.futures import zipfile import sys -from typing import Tuple, Optional, Dict, Any, Union, BinaryIO +from typing import ClassVar, List, Tuple, Optional, Dict, Any, Union, BinaryIO import ModuleUpdate ModuleUpdate.update() @@ -21,10 +21,10 @@ current_patch_version = 5 class AutoPatchRegister(type): - patch_types: Dict[str, APDeltaPatch] = {} - file_endings: Dict[str, APDeltaPatch] = {} + patch_types: ClassVar[Dict[str, AutoPatchRegister]] = {} + file_endings: ClassVar[Dict[str, AutoPatchRegister]] = {} - def __new__(cls, name: str, bases, dct: Dict[str, Any]): + def __new__(cls, name: str, bases: Tuple[type, ...], dct: Dict[str, Any]) -> AutoPatchRegister: # construct class new_class = super().__new__(cls, name, bases, dct) if "game" in dct: @@ -35,10 +35,11 @@ class AutoPatchRegister(type): return new_class @staticmethod - def get_handler(file: str) -> Optional[type(APDeltaPatch)]: + def get_handler(file: str) -> Optional[AutoPatchRegister]: for file_ending, handler in AutoPatchRegister.file_endings.items(): if file.endswith(file_ending): return handler + return None class APContainer: @@ -61,34 +62,36 @@ class APContainer: self.player_name = player_name self.server = server - def write(self, file: Optional[Union[str, BinaryIO]] = None): - if not self.path and not file: + def write(self, file: Optional[Union[str, BinaryIO]] = None) -> None: + zip_file = file if file else self.path + if not zip_file: raise FileNotFoundError(f"Cannot write {self.__class__.__name__} due to no path provided.") - with zipfile.ZipFile(file if file else self.path, "w", self.compression_method, True, self.compression_level) \ + with zipfile.ZipFile(zip_file, "w", self.compression_method, True, self.compression_level) \ as zf: if file: self.path = zf.filename self.write_contents(zf) - def write_contents(self, opened_zipfile: zipfile.ZipFile): + def write_contents(self, opened_zipfile: zipfile.ZipFile) -> None: manifest = self.get_manifest() try: - manifest = json.dumps(manifest) + manifest_str = json.dumps(manifest) except Exception as e: raise Exception(f"Manifest {manifest} did not convert to json.") from e else: - opened_zipfile.writestr("archipelago.json", manifest) + opened_zipfile.writestr("archipelago.json", manifest_str) - def read(self, file: Optional[Union[str, BinaryIO]] = None): + def read(self, file: Optional[Union[str, BinaryIO]] = None) -> None: """Read data into patch object. file can be file-like, such as an outer zip file's stream.""" - if not self.path and not file: + zip_file = file if file else self.path + if not zip_file: raise FileNotFoundError(f"Cannot read {self.__class__.__name__} due to no path provided.") - with zipfile.ZipFile(file if file else self.path, "r") as zf: + with zipfile.ZipFile(zip_file, "r") as zf: if file: self.path = zf.filename self.read_contents(zf) - def read_contents(self, opened_zipfile: zipfile.ZipFile): + def read_contents(self, opened_zipfile: zipfile.ZipFile) -> None: with opened_zipfile.open("archipelago.json", "r") as f: manifest = json.load(f) if manifest["compatible_version"] > self.version: @@ -98,7 +101,7 @@ class APContainer: self.server = manifest["server"] self.player_name = manifest["player_name"] - def get_manifest(self) -> dict: + def get_manifest(self) -> Dict[str, Any]: return { "server": self.server, # allow immediate connection to server in multiworld. Empty string otherwise "player": self.player, @@ -114,17 +117,17 @@ class APDeltaPatch(APContainer, metaclass=AutoPatchRegister): """An APContainer that additionally has delta.bsdiff4 containing a delta patch to get the desired file, often a rom.""" - hash = Optional[str] # base checksum of source file + hash: Optional[str] # base checksum of source file patch_file_ending: str = "" delta: Optional[bytes] = None result_file_ending: str = ".sfc" source_data: bytes - def __init__(self, *args, patched_path: str = "", **kwargs): + def __init__(self, *args: Any, patched_path: str = "", **kwargs: Any) -> None: self.patched_path = patched_path super(APDeltaPatch, self).__init__(*args, **kwargs) - def get_manifest(self) -> dict: + def get_manifest(self) -> Dict[str, Any]: manifest = super(APDeltaPatch, self).get_manifest() manifest["base_checksum"] = self.hash manifest["result_file_ending"] = self.result_file_ending @@ -205,15 +208,19 @@ def generate_yaml(patch: bytes, metadata: Optional[dict] = None, game: str = GAM return patch.encode(encoding="utf-8-sig") -def generate_patch(rom: bytes, metadata: Optional[dict] = None, game: str = GAME_ALTTP) -> bytes: +def generate_patch(rom: bytes, metadata: Optional[Dict[str, Any]] = None, game: str = GAME_ALTTP) -> bytes: if metadata is None: metadata = {} patch = bsdiff4.diff(get_base_rom_data(game), rom) return generate_yaml(patch, metadata, game) -def create_patch_file(rom_file_to_patch: str, server: str = "", destination: str = None, - player: int = 0, player_name: str = "", game: str = GAME_ALTTP) -> str: +def create_patch_file(rom_file_to_patch: str, + server: str = "", + destination: Optional[str] = None, + player: int = 0, + player_name: str = "", + game: str = GAME_ALTTP) -> str: meta = {"server": server, # allow immediate connection to server in multiworld. Empty string otherwise "player_id": player, "player_name": player_name} @@ -229,19 +236,19 @@ def create_patch_file(rom_file_to_patch: str, server: str = "", destination: str return target -def create_rom_bytes(patch_file: str, ignore_version: bool = False) -> Tuple[dict, str, bytearray]: +def create_rom_bytes(patch_file: str, ignore_version: bool = False) -> Tuple[Dict[str, Any], str, bytearray]: data = Utils.parse_yaml(lzma.decompress(load_bytes(patch_file)).decode("utf-8-sig")) game_name = data["game"] if not ignore_version and data["compatible_version"] > current_patch_version: raise RuntimeError("Patch file is incompatible with this patcher, likely an update is required.") - patched_data = bsdiff4.patch(get_base_rom_data(game_name), data["patch"]) + patched_data: bytearray = bsdiff4.patch(get_base_rom_data(game_name), data["patch"]) rom_hash = patched_data[int(0x7FC0):int(0x7FD5)] data["meta"]["hash"] = "".join(chr(x) for x in rom_hash) target = os.path.splitext(patch_file)[0] + ".sfc" return data["meta"], target, patched_data -def get_base_rom_data(game: str): +def get_base_rom_data(game: str) -> bytes: if game == GAME_ALTTP: from worlds.alttp.Rom import get_base_rom_bytes elif game == "alttp": # old version for A Link to the Past @@ -260,7 +267,7 @@ def get_base_rom_data(game: str): return get_base_rom_bytes() -def create_rom_file(patch_file: str) -> Tuple[dict, str]: +def create_rom_file(patch_file: str) -> Tuple[Dict[str, Any], str]: auto_handler = AutoPatchRegister.get_handler(patch_file) if auto_handler: handler: APDeltaPatch = auto_handler(patch_file) @@ -293,7 +300,7 @@ def write_lzma(data: bytes, path: str): f.write(data) -def read_rom(stream, strip_header=True) -> bytearray: +def read_rom(stream: BinaryIO, strip_header: bool = True) -> bytearray: """Reads rom into bytearray and optionally strips off any smc header""" buffer = bytearray(stream.read()) if strip_header and len(buffer) % 0x400 == 0x200: @@ -321,7 +328,7 @@ if __name__ == "__main__": elif rom.endswith(".apbp"): print(f"Applying patch {rom}") data, target = create_rom_file(rom) - #romfile, adjusted = Utils.get_adjuster_settings(target) + # romfile, adjusted = Utils.get_adjuster_settings(target) adjuster_settings = Utils.get_adjuster_settings(GAME_ALTTP) adjusted = False if adjuster_settings: @@ -385,21 +392,9 @@ if __name__ == "__main__": if 'server' in data: Utils.persistent_store("servers", data['hash'], data['server']) print(f"Host is {data['server']}") - elif rom.endswith(".apm3"): - print(f"Applying patch {rom}") - data, target = create_rom_file(rom) - print(f"Created rom {target}.") - if 'server' in data: - Utils.persistent_store("servers", data['hash'], data['server']) - print(f"Host is {data['server']}") - elif rom.endswith(".apsmz"): - print(f"Applying patch {rom}") - data, target = create_rom_file(rom) - print(f"Created rom {target}.") - if 'server' in data: - Utils.persistent_store("servers", data['hash'], data['server']) - print(f"Host is {data['server']}") - elif rom.endswith(".apdkc3"): + elif rom.endswith(".apm3") \ + or rom.endswith(".apsmz") \ + or rom.endswith(".apdkc3"): print(f"Applying patch {rom}") data, target = create_rom_file(rom) print(f"Created rom {target}.") @@ -410,8 +405,7 @@ if __name__ == "__main__": elif rom.endswith(".zip"): print(f"Updating host in patch files contained in {rom}") - - def _handle_zip_file_entry(zfinfo: zipfile.ZipInfo, server: str): + def _handle_zip_file_entry(zfinfo: zipfile.ZipInfo, server: str) -> str: data = zfr.read(zfinfo) if zfinfo.filename.endswith(".apbp") or \ zfinfo.filename.endswith(".apm3") or \ @@ -421,8 +415,7 @@ if __name__ == "__main__": zfw.writestr(zfinfo, data) return zfinfo.filename - - futures = [] + futures: List[concurrent.futures.Future[str]] = [] with zipfile.ZipFile(rom, "r") as zfr: updated_zip = os.path.splitext(rom)[0] + "_updated.zip" with zipfile.ZipFile(updated_zip, "w", compression=zipfile.ZIP_DEFLATED, diff --git a/Utils.py b/Utils.py index c362131d75..93627ffd89 100644 --- a/Utils.py +++ b/Utils.py @@ -217,8 +217,11 @@ def get_public_ipv6() -> str: return ip +OptionsType = typing.Dict[str, typing.Dict[str, typing.Any]] + + @cache_argsless -def get_default_options() -> dict: +def get_default_options() -> OptionsType: # Refer to host.yaml for comments as to what all these options mean. options = { "general_options": { @@ -290,7 +293,7 @@ def get_default_options() -> dict: return options -def update_options(src: dict, dest: dict, filename: str, keys: list) -> dict: +def update_options(src: dict, dest: dict, filename: str, keys: list) -> OptionsType: for key, value in src.items(): new_keys = keys.copy() new_keys.append(key) @@ -310,9 +313,9 @@ def update_options(src: dict, dest: dict, filename: str, keys: list) -> dict: @cache_argsless -def get_options() -> dict: +def get_options() -> OptionsType: filenames = ("options.yaml", "host.yaml") - locations = [] + locations: typing.List[str] = [] if os.path.join(os.getcwd()) != local_path(): locations += filenames # use files from cwd only if it's not the local_path locations += [user_path(filename) for filename in filenames] @@ -353,7 +356,7 @@ def persistent_load() -> typing.Dict[str, dict]: return storage -def get_adjuster_settings(game_name: str): +def get_adjuster_settings(game_name: str) -> typing.Dict[str, typing.Any]: adjuster_settings = persistent_load().get("adjuster", {}).get(game_name, {}) return adjuster_settings @@ -392,7 +395,8 @@ class RestrictedUnpickler(pickle.Unpickler): # Options and Plando are unpickled by WebHost -> Generate if module == "worlds.generic" and name in {"PlandoItem", "PlandoConnection"}: return getattr(self.generic_properties_module, name) - if module.endswith("Options"): + # pep 8 specifies that modules should have "all-lowercase names" (options, not Options) + if module.lower().endswith("options"): if module == "Options": mod = self.options_module else: diff --git a/WebHostLib/options.py b/WebHostLib/options.py index daa742d90e..6807d54689 100644 --- a/WebHostLib/options.py +++ b/WebHostLib/options.py @@ -64,7 +64,10 @@ def create(): for game_name, world in AutoWorldRegister.world_types.items(): - all_options = {**Options.per_game_common_options, **world.option_definitions} + all_options: typing.Dict[str, Options.AssembleOptions] = { + **Options.per_game_common_options, + **world.option_definitions + } with open(local_path("WebHostLib", "templates", "options.yaml")) as f: file_data = f.read() res = Template(file_data).render( diff --git a/WebHostLib/upload.py b/WebHostLib/upload.py index 00825df47b..6907bb2acd 100644 --- a/WebHostLib/upload.py +++ b/WebHostLib/upload.py @@ -22,7 +22,7 @@ def upload_zip_to_db(zfile: zipfile.ZipFile, owner=None, meta={"race": False}, s if not owner: owner = session["_id"] infolist = zfile.infolist() - slots = set() + slots: typing.Set[Slot] = set() spoiler = "" multidata = None for file in infolist: diff --git a/docs/running from source.md b/docs/running from source.md index 39addd0a28..24486146f8 100644 --- a/docs/running from source.md +++ b/docs/running from source.md @@ -16,6 +16,10 @@ Then run any of the starting point scripts, like Generate.py, and the included M required modules and after pressing enter proceed to install everything automatically. After this, you should be able to run the programs. + * With yaml(s) in the `Players` folder, `Generate.py` will generate the multiworld archive. + * `MultiServer.py`, with the filename of the generated archive as a command line parameter, will host the multiworld locally. + * `--log_network` is a command line parameter useful for debugging. + ## Windows diff --git a/test/general/TestReachability.py b/test/general/TestReachability.py index 2cadf9d29a..d638b56e8d 100644 --- a/test/general/TestReachability.py +++ b/test/general/TestReachability.py @@ -20,7 +20,7 @@ class TestBase(unittest.TestCase): for location in world.get_locations(): if location.name not in excluded: with self.subTest("Location should be reached", location=location): - self.assertTrue(location.can_reach(state)) + self.assertTrue(location.can_reach(state), f"{location.name} unreachable") with self.subTest("Completion Condition"): self.assertTrue(world.can_beat_game(state)) diff --git a/worlds/AutoWorld.py b/worlds/AutoWorld.py index db72ca6a95..5dea03481f 100644 --- a/worlds/AutoWorld.py +++ b/worlds/AutoWorld.py @@ -3,9 +3,9 @@ from __future__ import annotations import logging import sys import pathlib -from typing import Dict, FrozenSet, Set, Tuple, List, Optional, TextIO, Any, Callable, Union, TYPE_CHECKING +from typing import Dict, FrozenSet, Set, Tuple, List, Optional, TextIO, Any, Callable, Type, Union, TYPE_CHECKING -from Options import Option +from Options import AssembleOptions from BaseClasses import CollectionState if TYPE_CHECKING: @@ -13,7 +13,7 @@ if TYPE_CHECKING: class AutoWorldRegister(type): - world_types: Dict[str, type(World)] = {} + world_types: Dict[str, Type[World]] = {} def __new__(mcs, name: str, bases: Tuple[type, ...], dct: Dict[str, Any]) -> AutoWorldRegister: if "web" in dct: @@ -120,7 +120,7 @@ class World(metaclass=AutoWorldRegister): """A World object encompasses a game's Items, Locations, Rules and additional data or functionality required. A Game should have its own subclass of World in which it defines the required data structures.""" - option_definitions: Dict[str, Option[Any]] = {} # link your Options mapping + option_definitions: Dict[str, AssembleOptions] = {} # link your Options mapping game: str # name the game topology_present: bool = False # indicate if world type has any meaningful layout/pathing @@ -229,7 +229,8 @@ class World(metaclass=AutoWorldRegister): pass def post_fill(self) -> None: - """Optional Method that is called after regular fill. Can be used to do adjustments before output generation.""" + """Optional Method that is called after regular fill. Can be used to do adjustments before output generation. + This happens before progression balancing, so the items may not be in their final locations yet.""" def generate_output(self, output_directory: str) -> None: """This method gets called from a threadpool, do not use world.random here. @@ -237,7 +238,9 @@ class World(metaclass=AutoWorldRegister): pass def fill_slot_data(self) -> Dict[str, Any]: # json of WebHostLib.models.Slot - """Fill in the slot_data field in the Connected network package.""" + """Fill in the `slot_data` field in the `Connected` network package. + This is a way the generator can give custom data to the client. + The client will receive this as JSON in the `Connected` response.""" return {} def extend_hint_information(self, hint_data: Dict[int, Dict[int, str]]): diff --git a/worlds/generic/Rules.py b/worlds/generic/Rules.py index f53c417e1c..9b338e4d70 100644 --- a/worlds/generic/Rules.py +++ b/worlds/generic/Rules.py @@ -1,6 +1,6 @@ import typing -from BaseClasses import LocationProgressType +from BaseClasses import LocationProgressType, MultiWorld if typing.TYPE_CHECKING: import BaseClasses @@ -37,14 +37,14 @@ def locality_rules(world, player: int): forbid_items_for_player(location, world.non_local_items[player].value, player) -def exclusion_rules(world, player: int, exclude_locations: typing.Set[str]): +def exclusion_rules(world: MultiWorld, player: int, exclude_locations: typing.Set[str]) -> None: for loc_name in exclude_locations: try: location = world.get_location(loc_name, player) except KeyError as e: # failed to find the given location. Check if it's a legitimate location if loc_name not in world.worlds[player].location_name_to_id: raise Exception(f"Unable to exclude location {loc_name} in player {player}'s world.") from e - else: + else: add_item_rule(location, lambda i: not (i.advancement or i.useful)) location.progress_type = LocationProgressType.EXCLUDED From e6a4925f0c197e33cb2a16dce98deb52eb4adf31 Mon Sep 17 00:00:00 2001 From: black-sliver <59490463+black-sliver@users.noreply.github.com> Date: Thu, 29 Sep 2022 00:09:04 +0200 Subject: [PATCH 04/18] Doc: update apclientpp to header-only (#1054) --- docs/network protocol.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/network protocol.md b/docs/network protocol.md index 3315ddec2d..d5c56a62b4 100644 --- a/docs/network protocol.md +++ b/docs/network protocol.md @@ -21,7 +21,7 @@ There are also a number of community-supported libraries available that implemen | | [Archipelago SNIClient](https://github.com/ArchipelagoMW/Archipelago/blob/main/SNIClient.py) | For Super Nintendo Game Support; Utilizes [SNI](https://github.com/alttpo/sni). | | JVM (Java / Kotlin) | [Archipelago.MultiClient.Java](https://github.com/ArchipelagoMW/Archipelago.MultiClient.Java) | | | .NET (C# / C++ / F# / VB.NET) | [Archipelago.MultiClient.Net](https://www.nuget.org/packages/Archipelago.MultiClient.Net) | | -| C++ | [apclientpp](https://github.com/black-sliver/apclientpp) | almost-header-only | +| C++ | [apclientpp](https://github.com/black-sliver/apclientpp) | header-only | | | [APCpp](https://github.com/N00byKing/APCpp) | CMake | | JavaScript / TypeScript | [archipelago.js](https://www.npmjs.com/package/archipelago.js) | Browser and Node.js Supported | | Haxe | [hxArchipelago](https://lib.haxe.org/p/hxArchipelago) | | From 885c8d3fccb831c00f610fa4f07807b71ea115da Mon Sep 17 00:00:00 2001 From: Alchav <59858495+Alchav@users.noreply.github.com> Date: Thu, 29 Sep 2022 13:59:57 -0400 Subject: [PATCH 05/18] Fix minimal accessibility failures (#726) --- BaseClasses.py | 4 ++-- Fill.py | 44 ++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 44 insertions(+), 4 deletions(-) diff --git a/BaseClasses.py b/BaseClasses.py index 478dd1cf9c..634c2a833f 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -531,7 +531,7 @@ class MultiWorld(): beatable_fulfilled = False - def location_conditition(location: Location): + def location_condition(location: Location): """Determine if this location has to be accessible, location is already filtered by location_relevant""" if location.player in players["minimal"]: return False @@ -548,7 +548,7 @@ class MultiWorld(): def all_done(): """Check if all access rules are fulfilled""" if beatable_fulfilled: - if any(location_conditition(location) for location in locations): + if any(location_condition(location) for location in locations): return False # still locations required to be collected return True diff --git a/Fill.py b/Fill.py index c62eaabde8..4b095eb108 100644 --- a/Fill.py +++ b/Fill.py @@ -4,9 +4,10 @@ import collections import itertools from collections import Counter, deque -from BaseClasses import CollectionState, Location, LocationProgressType, MultiWorld, Item +from BaseClasses import CollectionState, Location, LocationProgressType, MultiWorld, Item, ItemClassification from worlds.AutoWorld import call_all +from worlds.generic.Rules import add_item_rule class FillError(RuntimeError): @@ -209,6 +210,34 @@ def fast_fill(world: MultiWorld, return item_pool[placing:], fill_locations[placing:] +def accessibility_corrections(world: MultiWorld, state: CollectionState, locations, pool=[]): + maximum_exploration_state = sweep_from_pool(state, pool) + minimal_players = {player for player in world.player_ids if world.accessibility[player] == "minimal"} + unreachable_locations = [location for location in world.get_locations() if location.player in minimal_players and + not location.can_reach(maximum_exploration_state)] + for location in unreachable_locations: + if (location.item is not None and location.item.advancement and location.address is not None and not + location.locked and location.item.player not in minimal_players): + pool.append(location.item) + state.remove(location.item) + location.item = None + location.event = False + if location in state.events: + state.events.remove(location) + locations.append(location) + + if pool: + fill_restrictive(world, state, locations, pool) + + +def inaccessible_location_rules(world: MultiWorld, state: CollectionState, locations): + maximum_exploration_state = sweep_from_pool(state, []) + unreachable_locations = [location for location in locations if not location.can_reach(maximum_exploration_state)] + for location in unreachable_locations: + add_item_rule(location, lambda item: not ((item.classification & 0b0011) and + world.accessibility[item.player] != 'minimal')) + + def distribute_items_restrictive(world: MultiWorld) -> None: fill_locations = sorted(world.get_unfilled_locations()) world.random.shuffle(fill_locations) @@ -239,7 +268,15 @@ def distribute_items_restrictive(world: MultiWorld) -> None: defaultlocations = locations[LocationProgressType.DEFAULT] excludedlocations = locations[LocationProgressType.EXCLUDED] - fill_restrictive(world, world.state, prioritylocations, progitempool, lock=True) + prioritylocations_lock = prioritylocations.copy() + + fill_restrictive(world, world.state, prioritylocations, progitempool) + accessibility_corrections(world, world.state, prioritylocations, progitempool) + + for location in prioritylocations_lock: + if location.item: + location.locked = True + if prioritylocations: defaultlocations = prioritylocations + defaultlocations @@ -248,6 +285,9 @@ def distribute_items_restrictive(world: MultiWorld) -> None: if progitempool: raise FillError( f'Not enough locations for progress items. There are {len(progitempool)} more items than locations') + accessibility_corrections(world, world.state, defaultlocations) + + inaccessible_location_rules(world, world.state, defaultlocations) remaining_fill(world, excludedlocations, filleritempool) if excludedlocations: From 13edfa60be199e0b35f7c879a224907ed6a247da Mon Sep 17 00:00:00 2001 From: PoryGone <98504756+PoryGone@users.noreply.github.com> Date: Thu, 29 Sep 2022 14:16:59 -0400 Subject: [PATCH 06/18] Super Mario World: Implement New Game (#1045) --- Launcher.py | 2 +- Patch.py | 1 + README.md | 1 + SNIClient.py | 12 +- Utils.py | 5 + host.yaml | 9 + inno_setup.iss | 35 + worlds/smw/Aesthetics.py | 202 ++++ worlds/smw/Client.py | 455 +++++++++ worlds/smw/Items.py | 69 ++ worlds/smw/Levels.py | 579 +++++++++++ worlds/smw/Locations.py | 233 +++++ worlds/smw/Names/ItemName.py | 32 + worlds/smw/Names/LiteratureTrap.py | 52 + worlds/smw/Names/LocationName.py | 364 +++++++ worlds/smw/Names/TextBox.py | 140 +++ worlds/smw/Options.py | 236 +++++ worlds/smw/Regions.py | 1187 +++++++++++++++++++++++ worlds/smw/Rom.py | 846 ++++++++++++++++ worlds/smw/Rules.py | 20 + worlds/smw/__init__.py | 253 +++++ worlds/smw/docs/en_Super Mario World.md | 43 + worlds/smw/docs/setup_en.md | 149 +++ 23 files changed, 4923 insertions(+), 2 deletions(-) create mode 100644 worlds/smw/Aesthetics.py create mode 100644 worlds/smw/Client.py create mode 100644 worlds/smw/Items.py create mode 100644 worlds/smw/Levels.py create mode 100644 worlds/smw/Locations.py create mode 100644 worlds/smw/Names/ItemName.py create mode 100644 worlds/smw/Names/LiteratureTrap.py create mode 100644 worlds/smw/Names/LocationName.py create mode 100644 worlds/smw/Names/TextBox.py create mode 100644 worlds/smw/Options.py create mode 100644 worlds/smw/Regions.py create mode 100644 worlds/smw/Rom.py create mode 100644 worlds/smw/Rules.py create mode 100644 worlds/smw/__init__.py create mode 100644 worlds/smw/docs/en_Super Mario World.md create mode 100644 worlds/smw/docs/setup_en.md diff --git a/Launcher.py b/Launcher.py index 8a3d53f866..9f9aaa4fb2 100644 --- a/Launcher.py +++ b/Launcher.py @@ -132,7 +132,7 @@ components: Iterable[Component] = ( Component('Text Client', 'CommonClient', 'ArchipelagoTextClient'), # SNI Component('SNI Client', 'SNIClient', - file_identifier=SuffixIdentifier('.apz3', '.apm3', '.apsoe', '.aplttp', '.apsm', '.apsmz3', '.apdkc3')), + file_identifier=SuffixIdentifier('.apz3', '.apm3', '.apsoe', '.aplttp', '.apsm', '.apsmz3', '.apdkc3', '.apsmw')), Component('LttP Adjuster', 'LttPAdjuster'), # Factorio Component('Factorio Client', 'FactorioClient'), diff --git a/Patch.py b/Patch.py index 9b2c88a6b6..6ac75dc9dd 100644 --- a/Patch.py +++ b/Patch.py @@ -171,6 +171,7 @@ GAME_SM = "Super Metroid" GAME_SOE = "Secret of Evermore" GAME_SMZ3 = "SMZ3" GAME_DKC3 = "Donkey Kong Country 3" +GAME_SMW = "Super Mario World" supported_games = {"A Link to the Past", "Super Metroid", "Secret of Evermore", "SMZ3", "Donkey Kong Country 3"} preferred_endings = { diff --git a/README.md b/README.md index c8362dddd0..a82282037b 100644 --- a/README.md +++ b/README.md @@ -28,6 +28,7 @@ Currently, the following games are supported: * Starcraft 2: Wings of Liberty * Donkey Kong Country 3 * Dark Souls 3 +* Super Mario World For setup and instructions check out our [tutorials page](https://archipelago.gg/tutorial/). Downloads can be found at [Releases](https://github.com/ArchipelagoMW/Archipelago/releases), including compiled diff --git a/SNIClient.py b/SNIClient.py index 477cde86a2..9170c845e3 100644 --- a/SNIClient.py +++ b/SNIClient.py @@ -30,7 +30,7 @@ from worlds.sm.Rom import ROM_PLAYER_LIMIT as SM_ROM_PLAYER_LIMIT from worlds.smz3.Rom import ROM_PLAYER_LIMIT as SMZ3_ROM_PLAYER_LIMIT import Utils from CommonClient import CommonContext, server_loop, ClientCommandProcessor, gui_enabled, get_base_parser -from Patch import GAME_ALTTP, GAME_SM, GAME_SMZ3, GAME_DKC3 +from Patch import GAME_ALTTP, GAME_SM, GAME_SMZ3, GAME_DKC3, GAME_SMW snes_logger = logging.getLogger("SNES") @@ -236,6 +236,10 @@ async def deathlink_kill_player(ctx: Context): snes_buffered_write(ctx, WRAM_START + 0x0A50, bytes([255])) # deal 255 of damage at next opportunity if not ctx.death_link_allow_survive: snes_buffered_write(ctx, WRAM_START + 0x09D6, bytes([0, 0])) # set current reserve to 0 + elif ctx.game == GAME_SMW: + from worlds.smw.Client import deathlink_kill_player as smw_deathlink_kill_player + await smw_deathlink_kill_player(ctx) + await snes_flush_writes(ctx) await asyncio.sleep(1) @@ -1041,6 +1045,9 @@ async def game_watcher(ctx: Context): from worlds.dkc3.Client import dkc3_rom_init init_handled = await dkc3_rom_init(ctx) + if not init_handled: + from worlds.smw.Client import smw_rom_init + init_handled = await smw_rom_init(ctx) if not init_handled: game_name = await snes_read(ctx, SM_ROMNAME_START, 5) if game_name is None: @@ -1299,6 +1306,9 @@ async def game_watcher(ctx: Context): elif ctx.game == GAME_DKC3: from worlds.dkc3.Client import dkc3_game_watcher await dkc3_game_watcher(ctx) + elif ctx.game == GAME_SMW: + from worlds.smw.Client import smw_game_watcher + await smw_game_watcher(ctx) async def run_game(romfile): diff --git a/Utils.py b/Utils.py index 93627ffd89..c5fc00035a 100644 --- a/Utils.py +++ b/Utils.py @@ -288,6 +288,11 @@ def get_default_options() -> OptionsType: "sni": "SNI", "rom_start": True, }, + "smw_options": { + "rom_file": "Super Mario World (USA).sfc", + "sni": "SNI", + "rom_start": True, + }, } return options diff --git a/host.yaml b/host.yaml index 901e6cd727..b114135520 100644 --- a/host.yaml +++ b/host.yaml @@ -138,3 +138,12 @@ dkc3_options: # True for operating system default program # Alternatively, a path to a program to open the .sfc file with rom_start: true +smw_options: + # File name of the SMW US rom + rom_file: "Super Mario World (USA).sfc" + # Set this to your SNI folder location if you want the MultiClient to attempt an auto start, does nothing if not found + sni: "SNI" + # Set this to false to never autostart a rom (such as after patching) + # True for operating system default program + # Alternatively, a path to a program to open the .sfc file with + rom_start: true diff --git a/inno_setup.iss b/inno_setup.iss index cfdfec7ba8..9a2a40444e 100644 --- a/inno_setup.iss +++ b/inno_setup.iss @@ -55,6 +55,7 @@ Name: "core"; Description: "Core Files"; Types: full hosting playing Name: "generator"; Description: "Generator"; Types: full hosting Name: "generator/sm"; Description: "Super Metroid ROM Setup"; Types: full hosting; ExtraDiskSpaceRequired: 3145728; Flags: disablenouninstallwarning Name: "generator/dkc3"; Description: "Donkey Kong Country 3 ROM Setup"; Types: full hosting; ExtraDiskSpaceRequired: 3145728; Flags: disablenouninstallwarning +Name: "generator/smw"; Description: "Super Mario World ROM Setup"; Types: full hosting; ExtraDiskSpaceRequired: 3145728; Flags: disablenouninstallwarning Name: "generator/soe"; Description: "Secret of Evermore ROM Setup"; Types: full hosting; ExtraDiskSpaceRequired: 3145728; Flags: disablenouninstallwarning Name: "generator/lttp"; Description: "A Link to the Past ROM Setup and Enemizer"; Types: full hosting; ExtraDiskSpaceRequired: 5191680 Name: "generator/oot"; Description: "Ocarina of Time ROM Setup"; Types: full hosting; ExtraDiskSpaceRequired: 100663296; Flags: disablenouninstallwarning @@ -64,6 +65,7 @@ Name: "client/sni"; Description: "SNI Client"; Types: full playing Name: "client/sni/lttp"; Description: "SNI Client - A Link to the Past Patch Setup"; Types: full playing; Flags: disablenouninstallwarning Name: "client/sni/sm"; Description: "SNI Client - Super Metroid Patch Setup"; Types: full playing; Flags: disablenouninstallwarning Name: "client/sni/dkc3"; Description: "SNI Client - Donkey Kong Country 3 Patch Setup"; Types: full playing; Flags: disablenouninstallwarning +Name: "client/sni/smw"; Description: "SNI Client - Super Mario World Patch Setup"; Types: full playing; Flags: disablenouninstallwarning Name: "client/factorio"; Description: "Factorio"; Types: full playing Name: "client/minecraft"; Description: "Minecraft"; Types: full playing; ExtraDiskSpaceRequired: 226894278 Name: "client/oot"; Description: "Ocarina of Time"; Types: full playing @@ -79,6 +81,7 @@ NAME: "{app}"; Flags: setntfscompression; Permissions: everyone-modify users-mod Source: "{code:GetROMPath}"; DestDir: "{app}"; DestName: "Zelda no Densetsu - Kamigami no Triforce (Japan).sfc"; Flags: external; Components: client/sni/lttp or generator/lttp Source: "{code:GetSMROMPath}"; DestDir: "{app}"; DestName: "Super Metroid (JU).sfc"; Flags: external; Components: client/sni/sm or generator/sm Source: "{code:GetDKC3ROMPath}"; DestDir: "{app}"; DestName: "Donkey Kong Country 3 - Dixie Kong's Double Trouble! (USA) (En,Fr).sfc"; Flags: external; Components: client/sni/dkc3 or generator/dkc3 +Source: "{code:GetSMWROMPath}"; DestDir: "{app}"; DestName: "Super Mario World (USA).sfc"; Flags: external; Components: client/sni/smw or generator/smw Source: "{code:GetSoEROMPath}"; DestDir: "{app}"; DestName: "Secret of Evermore (USA).sfc"; Flags: external; Components: generator/soe Source: "{code:GetOoTROMPath}"; DestDir: "{app}"; DestName: "The Legend of Zelda - Ocarina of Time.z64"; Flags: external; Components: client/oot or generator/oot Source: "{#source_path}\*"; Excludes: "*.sfc, *.log, data\sprites\alttpr, SNI, EnemizerCLI, Archipelago*.exe"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs @@ -151,6 +154,11 @@ Root: HKCR; Subkey: "{#MyAppName}dkc3patch"; ValueData: "Arc Root: HKCR; Subkey: "{#MyAppName}dkc3patch\DefaultIcon"; ValueData: "{app}\ArchipelagoSNIClient.exe,0"; ValueType: string; ValueName: ""; Components: client/sni Root: HKCR; Subkey: "{#MyAppName}dkc3patch\shell\open\command"; ValueData: """{app}\ArchipelagoSNIClient.exe"" ""%1"""; ValueType: string; ValueName: ""; Components: client/sni +Root: HKCR; Subkey: ".apsmw"; ValueData: "{#MyAppName}smwpatch"; Flags: uninsdeletevalue; ValueType: string; ValueName: ""; Components: client/sni +Root: HKCR; Subkey: "{#MyAppName}smwpatch"; ValueData: "Archipelago Super Mario World Patch"; Flags: uninsdeletekey; ValueType: string; ValueName: ""; Components: client/sni +Root: HKCR; Subkey: "{#MyAppName}smwpatch\DefaultIcon"; ValueData: "{app}\ArchipelagoSNIClient.exe,0"; ValueType: string; ValueName: ""; Components: client/sni +Root: HKCR; Subkey: "{#MyAppName}smwpatch\shell\open\command"; ValueData: """{app}\ArchipelagoSNIClient.exe"" ""%1"""; ValueType: string; ValueName: ""; Components: client/sni + Root: HKCR; Subkey: ".apsmz3"; ValueData: "{#MyAppName}smz3patch"; Flags: uninsdeletevalue; ValueType: string; ValueName: ""; Components: client/sni Root: HKCR; Subkey: "{#MyAppName}smz3patch"; ValueData: "Archipelago SMZ3 Patch"; Flags: uninsdeletekey; ValueType: string; ValueName: ""; Components: client/sni Root: HKCR; Subkey: "{#MyAppName}smz3patch\DefaultIcon"; ValueData: "{app}\ArchipelagoSNIClient.exe,0"; ValueType: string; ValueName: ""; Components: client/sni @@ -217,6 +225,9 @@ var SMRomFilePage: TInputFileWizardPage; var dkc3rom: string; var DKC3RomFilePage: TInputFileWizardPage; +var smwrom: string; +var SMWRomFilePage: TInputFileWizardPage; + var soerom: string; var SoERomFilePage: TInputFileWizardPage; @@ -308,6 +319,8 @@ begin Result := not (SMROMFilePage.Values[0] = '') else if (assigned(DKC3ROMFilePage)) and (CurPageID = DKC3ROMFilePage.ID) then Result := not (DKC3ROMFilePage.Values[0] = '') + else if (assigned(SMWROMFilePage)) and (CurPageID = SMWROMFilePage.ID) then + Result := not (SMWROMFilePage.Values[0] = '') else if (assigned(SoEROMFilePage)) and (CurPageID = SoEROMFilePage.ID) then Result := not (SoEROMFilePage.Values[0] = '') else if (assigned(OoTROMFilePage)) and (CurPageID = OoTROMFilePage.ID) then @@ -364,6 +377,22 @@ begin Result := ''; end; +function GetSMWROMPath(Param: string): string; +begin + if Length(smwrom) > 0 then + Result := smwrom + else if Assigned(SMWRomFilePage) then + begin + R := CompareStr(GetSNESMD5OfFile(SMWROMFilePage.Values[0]), 'cdd3c8c37322978ca8669b34bc89c804') + if R <> 0 then + MsgBox('Super Mario World ROM validation failed. Very likely wrong file.', mbInformation, MB_OK); + + Result := SMWROMFilePage.Values[0] + end + else + Result := ''; + end; + function GetSoEROMPath(Param: string): string; begin if Length(soerom) > 0 then @@ -412,6 +441,10 @@ begin if Length(dkc3rom) = 0 then DKC3RomFilePage:= AddRomPage('Donkey Kong Country 3 - Dixie Kong''s Double Trouble! (USA) (En,Fr).sfc'); + smwrom := CheckRom('Super Mario World (USA).sfc', 'cdd3c8c37322978ca8669b34bc89c804'); + if Length(smwrom) = 0 then + SMWRomFilePage:= AddRomPage('Super Mario World (USA).sfc'); + soerom := CheckRom('Secret of Evermore (USA).sfc', '6e9c94511d04fac6e0a1e582c170be3a'); if Length(soerom) = 0 then SoEROMFilePage:= AddRomPage('Secret of Evermore (USA).sfc'); @@ -427,6 +460,8 @@ begin Result := not (WizardIsComponentSelected('client/sni/sm') or WizardIsComponentSelected('generator/sm')); if (assigned(DKC3ROMFilePage)) and (PageID = DKC3ROMFilePage.ID) then Result := not (WizardIsComponentSelected('client/sni/dkc3') or WizardIsComponentSelected('generator/dkc3')); + if (assigned(SMWROMFilePage)) and (PageID = SMWROMFilePage.ID) then + Result := not (WizardIsComponentSelected('client/sni/smw') or WizardIsComponentSelected('generator/smw')); if (assigned(SoEROMFilePage)) and (PageID = SoEROMFilePage.ID) then Result := not (WizardIsComponentSelected('generator/soe')); if (assigned(OoTROMFilePage)) and (PageID = OoTROMFilePage.ID) then diff --git a/worlds/smw/Aesthetics.py b/worlds/smw/Aesthetics.py new file mode 100644 index 0000000000..624440c55f --- /dev/null +++ b/worlds/smw/Aesthetics.py @@ -0,0 +1,202 @@ + +mario_palettes = [ + [0x5F, 0x63, 0x1D, 0x58, 0x0A, 0x00, 0x1F, 0x39, 0xC4, 0x44, 0x08, 0x4E, 0x70, 0x67, 0xB6, 0x30, 0xDF, 0x35, 0xFF, 0x03], # Mario + [0x3F, 0x4F, 0x1D, 0x58, 0x40, 0x11, 0xE0, 0x3F, 0x07, 0x3C, 0xAE, 0x7C, 0xB3, 0x7D, 0x00, 0x2F, 0x5F, 0x16, 0xFF, 0x03], # Luigi + [0x5F, 0x63, 0x1D, 0x58, 0x0A, 0x00, 0x1F, 0x03, 0xC4, 0x44, 0x08, 0x4E, 0x70, 0x67, 0x16, 0x02, 0xDF, 0x35, 0xFF, 0x03], # Wario + [0x5F, 0x63, 0x1D, 0x58, 0x0A, 0x00, 0x12, 0x7C, 0xC4, 0x44, 0x08, 0x4E, 0x70, 0x67, 0x0D, 0x58, 0xDF, 0x35, 0xFF, 0x03], # Waluigi + [0x5F, 0x63, 0x1D, 0x58, 0x0A, 0x00, 0x00, 0x7C, 0xC4, 0x44, 0x08, 0x4E, 0x70, 0x67, 0x00, 0x58, 0xDF, 0x35, 0xFF, 0x03], # Geno + [0x5F, 0x63, 0x1D, 0x58, 0x0A, 0x00, 0x1F, 0x7C, 0xC4, 0x44, 0x08, 0x4E, 0x70, 0x67, 0x16, 0x58, 0xDF, 0x35, 0xFF, 0x03], # Princess + [0x5F, 0x63, 0x1D, 0x58, 0x0A, 0x00, 0xE0, 0x00, 0xC4, 0x44, 0x08, 0x4E, 0x70, 0x67, 0x80, 0x00, 0xDF, 0x35, 0xFF, 0x03], # Dark + [0x5F, 0x63, 0x1D, 0x58, 0x0A, 0x00, 0xFF, 0x01, 0xC4, 0x44, 0x08, 0x4E, 0x70, 0x67, 0x5F, 0x01, 0xDF, 0x35, 0xFF, 0x03], # Sponge +] + +fire_mario_palettes = [ + [0x5F, 0x63, 0x1D, 0x58, 0x29, 0x25, 0xFF, 0x7F, 0x08, 0x00, 0x17, 0x00, 0x1F, 0x00, 0x7B, 0x57, 0xDF, 0x0D, 0xFF, 0x03], # Mario + [0x1F, 0x3B, 0x1D, 0x58, 0x29, 0x25, 0xFF, 0x7F, 0x40, 0x11, 0xE0, 0x01, 0xE0, 0x02, 0x7B, 0x57, 0xDF, 0x0D, 0xFF, 0x03], # Luigi + [0x5F, 0x63, 0x1D, 0x58, 0x29, 0x25, 0xFF, 0x7F, 0x08, 0x00, 0x16, 0x02, 0x1F, 0x03, 0x7B, 0x57, 0xDF, 0x0D, 0xFF, 0x03], # Wario + [0x5F, 0x63, 0x1D, 0x58, 0x29, 0x25, 0xFF, 0x7F, 0x08, 0x00, 0x0D, 0x58, 0x12, 0x7C, 0x7B, 0x57, 0xDF, 0x0D, 0xFF, 0x03], # Waluigi + [0x5F, 0x63, 0x1D, 0x58, 0x29, 0x25, 0xFF, 0x7F, 0x08, 0x00, 0x00, 0x58, 0x00, 0x7C, 0x7B, 0x57, 0xDF, 0x0D, 0xFF, 0x03], # Geno + [0x5F, 0x63, 0x1D, 0x58, 0x29, 0x25, 0xFF, 0x7F, 0x08, 0x00, 0x16, 0x58, 0x1F, 0x7C, 0x7B, 0x57, 0xDF, 0x0D, 0xFF, 0x03], # Princess + [0x5F, 0x63, 0x1D, 0x58, 0x29, 0x25, 0xFF, 0x7F, 0x08, 0x00, 0x80, 0x00, 0xE0, 0x00, 0x7B, 0x57, 0xDF, 0x0D, 0xFF, 0x03], # Dark + [0x5F, 0x63, 0x1D, 0x58, 0x29, 0x25, 0xFF, 0x7F, 0x08, 0x00, 0x5F, 0x01, 0xFF, 0x01, 0x7B, 0x57, 0xDF, 0x0D, 0xFF, 0x03], # Sponge +] + +ow_mario_palettes = [ + [0x16, 0x00, 0x1F, 0x00], # Mario + [0x80, 0x02, 0xE0, 0x03], # Luigi + [0x16, 0x02, 0x1F, 0x03], # Wario + [0x0D, 0x58, 0x12, 0x7C], # Waluigi + [0x00, 0x58, 0x00, 0x7C], # Geno + [0x16, 0x58, 0x1F, 0x7C], # Princess + [0x80, 0x00, 0xE0, 0x00], # Dark + [0x5F, 0x01, 0xFF, 0x01], # Sponge +] + +level_music_address_data = [ + 0x284DB, + 0x284DC, + 0x284DD, + 0x284DE, + 0x284DF, + 0x284E0, + 0x284E1, + 0x284E2, +] + +level_music_value_data = [ + 0x02, + 0x06, + 0x01, + 0x08, + 0x07, + 0x03, + 0x05, + 0x12, +] + +ow_music_address_data = [ + [0x25BC8, 0x20D8A], + [0x25BC9, 0x20D8B], + [0x25BCA, 0x20D8C], + [0x25BCB, 0x20D8D], + [0x25BCC, 0x20D8E], + [0x25BCD, 0x20D8F], + [0x25BCE, 0x20D90], + [0x16C7] +] + +ow_music_value_data = [ + 0x02, + 0x03, + 0x04, + 0x06, + 0x07, + 0x09, + 0x05, + 0x01, +] + +valid_foreground_palettes = { + 0x00: [0x00, 0x01, 0x03, 0x04, 0x05, 0x07], # Normal 1 + 0x01: [0x03, 0x04, 0x05, 0x07], # Castle 1 + 0x02: [0x01, 0x02, 0x03, 0x04, 0x05, 0x07], # Rope 1 + 0x03: [0x02, 0x03, 0x04, 0x05, 0x07], # Underground 1 + 0x04: [0x01, 0x02, 0x03, 0x04, 0x05, 0x07], # Switch Palace 1 + 0x05: [0x04, 0x05], # Ghost House 1 + 0x06: [0x01, 0x02, 0x03, 0x04, 0x05, 0x07], # Rope 2 + 0x07: [0x00, 0x01, 0x03, 0x04, 0x05, 0x07], # Normal 2 + 0x08: [0x01, 0x02, 0x03, 0x04, 0x05, 0x07], # Rope 3 + 0x09: [0x01, 0x02, 0x03, 0x04, 0x05, 0x07], # Underground 2 + 0x0A: [0x01, 0x02, 0x03, 0x04, 0x05, 0x07], # Switch Palace 2 + 0x0B: [0x03, 0x04, 0x05, 0x07], # Castle 2 + #0x0C: [], # Cloud/Forest + 0x0D: [0x04, 0x05], # Ghost House 2 + 0x0E: [0x02, 0x03, 0x04, 0x05, 0x07], # Underground 3 +} + +valid_background_palettes = { + 0x06861B: [0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07], # Ghost House Exit + 0xFFD900: [0x01], # P. Hills + 0xFFDAB9: [0x04], # Water + 0xFFDC71: [0x00, 0x01, 0x02, 0x03, 0x05, 0x06, 0x07], # Hills & Clouds + 0xFFDD44: [0x00, 0x01, 0x02, 0x03, 0x05, 0x06, 0x07], # Clouds + 0xFFDE54: [0x00, 0x01, 0x02, 0x03, 0x05, 0x06, 0x07], # Small Hills + 0xFFDF59: [0x00, 0x01, 0x02, 0x03, 0x05, 0x06, 0x07], # Mountains & Clouds + 0xFFE103: [0x00, 0x01, 0x02, 0x03, 0x05, 0x06, 0x07], # Castle Pillars + 0xFFE472: [0x00, 0x01, 0x02, 0x03, 0x05, 0x06, 0x07], # Big Hills + 0xFFE674: [0x00, 0x01, 0x02, 0x03, 0x05, 0x06, 0x07], # Bonus + 0xFFE684: [0x01, 0x03, 0x05, 0x06], # Stars + 0xFFE7C0: [0x00, 0x01, 0x02, 0x03, 0x05, 0x06, 0x07], # Mountains + 0xFFE8EE: [0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07], # Empty/Layer 2 + 0xFFE8FE: [0x01, 0x06], # Cave + 0xFFEC82: [0x00, 0x02, 0x03, 0x05, 0x06, 0x07], # Bushes + 0xFFEF80: [0x01, 0x03, 0x05, 0x06], # Ghost House + 0xFFF175: [0x00, 0x01, 0x02, 0x03, 0x05, 0x06], # Ghost Ship + 0xFFF45A: [0x01, 0x03, 0x06], # Castle +} + +valid_background_colors = { + 0x06861B: [0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07], # Ghost House Exit + 0xFFD900: [0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07], # P. Hills + 0xFFDAB9: [0x02, 0x03, 0x05], # Water + 0xFFDC71: [0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07], # Hills & Clouds + 0xFFDD44: [0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07], # Clouds + 0xFFDE54: [0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07], # Small Hills + 0xFFDF59: [0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07], # Mountains & Clouds + 0xFFE103: [0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07], # Castle Pillars + 0xFFE472: [0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07], # Big Hills + 0xFFE674: [0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07], # Bonus + 0xFFE684: [0x02, 0x03, 0x04, 0x05], # Stars + 0xFFE7C0: [0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07], # Mountains + 0xFFE8EE: [0x03, 0x05], # Empty/Layer 2 + 0xFFE8FE: [0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07], # Cave + 0xFFEC82: [0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07], # Bushes + 0xFFEF80: [0x03, 0x04], # Ghost House + 0xFFF175: [0x02, 0x03, 0x04, 0x05], # Ghost Ship + 0xFFF45A: [0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07], # Castle +} + +def generate_shuffled_level_music(world, player): + shuffled_level_music = level_music_value_data.copy() + + if world.music_shuffle[player] == "consistent": + world.random.shuffle(shuffled_level_music) + elif world.music_shuffle[player] == "singularity": + single_song = world.random.choice(shuffled_level_music) + shuffled_level_music = [single_song for i in range(len(shuffled_level_music))] + + return shuffled_level_music + +def generate_shuffled_ow_music(world, player): + shuffled_ow_music = ow_music_value_data.copy() + + if world.music_shuffle[player] == "consistent" or world.music_shuffle[player] == "full": + world.random.shuffle(shuffled_ow_music) + elif world.music_shuffle[player] == "singularity": + single_song = world.random.choice(shuffled_ow_music) + shuffled_ow_music = [single_song for i in range(len(shuffled_ow_music))] + + return shuffled_ow_music + +def generate_shuffled_header_data(rom, world, player): + if world.music_shuffle[player] != "full" and not world.foreground_palette_shuffle[player] and not world.background_palette_shuffle[player]: + return + + for level_id in range(0, 0x200): + layer1_ptr_list = list(rom.read_bytes(0x2E000 + level_id * 3, 3)) + layer1_ptr = (layer1_ptr_list[2] << 16 | layer1_ptr_list[1] << 8 | layer1_ptr_list[0]) + + if layer1_ptr == 0x68000: + # Unused Levels + continue + + if layer1_ptr >= 0x70000: + layer1_ptr -= 0x8000 + + layer1_ptr -= 0x38000 + + level_header = list(rom.read_bytes(layer1_ptr, 5)) + + tileset = level_header[4] & 0x0F + + if world.music_shuffle[player] == "full": + level_header[2] &= 0x8F + level_header[2] |= (world.random.randint(0, 7) << 5) + + if (world.foreground_palette_shuffle[player] and tileset in valid_foreground_palettes): + level_header[3] &= 0xF8 + level_header[3] |= world.random.choice(valid_foreground_palettes[tileset]) + + if world.background_palette_shuffle[player]: + layer2_ptr_list = list(rom.read_bytes(0x2E600 + level_id * 3, 3)) + layer2_ptr = (layer2_ptr_list[2] << 16 | layer2_ptr_list[1] << 8 | layer2_ptr_list[0]) + + if layer2_ptr in valid_background_palettes: + level_header[0] &= 0x1F + level_header[0] |= (world.random.choice(valid_background_palettes[layer2_ptr]) << 5) + + if layer2_ptr in valid_background_colors: + level_header[1] &= 0x1F + level_header[1] |= (world.random.choice(valid_background_colors[layer2_ptr]) << 5) + + rom.write_bytes(layer1_ptr, bytes(level_header)) diff --git a/worlds/smw/Client.py b/worlds/smw/Client.py new file mode 100644 index 0000000000..6ddd4e1073 --- /dev/null +++ b/worlds/smw/Client.py @@ -0,0 +1,455 @@ +import logging +import asyncio +import time + +from NetUtils import ClientStatus, color +from worlds import AutoWorldRegister +from SNIClient import Context, snes_buffered_write, snes_flush_writes, snes_read +from .Names.TextBox import generate_received_text +from Patch import GAME_SMW + +snes_logger = logging.getLogger("SNES") + +ROM_START = 0x000000 +WRAM_START = 0xF50000 +WRAM_SIZE = 0x20000 +SRAM_START = 0xE00000 + +SAVEDATA_START = WRAM_START + 0xF000 +SAVEDATA_SIZE = 0x500 + +SMW_ROMHASH_START = 0x7FC0 +ROMHASH_SIZE = 0x15 + +SMW_PROGRESS_DATA = WRAM_START + 0x1F02 +SMW_DRAGON_COINS_DATA = WRAM_START + 0x1F2F +SMW_PATH_DATA = WRAM_START + 0x1EA2 +SMW_EVENT_ROM_DATA = ROM_START + 0x2D608 +SMW_ACTIVE_LEVEL_DATA = ROM_START + 0x37F70 + +SMW_GOAL_DATA = ROM_START + 0x01BFA0 +SMW_REQUIRED_BOSSES_DATA = ROM_START + 0x01BFA1 +SMW_REQUIRED_EGGS_DATA = ROM_START + 0x01BFA2 +SMW_SEND_MSG_DATA = ROM_START + 0x01BFA3 +SMW_RECEIVE_MSG_DATA = ROM_START + 0x01BFA4 +SMW_DEATH_LINK_ACTIVE_ADDR = ROM_START + 0x01BFA5 +SMW_DRAGON_COINS_ACTIVE_ADDR = ROM_START + 0x01BFA6 +SMW_SWAMP_DONUT_GH_ADDR = ROM_START + 0x01BFA7 + +SMW_GAME_STATE_ADDR = WRAM_START + 0x100 +SMW_MARIO_STATE_ADDR = WRAM_START + 0x71 +SMW_BOSS_STATE_ADDR = WRAM_START + 0xD9B +SMW_ACTIVE_BOSS_ADDR = WRAM_START + 0x13FC +SMW_CURRENT_LEVEL_ADDR = WRAM_START + 0x13BF +SMW_MESSAGE_BOX_ADDR = WRAM_START + 0x1426 +SMW_BONUS_STAR_ADDR = WRAM_START + 0xF48 +SMW_EGG_COUNT_ADDR = WRAM_START + 0x1F24 +SMW_BOSS_COUNT_ADDR = WRAM_START + 0x1F26 +SMW_NUM_EVENTS_ADDR = WRAM_START + 0x1F2E +SMW_SFX_ADDR = WRAM_START + 0x1DFC +SMW_PAUSE_ADDR = WRAM_START + 0x13D4 +SMW_MESSAGE_QUEUE_ADDR = WRAM_START + 0xC391 + +SMW_RECV_PROGRESS_ADDR = WRAM_START + 0x1F2B + +SMW_GOAL_LEVELS = [0x28, 0x31, 0x32] +SMW_INVALID_MARIO_STATES = [0x05, 0x06, 0x0A, 0x0C, 0x0D] +SMW_BAD_TEXT_BOX_LEVELS = [0x26, 0x02, 0x4B] +SMW_BOSS_STATES = [0x80, 0xC0, 0xC1] +SMW_UNCOLLECTABLE_LEVELS = [0x25, 0x07, 0x0B, 0x40, 0x0E, 0x1F, 0x20, 0x1B, 0x1A, 0x35, 0x34, 0x31, 0x32] + +async def deathlink_kill_player(ctx: Context): + if ctx.game == GAME_SMW: + game_state = await snes_read(ctx, SMW_GAME_STATE_ADDR, 0x1) + if game_state[0] != 0x14: + return + + mario_state = await snes_read(ctx, SMW_MARIO_STATE_ADDR, 0x1) + if mario_state[0] != 0x00: + return + + message_box = await snes_read(ctx, SMW_MESSAGE_BOX_ADDR, 0x1) + if message_box[0] != 0x00: + return + + pause_state = await snes_read(ctx, SMW_PAUSE_ADDR, 0x1) + if pause_state[0] != 0x00: + return + + snes_buffered_write(ctx, WRAM_START + 0x9D, bytes([0x30])) # Freeze Gameplay + snes_buffered_write(ctx, WRAM_START + 0x1DFB, bytes([0x09])) # Death Music + snes_buffered_write(ctx, WRAM_START + 0x0DDA, bytes([0xFF])) # Flush Music Buffer + snes_buffered_write(ctx, WRAM_START + 0x1407, bytes([0x00])) # Flush Cape Fly Phase + snes_buffered_write(ctx, WRAM_START + 0x140D, bytes([0x00])) # Flush Spin Jump Flag + snes_buffered_write(ctx, WRAM_START + 0x188A, bytes([0x00])) # Flush Empty Byte because the game does it + snes_buffered_write(ctx, WRAM_START + 0x7D, bytes([0x90])) # Mario Y Speed + snes_buffered_write(ctx, WRAM_START + 0x1496, bytes([0x30])) # Death Timer + snes_buffered_write(ctx, SMW_MARIO_STATE_ADDR, bytes([0x09])) # Mario State -> Dead + + await snes_flush_writes(ctx) + + from SNIClient import DeathState + ctx.death_state = DeathState.dead + ctx.last_death_link = time.time() + + return + + +async def smw_rom_init(ctx: Context): + if not ctx.rom: + ctx.finished_game = False + ctx.death_link_allow_survive = False + game_hash = await snes_read(ctx, SMW_ROMHASH_START, ROMHASH_SIZE) + if game_hash is None or game_hash == bytes([0] * ROMHASH_SIZE) or game_hash[:3] != b"SMW": + return False + else: + ctx.game = GAME_SMW + ctx.items_handling = 0b111 # remote items + + ctx.rom = game_hash + + receive_option = await snes_read(ctx, SMW_RECEIVE_MSG_DATA, 0x1) + send_option = await snes_read(ctx, SMW_SEND_MSG_DATA, 0x1) + + ctx.receive_option = receive_option[0] + ctx.send_option = send_option[0] + + ctx.message_queue = [] + + ctx.allow_collect = True + + death_link = await snes_read(ctx, SMW_DEATH_LINK_ACTIVE_ADDR, 1) + if death_link: + await ctx.update_death_link(bool(death_link[0] & 0b1)) + return True + + +def add_message_to_queue(ctx: Context, new_message): + + if not hasattr(ctx, "message_queue"): + ctx.message_queue = [] + + ctx.message_queue.append(new_message) + + return + + +async def handle_message_queue(ctx: Context): + + game_state = await snes_read(ctx, SMW_GAME_STATE_ADDR, 0x1) + if game_state[0] != 0x14: + return + + mario_state = await snes_read(ctx, SMW_MARIO_STATE_ADDR, 0x1) + if mario_state[0] != 0x00: + return + + message_box = await snes_read(ctx, SMW_MESSAGE_BOX_ADDR, 0x1) + if message_box[0] != 0x00: + return + + pause_state = await snes_read(ctx, SMW_PAUSE_ADDR, 0x1) + if pause_state[0] != 0x00: + return + + current_level = await snes_read(ctx, SMW_CURRENT_LEVEL_ADDR, 0x1) + if current_level[0] in SMW_BAD_TEXT_BOX_LEVELS: + return + + boss_state = await snes_read(ctx, SMW_BOSS_STATE_ADDR, 0x1) + if boss_state[0] in SMW_BOSS_STATES: + return + + active_boss = await snes_read(ctx, SMW_ACTIVE_BOSS_ADDR, 0x1) + if active_boss[0] != 0x00: + return + + if not hasattr(ctx, "message_queue") or len(ctx.message_queue) == 0: + return + + next_message = ctx.message_queue.pop(0) + + snes_buffered_write(ctx, SMW_MESSAGE_QUEUE_ADDR, bytes(next_message)) + snes_buffered_write(ctx, SMW_MESSAGE_BOX_ADDR, bytes([0x03])) + snes_buffered_write(ctx, SMW_SFX_ADDR, bytes([0x22])) + + await snes_flush_writes(ctx) + + return + + +async def smw_game_watcher(ctx: Context): + if ctx.game == GAME_SMW: + # SMW_TODO: Handle Deathlink + game_state = await snes_read(ctx, SMW_GAME_STATE_ADDR, 0x1) + mario_state = await snes_read(ctx, SMW_MARIO_STATE_ADDR, 0x1) + if game_state is None: + # We're not properly connected + return + elif game_state[0] >= 0x18: + if not ctx.finished_game: + current_level = await snes_read(ctx, SMW_CURRENT_LEVEL_ADDR, 0x1) + + if current_level[0] in SMW_GOAL_LEVELS: + await ctx.send_msgs([{"cmd": "StatusUpdate", "status": ClientStatus.CLIENT_GOAL}]) + ctx.finished_game = True + return + elif game_state[0] < 0x0B: + # We haven't loaded a save file + ctx.message_queue = [] + return + elif mario_state[0] in SMW_INVALID_MARIO_STATES: + # Mario can't come to the phone right now + return + + if "DeathLink" in ctx.tags and game_state[0] == 0x14 and ctx.last_death_link + 1 < time.time(): + currently_dead = mario_state[0] == 0x09 + await ctx.handle_deathlink_state(currently_dead) + + # Check for Egg Hunt ending + goal = await snes_read(ctx, SMW_GOAL_DATA, 0x1) + if game_state[0] == 0x14 and goal[0] == 1: + current_level = await snes_read(ctx, SMW_CURRENT_LEVEL_ADDR, 0x1) + message_box = await snes_read(ctx, SMW_MESSAGE_BOX_ADDR, 0x1) + egg_count = await snes_read(ctx, SMW_EGG_COUNT_ADDR, 0x1) + required_egg_count = await snes_read(ctx, SMW_REQUIRED_EGGS_DATA, 0x1) + + if current_level[0] == 0x28 and message_box[0] == 0x01 and egg_count[0] >= required_egg_count[0]: + snes_buffered_write(ctx, WRAM_START + 0x13C6, bytes([0x08])) + snes_buffered_write(ctx, WRAM_START + 0x13CE, bytes([0x01])) + snes_buffered_write(ctx, WRAM_START + 0x1DE9, bytes([0x01])) + snes_buffered_write(ctx, SMW_GAME_STATE_ADDR, bytes([0x18])) + + await snes_flush_writes(ctx) + return + + egg_count = await snes_read(ctx, SMW_EGG_COUNT_ADDR, 0x1) + boss_count = await snes_read(ctx, SMW_BOSS_COUNT_ADDR, 0x1) + display_count = await snes_read(ctx, SMW_BONUS_STAR_ADDR, 0x1) + + if goal[0] == 0 and boss_count[0] > display_count[0]: + snes_buffered_write(ctx, SMW_BONUS_STAR_ADDR, bytes([boss_count[0]])) + await snes_flush_writes(ctx) + elif goal[0] == 1 and egg_count[0] > display_count[0]: + snes_buffered_write(ctx, SMW_BONUS_STAR_ADDR, bytes([egg_count[0]])) + await snes_flush_writes(ctx) + + await handle_message_queue(ctx) + + new_checks = [] + event_data = await snes_read(ctx, SMW_EVENT_ROM_DATA, 0x60) + progress_data = bytearray(await snes_read(ctx, SMW_PROGRESS_DATA, 0x0F)) + dragon_coins_data = bytearray(await snes_read(ctx, SMW_DRAGON_COINS_DATA, 0x0C)) + dragon_coins_active = await snes_read(ctx, SMW_DRAGON_COINS_ACTIVE_ADDR, 0x1) + from worlds.smw.Rom import item_rom_data, ability_rom_data + from worlds.smw.Levels import location_id_to_level_id, level_info_dict + for loc_name, level_data in location_id_to_level_id.items(): + loc_id = AutoWorldRegister.world_types[ctx.game].location_name_to_id[loc_name] + if loc_id not in ctx.locations_checked: + + event_id = event_data[level_data[0]] + + if level_data[1] == 2: + # Dragon Coins Check + if not dragon_coins_active or dragon_coins_active[0] == 0: + continue + + progress_byte = (level_data[0] // 8) + progress_bit = 7 - (level_data[0] % 8) + + data = dragon_coins_data[progress_byte] + masked_data = data & (1 << progress_bit) + bit_set = (masked_data != 0) + + if bit_set: + # SMW_TODO: Handle non-included checks + new_checks.append(loc_id) + else: + event_id_value = event_id + level_data[1] + + progress_byte = (event_id_value // 8) + progress_bit = 7 - (event_id_value % 8) + + data = progress_data[progress_byte] + masked_data = data & (1 << progress_bit) + bit_set = (masked_data != 0) + + if bit_set: + # SMW_TODO: Handle non-included checks + new_checks.append(loc_id) + + verify_game_state = await snes_read(ctx, SMW_GAME_STATE_ADDR, 0x1) + if verify_game_state is None or verify_game_state[0] < 0x0B or verify_game_state[0] > 0x29: + # We have somehow exited the save file (or worse) + print("Exit Save File") + return + + rom = await snes_read(ctx, SMW_ROMHASH_START, ROMHASH_SIZE) + if rom != ctx.rom: + ctx.rom = None + print("Exit ROM") + # We have somehow loaded a different ROM + return + + for new_check_id in new_checks: + ctx.locations_checked.add(new_check_id) + location = ctx.location_names[new_check_id] + snes_logger.info( + f'New Check: {location} ({len(ctx.locations_checked)}/{len(ctx.missing_locations) + len(ctx.checked_locations)})') + await ctx.send_msgs([{"cmd": 'LocationChecks', "locations": [new_check_id]}]) + + if game_state[0] != 0x14: + # Don't receive items or collect locations outside of in-level mode + return + + recv_count = await snes_read(ctx, SMW_RECV_PROGRESS_ADDR, 1) + recv_index = recv_count[0] + + if recv_index < len(ctx.items_received): + item = ctx.items_received[recv_index] + recv_index += 1 + logging.info('Received %s from %s (%s) (%d/%d in list)' % ( + color(ctx.item_names[item.item], 'red', 'bold'), + color(ctx.player_names[item.player], 'yellow'), + ctx.location_names[item.location], recv_index, len(ctx.items_received))) + + if ctx.receive_option == 1 or (ctx.receive_option == 2 and ((item.flags & 1) != 0)): + if item.item != 0xBC0012: + # Don't send messages for Boss Tokens + item_name = ctx.item_names[item.item] + player_name = ctx.player_names[item.player] + + receive_message = generate_received_text(item_name, player_name) + add_message_to_queue(ctx, receive_message) + + snes_buffered_write(ctx, SMW_RECV_PROGRESS_ADDR, bytes([recv_index])) + if item.item in item_rom_data: + item_count = await snes_read(ctx, WRAM_START + item_rom_data[item.item][0], 0x1) + increment = item_rom_data[item.item][1] + + new_item_count = item_count[0] + if increment > 1: + new_item_count = increment + else: + new_item_count += increment + + if verify_game_state[0] == 0x14 and len(item_rom_data[item.item]) > 2: + snes_buffered_write(ctx, SMW_SFX_ADDR, bytes([item_rom_data[item.item][2]])) + + snes_buffered_write(ctx, WRAM_START + item_rom_data[item.item][0], bytes([new_item_count])) + elif item.item in ability_rom_data: + # Handle Upgrades + for rom_data in ability_rom_data[item.item]: + data = await snes_read(ctx, WRAM_START + rom_data[0], 1) + masked_data = data[0] | (1 << rom_data[1]) + snes_buffered_write(ctx, WRAM_START + rom_data[0], bytes([masked_data])) + snes_buffered_write(ctx, SMW_SFX_ADDR, bytes([0x3E])) # SMW_TODO: Custom sounds for each + elif item.item == 0xBC000A: + # Handle Progressive Powerup + data = await snes_read(ctx, WRAM_START + 0x1F2D, 1) + mushroom_data = data[0] & (1 << 0) + fire_flower_data = data[0] & (1 << 1) + cape_data = data[0] & (1 << 2) + if mushroom_data == 0: + masked_data = data[0] | (1 << 0) + snes_buffered_write(ctx, WRAM_START + 0x1F2D, bytes([masked_data])) + snes_buffered_write(ctx, SMW_SFX_ADDR, bytes([0x3E])) + elif fire_flower_data == 0: + masked_data = data[0] | (1 << 1) + snes_buffered_write(ctx, WRAM_START + 0x1F2D, bytes([masked_data])) + snes_buffered_write(ctx, SMW_SFX_ADDR, bytes([0x3E])) + elif cape_data == 0: + masked_data = data[0] | (1 << 2) + snes_buffered_write(ctx, WRAM_START + 0x1F2D, bytes([masked_data])) + snes_buffered_write(ctx, SMW_SFX_ADDR, bytes([0x41])) + else: + # Extra Powerup? + pass + elif item.item == 0xBC0015: + # Handle Literature Trap + from .Names.LiteratureTrap import lit_trap_text_list + import random + rand_trap = random.choice(lit_trap_text_list) + + for message in rand_trap: + add_message_to_queue(ctx, message) + + await snes_flush_writes(ctx) + + # Handle Collected Locations + new_events = 0 + path_data = bytearray(await snes_read(ctx, SMW_PATH_DATA, 0x60)) + donut_gh_swapped = await snes_read(ctx, SMW_SWAMP_DONUT_GH_ADDR, 0x1) + new_dragon_coin = False + for loc_id in ctx.checked_locations: + if loc_id not in ctx.locations_checked: + ctx.locations_checked.add(loc_id) + loc_name = ctx.location_names[loc_id] + + if loc_name not in location_id_to_level_id: + continue + + level_data = location_id_to_level_id[loc_name] + + if level_data[1] == 2: + # Dragon Coins Check + + progress_byte = (level_data[0] // 8) + progress_bit = 7 - (level_data[0] % 8) + + data = dragon_coins_data[progress_byte] + new_data = data | (1 << progress_bit) + dragon_coins_data[progress_byte] = new_data + + new_dragon_coin = True + else: + if level_data[0] in SMW_UNCOLLECTABLE_LEVELS: + continue + + event_id = event_data[level_data[0]] + event_id_value = event_id + level_data[1] + + progress_byte = (event_id_value // 8) + progress_bit = 7 - (event_id_value % 8) + + data = progress_data[progress_byte] + masked_data = data & (1 << progress_bit) + bit_set = (masked_data != 0) + + if bit_set: + continue + + new_events += 1 + new_data = data | (1 << progress_bit) + progress_data[progress_byte] = new_data + + tile_id = await snes_read(ctx, SMW_ACTIVE_LEVEL_DATA + level_data[0], 0x1) + + level_info = level_info_dict[tile_id[0]] + + path = level_info.exit1Path if level_data[1] == 0 else level_info.exit2Path + + if donut_gh_swapped[0] != 0 and tile_id[0] == 0x04: + # Handle Swapped Donut GH Exits + path = level_info.exit2Path if level_data[1] == 0 else level_info.exit1Path + + if not path: + continue + + this_end_path = path_data[tile_id[0]] + new_data = this_end_path | path.thisEndDirection + path_data[tile_id[0]] = new_data + + other_end_path = path_data[path.otherLevelID] + new_data = other_end_path | path.otherEndDirection + path_data[path.otherLevelID] = new_data + + if new_dragon_coin: + snes_buffered_write(ctx, SMW_DRAGON_COINS_DATA, bytes(dragon_coins_data)) + if new_events > 0: + snes_buffered_write(ctx, SMW_PROGRESS_DATA, bytes(progress_data)) + snes_buffered_write(ctx, SMW_PATH_DATA, bytes(path_data)) + old_events = await snes_read(ctx, SMW_NUM_EVENTS_ADDR, 0x1) + snes_buffered_write(ctx, SMW_NUM_EVENTS_ADDR, bytes([old_events[0] + new_events])) + + await snes_flush_writes(ctx) diff --git a/worlds/smw/Items.py b/worlds/smw/Items.py new file mode 100644 index 0000000000..e650aef4a5 --- /dev/null +++ b/worlds/smw/Items.py @@ -0,0 +1,69 @@ +import typing + +from BaseClasses import Item, ItemClassification +from .Names import ItemName + + +class ItemData(typing.NamedTuple): + code: typing.Optional[int] + progression: bool + trap: bool = False + quantity: int = 1 + event: bool = False + + +class SMWItem(Item): + game: str = "Super Mario World" + + +# Separate tables for each type of item. +junk_table = { + ItemName.one_up_mushroom: ItemData(0xBC0001, False), +} + +collectable_table = { + ItemName.yoshi_egg: ItemData(0xBC0002, True), +} + +upgrade_table = { + ItemName.mario_run: ItemData(0xBC0003, True), + ItemName.mario_carry: ItemData(0xBC0004, True), + ItemName.mario_swim: ItemData(0xBC0005, True), + ItemName.mario_spin_jump: ItemData(0xBC0006, True), + ItemName.mario_climb: ItemData(0xBC0007, True), + ItemName.yoshi_activate: ItemData(0xBC0008, True), + ItemName.p_switch: ItemData(0xBC0009, True), + ItemName.progressive_powerup: ItemData(0xBC000A, True), + ItemName.p_balloon: ItemData(0xBC000B, True), + ItemName.super_star_active: ItemData(0xBC000D, True), +} + +switch_palace_table = { + ItemName.yellow_switch_palace: ItemData(0xBC000E, True), + ItemName.green_switch_palace: ItemData(0xBC000F, True), + ItemName.red_switch_palace: ItemData(0xBC0010, True), + ItemName.blue_switch_palace: ItemData(0xBC0011, True), +} + +trap_table = { + ItemName.ice_trap: ItemData(0xBC0013, False, True), + ItemName.stun_trap: ItemData(0xBC0014, False, True), + ItemName.literature_trap: ItemData(0xBC0015, False, True), +} + +event_table = { + ItemName.victory: ItemData(0xBC0000, True), + ItemName.koopaling: ItemData(0xBC0012, True), +} + +# Complete item table. +item_table = { + **junk_table, + **collectable_table, + **upgrade_table, + **switch_palace_table, + **trap_table, + **event_table, +} + +lookup_id_to_name: typing.Dict[int, str] = {data.code: item_name for item_name, data in item_table.items() if data.code} diff --git a/worlds/smw/Levels.py b/worlds/smw/Levels.py new file mode 100644 index 0000000000..0783ebfd39 --- /dev/null +++ b/worlds/smw/Levels.py @@ -0,0 +1,579 @@ + +from .Names import LocationName + +class SMWPath(): + thisEndDirection: int + otherLevelID: int + otherEndDirection: int + + def __init__(self, thisEndDirection: int, otherLevelID: int, otherEndDirection: int): + self.thisEndDirection = thisEndDirection + self.otherLevelID = otherLevelID + self.otherEndDirection = otherEndDirection + + +class SMWLevel(): + levelName: str + levelIDAddress: int + #eventIDAddress: int + eventIDValue: int + #progressByte: int + #progressBit: int + exit1Path: SMWPath + exit2Path: SMWPath + + def __init__(self, levelName: str, levelIDAddress: int, eventIDValue: int, exit1Path: SMWPath = None, exit2Path: SMWPath = None): + self.levelName = levelName + self.levelIDAddress = levelIDAddress + #self.eventIDAddress = eventIDAddress # Inferred from: LevelIDValue (Dict Key): $2D608 + LevelIDValue + self.eventIDValue = eventIDValue + #self.progressByte = progressByte # Inferred from EventIDValue: (ID / 8) + $1F02 + #self.progressBit = progressBit # Inferred from EventIDValue: 1 << (7 - (ID % 8)) + self.exit1Path = exit1Path + self.exit2Path = exit2Path + + +level_info_dict = { + 0x28: SMWLevel(LocationName.yoshis_house, 0x37A76, 0x00), + 0x29: SMWLevel(LocationName.yoshis_island_1_region, 0x37A83, 0x01, SMWPath(0x08, 0x14, 0x04)), + 0x14: SMWLevel(LocationName.yellow_switch_palace, 0x37812, 0x02), + 0x2A: SMWLevel(LocationName.yoshis_island_2_region, 0x37A89, 0x03, SMWPath(0x08, 0x27, 0x04)), + 0x27: SMWLevel(LocationName.yoshis_island_3_region, 0x37A69, 0x04, SMWPath(0x01, 0x26, 0x04)), + 0x26: SMWLevel(LocationName.yoshis_island_4_region, 0x37A4B, 0x05, SMWPath(0x08, 0x25, 0x01)), + 0x25: SMWLevel(LocationName.yoshis_island_castle_region, 0x37A29, 0x06, SMWPath(0x08, 0x15, 0x04)), + + 0x15: SMWLevel(LocationName.donut_plains_1_region, 0x37815, 0x07, SMWPath(0x02, 0x09, 0x04), SMWPath(0x08, 0x0A, 0x04)), + 0x09: SMWLevel(LocationName.donut_plains_2_region, 0x376D3, 0x09, SMWPath(0x08, 0x04, 0x02), SMWPath(0x02, 0x08, 0x01)), + 0x0A: SMWLevel(LocationName.donut_secret_1_region, 0x376E5, 0x10, SMWPath(0x08, 0x04, 0x04), SMWPath(0x01, 0x13, 0x08)), + 0x08: SMWLevel(LocationName.green_switch_palace, 0x376D1, 0x28), + 0x04: SMWLevel(LocationName.donut_ghost_house_region, 0x376A5, 0x0B, SMWPath(0x08, 0x03, 0x04), SMWPath(0x01, 0x05, 0x02)), + 0x13: SMWLevel(LocationName.donut_secret_house_region, 0x37807, 0x12, SMWPath(0x01, 0x2F, 0x04), SMWPath(0x04, 0x16, 0x08)), # SMW_TODO: Check this wrt pipe behavior + 0x05: SMWLevel(LocationName.donut_plains_3_region, 0x376A9, 0x0D, SMWPath(0x01, 0x06, 0x08)), + 0x06: SMWLevel(LocationName.donut_plains_4_region, 0x376CB, 0x0E, SMWPath(0x01, 0x07, 0x02)), + 0x2F: SMWLevel(LocationName.donut_secret_2_region, 0x37B10, 0x14, SMWPath(0x01, 0x05, 0x04)), + 0x07: SMWLevel(LocationName.donut_plains_castle_region, 0x376CD, 0x0F, SMWPath(0x08, 0x3E, 0x04)), + 0x03: SMWLevel(LocationName.donut_plains_top_secret, 0x37685, 0xFF), + 0x16: SMWLevel(LocationName.donut_plains_star_road, 0x37827, 0xFF), + + 0x3E: SMWLevel(LocationName.vanilla_dome_1_region, 0x37C25, 0x15, SMWPath(0x01, 0x3C, 0x04), SMWPath(0x02, 0x2D, 0x04)), + 0x3C: SMWLevel(LocationName.vanilla_dome_2_region, 0x37C08, 0x17, SMWPath(0x08, 0x2B, 0x04), SMWPath(0x01, 0x3F, 0x08)), + 0x2D: SMWLevel(LocationName.vanilla_secret_1_region, 0x37AE3, 0x1D, SMWPath(0x08, 0x01, 0x02), SMWPath(0x02, 0x2C, 0x01)), + 0x2B: SMWLevel(LocationName.vanilla_ghost_house_region, 0x37AC8, 0x19, SMWPath(0x01, 0x2E, 0x08)), + 0x2E: SMWLevel(LocationName.vanilla_dome_3_region, 0x37AEC, 0x1A, SMWPath(0x04, 0x3D, 0x08)), + 0x3D: SMWLevel(LocationName.vanilla_dome_4_region, 0x37C0C, 0x1B, SMWPath(0x04, 0x40, 0x08)), + 0x3F: SMWLevel(LocationName.red_switch_palace, 0x37C2A, 0x29), + 0x01: SMWLevel(LocationName.vanilla_secret_2_region, 0x3763C, 0x1F, SMWPath(0x01, 0x02, 0x02)), + 0x02: SMWLevel(LocationName.vanilla_secret_3_region, 0x3763E, 0x20, SMWPath(0x01, 0x0B, 0x02)), + 0x0B: SMWLevel(LocationName.vanilla_fortress_region, 0x37730, 0x21, SMWPath(0x01, 0x0C, 0x02)), + 0x40: SMWLevel(LocationName.vanilla_dome_castle_region, 0x37C2C, 0x1C, SMWPath(0x04, 0x0F, 0x02)), + 0x2C: SMWLevel(LocationName.vanilla_dome_star_road, 0x37AE0, 0xFF), + + 0x0C: SMWLevel(LocationName.butter_bridge_1_region, 0x37734, 0x22, SMWPath(0x01, 0x0D, 0x02)), + 0x0D: SMWLevel(LocationName.butter_bridge_2_region, 0x37736, 0x23, SMWPath(0x01, 0x0E, 0x02)), + 0x0F: SMWLevel(LocationName.cheese_bridge_region, 0x37754, 0x25, SMWPath(0x01, 0x10, 0x02), SMWPath(0x04, 0x11, 0x08)), + 0x11: SMWLevel(LocationName.soda_lake_region, 0x37784, 0x60, SMWPath(0x04, 0x12, 0x04)), + 0x10: SMWLevel(LocationName.cookie_mountain_region, 0x37757, 0x27, SMWPath(0x04, 0x0E, 0x04)), + 0x0E: SMWLevel(LocationName.twin_bridges_castle_region, 0x3773A, 0x24, SMWPath(0x01, 0x42, 0x08)), + 0x12: SMWLevel(LocationName.twin_bridges_star_road, 0x377F0, 0xFF), + + 0x42: SMWLevel(LocationName.forest_of_illusion_1_region, 0x37C78, 0x2A, SMWPath(0x01, 0x44, 0x08), SMWPath(0x02, 0x41, 0x01)), + 0x44: SMWLevel(LocationName.forest_of_illusion_2_region, 0x37CAA, 0x2C, SMWPath(0x04, 0x47, 0x08), SMWPath(0x01, 0x45, 0x02)), + 0x47: SMWLevel(LocationName.forest_of_illusion_3_region, 0x37CC8, 0x2E, SMWPath(0x02, 0x41, 0x04), SMWPath(0x04, 0x20, 0x01)), + 0x43: SMWLevel(LocationName.forest_of_illusion_4_region, 0x37CA4, 0x32, SMWPath(0x01, 0x44, 0x02), SMWPath(0x04, 0x46, 0x08)), + 0x41: SMWLevel(LocationName.forest_ghost_house_region, 0x37C76, 0x30, SMWPath(0x01, 0x42, 0x02), SMWPath(0x02, 0x43, 0x08)), + 0x46: SMWLevel(LocationName.forest_secret_region, 0x37CC4, 0x34, SMWPath(0x04, 0x1F, 0x01)), + 0x45: SMWLevel(LocationName.blue_switch_palace, 0x37CAC, 0x37), + 0x1F: SMWLevel(LocationName.forest_fortress_region, 0x37906, 0x35, SMWPath(0x02, 0x1E, 0x01)), + 0x20: SMWLevel(LocationName.forest_castle_region, 0x37928, 0x61, SMWPath(0x04, 0x22, 0x08)), + 0x1E: SMWLevel(LocationName.forest_star_road, 0x37904, 0x36), + + 0x22: SMWLevel(LocationName.chocolate_island_1_region, 0x37968, 0x62, SMWPath(0x02, 0x21, 0x01)), + 0x24: SMWLevel(LocationName.chocolate_island_2_region, 0x379B5, 0x46, SMWPath(0x02, 0x23, 0x01), SMWPath(0x04, 0x3B, 0x01)), + 0x23: SMWLevel(LocationName.chocolate_island_3_region, 0x379B3, 0x48, SMWPath(0x04, 0x23, 0x08), SMWPath(0x02, 0x1B, 0x01)), + 0x1D: SMWLevel(LocationName.chocolate_island_4_region, 0x378DF, 0x4B, SMWPath(0x02, 0x1C, 0x01)), + 0x1C: SMWLevel(LocationName.chocolate_island_5_region, 0x378DC, 0x4C, SMWPath(0x08, 0x1A, 0x04)), + 0x21: SMWLevel(LocationName.chocolate_ghost_house_region, 0x37965, 0x63, SMWPath(0x04, 0x24, 0x08)), + 0x1B: SMWLevel(LocationName.chocolate_fortress_region, 0x378BF, 0x4A, SMWPath(0x04, 0x1D, 0x08)), + 0x3B: SMWLevel(LocationName.chocolate_secret_region, 0x37B97, 0x4F, SMWPath(0x02, 0x1A, 0x02)), + 0x1A: SMWLevel(LocationName.chocolate_castle_region, 0x378BC, 0x4D, SMWPath(0x08, 0x18, 0x02)), + + 0x18: SMWLevel(LocationName.sunken_ghost_ship_region, 0x3787E, 0x4E, SMWPath(0x08, 0x3A, 0x01)), + 0x3A: SMWLevel(LocationName.valley_of_bowser_1_region, 0x37B7B, 0x38, SMWPath(0x02, 0x39, 0x01)), + 0x39: SMWLevel(LocationName.valley_of_bowser_2_region, 0x37B79, 0x39, SMWPath(0x02, 0x38, 0x01), SMWPath(0x08, 0x35, 0x04)), + 0x37: SMWLevel(LocationName.valley_of_bowser_3_region, 0x37B74, 0x3D, SMWPath(0x08, 0x33, 0x04)), + 0x33: SMWLevel(LocationName.valley_of_bowser_4_region, 0x37B54, 0x3E, SMWPath(0x01, 0x34, 0x02), SMWPath(0x08, 0x30, 0x04)), + 0x38: SMWLevel(LocationName.valley_ghost_house_region, 0x37B77, 0x3B, SMWPath(0x02, 0x37, 0x01), SMWPath(0x08, 0x34, 0x04)), + 0x35: SMWLevel(LocationName.valley_fortress_region, 0x37B59, 0x41, SMWPath(0x08, 0x32, 0x04)), + 0x34: SMWLevel(LocationName.valley_castle_region, 0x37B57, 0x40, SMWPath(0x08, 0x31, 0x04)), + 0x31: SMWLevel(LocationName.front_door, 0x37B37, 0x45), + 0x81: SMWLevel(LocationName.front_door, 0x37B37, 0x45), # Fake Extra Front Door + 0x32: SMWLevel(LocationName.back_door, 0x37B39, 0x42), + 0x82: SMWLevel(LocationName.back_door, 0x37B39, 0x42), # Fake Extra Back Door + 0x30: SMWLevel(LocationName.valley_star_road, 0x37B34, 0x44), + + 0x5B: SMWLevel(LocationName.star_road_donut, 0x37DD3, 0x50), + 0x58: SMWLevel(LocationName.star_road_1_region, 0x37DA4, 0x51, None, SMWPath(0x02, 0x53, 0x04)), + 0x53: SMWLevel(LocationName.star_road_vanilla, 0x37D82, 0x53), + 0x54: SMWLevel(LocationName.star_road_2_region, 0x37D85, 0x54, None, SMWPath(0x08, 0x52, 0x02)), + 0x52: SMWLevel(LocationName.star_road_twin_bridges, 0x37D67, 0x56), + 0x56: SMWLevel(LocationName.star_road_3_region, 0x37D89, 0x57, None, SMWPath(0x01, 0x57, 0x02)), + 0x57: SMWLevel(LocationName.star_road_forest, 0x37D8C, 0x59), + 0x59: SMWLevel(LocationName.star_road_4_region, 0x37DAA, 0x5A, None, SMWPath(0x04, 0x5C, 0x08)), + 0x5C: SMWLevel(LocationName.star_road_valley, 0x37DDC, 0x5C), + 0x5A: SMWLevel(LocationName.star_road_5_region, 0x37DB7, 0x5D, SMWPath(0x02, 0x5B, 0x01), SMWPath(0x08, 0x55, 0x04)), + 0x55: SMWLevel(LocationName.star_road_special, 0x37D87, 0x5F), + + 0x4D: SMWLevel(LocationName.special_star_road, 0x37D31, 0x64), + 0x4E: SMWLevel(LocationName.special_zone_1_region, 0x37D33, 0x65, SMWPath(0x01, 0x4F, 0x02)), + 0x4F: SMWLevel(LocationName.special_zone_2_region, 0x37D36, 0x66, SMWPath(0x01, 0x50, 0x02)), + 0x50: SMWLevel(LocationName.special_zone_3_region, 0x37D39, 0x67, SMWPath(0x01, 0x51, 0x02)), + 0x51: SMWLevel(LocationName.special_zone_4_region, 0x37D3C, 0x68, SMWPath(0x01, 0x4C, 0x01)), + 0x4C: SMWLevel(LocationName.special_zone_5_region, 0x37D1C, 0x69, SMWPath(0x02, 0x4B, 0x01)), + 0x4B: SMWLevel(LocationName.special_zone_6_region, 0x37D19, 0x6A, SMWPath(0x02, 0x4A, 0x01)), + 0x4A: SMWLevel(LocationName.special_zone_7_region, 0x37D16, 0x6B, SMWPath(0x02, 0x49, 0x01)), + 0x49: SMWLevel(LocationName.special_zone_8_region, 0x37D13, 0x6C, SMWPath(0x02, 0x48, 0x01)), + 0x48: SMWLevel(LocationName.special_complete, 0x37D11, 0x6D), +} + +full_level_list = [ + 0x28, 0x29, 0x14, 0x2A, 0x27, 0x26, 0x25, + 0x15, 0x09, 0x0A, 0x08, 0x04, 0x13, 0x05, 0x06, 0x2F, 0x07, 0x03, 0x16, + 0x3E, 0x3C, 0x2D, 0x2B, 0x2E, 0x3D, 0x3F, 0x01, 0x02, 0x0B, 0x40, 0x2C, + 0x0C, 0x0D, 0x0F, 0x11, 0x10, 0x0E, 0x12, + 0x42, 0x44, 0x47, 0x43, 0x41, 0x46, 0x45, 0x1F, 0x20, 0x1E, + 0x22, 0x24, 0x23, 0x1D, 0x1C, 0x21, 0x1B, 0x3B, 0x1A, + 0x18, 0x3A, 0x39, 0x37, 0x33, 0x38, 0x35, 0x34, 0x31, 0x32, 0x30, + 0x5B, 0x58, 0x53, 0x54, 0x52, 0x56, 0x57, 0x59, 0x5C, 0x5A, 0x55, + 0x4D, 0x4E, 0x4F, 0x50, 0x51, 0x4C, 0x4B, 0x4A, 0x49, 0x48, +] + +submap_level_list = [ + 0x28, 0x29, 0x2A, 0x27, 0x26, 0x25, + 0x2F, + 0x3E, 0x3C, 0x2D, 0x2B, 0x2E, 0x3D, 0x3F, 0x40, 0x2C, + 0x42, 0x44, 0x47, 0x43, 0x41, 0x46, 0x45, + 0x3B, + 0x3A, 0x39, 0x37, 0x33, 0x38, 0x35, 0x34, 0x31, 0x32, 0x30, + 0x5B, 0x58, 0x53, 0x54, 0x52, 0x56, 0x57, 0x59, 0x5C, 0x5A, 0x55, + 0x4D, 0x4E, 0x4F, 0x50, 0x51, 0x4C, 0x4B, 0x4A, 0x49, 0x48, +] + +easy_castle_fortress_levels = [ + 0x07, + 0x40, + 0x1F, + 0x20, + 0x1B, + 0x34, +] + +hard_castle_fortress_levels = [ + 0x25, + 0x0B, + 0x0E, + 0x1A, + 0x35, +] + +easy_single_levels = [ + 0x29, + 0x2A, + 0x27, + 0x26, + 0x05, + 0x06, + 0x2F, + 0x2E, + 0x3D, + 0x01, + 0x0C, + 0x0D, + 0x46, + 0x1D, +] + +hard_single_levels = [ + 0x2B, + 0x02, + 0x11, + 0x10, + 0x22, + 0x1C, + 0x21, + 0x3B, + 0x3A, + 0x37, + 0x4E, + 0x4F, + 0x50, + 0x51, + 0x4C, + 0x4B, + 0x4A, + 0x49, +] + +easy_double_levels = [ + 0x15, + 0x09, + 0x0F, + 0x42, + 0x43, + 0x24, + 0x38, + 0x58, + 0x54, + 0x56, +] + +hard_double_levels = [ + 0x0A, + 0x04, + 0x13, + 0x3E, + 0x3C, + 0x2D, + 0x44, + 0x47, + 0x41, + 0x23, + 0x33, + 0x39, + 0x59, + 0x5A, +] + +switch_palace_levels = [ + 0x14, + 0x08, + 0x3F, + 0x45, +] + +location_id_to_level_id = { + LocationName.yoshis_island_1_exit_1: [0x29, 0], + LocationName.yoshis_island_1_dragon: [0x29, 2], + LocationName.yoshis_island_2_exit_1: [0x2A, 0], + LocationName.yoshis_island_2_dragon: [0x2A, 2], + LocationName.yoshis_island_3_exit_1: [0x27, 0], + LocationName.yoshis_island_3_dragon: [0x27, 2], + LocationName.yoshis_island_4_exit_1: [0x26, 0], + LocationName.yoshis_island_4_dragon: [0x26, 2], + LocationName.yoshis_island_castle: [0x25, 0], + LocationName.yoshis_island_koopaling: [0x25, 0], + LocationName.yellow_switch_palace: [0x14, 0], + + LocationName.donut_plains_1_exit_1: [0x15, 0], + LocationName.donut_plains_1_exit_2: [0x15, 1], + LocationName.donut_plains_1_dragon: [0x15, 2], + LocationName.donut_plains_2_exit_1: [0x09, 0], + LocationName.donut_plains_2_exit_2: [0x09, 1], + LocationName.donut_plains_2_dragon: [0x09, 2], + LocationName.donut_plains_3_exit_1: [0x05, 0], + LocationName.donut_plains_3_dragon: [0x05, 2], + LocationName.donut_plains_4_exit_1: [0x06, 0], + LocationName.donut_plains_4_dragon: [0x06, 2], + LocationName.donut_secret_1_exit_1: [0x0A, 0], + LocationName.donut_secret_1_exit_2: [0x0A, 1], + LocationName.donut_secret_1_dragon: [0x0A, 2], + LocationName.donut_secret_2_exit_1: [0x2F, 0], + LocationName.donut_secret_2_dragon: [0x2F, 2], + LocationName.donut_ghost_house_exit_1: [0x04, 0], + LocationName.donut_ghost_house_exit_2: [0x04, 1], + LocationName.donut_secret_house_exit_1: [0x13, 0], + LocationName.donut_secret_house_exit_2: [0x13, 1], + LocationName.donut_plains_castle: [0x07, 0], + LocationName.donut_plains_koopaling: [0x07, 0], + LocationName.green_switch_palace: [0x08, 0], + + LocationName.vanilla_dome_1_exit_1: [0x3E, 0], + LocationName.vanilla_dome_1_exit_2: [0x3E, 1], + LocationName.vanilla_dome_1_dragon: [0x3E, 2], + LocationName.vanilla_dome_2_exit_1: [0x3C, 0], + LocationName.vanilla_dome_2_exit_2: [0x3C, 1], + LocationName.vanilla_dome_2_dragon: [0x3C, 2], + LocationName.vanilla_dome_3_exit_1: [0x2E, 0], + LocationName.vanilla_dome_3_dragon: [0x2E, 2], + LocationName.vanilla_dome_4_exit_1: [0x3D, 0], + LocationName.vanilla_dome_4_dragon: [0x3D, 2], + LocationName.vanilla_secret_1_exit_1: [0x2D, 0], + LocationName.vanilla_secret_1_exit_2: [0x2D, 1], + LocationName.vanilla_secret_1_dragon: [0x2D, 2], + LocationName.vanilla_secret_2_exit_1: [0x01, 0], + LocationName.vanilla_secret_2_dragon: [0x01, 2], + LocationName.vanilla_secret_3_exit_1: [0x02, 0], + LocationName.vanilla_secret_3_dragon: [0x02, 2], + LocationName.vanilla_ghost_house_exit_1: [0x2B, 0], + LocationName.vanilla_ghost_house_dragon: [0x2B, 2], + LocationName.vanilla_fortress: [0x0B, 0], + LocationName.vanilla_reznor: [0x0B, 0], + LocationName.vanilla_dome_castle: [0x40, 0], + LocationName.vanilla_dome_koopaling: [0x40, 0], + LocationName.red_switch_palace: [0x3F, 0], + + LocationName.butter_bridge_1_exit_1: [0x0C, 0], + LocationName.butter_bridge_1_dragon: [0x0C, 2], + LocationName.butter_bridge_2_exit_1: [0x0D, 0], + LocationName.butter_bridge_2_dragon: [0x0D, 2], + LocationName.cheese_bridge_exit_1: [0x0F, 0], + LocationName.cheese_bridge_exit_2: [0x0F, 1], + LocationName.cheese_bridge_dragon: [0x0F, 2], + LocationName.cookie_mountain_exit_1: [0x10, 0], + LocationName.cookie_mountain_dragon: [0x10, 2], + LocationName.soda_lake_exit_1: [0x11, 0], + LocationName.soda_lake_dragon: [0x11, 2], + LocationName.twin_bridges_castle: [0x0E, 0], + LocationName.twin_bridges_koopaling: [0x0E, 0], + + LocationName.forest_of_illusion_1_exit_1: [0x42, 0], + LocationName.forest_of_illusion_1_exit_2: [0x42, 1], + LocationName.forest_of_illusion_2_exit_1: [0x44, 0], + LocationName.forest_of_illusion_2_exit_2: [0x44, 1], + LocationName.forest_of_illusion_2_dragon: [0x44, 2], + LocationName.forest_of_illusion_3_exit_1: [0x47, 0], + LocationName.forest_of_illusion_3_exit_2: [0x47, 1], + LocationName.forest_of_illusion_3_dragon: [0x47, 2], + LocationName.forest_of_illusion_4_exit_1: [0x43, 0], + LocationName.forest_of_illusion_4_exit_2: [0x43, 1], + LocationName.forest_of_illusion_4_dragon: [0x43, 2], + LocationName.forest_ghost_house_exit_1: [0x41, 0], + LocationName.forest_ghost_house_exit_2: [0x41, 1], + LocationName.forest_ghost_house_dragon: [0x41, 2], + LocationName.forest_secret_exit_1: [0x46, 0], + LocationName.forest_secret_dragon: [0x46, 2], + LocationName.forest_fortress: [0x1F, 0], + LocationName.forest_reznor: [0x1F, 0], + LocationName.forest_castle: [0x20, 0], + LocationName.forest_koopaling: [0x20, 0], + LocationName.forest_castle_dragon: [0x20, 2], + LocationName.blue_switch_palace: [0x45, 0], + + LocationName.chocolate_island_1_exit_1: [0x22, 0], + LocationName.chocolate_island_1_dragon: [0x22, 2], + LocationName.chocolate_island_2_exit_1: [0x24, 0], + LocationName.chocolate_island_2_exit_2: [0x24, 1], + LocationName.chocolate_island_2_dragon: [0x24, 2], + LocationName.chocolate_island_3_exit_1: [0x23, 0], + LocationName.chocolate_island_3_exit_2: [0x23, 1], + LocationName.chocolate_island_3_dragon: [0x23, 2], + LocationName.chocolate_island_4_exit_1: [0x1D, 0], + LocationName.chocolate_island_4_dragon: [0x1D, 2], + LocationName.chocolate_island_5_exit_1: [0x1C, 0], + LocationName.chocolate_island_5_dragon: [0x1C, 2], + LocationName.chocolate_ghost_house_exit_1: [0x21, 0], + LocationName.chocolate_secret_exit_1: [0x3B, 0], + LocationName.chocolate_fortress: [0x1B, 0], + LocationName.chocolate_reznor: [0x1B, 0], + LocationName.chocolate_castle: [0x1A, 0], + LocationName.chocolate_koopaling: [0x1A, 0], + + LocationName.sunken_ghost_ship: [0x18, 0], + LocationName.sunken_ghost_ship_dragon: [0x18, 2], + + LocationName.valley_of_bowser_1_exit_1: [0x3A, 0], + LocationName.valley_of_bowser_1_dragon: [0x3A, 2], + LocationName.valley_of_bowser_2_exit_1: [0x39, 0], + LocationName.valley_of_bowser_2_exit_2: [0x39, 1], + LocationName.valley_of_bowser_2_dragon: [0x39, 2], + LocationName.valley_of_bowser_3_exit_1: [0x37, 0], + LocationName.valley_of_bowser_3_dragon: [0x37, 2], + LocationName.valley_of_bowser_4_exit_1: [0x33, 0], + LocationName.valley_of_bowser_4_exit_2: [0x33, 1], + LocationName.valley_ghost_house_exit_1: [0x38, 0], + LocationName.valley_ghost_house_exit_2: [0x38, 1], + LocationName.valley_ghost_house_dragon: [0x38, 2], + LocationName.valley_fortress: [0x35, 0], + LocationName.valley_reznor: [0x35, 0], + LocationName.valley_castle: [0x34, 0], + LocationName.valley_koopaling: [0x34, 0], + LocationName.valley_castle_dragon: [0x34, 2], + + LocationName.star_road_1_exit_1: [0x58, 0], + LocationName.star_road_1_exit_2: [0x58, 1], + LocationName.star_road_1_dragon: [0x58, 2], + LocationName.star_road_2_exit_1: [0x54, 0], + LocationName.star_road_2_exit_2: [0x54, 1], + LocationName.star_road_3_exit_1: [0x56, 0], + LocationName.star_road_3_exit_2: [0x56, 1], + LocationName.star_road_4_exit_1: [0x59, 0], + LocationName.star_road_4_exit_2: [0x59, 1], + LocationName.star_road_5_exit_1: [0x5A, 0], + LocationName.star_road_5_exit_2: [0x5A, 1], + + LocationName.special_zone_1_exit_1: [0x4E, 0], + LocationName.special_zone_1_dragon: [0x4E, 2], + LocationName.special_zone_2_exit_1: [0x4F, 0], + LocationName.special_zone_2_dragon: [0x4F, 2], + LocationName.special_zone_3_exit_1: [0x50, 0], + LocationName.special_zone_3_dragon: [0x50, 2], + LocationName.special_zone_4_exit_1: [0x51, 0], + LocationName.special_zone_4_dragon: [0x51, 2], + LocationName.special_zone_5_exit_1: [0x4C, 0], + LocationName.special_zone_5_dragon: [0x4C, 2], + LocationName.special_zone_6_exit_1: [0x4B, 0], + LocationName.special_zone_6_dragon: [0x4B, 2], + LocationName.special_zone_7_exit_1: [0x4A, 0], + LocationName.special_zone_7_dragon: [0x4A, 2], + LocationName.special_zone_8_exit_1: [0x49, 0], + LocationName.special_zone_8_dragon: [0x49, 2], +} + +def generate_level_list(world, player): + + if not world.level_shuffle[player]: + out_level_list = full_level_list.copy() + out_level_list[0x00] = 0x03 + out_level_list[0x11] = 0x28 + + if world.bowser_castle_doors[player] == "fast": + out_level_list[0x41] = 0x82 + out_level_list[0x42] = 0x32 + elif world.bowser_castle_doors[player] == "slow": + out_level_list[0x41] = 0x31 + out_level_list[0x42] = 0x81 + + return out_level_list + + shuffled_level_list = [] + easy_castle_fortress_levels_copy = easy_castle_fortress_levels.copy() + world.random.shuffle(easy_castle_fortress_levels_copy) + hard_castle_fortress_levels_copy = hard_castle_fortress_levels.copy() + world.random.shuffle(hard_castle_fortress_levels_copy) + easy_single_levels_copy = easy_single_levels.copy() + world.random.shuffle(easy_single_levels_copy) + hard_single_levels_copy = hard_single_levels.copy() + world.random.shuffle(hard_single_levels_copy) + easy_double_levels_copy = easy_double_levels.copy() + world.random.shuffle(easy_double_levels_copy) + hard_double_levels_copy = hard_double_levels.copy() + world.random.shuffle(hard_double_levels_copy) + switch_palace_levels_copy = switch_palace_levels.copy() + world.random.shuffle(switch_palace_levels_copy) + + # Yoshi's Island + shuffled_level_list.append(0x03) + shuffled_level_list.append(easy_single_levels_copy.pop(0)) + shuffled_level_list.append(0x14) + shuffled_level_list.append(easy_single_levels_copy.pop(0)) + shuffled_level_list.append(easy_single_levels_copy.pop(0)) + shuffled_level_list.append(easy_single_levels_copy.pop(0)) + shuffled_level_list.append(easy_castle_fortress_levels_copy.pop(0)) + + # Donut Plains + shuffled_level_list.append(easy_double_levels_copy.pop(0)) + shuffled_level_list.append(easy_double_levels_copy.pop(0)) + shuffled_level_list.append(easy_double_levels_copy.pop(0)) + shuffled_level_list.append(0x08) + shuffled_level_list.append(easy_double_levels_copy.pop(0)) + shuffled_level_list.append(easy_double_levels_copy.pop(0)) + shuffled_level_list.append(easy_single_levels_copy.pop(0)) + shuffled_level_list.append(easy_single_levels_copy.pop(0)) + shuffled_level_list.append(easy_single_levels_copy.pop(0)) + shuffled_level_list.append(easy_castle_fortress_levels_copy.pop(0)) + shuffled_level_list.append(0x28) + shuffled_level_list.append(0x16) + + single_levels_copy = (easy_single_levels_copy.copy() + hard_single_levels_copy.copy()) + world.random.shuffle(single_levels_copy) + + castle_fortress_levels_copy = (easy_castle_fortress_levels_copy.copy() + hard_castle_fortress_levels_copy.copy()) + world.random.shuffle(castle_fortress_levels_copy) + + double_levels_copy = (easy_double_levels_copy.copy() + hard_double_levels_copy.copy()) + world.random.shuffle(double_levels_copy) + + # Vanilla Dome + shuffled_level_list.append(double_levels_copy.pop(0)) + shuffled_level_list.append(double_levels_copy.pop(0)) + shuffled_level_list.append(double_levels_copy.pop(0)) + shuffled_level_list.append(single_levels_copy.pop(0)) + shuffled_level_list.append(single_levels_copy.pop(0)) + shuffled_level_list.append(single_levels_copy.pop(0)) + shuffled_level_list.append(0x3F) + shuffled_level_list.append(single_levels_copy.pop(0)) + shuffled_level_list.append(single_levels_copy.pop(0)) + shuffled_level_list.append(castle_fortress_levels_copy.pop(0)) + shuffled_level_list.append(castle_fortress_levels_copy.pop(0)) + shuffled_level_list.append(0x2C) + + # Twin Bridges + shuffled_level_list.append(single_levels_copy.pop(0)) + shuffled_level_list.append(single_levels_copy.pop(0)) + shuffled_level_list.append(double_levels_copy.pop(0)) + shuffled_level_list.append(single_levels_copy.pop(0)) + shuffled_level_list.append(single_levels_copy.pop(0)) + shuffled_level_list.append(castle_fortress_levels_copy.pop(0)) + shuffled_level_list.append(0x12) + + # Forest of Illusion + shuffled_level_list.append(double_levels_copy.pop(0)) + shuffled_level_list.append(double_levels_copy.pop(0)) + shuffled_level_list.append(double_levels_copy.pop(0)) + shuffled_level_list.append(double_levels_copy.pop(0)) + shuffled_level_list.append(double_levels_copy.pop(0)) + shuffled_level_list.append(single_levels_copy.pop(0)) + shuffled_level_list.append(0x45) + shuffled_level_list.append(castle_fortress_levels_copy.pop(0)) + shuffled_level_list.append(castle_fortress_levels_copy.pop(0)) + shuffled_level_list.append(0x1E) + + # Chocolate Island + shuffled_level_list.append(single_levels_copy.pop(0)) + shuffled_level_list.append(double_levels_copy.pop(0)) + shuffled_level_list.append(double_levels_copy.pop(0)) + shuffled_level_list.append(single_levels_copy.pop(0)) + shuffled_level_list.append(single_levels_copy.pop(0)) + shuffled_level_list.append(single_levels_copy.pop(0)) + shuffled_level_list.append(castle_fortress_levels_copy.pop(0)) + shuffled_level_list.append(single_levels_copy.pop(0)) + shuffled_level_list.append(castle_fortress_levels_copy.pop(0)) + + # Valley of Bowser + shuffled_level_list.append(0x18) + shuffled_level_list.append(single_levels_copy.pop(0)) + shuffled_level_list.append(double_levels_copy.pop(0)) + shuffled_level_list.append(single_levels_copy.pop(0)) + shuffled_level_list.append(double_levels_copy.pop(0)) + shuffled_level_list.append(double_levels_copy.pop(0)) + shuffled_level_list.append(castle_fortress_levels_copy.pop(0)) + shuffled_level_list.append(castle_fortress_levels_copy.pop(0)) + + # Front/Back Door + if world.bowser_castle_doors[player] == "fast": + shuffled_level_list.append(0x82) + shuffled_level_list.append(0x32) + elif world.bowser_castle_doors[player] == "slow": + shuffled_level_list.append(0x31) + shuffled_level_list.append(0x81) + else: + shuffled_level_list.append(0x31) + shuffled_level_list.append(0x32) + + shuffled_level_list.append(0x30) + + # Star Road + shuffled_level_list.append(0x5B) + shuffled_level_list.append(double_levels_copy.pop(0)) + shuffled_level_list.append(0x53) + shuffled_level_list.append(double_levels_copy.pop(0)) + shuffled_level_list.append(0x52) + shuffled_level_list.append(double_levels_copy.pop(0)) + shuffled_level_list.append(0x57) + shuffled_level_list.append(double_levels_copy.pop(0)) + shuffled_level_list.append(0x5C) + shuffled_level_list.append(double_levels_copy.pop(0)) + shuffled_level_list.append(0x55) + + # Special Zone + shuffled_level_list.append(0x4D) + shuffled_level_list.append(single_levels_copy.pop(0)) + shuffled_level_list.append(single_levels_copy.pop(0)) + shuffled_level_list.append(single_levels_copy.pop(0)) + shuffled_level_list.append(single_levels_copy.pop(0)) + shuffled_level_list.append(single_levels_copy.pop(0)) + shuffled_level_list.append(single_levels_copy.pop(0)) + shuffled_level_list.append(single_levels_copy.pop(0)) + shuffled_level_list.append(single_levels_copy.pop(0)) + shuffled_level_list.append(0x48) + + return shuffled_level_list diff --git a/worlds/smw/Locations.py b/worlds/smw/Locations.py new file mode 100644 index 0000000000..a997f92f65 --- /dev/null +++ b/worlds/smw/Locations.py @@ -0,0 +1,233 @@ +import typing + +from BaseClasses import Location +from .Names import LocationName + + +class SMWLocation(Location): + game: str = "Super Mario World" + + def __init__(self, player: int, name: str = '', address: int = None, parent=None, prog_byte: int = None, prog_bit: int = None): + super().__init__(player, name, address, parent) + self.progress_byte = prog_byte + self.progress_bit = prog_bit + + +level_location_table = { + LocationName.yoshis_island_1_exit_1: 0xBC0000, + LocationName.yoshis_island_2_exit_1: 0xBC0001, + LocationName.yoshis_island_3_exit_1: 0xBC0002, + LocationName.yoshis_island_4_exit_1: 0xBC0003, + LocationName.yoshis_island_castle: 0xBC0004, + LocationName.yoshis_island_koopaling: 0xBC00A0, + + LocationName.yellow_switch_palace: 0xBC0005, + + LocationName.donut_plains_1_exit_1: 0xBC0006, + LocationName.donut_plains_1_exit_2: 0xBC0007, + LocationName.donut_plains_2_exit_1: 0xBC0008, + LocationName.donut_plains_2_exit_2: 0xBC0009, + LocationName.donut_plains_3_exit_1: 0xBC000A, + LocationName.donut_plains_4_exit_1: 0xBC000B, + LocationName.donut_secret_1_exit_1: 0xBC000C, + LocationName.donut_secret_1_exit_2: 0xBC000D, + LocationName.donut_secret_2_exit_1: 0xBC0063, + LocationName.donut_ghost_house_exit_1: 0xBC000E, + LocationName.donut_ghost_house_exit_2: 0xBC000F, + LocationName.donut_secret_house_exit_1: 0xBC0010, + LocationName.donut_secret_house_exit_2: 0xBC0011, + LocationName.donut_plains_castle: 0xBC0012, + LocationName.donut_plains_koopaling: 0xBC00A1, + + LocationName.green_switch_palace: 0xBC0013, + + LocationName.vanilla_dome_1_exit_1: 0xBC0014, + LocationName.vanilla_dome_1_exit_2: 0xBC0015, + LocationName.vanilla_dome_2_exit_1: 0xBC0016, + LocationName.vanilla_dome_2_exit_2: 0xBC0017, + LocationName.vanilla_dome_3_exit_1: 0xBC0018, + LocationName.vanilla_dome_4_exit_1: 0xBC0019, + LocationName.vanilla_secret_1_exit_1: 0xBC001A, + LocationName.vanilla_secret_1_exit_2: 0xBC001B, + LocationName.vanilla_secret_2_exit_1: 0xBC001C, + LocationName.vanilla_secret_3_exit_1: 0xBC001D, + LocationName.vanilla_ghost_house_exit_1: 0xBC001E, + LocationName.vanilla_fortress: 0xBC0020, + LocationName.vanilla_reznor: 0xBC00B0, + LocationName.vanilla_dome_castle: 0xBC0021, + LocationName.vanilla_dome_koopaling: 0xBC00A2, + + LocationName.red_switch_palace: 0xBC0022, + + LocationName.butter_bridge_1_exit_1: 0xBC0023, + LocationName.butter_bridge_2_exit_1: 0xBC0024, + LocationName.cheese_bridge_exit_1: 0xBC0025, + LocationName.cheese_bridge_exit_2: 0xBC0026, + LocationName.cookie_mountain_exit_1: 0xBC0027, + LocationName.soda_lake_exit_1: 0xBC0028, + LocationName.twin_bridges_castle: 0xBC0029, + LocationName.twin_bridges_koopaling: 0xBC00A3, + + LocationName.forest_of_illusion_1_exit_1: 0xBC002A, + LocationName.forest_of_illusion_1_exit_2: 0xBC002B, + LocationName.forest_of_illusion_2_exit_1: 0xBC002C, + LocationName.forest_of_illusion_2_exit_2: 0xBC002D, + LocationName.forest_of_illusion_3_exit_1: 0xBC002E, + LocationName.forest_of_illusion_3_exit_2: 0xBC002F, + LocationName.forest_of_illusion_4_exit_1: 0xBC0030, + LocationName.forest_of_illusion_4_exit_2: 0xBC0031, + LocationName.forest_ghost_house_exit_1: 0xBC0032, + LocationName.forest_ghost_house_exit_2: 0xBC0033, + LocationName.forest_secret_exit_1: 0xBC0034, + LocationName.forest_fortress: 0xBC0035, + LocationName.forest_reznor: 0xBC00B1, + LocationName.forest_castle: 0xBC0036, + LocationName.forest_koopaling: 0xBC00A4, + + LocationName.blue_switch_palace: 0xBC0037, + + LocationName.chocolate_island_1_exit_1: 0xBC0038, + LocationName.chocolate_island_2_exit_1: 0xBC0039, + LocationName.chocolate_island_2_exit_2: 0xBC003A, + LocationName.chocolate_island_3_exit_1: 0xBC003B, + LocationName.chocolate_island_3_exit_2: 0xBC003C, + LocationName.chocolate_island_4_exit_1: 0xBC003D, + LocationName.chocolate_island_5_exit_1: 0xBC003E, + LocationName.chocolate_ghost_house_exit_1: 0xBC003F, + LocationName.chocolate_secret_exit_1: 0xBC0041, + LocationName.chocolate_fortress: 0xBC0042, + LocationName.chocolate_reznor: 0xBC00B2, + LocationName.chocolate_castle: 0xBC0043, + LocationName.chocolate_koopaling: 0xBC00A5, + + LocationName.sunken_ghost_ship: 0xBC0044, + + LocationName.valley_of_bowser_1_exit_1: 0xBC0045, + LocationName.valley_of_bowser_2_exit_1: 0xBC0046, + LocationName.valley_of_bowser_2_exit_2: 0xBC0047, + LocationName.valley_of_bowser_3_exit_1: 0xBC0048, + LocationName.valley_of_bowser_4_exit_1: 0xBC0049, + LocationName.valley_of_bowser_4_exit_2: 0xBC004A, + LocationName.valley_ghost_house_exit_1: 0xBC004B, + LocationName.valley_ghost_house_exit_2: 0xBC004C, + LocationName.valley_fortress: 0xBC004E, + LocationName.valley_reznor: 0xBC00B3, + LocationName.valley_castle: 0xBC004F, + LocationName.valley_koopaling: 0xBC00A6, + + LocationName.star_road_1_exit_1: 0xBC0051, + LocationName.star_road_1_exit_2: 0xBC0052, + LocationName.star_road_2_exit_1: 0xBC0053, + LocationName.star_road_2_exit_2: 0xBC0054, + LocationName.star_road_3_exit_1: 0xBC0055, + LocationName.star_road_3_exit_2: 0xBC0056, + LocationName.star_road_4_exit_1: 0xBC0057, + LocationName.star_road_4_exit_2: 0xBC0058, + LocationName.star_road_5_exit_1: 0xBC0059, + LocationName.star_road_5_exit_2: 0xBC005A, + + LocationName.special_zone_1_exit_1: 0xBC005B, + LocationName.special_zone_2_exit_1: 0xBC005C, + LocationName.special_zone_3_exit_1: 0xBC005D, + LocationName.special_zone_4_exit_1: 0xBC005E, + LocationName.special_zone_5_exit_1: 0xBC005F, + LocationName.special_zone_6_exit_1: 0xBC0060, + LocationName.special_zone_7_exit_1: 0xBC0061, + LocationName.special_zone_8_exit_1: 0xBC0062, +} + +dragon_coin_location_table = { + LocationName.yoshis_island_1_dragon: 0xBC0100, + LocationName.yoshis_island_2_dragon: 0xBC0101, + LocationName.yoshis_island_3_dragon: 0xBC0102, + LocationName.yoshis_island_4_dragon: 0xBC0103, + + LocationName.donut_plains_1_dragon: 0xBC0106, + LocationName.donut_plains_2_dragon: 0xBC0108, + LocationName.donut_plains_3_dragon: 0xBC010A, + LocationName.donut_plains_4_dragon: 0xBC010B, + LocationName.donut_secret_1_dragon: 0xBC010C, + LocationName.donut_secret_2_dragon: 0xBC010D, + + LocationName.vanilla_dome_1_dragon: 0xBC0114, + LocationName.vanilla_dome_2_dragon: 0xBC0116, + LocationName.vanilla_dome_3_dragon: 0xBC0118, + LocationName.vanilla_dome_4_dragon: 0xBC0119, + LocationName.vanilla_secret_1_dragon: 0xBC011A, + LocationName.vanilla_secret_2_dragon: 0xBC011C, + LocationName.vanilla_secret_3_dragon: 0xBC011D, + LocationName.vanilla_ghost_house_dragon: 0xBC011E, + + LocationName.butter_bridge_1_dragon: 0xBC0123, + LocationName.butter_bridge_2_dragon: 0xBC0124, + LocationName.cheese_bridge_dragon: 0xBC0125, + LocationName.cookie_mountain_dragon: 0xBC0127, + LocationName.soda_lake_dragon: 0xBC0128, + + LocationName.forest_of_illusion_2_dragon: 0xBC012C, + LocationName.forest_of_illusion_3_dragon: 0xBC012E, + LocationName.forest_of_illusion_4_dragon: 0xBC0130, + LocationName.forest_ghost_house_dragon: 0xBC0132, + LocationName.forest_secret_dragon: 0xBC0134, + LocationName.forest_castle_dragon: 0xBC0136, + + LocationName.chocolate_island_1_dragon: 0xBC0138, + LocationName.chocolate_island_2_dragon: 0xBC0139, + LocationName.chocolate_island_3_dragon: 0xBC013B, + LocationName.chocolate_island_4_dragon: 0xBC013D, + LocationName.chocolate_island_5_dragon: 0xBC013E, + + LocationName.sunken_ghost_ship_dragon: 0xBC0144, + + LocationName.valley_of_bowser_1_dragon: 0xBC0145, + LocationName.valley_of_bowser_2_dragon: 0xBC0146, + LocationName.valley_of_bowser_3_dragon: 0xBC0148, + LocationName.valley_ghost_house_dragon: 0xBC014B, + LocationName.valley_castle_dragon: 0xBC014F, + + LocationName.star_road_1_dragon: 0xBC0151, + + LocationName.special_zone_1_dragon: 0xBC015B, + LocationName.special_zone_2_dragon: 0xBC015C, + LocationName.special_zone_3_dragon: 0xBC015D, + LocationName.special_zone_4_dragon: 0xBC015E, + LocationName.special_zone_5_dragon: 0xBC015F, + LocationName.special_zone_6_dragon: 0xBC0160, + LocationName.special_zone_7_dragon: 0xBC0161, + LocationName.special_zone_8_dragon: 0xBC0162, +} + +bowser_location_table = { + LocationName.bowser: 0xBC0200, +} + +yoshi_house_location_table = { + LocationName.yoshis_house: 0xBC0201, +} + +all_locations = { + **level_location_table, + **dragon_coin_location_table, + **bowser_location_table, + **yoshi_house_location_table, +} + +location_table = {} + + +def setup_locations(world, player: int): + location_table = {**level_location_table} + + # Dragon Coins here + if world.dragon_coin_checks[player].value: + location_table.update({**dragon_coin_location_table}) + + if world.goal[player] == "yoshi_egg_hunt": + location_table.update({**yoshi_house_location_table}) + else: + location_table.update({**bowser_location_table}) + + return location_table + + +lookup_id_to_name: typing.Dict[int, str] = {id: name for name, _ in all_locations.items()} diff --git a/worlds/smw/Names/ItemName.py b/worlds/smw/Names/ItemName.py new file mode 100644 index 0000000000..72c984b016 --- /dev/null +++ b/worlds/smw/Names/ItemName.py @@ -0,0 +1,32 @@ +# Junk Definitions +one_up_mushroom = "1-Up Mushroom" + +# Collectable Definitions +yoshi_egg = "Yoshi Egg" + +# Upgrade Definitions +mario_run = "Run" +mario_carry = "Carry" +mario_swim = "Swim" +mario_spin_jump = "Spin Jump" +mario_climb = "Climb" +yoshi_activate = "Yoshi" +p_switch = "P-Switch" +p_balloon = "P-Balloon" +progressive_powerup = "Progressive Powerup" +super_star_active = "Super Star Activate" + +# Switch Palace Definitions +yellow_switch_palace = "Yellow Switch Palace" +green_switch_palace = "Green Switch Palace" +red_switch_palace = "Red Switch Palace" +blue_switch_palace = "Blue Switch Palace" + +# Trap Definitions +ice_trap = "Ice Trap" +stun_trap = "Stun Trap" +literature_trap = "Literature Trap" + +# Other Definitions +victory = "The Princess" +koopaling = "Boss Token" diff --git a/worlds/smw/Names/LiteratureTrap.py b/worlds/smw/Names/LiteratureTrap.py new file mode 100644 index 0000000000..94c038228d --- /dev/null +++ b/worlds/smw/Names/LiteratureTrap.py @@ -0,0 +1,52 @@ +lit_trap_text_list = [ +[[0x8, 0x1f, 0x4c, 0x54, 0x52, 0x53, 0x1f, 0x4d, 0x4e, 0x53, 0x1f, 0x45, 0x44, 0x40, 0x51, 0x1b, 0x9f, 0x5, 0x44, 0x40, 0x51, 0x1f, 0x48, 0x52, 0x1f, 0x53, 0x47, 0x44, 0x9f, 0x4c, 0x48, 0x4d, 0x43, 0x1c, 0x4a, 0x48, 0x4b, 0x4b, 0x44, 0x51, 0x1b, 0x1f, 0x5, 0x44, 0x40, 0x51, 0x9f, 0x48, 0x52, 0x1f, 0x53, 0x47, 0x44, 0x9f, 0x4b, 0x48, 0x53, 0x53, 0x4b, 0x44, 0x1c, 0x43, 0x44, 0x40, 0x53, 0x47, 0x1f, 0x53, 0x47, 0x40, 0x53, 0x9f, 0x41, 0x51, 0x48, 0x4d, 0x46, 0x52, 0x1f, 0x40, 0x41, 0x4e, 0x54, 0x53, 0x1f, 0x53, 0x4e, 0x53, 0x40, 0xcb, 0x4e, 0x41, 0x4b, 0x48, 0x53, 0x44, 0x51, 0x40, 0x53, 0x48, 0x4e, 0x4d, 0x1b, 0x1f, 0x8, 0x9f, 0x56, 0x48, 0x4b, 0x4b, 0x1f, 0x45, 0x40, 0x42, 0x44, 0x1f, 0x4c, 0x58, 0x1f, 0x45, 0x44, 0x40, 0x51, 0x9b, ], [0x8, 0x1f, 0x56, 0x48, 0x4b, 0x4b, 0x1f, 0x4f, 0x44, 0x51, 0x4c, 0x48, 0x53, 0x1f, 0x48, 0x53, 0x9f, 0x53, 0x4e, 0x1f, 0x4f, 0x40, 0x52, 0x52, 0x1f, 0x4e, 0x55, 0x44, 0x51, 0x1f, 0x4c, 0x44, 0x9f, 0x40, 0x4d, 0x43, 0x1f, 0x53, 0x47, 0x51, 0x4e, 0x54, 0x46, 0x47, 0x1f, 0x4c, 0x44, 0x1b, 0x9f, 0x0, 0x4d, 0x43, 0x1f, 0x56, 0x47, 0x44, 0x4d, 0x1f, 0x48, 0x53, 0x1f, 0x47, 0x40, 0x52, 0x9f, 0x46, 0x4e, 0x4d, 0x44, 0x1f, 0x4f, 0x40, 0x52, 0x53, 0x1f, 0x8, 0x1f, 0x56, 0x48, 0x4b, 0x4b, 0x9f, 0x53, 0x54, 0x51, 0x4d, 0x1f, 0x53, 0x47, 0x44, 0x1f, 0x48, 0x4d, 0x4d, 0x44, 0x51, 0x1f, 0x44, 0x58, 0xc4, 0x53, 0x4e, 0x1f, 0x52, 0x44, 0x44, 0x1f, 0x48, 0x53, 0x52, 0x1f, 0x4f, 0x40, 0x53, 0x47, 0x1b, 0x9f, 0x16, 0x47, 0x44, 0x51, 0x44, 0x1f, 0x53, 0x47, 0x44, 0x1f, 0x45, 0x44, 0x40, 0x51, 0x1f, 0x47, 0x40, 0xd2, ], [0x46, 0x4e, 0x4d, 0x44, 0x1d, 0x1f, 0x53, 0x47, 0x44, 0x51, 0x44, 0x1f, 0x56, 0x48, 0x4b, 0x4b, 0x9f, 0x41, 0x44, 0x1f, 0x4d, 0x4e, 0x53, 0x47, 0x48, 0x4d, 0x46, 0x1b, 0x1f, 0xe, 0x4d, 0x4b, 0x58, 0x1f, 0x88, 0x56, 0x48, 0x4b, 0x4b, 0x1f, 0x51, 0x44, 0x4c, 0x40, 0x48, 0x4d, 0x1b, 0x9f, 0x1c, 0x7, 0x44, 0x51, 0x41, 0x44, 0x51, 0x53, 0x1f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, ]], +[[0x0, 0x41, 0x4e, 0x54, 0x53, 0x1f, 0x53, 0x47, 0x51, 0x44, 0x44, 0x1f, 0x53, 0x47, 0x48, 0x4d, 0x46, 0xd2, 0x8, 0x1f, 0x56, 0x40, 0x52, 0x1f, 0x40, 0x41, 0x52, 0x4e, 0x4b, 0x54, 0x53, 0x44, 0x4b, 0x58, 0x9f, 0x4f, 0x4e, 0x52, 0x48, 0x53, 0x48, 0x55, 0x44, 0x1b, 0x1f, 0x5, 0x48, 0x51, 0x52, 0x53, 0x1d, 0x9f, 0x4, 0x43, 0x56, 0x40, 0x51, 0x43, 0x1f, 0x56, 0x40, 0x52, 0x1f, 0x40, 0x9f, 0x55, 0x40, 0x4c, 0x4f, 0x48, 0x51, 0x44, 0x1b, 0x1f, 0x12, 0x44, 0x42, 0x4e, 0x4d, 0x43, 0x1d, 0x9f, 0x53, 0x47, 0x44, 0x51, 0x44, 0x1f, 0x56, 0x40, 0x52, 0x1f, 0x40, 0x1f, 0x4f, 0x40, 0x51, 0x53, 0x9f, 0x4e, 0x45, 0x1f, 0x47, 0x48, 0x4c, 0x1f, 0x1c, 0x1f, 0x40, 0x4d, 0x43, 0x1f, 0x8, 0x9f, 0x43, 0x48, 0x43, 0x4d, 0x5d, 0x53, 0x1f, 0x4a, 0x4d, 0x4e, 0x56, 0x1f, 0x47, 0x4e, 0x56, 0x9f, ], [0x4f, 0x4e, 0x53, 0x44, 0x4d, 0x53, 0x1f, 0x53, 0x47, 0x40, 0x53, 0x1f, 0x4f, 0x40, 0x51, 0x53, 0x9f, 0x4c, 0x48, 0x46, 0x47, 0x53, 0x1f, 0x41, 0x44, 0x1f, 0x1c, 0x1f, 0x53, 0x47, 0x40, 0x53, 0x9f, 0x53, 0x47, 0x48, 0x51, 0x52, 0x53, 0x44, 0x43, 0x1f, 0x45, 0x4e, 0x51, 0x1f, 0x4c, 0x58, 0x9f, 0x41, 0x4b, 0x4e, 0x4e, 0x43, 0x1b, 0x1f, 0x0, 0x4d, 0x43, 0x1f, 0x53, 0x47, 0x48, 0x51, 0x43, 0x1d, 0x9f, 0x8, 0x1f, 0x56, 0x40, 0x52, 0x9f, 0x54, 0x4d, 0x42, 0x4e, 0x4d, 0x43, 0x48, 0x53, 0x48, 0x4e, 0x4d, 0x40, 0x4b, 0x4b, 0x58, 0x9f, 0x40, 0x4d, 0x43, 0x1f, 0x48, 0x51, 0x51, 0x44, 0x55, 0x4e, 0x42, 0x40, 0x41, 0x4b, 0x58, 0x1f, 0x48, 0xcd, 0x4b, 0x4e, 0x55, 0x44, 0x1f, 0x56, 0x48, 0x53, 0x47, 0x1f, 0x47, 0x48, 0x4c, 0x1b, 0x9f, ], [0x1c, 0xc, 0x44, 0x58, 0x44, 0x51, 0x1f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, ]], +[[0x12, 0x40, 0x58, 0x1a, 0x1f, 0x8, 0x1f, 0x4b, 0x48, 0x4a, 0x44, 0x1f, 0x46, 0x51, 0x44, 0x44, 0x4d, 0x9f, 0x44, 0x46, 0x46, 0x52, 0x1f, 0x40, 0x4d, 0x43, 0x1f, 0x47, 0x40, 0x4c, 0x1a, 0x1f, 0x8, 0x9f, 0x43, 0x4e, 0x1a, 0x1f, 0x8, 0x1f, 0x4b, 0x48, 0x4a, 0x44, 0x1f, 0x53, 0x47, 0x44, 0x4c, 0x1d, 0x9f, 0x12, 0x40, 0x4c, 0x1c, 0x8, 0x1c, 0x40, 0x4c, 0x1a, 0x1f, 0x0, 0x4d, 0x43, 0x1f, 0x8, 0x9f, 0x56, 0x4e, 0x54, 0x4b, 0x43, 0x1f, 0x44, 0x40, 0x53, 0x1f, 0x53, 0x47, 0x44, 0x4c, 0x1f, 0x48, 0x4d, 0x9f, 0x40, 0x1f, 0x41, 0x4e, 0x40, 0x53, 0x1b, 0x1f, 0x0, 0x4d, 0x43, 0x1f, 0x8, 0x9f, 0x56, 0x4e, 0x54, 0x4b, 0x43, 0x1f, 0x44, 0x40, 0x53, 0x1f, 0x53, 0x47, 0x44, 0x4c, 0x9f, 0x56, 0x48, 0x53, 0x47, 0x1f, 0x40, 0x1f, 0x46, 0x4e, 0x40, 0x53, 0x1b, 0x1b, 0x1b, 0x1f, 0x0, 0x4d, 0xc3, ], [0x8, 0x1f, 0x56, 0x48, 0x4b, 0x4b, 0x1f, 0x44, 0x40, 0x53, 0x1f, 0x53, 0x47, 0x44, 0x4c, 0x1f, 0x48, 0xcd, 0x53, 0x47, 0x44, 0x1f, 0x51, 0x40, 0x48, 0x4d, 0x1b, 0x1f, 0x0, 0x4d, 0x43, 0x1f, 0x48, 0x4d, 0x9f, 0x53, 0x47, 0x44, 0x1f, 0x43, 0x40, 0x51, 0x4a, 0x1b, 0x1f, 0x0, 0x4d, 0x43, 0x1f, 0x4e, 0x4d, 0x1f, 0xc0, 0x53, 0x51, 0x40, 0x48, 0x4d, 0x1b, 0x1f, 0x0, 0x4d, 0x43, 0x1f, 0x48, 0x4d, 0x1f, 0x40, 0x9f, 0x42, 0x40, 0x51, 0x1b, 0x1f, 0x0, 0x4d, 0x43, 0x1f, 0x48, 0x4d, 0x1f, 0x40, 0x9f, 0x53, 0x51, 0x44, 0x44, 0x1b, 0x1f, 0x13, 0x47, 0x44, 0x58, 0x1f, 0x40, 0x51, 0x44, 0x1f, 0x52, 0x4e, 0x9f, 0x46, 0x4e, 0x4e, 0x43, 0x1d, 0x1f, 0x52, 0x4e, 0x1f, 0x46, 0x4e, 0x4e, 0x43, 0x1d, 0x1f, 0x58, 0x4e, 0xd4, 0x52, 0x44, 0x44, 0x1a, 0x1f, 0x12, 0x4e, 0x1f, 0x8, 0x1f, 0x56, 0x48, 0x4b, 0x4b, 0x1f, 0x44, 0x40, 0xd3, ], [0x53, 0x47, 0x44, 0x4c, 0x1f, 0x48, 0x4d, 0x1f, 0x40, 0x1f, 0x41, 0x4e, 0x57, 0x1b, 0x1f, 0x0, 0x4d, 0xc3, 0x8, 0x1f, 0x56, 0x48, 0x4b, 0x4b, 0x1f, 0x44, 0x40, 0x53, 0x1f, 0x53, 0x47, 0x44, 0x4c, 0x9f, 0x56, 0x48, 0x53, 0x47, 0x1f, 0x40, 0x1f, 0x45, 0x4e, 0x57, 0x1b, 0x1f, 0x0, 0x4d, 0x43, 0x1f, 0x8, 0x9f, 0x56, 0x48, 0x4b, 0x4b, 0x1f, 0x44, 0x40, 0x53, 0x1f, 0x53, 0x47, 0x44, 0x4c, 0x1f, 0x48, 0x4d, 0x1f, 0xc0, 0x47, 0x4e, 0x54, 0x52, 0x44, 0x1b, 0x1f, 0x0, 0x4d, 0x43, 0x1f, 0x8, 0x1f, 0x56, 0x48, 0x4b, 0x4b, 0x9f, 0x44, 0x40, 0x53, 0x1f, 0x53, 0x47, 0x44, 0x4c, 0x1f, 0x56, 0x48, 0x53, 0x47, 0x1f, 0x40, 0x9f, 0x4c, 0x4e, 0x54, 0x52, 0x44, 0x1b, 0x1f, 0x0, 0x4d, 0x43, 0x1f, 0x8, 0x1f, 0x56, 0x48, 0x4b, 0x4b, 0x9f, 0x44, 0x40, 0x53, 0x1f, 0x53, 0x47, 0x44, 0x4c, 0x1f, 0x47, 0x44, 0x51, 0x44, 0x1f, 0x40, 0x4d, 0x43, 0x9f, ], [0x53, 0x47, 0x44, 0x51, 0x44, 0x1b, 0x1f, 0x12, 0x40, 0x58, 0x1a, 0x1f, 0x8, 0x1f, 0x56, 0x48, 0x4b, 0xcb, 0x44, 0x40, 0x53, 0x1f, 0x53, 0x47, 0x44, 0x4c, 0x1f, 0x40, 0x4d, 0x58, 0x56, 0x47, 0x44, 0x51, 0x44, 0x9a, 0x8, 0x1f, 0x43, 0x4e, 0x1f, 0x52, 0x4e, 0x1f, 0x4b, 0x48, 0x4a, 0x44, 0x1f, 0x46, 0x51, 0x44, 0x44, 0xcd, 0x44, 0x46, 0x46, 0x52, 0x1f, 0x40, 0x4d, 0x43, 0x1f, 0x47, 0x40, 0x4c, 0x1a, 0x9f, 0x13, 0x47, 0x40, 0x4d, 0x4a, 0x1f, 0x58, 0x4e, 0x54, 0x1a, 0x1f, 0x13, 0x47, 0x40, 0x4d, 0x4a, 0x9f, 0x58, 0x4e, 0x54, 0x1d, 0x1f, 0x12, 0x40, 0x4c, 0x1c, 0x8, 0x1c, 0x40, 0x4c, 0x1a, 0x9f, 0x1c, 0x12, 0x44, 0x54, 0x52, 0x52, 0x1f, 0x9f, 0x9f, ]], +[[0x1, 0x54, 0x53, 0x1d, 0x1f, 0x52, 0x4e, 0x45, 0x53, 0x1a, 0x1f, 0x56, 0x47, 0x40, 0x53, 0x9f, 0x4b, 0x48, 0x46, 0x47, 0x53, 0x1f, 0x53, 0x47, 0x51, 0x4e, 0x54, 0x46, 0x47, 0x9f, 0x58, 0x4e, 0x4d, 0x43, 0x44, 0x51, 0x1f, 0x56, 0x48, 0x4d, 0x43, 0x4e, 0x56, 0x9f, 0x41, 0x51, 0x44, 0x40, 0x4a, 0x52, 0x1e, 0x1f, 0x8, 0x53, 0x1f, 0x48, 0x52, 0x1f, 0x53, 0x47, 0x44, 0x9f, 0x44, 0x40, 0x52, 0x53, 0x1d, 0x1f, 0x40, 0x4d, 0x43, 0x1f, 0x9, 0x54, 0x4b, 0x48, 0x44, 0x53, 0x9f, 0x48, 0x52, 0x1f, 0x53, 0x47, 0x44, 0x1f, 0x52, 0x54, 0x4d, 0x1b, 0x1f, 0x0, 0x51, 0x48, 0x52, 0x44, 0x9d, 0x45, 0x40, 0x48, 0x51, 0x1f, 0x52, 0x54, 0x4d, 0x1d, 0x1f, 0x40, 0x4d, 0x43, 0x1f, 0x4a, 0x48, 0x4b, 0xcb, 0x53, 0x47, 0x44, 0x1f, 0x44, 0x4d, 0x55, 0x48, 0x4e, 0x54, 0x52, 0x1f, 0x4c, 0x4e, 0x4e, 0x4d, 0x1d, 0x9f, ], [0x16, 0x47, 0x4e, 0x1f, 0x48, 0x52, 0x1f, 0x40, 0x4b, 0x51, 0x44, 0x40, 0x43, 0x58, 0x9f, 0x52, 0x48, 0x42, 0x4a, 0x1f, 0x40, 0x4d, 0x43, 0x1f, 0x4f, 0x40, 0x4b, 0x44, 0x1f, 0x56, 0x48, 0x53, 0xc7, 0x46, 0x51, 0x48, 0x44, 0x45, 0x1d, 0x1f, 0x13, 0x47, 0x40, 0x53, 0x1f, 0x53, 0x47, 0x4e, 0x54, 0x9f, 0x47, 0x44, 0x51, 0x1f, 0x4c, 0x40, 0x48, 0x43, 0x1f, 0x40, 0x51, 0x53, 0x1f, 0x45, 0x40, 0x51, 0x9f, 0x4c, 0x4e, 0x51, 0x44, 0x1f, 0x45, 0x40, 0x48, 0x51, 0x1f, 0x53, 0x47, 0x40, 0x4d, 0x9f, 0x52, 0x47, 0x44, 0x1b, 0x1f, 0x1, 0x44, 0x1f, 0x4d, 0x4e, 0x53, 0x1f, 0x47, 0x44, 0x51, 0x9f, 0x4c, 0x40, 0x48, 0x43, 0x1d, 0x1f, 0x52, 0x48, 0x4d, 0x42, 0x44, 0x1f, 0x52, 0x47, 0x44, 0x1f, 0x48, 0xd2, 0x44, 0x4d, 0x55, 0x48, 0x4e, 0x54, 0x52, 0x1b, 0x1f, 0x7, 0x44, 0x51, 0x9f, ], [0x55, 0x44, 0x52, 0x53, 0x40, 0x4b, 0x1f, 0x4b, 0x48, 0x55, 0x44, 0x51, 0x58, 0x1f, 0x48, 0x52, 0x9f, 0x41, 0x54, 0x53, 0x1f, 0x52, 0x48, 0x42, 0x4a, 0x1f, 0x40, 0x4d, 0x43, 0x1f, 0x46, 0x51, 0x44, 0x44, 0xcd, 0x0, 0x4d, 0x43, 0x1f, 0x4d, 0x4e, 0x4d, 0x44, 0x1f, 0x41, 0x54, 0x53, 0x1f, 0x45, 0x4e, 0x4e, 0x4b, 0xd2, 0x43, 0x4e, 0x1f, 0x56, 0x44, 0x40, 0x51, 0x1f, 0x48, 0x53, 0x1b, 0x1f, 0x42, 0x40, 0x52, 0x53, 0x9f, 0x48, 0x53, 0x1f, 0x4e, 0x45, 0x45, 0x1b, 0x1f, 0x8, 0x53, 0x1f, 0x48, 0x52, 0x1f, 0x4c, 0x58, 0x9f, 0x4b, 0x40, 0x43, 0x58, 0x1d, 0x1f, 0xe, 0x1d, 0x1f, 0x48, 0x53, 0x1f, 0x48, 0x52, 0x1f, 0x4c, 0x58, 0x9f, 0x4b, 0x4e, 0x55, 0x44, 0x1a, 0x1f, 0xe, 0x1d, 0x1f, 0x53, 0x47, 0x40, 0x53, 0x1f, 0x52, 0x47, 0x44, 0x9f, 0x4a, 0x4d, 0x44, 0x56, 0x1f, 0x52, 0x47, 0x44, 0x1f, 0x56, 0x44, 0x51, 0x44, 0x1a, 0x9f, ], [0x1c, 0x12, 0x47, 0x40, 0x4a, 0x44, 0x52, 0x4f, 0x44, 0x40, 0x51, 0x44, 0x1f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, ]], +[[0x8, 0x53, 0x1f, 0x56, 0x40, 0x52, 0x1f, 0x53, 0x47, 0x44, 0x1f, 0x41, 0x44, 0x52, 0x53, 0x1f, 0x4e, 0xc5, 0x53, 0x48, 0x4c, 0x44, 0x52, 0x1d, 0x1f, 0x48, 0x53, 0x1f, 0x56, 0x40, 0x52, 0x1f, 0x53, 0x47, 0x44, 0x9f, 0x56, 0x4e, 0x51, 0x52, 0x53, 0x1f, 0x4e, 0x45, 0x1f, 0x53, 0x48, 0x4c, 0x44, 0x52, 0x1d, 0x1f, 0x48, 0xd3, 0x56, 0x40, 0x52, 0x1f, 0x53, 0x47, 0x44, 0x1f, 0x40, 0x46, 0x44, 0x1f, 0x4e, 0x45, 0x9f, 0x56, 0x48, 0x52, 0x43, 0x4e, 0x4c, 0x1d, 0x1f, 0x48, 0x53, 0x1f, 0x56, 0x40, 0x52, 0x1f, 0x53, 0x47, 0xc4, 0x40, 0x46, 0x44, 0x1f, 0x4e, 0x45, 0x9f, 0x45, 0x4e, 0x4e, 0x4b, 0x48, 0x52, 0x47, 0x4d, 0x44, 0x52, 0x52, 0x1d, 0x1f, 0x48, 0x53, 0x9f, 0x56, 0x40, 0x52, 0x1f, 0x53, 0x47, 0x44, 0x1f, 0x44, 0x4f, 0x4e, 0x42, 0x47, 0x1f, 0x4e, 0x45, 0x9f, ], [0x41, 0x44, 0x4b, 0x48, 0x44, 0x45, 0x1d, 0x1f, 0x48, 0x53, 0x1f, 0x56, 0x40, 0x52, 0x1f, 0x53, 0x47, 0xc4, 0x44, 0x4f, 0x4e, 0x42, 0x47, 0x1f, 0x4e, 0x45, 0x9f, 0x48, 0x4d, 0x42, 0x51, 0x44, 0x43, 0x54, 0x4b, 0x48, 0x53, 0x58, 0x1d, 0x1f, 0x48, 0x53, 0x9f, 0x56, 0x40, 0x52, 0x1f, 0x53, 0x47, 0x44, 0x1f, 0x52, 0x44, 0x40, 0x52, 0x4e, 0x4d, 0x1f, 0x4e, 0x45, 0x9f, 0x4b, 0x48, 0x46, 0x47, 0x53, 0x1d, 0x1f, 0x48, 0x53, 0x1f, 0x56, 0x40, 0x52, 0x1f, 0x53, 0x47, 0x44, 0x9f, 0x52, 0x44, 0x40, 0x52, 0x4e, 0x4d, 0x1f, 0x4e, 0x45, 0x9f, 0x43, 0x40, 0x51, 0x4a, 0x4d, 0x44, 0x52, 0x52, 0x1d, 0x1f, 0x48, 0x53, 0x1f, 0x56, 0x40, 0x52, 0x9f, 0x53, 0x47, 0x44, 0x1f, 0x52, 0x4f, 0x51, 0x48, 0x4d, 0x46, 0x1f, 0x4e, 0x45, 0x9f, ], [0x47, 0x4e, 0x4f, 0x44, 0x1d, 0x1f, 0x48, 0x53, 0x1f, 0x56, 0x40, 0x52, 0x1f, 0x53, 0x47, 0x44, 0x9f, 0x56, 0x48, 0x4d, 0x53, 0x44, 0x51, 0x1f, 0x4e, 0x45, 0x1f, 0x43, 0x44, 0x52, 0x4f, 0x40, 0x48, 0x51, 0x9b, 0x1c, 0x3, 0x48, 0x42, 0x4a, 0x44, 0x4d, 0x52, 0x1f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, ]], +[[0xc, 0x40, 0x51, 0x4b, 0x44, 0x58, 0x1f, 0x56, 0x40, 0x52, 0x1f, 0x43, 0x44, 0x40, 0x43, 0x1d, 0x9f, 0x53, 0x4e, 0x1f, 0x41, 0x44, 0x46, 0x48, 0x4d, 0x1f, 0x56, 0x48, 0x53, 0x47, 0x1b, 0x9f, 0x13, 0x47, 0x44, 0x51, 0x44, 0x1f, 0x48, 0x52, 0x1f, 0x4d, 0x4e, 0x1f, 0x43, 0x4e, 0x54, 0x41, 0x53, 0x9f, 0x56, 0x47, 0x40, 0x53, 0x44, 0x55, 0x44, 0x51, 0x1d, 0x1f, 0x40, 0x41, 0x4e, 0x54, 0x53, 0x9f, 0x53, 0x47, 0x40, 0x53, 0x1b, 0x1f, 0x13, 0x47, 0x44, 0x1f, 0x51, 0x44, 0x46, 0x48, 0x52, 0x53, 0x44, 0xd1, 0x4e, 0x45, 0x1f, 0x47, 0x48, 0x52, 0x1f, 0x41, 0x54, 0x51, 0x48, 0x40, 0x4b, 0x1f, 0x56, 0x40, 0x52, 0x9f, 0x52, 0x48, 0x46, 0x4d, 0x44, 0x43, 0x1f, 0x41, 0x58, 0x1f, 0x53, 0x47, 0x44, 0x9f, 0x42, 0x4b, 0x44, 0x51, 0x46, 0x58, 0x4c, 0x40, 0x4d, 0x1d, 0x1f, 0x53, 0x47, 0x44, 0x9f, ], [0x42, 0x4b, 0x44, 0x51, 0x4a, 0x1d, 0x1f, 0x53, 0x47, 0x44, 0x9f, 0x54, 0x4d, 0x43, 0x44, 0x51, 0x53, 0x40, 0x4a, 0x44, 0x51, 0x1d, 0x1f, 0x40, 0x4d, 0x43, 0x9f, 0x53, 0x47, 0x44, 0x1f, 0x42, 0x47, 0x48, 0x44, 0x45, 0x1f, 0x4c, 0x4e, 0x54, 0x51, 0x4d, 0x44, 0x51, 0x9b, 0x12, 0x42, 0x51, 0x4e, 0x4e, 0x46, 0x44, 0x1f, 0x52, 0x48, 0x46, 0x4d, 0x44, 0x43, 0x1f, 0x48, 0x53, 0x9b, 0x40, 0x4d, 0x43, 0x1f, 0x12, 0x42, 0x51, 0x4e, 0x4e, 0x46, 0x44, 0x5d, 0x52, 0x1f, 0x4d, 0x40, 0x4c, 0xc4, 0x56, 0x40, 0x52, 0x1f, 0x46, 0x4e, 0x4e, 0x43, 0x1f, 0x54, 0x4f, 0x4e, 0x4d, 0x9f, 0x5d, 0x42, 0x47, 0x40, 0x4d, 0x46, 0x44, 0x1d, 0x1f, 0x45, 0x4e, 0x51, 0x9f, 0x40, 0x4d, 0x58, 0x53, 0x47, 0x48, 0x4d, 0x46, 0x1f, 0x47, 0x44, 0x1f, 0x42, 0x47, 0x4e, 0x52, 0x44, 0x9f, ], [0x53, 0x4e, 0x1f, 0x4f, 0x54, 0x53, 0x1f, 0x47, 0x48, 0x52, 0x1f, 0x47, 0x40, 0x4d, 0x43, 0x9f, 0x53, 0x4e, 0x1b, 0x1f, 0xe, 0x4b, 0x43, 0x1f, 0xc, 0x40, 0x51, 0x4b, 0x44, 0x58, 0x1f, 0x56, 0x40, 0xd2, 0x40, 0x52, 0x1f, 0x43, 0x44, 0x40, 0x43, 0x1f, 0x40, 0x52, 0x1f, 0x40, 0x9f, 0x43, 0x4e, 0x4e, 0x51, 0x1c, 0x4d, 0x40, 0x48, 0x4b, 0x1b, 0x9f, 0x1c, 0x3, 0x48, 0x42, 0x4a, 0x44, 0x4d, 0x52, 0x1f, 0x9f, 0x9f, 0x9f, 0x9f, ]], +[[0xc, 0x40, 0x4d, 0x58, 0x1f, 0x53, 0x47, 0x40, 0x53, 0x1f, 0x4b, 0x48, 0x55, 0x44, 0x9f, 0x43, 0x44, 0x52, 0x44, 0x51, 0x55, 0x44, 0x1f, 0x43, 0x44, 0x40, 0x53, 0x47, 0x1b, 0x1f, 0x0, 0x4d, 0xc3, 0x52, 0x4e, 0x4c, 0x44, 0x1f, 0x53, 0x47, 0x40, 0x53, 0x1f, 0x43, 0x48, 0x44, 0x9f, 0x43, 0x44, 0x52, 0x44, 0x51, 0x55, 0x44, 0x1f, 0x4b, 0x48, 0x45, 0x44, 0x1b, 0x1f, 0x2, 0x40, 0x4d, 0x9f, 0x58, 0x4e, 0x54, 0x1f, 0x46, 0x48, 0x55, 0x44, 0x1f, 0x48, 0x53, 0x1f, 0x53, 0x4e, 0x9f, 0x53, 0x47, 0x44, 0x4c, 0x1e, 0x1f, 0x13, 0x47, 0x44, 0x4d, 0x1f, 0x43, 0x4e, 0x1f, 0x4d, 0x4e, 0x53, 0x9f, 0x41, 0x44, 0x1f, 0x53, 0x4e, 0x4e, 0x1f, 0x44, 0x40, 0x46, 0x44, 0x51, 0x1f, 0x53, 0x4e, 0x9f, 0x43, 0x44, 0x40, 0x4b, 0x1f, 0x4e, 0x54, 0x53, 0x1f, 0x43, 0x44, 0x40, 0x53, 0x47, 0x1f, 0x48, 0x4d, 0x9f, ], [0x49, 0x54, 0x43, 0x46, 0x44, 0x4c, 0x44, 0x4d, 0x53, 0x1b, 0x1f, 0x5, 0x4e, 0x51, 0x9f, 0x44, 0x55, 0x44, 0x4d, 0x1f, 0x53, 0x47, 0x44, 0x1f, 0x55, 0x44, 0x51, 0x58, 0x1f, 0x56, 0x48, 0x52, 0xc4, 0x42, 0x40, 0x4d, 0x4d, 0x4e, 0x53, 0x1f, 0x52, 0x44, 0x44, 0x1f, 0x40, 0x4b, 0x4b, 0x9f, 0x44, 0x4d, 0x43, 0x52, 0x1b, 0x1f, 0x1c, 0x13, 0x4e, 0x4b, 0x4a, 0x48, 0x44, 0x4d, 0x1f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, ]], +[[0x8, 0x53, 0x1f, 0x48, 0x52, 0x1f, 0x40, 0x1f, 0x53, 0x51, 0x54, 0x53, 0x47, 0x9f, 0x54, 0x4d, 0x48, 0x55, 0x44, 0x51, 0x52, 0x40, 0x4b, 0x4b, 0x58, 0x9f, 0x40, 0x42, 0x4a, 0x4d, 0x4e, 0x56, 0x4b, 0x44, 0x43, 0x46, 0x44, 0x43, 0x1d, 0x1f, 0x53, 0x47, 0x40, 0xd3, 0x40, 0x1f, 0x52, 0x48, 0x4d, 0x46, 0x4b, 0x44, 0x1f, 0x4c, 0x40, 0x4d, 0x1f, 0x48, 0x4d, 0x9f, 0x4f, 0x4e, 0x52, 0x52, 0x44, 0x52, 0x52, 0x48, 0x4e, 0x4d, 0x1f, 0x4e, 0x45, 0x1f, 0x40, 0x9f, 0x46, 0x4e, 0x4e, 0x43, 0x1f, 0x45, 0x4e, 0x51, 0x53, 0x54, 0x4d, 0x44, 0x1d, 0x1f, 0x4c, 0x54, 0x52, 0xd3, 0x41, 0x44, 0x1f, 0x48, 0x4d, 0x1f, 0x56, 0x40, 0x4d, 0x53, 0x1f, 0x4e, 0x45, 0x1f, 0x40, 0x9f, 0x56, 0x48, 0x45, 0x44, 0x1b, 0x1f, 0x1c, 0x0, 0x54, 0x52, 0x53, 0x44, 0x4d, 0x1f, 0x9f, ]], +[[0x13, 0x47, 0x44, 0x1f, 0x53, 0x51, 0x54, 0x53, 0x47, 0x1f, 0x40, 0x4b, 0x56, 0x40, 0x58, 0x52, 0x9f, 0x42, 0x40, 0x51, 0x51, 0x48, 0x44, 0x52, 0x1f, 0x53, 0x47, 0x44, 0x9f, 0x40, 0x4c, 0x41, 0x48, 0x46, 0x54, 0x48, 0x53, 0x58, 0x1f, 0x4e, 0x45, 0x1f, 0x53, 0x47, 0x44, 0x9f, 0x56, 0x4e, 0x51, 0x43, 0x52, 0x1f, 0x54, 0x52, 0x44, 0x43, 0x1f, 0x53, 0x4e, 0x9f, 0x44, 0x57, 0x4f, 0x51, 0x44, 0x52, 0x52, 0x1f, 0x48, 0x53, 0x1b, 0x9f, 0x1c, 0x7, 0x44, 0x51, 0x41, 0x44, 0x51, 0x53, 0x1f, 0x9f, 0x9f, 0x9f, ]], +[[0x5d, 0x8, 0x1f, 0x43, 0x40, 0x51, 0x44, 0x52, 0x40, 0x58, 0x1f, 0x58, 0x4e, 0x54, 0x9f, 0x47, 0x40, 0x55, 0x44, 0x4d, 0x5d, 0x53, 0x1f, 0x47, 0x40, 0x43, 0x1f, 0x4c, 0x54, 0x42, 0x47, 0x9f, 0x4f, 0x51, 0x40, 0x42, 0x53, 0x48, 0x42, 0x44, 0x1d, 0x5d, 0x1f, 0x52, 0x40, 0x48, 0x43, 0x9f, 0x53, 0x47, 0x44, 0x1f, 0x10, 0x54, 0x44, 0x44, 0x4d, 0x1b, 0x1f, 0x5d, 0x16, 0x47, 0x44, 0x4d, 0x1f, 0x88, 0x56, 0x40, 0x52, 0x1f, 0x58, 0x4e, 0x54, 0x51, 0x1f, 0x40, 0x46, 0x44, 0x1d, 0x1f, 0x8, 0x9f, 0x40, 0x4b, 0x56, 0x40, 0x58, 0x52, 0x1f, 0x43, 0x48, 0x43, 0x1f, 0x48, 0x53, 0x1f, 0x45, 0x4e, 0x51, 0x9f, 0x47, 0x40, 0x4b, 0x45, 0x1c, 0x40, 0x4d, 0x1c, 0x47, 0x4e, 0x54, 0x51, 0x1f, 0x40, 0x9f, 0x43, 0x40, 0x58, 0x1b, 0x1f, 0x16, 0x47, 0x58, 0x1d, 0x9f, ], [0x52, 0x4e, 0x4c, 0x44, 0x53, 0x48, 0x4c, 0x44, 0x52, 0x1f, 0x8, 0x5d, 0x55, 0x44, 0x9f, 0x41, 0x44, 0x4b, 0x48, 0x44, 0x55, 0x44, 0x43, 0x1f, 0x40, 0x52, 0x1f, 0x4c, 0x40, 0x4d, 0x58, 0x9f, 0x40, 0x52, 0x1f, 0x52, 0x48, 0x57, 0x1f, 0x48, 0x4c, 0x4f, 0x4e, 0x52, 0x52, 0x48, 0x41, 0x4b, 0x44, 0x9f, 0x53, 0x47, 0x48, 0x4d, 0x46, 0x52, 0x1f, 0x41, 0x44, 0x45, 0x4e, 0x51, 0x44, 0x9f, 0x41, 0x51, 0x44, 0x40, 0x4a, 0x45, 0x40, 0x52, 0x53, 0x1b, 0x5d, 0x9f, 0x1c, 0x2, 0x40, 0x51, 0x51, 0x4e, 0x4b, 0x4b, 0x1f, 0x9f, 0x9f, 0x9f, ]], +[[0xb, 0x48, 0x45, 0x44, 0x1d, 0x1f, 0x56, 0x48, 0x53, 0x47, 0x1f, 0x48, 0x53, 0x52, 0x9f, 0x51, 0x54, 0x4b, 0x44, 0x52, 0x1d, 0x1f, 0x48, 0x53, 0x52, 0x9f, 0x4e, 0x41, 0x4b, 0x48, 0x46, 0x40, 0x53, 0x48, 0x4e, 0x4d, 0x52, 0x1d, 0x1f, 0x40, 0x4d, 0x43, 0x9f, 0x48, 0x53, 0x52, 0x1f, 0x45, 0x51, 0x44, 0x44, 0x43, 0x4e, 0x4c, 0x52, 0x1d, 0x1f, 0x48, 0x52, 0x9f, 0x4b, 0x48, 0x4a, 0x44, 0x1f, 0x40, 0x1f, 0x52, 0x4e, 0x4d, 0x4d, 0x44, 0x53, 0x1b, 0x9f, 0x18, 0x4e, 0x54, 0x5d, 0x51, 0x44, 0x1f, 0x46, 0x48, 0x55, 0x44, 0x4d, 0x1f, 0x53, 0x47, 0x44, 0x9f, 0x45, 0x4e, 0x51, 0x4c, 0x1d, 0x1f, 0x41, 0x54, 0x53, 0x1f, 0x58, 0x4e, 0x54, 0x1f, 0x47, 0x40, 0x55, 0xc4, 0x53, 0x4e, 0x1f, 0x56, 0x51, 0x48, 0x53, 0x44, 0x1f, 0x53, 0x47, 0x44, 0x9f, ], [0x52, 0x4e, 0x4d, 0x4d, 0x44, 0x53, 0x1f, 0x58, 0x4e, 0x54, 0x51, 0x52, 0x44, 0x4b, 0x45, 0x1b, 0x9f, 0x1c, 0xb, 0x5d, 0x4, 0x4d, 0x46, 0x4b, 0x44, 0x1f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, ]], +[[0xb, 0x48, 0x4a, 0x44, 0x1f, 0x53, 0x47, 0x44, 0x1f, 0x4c, 0x4e, 0x4e, 0x4d, 0x1f, 0x4e, 0x55, 0x44, 0xd1, 0x53, 0x47, 0x44, 0x1f, 0x43, 0x40, 0x58, 0x1f, 0x4c, 0x58, 0x1f, 0x46, 0x44, 0x4d, 0x48, 0x54, 0x52, 0x9f, 0x40, 0x4d, 0x43, 0x1f, 0x41, 0x51, 0x40, 0x56, 0x4d, 0x1f, 0x40, 0x51, 0x44, 0x1f, 0x4b, 0x4e, 0x52, 0xd3, 0x4e, 0x4d, 0x1f, 0x53, 0x47, 0x44, 0x52, 0x44, 0x1f, 0x45, 0x4e, 0x4e, 0x4b, 0x52, 0x9f, 0x1c, 0x7, 0x40, 0x48, 0x4a, 0x54, 0x1f, 0x9f, 0x9f, 0x9f, 0x9f, ]], +[[0x8, 0x4d, 0x1f, 0x40, 0x1f, 0x47, 0x4e, 0x4b, 0x44, 0x1f, 0x48, 0x4d, 0x1f, 0x53, 0x47, 0x44, 0x9f, 0x46, 0x51, 0x4e, 0x54, 0x4d, 0x43, 0x1f, 0x53, 0x47, 0x44, 0x51, 0x44, 0x1f, 0x4b, 0x48, 0x55, 0x44, 0xc3, 0x40, 0x1f, 0x47, 0x4e, 0x41, 0x41, 0x48, 0x53, 0x1b, 0x1f, 0xd, 0x4e, 0x53, 0x1f, 0x40, 0x9f, 0x4d, 0x40, 0x52, 0x53, 0x58, 0x1d, 0x1f, 0x43, 0x48, 0x51, 0x53, 0x58, 0x1d, 0x1f, 0x56, 0x44, 0x53, 0x9f, 0x47, 0x4e, 0x4b, 0x44, 0x1d, 0x1f, 0x45, 0x48, 0x4b, 0x4b, 0x44, 0x43, 0x1f, 0x56, 0x48, 0x53, 0x47, 0x9f, 0x53, 0x47, 0x44, 0x1f, 0x44, 0x4d, 0x43, 0x52, 0x1f, 0x4e, 0x45, 0x1f, 0x56, 0x4e, 0x51, 0x4c, 0x52, 0x9f, 0x40, 0x4d, 0x43, 0x1f, 0x40, 0x4d, 0x1f, 0x4e, 0x4e, 0x59, 0x58, 0x1f, 0x52, 0x4c, 0x44, 0x4b, 0x4b, 0x9d, 0x4d, 0x4e, 0x51, 0x1f, 0x58, 0x44, 0x53, 0x1f, 0x40, 0x1f, 0x43, 0x51, 0x58, 0x1d, 0x9f, ], [0x41, 0x40, 0x51, 0x44, 0x1d, 0x1f, 0x52, 0x40, 0x4d, 0x43, 0x58, 0x1f, 0x47, 0x4e, 0x4b, 0x44, 0x9f, 0x56, 0x48, 0x53, 0x47, 0x1f, 0x4d, 0x4e, 0x53, 0x47, 0x48, 0x4d, 0x46, 0x1f, 0x48, 0x4d, 0x1f, 0x48, 0xd3, 0x53, 0x4e, 0x1f, 0x52, 0x48, 0x53, 0x1f, 0x43, 0x4e, 0x56, 0x4d, 0x1f, 0x4e, 0x4d, 0x1f, 0x4e, 0x51, 0x9f, 0x53, 0x4e, 0x1f, 0x44, 0x40, 0x53, 0x1b, 0x1f, 0x48, 0x53, 0x1f, 0x56, 0x40, 0x52, 0x1f, 0x40, 0x9f, 0x47, 0x4e, 0x41, 0x41, 0x48, 0x53, 0x1c, 0x47, 0x4e, 0x4b, 0x44, 0x1d, 0x1f, 0x40, 0x4d, 0x43, 0x9f, 0x53, 0x47, 0x40, 0x53, 0x1f, 0x4c, 0x44, 0x40, 0x4d, 0x52, 0x9f, 0x42, 0x4e, 0x4c, 0x45, 0x4e, 0x51, 0x53, 0x1b, 0x1f, 0x1c, 0x13, 0x4e, 0x4b, 0x4a, 0x48, 0x44, 0x4d, 0x1f, 0x9f, 0x9f, ]], +[[0x5d, 0x6, 0x4e, 0x4e, 0x43, 0x1f, 0xc, 0x4e, 0x51, 0x4d, 0x48, 0x4d, 0x46, 0x1a, 0x5d, 0x9f, 0x52, 0x40, 0x48, 0x43, 0x1f, 0x1, 0x48, 0x4b, 0x41, 0x4e, 0x1d, 0x1f, 0x40, 0x4d, 0x43, 0x1f, 0x47, 0xc4, 0x4c, 0x44, 0x40, 0x4d, 0x53, 0x1f, 0x48, 0x53, 0x1b, 0x1f, 0x13, 0x47, 0x44, 0x1f, 0x52, 0x54, 0x4d, 0x9f, 0x56, 0x40, 0x52, 0x1f, 0x52, 0x47, 0x48, 0x4d, 0x48, 0x4d, 0x46, 0x1d, 0x1f, 0x40, 0x4d, 0x43, 0x9f, 0x53, 0x47, 0x44, 0x1f, 0x46, 0x51, 0x40, 0x52, 0x52, 0x1f, 0x56, 0x40, 0x52, 0x1f, 0x55, 0x44, 0x51, 0xd8, 0x46, 0x51, 0x44, 0x44, 0x4d, 0x1b, 0x1f, 0x1, 0x54, 0x53, 0x1f, 0x6, 0x40, 0x4d, 0x43, 0x40, 0x4b, 0xc5, 0x4b, 0x4e, 0x4e, 0x4a, 0x44, 0x43, 0x1f, 0x40, 0x53, 0x1f, 0x47, 0x48, 0x4c, 0x1f, 0x45, 0x51, 0x4e, 0xcc, 0x54, 0x4d, 0x43, 0x44, 0x51, 0x1f, 0x4b, 0x4e, 0x4d, 0x46, 0x1f, 0x41, 0x54, 0x52, 0x47, 0x58, 0x9f, ], [0x44, 0x58, 0x44, 0x41, 0x51, 0x4e, 0x56, 0x52, 0x1f, 0x53, 0x47, 0x40, 0x53, 0x9f, 0x52, 0x53, 0x54, 0x42, 0x4a, 0x1f, 0x4e, 0x54, 0x53, 0x1f, 0x45, 0x54, 0x51, 0x53, 0x47, 0x44, 0x51, 0x9f, 0x53, 0x47, 0x40, 0x4d, 0x1f, 0x53, 0x47, 0x44, 0x1f, 0x41, 0x51, 0x48, 0x4c, 0x1f, 0x4e, 0x45, 0x9f, 0x47, 0x48, 0x52, 0x1f, 0x52, 0x47, 0x40, 0x43, 0x58, 0x1f, 0x47, 0x40, 0x53, 0x1b, 0x9f, 0x5d, 0x16, 0x47, 0x40, 0x53, 0x1f, 0x43, 0x4e, 0x1f, 0x58, 0x4e, 0x54, 0x9f, 0x4c, 0x44, 0x40, 0x4d, 0x1e, 0x5d, 0x1f, 0x47, 0x44, 0x1f, 0x52, 0x40, 0x48, 0x43, 0x1b, 0x9f, 0x5d, 0x3, 0x4e, 0x1f, 0x58, 0x4e, 0x54, 0x1f, 0x56, 0x48, 0x52, 0x47, 0x1f, 0x4c, 0x44, 0x1f, 0x40, 0x9f, 0x46, 0x4e, 0x4e, 0x43, 0x1f, 0x4c, 0x4e, 0x51, 0x4d, 0x48, 0x4d, 0x46, 0x1d, 0x1f, 0x4e, 0x51, 0x9f, ], [0x4c, 0x44, 0x40, 0x4d, 0x1f, 0x53, 0x47, 0x40, 0x53, 0x1f, 0x48, 0x53, 0x1f, 0x48, 0x52, 0x1f, 0x40, 0x9f, 0x46, 0x4e, 0x4e, 0x43, 0x1f, 0x4c, 0x4e, 0x51, 0x4d, 0x48, 0x4d, 0x46, 0x9f, 0x56, 0x47, 0x44, 0x53, 0x47, 0x44, 0x51, 0x1f, 0x8, 0x1f, 0x56, 0x40, 0x4d, 0x53, 0x1f, 0x48, 0x53, 0x9f, 0x4e, 0x51, 0x1f, 0x4d, 0x4e, 0x53, 0x1d, 0x1f, 0x4e, 0x51, 0x1f, 0x53, 0x47, 0x40, 0x53, 0x9f, 0x58, 0x4e, 0x54, 0x1f, 0x45, 0x44, 0x44, 0x4b, 0x1f, 0x46, 0x4e, 0x4e, 0x43, 0x1f, 0x53, 0x47, 0x48, 0xd2, 0x4c, 0x4e, 0x51, 0x4d, 0x48, 0x4d, 0x46, 0x1d, 0x1f, 0x4e, 0x51, 0x1f, 0x53, 0x47, 0x40, 0x53, 0x9f, 0x48, 0x53, 0x1f, 0x48, 0x52, 0x1f, 0x40, 0x1f, 0x4c, 0x4e, 0x51, 0x4d, 0x48, 0x4d, 0x46, 0x1f, 0x53, 0xce, 0x41, 0x44, 0x1f, 0x46, 0x4e, 0x4e, 0x43, 0x1f, 0x4e, 0x4d, 0x1e, 0x5d, 0x1f, 0x5d, 0x0, 0x4b, 0x4b, 0x9f, ], [0x4e, 0x45, 0x1f, 0x53, 0x47, 0x44, 0x4c, 0x1f, 0x40, 0x53, 0x1f, 0x4e, 0x4d, 0x42, 0x44, 0x1d, 0x5d, 0x9f, 0x52, 0x40, 0x48, 0x43, 0x1f, 0x1, 0x48, 0x4b, 0x41, 0x4e, 0x1b, 0x9f, 0x1c, 0x13, 0x4e, 0x4b, 0x4a, 0x48, 0x44, 0x4d, 0x1f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, ]], +[[0x5d, 0x6, 0x4e, 0x4e, 0x43, 0x1f, 0x4c, 0x4e, 0x51, 0x4d, 0x48, 0x4d, 0x46, 0x1a, 0x5d, 0x1f, 0x47, 0xc4, 0x52, 0x40, 0x48, 0x43, 0x1f, 0x40, 0x53, 0x1f, 0x4b, 0x40, 0x52, 0x53, 0x1b, 0x1f, 0x5d, 0x16, 0x44, 0x9f, 0x43, 0x4e, 0x4d, 0x5d, 0x53, 0x1f, 0x56, 0x40, 0x4d, 0x53, 0x1f, 0x40, 0x4d, 0x58, 0x9f, 0x40, 0x43, 0x55, 0x44, 0x4d, 0x53, 0x54, 0x51, 0x44, 0x52, 0x1f, 0x47, 0x44, 0x51, 0x44, 0x1d, 0x9f, 0x53, 0x47, 0x40, 0x4d, 0x4a, 0x1f, 0x58, 0x4e, 0x54, 0x1a, 0x1f, 0x18, 0x4e, 0x54, 0x9f, 0x4c, 0x48, 0x46, 0x47, 0x53, 0x1f, 0x53, 0x51, 0x58, 0x1f, 0x4e, 0x55, 0x44, 0x51, 0x1f, 0x13, 0x47, 0xc4, 0x7, 0x48, 0x4b, 0x4b, 0x1f, 0x4e, 0x51, 0x1f, 0x40, 0x42, 0x51, 0x4e, 0x52, 0x52, 0x1f, 0x13, 0x47, 0xc4, 0x16, 0x40, 0x53, 0x44, 0x51, 0x1b, 0x5d, 0x1f, 0x1, 0x58, 0x1f, 0x53, 0x47, 0x48, 0x52, 0x1f, 0x47, 0xc4, ], [0x4c, 0x44, 0x40, 0x4d, 0x53, 0x1f, 0x53, 0x47, 0x40, 0x53, 0x1f, 0x53, 0x47, 0x44, 0x9f, 0x42, 0x4e, 0x4d, 0x55, 0x44, 0x51, 0x52, 0x40, 0x53, 0x48, 0x4e, 0x4d, 0x1f, 0x56, 0x40, 0x52, 0x9f, 0x40, 0x53, 0x1f, 0x40, 0x4d, 0x1f, 0x44, 0x4d, 0x43, 0x1b, 0x1f, 0x5d, 0x16, 0x47, 0x40, 0x53, 0x1f, 0xc0, 0x4b, 0x4e, 0x53, 0x1f, 0x4e, 0x45, 0x1f, 0x53, 0x47, 0x48, 0x4d, 0x46, 0x52, 0x1f, 0x58, 0x4e, 0x54, 0x9f, 0x43, 0x4e, 0x1f, 0x54, 0x52, 0x44, 0x1f, 0x6, 0x4e, 0x4e, 0x43, 0x9f, 0x4c, 0x4e, 0x51, 0x4d, 0x48, 0x4d, 0x46, 0x1f, 0x45, 0x4e, 0x51, 0x1a, 0x5d, 0x1f, 0x52, 0x40, 0x48, 0xc3, 0x6, 0x40, 0x4d, 0x43, 0x40, 0x4b, 0x45, 0x1b, 0x1f, 0x5d, 0xd, 0x4e, 0x56, 0x1f, 0x58, 0x4e, 0x54, 0x9f, 0x4c, 0x44, 0x40, 0x4d, 0x1f, 0x53, 0x47, 0x40, 0x53, 0x1f, 0x58, 0x4e, 0x54, 0x1f, 0x56, 0x40, 0x4d, 0xd3, ], [0x53, 0x4e, 0x1f, 0x46, 0x44, 0x53, 0x1f, 0x51, 0x48, 0x43, 0x1f, 0x4e, 0x45, 0x1f, 0x4c, 0x44, 0x1d, 0x9f, 0x40, 0x4d, 0x43, 0x1f, 0x53, 0x47, 0x40, 0x53, 0x1f, 0x48, 0x53, 0x1f, 0x56, 0x4e, 0x4d, 0x5d, 0x53, 0x9f, 0x41, 0x44, 0x1f, 0x46, 0x4e, 0x4e, 0x43, 0x1f, 0x53, 0x48, 0x4b, 0x4b, 0x1f, 0x8, 0x9f, 0x4c, 0x4e, 0x55, 0x44, 0x1f, 0x4e, 0x45, 0x45, 0x1b, 0x1b, 0x9f, 0x1c, 0x13, 0x4e, 0x4b, 0x4a, 0x48, 0x44, 0x4d, 0x1f, 0x9f, 0x9f, 0x9f, 0x9f, ]], +[[0x5d, 0x13, 0x47, 0x44, 0x51, 0x44, 0x1f, 0x48, 0x52, 0x1f, 0x4d, 0x4e, 0x53, 0x47, 0x48, 0x4d, 0x46, 0x9f, 0x4b, 0x48, 0x4a, 0x44, 0x1f, 0x4b, 0x4e, 0x4e, 0x4a, 0x48, 0x4d, 0x46, 0x1d, 0x1f, 0x48, 0x45, 0x9f, 0x58, 0x4e, 0x54, 0x1f, 0x56, 0x40, 0x4d, 0x53, 0x1f, 0x53, 0x4e, 0x1f, 0x45, 0x48, 0x4d, 0x43, 0x9f, 0x52, 0x4e, 0x4c, 0x44, 0x53, 0x47, 0x48, 0x4d, 0x46, 0x1b, 0x1f, 0x18, 0x4e, 0x54, 0x9f, 0x42, 0x44, 0x51, 0x53, 0x40, 0x48, 0x4d, 0x4b, 0x58, 0x1f, 0x54, 0x52, 0x54, 0x40, 0x4b, 0x4b, 0x58, 0x9f, 0x45, 0x48, 0x4d, 0x43, 0x1f, 0x52, 0x4e, 0x4c, 0x44, 0x53, 0x47, 0x48, 0x4d, 0x46, 0x1d, 0x1f, 0x48, 0xc5, 0x58, 0x4e, 0x54, 0x1f, 0x4b, 0x4e, 0x4e, 0x4a, 0x1d, 0x1f, 0x41, 0x54, 0x53, 0x1f, 0x48, 0x53, 0x9f, 0x48, 0x52, 0x1f, 0x4d, 0x4e, 0x53, 0x1f, 0x40, 0x4b, 0x56, 0x40, 0x58, 0x52, 0x9f, ], [0x50, 0x54, 0x48, 0x53, 0x44, 0x1f, 0x53, 0x47, 0x44, 0x9f, 0x52, 0x4e, 0x4c, 0x44, 0x53, 0x47, 0x48, 0x4d, 0x46, 0x1f, 0x58, 0x4e, 0x54, 0x1f, 0x56, 0x44, 0x51, 0xc4, 0x40, 0x45, 0x53, 0x44, 0x51, 0x1b, 0x5d, 0x1f, 0x1c, 0x13, 0x4e, 0x4b, 0x4a, 0x48, 0x44, 0x4d, 0x1f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, ]], +[[0x3, 0x51, 0x54, 0x4c, 0x4c, 0x44, 0x51, 0x1d, 0x1f, 0x41, 0x44, 0x40, 0x53, 0x1d, 0x1f, 0x40, 0x4d, 0xc3, 0x4f, 0x48, 0x4f, 0x44, 0x51, 0x1d, 0x1f, 0x41, 0x4b, 0x4e, 0x56, 0x1b, 0x9f, 0x7, 0x40, 0x51, 0x4f, 0x44, 0x51, 0x1d, 0x1f, 0x52, 0x53, 0x51, 0x48, 0x4a, 0x44, 0x1d, 0x9f, 0x40, 0x4d, 0x43, 0x1f, 0x52, 0x4e, 0x4b, 0x43, 0x48, 0x44, 0x51, 0x1d, 0x1f, 0x46, 0x4e, 0x1b, 0x9f, 0x5, 0x51, 0x44, 0x44, 0x1f, 0x53, 0x47, 0x44, 0x1f, 0x45, 0x4b, 0x40, 0x4c, 0x44, 0x1f, 0x40, 0x4d, 0xc3, 0x52, 0x44, 0x40, 0x51, 0x1f, 0x53, 0x47, 0x44, 0x1f, 0x46, 0x51, 0x40, 0x52, 0x52, 0x44, 0x52, 0x1d, 0x9f, 0x13, 0x48, 0x4b, 0x1f, 0x53, 0x47, 0x44, 0x1f, 0x43, 0x40, 0x56, 0x4d, 0x48, 0x4d, 0x46, 0x9f, 0x11, 0x44, 0x43, 0x1f, 0x12, 0x53, 0x40, 0x51, 0x1f, 0x4f, 0x40, 0x52, 0x52, 0x44, 0x52, 0x1b, 0x9f, ], [0x1c, 0xc, 0x42, 0x2, 0x40, 0x45, 0x45, 0x51, 0x44, 0x58, 0x1f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, ]], +[[0x13, 0x47, 0x44, 0x1f, 0x53, 0x44, 0x40, 0x51, 0x52, 0x1f, 0x8, 0x1f, 0x45, 0x44, 0x44, 0x4b, 0x9f, 0x53, 0x4e, 0x43, 0x40, 0x58, 0x1f, 0x8, 0x5d, 0x4b, 0x4b, 0x1f, 0x56, 0x40, 0x48, 0x53, 0x1f, 0x53, 0xce, 0x52, 0x47, 0x44, 0x43, 0x1f, 0x53, 0x4e, 0x4c, 0x4e, 0x51, 0x51, 0x4e, 0x56, 0x1b, 0x9f, 0x13, 0x47, 0x4e, 0x54, 0x46, 0x47, 0x1f, 0x8, 0x5d, 0x4b, 0x4b, 0x1f, 0x4d, 0x4e, 0x53, 0x9f, 0x52, 0x4b, 0x44, 0x44, 0x4f, 0x1f, 0x53, 0x47, 0x48, 0x52, 0x1f, 0x4d, 0x48, 0x46, 0x47, 0x53, 0x9f, 0xd, 0x4e, 0x51, 0x1f, 0x45, 0x48, 0x4d, 0x43, 0x1f, 0x52, 0x54, 0x51, 0x42, 0x44, 0x40, 0x52, 0x44, 0x9f, 0x45, 0x51, 0x4e, 0x4c, 0x1f, 0x52, 0x4e, 0x51, 0x51, 0x4e, 0x56, 0x1b, 0x1f, 0xc, 0x58, 0x9f, 0x44, 0x58, 0x44, 0x52, 0x1f, 0x4c, 0x54, 0x52, 0x53, 0x1f, 0x4a, 0x44, 0x44, 0x4f, 0x9f, ], [0x53, 0x47, 0x44, 0x48, 0x51, 0x1f, 0x52, 0x48, 0x46, 0x47, 0x53, 0x1b, 0x1f, 0x8, 0x9f, 0x43, 0x40, 0x51, 0x44, 0x1f, 0x4d, 0x4e, 0x53, 0x1f, 0x41, 0x44, 0x9f, 0x53, 0x44, 0x40, 0x51, 0x1c, 0x41, 0x4b, 0x48, 0x4d, 0x43, 0x44, 0x43, 0x1b, 0x1f, 0x8, 0x9f, 0x4c, 0x54, 0x52, 0x53, 0x1f, 0x41, 0x44, 0x1f, 0x45, 0x51, 0x44, 0x44, 0x1f, 0x53, 0x4e, 0x9f, 0x53, 0x40, 0x4b, 0x4a, 0x1f, 0xd, 0x4e, 0x53, 0x1f, 0x42, 0x47, 0x4e, 0x4a, 0x44, 0x43, 0x9f, 0x56, 0x48, 0x53, 0x47, 0x1f, 0x46, 0x51, 0x48, 0x44, 0x45, 0x1d, 0x9f, 0x42, 0x4b, 0x44, 0x40, 0x51, 0x1c, 0x4c, 0x48, 0x4d, 0x43, 0x44, 0x43, 0x1b, 0x1f, 0xc, 0x58, 0x9f, 0x4c, 0x4e, 0x54, 0x53, 0x47, 0x1f, 0x42, 0x40, 0x4d, 0x4d, 0x4e, 0x53, 0x9f, ], [0x41, 0x44, 0x53, 0x51, 0x40, 0x58, 0x1f, 0x13, 0x47, 0x44, 0x1f, 0x40, 0x4d, 0x46, 0x54, 0x48, 0x52, 0xc7, 0x53, 0x47, 0x40, 0x53, 0x1f, 0x8, 0x1f, 0x4a, 0x4d, 0x4e, 0x56, 0x1b, 0x1f, 0x18, 0x44, 0x52, 0x1d, 0x9f, 0x8, 0x5d, 0x4b, 0x4b, 0x1f, 0x4a, 0x44, 0x44, 0x4f, 0x1f, 0x4c, 0x58, 0x1f, 0x53, 0x44, 0x40, 0x51, 0xd2, 0x53, 0x48, 0x4b, 0x1f, 0x4b, 0x40, 0x53, 0x44, 0x51, 0x1b, 0x1f, 0x1, 0x54, 0x53, 0x1f, 0x4c, 0x58, 0x9f, 0x46, 0x51, 0x48, 0x44, 0x45, 0x1f, 0x56, 0x48, 0x4b, 0x4b, 0x1f, 0x4d, 0x44, 0x55, 0x44, 0x51, 0x9f, 0x46, 0x4e, 0x1b, 0x1f, 0x1c, 0xc, 0x42, 0x2, 0x40, 0x45, 0x45, 0x51, 0x44, 0x58, 0x1f, 0x9f, 0x9f, 0x9f, ]], +[[0x16, 0x47, 0x4e, 0x1f, 0x56, 0x48, 0x4b, 0x4b, 0x52, 0x1d, 0x1f, 0x2, 0x40, 0x4d, 0x1b, 0x9f, 0x16, 0x47, 0x4e, 0x1f, 0x53, 0x51, 0x48, 0x44, 0x52, 0x1d, 0x1f, 0x3, 0x4e, 0x44, 0x52, 0x1b, 0x9f, 0x16, 0x47, 0x4e, 0x1f, 0x4b, 0x4e, 0x55, 0x44, 0x52, 0x1d, 0x1f, 0xb, 0x48, 0x55, 0x44, 0x52, 0x1b, 0x9f, 0x1c, 0xc, 0x42, 0x2, 0x40, 0x45, 0x45, 0x51, 0x44, 0x58, 0x1f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, ]], +[[0x13, 0x47, 0x44, 0x1f, 0x4b, 0x48, 0x53, 0x53, 0x4b, 0x44, 0x1f, 0x50, 0x54, 0x44, 0x44, 0x4d, 0x9f, 0x40, 0x4b, 0x4b, 0x1f, 0x46, 0x4e, 0x4b, 0x43, 0x44, 0x4d, 0x1f, 0x5, 0x4b, 0x44, 0x56, 0x9f, 0x47, 0x48, 0x52, 0x52, 0x48, 0x4d, 0x46, 0x1f, 0x40, 0x53, 0x1f, 0x53, 0x47, 0x44, 0x9f, 0x52, 0x44, 0x40, 0x1b, 0x1f, 0x13, 0x4e, 0x1f, 0x52, 0x53, 0x4e, 0x4f, 0x1f, 0x44, 0x40, 0x42, 0x47, 0x9f, 0x56, 0x40, 0x55, 0x44, 0x1f, 0x7, 0x44, 0x51, 0x1f, 0x42, 0x4b, 0x54, 0x53, 0x42, 0x47, 0x1f, 0x53, 0xce, 0x52, 0x40, 0x55, 0x44, 0x1f, 0x12, 0x47, 0x44, 0x1f, 0x55, 0x44, 0x4d, 0x53, 0x54, 0x51, 0x44, 0x43, 0x9f, 0x41, 0x51, 0x40, 0x55, 0x44, 0x4b, 0x58, 0x1b, 0x1f, 0x0, 0x52, 0x1f, 0x52, 0x47, 0x44, 0x9f, 0x40, 0x53, 0x53, 0x40, 0x42, 0x4a, 0x44, 0x43, 0x1f, 0x53, 0x47, 0x44, 0x1f, 0x52, 0x44, 0x40, 0x9f, ], [0x48, 0x4d, 0x1f, 0x51, 0x40, 0x46, 0x44, 0x1f, 0x0, 0x9f, 0x47, 0x4e, 0x4b, 0x43, 0x44, 0x51, 0x4c, 0x40, 0x4d, 0x1f, 0x42, 0x40, 0x4c, 0x44, 0x9f, 0x4d, 0x48, 0x46, 0x47, 0x1f, 0x0, 0x4b, 0x4e, 0x4d, 0x46, 0x1f, 0x53, 0x47, 0x44, 0x9f, 0x52, 0x40, 0x4d, 0x43, 0x1f, 0x5, 0x48, 0x52, 0x47, 0x4d, 0x44, 0x53, 0x1f, 0x48, 0x4d, 0x9f, 0x47, 0x40, 0x4d, 0x43, 0x1f, 0x0, 0x4d, 0x43, 0x1f, 0x52, 0x40, 0x56, 0x1f, 0x53, 0x47, 0x44, 0x9f, 0x50, 0x54, 0x44, 0x44, 0x4d, 0x1f, 0x4c, 0x48, 0x43, 0x52, 0x4a, 0x58, 0x1b, 0x1f, 0x7, 0x44, 0x9f, 0x52, 0x53, 0x40, 0x51, 0x44, 0x43, 0x1f, 0x40, 0x53, 0x1f, 0x47, 0x44, 0x51, 0x1f, 0x48, 0x4d, 0x9f, 0x56, 0x4e, 0x4d, 0x43, 0x44, 0x51, 0x1f, 0x5, 0x4e, 0x51, 0x1f, 0x4e, 0x45, 0x53, 0x44, 0x4d, 0x9f, ], [0x47, 0x44, 0x5d, 0x43, 0x1f, 0x41, 0x44, 0x44, 0x4d, 0x1f, 0x53, 0x4e, 0x4b, 0x43, 0x9f, 0x13, 0x47, 0x40, 0x53, 0x1f, 0x52, 0x54, 0x42, 0x47, 0x1f, 0x40, 0x52, 0x1f, 0x52, 0x47, 0x44, 0x9f, 0x2, 0x4e, 0x54, 0x4b, 0x43, 0x1f, 0x4d, 0x44, 0x55, 0x44, 0x51, 0x1f, 0x41, 0x44, 0x1f, 0x16, 0x47, 0xce, 0x47, 0x4e, 0x55, 0x44, 0x51, 0x44, 0x43, 0x1f, 0x53, 0x47, 0x44, 0x51, 0x44, 0x1d, 0x9f, 0x41, 0x51, 0x48, 0x46, 0x47, 0x53, 0x1f, 0x46, 0x4e, 0x4b, 0x43, 0x1b, 0x1f, 0x7, 0x44, 0x9f, 0x52, 0x40, 0x56, 0x1f, 0x47, 0x44, 0x51, 0x1f, 0x4f, 0x4b, 0x48, 0x46, 0x47, 0x53, 0x1f, 0x40, 0x4d, 0xc3, 0x50, 0x54, 0x48, 0x42, 0x4a, 0x4b, 0x58, 0x1f, 0x7, 0x44, 0x1f, 0x4b, 0x4e, 0x4e, 0x4a, 0x44, 0x43, 0x9f, 0x54, 0x4f, 0x1f, 0x53, 0x47, 0x44, 0x1f, 0x42, 0x4b, 0x48, 0x45, 0x45, 0x1f, 0x47, 0x44, 0x9f, ], [0x45, 0x40, 0x42, 0x44, 0x43, 0x1f, 0x0, 0x4d, 0x43, 0x1f, 0x52, 0x40, 0x56, 0x1f, 0x40, 0x9f, 0x42, 0x40, 0x55, 0x44, 0x1f, 0x0, 0x41, 0x4e, 0x55, 0x44, 0x1f, 0x53, 0x47, 0x44, 0x9f, 0x56, 0x40, 0x55, 0x44, 0x1f, 0x8, 0x4d, 0x1f, 0x56, 0x47, 0x48, 0x42, 0x47, 0x1f, 0x47, 0x44, 0x51, 0x9f, 0x44, 0x46, 0x46, 0x52, 0x1f, 0x47, 0x44, 0x1f, 0x4f, 0x4b, 0x40, 0x42, 0x44, 0x43, 0x1b, 0x9f, 0x13, 0x47, 0x44, 0x1f, 0x4b, 0x48, 0x53, 0x53, 0x4b, 0x44, 0x1f, 0x50, 0x54, 0x44, 0x44, 0x4d, 0x9f, 0x40, 0x4b, 0x4b, 0x1f, 0x46, 0x4e, 0x4b, 0x43, 0x44, 0x4d, 0x1f, 0x14, 0x4f, 0x4e, 0x4d, 0x9f, 0x47, 0x48, 0x52, 0x1f, 0x52, 0x47, 0x4e, 0x54, 0x4b, 0x43, 0x44, 0x51, 0x1f, 0x52, 0x53, 0x4e, 0x4e, 0xc3, 0x7, 0x44, 0x51, 0x1f, 0x44, 0x58, 0x44, 0x52, 0x1f, 0x40, 0x4b, 0x4b, 0x1f, 0x41, 0x4b, 0x54, 0x44, 0x9f, ], [0x6, 0x4b, 0x4e, 0x56, 0x44, 0x43, 0x1f, 0x4e, 0x45, 0x1f, 0x47, 0x44, 0x51, 0x1f, 0x53, 0x51, 0x54, 0xc4, 0x14, 0x4d, 0x43, 0x58, 0x48, 0x4d, 0x46, 0x1f, 0x46, 0x51, 0x40, 0x53, 0x48, 0x53, 0x54, 0x43, 0x44, 0x9b, 0x1c, 0xc, 0x42, 0x2, 0x40, 0x45, 0x45, 0x51, 0x44, 0x58, 0x1f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, ]], +[[0x7, 0x40, 0x51, 0x4f, 0x44, 0x51, 0x1d, 0x1f, 0x53, 0x51, 0x44, 0x40, 0x53, 0x1f, 0x58, 0x4e, 0x54, 0xd1, 0x56, 0x4e, 0x51, 0x43, 0x52, 0x1f, 0x56, 0x48, 0x53, 0x47, 0x1f, 0x42, 0x40, 0x51, 0x44, 0x9f, 0x5, 0x4e, 0x51, 0x1f, 0x53, 0x47, 0x44, 0x58, 0x1f, 0x4c, 0x40, 0x58, 0x1f, 0x42, 0x40, 0x54, 0x52, 0xc4, 0x49, 0x4e, 0x58, 0x1f, 0x4e, 0x51, 0x1f, 0x43, 0x44, 0x52, 0x4f, 0x40, 0x48, 0x51, 0x9f, 0x12, 0x48, 0x4d, 0x46, 0x1f, 0x58, 0x4e, 0x54, 0x51, 0x1f, 0x52, 0x4e, 0x4d, 0x46, 0x52, 0x1f, 0x4e, 0xc5, 0x47, 0x44, 0x40, 0x4b, 0x53, 0x47, 0x1f, 0x40, 0x4d, 0x43, 0x1f, 0x4b, 0x4e, 0x55, 0x44, 0x1f, 0xe, 0xc5, 0x43, 0x51, 0x40, 0x46, 0x4e, 0x4d, 0x52, 0x1f, 0x45, 0x4b, 0x40, 0x4c, 0x48, 0x4d, 0x46, 0x9f, 0x45, 0x51, 0x4e, 0x4c, 0x1f, 0x40, 0x41, 0x4e, 0x55, 0x44, 0x1b, 0x9f, ], [0x1c, 0xc, 0x42, 0x2, 0x40, 0x45, 0x45, 0x51, 0x44, 0x58, 0x1f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, ]], +[[0x13, 0x47, 0x44, 0x51, 0x44, 0x1f, 0x56, 0x40, 0x52, 0x1f, 0x4e, 0x4d, 0x4b, 0x58, 0x1f, 0x4e, 0x4d, 0xc4, 0x42, 0x40, 0x53, 0x42, 0x47, 0x1f, 0x40, 0x4d, 0x43, 0x1f, 0x53, 0x47, 0x40, 0x53, 0x1f, 0x56, 0x40, 0xd2, 0x2, 0x40, 0x53, 0x42, 0x47, 0x1c, 0x24, 0x24, 0x1d, 0x1f, 0x56, 0x47, 0x48, 0x42, 0x47, 0x9f, 0x52, 0x4f, 0x44, 0x42, 0x48, 0x45, 0x48, 0x44, 0x43, 0x1f, 0x53, 0x47, 0x40, 0x53, 0x1f, 0x40, 0x9f, 0x42, 0x4e, 0x4d, 0x42, 0x44, 0x51, 0x4d, 0x1f, 0x45, 0x4e, 0x51, 0x1f, 0x4e, 0x4d, 0x44, 0x5d, 0x52, 0x9f, 0x52, 0x40, 0x45, 0x44, 0x53, 0x58, 0x1f, 0x48, 0x4d, 0x1f, 0x53, 0x47, 0x44, 0x1f, 0x45, 0x40, 0x42, 0xc4, 0x4e, 0x45, 0x1f, 0x43, 0x40, 0x4d, 0x46, 0x44, 0x51, 0x52, 0x1f, 0x53, 0x47, 0x40, 0x53, 0x9f, 0x56, 0x44, 0x51, 0x44, 0x1f, 0x51, 0x44, 0x40, 0x4b, 0x1f, 0x40, 0x4d, 0x43, 0x9f, ], [0x48, 0x4c, 0x4c, 0x44, 0x43, 0x48, 0x40, 0x53, 0x44, 0x1f, 0x56, 0x40, 0x52, 0x1f, 0x53, 0x47, 0x44, 0x9f, 0x4f, 0x51, 0x4e, 0x42, 0x44, 0x52, 0x52, 0x1f, 0x4e, 0x45, 0x1f, 0x40, 0x9f, 0x51, 0x40, 0x53, 0x48, 0x4e, 0x4d, 0x40, 0x4b, 0x1f, 0x4c, 0x48, 0x4d, 0x43, 0x1b, 0x1f, 0xe, 0x51, 0xd1, 0x56, 0x40, 0x52, 0x1f, 0x42, 0x51, 0x40, 0x59, 0x58, 0x1f, 0x40, 0x4d, 0x43, 0x9f, 0x42, 0x4e, 0x54, 0x4b, 0x43, 0x1f, 0x41, 0x44, 0x1f, 0x46, 0x51, 0x4e, 0x54, 0x4d, 0x43, 0x44, 0x43, 0x9b, 0x0, 0x4b, 0x4b, 0x1f, 0x47, 0x44, 0x1f, 0x47, 0x40, 0x43, 0x1f, 0x53, 0x4e, 0x1f, 0x43, 0x4e, 0x9f, 0x56, 0x40, 0x52, 0x1f, 0x40, 0x52, 0x4a, 0x1d, 0x1f, 0x40, 0x4d, 0x43, 0x1f, 0x40, 0x52, 0x9f, 0x52, 0x4e, 0x4e, 0x4d, 0x1f, 0x40, 0x52, 0x1f, 0x47, 0x44, 0x1f, 0x43, 0x48, 0x43, 0x1d, 0x1f, 0x47, 0xc4, ], [0x56, 0x4e, 0x54, 0x4b, 0x43, 0x1f, 0x4d, 0x4e, 0x1f, 0x4b, 0x4e, 0x4d, 0x46, 0x44, 0x51, 0x1f, 0x41, 0xc4, 0x42, 0x51, 0x40, 0x59, 0x58, 0x1f, 0x40, 0x4d, 0x43, 0x1f, 0x56, 0x4e, 0x54, 0x4b, 0x43, 0x9f, 0x47, 0x40, 0x55, 0x44, 0x1f, 0x53, 0x4e, 0x1f, 0x45, 0x4b, 0x58, 0x1f, 0x4c, 0x4e, 0x51, 0x44, 0x9f, 0x4c, 0x48, 0x52, 0x52, 0x48, 0x4e, 0x4d, 0x52, 0x1b, 0x1f, 0xe, 0x51, 0x51, 0x9f, 0x56, 0x4e, 0x54, 0x4b, 0x43, 0x1f, 0x41, 0x44, 0x1f, 0x42, 0x51, 0x40, 0x59, 0x58, 0x1f, 0x53, 0x4e, 0x9f, 0x45, 0x4b, 0x58, 0x1f, 0x4c, 0x4e, 0x51, 0x44, 0x1f, 0x4c, 0x48, 0x52, 0x52, 0x48, 0x4e, 0x4d, 0x52, 0x9f, 0x40, 0x4d, 0x43, 0x1f, 0x52, 0x40, 0x4d, 0x44, 0x1f, 0x48, 0x45, 0x1f, 0x47, 0x44, 0x9f, 0x43, 0x48, 0x43, 0x4d, 0x5d, 0x53, 0x1d, 0x1f, 0x41, 0x54, 0x53, 0x1f, 0x48, 0x45, 0x1f, 0x47, 0x44, 0x9f, ], [0x56, 0x40, 0x52, 0x1f, 0x52, 0x40, 0x4d, 0x44, 0x1f, 0x47, 0x44, 0x1f, 0x47, 0x40, 0x43, 0x1f, 0x53, 0xce, 0x45, 0x4b, 0x58, 0x1f, 0x53, 0x47, 0x44, 0x4c, 0x1b, 0x1f, 0x8, 0x45, 0x1f, 0x47, 0x44, 0x9f, 0x45, 0x4b, 0x44, 0x56, 0x1f, 0x53, 0x47, 0x44, 0x4c, 0x1f, 0x47, 0x44, 0x1f, 0x56, 0x40, 0x52, 0x9f, 0x42, 0x51, 0x40, 0x59, 0x58, 0x1f, 0x40, 0x4d, 0x43, 0x1f, 0x43, 0x48, 0x43, 0x4d, 0x5d, 0x53, 0x9f, 0x47, 0x40, 0x55, 0x44, 0x1f, 0x53, 0x4e, 0x1d, 0x1f, 0x41, 0x54, 0x53, 0x1f, 0x48, 0x45, 0x1f, 0x47, 0xc4, 0x43, 0x48, 0x43, 0x4d, 0x5d, 0x53, 0x1f, 0x56, 0x40, 0x4d, 0x53, 0x1f, 0x53, 0x4e, 0x1f, 0x47, 0x44, 0x9f, 0x56, 0x40, 0x52, 0x1f, 0x52, 0x40, 0x4d, 0x44, 0x1f, 0x40, 0x4d, 0x43, 0x1f, 0x47, 0x40, 0x43, 0x9f, 0x53, 0x4e, 0x1b, 0x1f, 0x1c, 0x7, 0x44, 0x4b, 0x4b, 0x44, 0x51, 0x1f, 0x9f, ]], +[[0x5d, 0x13, 0x47, 0x44, 0x58, 0x5d, 0x51, 0x44, 0x1f, 0x53, 0x51, 0x58, 0x48, 0x4d, 0x46, 0x1f, 0x53, 0xce, 0x4a, 0x48, 0x4b, 0x4b, 0x1f, 0x4c, 0x44, 0x1d, 0x5d, 0x9f, 0x18, 0x4e, 0x52, 0x52, 0x40, 0x51, 0x48, 0x40, 0x4d, 0x1f, 0x53, 0x4e, 0x4b, 0x43, 0x1f, 0x47, 0x48, 0xcc, 0x42, 0x40, 0x4b, 0x4c, 0x4b, 0x58, 0x1b, 0x1f, 0x5d, 0xd, 0x4e, 0x1f, 0x4e, 0x4d, 0x44, 0x5d, 0x52, 0x9f, 0x53, 0x51, 0x58, 0x48, 0x4d, 0x46, 0x1f, 0x53, 0x4e, 0x1f, 0x4a, 0x48, 0x4b, 0x4b, 0x9f, 0x58, 0x4e, 0x54, 0x1d, 0x5d, 0x1f, 0x2, 0x4b, 0x44, 0x55, 0x48, 0x4d, 0x46, 0x44, 0x51, 0x9f, 0x42, 0x51, 0x48, 0x44, 0x43, 0x1b, 0x1f, 0x5d, 0x13, 0x47, 0x44, 0x4d, 0x1f, 0x56, 0x47, 0x58, 0x9f, 0x40, 0x51, 0x44, 0x1f, 0x53, 0x47, 0x44, 0x58, 0x1f, 0x52, 0x47, 0x4e, 0x4e, 0x53, 0x48, 0x4d, 0x46, 0x9f, ], [0x40, 0x53, 0x1f, 0x4c, 0x44, 0x1e, 0x5d, 0x1f, 0x18, 0x4e, 0x52, 0x52, 0x40, 0x51, 0x48, 0x40, 0x4d, 0x9f, 0x40, 0x52, 0x4a, 0x44, 0x43, 0x1b, 0x1f, 0x5d, 0x13, 0x47, 0x44, 0x58, 0x5d, 0x51, 0x44, 0x9f, 0x52, 0x47, 0x4e, 0x4e, 0x53, 0x48, 0x4d, 0x46, 0x1f, 0x40, 0x53, 0x9f, 0x44, 0x55, 0x44, 0x51, 0x58, 0x4e, 0x4d, 0x44, 0x1d, 0x5d, 0x9f, 0x2, 0x4b, 0x44, 0x55, 0x48, 0x4d, 0x46, 0x44, 0x51, 0x9f, 0x40, 0x4d, 0x52, 0x56, 0x44, 0x51, 0x44, 0x43, 0x1b, 0x1f, 0x5d, 0x13, 0x47, 0x44, 0x58, 0x5d, 0x51, 0xc4, 0x53, 0x51, 0x58, 0x48, 0x4d, 0x46, 0x1f, 0x53, 0x4e, 0x1f, 0x4a, 0x48, 0x4b, 0x4b, 0x9f, 0x44, 0x55, 0x44, 0x51, 0x58, 0x4e, 0x4d, 0x44, 0x1b, 0x5d, 0x1f, 0x0, 0x4d, 0x43, 0x9f, ], [0x56, 0x47, 0x40, 0x53, 0x1f, 0x43, 0x48, 0x45, 0x45, 0x44, 0x51, 0x44, 0x4d, 0x42, 0x44, 0x9f, 0x43, 0x4e, 0x44, 0x52, 0x1f, 0x53, 0x47, 0x40, 0x53, 0x1f, 0x4c, 0x40, 0x4a, 0x44, 0x1e, 0x9f, 0x1c, 0x7, 0x44, 0x4b, 0x4b, 0x44, 0x51, 0x1f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, ]], +[[0x18, 0x4e, 0x54, 0x1f, 0x47, 0x40, 0x55, 0x44, 0x1f, 0x41, 0x51, 0x40, 0x48, 0x4d, 0x52, 0x1f, 0x48, 0xcd, 0x58, 0x4e, 0x54, 0x51, 0x1f, 0x47, 0x44, 0x40, 0x43, 0x1b, 0x1f, 0x18, 0x4e, 0x54, 0x9f, 0x47, 0x40, 0x55, 0x44, 0x1f, 0x45, 0x44, 0x44, 0x53, 0x1f, 0x48, 0x4d, 0x1f, 0x58, 0x4e, 0x54, 0x51, 0x9f, 0x52, 0x47, 0x4e, 0x44, 0x52, 0x1b, 0x1f, 0x18, 0x4e, 0x54, 0x1f, 0x42, 0x40, 0x4d, 0x9f, 0x52, 0x53, 0x44, 0x44, 0x51, 0x1f, 0x58, 0x4e, 0x54, 0x51, 0x52, 0x44, 0x4b, 0x45, 0x1f, 0x40, 0x4d, 0xd8, 0x43, 0x48, 0x51, 0x44, 0x42, 0x53, 0x48, 0x4e, 0x4d, 0x1f, 0x58, 0x4e, 0x54, 0x9f, 0x42, 0x47, 0x4e, 0x4e, 0x52, 0x44, 0x1b, 0x1f, 0x18, 0x4e, 0x54, 0x5d, 0x51, 0x44, 0x1f, 0x4e, 0x4d, 0x9f, 0x58, 0x4e, 0x54, 0x51, 0x1f, 0x4e, 0x56, 0x4d, 0x1b, 0x1f, 0x0, 0x4d, 0x43, 0x1f, 0x58, 0x4e, 0x54, 0x9f, ], [0x4a, 0x4d, 0x4e, 0x56, 0x1f, 0x56, 0x47, 0x40, 0x53, 0x1f, 0x58, 0x4e, 0x54, 0x9f, 0x4a, 0x4d, 0x4e, 0x56, 0x1b, 0x1f, 0x0, 0x4d, 0x43, 0x1f, 0x18, 0xe, 0x14, 0x1f, 0x40, 0x51, 0x44, 0x9f, 0x53, 0x47, 0x44, 0x1f, 0x4e, 0x4d, 0x44, 0x1f, 0x56, 0x47, 0x4e, 0x5d, 0x4b, 0x4b, 0x9f, 0x43, 0x44, 0x42, 0x48, 0x43, 0x44, 0x1f, 0x56, 0x47, 0x44, 0x51, 0x44, 0x1f, 0x53, 0x4e, 0x9f, 0x46, 0x4e, 0x1b, 0x1b, 0x1b, 0x1f, 0x1c, 0x12, 0x54, 0x44, 0x52, 0x52, 0x1f, 0x9f, 0x9f, 0x9f, 0x9f, ]], +[[0x16, 0x47, 0x44, 0x4d, 0x1f, 0x58, 0x4e, 0x54, 0x1f, 0x47, 0x40, 0x55, 0x44, 0x9f, 0x44, 0x4b, 0x48, 0x4c, 0x48, 0x4d, 0x40, 0x53, 0x44, 0x43, 0x1f, 0x40, 0x4b, 0x4b, 0x9f, 0x56, 0x47, 0x48, 0x42, 0x47, 0x1f, 0x48, 0x52, 0x9f, 0x48, 0x4c, 0x4f, 0x4e, 0x52, 0x52, 0x48, 0x41, 0x4b, 0x44, 0x1d, 0x1f, 0x53, 0x47, 0x44, 0x4d, 0x9f, 0x56, 0x47, 0x40, 0x53, 0x44, 0x55, 0x44, 0x51, 0x1f, 0x51, 0x44, 0x4c, 0x40, 0x48, 0x4d, 0x52, 0x1d, 0x9f, 0x47, 0x4e, 0x56, 0x44, 0x55, 0x44, 0x51, 0x9f, 0x48, 0x4c, 0x4f, 0x51, 0x4e, 0x41, 0x40, 0x41, 0x4b, 0x44, 0x1d, 0x1f, 0x4c, 0x54, 0x52, 0x53, 0x9f, 0x41, 0x44, 0x1f, 0x53, 0x47, 0x44, 0x1f, 0x53, 0x51, 0x54, 0x53, 0x47, 0x1b, 0x9f, ], [0x1c, 0x3, 0x4e, 0x58, 0x4b, 0x44, 0x1f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, ]], +[[0x5d, 0xc, 0x58, 0x1f, 0x4c, 0x48, 0x4d, 0x43, 0x1d, 0x5d, 0x1f, 0x47, 0x44, 0x9f, 0x52, 0x40, 0x48, 0x43, 0x1d, 0x1f, 0x5d, 0x51, 0x44, 0x41, 0x44, 0x4b, 0x52, 0x1f, 0x40, 0x53, 0x9f, 0x52, 0x53, 0x40, 0x46, 0x4d, 0x40, 0x53, 0x48, 0x4e, 0x4d, 0x1b, 0x1f, 0x6, 0x48, 0x55, 0x44, 0x9f, 0x4c, 0x44, 0x1f, 0x4f, 0x51, 0x4e, 0x41, 0x4b, 0x44, 0x4c, 0x52, 0x1d, 0x1f, 0x46, 0x48, 0x55, 0x44, 0x9f, 0x4c, 0x44, 0x1f, 0x56, 0x4e, 0x51, 0x4a, 0x1d, 0x1f, 0x46, 0x48, 0x55, 0x44, 0x1f, 0x4c, 0x44, 0x9f, 0x53, 0x47, 0x44, 0x1f, 0x4c, 0x4e, 0x52, 0x53, 0x1f, 0x40, 0x41, 0x52, 0x53, 0x51, 0x54, 0x52, 0x44, 0x9f, 0x42, 0x51, 0x58, 0x4f, 0x53, 0x4e, 0x46, 0x51, 0x40, 0x4c, 0x1f, 0x4e, 0x51, 0x1f, 0x53, 0x47, 0x44, 0x9f, 0x4c, 0x4e, 0x52, 0x53, 0x1f, 0x48, 0x4d, 0x53, 0x51, 0x48, 0x42, 0x40, 0x53, 0x44, 0x9f, ], [0x40, 0x4d, 0x40, 0x4b, 0x58, 0x52, 0x48, 0x52, 0x1d, 0x1f, 0x40, 0x4d, 0x43, 0x1f, 0x8, 0x1f, 0x40, 0xcc, 0x48, 0x4d, 0x1f, 0x4c, 0x58, 0x1f, 0x4e, 0x56, 0x4d, 0x1f, 0x4f, 0x51, 0x4e, 0x4f, 0x44, 0x51, 0x9f, 0x40, 0x53, 0x4c, 0x4e, 0x52, 0x4f, 0x47, 0x44, 0x51, 0x44, 0x1b, 0x1f, 0x8, 0x1f, 0x42, 0x40, 0x4d, 0x9f, 0x43, 0x48, 0x52, 0x4f, 0x44, 0x4d, 0x52, 0x44, 0x1f, 0x53, 0x47, 0x44, 0x4d, 0x1f, 0x56, 0x48, 0x53, 0xc7, 0x40, 0x51, 0x53, 0x48, 0x45, 0x48, 0x42, 0x48, 0x40, 0x4b, 0x9f, 0x52, 0x53, 0x48, 0x4c, 0x54, 0x4b, 0x40, 0x4d, 0x53, 0x52, 0x1b, 0x1f, 0x1, 0x54, 0x53, 0x1f, 0x8, 0x9f, 0x40, 0x41, 0x47, 0x4e, 0x51, 0x1f, 0x53, 0x47, 0x44, 0x1f, 0x43, 0x54, 0x4b, 0x4b, 0x9f, 0x51, 0x4e, 0x54, 0x53, 0x48, 0x4d, 0x44, 0x1f, 0x4e, 0x45, 0x9f, ], [0x44, 0x57, 0x48, 0x52, 0x53, 0x44, 0x4d, 0x42, 0x44, 0x1b, 0x1f, 0x8, 0x1f, 0x42, 0x51, 0x40, 0x55, 0xc4, 0x45, 0x4e, 0x51, 0x1f, 0x4c, 0x44, 0x4d, 0x53, 0x40, 0x4b, 0x9f, 0x44, 0x57, 0x40, 0x4b, 0x53, 0x40, 0x53, 0x48, 0x4e, 0x4d, 0x1b, 0x1f, 0x13, 0x47, 0x40, 0x53, 0x9f, 0x48, 0x52, 0x1f, 0x56, 0x47, 0x58, 0x1f, 0x8, 0x1f, 0x47, 0x40, 0x55, 0x44, 0x9f, 0x42, 0x47, 0x4e, 0x52, 0x44, 0x4d, 0x1f, 0x4c, 0x58, 0x1f, 0x4e, 0x56, 0x4d, 0x9f, 0x4f, 0x40, 0x51, 0x53, 0x48, 0x42, 0x54, 0x4b, 0x40, 0x51, 0x9f, 0x4f, 0x51, 0x4e, 0x45, 0x44, 0x52, 0x52, 0x48, 0x4e, 0x4d, 0x1d, 0x1f, 0x4e, 0x51, 0x9f, 0x51, 0x40, 0x53, 0x47, 0x44, 0x51, 0x1f, 0x42, 0x51, 0x44, 0x40, 0x53, 0x44, 0x43, 0x1f, 0x48, 0x53, 0x9d, ], [0x45, 0x4e, 0x51, 0x1f, 0x8, 0x1f, 0x40, 0x4c, 0x1f, 0x53, 0x47, 0x44, 0x1f, 0x4e, 0x4d, 0x4b, 0x58, 0x9f, 0x4e, 0x4d, 0x44, 0x1f, 0x48, 0x4d, 0x1f, 0x53, 0x47, 0x44, 0x1f, 0x56, 0x4e, 0x51, 0x4b, 0x43, 0x1b, 0xdd, 0x1c, 0x3, 0x4e, 0x58, 0x4b, 0x44, 0x1f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, ]], +[[0xb, 0x48, 0x45, 0x44, 0x1f, 0x48, 0x52, 0x1f, 0x48, 0x4d, 0x45, 0x48, 0x4d, 0x48, 0x53, 0x44, 0x4b, 0xd8, 0x52, 0x53, 0x51, 0x40, 0x4d, 0x46, 0x44, 0x51, 0x1f, 0x53, 0x47, 0x40, 0x4d, 0x9f, 0x40, 0x4d, 0x58, 0x53, 0x47, 0x48, 0x4d, 0x46, 0x1f, 0x56, 0x47, 0x48, 0x42, 0x47, 0x1f, 0x53, 0x47, 0xc4, 0x4c, 0x48, 0x4d, 0x43, 0x1f, 0x4e, 0x45, 0x1f, 0x4c, 0x40, 0x4d, 0x1f, 0x42, 0x4e, 0x54, 0x4b, 0x43, 0x9f, 0x48, 0x4d, 0x55, 0x44, 0x4d, 0x53, 0x1b, 0x1f, 0x16, 0x44, 0x1f, 0x56, 0x4e, 0x54, 0x4b, 0x43, 0x9f, 0x4d, 0x4e, 0x53, 0x1f, 0x43, 0x40, 0x51, 0x44, 0x1f, 0x53, 0x4e, 0x9f, 0x42, 0x4e, 0x4d, 0x42, 0x44, 0x48, 0x55, 0x44, 0x1f, 0x53, 0x47, 0x44, 0x9f, 0x53, 0x47, 0x48, 0x4d, 0x46, 0x52, 0x1f, 0x56, 0x47, 0x48, 0x42, 0x47, 0x1f, 0x40, 0x51, 0x44, 0x9f, ], [0x51, 0x44, 0x40, 0x4b, 0x4b, 0x58, 0x1f, 0x4c, 0x44, 0x51, 0x44, 0x9f, 0x42, 0x4e, 0x4c, 0x4c, 0x4e, 0x4d, 0x4f, 0x4b, 0x40, 0x42, 0x44, 0x52, 0x1f, 0x4e, 0x45, 0x9f, 0x44, 0x57, 0x48, 0x52, 0x53, 0x44, 0x4d, 0x42, 0x44, 0x1b, 0x1f, 0x8, 0x45, 0x1f, 0x56, 0x44, 0x9f, 0x42, 0x4e, 0x54, 0x4b, 0x43, 0x1f, 0x45, 0x4b, 0x58, 0x1f, 0x4e, 0x54, 0x53, 0x1f, 0x4e, 0x45, 0x9f, 0x53, 0x47, 0x40, 0x53, 0x1f, 0x56, 0x48, 0x4d, 0x43, 0x4e, 0x56, 0x1f, 0x47, 0x40, 0x4d, 0x43, 0x9f, 0x48, 0x4d, 0x1f, 0x47, 0x40, 0x4d, 0x43, 0x1d, 0x1f, 0x47, 0x4e, 0x55, 0x44, 0x51, 0x9f, 0x4e, 0x55, 0x44, 0x51, 0x1f, 0x53, 0x47, 0x48, 0x52, 0x1f, 0x46, 0x51, 0x44, 0x40, 0x53, 0x9f, 0x42, 0x48, 0x53, 0x58, 0x1d, 0x1f, 0x46, 0x44, 0x4d, 0x53, 0x4b, 0x58, 0x9f, ], [0x51, 0x44, 0x4c, 0x4e, 0x55, 0x44, 0x1f, 0x53, 0x47, 0x44, 0x1f, 0x51, 0x4e, 0x4e, 0x45, 0x52, 0x1d, 0x9f, 0x40, 0x4d, 0x43, 0x1f, 0x40, 0x4d, 0x43, 0x1f, 0x4f, 0x44, 0x44, 0x4f, 0x1f, 0x48, 0x4d, 0x1f, 0x40, 0xd3, 0x53, 0x47, 0x44, 0x1f, 0x50, 0x54, 0x44, 0x44, 0x51, 0x1f, 0x53, 0x47, 0x48, 0x4d, 0x46, 0x52, 0x9f, 0x56, 0x47, 0x48, 0x42, 0x47, 0x1f, 0x40, 0x51, 0x44, 0x1f, 0x46, 0x4e, 0x48, 0x4d, 0x46, 0x9f, 0x4e, 0x4d, 0x1d, 0x1f, 0x53, 0x47, 0x44, 0x1f, 0x52, 0x53, 0x51, 0x40, 0x4d, 0x46, 0x44, 0x9f, 0x42, 0x4e, 0x48, 0x4d, 0x42, 0x48, 0x43, 0x44, 0x4d, 0x42, 0x44, 0x52, 0x1d, 0x1f, 0x53, 0x47, 0x44, 0x9f, 0x4f, 0x4b, 0x40, 0x4d, 0x4d, 0x48, 0x4d, 0x46, 0x52, 0x1d, 0x1f, 0x53, 0x47, 0x44, 0x9f, 0x42, 0x51, 0x4e, 0x52, 0x52, 0x1c, 0x4f, 0x54, 0x51, 0x4f, 0x4e, 0x52, 0x44, 0x52, 0x1d, 0x9f, ], [0x53, 0x47, 0x44, 0x1f, 0x56, 0x4e, 0x4d, 0x43, 0x44, 0x51, 0x45, 0x54, 0x4b, 0x9f, 0x42, 0x47, 0x40, 0x48, 0x4d, 0x52, 0x1f, 0x4e, 0x45, 0x1f, 0x44, 0x55, 0x44, 0x4d, 0x53, 0x52, 0x1d, 0x9f, 0x56, 0x4e, 0x51, 0x4a, 0x48, 0x4d, 0x46, 0x1f, 0x53, 0x47, 0x51, 0x4e, 0x54, 0x46, 0x47, 0x9f, 0x46, 0x44, 0x4d, 0x44, 0x51, 0x40, 0x53, 0x48, 0x4e, 0x4d, 0x52, 0x1d, 0x1f, 0x40, 0x4d, 0x43, 0x9f, 0x4b, 0x44, 0x40, 0x43, 0x48, 0x4d, 0x46, 0x1f, 0x53, 0x4e, 0x1f, 0x53, 0x47, 0x44, 0x9f, 0x4c, 0x4e, 0x52, 0x53, 0x1f, 0x4e, 0x54, 0x53, 0x51, 0x44, 0x9f, 0x51, 0x44, 0x52, 0x54, 0x4b, 0x53, 0x52, 0x1d, 0x1f, 0x48, 0x53, 0x1f, 0x56, 0x4e, 0x54, 0x4b, 0x43, 0x9f, 0x4c, 0x40, 0x4a, 0x44, 0x1f, 0x40, 0x4b, 0x4b, 0x1f, 0x45, 0x48, 0x42, 0x53, 0x48, 0x4e, 0x4d, 0x9f, ], [0x56, 0x48, 0x53, 0x47, 0x1f, 0x48, 0x53, 0x52, 0x9f, 0x42, 0x4e, 0x4d, 0x55, 0x44, 0x4d, 0x53, 0x48, 0x4e, 0x4d, 0x40, 0x4b, 0x48, 0x53, 0x48, 0x44, 0x52, 0x9f, 0x40, 0x4d, 0x43, 0x1f, 0x45, 0x4e, 0x51, 0x44, 0x52, 0x44, 0x44, 0x4d, 0x9f, 0x42, 0x4e, 0x4d, 0x42, 0x4b, 0x54, 0x52, 0x48, 0x4e, 0x4d, 0x52, 0x1f, 0x4c, 0x4e, 0x52, 0x53, 0x9f, 0x52, 0x53, 0x40, 0x4b, 0x44, 0x1f, 0x40, 0x4d, 0x43, 0x9f, 0x54, 0x4d, 0x4f, 0x51, 0x4e, 0x45, 0x48, 0x53, 0x40, 0x41, 0x4b, 0x44, 0x1b, 0x9f, 0x1c, 0x3, 0x4e, 0x58, 0x4b, 0x44, 0x1f, 0x9f, 0x9f, ]], +[[0x13, 0x47, 0x44, 0x1f, 0x52, 0x53, 0x4e, 0x51, 0x58, 0x1f, 0x52, 0x4e, 0x1f, 0x45, 0x40, 0x51, 0x1b, 0x9f, 0x8, 0x4d, 0x1f, 0x53, 0x47, 0x44, 0x1f, 0x41, 0x44, 0x46, 0x48, 0x4d, 0x4d, 0x48, 0x4d, 0x46, 0x9f, 0x53, 0x47, 0x44, 0x1f, 0x14, 0x4d, 0x48, 0x55, 0x44, 0x51, 0x52, 0x44, 0x1f, 0x56, 0x40, 0x52, 0x9f, 0x42, 0x51, 0x44, 0x40, 0x53, 0x44, 0x43, 0x1b, 0x1f, 0x13, 0x47, 0x48, 0x52, 0x1f, 0x47, 0x40, 0x52, 0x9f, 0x4c, 0x40, 0x43, 0x44, 0x1f, 0x40, 0x1f, 0x4b, 0x4e, 0x53, 0x1f, 0x4e, 0x45, 0x9f, 0x4f, 0x44, 0x4e, 0x4f, 0x4b, 0x44, 0x1f, 0x55, 0x44, 0x51, 0x58, 0x1f, 0x40, 0x4d, 0x46, 0x51, 0x58, 0x9f, 0x40, 0x4d, 0x43, 0x1f, 0x41, 0x44, 0x44, 0x4d, 0x1f, 0x56, 0x48, 0x43, 0x44, 0x4b, 0x58, 0x9f, 0x51, 0x44, 0x46, 0x40, 0x51, 0x43, 0x44, 0x43, 0x1f, 0x40, 0x52, 0x1f, 0x40, 0x1f, 0x41, 0x40, 0x43, 0x9f, ], [0x4c, 0x4e, 0x55, 0x44, 0x1b, 0x1f, 0x1c, 0x0, 0x43, 0x40, 0x4c, 0x52, 0x1f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, ]], +[[0x5, 0x4e, 0x51, 0x1f, 0x48, 0x4d, 0x52, 0x53, 0x40, 0x4d, 0x42, 0x44, 0x1d, 0x1f, 0x4e, 0x4d, 0x9f, 0x53, 0x47, 0x44, 0x1f, 0x4f, 0x4b, 0x40, 0x4d, 0x44, 0x53, 0x1f, 0x4, 0x40, 0x51, 0x53, 0x47, 0x1d, 0x9f, 0x4c, 0x40, 0x4d, 0x1f, 0x47, 0x40, 0x43, 0x1f, 0x40, 0x4b, 0x56, 0x40, 0x58, 0x52, 0x9f, 0x40, 0x52, 0x52, 0x54, 0x4c, 0x44, 0x43, 0x1f, 0x53, 0x47, 0x40, 0x53, 0x1f, 0x47, 0x44, 0x9f, 0x56, 0x40, 0x52, 0x1f, 0x4c, 0x4e, 0x51, 0x44, 0x9f, 0x48, 0x4d, 0x53, 0x44, 0x4b, 0x4b, 0x48, 0x46, 0x44, 0x4d, 0x53, 0x1f, 0x53, 0x47, 0x40, 0x4d, 0x9f, 0x43, 0x4e, 0x4b, 0x4f, 0x47, 0x48, 0x4d, 0x52, 0x1f, 0x41, 0x44, 0x42, 0x40, 0x54, 0x52, 0x44, 0x9f, 0x47, 0x44, 0x1f, 0x47, 0x40, 0x43, 0x1f, 0x40, 0x42, 0x47, 0x48, 0x44, 0x55, 0x44, 0x43, 0x1f, 0x52, 0xce, ], [0x4c, 0x54, 0x42, 0x47, 0x1c, 0x53, 0x47, 0x44, 0x1f, 0x56, 0x47, 0x44, 0x44, 0x4b, 0x1d, 0x9f, 0xd, 0x44, 0x56, 0x1f, 0x18, 0x4e, 0x51, 0x4a, 0x1d, 0x1f, 0x56, 0x40, 0x51, 0x52, 0x1f, 0x40, 0x4d, 0xc3, 0x52, 0x4e, 0x1f, 0x4e, 0x4d, 0x1c, 0x56, 0x47, 0x48, 0x4b, 0x52, 0x53, 0x1f, 0x40, 0x4b, 0x4b, 0x9f, 0x53, 0x47, 0x44, 0x1f, 0x43, 0x4e, 0x4b, 0x4f, 0x47, 0x48, 0x4d, 0x52, 0x1f, 0x47, 0x40, 0x43, 0x9f, 0x44, 0x55, 0x44, 0x51, 0x1f, 0x43, 0x4e, 0x4d, 0x44, 0x1f, 0x56, 0x40, 0x52, 0x1f, 0x4c, 0x54, 0x42, 0xca, 0x40, 0x41, 0x4e, 0x54, 0x53, 0x1f, 0x48, 0x4d, 0x1f, 0x53, 0x47, 0x44, 0x1f, 0x56, 0x40, 0x53, 0x44, 0xd1, 0x47, 0x40, 0x55, 0x48, 0x4d, 0x46, 0x1f, 0x40, 0x1f, 0x46, 0x4e, 0x4e, 0x43, 0x9f, 0x53, 0x48, 0x4c, 0x44, 0x1b, 0x1f, 0x1, 0x54, 0x53, 0x9f, ], [0x42, 0x4e, 0x4d, 0x55, 0x44, 0x51, 0x52, 0x44, 0x4b, 0x58, 0x1d, 0x1f, 0x53, 0x47, 0x44, 0x9f, 0x43, 0x4e, 0x4b, 0x4f, 0x47, 0x48, 0x4d, 0x52, 0x1f, 0x47, 0x40, 0x43, 0x9f, 0x40, 0x4b, 0x56, 0x40, 0x58, 0x52, 0x1f, 0x41, 0x44, 0x4b, 0x48, 0x44, 0x55, 0x44, 0x43, 0x9f, 0x53, 0x47, 0x40, 0x53, 0x1f, 0x53, 0x47, 0x44, 0x58, 0x1f, 0x56, 0x44, 0x51, 0x44, 0x1f, 0x45, 0x40, 0xd1, 0x4c, 0x4e, 0x51, 0x44, 0x1f, 0x48, 0x4d, 0x53, 0x44, 0x4b, 0x4b, 0x48, 0x46, 0x44, 0x4d, 0x53, 0x9f, 0x53, 0x47, 0x40, 0x4d, 0x1f, 0x4c, 0x40, 0x4d, 0x1b, 0x45, 0x4e, 0x51, 0x9f, 0x4f, 0x51, 0x44, 0x42, 0x48, 0x52, 0x44, 0x4b, 0x58, 0x1f, 0x53, 0x47, 0x44, 0x1f, 0x52, 0x40, 0x4c, 0xc4, 0x51, 0x44, 0x40, 0x52, 0x4e, 0x4d, 0x52, 0x1b, 0x1f, 0x1c, 0x0, 0x43, 0x40, 0x4c, 0x52, 0x1f, 0x9f, ]], +[[0x8, 0x53, 0x1f, 0x48, 0x52, 0x1f, 0x4a, 0x4d, 0x4e, 0x56, 0x4d, 0x1f, 0x53, 0x47, 0x40, 0x53, 0x9f, 0x53, 0x47, 0x44, 0x51, 0x44, 0x1f, 0x40, 0x51, 0x44, 0x1f, 0x40, 0x4d, 0x9f, 0x48, 0x4d, 0x45, 0x48, 0x4d, 0x48, 0x53, 0x44, 0x1f, 0x4d, 0x54, 0x4c, 0x41, 0x44, 0x51, 0x1f, 0x4e, 0xc5, 0x56, 0x4e, 0x51, 0x4b, 0x43, 0x52, 0x1d, 0x1f, 0x52, 0x48, 0x4c, 0x4f, 0x4b, 0x58, 0x9f, 0x41, 0x44, 0x42, 0x40, 0x54, 0x52, 0x44, 0x1f, 0x53, 0x47, 0x44, 0x51, 0x44, 0x1f, 0x48, 0x52, 0x9f, 0x40, 0x4d, 0x1f, 0x48, 0x4d, 0x45, 0x48, 0x4d, 0x48, 0x53, 0x44, 0x1f, 0x40, 0x4c, 0x4e, 0x54, 0x4d, 0xd3, 0x4e, 0x45, 0x1f, 0x52, 0x4f, 0x40, 0x42, 0x44, 0x1f, 0x45, 0x4e, 0x51, 0x1f, 0x53, 0x47, 0x44, 0x4c, 0x9f, 0x53, 0x4e, 0x1f, 0x41, 0x44, 0x1f, 0x48, 0x4d, 0x1b, 0x1f, 0x7, 0x4e, 0x56, 0x44, 0x55, 0x44, 0x51, 0x9d, ], [0x4d, 0x4e, 0x53, 0x1f, 0x44, 0x55, 0x44, 0x51, 0x58, 0x1f, 0x4e, 0x4d, 0x44, 0x1f, 0x4e, 0x45, 0x9f, 0x53, 0x47, 0x44, 0x4c, 0x1f, 0x48, 0x52, 0x1f, 0x48, 0x4d, 0x47, 0x40, 0x41, 0x48, 0x53, 0x44, 0x43, 0x9b, 0x13, 0x47, 0x44, 0x51, 0x44, 0x45, 0x4e, 0x51, 0x44, 0x1d, 0x1f, 0x53, 0x47, 0x44, 0x51, 0x44, 0x9f, 0x4c, 0x54, 0x52, 0x53, 0x1f, 0x41, 0x44, 0x1f, 0x40, 0x1f, 0x45, 0x48, 0x4d, 0x48, 0x53, 0x44, 0x9f, 0x4d, 0x54, 0x4c, 0x41, 0x44, 0x51, 0x1f, 0x4e, 0x45, 0x9f, 0x48, 0x4d, 0x47, 0x40, 0x41, 0x48, 0x53, 0x44, 0x43, 0x1f, 0x56, 0x4e, 0x51, 0x4b, 0x43, 0x52, 0x1b, 0x9f, 0x0, 0x4d, 0x58, 0x1f, 0x45, 0x48, 0x4d, 0x48, 0x53, 0x44, 0x1f, 0x4d, 0x54, 0x4c, 0x41, 0x44, 0x51, 0x9f, 0x43, 0x48, 0x55, 0x48, 0x43, 0x44, 0x43, 0x1f, 0x41, 0x58, 0x9f, ], [0x48, 0x4d, 0x45, 0x48, 0x4d, 0x48, 0x53, 0x58, 0x1f, 0x48, 0x52, 0x1f, 0x40, 0x52, 0x9f, 0x4d, 0x44, 0x40, 0x51, 0x1f, 0x53, 0x4e, 0x1f, 0x4d, 0x4e, 0x53, 0x47, 0x48, 0x4d, 0x46, 0x1f, 0x40, 0xd2, 0x4c, 0x40, 0x4a, 0x44, 0x52, 0x1f, 0x4d, 0x4e, 0x1f, 0x4e, 0x43, 0x43, 0x52, 0x1d, 0x1f, 0x52, 0x4e, 0x9f, 0x53, 0x47, 0x44, 0x1f, 0x40, 0x55, 0x44, 0x51, 0x40, 0x46, 0x44, 0x9f, 0x4f, 0x4e, 0x4f, 0x54, 0x4b, 0x40, 0x53, 0x48, 0x4e, 0x4d, 0x1f, 0x4e, 0x45, 0x1f, 0x40, 0x4b, 0x4b, 0x9f, 0x53, 0x47, 0x44, 0x1f, 0x4f, 0x4b, 0x40, 0x4d, 0x44, 0x53, 0x52, 0x1f, 0x48, 0x4d, 0x1f, 0x53, 0x47, 0xc4, 0x14, 0x4d, 0x48, 0x55, 0x44, 0x51, 0x52, 0x44, 0x1f, 0x42, 0x40, 0x4d, 0x1f, 0x41, 0x44, 0x9f, 0x52, 0x40, 0x48, 0x43, 0x1f, 0x53, 0x4e, 0x1f, 0x41, 0x44, 0x1f, 0x59, 0x44, 0x51, 0x4e, 0x1b, 0x9f, ], [0x5, 0x51, 0x4e, 0x4c, 0x1f, 0x53, 0x47, 0x48, 0x52, 0x1f, 0x48, 0x53, 0x9f, 0x45, 0x4e, 0x4b, 0x4b, 0x4e, 0x56, 0x52, 0x1f, 0x53, 0x47, 0x40, 0x53, 0x1f, 0x53, 0x47, 0x44, 0x9f, 0x4f, 0x4e, 0x4f, 0x54, 0x4b, 0x40, 0x53, 0x48, 0x4e, 0x4d, 0x1f, 0x4e, 0x45, 0x1f, 0x53, 0x47, 0x44, 0x9f, 0x56, 0x47, 0x4e, 0x4b, 0x44, 0x1f, 0x14, 0x4d, 0x48, 0x55, 0x44, 0x51, 0x52, 0x44, 0x1f, 0x48, 0x52, 0x9f, 0x40, 0x4b, 0x52, 0x4e, 0x1f, 0x59, 0x44, 0x51, 0x4e, 0x1d, 0x1f, 0x40, 0x4d, 0x43, 0x9f, 0x53, 0x47, 0x40, 0x53, 0x1f, 0x40, 0x4d, 0x58, 0x1f, 0x4f, 0x44, 0x4e, 0x4f, 0x4b, 0x44, 0x9f, 0x58, 0x4e, 0x54, 0x1f, 0x4c, 0x40, 0x58, 0x1f, 0x4c, 0x44, 0x44, 0x53, 0x1f, 0x45, 0x51, 0x4e, 0x4c, 0x9f, 0x53, 0x48, 0x4c, 0x44, 0x1f, 0x53, 0x4e, 0x1f, 0x53, 0x48, 0x4c, 0x44, 0x1f, 0x40, 0x51, 0x44, 0x9f, ], [0x4c, 0x44, 0x51, 0x44, 0x4b, 0x58, 0x1f, 0x53, 0x47, 0x44, 0x9f, 0x4f, 0x51, 0x4e, 0x43, 0x54, 0x42, 0x53, 0x52, 0x1f, 0x4e, 0x45, 0x1f, 0x40, 0x9f, 0x43, 0x44, 0x51, 0x40, 0x4d, 0x46, 0x44, 0x43, 0x9f, 0x48, 0x4c, 0x40, 0x46, 0x48, 0x4d, 0x40, 0x53, 0x48, 0x4e, 0x4d, 0x1b, 0x9f, 0x1c, 0x0, 0x43, 0x40, 0x4c, 0x52, 0x1f, 0x9f, 0x9f, 0x9f, 0x9f, ]], +[[0x5, 0x40, 0x51, 0x1f, 0x4e, 0x55, 0x44, 0x51, 0x1f, 0x53, 0x47, 0x44, 0x1f, 0x4c, 0x48, 0x52, 0x53, 0xd8, 0x4c, 0x4e, 0x54, 0x4d, 0x53, 0x40, 0x48, 0x4d, 0x52, 0x1f, 0x42, 0x4e, 0x4b, 0x43, 0x1f, 0x13, 0x4e, 0x9f, 0x43, 0x54, 0x4d, 0x46, 0x44, 0x4e, 0x4d, 0x52, 0x1f, 0x43, 0x44, 0x44, 0x4f, 0x1f, 0x40, 0x4d, 0x43, 0x9f, 0x42, 0x40, 0x55, 0x44, 0x51, 0x4d, 0x52, 0x1f, 0x4e, 0x4b, 0x43, 0x1f, 0x16, 0x44, 0x9f, 0x4c, 0x54, 0x52, 0x53, 0x1f, 0x40, 0x56, 0x40, 0x58, 0x1d, 0x1f, 0x44, 0x51, 0x44, 0x9f, 0x41, 0x51, 0x44, 0x40, 0x4a, 0x1f, 0x4e, 0x45, 0x1f, 0x43, 0x40, 0x58, 0x1d, 0x1f, 0x13, 0x4e, 0x9f, 0x52, 0x44, 0x44, 0x4a, 0x1f, 0x53, 0x47, 0x44, 0x1f, 0x4f, 0x40, 0x4b, 0x44, 0x9f, 0x44, 0x4d, 0x42, 0x47, 0x40, 0x4d, 0x53, 0x44, 0x43, 0x1f, 0x46, 0x4e, 0x4b, 0x43, 0x1b, 0x9f, ], [0x13, 0x47, 0x44, 0x1f, 0x43, 0x56, 0x40, 0x51, 0x55, 0x44, 0x52, 0x1f, 0x4e, 0x45, 0x9f, 0x58, 0x4e, 0x51, 0x44, 0x1f, 0x4c, 0x40, 0x43, 0x44, 0x1f, 0x4c, 0x48, 0x46, 0x47, 0x53, 0x58, 0x9f, 0x52, 0x4f, 0x44, 0x4b, 0x4b, 0x52, 0x1d, 0x1f, 0x16, 0x47, 0x48, 0x4b, 0x44, 0x9f, 0x47, 0x40, 0x4c, 0x4c, 0x44, 0x51, 0x52, 0x1f, 0x45, 0x44, 0x4b, 0x4b, 0x1f, 0x4b, 0x48, 0x4a, 0x44, 0x9f, 0x51, 0x48, 0x4d, 0x46, 0x48, 0x4d, 0x46, 0x1f, 0x41, 0x44, 0x4b, 0x4b, 0x52, 0x1f, 0x8, 0x4d, 0x9f, 0x4f, 0x4b, 0x40, 0x42, 0x44, 0x52, 0x1f, 0x43, 0x44, 0x44, 0x4f, 0x1d, 0x1f, 0x56, 0x47, 0x44, 0x51, 0xc4, 0x43, 0x40, 0x51, 0x4a, 0x1f, 0x53, 0x47, 0x48, 0x4d, 0x46, 0x52, 0x1f, 0x52, 0x4b, 0x44, 0x44, 0x4f, 0x9d, 0x8, 0x4d, 0x1f, 0x47, 0x4e, 0x4b, 0x4b, 0x4e, 0x56, 0x1f, 0x47, 0x40, 0x4b, 0x4b, 0x52, 0x9f, ], [0x41, 0x44, 0x4d, 0x44, 0x40, 0x53, 0x47, 0x1f, 0x53, 0x47, 0x44, 0x1f, 0x45, 0x44, 0x4b, 0x4b, 0x52, 0x9b, 0x5, 0x4e, 0x51, 0x1f, 0x40, 0x4d, 0x42, 0x48, 0x44, 0x4d, 0x53, 0x1f, 0x4a, 0x48, 0x4d, 0x46, 0x9f, 0x40, 0x4d, 0x43, 0x1f, 0x44, 0x4b, 0x55, 0x48, 0x52, 0x47, 0x1f, 0x4b, 0x4e, 0x51, 0x43, 0x9f, 0x13, 0x47, 0x44, 0x51, 0x44, 0x1f, 0x4c, 0x40, 0x4d, 0x58, 0x1f, 0x40, 0x9f, 0x46, 0x4b, 0x44, 0x40, 0x4c, 0x48, 0x4d, 0x46, 0x1f, 0x46, 0x4e, 0x4b, 0x43, 0x44, 0x4d, 0x9f, 0x47, 0x4e, 0x40, 0x51, 0x43, 0x1f, 0x13, 0x47, 0x44, 0x58, 0x1f, 0x52, 0x47, 0x40, 0x4f, 0x44, 0x43, 0x9f, 0x40, 0x4d, 0x43, 0x1f, 0x56, 0x51, 0x4e, 0x54, 0x46, 0x47, 0x53, 0x1d, 0x1f, 0x40, 0x4d, 0x43, 0x9f, 0x4b, 0x48, 0x46, 0x47, 0x53, 0x1f, 0x53, 0x47, 0x44, 0x58, 0x1f, 0x42, 0x40, 0x54, 0x46, 0x47, 0x53, 0x9f, ], [0x13, 0x4e, 0x1f, 0x47, 0x48, 0x43, 0x44, 0x1f, 0x48, 0x4d, 0x1f, 0x46, 0x44, 0x4c, 0x52, 0x1f, 0x4e, 0xcd, 0x47, 0x48, 0x4b, 0x53, 0x1f, 0x4e, 0x45, 0x1f, 0x52, 0x56, 0x4e, 0x51, 0x43, 0x1b, 0x1f, 0xe, 0x4d, 0x9f, 0x52, 0x48, 0x4b, 0x55, 0x44, 0x51, 0x1f, 0x4d, 0x44, 0x42, 0x4a, 0x4b, 0x40, 0x42, 0x44, 0x52, 0x9f, 0x53, 0x47, 0x44, 0x58, 0x1f, 0x52, 0x53, 0x51, 0x54, 0x4d, 0x46, 0x1f, 0x13, 0x47, 0x44, 0x9f, 0x45, 0x4b, 0x4e, 0x56, 0x44, 0x51, 0x48, 0x4d, 0x46, 0x1f, 0x52, 0x53, 0x40, 0x51, 0x52, 0x1d, 0x9f, 0x4e, 0x4d, 0x1f, 0x42, 0x51, 0x4e, 0x56, 0x4d, 0x52, 0x1f, 0x53, 0x47, 0x44, 0x58, 0x9f, 0x47, 0x54, 0x4d, 0x46, 0x1f, 0x13, 0x47, 0x44, 0x9f, 0x43, 0x51, 0x40, 0x46, 0x4e, 0x4d, 0x1c, 0x45, 0x48, 0x51, 0x44, 0x1d, 0x1f, 0x48, 0x4d, 0x9f, ], [0x53, 0x56, 0x48, 0x52, 0x53, 0x44, 0x43, 0x1f, 0x56, 0x48, 0x51, 0x44, 0x1f, 0x13, 0x47, 0x44, 0x58, 0x9f, 0x4c, 0x44, 0x52, 0x47, 0x44, 0x43, 0x1f, 0x53, 0x47, 0x44, 0x1f, 0x4b, 0x48, 0x46, 0x47, 0x53, 0x9f, 0x4e, 0x45, 0x1f, 0x4c, 0x4e, 0x4e, 0x4d, 0x1f, 0x40, 0x4d, 0x43, 0x1f, 0x52, 0x54, 0x4d, 0x1b, 0x9f, 0x1c, 0x13, 0x4e, 0x4b, 0x4a, 0x48, 0x44, 0x4d, 0x1f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, ]], +[[0x5, 0x40, 0x51, 0x1f, 0x4e, 0x55, 0x44, 0x51, 0x1f, 0x53, 0x47, 0x44, 0x1f, 0x4c, 0x48, 0x52, 0x53, 0xd8, 0x4c, 0x4e, 0x54, 0x4d, 0x53, 0x40, 0x48, 0x4d, 0x52, 0x1f, 0x42, 0x4e, 0x4b, 0x43, 0x1f, 0x13, 0x4e, 0x9f, 0x43, 0x54, 0x4d, 0x46, 0x44, 0x4e, 0x4d, 0x52, 0x1f, 0x43, 0x44, 0x44, 0x4f, 0x1f, 0x40, 0x4d, 0x43, 0x9f, 0x42, 0x40, 0x55, 0x44, 0x51, 0x4d, 0x52, 0x1f, 0x4e, 0x4b, 0x43, 0x1f, 0x16, 0x44, 0x9f, 0x4c, 0x54, 0x52, 0x53, 0x1f, 0x40, 0x56, 0x40, 0x58, 0x1d, 0x1f, 0x44, 0x51, 0x44, 0x9f, 0x41, 0x51, 0x44, 0x40, 0x4a, 0x1f, 0x4e, 0x45, 0x1f, 0x43, 0x40, 0x58, 0x1d, 0x1f, 0x13, 0x4e, 0x9f, 0x42, 0x4b, 0x40, 0x48, 0x4c, 0x1f, 0x4e, 0x54, 0x51, 0x9f, 0x4b, 0x4e, 0x4d, 0x46, 0x1c, 0x45, 0x4e, 0x51, 0x46, 0x4e, 0x53, 0x53, 0x44, 0x4d, 0x9f, ], [0x46, 0x4e, 0x4b, 0x43, 0x1b, 0x1f, 0x6, 0x4e, 0x41, 0x4b, 0x44, 0x53, 0x52, 0x1f, 0x53, 0x47, 0x44, 0xd8, 0x42, 0x40, 0x51, 0x55, 0x44, 0x43, 0x1f, 0x53, 0x47, 0x44, 0x51, 0x44, 0x1f, 0x45, 0x4e, 0x51, 0x9f, 0x53, 0x47, 0x44, 0x4c, 0x52, 0x44, 0x4b, 0x55, 0x44, 0x52, 0x1f, 0x0, 0x4d, 0x43, 0x9f, 0x47, 0x40, 0x51, 0x4f, 0x52, 0x1f, 0x4e, 0x45, 0x1f, 0x46, 0x4e, 0x4b, 0x43, 0x1d, 0x9f, 0x56, 0x47, 0x44, 0x51, 0x44, 0x1f, 0x4d, 0x4e, 0x1f, 0x4c, 0x40, 0x4d, 0x9f, 0x43, 0x44, 0x4b, 0x55, 0x44, 0x52, 0x1f, 0x13, 0x47, 0x44, 0x51, 0x44, 0x1f, 0x4b, 0x40, 0x58, 0x9f, 0x53, 0x47, 0x44, 0x58, 0x1f, 0x4b, 0x4e, 0x4d, 0x46, 0x1d, 0x1f, 0x40, 0x4d, 0x43, 0x9f, 0x4c, 0x40, 0x4d, 0x58, 0x1f, 0x40, 0x1f, 0x52, 0x4e, 0x4d, 0x46, 0x1f, 0x16, 0x40, 0x52, 0x9f, ], [0x52, 0x54, 0x4d, 0x46, 0x1f, 0x54, 0x4d, 0x47, 0x44, 0x40, 0x51, 0x43, 0x1f, 0x41, 0x58, 0x9f, 0x4c, 0x44, 0x4d, 0x1f, 0x4e, 0x51, 0x1f, 0x44, 0x4b, 0x55, 0x44, 0x52, 0x1b, 0x1f, 0x13, 0x47, 0x44, 0x9f, 0x4f, 0x48, 0x4d, 0x44, 0x52, 0x1f, 0x56, 0x44, 0x51, 0x44, 0x1f, 0x51, 0x4e, 0x40, 0x51, 0x48, 0x4d, 0xc6, 0x4e, 0x4d, 0x1f, 0x53, 0x47, 0x44, 0x1f, 0x47, 0x44, 0x48, 0x46, 0x47, 0x53, 0x1d, 0x1f, 0x13, 0x47, 0xc4, 0x56, 0x48, 0x4d, 0x43, 0x52, 0x1f, 0x56, 0x44, 0x51, 0x44, 0x1f, 0x4c, 0x4e, 0x40, 0x4d, 0x48, 0x4d, 0xc6, 0x48, 0x4d, 0x1f, 0x53, 0x47, 0x44, 0x1f, 0x4d, 0x48, 0x46, 0x47, 0x53, 0x1b, 0x1f, 0x13, 0x47, 0x44, 0x9f, 0x45, 0x48, 0x51, 0x44, 0x1f, 0x56, 0x40, 0x52, 0x1f, 0x51, 0x44, 0x43, 0x1d, 0x1f, 0x48, 0x53, 0x9f, 0x45, 0x4b, 0x40, 0x4c, 0x48, 0x4d, 0x46, 0x1f, 0x52, 0x4f, 0x51, 0x44, 0x40, 0x43, 0x1b, 0x9f, ], [0x13, 0x47, 0x44, 0x1f, 0x53, 0x51, 0x44, 0x44, 0x52, 0x1f, 0x4b, 0x48, 0x4a, 0x44, 0x9f, 0x53, 0x4e, 0x51, 0x42, 0x47, 0x44, 0x52, 0x1f, 0x41, 0x4b, 0x40, 0x59, 0x44, 0x43, 0x9f, 0x56, 0x48, 0x53, 0x47, 0x1f, 0x4b, 0x48, 0x46, 0x47, 0x53, 0x1b, 0x9f, 0x1c, 0x13, 0x4e, 0x4b, 0x4a, 0x48, 0x44, 0x4d, 0x1f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, ]], +[[0x13, 0x47, 0x44, 0x1f, 0x41, 0x44, 0x4b, 0x4b, 0x52, 0x1f, 0x56, 0x44, 0x51, 0x44, 0x9f, 0x51, 0x48, 0x4d, 0x46, 0x48, 0x4d, 0x46, 0x1f, 0x48, 0x4d, 0x1f, 0x53, 0x47, 0x44, 0x9f, 0x43, 0x40, 0x4b, 0x44, 0x1f, 0x0, 0x4d, 0x43, 0x1f, 0x4c, 0x44, 0x4d, 0x9f, 0x4b, 0x4e, 0x4e, 0x4a, 0x44, 0x43, 0x1f, 0x54, 0x4f, 0x1f, 0x56, 0x48, 0x53, 0x47, 0x9f, 0x45, 0x40, 0x42, 0x44, 0x52, 0x1f, 0x4f, 0x40, 0x4b, 0x44, 0x1b, 0x1f, 0x13, 0x47, 0x44, 0x4d, 0x9f, 0x43, 0x51, 0x40, 0x46, 0x4e, 0x4d, 0x5d, 0x52, 0x1f, 0x48, 0x51, 0x44, 0x1f, 0x4c, 0x4e, 0x51, 0x44, 0x9f, 0x45, 0x48, 0x44, 0x51, 0x42, 0x44, 0x1f, 0x53, 0x47, 0x40, 0x4d, 0x1f, 0x45, 0x48, 0x51, 0x44, 0x9f, 0xb, 0x40, 0x48, 0x43, 0x1f, 0x4b, 0x4e, 0x56, 0x1f, 0x53, 0x47, 0x44, 0x48, 0x51, 0x9f, ], [0x53, 0x4e, 0x56, 0x44, 0x51, 0x52, 0x1f, 0x40, 0x4d, 0x43, 0x1f, 0x47, 0x4e, 0x54, 0x52, 0x44, 0x52, 0x9f, 0x45, 0x51, 0x40, 0x48, 0x4b, 0x1b, 0x1f, 0x13, 0x47, 0x44, 0x9f, 0x4c, 0x4e, 0x54, 0x4d, 0x53, 0x40, 0x48, 0x4d, 0x1f, 0x52, 0x4c, 0x4e, 0x4a, 0x44, 0x43, 0x9f, 0x41, 0x44, 0x4d, 0x44, 0x40, 0x53, 0x47, 0x1f, 0x53, 0x47, 0x44, 0x1f, 0x4c, 0x4e, 0x4e, 0x4d, 0x1b, 0x9f, 0x13, 0x47, 0x44, 0x1f, 0x43, 0x56, 0x40, 0x51, 0x55, 0x44, 0x52, 0x1d, 0x1f, 0x53, 0x47, 0x44, 0x58, 0x9f, 0x47, 0x44, 0x40, 0x51, 0x43, 0x1f, 0x53, 0x47, 0x44, 0x1f, 0x53, 0x51, 0x40, 0x4c, 0x4f, 0x1f, 0x4e, 0xc5, 0x43, 0x4e, 0x4e, 0x4c, 0x1b, 0x1f, 0x13, 0x47, 0x44, 0x58, 0x1f, 0x45, 0x4b, 0x44, 0x43, 0x9f, 0x53, 0x47, 0x44, 0x48, 0x51, 0x1f, 0x47, 0x40, 0x4b, 0x4b, 0x1d, 0x1f, 0x53, 0x4e, 0x9f, ], [0x43, 0x58, 0x48, 0x4d, 0x46, 0x1f, 0x45, 0x40, 0x4b, 0x4b, 0x1f, 0x1, 0x44, 0x4d, 0x44, 0x40, 0x53, 0xc7, 0x47, 0x48, 0x52, 0x1f, 0x45, 0x44, 0x44, 0x53, 0x1d, 0x1f, 0x41, 0x44, 0x4d, 0x44, 0x40, 0x53, 0x47, 0x9f, 0x53, 0x47, 0x44, 0x1f, 0x4c, 0x4e, 0x4e, 0x4d, 0x1b, 0x1f, 0x5, 0x40, 0x51, 0x1f, 0x4e, 0x55, 0x44, 0xd1, 0x53, 0x47, 0x44, 0x1f, 0x4c, 0x48, 0x52, 0x53, 0x58, 0x9f, 0x4c, 0x4e, 0x54, 0x4d, 0x53, 0x40, 0x48, 0x4d, 0x52, 0x1f, 0x46, 0x51, 0x48, 0x4c, 0x1f, 0x13, 0x4e, 0x9f, 0x43, 0x54, 0x4d, 0x46, 0x44, 0x4e, 0x4d, 0x52, 0x1f, 0x43, 0x44, 0x44, 0x4f, 0x1f, 0x40, 0x4d, 0x43, 0x9f, 0x42, 0x40, 0x55, 0x44, 0x51, 0x4d, 0x52, 0x1f, 0x43, 0x48, 0x4c, 0x1f, 0x16, 0x44, 0x9f, 0x4c, 0x54, 0x52, 0x53, 0x1f, 0x40, 0x56, 0x40, 0x58, 0x1d, 0x1f, 0x44, 0x51, 0x44, 0x9f, ], [0x41, 0x51, 0x44, 0x40, 0x4a, 0x1f, 0x4e, 0x45, 0x1f, 0x43, 0x40, 0x58, 0x1d, 0x1f, 0x13, 0x4e, 0x9f, 0x56, 0x48, 0x4d, 0x1f, 0x4e, 0x54, 0x51, 0x1f, 0x47, 0x40, 0x51, 0x4f, 0x52, 0x1f, 0x40, 0x4d, 0x43, 0x9f, 0x46, 0x4e, 0x4b, 0x43, 0x1f, 0x45, 0x51, 0x4e, 0x4c, 0x1f, 0x47, 0x48, 0x4c, 0x1a, 0x9f, 0x1c, 0x13, 0x4e, 0x4b, 0x4a, 0x48, 0x44, 0x4d, 0x1f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, ]], +[[0x11, 0x4e, 0x40, 0x43, 0x52, 0x1f, 0x46, 0x4e, 0x1f, 0x44, 0x55, 0x44, 0x51, 0x1f, 0x44, 0x55, 0x44, 0xd1, 0x4e, 0x4d, 0x1d, 0x1f, 0xe, 0x55, 0x44, 0x51, 0x1f, 0x51, 0x4e, 0x42, 0x4a, 0x1f, 0x40, 0x4d, 0x43, 0x9f, 0x54, 0x4d, 0x43, 0x44, 0x51, 0x1f, 0x53, 0x51, 0x44, 0x44, 0x1d, 0x1f, 0x1, 0x58, 0x9f, 0x42, 0x40, 0x55, 0x44, 0x52, 0x1f, 0x56, 0x47, 0x44, 0x51, 0x44, 0x1f, 0x4d, 0x44, 0x55, 0x44, 0x51, 0x9f, 0x52, 0x54, 0x4d, 0x1f, 0x47, 0x40, 0x52, 0x1f, 0x52, 0x47, 0x4e, 0x4d, 0x44, 0x1d, 0x1f, 0x1, 0x58, 0x9f, 0x52, 0x53, 0x51, 0x44, 0x40, 0x4c, 0x52, 0x1f, 0x53, 0x47, 0x40, 0x53, 0x1f, 0x4d, 0x44, 0x55, 0x44, 0xd1, 0x45, 0x48, 0x4d, 0x43, 0x1f, 0x53, 0x47, 0x44, 0x1f, 0x52, 0x44, 0x40, 0x1b, 0x1f, 0xe, 0x55, 0x44, 0xd1, 0x52, 0x4d, 0x4e, 0x56, 0x1f, 0x41, 0x58, 0x1f, 0x56, 0x48, 0x4d, 0x53, 0x44, 0x51, 0x9f, ], [0x52, 0x4e, 0x56, 0x4d, 0x1d, 0x1f, 0x0, 0x4d, 0x43, 0x1f, 0x53, 0x47, 0x51, 0x4e, 0x54, 0x46, 0x47, 0x9f, 0x53, 0x47, 0x44, 0x1f, 0x4c, 0x44, 0x51, 0x51, 0x58, 0x1f, 0x45, 0x4b, 0x4e, 0x56, 0x44, 0x51, 0x52, 0x9f, 0x4e, 0x45, 0x1f, 0x9, 0x54, 0x4d, 0x44, 0x1d, 0x1f, 0xe, 0x55, 0x44, 0x51, 0x9f, 0x46, 0x51, 0x40, 0x52, 0x52, 0x1f, 0x40, 0x4d, 0x43, 0x1f, 0x4e, 0x55, 0x44, 0x51, 0x9f, 0x52, 0x53, 0x4e, 0x4d, 0x44, 0x1d, 0x1f, 0x0, 0x4d, 0x43, 0x1f, 0x54, 0x4d, 0x43, 0x44, 0x51, 0x9f, 0x4c, 0x4e, 0x54, 0x4d, 0x53, 0x40, 0x48, 0x4d, 0x52, 0x1f, 0x4e, 0x45, 0x1f, 0x53, 0x47, 0x44, 0x9f, 0x4c, 0x4e, 0x4e, 0x4d, 0x1b, 0x1f, 0x11, 0x4e, 0x40, 0x43, 0x52, 0x1f, 0x46, 0x4e, 0x9f, 0x44, 0x55, 0x44, 0x51, 0x1f, 0x44, 0x55, 0x44, 0x51, 0x1f, 0x4e, 0x4d, 0x1f, 0x14, 0x4d, 0x43, 0x44, 0xd1, ], [0x42, 0x4b, 0x4e, 0x54, 0x43, 0x1f, 0x40, 0x4d, 0x43, 0x1f, 0x54, 0x4d, 0x43, 0x44, 0x51, 0x9f, 0x52, 0x53, 0x40, 0x51, 0x1d, 0x1f, 0x18, 0x44, 0x53, 0x1f, 0x45, 0x44, 0x44, 0x53, 0x9f, 0x53, 0x47, 0x40, 0x53, 0x1f, 0x56, 0x40, 0x4d, 0x43, 0x44, 0x51, 0x48, 0x4d, 0x46, 0x9f, 0x47, 0x40, 0x55, 0x44, 0x1f, 0x46, 0x4e, 0x4d, 0x44, 0x1f, 0x13, 0x54, 0x51, 0x4d, 0x1f, 0x40, 0x53, 0x9f, 0x4b, 0x40, 0x52, 0x53, 0x1f, 0x53, 0x4e, 0x1f, 0x47, 0x4e, 0x4c, 0x44, 0x1f, 0x40, 0x45, 0x40, 0x51, 0x9b, 0x4, 0x58, 0x44, 0x52, 0x1f, 0x53, 0x47, 0x40, 0x53, 0x1f, 0x45, 0x48, 0x51, 0x44, 0x1f, 0x40, 0x4d, 0xc3, 0x52, 0x56, 0x4e, 0x51, 0x43, 0x1f, 0x47, 0x40, 0x55, 0x44, 0x1f, 0x52, 0x44, 0x44, 0x4d, 0x9f, 0x0, 0x4d, 0x43, 0x1f, 0x47, 0x4e, 0x51, 0x51, 0x4e, 0x51, 0x1f, 0x48, 0x4d, 0x1f, 0x53, 0x47, 0x44, 0x9f, ], [0x47, 0x40, 0x4b, 0x4b, 0x52, 0x1f, 0x4e, 0x45, 0x1f, 0x52, 0x53, 0x4e, 0x4d, 0x44, 0x9f, 0xb, 0x4e, 0x4e, 0x4a, 0x1f, 0x40, 0x53, 0x1f, 0x4b, 0x40, 0x52, 0x53, 0x1f, 0x4e, 0x4d, 0x9f, 0x4c, 0x44, 0x40, 0x43, 0x4e, 0x56, 0x52, 0x1f, 0x46, 0x51, 0x44, 0x44, 0x4d, 0x1f, 0x0, 0x4d, 0x43, 0x9f, 0x53, 0x51, 0x44, 0x44, 0x52, 0x1f, 0x40, 0x4d, 0x43, 0x1f, 0x47, 0x48, 0x4b, 0x4b, 0x52, 0x9f, 0x53, 0x47, 0x44, 0x58, 0x1f, 0x4b, 0x4e, 0x4d, 0x46, 0x1f, 0x47, 0x40, 0x55, 0x44, 0x9f, 0x4a, 0x4d, 0x4e, 0x56, 0x4d, 0x1b, 0x1f, 0x1c, 0x13, 0x4e, 0x4b, 0x4a, 0x48, 0x44, 0x4d, 0x1f, 0x9f, 0x9f, 0x9f, ]], +[[0x13, 0x47, 0x51, 0x44, 0x44, 0x1f, 0x11, 0x48, 0x4d, 0x46, 0x52, 0x1f, 0x45, 0x4e, 0x51, 0x9f, 0x53, 0x47, 0x44, 0x1f, 0x4, 0x4b, 0x55, 0x44, 0x4d, 0x1c, 0x4a, 0x48, 0x4d, 0x46, 0x52, 0x9f, 0x54, 0x4d, 0x43, 0x44, 0x51, 0x1f, 0x53, 0x47, 0x44, 0x1f, 0x52, 0x4a, 0x58, 0x1d, 0x9f, 0x12, 0x44, 0x55, 0x44, 0x4d, 0x1f, 0x45, 0x4e, 0x51, 0x1f, 0x53, 0x47, 0x44, 0x9f, 0x3, 0x56, 0x40, 0x51, 0x45, 0x1c, 0x4b, 0x4e, 0x51, 0x43, 0x52, 0x1f, 0x48, 0x4d, 0x9f, 0x53, 0x47, 0x44, 0x48, 0x51, 0x1f, 0x47, 0x40, 0x4b, 0x4b, 0x52, 0x1f, 0x4e, 0x45, 0x9f, 0x52, 0x53, 0x4e, 0x4d, 0x44, 0x1d, 0x1f, 0xd, 0x48, 0x4d, 0x44, 0x1f, 0x45, 0x4e, 0x51, 0x9f, 0xc, 0x4e, 0x51, 0x53, 0x40, 0x4b, 0x1f, 0xc, 0x44, 0x4d, 0x1f, 0x43, 0x4e, 0x4e, 0x4c, 0x44, 0x43, 0x9f, ], [0x53, 0x4e, 0x1f, 0x43, 0x48, 0x44, 0x1d, 0x1f, 0xe, 0x4d, 0x44, 0x1f, 0x45, 0x4e, 0x51, 0x9f, 0x53, 0x47, 0x44, 0x1f, 0x3, 0x40, 0x51, 0x4a, 0x1f, 0xb, 0x4e, 0x51, 0x43, 0x1f, 0x4e, 0x4d, 0x9f, 0x47, 0x48, 0x52, 0x1f, 0x43, 0x40, 0x51, 0x4a, 0x1f, 0x53, 0x47, 0x51, 0x4e, 0x4d, 0x44, 0x1f, 0x8, 0xcd, 0x53, 0x47, 0x44, 0x1f, 0xb, 0x40, 0x4d, 0x43, 0x1f, 0x4e, 0x45, 0x1f, 0xc, 0x4e, 0x51, 0x43, 0x4e, 0xd1, 0x56, 0x47, 0x44, 0x51, 0x44, 0x1f, 0x53, 0x47, 0x44, 0x1f, 0x12, 0x47, 0x40, 0x43, 0x4e, 0x56, 0x52, 0x9f, 0x4b, 0x48, 0x44, 0x1b, 0x1f, 0xe, 0x4d, 0x44, 0x1f, 0x11, 0x48, 0x4d, 0x46, 0x1f, 0x53, 0x4e, 0x9f, 0x51, 0x54, 0x4b, 0x44, 0x1f, 0x53, 0x47, 0x44, 0x4c, 0x1f, 0x40, 0x4b, 0x4b, 0x1d, 0x1f, 0xe, 0x4d, 0xc4, 0x11, 0x48, 0x4d, 0x46, 0x1f, 0x53, 0x4e, 0x1f, 0x45, 0x48, 0x4d, 0x43, 0x1f, 0x53, 0x47, 0x44, 0x4c, 0x9d, ], [0xe, 0x4d, 0x44, 0x1f, 0x11, 0x48, 0x4d, 0x46, 0x1f, 0x53, 0x4e, 0x1f, 0x41, 0x51, 0x48, 0x4d, 0x46, 0x9f, 0x53, 0x47, 0x44, 0x4c, 0x1f, 0x40, 0x4b, 0x4b, 0x1d, 0x1f, 0x40, 0x4d, 0x43, 0x1f, 0x48, 0x4d, 0x9f, 0x53, 0x47, 0x44, 0x1f, 0x43, 0x40, 0x51, 0x4a, 0x4d, 0x44, 0x52, 0x52, 0x1f, 0x41, 0x48, 0x4d, 0x43, 0x9f, 0x53, 0x47, 0x44, 0x4c, 0x1d, 0x1f, 0x8, 0x4d, 0x1f, 0x53, 0x47, 0x44, 0x1f, 0xb, 0x40, 0x4d, 0x43, 0x9f, 0x4e, 0x45, 0x1f, 0xc, 0x4e, 0x51, 0x43, 0x4e, 0x51, 0x1f, 0x56, 0x47, 0x44, 0x51, 0x44, 0x9f, 0x53, 0x47, 0x44, 0x1f, 0x12, 0x47, 0x40, 0x43, 0x4e, 0x56, 0x52, 0x1f, 0x4b, 0x48, 0x44, 0x1b, 0x9f, 0x1c, 0x13, 0x4e, 0x4b, 0x4a, 0x48, 0x44, 0x4d, 0x1f, 0x9f, 0x9f, ]], +[[0x0, 0x4b, 0x4b, 0x1f, 0x53, 0x47, 0x40, 0x53, 0x1f, 0x48, 0x52, 0x1f, 0x46, 0x4e, 0x4b, 0x43, 0x9f, 0x43, 0x4e, 0x44, 0x52, 0x1f, 0x4d, 0x4e, 0x53, 0x1f, 0x46, 0x4b, 0x48, 0x53, 0x53, 0x44, 0x51, 0x1d, 0x9f, 0xd, 0x4e, 0x53, 0x1f, 0x40, 0x4b, 0x4b, 0x1f, 0x53, 0x47, 0x4e, 0x52, 0x44, 0x1f, 0x56, 0x47, 0x4e, 0x9f, 0x56, 0x40, 0x4d, 0x43, 0x44, 0x51, 0x1f, 0x40, 0x51, 0x44, 0x1f, 0x4b, 0x4e, 0x52, 0x53, 0x1b, 0x9f, 0x13, 0x47, 0x44, 0x1f, 0x4e, 0x4b, 0x43, 0x1f, 0x53, 0x47, 0x40, 0x53, 0x1f, 0x48, 0x52, 0x9f, 0x52, 0x53, 0x51, 0x4e, 0x4d, 0x46, 0x1f, 0x43, 0x4e, 0x44, 0x52, 0x1f, 0x4d, 0x4e, 0x53, 0x9f, 0x56, 0x48, 0x53, 0x47, 0x44, 0x51, 0x1d, 0x1f, 0x3, 0x44, 0x44, 0x4f, 0x1f, 0x51, 0x4e, 0x4e, 0x53, 0xd2, 0x40, 0x51, 0x44, 0x1f, 0x4d, 0x4e, 0x53, 0x1f, 0x51, 0x44, 0x40, 0x42, 0x47, 0x44, 0x43, 0x1f, 0x41, 0xd8, ], [0x53, 0x47, 0x44, 0x1f, 0x45, 0x51, 0x4e, 0x52, 0x53, 0x1b, 0x1f, 0x5, 0x51, 0x4e, 0x4c, 0x9f, 0x53, 0x47, 0x44, 0x1f, 0x40, 0x52, 0x47, 0x44, 0x52, 0x1f, 0x40, 0x1f, 0x45, 0x48, 0x51, 0x44, 0x9f, 0x52, 0x47, 0x40, 0x4b, 0x4b, 0x1f, 0x41, 0x44, 0x1f, 0x56, 0x4e, 0x4a, 0x44, 0x4d, 0x1d, 0x1f, 0x0, 0x9f, 0x4b, 0x48, 0x46, 0x47, 0x53, 0x1f, 0x45, 0x51, 0x4e, 0x4c, 0x1f, 0x53, 0x47, 0x44, 0x9f, 0x52, 0x47, 0x40, 0x43, 0x4e, 0x56, 0x52, 0x1f, 0x52, 0x47, 0x40, 0x4b, 0x4b, 0x9f, 0x52, 0x4f, 0x51, 0x48, 0x4d, 0x46, 0x1b, 0x1f, 0x11, 0x44, 0x4d, 0x44, 0x56, 0x44, 0x43, 0x9f, 0x52, 0x47, 0x40, 0x4b, 0x4b, 0x1f, 0x41, 0x44, 0x1f, 0x41, 0x4b, 0x40, 0x43, 0x44, 0x9f, 0x53, 0x47, 0x40, 0x53, 0x1f, 0x56, 0x40, 0x52, 0x1f, 0x41, 0x51, 0x4e, 0x4a, 0x44, 0x4d, 0x1d, 0x9f, ], [0x13, 0x47, 0x44, 0x1f, 0x42, 0x51, 0x4e, 0x56, 0x4d, 0x4b, 0x44, 0x52, 0x52, 0x9f, 0x40, 0x46, 0x40, 0x48, 0x4d, 0x1f, 0x52, 0x47, 0x40, 0x4b, 0x4b, 0x1f, 0x41, 0x44, 0x9f, 0x4a, 0x48, 0x4d, 0x46, 0x1b, 0x1f, 0x1c, 0x13, 0x4e, 0x4b, 0x4a, 0x48, 0x44, 0x4d, 0x1f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, ]], +[[0x16, 0x47, 0x4e, 0x1f, 0x56, 0x48, 0x4b, 0x4b, 0x52, 0x1d, 0x1f, 0x2, 0x40, 0x4d, 0x1b, 0x9f, 0x16, 0x47, 0x4e, 0x1f, 0x53, 0x51, 0x48, 0x44, 0x52, 0x1d, 0x1f, 0x3, 0x4e, 0x44, 0x52, 0x1b, 0x9f, 0x16, 0x47, 0x4e, 0x1f, 0x4b, 0x4e, 0x55, 0x44, 0x52, 0x1d, 0x1f, 0xb, 0x48, 0x55, 0x44, 0x52, 0x1b, 0x9f, 0x1c, 0xc, 0x42, 0x2, 0x40, 0x45, 0x45, 0x51, 0x44, 0x58, 0x1f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, ]], +[[0x6, 0x4e, 0x4d, 0x44, 0x1f, 0x40, 0x56, 0x40, 0x58, 0x1d, 0x1f, 0x46, 0x4e, 0x4d, 0x44, 0x9f, 0x40, 0x47, 0x44, 0x40, 0x43, 0x1d, 0x1f, 0x4, 0x42, 0x47, 0x4e, 0x44, 0x52, 0x1f, 0x51, 0x4e, 0x4b, 0xcb, 0x54, 0x4d, 0x40, 0x4d, 0x52, 0x56, 0x44, 0x51, 0x44, 0x43, 0x1b, 0x1f, 0x4, 0x4c, 0x4f, 0x53, 0x58, 0x9d, 0x4e, 0x4f, 0x44, 0x4d, 0x1d, 0x1f, 0x43, 0x54, 0x52, 0x53, 0x58, 0x1d, 0x1f, 0x43, 0x44, 0x40, 0x43, 0x9d, 0x16, 0x47, 0x58, 0x1f, 0x47, 0x40, 0x55, 0x44, 0x1f, 0x40, 0x4b, 0x4b, 0x1f, 0x53, 0x47, 0x44, 0x9f, 0x16, 0x44, 0x58, 0x51, 0x45, 0x4e, 0x4b, 0x4a, 0x1f, 0x45, 0x4b, 0x44, 0x43, 0x1e, 0x9f, 0x16, 0x47, 0x44, 0x51, 0x44, 0x1f, 0x47, 0x40, 0x55, 0x44, 0x1f, 0x43, 0x51, 0x40, 0x46, 0x4e, 0x4d, 0xd2, 0x46, 0x4e, 0x4d, 0x44, 0x1f, 0x53, 0x4e, 0x46, 0x44, 0x53, 0x47, 0x44, 0x51, 0x1e, 0x9f, ], [0xb, 0x44, 0x40, 0x55, 0x48, 0x4d, 0x46, 0x1f, 0x16, 0x44, 0x58, 0x51, 0x52, 0x1f, 0x53, 0x4e, 0x9f, 0x56, 0x48, 0x4d, 0x43, 0x1f, 0x40, 0x4d, 0x43, 0x1f, 0x56, 0x44, 0x40, 0x53, 0x47, 0x44, 0x51, 0x1e, 0x9f, 0x12, 0x44, 0x53, 0x53, 0x48, 0x4d, 0x46, 0x1f, 0x47, 0x44, 0x51, 0x43, 0x41, 0x44, 0x40, 0x52, 0x53, 0xd2, 0x45, 0x51, 0x44, 0x44, 0x1f, 0x4e, 0x45, 0x1f, 0x53, 0x44, 0x53, 0x47, 0x44, 0x51, 0x1e, 0x9f, 0x6, 0x4e, 0x4d, 0x44, 0x1d, 0x1f, 0x4e, 0x54, 0x51, 0x9f, 0x52, 0x40, 0x45, 0x44, 0x46, 0x54, 0x40, 0x51, 0x43, 0x52, 0x1d, 0x1f, 0x46, 0x4e, 0x4d, 0x44, 0x1d, 0x9f, 0x41, 0x54, 0x53, 0x1f, 0x56, 0x47, 0x48, 0x53, 0x47, 0x44, 0x51, 0x1e, 0x1f, 0x7, 0x40, 0x55, 0x44, 0x9f, 0x53, 0x47, 0x44, 0x58, 0x1f, 0x45, 0x4b, 0x4e, 0x56, 0x4d, 0x1f, 0x53, 0x4e, 0x1f, 0x52, 0x4e, 0x4c, 0xc4, ], [0x4d, 0x44, 0x56, 0x1f, 0x16, 0x44, 0x58, 0x51, 0x1f, 0x16, 0x47, 0x44, 0x4d, 0x9f, 0x42, 0x51, 0x54, 0x44, 0x4b, 0x1f, 0x13, 0x47, 0x51, 0x44, 0x40, 0x43, 0x52, 0x1f, 0x52, 0x4e, 0x4c, 0xc4, 0x4e, 0x53, 0x47, 0x44, 0x51, 0x52, 0x1f, 0x45, 0x44, 0x40, 0x51, 0x1e, 0x1f, 0x0, 0x51, 0x44, 0x9f, 0x53, 0x47, 0x44, 0x58, 0x1f, 0x56, 0x4e, 0x51, 0x4b, 0x43, 0x52, 0x1f, 0x40, 0x56, 0x40, 0x58, 0x9f, 0x45, 0x51, 0x4e, 0x4c, 0x1f, 0x47, 0x44, 0x51, 0x44, 0x1e, 0x1f, 0x16, 0x47, 0x58, 0x1d, 0x9f, 0x4e, 0x47, 0x1d, 0x1f, 0x56, 0x47, 0x58, 0x1d, 0x1f, 0x53, 0x47, 0x44, 0x1f, 0x44, 0x4c, 0x4f, 0x53, 0xd8, 0x16, 0x44, 0x58, 0x51, 0x1e, 0x1f, 0x1c, 0xc, 0x42, 0x2, 0x40, 0x45, 0x45, 0x51, 0x44, 0x58, 0x1f, 0x9f, 0x9f, ]], +[[0xc, 0x51, 0x1b, 0x1f, 0x40, 0x4d, 0x43, 0x1f, 0xc, 0x51, 0x52, 0x1b, 0x9f, 0x3, 0x54, 0x51, 0x52, 0x4b, 0x44, 0x58, 0x1d, 0x1f, 0x4e, 0x45, 0x1f, 0x4d, 0x54, 0x4c, 0x41, 0x44, 0xd1, 0x45, 0x4e, 0x54, 0x51, 0x1d, 0x1f, 0xf, 0x51, 0x48, 0x55, 0x44, 0x53, 0x9f, 0x3, 0x51, 0x48, 0x55, 0x44, 0x1d, 0x1f, 0x56, 0x44, 0x51, 0x44, 0x1f, 0x4f, 0x51, 0x4e, 0x54, 0x43, 0x9f, 0x53, 0x4e, 0x1f, 0x52, 0x40, 0x58, 0x1f, 0x53, 0x47, 0x40, 0x53, 0x1f, 0x53, 0x47, 0x44, 0x58, 0x9f, 0x56, 0x44, 0x51, 0x44, 0x1f, 0x4f, 0x44, 0x51, 0x45, 0x44, 0x42, 0x53, 0x4b, 0x58, 0x9f, 0x4d, 0x4e, 0x51, 0x4c, 0x40, 0x4b, 0x1d, 0x1f, 0x53, 0x47, 0x40, 0x4d, 0x4a, 0x1f, 0x58, 0x4e, 0x54, 0x9f, 0x55, 0x44, 0x51, 0x58, 0x1f, 0x4c, 0x54, 0x42, 0x47, 0x1b, 0x1f, 0x13, 0x47, 0x44, 0x58, 0x9f, ], [0x56, 0x44, 0x51, 0x44, 0x1f, 0x53, 0x47, 0x44, 0x1f, 0x4b, 0x40, 0x52, 0x53, 0x9f, 0x4f, 0x44, 0x4e, 0x4f, 0x4b, 0x44, 0x1f, 0x58, 0x4e, 0x54, 0x5d, 0x43, 0x9f, 0x44, 0x57, 0x4f, 0x44, 0x42, 0x53, 0x1f, 0x53, 0x4e, 0x1f, 0x41, 0x44, 0x9f, 0x48, 0x4d, 0x55, 0x4e, 0x4b, 0x55, 0x44, 0x43, 0x1f, 0x48, 0x4d, 0x9f, 0x40, 0x4d, 0x58, 0x53, 0x47, 0x48, 0x4d, 0x46, 0x1f, 0x52, 0x53, 0x51, 0x40, 0x4d, 0x46, 0x44, 0x9f, 0x4e, 0x51, 0x1f, 0x4c, 0x58, 0x52, 0x53, 0x44, 0x51, 0x48, 0x4e, 0x54, 0x52, 0x1d, 0x9f, 0x41, 0x44, 0x42, 0x40, 0x54, 0x52, 0x44, 0x1f, 0x53, 0x47, 0x44, 0x58, 0x1f, 0x49, 0x54, 0x52, 0x53, 0x9f, 0x43, 0x48, 0x43, 0x4d, 0x5d, 0x53, 0x1f, 0x47, 0x4e, 0x4b, 0x43, 0x1f, 0x56, 0x48, 0x53, 0x47, 0x9f, ], [0x52, 0x54, 0x42, 0x47, 0x1f, 0x4d, 0x4e, 0x4d, 0x52, 0x44, 0x4d, 0x52, 0x44, 0x1b, 0x9f, 0x1c, 0x11, 0x4e, 0x56, 0x4b, 0x48, 0x4d, 0x46, 0x1f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, ]], +[[0xb, 0x4e, 0x55, 0x44, 0x1d, 0x1f, 0x56, 0x47, 0x48, 0x42, 0x47, 0x9f, 0x50, 0x54, 0x48, 0x42, 0x4a, 0x4b, 0x58, 0x1f, 0x40, 0x51, 0x51, 0x44, 0x52, 0x53, 0x52, 0x9f, 0x53, 0x47, 0x44, 0x1f, 0x46, 0x44, 0x4d, 0x53, 0x4b, 0x44, 0x1f, 0x47, 0x44, 0x40, 0x51, 0x53, 0x1d, 0x9f, 0x12, 0x44, 0x48, 0x59, 0x44, 0x43, 0x1f, 0x47, 0x48, 0x4c, 0x1f, 0x56, 0x48, 0x53, 0x47, 0x1f, 0x4c, 0xd8, 0x41, 0x44, 0x40, 0x54, 0x53, 0x48, 0x45, 0x54, 0x4b, 0x1f, 0x45, 0x4e, 0x51, 0x4c, 0x9f, 0x13, 0x47, 0x40, 0x53, 0x1f, 0x56, 0x40, 0x52, 0x1f, 0x53, 0x40, 0x4a, 0x44, 0x4d, 0x9f, 0x45, 0x51, 0x4e, 0x4c, 0x1f, 0x4c, 0x44, 0x1d, 0x1f, 0x48, 0x4d, 0x1f, 0x40, 0x9f, 0x4c, 0x40, 0x4d, 0x4d, 0x44, 0x51, 0x1f, 0x56, 0x47, 0x48, 0x42, 0x47, 0x1f, 0x52, 0x53, 0x48, 0x4b, 0xcb, ], [0x46, 0x51, 0x48, 0x44, 0x55, 0x44, 0x52, 0x1f, 0x4c, 0x44, 0x1b, 0x1f, 0xb, 0x4e, 0x55, 0x44, 0x1d, 0x9f, 0x56, 0x47, 0x48, 0x42, 0x47, 0x1f, 0x4f, 0x40, 0x51, 0x43, 0x4e, 0x4d, 0x52, 0x1f, 0x4d, 0x4e, 0x9f, 0x41, 0x44, 0x4b, 0x4e, 0x55, 0x44, 0x43, 0x1f, 0x45, 0x51, 0x4e, 0x4c, 0x9f, 0x4b, 0x4e, 0x55, 0x48, 0x4d, 0x46, 0x1d, 0x1f, 0x53, 0x4e, 0x4e, 0x4a, 0x1f, 0x4c, 0x44, 0x1f, 0x52, 0xce, 0x52, 0x53, 0x51, 0x4e, 0x4d, 0x46, 0x4b, 0x58, 0x1f, 0x56, 0x48, 0x53, 0x47, 0x9f, 0x43, 0x44, 0x4b, 0x48, 0x46, 0x47, 0x53, 0x1f, 0x48, 0x4d, 0x1f, 0x47, 0x48, 0x4c, 0x9f, 0x13, 0x47, 0x40, 0x53, 0x1d, 0x1f, 0x40, 0x52, 0x1f, 0x58, 0x4e, 0x54, 0x1f, 0x52, 0x44, 0x44, 0x1d, 0x9f, 0x48, 0x53, 0x1f, 0x52, 0x53, 0x48, 0x4b, 0x4b, 0x1f, 0x40, 0x41, 0x40, 0x4d, 0x43, 0x4e, 0x4d, 0x52, 0x9f, ], [0x4c, 0x44, 0x1f, 0x4d, 0x4e, 0x53, 0x1b, 0x1b, 0x1b, 0x9f, 0x1c, 0x0, 0x4b, 0x48, 0x46, 0x47, 0x48, 0x44, 0x51, 0x48, 0x1f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, ]], +[[0x8, 0x1f, 0x42, 0x40, 0x4d, 0x4d, 0x4e, 0x53, 0x1f, 0x44, 0x57, 0x4f, 0x51, 0x44, 0x52, 0x52, 0x9f, 0x48, 0x53, 0x1d, 0x1f, 0x41, 0x54, 0x53, 0x1f, 0x52, 0x54, 0x51, 0x44, 0x4b, 0x58, 0x1f, 0x58, 0x4e, 0xd4, 0x40, 0x4d, 0x43, 0x1f, 0x44, 0x55, 0x44, 0x51, 0x58, 0x41, 0x4e, 0x43, 0x58, 0x1f, 0x47, 0x40, 0x55, 0xc4, 0x40, 0x1f, 0x4d, 0x4e, 0x53, 0x48, 0x4e, 0x4d, 0x1f, 0x53, 0x47, 0x40, 0x53, 0x9f, 0x53, 0x47, 0x44, 0x51, 0x44, 0x1f, 0x48, 0x52, 0x1f, 0x4e, 0x51, 0x1f, 0x52, 0x47, 0x4e, 0x54, 0x4b, 0xc3, 0x41, 0x44, 0x1f, 0x40, 0x4d, 0x1f, 0x44, 0x57, 0x48, 0x52, 0x53, 0x44, 0x4d, 0x42, 0x44, 0x1f, 0x4e, 0xc5, 0x58, 0x4e, 0x54, 0x51, 0x52, 0x1f, 0x41, 0x44, 0x58, 0x4e, 0x4d, 0x43, 0x1f, 0x58, 0x4e, 0x54, 0x1b, 0x9f, 0x16, 0x47, 0x40, 0x53, 0x1f, 0x56, 0x44, 0x51, 0x44, 0x1f, 0x53, 0x47, 0x44, 0x1f, 0x54, 0x52, 0x44, 0x9f, ], [0x4e, 0x45, 0x1f, 0x4c, 0x58, 0x1f, 0x42, 0x51, 0x44, 0x40, 0x53, 0x48, 0x4e, 0x4d, 0x1d, 0x1f, 0x48, 0xc5, 0x8, 0x1f, 0x56, 0x44, 0x51, 0x44, 0x1f, 0x44, 0x4d, 0x53, 0x48, 0x51, 0x44, 0x4b, 0x58, 0x9f, 0x42, 0x4e, 0x4d, 0x53, 0x40, 0x48, 0x4d, 0x44, 0x43, 0x1f, 0x47, 0x44, 0x51, 0x44, 0x1e, 0x1f, 0xc, 0xd8, 0x46, 0x51, 0x44, 0x40, 0x53, 0x1f, 0x4c, 0x48, 0x52, 0x44, 0x51, 0x48, 0x44, 0x52, 0x1f, 0x48, 0x4d, 0x9f, 0x53, 0x47, 0x48, 0x52, 0x1f, 0x56, 0x4e, 0x51, 0x4b, 0x43, 0x1f, 0x47, 0x40, 0x55, 0x44, 0x9f, 0x41, 0x44, 0x44, 0x4d, 0x1f, 0x7, 0x44, 0x40, 0x53, 0x47, 0x42, 0x4b, 0x48, 0x45, 0x45, 0x5d, 0x52, 0x9f, 0x4c, 0x48, 0x52, 0x44, 0x51, 0x48, 0x44, 0x52, 0x1d, 0x1f, 0x40, 0x4d, 0x43, 0x1f, 0x8, 0x9f, 0x56, 0x40, 0x53, 0x42, 0x47, 0x44, 0x43, 0x1f, 0x40, 0x4d, 0x43, 0x1f, 0x45, 0x44, 0x4b, 0x53, 0x9f, ], [0x44, 0x40, 0x42, 0x47, 0x1f, 0x45, 0x51, 0x4e, 0x4c, 0x1f, 0x53, 0x47, 0x44, 0x9f, 0x41, 0x44, 0x46, 0x48, 0x4d, 0x4d, 0x48, 0x4d, 0x46, 0x1b, 0x1f, 0x4c, 0x58, 0x9f, 0x46, 0x51, 0x44, 0x40, 0x53, 0x1f, 0x53, 0x47, 0x4e, 0x54, 0x46, 0x47, 0x53, 0x1f, 0x48, 0x4d, 0x9f, 0x4b, 0x48, 0x55, 0x48, 0x4d, 0x46, 0x1f, 0x48, 0x52, 0x1f, 0x47, 0x48, 0x4c, 0x52, 0x44, 0x4b, 0x45, 0x9b, 0x8, 0x45, 0x1f, 0x40, 0x4b, 0x4b, 0x1f, 0x44, 0x4b, 0x52, 0x44, 0x9f, 0x4f, 0x44, 0x51, 0x48, 0x52, 0x47, 0x44, 0x43, 0x1d, 0x1f, 0x40, 0x4d, 0x43, 0x1f, 0x47, 0x44, 0x9f, 0x51, 0x44, 0x4c, 0x40, 0x48, 0x4d, 0x44, 0x43, 0x1d, 0x1f, 0x8, 0x1f, 0x52, 0x47, 0x4e, 0x54, 0x4b, 0xc3, 0x52, 0x53, 0x48, 0x4b, 0x4b, 0x1f, 0x42, 0x4e, 0x4d, 0x53, 0x48, 0x4d, 0x54, 0x44, 0x1f, 0x53, 0x4e, 0x9f, ], [0x41, 0x44, 0x1d, 0x1f, 0x40, 0x4d, 0x43, 0x1f, 0x48, 0x45, 0x1f, 0x40, 0x4b, 0x4b, 0x9f, 0x44, 0x4b, 0x52, 0x44, 0x1f, 0x51, 0x44, 0x4c, 0x40, 0x48, 0x4d, 0x44, 0x43, 0x1d, 0x1f, 0x40, 0x4d, 0xc3, 0x47, 0x44, 0x1f, 0x56, 0x44, 0x51, 0x44, 0x9f, 0x40, 0x4d, 0x4d, 0x48, 0x47, 0x48, 0x4b, 0x40, 0x53, 0x44, 0x43, 0x1d, 0x1f, 0x53, 0x47, 0x44, 0x9f, 0x54, 0x4d, 0x48, 0x55, 0x44, 0x51, 0x52, 0x44, 0x1f, 0x56, 0x4e, 0x54, 0x4b, 0x43, 0x9f, 0x53, 0x54, 0x51, 0x4d, 0x1f, 0x53, 0x4e, 0x1f, 0x40, 0x1f, 0x4c, 0x48, 0x46, 0x47, 0x53, 0x58, 0x9f, 0x52, 0x53, 0x51, 0x40, 0x4d, 0x46, 0x44, 0x51, 0x1b, 0x1f, 0x8, 0x1f, 0x52, 0x47, 0x4e, 0x54, 0x4b, 0xc3, 0x4d, 0x4e, 0x53, 0x1f, 0x52, 0x44, 0x44, 0x4c, 0x1f, 0x40, 0x1f, 0x4f, 0x40, 0x51, 0x53, 0x1f, 0x4e, 0xc5, ], [0x48, 0x53, 0x1b, 0x1f, 0x1c, 0x1, 0x51, 0x4e, 0x4d, 0x53, 0x44, 0x1f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, ]], +[[0x8, 0x1f, 0x47, 0x40, 0x55, 0x44, 0x1f, 0x43, 0x51, 0x44, 0x40, 0x4c, 0x53, 0x1f, 0x48, 0x4d, 0x9f, 0x4c, 0x58, 0x1f, 0x4b, 0x48, 0x45, 0x44, 0x1d, 0x1f, 0x43, 0x51, 0x44, 0x40, 0x4c, 0x52, 0x9f, 0x53, 0x47, 0x40, 0x53, 0x1f, 0x47, 0x40, 0x55, 0x44, 0x1f, 0x52, 0x53, 0x40, 0x58, 0x44, 0x43, 0x9f, 0x56, 0x48, 0x53, 0x47, 0x1f, 0x4c, 0x44, 0x1f, 0x44, 0x55, 0x44, 0x51, 0x9f, 0x40, 0x45, 0x53, 0x44, 0x51, 0x1d, 0x1f, 0x40, 0x4d, 0x43, 0x1f, 0x42, 0x47, 0x40, 0x4d, 0x46, 0x44, 0xc3, 0x4c, 0x58, 0x1f, 0x48, 0x43, 0x44, 0x40, 0x52, 0x1b, 0x1f, 0x13, 0x47, 0x44, 0x58, 0x9f, 0x47, 0x40, 0x55, 0x44, 0x1f, 0x46, 0x4e, 0x4d, 0x44, 0x1f, 0x53, 0x47, 0x51, 0x4e, 0x54, 0x46, 0x47, 0x9f, 0x40, 0x4d, 0x43, 0x1f, 0x53, 0x47, 0x51, 0x4e, 0x54, 0x46, 0x47, 0x1f, 0x4c, 0x44, 0x1d, 0x9f, ], [0x4b, 0x48, 0x4a, 0x44, 0x1f, 0x56, 0x48, 0x4d, 0x44, 0x1f, 0x53, 0x47, 0x51, 0x4e, 0x54, 0x46, 0x47, 0x9f, 0x56, 0x40, 0x53, 0x44, 0x51, 0x1d, 0x1f, 0x40, 0x4d, 0x43, 0x1f, 0x40, 0x4b, 0x53, 0x44, 0x51, 0x44, 0xc3, 0x53, 0x47, 0x44, 0x1f, 0x42, 0x4e, 0x4b, 0x4e, 0x51, 0x1f, 0x4e, 0x45, 0x1f, 0x4c, 0x58, 0x9f, 0x4c, 0x48, 0x4d, 0x43, 0x1b, 0x1f, 0x0, 0x4d, 0x43, 0x1f, 0x53, 0x47, 0x48, 0x52, 0x1f, 0x48, 0x52, 0x9f, 0x4e, 0x4d, 0x44, 0x1b, 0x1f, 0x8, 0x5d, 0x4c, 0x1f, 0x46, 0x4e, 0x48, 0x4d, 0x46, 0x1f, 0x53, 0x4e, 0x9f, 0x53, 0x44, 0x4b, 0x4b, 0x1f, 0x48, 0x53, 0x1f, 0x1c, 0x1f, 0x41, 0x54, 0x53, 0x1f, 0x53, 0x40, 0x4a, 0xc4, 0x42, 0x40, 0x51, 0x44, 0x1f, 0x4d, 0x4e, 0x53, 0x1f, 0x53, 0x4e, 0x1f, 0x52, 0x4c, 0x48, 0x4b, 0x44, 0x9f, 0x40, 0x53, 0x1f, 0x40, 0x4d, 0x58, 0x1f, 0x4f, 0x40, 0x51, 0x53, 0x1f, 0x4e, 0x45, 0x1f, 0x48, 0x53, 0x9b, ], [0x1c, 0x1, 0x51, 0x4e, 0x4d, 0x53, 0x44, 0x1f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, ]], +[[0x13, 0x47, 0x48, 0x52, 0x1f, 0x52, 0x53, 0x4e, 0x51, 0x58, 0x1f, 0x52, 0x47, 0x40, 0x4b, 0x4b, 0x9f, 0x53, 0x47, 0x44, 0x1f, 0x46, 0x4e, 0x4e, 0x43, 0x1f, 0x4c, 0x40, 0x4d, 0x1f, 0x53, 0x44, 0x40, 0x42, 0xc7, 0x47, 0x48, 0x52, 0x1f, 0x52, 0x4e, 0x4d, 0x1d, 0x1f, 0x0, 0x4d, 0x43, 0x9f, 0x2, 0x51, 0x48, 0x52, 0x4f, 0x48, 0x4d, 0x1f, 0x2, 0x51, 0x48, 0x52, 0x4f, 0x48, 0x40, 0x4d, 0x9f, 0x52, 0x47, 0x40, 0x4b, 0x4b, 0x1f, 0x4d, 0x44, 0x5d, 0x44, 0x51, 0x1f, 0x46, 0x4e, 0x1f, 0x41, 0x58, 0x9d, 0x5, 0x51, 0x4e, 0x4c, 0x1f, 0x53, 0x47, 0x48, 0x52, 0x1f, 0x43, 0x40, 0x58, 0x1f, 0x53, 0x4e, 0x9f, 0x53, 0x47, 0x44, 0x1f, 0x44, 0x4d, 0x43, 0x48, 0x4d, 0x46, 0x1f, 0x4e, 0x45, 0x1f, 0x53, 0x47, 0x44, 0x9f, 0x56, 0x4e, 0x51, 0x4b, 0x43, 0x1d, 0x1f, 0x1, 0x54, 0x53, 0x1f, 0x56, 0x44, 0x1f, 0x48, 0x4d, 0x9f, ], [0x48, 0x53, 0x1f, 0x52, 0x47, 0x40, 0x4b, 0x4b, 0x1f, 0x41, 0x44, 0x9f, 0x51, 0x44, 0x4c, 0x44, 0x4c, 0x41, 0x44, 0x51, 0x44, 0x43, 0x1b, 0x1f, 0x16, 0x44, 0x9f, 0x45, 0x44, 0x56, 0x1d, 0x1f, 0x56, 0x44, 0x1f, 0x47, 0x40, 0x4f, 0x4f, 0x58, 0x1f, 0x45, 0x44, 0x56, 0x9d, 0x56, 0x44, 0x1f, 0x41, 0x40, 0x4d, 0x43, 0x1f, 0x4e, 0x45, 0x9f, 0x41, 0x51, 0x4e, 0x53, 0x47, 0x44, 0x51, 0x52, 0x1b, 0x1f, 0x5, 0x4e, 0x51, 0x1f, 0x47, 0x44, 0x9f, 0x53, 0x4e, 0x1c, 0x43, 0x40, 0x58, 0x1f, 0x53, 0x47, 0x40, 0x53, 0x1f, 0x52, 0x47, 0x44, 0x43, 0x52, 0x9f, 0x47, 0x48, 0x52, 0x1f, 0x41, 0x4b, 0x4e, 0x4e, 0x43, 0x1f, 0x56, 0x48, 0x53, 0x47, 0x1f, 0x4c, 0x44, 0x9f, 0x12, 0x47, 0x40, 0x4b, 0x4b, 0x1f, 0x41, 0x44, 0x1f, 0x4c, 0x58, 0x9f, ], [0x41, 0x51, 0x4e, 0x53, 0x47, 0x44, 0x51, 0x1b, 0x9f, 0x1c, 0x12, 0x47, 0x40, 0x4a, 0x44, 0x52, 0x4f, 0x44, 0x40, 0x51, 0x44, 0x1f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, ]], +[[0x7, 0x4e, 0x56, 0x44, 0x55, 0x44, 0x51, 0x1f, 0x4c, 0x44, 0x40, 0x4d, 0x1f, 0x58, 0x4e, 0x54, 0x51, 0x9f, 0x4b, 0x48, 0x45, 0x44, 0x1f, 0x48, 0x52, 0x1d, 0x1f, 0x4c, 0x44, 0x44, 0x53, 0x1f, 0x48, 0x53, 0x9f, 0x40, 0x4d, 0x43, 0x1f, 0x4b, 0x48, 0x55, 0x44, 0x1f, 0x48, 0x53, 0x1b, 0x1f, 0x3, 0x4e, 0x9f, 0x4d, 0x4e, 0x53, 0x1f, 0x52, 0x47, 0x54, 0x4d, 0x1f, 0x48, 0x53, 0x1f, 0x40, 0x4d, 0x43, 0x9f, 0x42, 0x40, 0x4b, 0x4b, 0x1f, 0x48, 0x53, 0x1f, 0x47, 0x40, 0x51, 0x43, 0x9f, 0x4d, 0x40, 0x4c, 0x44, 0x52, 0x1b, 0x1f, 0x8, 0x53, 0x1f, 0x48, 0x52, 0x1f, 0x4d, 0x4e, 0x53, 0x9f, 0x52, 0x4e, 0x1f, 0x41, 0x40, 0x43, 0x1f, 0x40, 0x52, 0x1f, 0x58, 0x4e, 0x54, 0x1f, 0x40, 0x51, 0x44, 0x9b, 0x8, 0x53, 0x1f, 0x4b, 0x4e, 0x4e, 0x4a, 0x52, 0x1f, 0x4f, 0x4e, 0x4e, 0x51, 0x44, 0x52, 0x53, 0x9f, ], [0x56, 0x47, 0x44, 0x4d, 0x1f, 0x58, 0x4e, 0x54, 0x1f, 0x40, 0x51, 0x44, 0x9f, 0x51, 0x48, 0x42, 0x47, 0x44, 0x52, 0x53, 0x1b, 0x1f, 0x13, 0x47, 0x44, 0x9f, 0x45, 0x40, 0x54, 0x4b, 0x53, 0x1c, 0x45, 0x48, 0x4d, 0x43, 0x44, 0x51, 0x1f, 0x56, 0x48, 0x4b, 0x4b, 0x9f, 0x45, 0x48, 0x4d, 0x43, 0x1f, 0x45, 0x40, 0x54, 0x4b, 0x53, 0x52, 0x1f, 0x44, 0x55, 0x44, 0x4d, 0x9f, 0x48, 0x4d, 0x1f, 0x4f, 0x40, 0x51, 0x40, 0x43, 0x48, 0x52, 0x44, 0x1b, 0x1f, 0xb, 0x4e, 0x55, 0x44, 0x9f, 0x58, 0x4e, 0x54, 0x51, 0x1f, 0x4b, 0x48, 0x45, 0x44, 0x1d, 0x1f, 0x4f, 0x4e, 0x4e, 0x51, 0x1f, 0x40, 0xd2, 0x48, 0x53, 0x1f, 0x48, 0x52, 0x1b, 0x1f, 0x18, 0x4e, 0x54, 0x1f, 0x4c, 0x40, 0x58, 0x9f, 0x4f, 0x44, 0x51, 0x47, 0x40, 0x4f, 0x52, 0x1f, 0x47, 0x40, 0x55, 0x44, 0x1f, 0x52, 0x4e, 0x4c, 0x44, 0x9f, ], [0x4f, 0x4b, 0x44, 0x40, 0x52, 0x40, 0x4d, 0x53, 0x1d, 0x9f, 0x53, 0x47, 0x51, 0x48, 0x4b, 0x4b, 0x48, 0x4d, 0x46, 0x1d, 0x9f, 0x46, 0x4b, 0x4e, 0x51, 0x48, 0x4e, 0x54, 0x52, 0x1f, 0x47, 0x4e, 0x54, 0x51, 0x52, 0x1d, 0x9f, 0x44, 0x55, 0x44, 0x4d, 0x1f, 0x48, 0x4d, 0x1f, 0x40, 0x9f, 0x4f, 0x4e, 0x4e, 0x51, 0x47, 0x4e, 0x54, 0x52, 0x44, 0x1b, 0x1f, 0x13, 0x47, 0x44, 0x9f, 0x52, 0x44, 0x53, 0x53, 0x48, 0x4d, 0x46, 0x1f, 0x52, 0x54, 0x4d, 0x1f, 0x48, 0x52, 0x9f, 0x51, 0x44, 0x45, 0x4b, 0x44, 0x42, 0x53, 0x44, 0x43, 0x1f, 0x45, 0x51, 0x4e, 0x4c, 0x1f, 0x53, 0x47, 0xc4, 0x56, 0x48, 0x4d, 0x43, 0x4e, 0x56, 0x52, 0x1f, 0x4e, 0x45, 0x1f, 0x53, 0x47, 0x44, 0x9f, ], [0x40, 0x4b, 0x4c, 0x52, 0x47, 0x4e, 0x54, 0x52, 0x44, 0x1f, 0x40, 0x52, 0x9f, 0x41, 0x51, 0x48, 0x46, 0x47, 0x53, 0x4b, 0x58, 0x1f, 0x40, 0x52, 0x1f, 0x45, 0x51, 0x4e, 0x4c, 0x9f, 0x53, 0x47, 0x44, 0x1f, 0x51, 0x48, 0x42, 0x47, 0x1f, 0x4c, 0x40, 0x4d, 0x5d, 0x52, 0x9f, 0x40, 0x41, 0x4e, 0x43, 0x44, 0x1b, 0x1f, 0x53, 0x47, 0x44, 0x1f, 0x52, 0x4d, 0x4e, 0x56, 0x9f, 0x4c, 0x44, 0x4b, 0x53, 0x52, 0x1f, 0x41, 0x44, 0x45, 0x4e, 0x51, 0x44, 0x1f, 0x48, 0x53, 0x52, 0x9f, 0x43, 0x4e, 0x4e, 0x51, 0x1f, 0x40, 0x52, 0x1f, 0x44, 0x40, 0x51, 0x4b, 0x58, 0x1f, 0x48, 0x4d, 0x9f, 0x53, 0x47, 0x44, 0x1f, 0x52, 0x4f, 0x51, 0x48, 0x4d, 0x46, 0x1b, 0x1f, 0x8, 0x1f, 0x43, 0x4e, 0x9f, 0x4d, 0x4e, 0x53, 0x1f, 0x52, 0x44, 0x44, 0x1f, 0x41, 0x54, 0x53, 0x1f, 0x40, 0x9f, ], [0x50, 0x54, 0x48, 0x44, 0x53, 0x1f, 0x4c, 0x48, 0x4d, 0x43, 0x1f, 0x4c, 0x40, 0x58, 0x9f, 0x4b, 0x48, 0x55, 0x44, 0x1f, 0x40, 0x52, 0x9f, 0x42, 0x4e, 0x4d, 0x53, 0x44, 0x4d, 0x53, 0x44, 0x43, 0x4b, 0x58, 0x1f, 0x53, 0x47, 0x44, 0x51, 0x44, 0x9d, 0x40, 0x4d, 0x43, 0x1f, 0x47, 0x40, 0x55, 0x44, 0x1f, 0x40, 0x52, 0x9f, 0x42, 0x47, 0x44, 0x44, 0x51, 0x48, 0x4d, 0x46, 0x1f, 0x53, 0x47, 0x4e, 0x54, 0x46, 0x47, 0x53, 0x52, 0x9d, 0x40, 0x52, 0x1f, 0x48, 0x4d, 0x1f, 0x40, 0x1f, 0x4f, 0x40, 0x4b, 0x40, 0x42, 0x44, 0x1b, 0x9f, 0x1c, 0x13, 0x47, 0x4e, 0x51, 0x44, 0x40, 0x54, 0x1f, 0x9f, 0x9f, ]], +[[0x16, 0x44, 0x1f, 0x4c, 0x54, 0x52, 0x53, 0x1f, 0x4b, 0x44, 0x40, 0x51, 0x4d, 0x1f, 0x53, 0x4e, 0x9f, 0x51, 0x44, 0x40, 0x56, 0x40, 0x4a, 0x44, 0x4d, 0x1f, 0x40, 0x4d, 0x43, 0x1f, 0x4a, 0x44, 0x44, 0x4f, 0x9f, 0x4e, 0x54, 0x51, 0x52, 0x44, 0x4b, 0x55, 0x44, 0x52, 0x1f, 0x40, 0x56, 0x40, 0x4a, 0x44, 0x1d, 0x9f, 0x4d, 0x4e, 0x53, 0x1f, 0x41, 0x58, 0x1f, 0x4c, 0x44, 0x42, 0x47, 0x40, 0x4d, 0x48, 0x42, 0x40, 0x4b, 0x9f, 0x40, 0x48, 0x43, 0x52, 0x1d, 0x1f, 0x41, 0x54, 0x53, 0x1f, 0x41, 0x58, 0x1f, 0x40, 0x4d, 0x9f, 0x48, 0x4d, 0x45, 0x48, 0x4d, 0x48, 0x53, 0x44, 0x9f, 0x44, 0x57, 0x4f, 0x44, 0x42, 0x53, 0x40, 0x53, 0x48, 0x4e, 0x4d, 0x1f, 0x4e, 0x45, 0x1f, 0x53, 0x47, 0xc4, 0x43, 0x40, 0x56, 0x4d, 0x1d, 0x1f, 0x56, 0x47, 0x48, 0x42, 0x47, 0x1f, 0x43, 0x4e, 0x44, 0x52, 0x9f, ], [0x4d, 0x4e, 0x53, 0x1f, 0x45, 0x4e, 0x51, 0x52, 0x40, 0x4a, 0x44, 0x1f, 0x54, 0x52, 0x9f, 0x44, 0x55, 0x44, 0x4d, 0x1f, 0x48, 0x4d, 0x1f, 0x4e, 0x54, 0x51, 0x9f, 0x52, 0x4e, 0x54, 0x4d, 0x43, 0x44, 0x52, 0x53, 0x1f, 0x52, 0x4b, 0x44, 0x44, 0x4f, 0x1b, 0x1f, 0x8, 0x9f, 0x4a, 0x4d, 0x4e, 0x56, 0x1f, 0x4e, 0x45, 0x1f, 0x4d, 0x4e, 0x1f, 0x4c, 0x4e, 0x51, 0x44, 0x9f, 0x44, 0x4d, 0x42, 0x4e, 0x54, 0x51, 0x40, 0x46, 0x48, 0x4d, 0x46, 0x1f, 0x45, 0x40, 0x42, 0x53, 0x9f, 0x53, 0x47, 0x40, 0x4d, 0x1f, 0x53, 0x47, 0x44, 0x9f, 0x54, 0x4d, 0x50, 0x54, 0x44, 0x52, 0x53, 0x48, 0x4e, 0x4d, 0x40, 0x41, 0x4b, 0x44, 0x9f, 0x40, 0x41, 0x48, 0x4b, 0x48, 0x53, 0x58, 0x1f, 0x4e, 0x45, 0x1f, 0x4c, 0x40, 0x4d, 0x1f, 0x53, 0x4e, 0x9f, ], [0x44, 0x4b, 0x44, 0x55, 0x40, 0x53, 0x44, 0x1f, 0x47, 0x48, 0x52, 0x1f, 0x4b, 0x48, 0x45, 0x44, 0x9f, 0x41, 0x58, 0x1f, 0x40, 0x1f, 0x42, 0x4e, 0x4d, 0x52, 0x42, 0x48, 0x4e, 0x54, 0x52, 0x9f, 0x44, 0x4d, 0x43, 0x44, 0x40, 0x55, 0x4e, 0x54, 0x51, 0x1b, 0x1f, 0x8, 0x53, 0x1f, 0x48, 0x52, 0x9f, 0x52, 0x4e, 0x4c, 0x44, 0x53, 0x47, 0x48, 0x4d, 0x46, 0x1f, 0x53, 0x4e, 0x1f, 0x41, 0x44, 0x9f, 0x40, 0x41, 0x4b, 0x44, 0x1f, 0x53, 0x4e, 0x1f, 0x4f, 0x40, 0x48, 0x4d, 0x53, 0x1f, 0x40, 0x9f, 0x4f, 0x40, 0x51, 0x53, 0x48, 0x42, 0x54, 0x4b, 0x40, 0x51, 0x9f, 0x4f, 0x48, 0x42, 0x53, 0x54, 0x51, 0x44, 0x1d, 0x1f, 0x4e, 0x51, 0x1f, 0x53, 0x4e, 0x9f, 0x42, 0x40, 0x51, 0x55, 0x44, 0x1f, 0x40, 0x1f, 0x52, 0x53, 0x40, 0x53, 0x54, 0x44, 0x1d, 0x9f, ], [0x40, 0x4d, 0x43, 0x1f, 0x52, 0x4e, 0x1f, 0x53, 0x4e, 0x1f, 0x4c, 0x40, 0x4a, 0x44, 0x1f, 0x40, 0x9f, 0x45, 0x44, 0x56, 0x1f, 0x4e, 0x41, 0x49, 0x44, 0x42, 0x53, 0x52, 0x9f, 0x41, 0x44, 0x40, 0x54, 0x53, 0x48, 0x45, 0x54, 0x4b, 0x1d, 0x1f, 0x41, 0x54, 0x53, 0x1f, 0x48, 0x53, 0x9f, 0x48, 0x52, 0x1f, 0x45, 0x40, 0x51, 0x1f, 0x4c, 0x4e, 0x51, 0x44, 0x9f, 0x46, 0x4b, 0x4e, 0x51, 0x48, 0x4e, 0x54, 0x52, 0x1f, 0x53, 0x4e, 0x1f, 0x42, 0x40, 0x51, 0x55, 0x44, 0x9f, 0x40, 0x4d, 0x43, 0x1f, 0x4f, 0x40, 0x48, 0x4d, 0x53, 0x1f, 0x53, 0x47, 0x44, 0x1f, 0x55, 0x44, 0x51, 0xd8, 0x40, 0x53, 0x4c, 0x4e, 0x52, 0x4f, 0x47, 0x44, 0x51, 0x44, 0x1f, 0x40, 0x4d, 0x43, 0x9f, 0x4c, 0x44, 0x43, 0x48, 0x54, 0x4c, 0x1f, 0x53, 0x47, 0x51, 0x4e, 0x54, 0x46, 0x47, 0x9f, ], [0x56, 0x47, 0x48, 0x42, 0x47, 0x1f, 0x56, 0x44, 0x1f, 0x4b, 0x4e, 0x4e, 0x4a, 0x1d, 0x9f, 0x56, 0x47, 0x48, 0x42, 0x47, 0x1f, 0x4c, 0x4e, 0x51, 0x40, 0x4b, 0x4b, 0x58, 0x1f, 0x56, 0x44, 0x9f, 0x42, 0x40, 0x4d, 0x1f, 0x43, 0x4e, 0x1b, 0x1f, 0x13, 0x4e, 0x1f, 0x40, 0x45, 0x45, 0x44, 0x42, 0x53, 0x9f, 0x53, 0x47, 0x44, 0x1f, 0x50, 0x54, 0x40, 0x4b, 0x48, 0x53, 0x58, 0x1f, 0x4e, 0x45, 0x1f, 0x53, 0x47, 0xc4, 0x43, 0x40, 0x58, 0x1d, 0x1f, 0x53, 0x47, 0x40, 0x53, 0x1f, 0x48, 0x52, 0x1f, 0x53, 0x47, 0x44, 0x9f, 0x47, 0x48, 0x46, 0x47, 0x44, 0x52, 0x53, 0x1f, 0x4e, 0x45, 0x1f, 0x40, 0x51, 0x53, 0x52, 0x1b, 0x9f, 0x1c, 0x13, 0x47, 0x4e, 0x51, 0x44, 0x40, 0x54, 0x1f, 0x9f, 0x9f, ]], +[[0x8, 0x45, 0x1f, 0x4e, 0x4d, 0x44, 0x1f, 0x40, 0x43, 0x55, 0x40, 0x4d, 0x42, 0x44, 0x52, 0x9f, 0x42, 0x4e, 0x4d, 0x45, 0x48, 0x43, 0x44, 0x4d, 0x53, 0x4b, 0x58, 0x1f, 0x48, 0x4d, 0x1f, 0x53, 0x47, 0xc4, 0x43, 0x48, 0x51, 0x44, 0x42, 0x53, 0x48, 0x4e, 0x4d, 0x1f, 0x4e, 0x45, 0x1f, 0x47, 0x48, 0x52, 0x9f, 0x43, 0x51, 0x44, 0x40, 0x4c, 0x52, 0x1d, 0x1f, 0x40, 0x4d, 0x43, 0x9f, 0x44, 0x4d, 0x43, 0x44, 0x40, 0x55, 0x4e, 0x51, 0x52, 0x1f, 0x53, 0x4e, 0x1f, 0x4b, 0x48, 0x55, 0x44, 0x9f, 0x53, 0x47, 0x44, 0x1f, 0x4b, 0x48, 0x45, 0x44, 0x1f, 0x56, 0x47, 0x48, 0x42, 0x47, 0x1f, 0x47, 0x44, 0x9f, 0x47, 0x40, 0x52, 0x1f, 0x48, 0x4c, 0x40, 0x46, 0x48, 0x4d, 0x44, 0x43, 0x1d, 0x1f, 0x47, 0x44, 0x9f, 0x56, 0x48, 0x4b, 0x4b, 0x1f, 0x4c, 0x44, 0x44, 0x53, 0x1f, 0x56, 0x48, 0x53, 0x47, 0x1f, 0x40, 0x9f, ], [0x52, 0x54, 0x42, 0x42, 0x44, 0x52, 0x52, 0x1f, 0x54, 0x4d, 0x44, 0x57, 0x4f, 0x44, 0x42, 0x53, 0x44, 0xc3, 0x48, 0x4d, 0x1f, 0x42, 0x4e, 0x4c, 0x4c, 0x4e, 0x4d, 0x1f, 0x47, 0x4e, 0x54, 0x51, 0x52, 0x1b, 0x9f, 0x7, 0x44, 0x1f, 0x56, 0x48, 0x4b, 0x4b, 0x1f, 0x4f, 0x54, 0x53, 0x1f, 0x52, 0x4e, 0x4c, 0x44, 0x9f, 0x53, 0x47, 0x48, 0x4d, 0x46, 0x52, 0x1f, 0x41, 0x44, 0x47, 0x48, 0x4d, 0x43, 0x1d, 0x9f, 0x56, 0x48, 0x4b, 0x4b, 0x1f, 0x4f, 0x40, 0x52, 0x52, 0x1f, 0x40, 0x4d, 0x9f, 0x48, 0x4d, 0x55, 0x48, 0x52, 0x48, 0x41, 0x4b, 0x44, 0x9f, 0x41, 0x4e, 0x54, 0x4d, 0x43, 0x40, 0x51, 0x58, 0x1b, 0x1f, 0xd, 0x44, 0x56, 0x1d, 0x9f, 0x54, 0x4d, 0x48, 0x55, 0x44, 0x51, 0x52, 0x40, 0x4b, 0x1d, 0x1f, 0x40, 0x4d, 0x43, 0x9f, ], [0x4c, 0x4e, 0x51, 0x44, 0x1f, 0x4b, 0x48, 0x41, 0x44, 0x51, 0x40, 0x4b, 0x1f, 0x4b, 0x40, 0x56, 0x52, 0x9f, 0x56, 0x48, 0x4b, 0x4b, 0x1f, 0x41, 0x44, 0x46, 0x48, 0x4d, 0x1f, 0x53, 0x4e, 0x9f, 0x44, 0x52, 0x53, 0x40, 0x41, 0x4b, 0x48, 0x52, 0x47, 0x9f, 0x53, 0x47, 0x44, 0x4c, 0x52, 0x44, 0x4b, 0x55, 0x44, 0x52, 0x1f, 0x40, 0x51, 0x4e, 0x54, 0x4d, 0x43, 0x9f, 0x40, 0x4d, 0x43, 0x1f, 0x56, 0x48, 0x53, 0x47, 0x48, 0x4d, 0x1f, 0x47, 0x48, 0x4c, 0x1d, 0x1f, 0x4e, 0xd1, 0x53, 0x47, 0x44, 0x1f, 0x4e, 0x4b, 0x43, 0x1f, 0x4b, 0x40, 0x56, 0x52, 0x1f, 0x41, 0x44, 0x9f, 0x44, 0x57, 0x4f, 0x40, 0x4d, 0x43, 0x44, 0x43, 0x1d, 0x1f, 0x40, 0x4d, 0x43, 0x9f, 0x48, 0x4d, 0x53, 0x44, 0x51, 0x4f, 0x51, 0x44, 0x53, 0x44, 0x43, 0x1f, 0x48, 0x4d, 0x1f, 0x47, 0x48, 0xd2, ], [0x45, 0x40, 0x55, 0x4e, 0x51, 0x1f, 0x48, 0x4d, 0x1f, 0x40, 0x1f, 0x4c, 0x4e, 0x51, 0x44, 0x9f, 0x4b, 0x48, 0x41, 0x44, 0x51, 0x40, 0x4b, 0x1f, 0x52, 0x44, 0x4d, 0x52, 0x44, 0x1d, 0x1f, 0x40, 0x4d, 0xc3, 0x47, 0x44, 0x1f, 0x56, 0x48, 0x4b, 0x4b, 0x1f, 0x4b, 0x48, 0x55, 0x44, 0x1f, 0x56, 0x48, 0x53, 0x47, 0x9f, 0x53, 0x47, 0x44, 0x1f, 0x4b, 0x48, 0x42, 0x44, 0x4d, 0x52, 0x44, 0x1f, 0x4e, 0x45, 0x1f, 0x40, 0x9f, 0x47, 0x48, 0x46, 0x47, 0x44, 0x51, 0x1f, 0x4e, 0x51, 0x43, 0x44, 0x51, 0x1f, 0x4e, 0x45, 0x9f, 0x41, 0x44, 0x48, 0x4d, 0x46, 0x52, 0x1b, 0x1f, 0x9f, 0x9f, 0x9f, ]], +[[0x8, 0x5d, 0x4c, 0x1f, 0x4d, 0x4e, 0x41, 0x4e, 0x43, 0x58, 0x1a, 0x1f, 0x16, 0x47, 0x4e, 0x9f, 0x40, 0x51, 0x44, 0x1f, 0x58, 0x4e, 0x54, 0x1e, 0x1f, 0x0, 0x51, 0x44, 0x1f, 0x58, 0x4e, 0x54, 0x9f, 0x4d, 0x4e, 0x41, 0x4e, 0x43, 0x58, 0x1d, 0x1f, 0x53, 0x4e, 0x4e, 0x1e, 0x1f, 0x13, 0x47, 0x44, 0x4d, 0x9f, 0x53, 0x47, 0x44, 0x51, 0x44, 0x5d, 0x52, 0x1f, 0x40, 0x1f, 0x4f, 0x40, 0x48, 0x51, 0x1f, 0x4e, 0x45, 0x9f, 0x54, 0x52, 0x1d, 0x1f, 0x43, 0x4e, 0x4d, 0x5d, 0x53, 0x1f, 0x53, 0x44, 0x4b, 0x4b, 0x1a, 0x9f, 0x13, 0x47, 0x44, 0x58, 0x5d, 0x43, 0x1f, 0x41, 0x40, 0x4d, 0x48, 0x52, 0x47, 0x1f, 0x54, 0x52, 0x1d, 0x9f, 0x58, 0x4e, 0x54, 0x1f, 0x4a, 0x4d, 0x4e, 0x56, 0x1b, 0x1f, 0x7, 0x4e, 0x56, 0x9f, 0x43, 0x51, 0x44, 0x40, 0x51, 0x58, 0x1f, 0x53, 0x4e, 0x1f, 0x41, 0x44, 0x9f, ], [0x52, 0x4e, 0x4c, 0x44, 0x41, 0x4e, 0x43, 0x58, 0x1a, 0x1f, 0x7, 0x4e, 0x56, 0x9f, 0x4f, 0x54, 0x41, 0x4b, 0x48, 0x42, 0x1d, 0x1f, 0x4b, 0x48, 0x4a, 0x44, 0x1f, 0x40, 0x9f, 0x45, 0x51, 0x4e, 0x46, 0x1f, 0x13, 0x4e, 0x1f, 0x53, 0x44, 0x4b, 0x4b, 0x1f, 0x58, 0x4e, 0x54, 0x51, 0x9f, 0x4d, 0x40, 0x4c, 0x44, 0x1f, 0x53, 0x47, 0x44, 0x1f, 0x4b, 0x48, 0x55, 0x44, 0x4b, 0x4e, 0x4d, 0x46, 0x9f, 0x43, 0x40, 0x58, 0x1f, 0x13, 0x4e, 0x1f, 0x40, 0x4d, 0x1f, 0x40, 0x43, 0x4c, 0x48, 0x51, 0x48, 0x4d, 0xc6, 0x41, 0x4e, 0x46, 0x1a, 0x1f, 0x1c, 0x3, 0x48, 0x42, 0x4a, 0x48, 0x4d, 0x52, 0x4e, 0x4d, 0x1f, 0x9f, 0x9f, 0x9f, ]], +[[0x7, 0x4e, 0x56, 0x1f, 0x47, 0x40, 0x4f, 0x4f, 0x58, 0x1f, 0x48, 0x52, 0x1f, 0x53, 0x47, 0x44, 0x9f, 0x4b, 0x48, 0x53, 0x53, 0x4b, 0x44, 0x1f, 0x52, 0x53, 0x4e, 0x4d, 0x44, 0x1f, 0x13, 0x47, 0x40, 0x53, 0x9f, 0x51, 0x40, 0x4c, 0x41, 0x4b, 0x44, 0x52, 0x1f, 0x48, 0x4d, 0x1f, 0x53, 0x47, 0x44, 0x9f, 0x51, 0x4e, 0x40, 0x43, 0x1f, 0x40, 0x4b, 0x4e, 0x4d, 0x44, 0x1d, 0x1f, 0x0, 0x4d, 0x43, 0x9f, 0x43, 0x4e, 0x44, 0x52, 0x4d, 0x5d, 0x53, 0x1f, 0x42, 0x40, 0x51, 0x44, 0x1f, 0x40, 0x41, 0x4e, 0x54, 0xd3, 0x42, 0x40, 0x51, 0x44, 0x44, 0x51, 0x52, 0x1d, 0x1f, 0x0, 0x4d, 0x43, 0x9f, 0x44, 0x57, 0x48, 0x46, 0x44, 0x4d, 0x42, 0x48, 0x44, 0x52, 0x1f, 0x4d, 0x44, 0x55, 0x44, 0x51, 0x9f, 0x45, 0x44, 0x40, 0x51, 0x52, 0x1b, 0x1f, 0x16, 0x47, 0x4e, 0x52, 0x44, 0x1f, 0x42, 0x4e, 0x40, 0x53, 0x9f, ], [0x4e, 0x45, 0x1f, 0x44, 0x4b, 0x44, 0x4c, 0x44, 0x4d, 0x53, 0x40, 0x4b, 0x1f, 0x41, 0x51, 0x4e, 0x56, 0xcd, 0x0, 0x1f, 0x4f, 0x40, 0x52, 0x52, 0x48, 0x4d, 0x46, 0x1f, 0x54, 0x4d, 0x48, 0x55, 0x44, 0x51, 0x52, 0xc4, 0x4f, 0x54, 0x53, 0x1f, 0x4e, 0x4d, 0x1b, 0x1f, 0x0, 0x4d, 0x43, 0x9f, 0x48, 0x4d, 0x43, 0x44, 0x4f, 0x44, 0x4d, 0x43, 0x44, 0x4d, 0x53, 0x1f, 0x40, 0x52, 0x1f, 0x53, 0x47, 0xc4, 0x52, 0x54, 0x4d, 0x1d, 0x1f, 0x0, 0x52, 0x52, 0x4e, 0x42, 0x48, 0x40, 0x53, 0x44, 0x52, 0x1f, 0x4e, 0xd1, 0x46, 0x4b, 0x4e, 0x56, 0x52, 0x1f, 0x40, 0x4b, 0x4e, 0x4d, 0x44, 0x1d, 0x9f, 0x5, 0x54, 0x4b, 0x45, 0x48, 0x4b, 0x4b, 0x48, 0x4d, 0x46, 0x9f, 0x40, 0x41, 0x52, 0x4e, 0x4b, 0x54, 0x53, 0x44, 0x1f, 0x43, 0x44, 0x42, 0x51, 0x44, 0x44, 0x1f, 0x8, 0xcd, ], [0x42, 0x40, 0x52, 0x54, 0x40, 0x4b, 0x1f, 0x52, 0x48, 0x4c, 0x4f, 0x4b, 0x48, 0x42, 0x48, 0x53, 0x58, 0x9b, 0x1c, 0x3, 0x48, 0x42, 0x4a, 0x48, 0x4d, 0x52, 0x4e, 0x4d, 0x1f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, ]], +[[0x1, 0x44, 0x42, 0x40, 0x54, 0x52, 0x44, 0x1f, 0x8, 0x1f, 0x42, 0x4e, 0x54, 0x4b, 0x43, 0x9f, 0x4d, 0x4e, 0x53, 0x1f, 0x52, 0x53, 0x4e, 0x4f, 0x1f, 0x45, 0x4e, 0x51, 0x1f, 0x43, 0x44, 0x40, 0x53, 0xc7, 0x7, 0x44, 0x1f, 0x4a, 0x48, 0x4d, 0x43, 0x4b, 0x58, 0x1f, 0x52, 0x53, 0x4e, 0x4f, 0x4f, 0x44, 0x43, 0x9f, 0x45, 0x4e, 0x51, 0x1f, 0x4c, 0x44, 0x1b, 0x1f, 0x13, 0x47, 0x44, 0x9f, 0x42, 0x40, 0x51, 0x51, 0x48, 0x40, 0x46, 0x44, 0x1f, 0x47, 0x44, 0x4b, 0x43, 0x1f, 0x41, 0x54, 0x53, 0x9f, 0x49, 0x54, 0x52, 0x53, 0x1f, 0x4e, 0x54, 0x51, 0x52, 0x44, 0x4b, 0x55, 0x44, 0x52, 0x1f, 0x0, 0x4d, 0xc3, 0x48, 0x4c, 0x4c, 0x4e, 0x51, 0x53, 0x40, 0x4b, 0x48, 0x53, 0x58, 0x9f, 0x1c, 0x3, 0x48, 0x42, 0x4a, 0x48, 0x4d, 0x52, 0x4e, 0x4d, 0x1f, 0x9f, ]], +[[0x5, 0x4e, 0x51, 0x1d, 0x1f, 0x4b, 0x48, 0x4a, 0x44, 0x1f, 0x40, 0x4b, 0x4c, 0x4e, 0x52, 0x53, 0x9f, 0x44, 0x55, 0x44, 0x51, 0x58, 0x4e, 0x4d, 0x44, 0x1f, 0x44, 0x4b, 0x52, 0x44, 0x1f, 0x48, 0x4d, 0x9f, 0x4e, 0x54, 0x51, 0x1f, 0x42, 0x4e, 0x54, 0x4d, 0x53, 0x51, 0x58, 0x1d, 0x1f, 0x8, 0x9f, 0x52, 0x53, 0x40, 0x51, 0x53, 0x44, 0x43, 0x1f, 0x4e, 0x54, 0x53, 0x1f, 0x56, 0x48, 0x53, 0x47, 0x9f, 0x4c, 0x58, 0x1f, 0x52, 0x47, 0x40, 0x51, 0x44, 0x1f, 0x4e, 0x45, 0x9f, 0x4e, 0x4f, 0x53, 0x48, 0x4c, 0x48, 0x52, 0x4c, 0x1b, 0x1f, 0x8, 0x9f, 0x41, 0x44, 0x4b, 0x48, 0x44, 0x55, 0x44, 0x43, 0x1f, 0x48, 0x4d, 0x1f, 0x47, 0x40, 0x51, 0x43, 0x9f, 0x56, 0x4e, 0x51, 0x4a, 0x1f, 0x40, 0x4d, 0x43, 0x1f, 0x4f, 0x51, 0x4e, 0x46, 0x51, 0x44, 0x52, 0x52, 0x9f, ], [0x40, 0x4d, 0x43, 0x1f, 0x40, 0x42, 0x53, 0x48, 0x4e, 0x4d, 0x1d, 0x1f, 0x41, 0x54, 0x53, 0x9f, 0x4d, 0x4e, 0x56, 0x1d, 0x1f, 0x40, 0x45, 0x53, 0x44, 0x51, 0x1f, 0x45, 0x48, 0x51, 0x52, 0x53, 0x9f, 0x41, 0x44, 0x48, 0x4d, 0x46, 0x1f, 0x5d, 0x45, 0x4e, 0x51, 0x5d, 0x9f, 0x52, 0x4e, 0x42, 0x48, 0x44, 0x53, 0x58, 0x1f, 0x40, 0x4d, 0x43, 0x1f, 0x53, 0x47, 0x44, 0x4d, 0x9f, 0x5d, 0x40, 0x46, 0x40, 0x48, 0x4d, 0x52, 0x53, 0x5d, 0x1f, 0x48, 0x53, 0x1d, 0x1f, 0x8, 0x9f, 0x40, 0x52, 0x52, 0x48, 0x46, 0x4d, 0x1f, 0x4c, 0x58, 0x52, 0x44, 0x4b, 0x45, 0x1f, 0x4d, 0x4e, 0x9f, 0x51, 0x40, 0x4d, 0x4a, 0x1f, 0x4e, 0x51, 0x1f, 0x40, 0x4d, 0x58, 0x1f, 0x4b, 0x48, 0x4c, 0x48, 0x53, 0x9d, 0x40, 0x4d, 0x43, 0x1f, 0x52, 0x54, 0x42, 0x47, 0x1f, 0x40, 0x4d, 0x9f, ], [0x40, 0x53, 0x53, 0x48, 0x53, 0x54, 0x43, 0x44, 0x1f, 0x48, 0x52, 0x1f, 0x55, 0x44, 0x51, 0x58, 0x9f, 0x4c, 0x54, 0x42, 0x47, 0x1f, 0x40, 0x46, 0x40, 0x48, 0x4d, 0x52, 0x53, 0x1f, 0x53, 0x47, 0x44, 0x9f, 0x53, 0x51, 0x44, 0x4d, 0x43, 0x1f, 0x4e, 0x45, 0x1f, 0x53, 0x47, 0x44, 0x9f, 0x53, 0x48, 0x4c, 0x44, 0x52, 0x1b, 0x1f, 0x1, 0x54, 0x53, 0x1f, 0x4c, 0x58, 0x9f, 0x56, 0x4e, 0x51, 0x4b, 0x43, 0x1f, 0x47, 0x40, 0x52, 0x1f, 0x41, 0x44, 0x42, 0x4e, 0x4c, 0x44, 0x9f, 0x4e, 0x4d, 0x44, 0x1f, 0x4e, 0x45, 0x1f, 0x48, 0x4d, 0x45, 0x48, 0x4d, 0x48, 0x53, 0x44, 0x9f, 0x4f, 0x4e, 0x52, 0x52, 0x48, 0x41, 0x48, 0x4b, 0x48, 0x53, 0x48, 0x44, 0x52, 0x1b, 0x9f, 0x16, 0x47, 0x40, 0x53, 0x1f, 0x40, 0x1f, 0x4f, 0x47, 0x51, 0x40, 0x52, 0x44, 0x1f, 0x1c, 0x9f, ], [0x52, 0x53, 0x48, 0x4b, 0x4b, 0x1f, 0x48, 0x53, 0x5d, 0x52, 0x1f, 0x40, 0x1f, 0x46, 0x4e, 0x4e, 0x43, 0x9f, 0x4f, 0x47, 0x51, 0x40, 0x52, 0x44, 0x1f, 0x40, 0x4d, 0x43, 0x1f, 0x40, 0x1f, 0x46, 0x4e, 0x4e, 0x43, 0x9f, 0x55, 0x48, 0x44, 0x56, 0x1f, 0x4e, 0x45, 0x1f, 0x4b, 0x48, 0x45, 0x44, 0x1d, 0x1f, 0x40, 0x4d, 0x43, 0x9f, 0x40, 0x1f, 0x4c, 0x40, 0x4d, 0x1f, 0x52, 0x47, 0x4e, 0x54, 0x4b, 0x43, 0x4d, 0x5d, 0x53, 0x9f, 0x40, 0x42, 0x42, 0x44, 0x4f, 0x53, 0x1f, 0x40, 0x4d, 0x58, 0x1f, 0x4e, 0x53, 0x47, 0x44, 0x51, 0x1b, 0x9f, 0x53, 0x47, 0x40, 0x53, 0x1f, 0x4c, 0x54, 0x42, 0x47, 0x1f, 0x8, 0x5d, 0x55, 0x44, 0x9f, 0x4b, 0x44, 0x40, 0x51, 0x4d, 0x44, 0x43, 0x9f, 0x54, 0x4d, 0x43, 0x44, 0x51, 0x46, 0x51, 0x4e, 0x54, 0x4d, 0x43, 0x1b, 0x1f, 0x14, 0x4d, 0x53, 0x48, 0xcb, ], [0x52, 0x4e, 0x4c, 0x44, 0x1f, 0x46, 0x40, 0x4d, 0x46, 0x1f, 0x52, 0x54, 0x42, 0x42, 0x44, 0x44, 0x43, 0xd2, 0x48, 0x4d, 0x1f, 0x4f, 0x54, 0x53, 0x53, 0x48, 0x4d, 0x46, 0x1f, 0x53, 0x47, 0x44, 0x9f, 0x56, 0x4e, 0x51, 0x4b, 0x43, 0x1f, 0x48, 0x4d, 0x1f, 0x40, 0x1f, 0x52, 0x53, 0x51, 0x40, 0x48, 0x53, 0x9f, 0x49, 0x40, 0x42, 0x4a, 0x44, 0x53, 0x1d, 0x1f, 0x48, 0x53, 0x52, 0x9f, 0x43, 0x44, 0x45, 0x48, 0x4d, 0x48, 0x53, 0x48, 0x4e, 0x4d, 0x1f, 0x48, 0x52, 0x9f, 0x4f, 0x4e, 0x52, 0x52, 0x48, 0x41, 0x48, 0x4b, 0x48, 0x53, 0x58, 0x1b, 0x9f, 0x1c, 0x4, 0x4b, 0x4b, 0x48, 0x52, 0x4e, 0x4d, 0x1f, 0x9f, 0x9f, ]], +] \ No newline at end of file diff --git a/worlds/smw/Names/LocationName.py b/worlds/smw/Names/LocationName.py new file mode 100644 index 0000000000..2b42cd21a4 --- /dev/null +++ b/worlds/smw/Names/LocationName.py @@ -0,0 +1,364 @@ +# Level Definitions +yoshis_house = "Yoshi's House" +yoshis_house_tile = "Yoshi's House - Tile" + +yoshis_island_1_exit_1 = "Yoshi's Island 1 - Normal Exit" +yoshis_island_1_dragon = "Yoshi's Island 1 - Dragon Coins" +yoshis_island_2_exit_1 = "Yoshi's Island 2 - Normal Exit" +yoshis_island_2_dragon = "Yoshi's Island 2 - Dragon Coins" +yoshis_island_3_exit_1 = "Yoshi's Island 3 - Normal Exit" +yoshis_island_3_dragon = "Yoshi's Island 3 - Dragon Coins" +yoshis_island_4_exit_1 = "Yoshi's Island 4 - Normal Exit" +yoshis_island_4_dragon = "Yoshi's Island 4 - Dragon Coins" +yoshis_island_castle = "#1 Iggy's Castle - Normal Exit" +yoshis_island_koopaling = "#1 Iggy's Castle - Boss" + +yellow_switch_palace = "Yellow Switch Palace" + +donut_plains_1_exit_1 = "Donut Plains 1 - Normal Exit" +donut_plains_1_exit_2 = "Donut Plains 1 - Secret Exit" +donut_plains_1_dragon = "Donut Plains 1 - Dragon Coins" +donut_plains_2_exit_1 = "Donut Plains 2 - Normal Exit" +donut_plains_2_exit_2 = "Donut Plains 2 - Secret Exit" +donut_plains_2_dragon = "Donut Plains 2 - Dragon Coins" +donut_plains_3_exit_1 = "Donut Plains 3 - Normal Exit" +donut_plains_3_dragon = "Donut Plains 3 - Dragon Coins" +donut_plains_4_exit_1 = "Donut Plains 4 - Normal Exit" +donut_plains_4_dragon = "Donut Plains 4 - Dragon Coins" +donut_secret_1_exit_1 = "Donut Secret 1 - Normal Exit" +donut_secret_1_exit_2 = "Donut Secret 1 - Secret Exit" +donut_secret_1_dragon = "Donut Secret 1 - Dragon Coins" +donut_secret_2_exit_1 = "Donut Secret 2 - Normal Exit" +donut_secret_2_dragon = "Donut Secret 2 - Dragon Coins" +donut_ghost_house_exit_1 = "Donut Ghost House - Normal Exit" +donut_ghost_house_exit_2 = "Donut Ghost House - Secret Exit" +donut_secret_house_exit_1 = "Donut Secret House - Normal Exit" +donut_secret_house_exit_2 = "Donut Secret House - Secret Exit" +donut_plains_castle = "#2 Morton's Castle - Normal Exit" +donut_plains_koopaling = "#2 Morton's Castle - Boss" + +green_switch_palace = "Green Switch Palace" + +vanilla_dome_1_exit_1 = "Vanilla Dome 1 - Normal Exit" +vanilla_dome_1_exit_2 = "Vanilla Dome 1 - Secret Exit" +vanilla_dome_1_dragon = "Vanilla Dome 1 - Dragon Coins" +vanilla_dome_2_exit_1 = "Vanilla Dome 2 - Normal Exit" +vanilla_dome_2_exit_2 = "Vanilla Dome 2 - Secret Exit" +vanilla_dome_2_dragon = "Vanilla Dome 2 - Dragon Coins" +vanilla_dome_3_exit_1 = "Vanilla Dome 3 - Normal Exit" +vanilla_dome_3_dragon = "Vanilla Dome 3 - Dragon Coins" +vanilla_dome_4_exit_1 = "Vanilla Dome 4 - Normal Exit" +vanilla_dome_4_dragon = "Vanilla Dome 4 - Dragon Coins" +vanilla_secret_1_exit_1 = "Vanilla Secret 1 - Normal Exit" +vanilla_secret_1_exit_2 = "Vanilla Secret 1 - Secret Exit" +vanilla_secret_1_dragon = "Vanilla Secret 1 - Dragon Coins" +vanilla_secret_2_exit_1 = "Vanilla Secret 2 - Normal Exit" +vanilla_secret_2_dragon = "Vanilla Secret 2 - Dragon Coins" +vanilla_secret_3_exit_1 = "Vanilla Secret 3 - Normal Exit" +vanilla_secret_3_dragon = "Vanilla Secret 3 - Dragon Coins" +vanilla_ghost_house_exit_1 = "Vanilla Ghost House - Normal Exit" +vanilla_ghost_house_dragon = "Vanilla Ghost House - Dragon Coins" +vanilla_fortress = "Vanilla Fortress - Normal Exit" +vanilla_reznor = "Vanilla Fortress - Boss" +vanilla_dome_castle = "#3 Lemmy's Castle - Normal Exit" +vanilla_dome_koopaling = "#3 Lemmy's Castle - Boss" + +red_switch_palace = "Red Switch Palace" + +butter_bridge_1_exit_1 = "Butter Bridge 1 - Normal Exit" +butter_bridge_1_dragon = "Butter Bridge 1 - Dragon Coins" +butter_bridge_2_exit_1 = "Butter Bridge 2 - Normal Exit" +butter_bridge_2_dragon = "Butter Bridge 2 - Dragon Coins" +cheese_bridge_exit_1 = "Cheese Bridge - Normal Exit" +cheese_bridge_exit_2 = "Cheese Bridge - Secret Exit" +cheese_bridge_dragon = "Cheese Bridge - Dragon Coins" +cookie_mountain_exit_1 = "Cookie Mountain - Normal Exit" +cookie_mountain_dragon = "Cookie Mountain - Dragon Coins" +soda_lake_exit_1 = "Soda Lake - Normal Exit" +soda_lake_dragon = "Soda Lake - Dragon Coins" +twin_bridges_castle = "#4 Ludwig's Castle - Normal Exit" +twin_bridges_koopaling = "#4 Ludwig's Castle - Boss" + +forest_of_illusion_1_exit_1 = "Forest of Illusion 1 - Normal Exit" +forest_of_illusion_1_exit_2 = "Forest of Illusion 1 - Secret Exit" +forest_of_illusion_2_exit_1 = "Forest of Illusion 2 - Normal Exit" +forest_of_illusion_2_exit_2 = "Forest of Illusion 2 - Secret Exit" +forest_of_illusion_2_dragon = "Forest of Illusion 2 - Dragon Coins" +forest_of_illusion_3_exit_1 = "Forest of Illusion 3 - Normal Exit" +forest_of_illusion_3_exit_2 = "Forest of Illusion 3 - Secret Exit" +forest_of_illusion_3_dragon = "Forest of Illusion 3 - Dragon Coins" +forest_of_illusion_4_exit_1 = "Forest of Illusion 4 - Normal Exit" +forest_of_illusion_4_exit_2 = "Forest of Illusion 4 - Secret Exit" +forest_of_illusion_4_dragon = "Forest of Illusion 4 - Dragon Coins" +forest_ghost_house_exit_1 = "Forest Ghost House - Normal Exit" +forest_ghost_house_exit_2 = "Forest Ghost House - Secret Exit" +forest_ghost_house_dragon = "Forest Ghost House - Dragon Coins" +forest_secret_exit_1 = "Forest Secret - Normal Exit" +forest_secret_dragon = "Forest Secret - Dragon Coins" +forest_fortress = "Forest Fortress - Normal Exit" +forest_reznor = "Forest Fortress - Boss" +forest_castle = "#5 Roy's Castle - Normal Exit" +forest_castle_dragon = "#5 Roy's Castle - Dragon Coins" +forest_koopaling = "#5 Roy's Castle - Boss" + +blue_switch_palace = "Blue Switch Palace" + +chocolate_island_1_exit_1 = "Chocolate Island 1 - Normal Exit" +chocolate_island_1_dragon = "Chocolate Island 1 - Dragon Coins" +chocolate_island_2_exit_1 = "Chocolate Island 2 - Normal Exit" +chocolate_island_2_exit_2 = "Chocolate Island 2 - Secret Exit" +chocolate_island_2_dragon = "Chocolate Island 2 - Dragon Coins" +chocolate_island_3_exit_1 = "Chocolate Island 3 - Normal Exit" +chocolate_island_3_exit_2 = "Chocolate Island 3 - Secret Exit" +chocolate_island_3_dragon = "Chocolate Island 3 - Dragon Coins" +chocolate_island_4_exit_1 = "Chocolate Island 4 - Normal Exit" +chocolate_island_4_dragon = "Chocolate Island 4 - Dragon Coins" +chocolate_island_5_exit_1 = "Chocolate Island 5 - Normal Exit" +chocolate_island_5_dragon = "Chocolate Island 5 - Dragon Coins" +chocolate_ghost_house_exit_1 = "Choco-Ghost House - Normal Exit" +chocolate_secret_exit_1 = "Chocolate Secret - Normal Exit" +chocolate_fortress = "Chocolate Fortress - Normal Exit" +chocolate_reznor = "Chocolate Fortress Defeat" +chocolate_castle = "#6 Wendy's Castle - Normal Exit" +chocolate_koopaling = "#6 Wendy's Castle - Boss" + +sunken_ghost_ship = "Sunken Ghost Ship - Normal Exit" +sunken_ghost_ship_dragon = "Sunken Ghost Ship - Dragon Coins" + +valley_of_bowser_1_exit_1 = "Valley of Bowser 1 - Normal Exit" +valley_of_bowser_1_dragon = "Valley of Bowser 1 - Dragon Coins" +valley_of_bowser_2_exit_1 = "Valley of Bowser 2 - Normal Exit" +valley_of_bowser_2_exit_2 = "Valley of Bowser 2 - Secret Exit" +valley_of_bowser_2_dragon = "Valley of Bowser 2 - Dragon Coins" +valley_of_bowser_3_exit_1 = "Valley of Bowser 3 - Normal Exit" +valley_of_bowser_3_dragon = "Valley of Bowser 3 - Dragon Coins" +valley_of_bowser_4_exit_1 = "Valley of Bowser 4 - Normal Exit" +valley_of_bowser_4_exit_2 = "Valley of Bowser 4 - Secret Exit" +valley_ghost_house_exit_1 = "Valley Ghost House - Normal Exit" +valley_ghost_house_exit_2 = "Valley Ghost House - Secret Exit" +valley_ghost_house_dragon = "Valley Ghost House - Dragon Coins" +valley_fortress = "Valley Fortress - Normal Exit" +valley_reznor = "Valley Fortress - Boss" +valley_castle = "#7 Larry's Castle - Normal Exit" +valley_castle_dragon = "#7 Larry's Castle - Dragon Coins" +valley_koopaling = "#7 Larry's Castle - Boss" + +front_door = "Front Door" +back_door = "Back Door" +bowser = "Bowser" + +star_road_1_exit_1 = "Star Road 1 - Normal Exit" +star_road_1_exit_2 = "Star Road 1 - Secret Exit" +star_road_1_dragon = "Star Road 1 - Dragon Coins" +star_road_2_exit_1 = "Star Road 2 - Normal Exit" +star_road_2_exit_2 = "Star Road 2 - Secret Exit" +star_road_3_exit_1 = "Star Road 3 - Normal Exit" +star_road_3_exit_2 = "Star Road 3 - Secret Exit" +star_road_4_exit_1 = "Star Road 4 - Normal Exit" +star_road_4_exit_2 = "Star Road 4 - Secret Exit" +star_road_5_exit_1 = "Star Road 5 - Normal Exit" +star_road_5_exit_2 = "Star Road 5 - Secret Exit" + +special_zone_1_exit_1 = "Gnarly - Normal Exit" +special_zone_1_dragon = "Gnarly - Dragon Coins" +special_zone_2_exit_1 = "Tubular - Normal Exit" +special_zone_2_dragon = "Tubular - Dragon Coins" +special_zone_3_exit_1 = "Way Cool - Normal Exit" +special_zone_3_dragon = "Way Cool - Dragon Coins" +special_zone_4_exit_1 = "Awesome - Normal Exit" +special_zone_4_dragon = "Awesome - Dragon Coins" +special_zone_5_exit_1 = "Groovy - Normal Exit" +special_zone_5_dragon = "Groovy - Dragon Coins" +special_zone_6_exit_1 = "Mondo - Normal Exit" +special_zone_6_dragon = "Mondo - Dragon Coins" +special_zone_7_exit_1 = "Outrageous - Normal Exit" +special_zone_7_dragon = "Outrageous - Dragon Coins" +special_zone_8_exit_1 = "Funky - Normal Exit" +special_zone_8_dragon = "Funky - Dragon Coins" + + +# Region Definitions +menu_region = "Menu" + +yoshis_island_region = "Yoshi's Island" +donut_plains_region = "Donut Plains" +vanilla_dome_region = "Vanilla Dome" +twin_bridges_region = "Twin Bridges" +forest_of_illusion_region = "Forest of Illusion" +chocolate_island_region = "Chocolate Island" +valley_of_bowser_region = "Valley of Bowser" +star_road_region = "Star Road" +special_zone_region = "Special Zone" + +yoshis_island_1_tile = "Yoshi's Island 1 - Tile" +yoshis_island_1_region = "Yoshi's Island 1" +yoshis_island_2_tile = "Yoshi's Island 2 - Tile" +yoshis_island_2_region = "Yoshi's Island 2" +yoshis_island_3_tile = "Yoshi's Island 3 - Tile" +yoshis_island_3_region = "Yoshi's Island 3" +yoshis_island_4_tile = "Yoshi's Island 4 - Tile" +yoshis_island_4_region = "Yoshi's Island 4" +yoshis_island_castle_tile = "#1 Iggy's Castle - Tile" +yoshis_island_castle_region = "#1 Iggy's Castle" + +yellow_switch_palace_tile = "Yellow Switch Palace - Tile" + +donut_plains_1_tile = "Donut Plains 1 - Tile" +donut_plains_1_region = "Donut Plains 1" +donut_plains_2_tile = "Donut Plains 2 - Tile" +donut_plains_2_region = "Donut Plains 2" +donut_plains_3_tile = "Donut Plains 3 - Tile" +donut_plains_3_region = "Donut Plains 3" +donut_plains_4_tile = "Donut Plains 4 - Tile" +donut_plains_4_region = "Donut Plains 4" +donut_secret_1_tile = "Donut Secret 1 - Tile" +donut_secret_1_region = "Donut Secret 1" +donut_secret_2_tile = "Donut Secret 2 - Tile" +donut_secret_2_region = "Donut Secret 2" +donut_ghost_house_tile = "Donut Ghost House - Tile" +donut_ghost_house_region = "Donut Ghost House" +donut_secret_house_tile = "Donut Secret House - Tile" +donut_secret_house_region = "Donut Secret House" +donut_plains_castle_tile = "#2 Morton's Castle - Tile" +donut_plains_castle_region = "#2 Morton's Castle" +donut_plains_top_secret = "Top Secret Area" +donut_plains_top_secret_tile = "Top Secret Area - Tile" +donut_plains_star_road = "Donut Plains - Star Road" + +green_switch_palace_tile = "Green Switch Palace - Tile" + +vanilla_dome_1_tile = "Vanilla Dome 1 - Tile" +vanilla_dome_1_region = "Vanilla Dome 1" +vanilla_dome_2_tile = "Vanilla Dome 2 - Tile" +vanilla_dome_2_region = "Vanilla Dome 2" +vanilla_dome_3_tile = "Vanilla Dome 3 - Tile" +vanilla_dome_3_region = "Vanilla Dome 3" +vanilla_dome_4_tile = "Vanilla Dome 4 - Tile" +vanilla_dome_4_region = "Vanilla Dome 4" +vanilla_secret_1_tile = "Vanilla Secret 1 - Tile" +vanilla_secret_1_region = "Vanilla Secret 1" +vanilla_secret_2_tile = "Vanilla Secret 2 - Tile" +vanilla_secret_2_region = "Vanilla Secret 2" +vanilla_secret_3_tile = "Vanilla Secret 3 - Tile" +vanilla_secret_3_region = "Vanilla Secret 3" +vanilla_ghost_house_tile = "Vanilla Ghost House - Tile" +vanilla_ghost_house_region = "Vanilla Ghost House" +vanilla_fortress_tile = "Vanilla Fortress - Tile" +vanilla_fortress_region = "Vanilla Fortress" +vanilla_dome_castle_tile = "#3 Lemmy's Castle - Tile" +vanilla_dome_castle_region = "#3 Lemmy's Castle" +vanilla_dome_star_road = "Vanilla Dome - Star Road" + +red_switch_palace_tile = "Red Switch Palace - Tile" + +butter_bridge_1_tile = "Butter Bridge 1 - Tile" +butter_bridge_1_region = "Butter Bridge 1" +butter_bridge_2_tile = "Butter Bridge 2 - Tile" +butter_bridge_2_region = "Butter Bridge 2" +cheese_bridge_tile = "Cheese Bridge - Tile" +cheese_bridge_region = "Cheese Bridge" +cookie_mountain_tile = "Cookie Mountain - Tile" +cookie_mountain_region = "Cookie Mountain" +soda_lake_tile = "Soda Lake - Tile" +soda_lake_region = "Soda Lake" +twin_bridges_castle_tile = "#4 Ludwig's Castle - Tile" +twin_bridges_castle_region = "#4 Ludwig's Castle" +twin_bridges_star_road = "Twin Bridges - Star Road" + +forest_of_illusion_1_tile = "Forest of Illusion 1 - Tile" +forest_of_illusion_1_region = "Forest of Illusion 1" +forest_of_illusion_2_tile = "Forest of Illusion 2 - Tile" +forest_of_illusion_2_region = "Forest of Illusion 2" +forest_of_illusion_3_tile = "Forest of Illusion 3 - Tile" +forest_of_illusion_3_region = "Forest of Illusion 3" +forest_of_illusion_4_tile = "Forest of Illusion 4 - Tile" +forest_of_illusion_4_region = "Forest of Illusion 4" +forest_ghost_house_tile = "Forest Ghost House - Tile" +forest_ghost_house_region = "Forest Ghost House" +forest_secret_tile = "Forest Secret - Tile" +forest_secret_region = "Forest Secret" +forest_fortress_tile = "Forest Fortress - Tile" +forest_fortress_region = "Forest Fortress" +forest_castle_tile = "#5 Roy's Castle - Tile" +forest_castle_region = "#5 Roy's Castle" +forest_star_road = "Forest of Illusion - Star Road" + +blue_switch_palace_tile = "Blue Switch Palace - Tile" + +chocolate_island_1_tile = "Chocolate Island 1 - Tile" +chocolate_island_1_region = "Chocolate Island 1" +chocolate_island_2_tile = "Chocolate Island 2 - Tile" +chocolate_island_2_region = "Chocolate Island 2" +chocolate_island_3_tile = "Chocolate Island 3 - Tile" +chocolate_island_3_region = "Chocolate Island 3" +chocolate_island_4_tile = "Chocolate Island 4 - Tile" +chocolate_island_4_region = "Chocolate Island 4" +chocolate_island_5_tile = "Chocolate Island 5 - Tile" +chocolate_island_5_region = "Chocolate Island 5" +chocolate_ghost_house_tile = "Choco-Ghost House - Tile" +chocolate_ghost_house_region = "Choco-Ghost House" +chocolate_secret_tile = "Chocolate Secret - Tile" +chocolate_secret_region = "Chocolate Secret" +chocolate_fortress_tile = "Chocolate Fortress - Tile" +chocolate_fortress_region = "Chocolate Fortress" +chocolate_castle_tile = "#6 Wendy's Castle - Tile" +chocolate_castle_region = "#6 Wendy's Castle" + +sunken_ghost_ship_tile = "Sunken Ghost Ship - Tile" +sunken_ghost_ship_region = "Sunken Ghost Ship" + +valley_of_bowser_1_tile = "Valley of Bowser 1 - Tile" +valley_of_bowser_1_region = "Valley of Bowser 1" +valley_of_bowser_2_tile = "Valley of Bowser 2 - Tile" +valley_of_bowser_2_region = "Valley of Bowser 2" +valley_of_bowser_3_tile = "Valley of Bowser 3 - Tile" +valley_of_bowser_3_region = "Valley of Bowser 3" +valley_of_bowser_4_tile = "Valley of Bowser 4 - Tile" +valley_of_bowser_4_region = "Valley of Bowser 4" +valley_ghost_house_tile = "Valley Ghost House - Tile" +valley_ghost_house_region = "Valley Ghost House" +valley_fortress_tile = "Valley Fortress - Tile" +valley_fortress_region = "Valley Fortress" +valley_castle_tile = "#7 Larry's Castle - Tile" +valley_castle_region = "#7 Larry's Castle" +valley_star_road = "Valley of Bowser - Star Road" + +front_door_tile = "Front Door - Tile" +back_door_tile = "Back Door - Tile" +bowser_region = "Bowser - Region" + +star_road_donut = "Star Road - Donut Plains" +star_road_1_tile = "Star Road 1 - Tile" +star_road_1_region = "Star Road 1" +star_road_vanilla = "Star Road - Vanilla Dome" +star_road_2_tile = "Star Road 2 - Tile" +star_road_2_region = "Star Road 2" +star_road_twin_bridges = "Star Road - Twin Bridges" +star_road_3_tile = "Star Road 3 - Tile" +star_road_3_region = "Star Road 3" +star_road_forest = "Star Road - Forest of Illusion" +star_road_4_tile = "Star Road 4 - Tile" +star_road_4_region = "Star Road 4" +star_road_valley = "Star Road - Valley of Bowser" +star_road_5_tile = "Star Road 5 - Tile" +star_road_5_region = "Star Road 5" +star_road_special = "Star Road - Special Zone" + +special_star_road = "Special Zone - Star Road" +special_zone_1_tile = "Gnarly - Tile" +special_zone_1_region = "Gnarly" +special_zone_2_tile = "Tubular - Tile" +special_zone_2_region = "Tubular" +special_zone_3_tile = "Way Cool - Tile" +special_zone_3_region = "Way Cool" +special_zone_4_tile = "Awesome - Tile" +special_zone_4_region = "Awesome" +special_zone_5_tile = "Groovy - Tile" +special_zone_5_region = "Groovy" +special_zone_6_tile = "Mondo - Tile" +special_zone_6_region = "Mondo" +special_zone_7_tile = "Outrageous - Tile" +special_zone_7_region = "Outrageous" +special_zone_8_tile = "Funky - Tile" +special_zone_8_region = "Funky" +special_complete = "Special Zone - Star Road - Complete" diff --git a/worlds/smw/Names/TextBox.py b/worlds/smw/Names/TextBox.py new file mode 100644 index 0000000000..cecf088661 --- /dev/null +++ b/worlds/smw/Names/TextBox.py @@ -0,0 +1,140 @@ + +from BaseClasses import MultiWorld + +import math + + +text_mapping = { + "A": 0x00, "B": 0x01, "C": 0x02, "D": 0x03, "E": 0x04, "F": 0x05, "G": 0x06, "H": 0x07, "I": 0x08, "J": 0x09, + "K": 0x0A, "L": 0x0B, "M": 0x0C, "N": 0x0D, "O": 0x0E, "P": 0x0F, "Q": 0x10, "R": 0x11, "S": 0x12, "T": 0x13, + "U": 0x14, "V": 0x15, "W": 0x16, "X": 0x17, "Y": 0x18, "Z": 0x19, + + "!": 0x1A, ".": 0x1B, "-": 0x1C, ",": 0x1D, "?": 0x1E, " ": 0x1F, + + "0": 0x22, "1": 0x23, "2": 0x24, "3": 0x25, "4": 0x26, "5": 0x27, "6": 0x28, "7": 0x29, "8": 0x2A, "9": 0x2B, + + "a": 0x40, "b": 0x41, "c": 0x42, "d": 0x43, "e": 0x44, "f": 0x45, "g": 0x46, "h": 0x47, "i": 0x48, "j": 0x49, + "k": 0x4A, "l": 0x4B, "m": 0x4C, "n": 0x4D, "o": 0x4E, "p": 0x4F, "q": 0x50, "r": 0x51, "s": 0x52, "t": 0x53, + "u": 0x54, "v": 0x55, "w": 0x56, "x": 0x57, "y": 0x58, "z": 0x59, + + "#": 0x5A, "(": 0x5B, ")": 0x5C, "'": 0x5D +} + +title_text_mapping = { + "A": [0x0A, 0x38], "B": [0x0B, 0x38], "C": [0x0C, 0x38], "D": [0x0D, 0x38], "E": [0x0E, 0x38], + "F": [0x0F, 0x38], "G": [0x10, 0x38], "H": [0x11, 0x38], "I": [0x12, 0x38], "J": [0x13, 0x38], + "K": [0x14, 0x38], "L": [0x15, 0x38], "M": [0x16, 0x38], "N": [0x17, 0x38], "O": [0x18, 0x38], + "P": [0x19, 0x38], "Q": [0x1A, 0x38], "R": [0x1B, 0x38], "S": [0x1C, 0x38], "T": [0x1D, 0x38], + "U": [0x1E, 0x38], "V": [0x1F, 0x38], "W": [0x20, 0x38], "X": [0x21, 0x38], "Y": [0x22, 0x38], + "Z": [0x23, 0x38], " ": [0xFC, 0x38], ".": [0x24, 0x38], + "0": [0x00, 0x38], "1": [0x01, 0x38], "2": [0x02, 0x38], "3": [0x03, 0x38], "4": [0x04, 0x38], + "5": [0x05, 0x38], "6": [0x06, 0x38], "7": [0x07, 0x38], "8": [0x08, 0x38], "9": [0x09, 0x38], +} + + +def string_to_bytes(input_string): + out_array = bytearray() + for letter in input_string: + out_array.append(text_mapping[letter] if letter in text_mapping else text_mapping["."]) + + return out_array + + +def generate_text_box(input_string): + out_bytes = bytearray() + box_line_count = 0 + box_line_chr_count = 0 + for word in input_string.split(): + if box_line_chr_count + len(word) > 18: + out_bytes[-1] += 0x80 + box_line_count += 1 + box_line_chr_count = 0 + + out_bytes.extend(string_to_bytes(word)) + box_line_chr_count += len(word) + + if box_line_chr_count < 18: + box_line_chr_count += 1 + out_bytes.append(0x1F) + + for i in range(box_line_count, 8): + out_bytes.append(0x9F) + + return out_bytes + + +def generate_goal_text(world: MultiWorld, player: int): + out_array = bytearray() + if world.goal[player] == "yoshi_egg_hunt": + required_yoshi_eggs = max(math.floor( + world.number_of_yoshi_eggs[player].value * (world.percentage_of_yoshi_eggs[player].value / 100.0)), 1) + out_array += bytearray([0x9F, 0x9F]) + out_array += string_to_bytes(" You must acquire") + out_array[-1] += 0x80 + out_array += string_to_bytes(f' {required_yoshi_eggs:02} Yoshi Eggs,') + out_array[-1] += 0x80 + out_array += string_to_bytes("then return here.") + out_array[-1] += 0x80 + out_array += bytearray([0x9F, 0x9F, 0x9F]) + else: + bosses_required = world.bosses_required[player].value + out_array += bytearray([0x9F, 0x9F]) + out_array += string_to_bytes(" You must defeat") + out_array[-1] += 0x80 + out_array += string_to_bytes(f' {bosses_required:02} Bosses,') + out_array[-1] += 0x80 + out_array += string_to_bytes("then defeat Bowser") + out_array[-1] += 0x80 + out_array += bytearray([0x9F, 0x9F, 0x9F]) + + return out_array + + +def generate_received_text(item_name: str, player_name: str): + out_array = bytearray() + + item_name = item_name[:18] + player_name = player_name[:18] + + item_buffer = max(0, math.floor((18 - len(item_name)) / 2)) + player_buffer = max(0, math.floor((18 - len(player_name)) / 2)) + + out_array += bytearray([0x9F, 0x9F]) + out_array += string_to_bytes(" Received") + out_array[-1] += 0x80 + out_array += bytearray([0x1F] * item_buffer) + out_array += string_to_bytes(item_name) + out_array[-1] += 0x80 + out_array += string_to_bytes(" from") + out_array[-1] += 0x80 + out_array += bytearray([0x1F] * player_buffer) + out_array += string_to_bytes(player_name) + out_array[-1] += 0x80 + out_array += bytearray([0x9F, 0x9F]) + + return out_array + + +def generate_sent_text(item_name: str, player_name: str): + out_array = bytearray() + + item_name = item_name[:18] + player_name = player_name[:18] + + item_buffer = max(0, math.floor((18 - len(item_name)) / 2)) + player_buffer = max(0, math.floor((18 - len(player_name)) / 2)) + + out_array += bytearray([0x9F, 0x9F]) + out_array += string_to_bytes(" Sent") + out_array[-1] += 0x80 + out_array += bytearray([0x1F] * item_buffer) + out_array += string_to_bytes(item_name) + out_array[-1] += 0x80 + out_array += string_to_bytes(" to") + out_array[-1] += 0x80 + out_array += bytearray([0x1F] * player_buffer) + out_array += string_to_bytes(player_name) + out_array[-1] += 0x80 + out_array += bytearray([0x9F, 0x9F]) + + return out_array diff --git a/worlds/smw/Options.py b/worlds/smw/Options.py new file mode 100644 index 0000000000..80af63f5a4 --- /dev/null +++ b/worlds/smw/Options.py @@ -0,0 +1,236 @@ +import typing + +from Options import Choice, Range, Option, Toggle, DeathLink, DefaultOnToggle, OptionList + + +class Goal(Choice): + """ + Determines the goal of the seed + Bowser: Defeat Koopalings, reach Bowser's Castle and defeat Bowser + Yoshi Egg Hunt: Find a certain number of Yoshi Eggs + """ + display_name = "Goal" + option_bowser = 0 + option_yoshi_egg_hunt = 1 + default = 0 + + +class BossesRequired(Range): + """ + How many Bosses (Koopalings or Reznor) must be defeated in order to defeat Bowser + """ + display_name = "Bosses Required" + range_start = 0 + range_end = 11 + default = 7 + + +class NumberOfYoshiEggs(Range): + """ + How many Yoshi Eggs are in the pool for Yoshi Egg Hunt + """ + display_name = "Total Number of Yoshi Eggs" + range_start = 1 + range_end = 80 + default = 50 + + +class PercentageOfYoshiEggs(Range): + """ + What Percentage of Yoshi Eggs are required to finish Yoshi Egg Hunt + """ + display_name = "Required Percentage of Yoshi Eggs" + range_start = 1 + range_end = 100 + default = 100 + + +class DragonCoinChecks(Toggle): + """ + Whether collecting 5 Dragon Coins in each level will grant a check + """ + display_name = "Dragon Coin Checks" + + +class BowserCastleDoors(Choice): + """ + How the doors of Bowser's Castle behave + Vanilla: Front and Back Doors behave as vanilla + Fast: Both doors behave as the Back Door + Slow: Both doors behave as the Front Door + "Front Door" requires beating all 8 Rooms + "Back Door" only requires going through the dark hallway to Bowser + """ + display_name = "Bowser Castle Doors" + option_vanilla = 0 + option_fast = 1 + option_slow = 2 + default = 0 + + +class LevelShuffle(Toggle): + """ + Whether levels are shuffled + """ + display_name = "Level Shuffle" + + +class SwapDonutGhostHouseExits(Toggle): + """ + If enabled, this option will swap which overworld direction the two exits of the level at the Donut Ghost House overworld tile go: + False: Normal Exit goes up, Secret Exit goes right. + True: Normal Exit goes right, Secret Exit goes up. + """ + display_name = "Swap Donut GH Exits" + + +class DisplaySentItemPopups(Choice): + """ + What messages to display in-game for items sent + """ + display_name = "Display Sent Item Popups" + option_none = 0 + option_all = 1 + default = 1 + + +class DisplayReceivedItemPopups(Choice): + """ + What messages to display in-game for items received + """ + display_name = "Display Received Item Popups" + option_none = 0 + option_all = 1 + option_progression = 2 + default = 2 + + +class TrapFillPercentage(Range): + """ + Replace a percentage of junk items in the item pool with random traps + """ + display_name = "Trap Fill Percentage" + range_start = 0 + range_end = 100 + default = 0 + + +class BaseTrapWeight(Choice): + """ + Base Class for Trap Weights + """ + option_none = 0 + option_low = 1 + option_medium = 2 + option_high = 4 + default = 2 + + +class IceTrapWeight(BaseTrapWeight): + """ + Likelihood of a receiving a trap which causes the level to become slippery + """ + display_name = "Ice Trap Weight" + + +class StunTrapWeight(BaseTrapWeight): + """ + Likelihood of a receiving a trap which briefly stuns Mario + """ + display_name = "Stun Trap Weight" + + +class LiteratureTrapWeight(BaseTrapWeight): + """ + Likelihood of a receiving a trap which causes the player to read literature + """ + display_name = "Literature Trap Weight" + + +class Autosave(DefaultOnToggle): + """ + Whether a save prompt will appear after every level + """ + display_name = "Autosave" + + +class MusicShuffle(Choice): + """ + Music shuffle type + None: No Music is shuffled + Consistent: Each music track is consistently shuffled throughout the game + Full: Each individual level has a random music track + Singularity: The entire game uses one song for overworld and one song for levels + """ + display_name = "Music Shuffle" + option_none = 0 + option_consistent = 1 + option_full = 2 + option_singularity = 3 + default = 0 + + +class MarioPalette(Choice): + """ + Mario palette color + """ + display_name = "Mario Palette" + option_mario = 0 + option_luigi = 1 + option_wario = 2 + option_waluigi = 3 + option_geno = 4 + option_princess = 5 + option_dark = 6 + option_sponge = 7 + default = 0 + + +class ForegroundPaletteShuffle(Toggle): + """ + Whether to shuffle level foreground palettes + """ + display_name = "Foreground Palette Shuffle" + + +class BackgroundPaletteShuffle(Toggle): + """ + Whether to shuffle level background palettes + """ + display_name = "Background Palette Shuffle" + + +class StartingLifeCount(Range): + """ + How many extra lives to start the game with + """ + display_name = "Starting Life Count" + range_start = 1 + range_end = 99 + default = 5 + + + +smw_options: typing.Dict[str, type(Option)] = { + "death_link": DeathLink, + "goal": Goal, + "bosses_required": BossesRequired, + "number_of_yoshi_eggs": NumberOfYoshiEggs, + "percentage_of_yoshi_eggs": PercentageOfYoshiEggs, + "dragon_coin_checks": DragonCoinChecks, + "bowser_castle_doors": BowserCastleDoors, + "level_shuffle": LevelShuffle, + "swap_donut_gh_exits": SwapDonutGhostHouseExits, + #"display_sent_item_popups": DisplaySentItemPopups, + "display_received_item_popups": DisplayReceivedItemPopups, + "trap_fill_percentage": TrapFillPercentage, + "ice_trap_weight": IceTrapWeight, + "stun_trap_weight": StunTrapWeight, + "literature_trap_weight": LiteratureTrapWeight, + "autosave": Autosave, + "music_shuffle": MusicShuffle, + "mario_palette": MarioPalette, + "foreground_palette_shuffle": ForegroundPaletteShuffle, + "background_palette_shuffle": BackgroundPaletteShuffle, + "starting_life_count": StartingLifeCount, +} diff --git a/worlds/smw/Regions.py b/worlds/smw/Regions.py new file mode 100644 index 0000000000..7af7b888c9 --- /dev/null +++ b/worlds/smw/Regions.py @@ -0,0 +1,1187 @@ +import typing + +from BaseClasses import MultiWorld, Region, RegionType, Entrance +from .Locations import SMWLocation +from .Levels import level_info_dict +from .Names import LocationName, ItemName +from ..generic.Rules import add_rule, set_rule + + +def create_regions(world, player: int, active_locations): + menu_region = create_region(world, player, active_locations, 'Menu', None) + + yoshis_island_region = create_region(world, player, active_locations, LocationName.yoshis_island_region, None) + donut_plains_region = create_region(world, player, active_locations, LocationName.donut_plains_region, None) + vanilla_dome_region = create_region(world, player, active_locations, LocationName.vanilla_dome_region, None) + twin_bridges_region = create_region(world, player, active_locations, LocationName.twin_bridges_region, None) + forest_of_illusion_region = create_region(world, player, active_locations, LocationName.forest_of_illusion_region, None) + chocolate_island_region = create_region(world, player, active_locations, LocationName.chocolate_island_region, None) + valley_of_bowser_region = create_region(world, player, active_locations, LocationName.valley_of_bowser_region, None) + star_road_region = create_region(world, player, active_locations, LocationName.star_road_region, None) + special_zone_region = create_region(world, player, active_locations, LocationName.special_zone_region, None) + + + yoshis_house_tile = create_region(world, player, active_locations, LocationName.yoshis_house_tile, None) + + yoshis_house_region_locations = [] + if world.goal[player] == "yoshi_egg_hunt": + yoshis_house_region_locations.append(LocationName.yoshis_house) + yoshis_house_region = create_region(world, player, active_locations, LocationName.yoshis_house, + yoshis_house_region_locations) + + yoshis_island_1_tile = create_region(world, player, active_locations, LocationName.yoshis_island_1_tile, None) + yoshis_island_1_region = create_region(world, player, active_locations, LocationName.yoshis_island_1_region, None) + yoshis_island_1_exit_1 = create_region(world, player, active_locations, LocationName.yoshis_island_1_exit_1, + [LocationName.yoshis_island_1_exit_1]) + + yoshis_island_2_tile = create_region(world, player, active_locations, LocationName.yoshis_island_2_tile, None) + yoshis_island_2_region = create_region(world, player, active_locations, LocationName.yoshis_island_2_region, None) + yoshis_island_2_exit_1 = create_region(world, player, active_locations, LocationName.yoshis_island_2_exit_1, + [LocationName.yoshis_island_2_exit_1]) + + yoshis_island_3_tile = create_region(world, player, active_locations, LocationName.yoshis_island_3_tile, None) + yoshis_island_3_region = create_region(world, player, active_locations, LocationName.yoshis_island_3_region, None) + yoshis_island_3_exit_1 = create_region(world, player, active_locations, LocationName.yoshis_island_3_exit_1, + [LocationName.yoshis_island_3_exit_1]) + + yoshis_island_4_tile = create_region(world, player, active_locations, LocationName.yoshis_island_4_tile, None) + yoshis_island_4_region = create_region(world, player, active_locations, LocationName.yoshis_island_4_region, None) + yoshis_island_4_exit_1 = create_region(world, player, active_locations, LocationName.yoshis_island_4_exit_1, + [LocationName.yoshis_island_4_exit_1]) + + yoshis_island_castle_tile = create_region(world, player, active_locations, LocationName.yoshis_island_castle_tile, None) + yoshis_island_castle_region = create_region(world, player, active_locations, LocationName.yoshis_island_castle_region, None) + yoshis_island_castle = create_region(world, player, active_locations, LocationName.yoshis_island_castle, + [LocationName.yoshis_island_castle, LocationName.yoshis_island_koopaling]) + + yellow_switch_palace_tile = create_region(world, player, active_locations, LocationName.yellow_switch_palace_tile, None) + yellow_switch_palace = create_region(world, player, active_locations, LocationName.yellow_switch_palace, + [LocationName.yellow_switch_palace]) + + + donut_plains_1_tile = create_region(world, player, active_locations, LocationName.donut_plains_1_tile, None) + donut_plains_1_region = create_region(world, player, active_locations, LocationName.donut_plains_1_region, None) + donut_plains_1_exit_1 = create_region(world, player, active_locations, LocationName.donut_plains_1_exit_1, + [LocationName.donut_plains_1_exit_1]) + donut_plains_1_exit_2 = create_region(world, player, active_locations, LocationName.donut_plains_1_exit_2, + [LocationName.donut_plains_1_exit_2]) + + donut_plains_2_tile = create_region(world, player, active_locations, LocationName.donut_plains_2_tile, None) + donut_plains_2_region = create_region(world, player, active_locations, LocationName.donut_plains_2_region, None) + donut_plains_2_exit_1 = create_region(world, player, active_locations, LocationName.donut_plains_2_exit_1, + [LocationName.donut_plains_2_exit_1]) + donut_plains_2_exit_2 = create_region(world, player, active_locations, LocationName.donut_plains_2_exit_2, + [LocationName.donut_plains_2_exit_2]) + + donut_plains_3_tile = create_region(world, player, active_locations, LocationName.donut_plains_3_tile, None) + donut_plains_3_region = create_region(world, player, active_locations, LocationName.donut_plains_3_region, None) + donut_plains_3_exit_1 = create_region(world, player, active_locations, LocationName.donut_plains_3_exit_1, + [LocationName.donut_plains_3_exit_1]) + + donut_plains_4_tile = create_region(world, player, active_locations, LocationName.donut_plains_4_tile, None) + donut_plains_4_region = create_region(world, player, active_locations, LocationName.donut_plains_4_region, None) + donut_plains_4_exit_1 = create_region(world, player, active_locations, LocationName.donut_plains_4_exit_1, + [LocationName.donut_plains_4_exit_1]) + + donut_secret_1_tile = create_region(world, player, active_locations, LocationName.donut_secret_1_tile, None) + donut_secret_1_region = create_region(world, player, active_locations, LocationName.donut_secret_1_region, None) + donut_secret_1_exit_1 = create_region(world, player, active_locations, LocationName.donut_secret_1_exit_1, + [LocationName.donut_secret_1_exit_1]) + donut_secret_1_exit_2 = create_region(world, player, active_locations, LocationName.donut_secret_1_exit_2, + [LocationName.donut_secret_1_exit_2]) + + donut_secret_2_tile = create_region(world, player, active_locations, LocationName.donut_secret_2_tile, None) + donut_secret_2_region = create_region(world, player, active_locations, LocationName.donut_secret_2_region, None) + donut_secret_2_exit_1 = create_region(world, player, active_locations, LocationName.donut_secret_2_exit_1, + [LocationName.donut_secret_2_exit_1]) + + donut_ghost_house_tile = create_region(world, player, active_locations, LocationName.donut_ghost_house_tile, None) + donut_ghost_house_region = create_region(world, player, active_locations, LocationName.donut_ghost_house_region, None) + donut_ghost_house_exit_1 = create_region(world, player, active_locations, LocationName.donut_ghost_house_exit_1, + [LocationName.donut_ghost_house_exit_1]) + donut_ghost_house_exit_2 = create_region(world, player, active_locations, LocationName.donut_ghost_house_exit_2, + [LocationName.donut_ghost_house_exit_2]) + + donut_secret_house_tile = create_region(world, player, active_locations, LocationName.donut_secret_house_tile, None) + donut_secret_house_region = create_region(world, player, active_locations, LocationName.donut_secret_house_region, None) + donut_secret_house_exit_1 = create_region(world, player, active_locations, LocationName.donut_secret_house_exit_1, + [LocationName.donut_secret_house_exit_1]) + donut_secret_house_exit_2 = create_region(world, player, active_locations, LocationName.donut_secret_house_exit_2, + [LocationName.donut_secret_house_exit_2]) + + donut_plains_castle_tile = create_region(world, player, active_locations, LocationName.donut_plains_castle_tile, None) + donut_plains_castle_region = create_region(world, player, active_locations, LocationName.donut_plains_castle_region, None) + donut_plains_castle = create_region(world, player, active_locations, LocationName.donut_plains_castle, + [LocationName.donut_plains_castle, LocationName.donut_plains_koopaling]) + + green_switch_palace_tile = create_region(world, player, active_locations, LocationName.green_switch_palace_tile, None) + green_switch_palace = create_region(world, player, active_locations, LocationName.green_switch_palace, + [LocationName.green_switch_palace]) + + donut_plains_top_secret_tile = create_region(world, player, active_locations, LocationName.donut_plains_top_secret_tile, None) + donut_plains_top_secret = create_region(world, player, active_locations, LocationName.donut_plains_top_secret, None) + + + vanilla_dome_1_tile = create_region(world, player, active_locations, LocationName.vanilla_dome_1_tile, None) + vanilla_dome_1_region = create_region(world, player, active_locations, LocationName.vanilla_dome_1_region, None) + vanilla_dome_1_exit_1 = create_region(world, player, active_locations, LocationName.vanilla_dome_1_exit_1, + [LocationName.vanilla_dome_1_exit_1]) + vanilla_dome_1_exit_2 = create_region(world, player, active_locations, LocationName.vanilla_dome_1_exit_2, + [LocationName.vanilla_dome_1_exit_2]) + + vanilla_dome_2_tile = create_region(world, player, active_locations, LocationName.vanilla_dome_2_tile, None) + vanilla_dome_2_region = create_region(world, player, active_locations, LocationName.vanilla_dome_2_region, None) + vanilla_dome_2_exit_1 = create_region(world, player, active_locations, LocationName.vanilla_dome_2_exit_1, + [LocationName.vanilla_dome_2_exit_1]) + vanilla_dome_2_exit_2 = create_region(world, player, active_locations, LocationName.vanilla_dome_2_exit_2, + [LocationName.vanilla_dome_2_exit_2]) + + vanilla_dome_3_tile = create_region(world, player, active_locations, LocationName.vanilla_dome_3_tile, None) + vanilla_dome_3_region = create_region(world, player, active_locations, LocationName.vanilla_dome_3_region, None) + vanilla_dome_3_exit_1 = create_region(world, player, active_locations, LocationName.vanilla_dome_3_exit_1, + [LocationName.vanilla_dome_3_exit_1]) + + vanilla_dome_4_tile = create_region(world, player, active_locations, LocationName.vanilla_dome_4_tile, None) + vanilla_dome_4_region = create_region(world, player, active_locations, LocationName.vanilla_dome_4_region, None) + vanilla_dome_4_exit_1 = create_region(world, player, active_locations, LocationName.vanilla_dome_4_exit_1, + [LocationName.vanilla_dome_4_exit_1]) + + vanilla_secret_1_tile = create_region(world, player, active_locations, LocationName.vanilla_secret_1_tile, None) + vanilla_secret_1_region = create_region(world, player, active_locations, LocationName.vanilla_secret_1_region, None) + vanilla_secret_1_exit_1 = create_region(world, player, active_locations, LocationName.vanilla_secret_1_exit_1, + [LocationName.vanilla_secret_1_exit_1]) + vanilla_secret_1_exit_2 = create_region(world, player, active_locations, LocationName.vanilla_secret_1_exit_2, + [LocationName.vanilla_secret_1_exit_2]) + + vanilla_secret_2_tile = create_region(world, player, active_locations, LocationName.vanilla_secret_2_tile, None) + vanilla_secret_2_region = create_region(world, player, active_locations, LocationName.vanilla_secret_2_region, None) + vanilla_secret_2_exit_1 = create_region(world, player, active_locations, LocationName.vanilla_secret_2_exit_1, + [LocationName.vanilla_secret_2_exit_1]) + + vanilla_secret_3_tile = create_region(world, player, active_locations, LocationName.vanilla_secret_3_tile, None) + vanilla_secret_3_region = create_region(world, player, active_locations, LocationName.vanilla_secret_3_region, None) + vanilla_secret_3_exit_1 = create_region(world, player, active_locations, LocationName.vanilla_secret_3_exit_1, + [LocationName.vanilla_secret_3_exit_1]) + + vanilla_ghost_house_tile = create_region(world, player, active_locations, LocationName.vanilla_ghost_house_tile, None) + vanilla_ghost_house_region = create_region(world, player, active_locations, LocationName.vanilla_ghost_house_region, None) + vanilla_ghost_house_exit_1 = create_region(world, player, active_locations, LocationName.vanilla_ghost_house_exit_1, + [LocationName.vanilla_ghost_house_exit_1]) + + vanilla_fortress_tile = create_region(world, player, active_locations, LocationName.vanilla_fortress_tile, None) + vanilla_fortress_region = create_region(world, player, active_locations, LocationName.vanilla_fortress_region, None) + vanilla_fortress = create_region(world, player, active_locations, LocationName.vanilla_fortress, + [LocationName.vanilla_fortress, LocationName.vanilla_reznor]) + + vanilla_dome_castle_tile = create_region(world, player, active_locations, LocationName.vanilla_dome_castle_tile, None) + vanilla_dome_castle_region = create_region(world, player, active_locations, LocationName.vanilla_dome_castle_region, None) + vanilla_dome_castle = create_region(world, player, active_locations, LocationName.vanilla_dome_castle, + [LocationName.vanilla_dome_castle, LocationName.vanilla_dome_koopaling]) + + red_switch_palace_tile = create_region(world, player, active_locations, LocationName.red_switch_palace_tile, None) + red_switch_palace = create_region(world, player, active_locations, LocationName.red_switch_palace, + [LocationName.red_switch_palace]) + + + butter_bridge_1_tile = create_region(world, player, active_locations, LocationName.butter_bridge_1_tile, None) + butter_bridge_1_region = create_region(world, player, active_locations, LocationName.butter_bridge_1_region, None) + butter_bridge_1_exit_1 = create_region(world, player, active_locations, LocationName.butter_bridge_1_exit_1, + [LocationName.butter_bridge_1_exit_1]) + + butter_bridge_2_tile = create_region(world, player, active_locations, LocationName.butter_bridge_2_tile, None) + butter_bridge_2_region = create_region(world, player, active_locations, LocationName.butter_bridge_2_region, None) + butter_bridge_2_exit_1 = create_region(world, player, active_locations, LocationName.butter_bridge_2_exit_1, + [LocationName.butter_bridge_2_exit_1]) + + cheese_bridge_tile = create_region(world, player, active_locations, LocationName.cheese_bridge_tile, None) + cheese_bridge_region = create_region(world, player, active_locations, LocationName.cheese_bridge_region, None) + cheese_bridge_exit_1 = create_region(world, player, active_locations, LocationName.cheese_bridge_exit_1, + [LocationName.cheese_bridge_exit_1]) + cheese_bridge_exit_2 = create_region(world, player, active_locations, LocationName.cheese_bridge_exit_2, + [LocationName.cheese_bridge_exit_2]) + + cookie_mountain_tile = create_region(world, player, active_locations, LocationName.cookie_mountain_tile, None) + cookie_mountain_region = create_region(world, player, active_locations, LocationName.cookie_mountain_region, None) + cookie_mountain_exit_1 = create_region(world, player, active_locations, LocationName.cookie_mountain_exit_1, + [LocationName.cookie_mountain_exit_1]) + + soda_lake_tile = create_region(world, player, active_locations, LocationName.soda_lake_tile, None) + soda_lake_region = create_region(world, player, active_locations, LocationName.soda_lake_region, None) + soda_lake_exit_1 = create_region(world, player, active_locations, LocationName.soda_lake_exit_1, + [LocationName.soda_lake_exit_1]) + + twin_bridges_castle_tile = create_region(world, player, active_locations, LocationName.twin_bridges_castle_tile, None) + twin_bridges_castle_region = create_region(world, player, active_locations, LocationName.twin_bridges_castle_region, None) + twin_bridges_castle = create_region(world, player, active_locations, LocationName.twin_bridges_castle, + [LocationName.twin_bridges_castle, LocationName.twin_bridges_koopaling]) + + + forest_of_illusion_1_tile = create_region(world, player, active_locations, LocationName.forest_of_illusion_1_tile, None) + forest_of_illusion_1_region = create_region(world, player, active_locations, LocationName.forest_of_illusion_1_region, None) + forest_of_illusion_1_exit_1 = create_region(world, player, active_locations, LocationName.forest_of_illusion_1_exit_1, + [LocationName.forest_of_illusion_1_exit_1]) + forest_of_illusion_1_exit_2 = create_region(world, player, active_locations, LocationName.forest_of_illusion_1_exit_2, + [LocationName.forest_of_illusion_1_exit_2]) + + forest_of_illusion_2_tile = create_region(world, player, active_locations, LocationName.forest_of_illusion_2_tile, None) + forest_of_illusion_2_region = create_region(world, player, active_locations, LocationName.forest_of_illusion_2_region, None) + forest_of_illusion_2_exit_1 = create_region(world, player, active_locations, LocationName.forest_of_illusion_2_exit_1, + [LocationName.forest_of_illusion_2_exit_1]) + forest_of_illusion_2_exit_2 = create_region(world, player, active_locations, LocationName.forest_of_illusion_2_exit_2, + [LocationName.forest_of_illusion_2_exit_2]) + + forest_of_illusion_3_tile = create_region(world, player, active_locations, LocationName.forest_of_illusion_3_tile, None) + forest_of_illusion_3_region = create_region(world, player, active_locations, LocationName.forest_of_illusion_3_region, None) + forest_of_illusion_3_exit_1 = create_region(world, player, active_locations, LocationName.forest_of_illusion_3_exit_1, + [LocationName.forest_of_illusion_3_exit_1]) + forest_of_illusion_3_exit_2 = create_region(world, player, active_locations, LocationName.forest_of_illusion_3_exit_2, + [LocationName.forest_of_illusion_3_exit_2]) + + forest_of_illusion_4_tile = create_region(world, player, active_locations, LocationName.forest_of_illusion_4_tile, None) + forest_of_illusion_4_region = create_region(world, player, active_locations, LocationName.forest_of_illusion_4_region, None) + forest_of_illusion_4_exit_1 = create_region(world, player, active_locations, LocationName.forest_of_illusion_4_exit_1, + [LocationName.forest_of_illusion_4_exit_1]) + forest_of_illusion_4_exit_2 = create_region(world, player, active_locations, LocationName.forest_of_illusion_4_exit_2, + [LocationName.forest_of_illusion_4_exit_2]) + + forest_ghost_house_tile = create_region(world, player, active_locations, LocationName.forest_ghost_house_tile, None) + forest_ghost_house_region = create_region(world, player, active_locations, LocationName.forest_ghost_house_region, None) + forest_ghost_house_exit_1 = create_region(world, player, active_locations, LocationName.forest_ghost_house_exit_1, + [LocationName.forest_ghost_house_exit_1]) + forest_ghost_house_exit_2 = create_region(world, player, active_locations, LocationName.forest_ghost_house_exit_2, + [LocationName.forest_ghost_house_exit_2]) + + forest_secret_tile = create_region(world, player, active_locations, LocationName.forest_secret_tile, None) + forest_secret_region = create_region(world, player, active_locations, LocationName.forest_secret_region, None) + forest_secret_exit_1 = create_region(world, player, active_locations, LocationName.forest_secret_exit_1, + [LocationName.forest_secret_exit_1]) + + forest_fortress_tile = create_region(world, player, active_locations, LocationName.forest_fortress_tile, None) + forest_fortress_region = create_region(world, player, active_locations, LocationName.forest_fortress_region, None) + forest_fortress = create_region(world, player, active_locations, LocationName.forest_fortress, + [LocationName.forest_fortress, LocationName.forest_reznor]) + + forest_castle_tile = create_region(world, player, active_locations, LocationName.forest_castle_tile, None) + forest_castle_region = create_region(world, player, active_locations, LocationName.forest_castle_region, None) + forest_castle = create_region(world, player, active_locations, LocationName.forest_castle, + [LocationName.forest_castle, LocationName.forest_koopaling]) + + blue_switch_palace_tile = create_region(world, player, active_locations, LocationName.blue_switch_palace_tile, None) + blue_switch_palace = create_region(world, player, active_locations, LocationName.blue_switch_palace, + [LocationName.blue_switch_palace]) + + + chocolate_island_1_tile = create_region(world, player, active_locations, LocationName.chocolate_island_1_tile, None) + chocolate_island_1_region = create_region(world, player, active_locations, LocationName.chocolate_island_1_region, None) + chocolate_island_1_exit_1 = create_region(world, player, active_locations, LocationName.chocolate_island_1_exit_1, + [LocationName.chocolate_island_1_exit_1]) + + chocolate_island_2_tile = create_region(world, player, active_locations, LocationName.chocolate_island_2_tile, None) + chocolate_island_2_region = create_region(world, player, active_locations, LocationName.chocolate_island_2_region, None) + chocolate_island_2_exit_1 = create_region(world, player, active_locations, LocationName.chocolate_island_2_exit_1, + [LocationName.chocolate_island_2_exit_1]) + chocolate_island_2_exit_2 = create_region(world, player, active_locations, LocationName.chocolate_island_2_exit_2, + [LocationName.chocolate_island_2_exit_2]) + + chocolate_island_3_tile = create_region(world, player, active_locations, LocationName.chocolate_island_3_tile, None) + chocolate_island_3_region = create_region(world, player, active_locations, LocationName.chocolate_island_3_region, None) + chocolate_island_3_exit_1 = create_region(world, player, active_locations, LocationName.chocolate_island_3_exit_1, + [LocationName.chocolate_island_3_exit_1]) + chocolate_island_3_exit_2 = create_region(world, player, active_locations, LocationName.chocolate_island_3_exit_2, + [LocationName.chocolate_island_3_exit_2]) + + chocolate_island_4_tile = create_region(world, player, active_locations, LocationName.chocolate_island_4_tile, None) + chocolate_island_4_region = create_region(world, player, active_locations, LocationName.chocolate_island_4_region, None) + chocolate_island_4_exit_1 = create_region(world, player, active_locations, LocationName.chocolate_island_4_exit_1, + [LocationName.chocolate_island_4_exit_1]) + + chocolate_island_5_tile = create_region(world, player, active_locations, LocationName.chocolate_island_5_tile, None) + chocolate_island_5_region = create_region(world, player, active_locations, LocationName.chocolate_island_5_region, None) + chocolate_island_5_exit_1 = create_region(world, player, active_locations, LocationName.chocolate_island_5_exit_1, + [LocationName.chocolate_island_5_exit_1]) + + chocolate_ghost_house_tile = create_region(world, player, active_locations, LocationName.chocolate_ghost_house_tile, None) + chocolate_ghost_house_region = create_region(world, player, active_locations, LocationName.chocolate_ghost_house_region, None) + chocolate_ghost_house_exit_1 = create_region(world, player, active_locations, LocationName.chocolate_ghost_house_exit_1, + [LocationName.chocolate_ghost_house_exit_1]) + + chocolate_secret_tile = create_region(world, player, active_locations, LocationName.chocolate_secret_tile, None) + chocolate_secret_region = create_region(world, player, active_locations, LocationName.chocolate_secret_region, None) + chocolate_secret_exit_1 = create_region(world, player, active_locations, LocationName.chocolate_secret_exit_1, + [LocationName.chocolate_secret_exit_1]) + + chocolate_fortress_tile = create_region(world, player, active_locations, LocationName.chocolate_fortress_tile, None) + chocolate_fortress_region = create_region(world, player, active_locations, LocationName.chocolate_fortress_region, None) + chocolate_fortress = create_region(world, player, active_locations, LocationName.chocolate_fortress, + [LocationName.chocolate_fortress, LocationName.chocolate_reznor]) + + chocolate_castle_tile = create_region(world, player, active_locations, LocationName.chocolate_castle_tile, None) + chocolate_castle_region = create_region(world, player, active_locations, LocationName.chocolate_castle_region, None) + chocolate_castle = create_region(world, player, active_locations, LocationName.chocolate_castle, + [LocationName.chocolate_castle, LocationName.chocolate_koopaling]) + + sunken_ghost_ship_tile = create_region(world, player, active_locations, LocationName.sunken_ghost_ship_tile, None) + sunken_ghost_ship_region = create_region(world, player, active_locations, LocationName.sunken_ghost_ship_region, None) + sunken_ghost_ship = create_region(world, player, active_locations, LocationName.sunken_ghost_ship, + [LocationName.sunken_ghost_ship]) + + + valley_of_bowser_1_tile = create_region(world, player, active_locations, LocationName.valley_of_bowser_1_tile, None) + valley_of_bowser_1_region = create_region(world, player, active_locations, LocationName.valley_of_bowser_1_region, None) + valley_of_bowser_1_exit_1 = create_region(world, player, active_locations, LocationName.valley_of_bowser_1_exit_1, + [LocationName.valley_of_bowser_1_exit_1]) + + valley_of_bowser_2_tile = create_region(world, player, active_locations, LocationName.valley_of_bowser_2_tile, None) + valley_of_bowser_2_region = create_region(world, player, active_locations, LocationName.valley_of_bowser_2_region, None) + valley_of_bowser_2_exit_1 = create_region(world, player, active_locations, LocationName.valley_of_bowser_2_exit_1, + [LocationName.valley_of_bowser_2_exit_1]) + valley_of_bowser_2_exit_2 = create_region(world, player, active_locations, LocationName.valley_of_bowser_2_exit_2, + [LocationName.valley_of_bowser_2_exit_2]) + + valley_of_bowser_3_tile = create_region(world, player, active_locations, LocationName.valley_of_bowser_3_tile, None) + valley_of_bowser_3_region = create_region(world, player, active_locations, LocationName.valley_of_bowser_3_region, None) + valley_of_bowser_3_exit_1 = create_region(world, player, active_locations, LocationName.valley_of_bowser_3_exit_1, + [LocationName.valley_of_bowser_3_exit_1]) + + valley_of_bowser_4_tile = create_region(world, player, active_locations, LocationName.valley_of_bowser_4_tile, None) + valley_of_bowser_4_region = create_region(world, player, active_locations, LocationName.valley_of_bowser_4_region, None) + valley_of_bowser_4_exit_1 = create_region(world, player, active_locations, LocationName.valley_of_bowser_4_exit_1, + [LocationName.valley_of_bowser_4_exit_1]) + valley_of_bowser_4_exit_2 = create_region(world, player, active_locations, LocationName.valley_of_bowser_4_exit_2, + [LocationName.valley_of_bowser_4_exit_2]) + + valley_ghost_house_tile = create_region(world, player, active_locations, LocationName.valley_ghost_house_tile, None) + valley_ghost_house_region = create_region(world, player, active_locations, LocationName.valley_ghost_house_region, None) + valley_ghost_house_exit_1 = create_region(world, player, active_locations, LocationName.valley_ghost_house_exit_1, + [LocationName.valley_ghost_house_exit_1]) + valley_ghost_house_exit_2 = create_region(world, player, active_locations, LocationName.valley_ghost_house_exit_2, + [LocationName.valley_ghost_house_exit_2]) + + valley_fortress_tile = create_region(world, player, active_locations, LocationName.valley_fortress_tile, None) + valley_fortress_region = create_region(world, player, active_locations, LocationName.valley_fortress_region, None) + valley_fortress = create_region(world, player, active_locations, LocationName.valley_fortress, + [LocationName.valley_fortress, LocationName.valley_reznor]) + + valley_castle_tile = create_region(world, player, active_locations, LocationName.valley_castle_tile, None) + valley_castle_region = create_region(world, player, active_locations, LocationName.valley_castle_region, None) + valley_castle = create_region(world, player, active_locations, LocationName.valley_castle, + [LocationName.valley_castle, LocationName.valley_koopaling]) + + front_door_tile = create_region(world, player, active_locations, LocationName.front_door_tile, None) + front_door_region = create_region(world, player, active_locations, LocationName.front_door, None) + back_door_tile = create_region(world, player, active_locations, LocationName.back_door_tile, None) + back_door_region = create_region(world, player, active_locations, LocationName.back_door, None) + bowser_region_locations = [] + if world.goal[player] == "bowser": + bowser_region_locations += [LocationName.bowser] + bowser_region = create_region(world, player, active_locations, LocationName.bowser_region, bowser_region_locations) + + + donut_plains_star_road = create_region(world, player, active_locations, LocationName.donut_plains_star_road, None) + vanilla_dome_star_road = create_region(world, player, active_locations, LocationName.vanilla_dome_star_road, None) + twin_bridges_star_road = create_region(world, player, active_locations, LocationName.twin_bridges_star_road, None) + forest_star_road = create_region(world, player, active_locations, LocationName.forest_star_road, None) + valley_star_road = create_region(world, player, active_locations, LocationName.valley_star_road, None) + star_road_donut = create_region(world, player, active_locations, LocationName.star_road_donut, None) + star_road_vanilla = create_region(world, player, active_locations, LocationName.star_road_vanilla, None) + star_road_twin_bridges = create_region(world, player, active_locations, LocationName.star_road_twin_bridges, None) + star_road_forest = create_region(world, player, active_locations, LocationName.star_road_forest, None) + star_road_valley = create_region(world, player, active_locations, LocationName.star_road_valley, None) + star_road_special = create_region(world, player, active_locations, LocationName.star_road_special, None) + special_star_road = create_region(world, player, active_locations, LocationName.special_star_road, None) + + star_road_1_tile = create_region(world, player, active_locations, LocationName.star_road_1_tile, None) + star_road_1_region = create_region(world, player, active_locations, LocationName.star_road_1_region, None) + star_road_1_exit_1 = create_region(world, player, active_locations, LocationName.star_road_1_exit_1, + [LocationName.star_road_1_exit_1]) + star_road_1_exit_2 = create_region(world, player, active_locations, LocationName.star_road_1_exit_2, + [LocationName.star_road_1_exit_2]) + + star_road_2_tile = create_region(world, player, active_locations, LocationName.star_road_2_tile, None) + star_road_2_region = create_region(world, player, active_locations, LocationName.star_road_2_region, None) + star_road_2_exit_1 = create_region(world, player, active_locations, LocationName.star_road_2_exit_1, + [LocationName.star_road_2_exit_1]) + star_road_2_exit_2 = create_region(world, player, active_locations, LocationName.star_road_2_exit_2, + [LocationName.star_road_2_exit_2]) + + star_road_3_tile = create_region(world, player, active_locations, LocationName.star_road_3_tile, None) + star_road_3_region = create_region(world, player, active_locations, LocationName.star_road_3_region, None) + star_road_3_exit_1 = create_region(world, player, active_locations, LocationName.star_road_3_exit_1, + [LocationName.star_road_3_exit_1]) + star_road_3_exit_2 = create_region(world, player, active_locations, LocationName.star_road_3_exit_2, + [LocationName.star_road_3_exit_2]) + + star_road_4_tile = create_region(world, player, active_locations, LocationName.star_road_4_tile, None) + star_road_4_region = create_region(world, player, active_locations, LocationName.star_road_4_region, None) + star_road_4_exit_1 = create_region(world, player, active_locations, LocationName.star_road_4_exit_1, + [LocationName.star_road_4_exit_1]) + star_road_4_exit_2 = create_region(world, player, active_locations, LocationName.star_road_4_exit_2, + [LocationName.star_road_4_exit_2]) + + star_road_5_tile = create_region(world, player, active_locations, LocationName.star_road_5_tile, None) + star_road_5_region = create_region(world, player, active_locations, LocationName.star_road_5_region, None) + star_road_5_exit_1 = create_region(world, player, active_locations, LocationName.star_road_5_exit_1, + [LocationName.star_road_5_exit_1]) + star_road_5_exit_2 = create_region(world, player, active_locations, LocationName.star_road_5_exit_2, + [LocationName.star_road_5_exit_2]) + + + special_zone_1_tile = create_region(world, player, active_locations, LocationName.special_zone_1_tile, None) + special_zone_1_region = create_region(world, player, active_locations, LocationName.special_zone_1_region, None) + special_zone_1_exit_1 = create_region(world, player, active_locations, LocationName.special_zone_1_exit_1, + [LocationName.special_zone_1_exit_1]) + + special_zone_2_tile = create_region(world, player, active_locations, LocationName.special_zone_2_tile, None) + special_zone_2_region = create_region(world, player, active_locations, LocationName.special_zone_2_region, None) + special_zone_2_exit_1 = create_region(world, player, active_locations, LocationName.special_zone_2_exit_1, + [LocationName.special_zone_2_exit_1]) + + special_zone_3_tile = create_region(world, player, active_locations, LocationName.special_zone_3_tile, None) + special_zone_3_region = create_region(world, player, active_locations, LocationName.special_zone_3_region, None) + special_zone_3_exit_1 = create_region(world, player, active_locations, LocationName.special_zone_3_exit_1, + [LocationName.special_zone_3_exit_1]) + + special_zone_4_tile = create_region(world, player, active_locations, LocationName.special_zone_4_tile, None) + special_zone_4_region = create_region(world, player, active_locations, LocationName.special_zone_4_region, None) + special_zone_4_exit_1 = create_region(world, player, active_locations, LocationName.special_zone_4_exit_1, + [LocationName.special_zone_4_exit_1]) + + special_zone_5_tile = create_region(world, player, active_locations, LocationName.special_zone_5_tile, None) + special_zone_5_region = create_region(world, player, active_locations, LocationName.special_zone_5_region, None) + special_zone_5_exit_1 = create_region(world, player, active_locations, LocationName.special_zone_5_exit_1, + [LocationName.special_zone_5_exit_1]) + + special_zone_6_tile = create_region(world, player, active_locations, LocationName.special_zone_6_tile, None) + special_zone_6_region = create_region(world, player, active_locations, LocationName.special_zone_6_region, None) + special_zone_6_exit_1 = create_region(world, player, active_locations, LocationName.special_zone_6_exit_1, + [LocationName.special_zone_6_exit_1]) + + special_zone_7_tile = create_region(world, player, active_locations, LocationName.special_zone_7_tile, None) + special_zone_7_region = create_region(world, player, active_locations, LocationName.special_zone_7_region, None) + special_zone_7_exit_1 = create_region(world, player, active_locations, LocationName.special_zone_7_exit_1, + [LocationName.special_zone_7_exit_1]) + + special_zone_8_tile = create_region(world, player, active_locations, LocationName.special_zone_8_tile, None) + special_zone_8_region = create_region(world, player, active_locations, LocationName.special_zone_8_region, None) + special_zone_8_exit_1 = create_region(world, player, active_locations, LocationName.special_zone_8_exit_1, + [LocationName.special_zone_8_exit_1]) + special_complete = create_region(world, player, active_locations, LocationName.special_complete, None) + + + # Set up the regions correctly. + world.regions += [ + menu_region, + yoshis_island_region, + donut_plains_region, + vanilla_dome_region, + twin_bridges_region, + forest_of_illusion_region, + chocolate_island_region, + valley_of_bowser_region, + star_road_region, + special_zone_region, + yoshis_house_tile, + yoshis_house_region, + yoshis_island_1_tile, + yoshis_island_1_region, + yoshis_island_1_exit_1, + yoshis_island_2_tile, + yoshis_island_2_region, + yoshis_island_2_exit_1, + yoshis_island_3_tile, + yoshis_island_3_region, + yoshis_island_3_exit_1, + yoshis_island_4_tile, + yoshis_island_4_region, + yoshis_island_4_exit_1, + yoshis_island_castle_tile, + yoshis_island_castle_region, + yoshis_island_castle, + yellow_switch_palace_tile, + yellow_switch_palace, + donut_plains_1_tile, + donut_plains_1_region, + donut_plains_1_exit_1, + donut_plains_1_exit_2, + donut_plains_2_tile, + donut_plains_2_region, + donut_plains_2_exit_1, + donut_plains_2_exit_2, + donut_plains_3_tile, + donut_plains_3_region, + donut_plains_3_exit_1, + donut_plains_4_tile, + donut_plains_4_region, + donut_plains_4_exit_1, + donut_secret_1_tile, + donut_secret_1_region, + donut_secret_1_exit_1, + donut_secret_1_exit_2, + donut_secret_2_tile, + donut_secret_2_region, + donut_secret_2_exit_1, + donut_ghost_house_tile, + donut_ghost_house_region, + donut_ghost_house_exit_1, + donut_ghost_house_exit_2, + donut_secret_house_tile, + donut_secret_house_region, + donut_secret_house_exit_1, + donut_secret_house_exit_2, + donut_plains_castle_tile, + donut_plains_castle_region, + donut_plains_castle, + green_switch_palace_tile, + green_switch_palace, + donut_plains_top_secret_tile, + donut_plains_top_secret, + vanilla_dome_1_tile, + vanilla_dome_1_region, + vanilla_dome_1_exit_1, + vanilla_dome_1_exit_2, + vanilla_dome_2_tile, + vanilla_dome_2_region, + vanilla_dome_2_exit_1, + vanilla_dome_2_exit_2, + vanilla_dome_3_tile, + vanilla_dome_3_region, + vanilla_dome_3_exit_1, + vanilla_dome_4_tile, + vanilla_dome_4_region, + vanilla_dome_4_exit_1, + vanilla_secret_1_tile, + vanilla_secret_1_region, + vanilla_secret_1_exit_1, + vanilla_secret_1_exit_2, + vanilla_secret_2_tile, + vanilla_secret_2_region, + vanilla_secret_2_exit_1, + vanilla_secret_3_tile, + vanilla_secret_3_region, + vanilla_secret_3_exit_1, + vanilla_ghost_house_tile, + vanilla_ghost_house_region, + vanilla_ghost_house_exit_1, + vanilla_fortress_tile, + vanilla_fortress_region, + vanilla_fortress, + vanilla_dome_castle_tile, + vanilla_dome_castle_region, + vanilla_dome_castle, + red_switch_palace_tile, + red_switch_palace, + butter_bridge_1_tile, + butter_bridge_1_region, + butter_bridge_1_exit_1, + butter_bridge_2_tile, + butter_bridge_2_region, + butter_bridge_2_exit_1, + cheese_bridge_tile, + cheese_bridge_region, + cheese_bridge_exit_1, + cheese_bridge_exit_2, + cookie_mountain_tile, + cookie_mountain_region, + cookie_mountain_exit_1, + soda_lake_tile, + soda_lake_region, + soda_lake_exit_1, + twin_bridges_castle_tile, + twin_bridges_castle_region, + twin_bridges_castle, + forest_of_illusion_1_tile, + forest_of_illusion_1_region, + forest_of_illusion_1_exit_1, + forest_of_illusion_1_exit_2, + forest_of_illusion_2_tile, + forest_of_illusion_2_region, + forest_of_illusion_2_exit_1, + forest_of_illusion_2_exit_2, + forest_of_illusion_3_tile, + forest_of_illusion_3_region, + forest_of_illusion_3_exit_1, + forest_of_illusion_3_exit_2, + forest_of_illusion_4_tile, + forest_of_illusion_4_region, + forest_of_illusion_4_exit_1, + forest_of_illusion_4_exit_2, + forest_ghost_house_tile, + forest_ghost_house_region, + forest_ghost_house_exit_1, + forest_ghost_house_exit_2, + forest_secret_tile, + forest_secret_region, + forest_secret_exit_1, + forest_fortress_tile, + forest_fortress_region, + forest_fortress, + forest_castle_tile, + forest_castle_region, + forest_castle, + blue_switch_palace_tile, + blue_switch_palace, + chocolate_island_1_tile, + chocolate_island_1_region, + chocolate_island_1_exit_1, + chocolate_island_2_tile, + chocolate_island_2_region, + chocolate_island_2_exit_1, + chocolate_island_2_exit_2, + chocolate_island_3_tile, + chocolate_island_3_region, + chocolate_island_3_exit_1, + chocolate_island_3_exit_2, + chocolate_island_4_tile, + chocolate_island_4_region, + chocolate_island_4_exit_1, + chocolate_island_5_tile, + chocolate_island_5_region, + chocolate_island_5_exit_1, + chocolate_ghost_house_tile, + chocolate_ghost_house_region, + chocolate_ghost_house_exit_1, + chocolate_secret_tile, + chocolate_secret_region, + chocolate_secret_exit_1, + chocolate_fortress_tile, + chocolate_fortress_region, + chocolate_fortress, + chocolate_castle_tile, + chocolate_castle_region, + chocolate_castle, + sunken_ghost_ship_tile, + sunken_ghost_ship_region, + sunken_ghost_ship, + valley_of_bowser_1_tile, + valley_of_bowser_1_region, + valley_of_bowser_1_exit_1, + valley_of_bowser_2_tile, + valley_of_bowser_2_region, + valley_of_bowser_2_exit_1, + valley_of_bowser_2_exit_2, + valley_of_bowser_3_tile, + valley_of_bowser_3_region, + valley_of_bowser_3_exit_1, + valley_of_bowser_4_tile, + valley_of_bowser_4_region, + valley_of_bowser_4_exit_1, + valley_of_bowser_4_exit_2, + valley_ghost_house_tile, + valley_ghost_house_region, + valley_ghost_house_exit_1, + valley_ghost_house_exit_2, + valley_fortress_tile, + valley_fortress_region, + valley_fortress, + valley_castle_tile, + valley_castle_region, + valley_castle, + front_door_tile, + front_door_region, + back_door_tile, + back_door_region, + bowser_region, + donut_plains_star_road, + vanilla_dome_star_road, + twin_bridges_star_road, + forest_star_road, + valley_star_road, + star_road_donut, + star_road_vanilla, + star_road_twin_bridges, + star_road_forest, + star_road_valley, + star_road_special, + special_star_road, + star_road_1_tile, + star_road_1_region, + star_road_1_exit_1, + star_road_1_exit_2, + star_road_2_tile, + star_road_2_region, + star_road_2_exit_1, + star_road_2_exit_2, + star_road_3_tile, + star_road_3_region, + star_road_3_exit_1, + star_road_3_exit_2, + star_road_4_tile, + star_road_4_region, + star_road_4_exit_1, + star_road_4_exit_2, + star_road_5_tile, + star_road_5_region, + star_road_5_exit_1, + star_road_5_exit_2, + special_zone_1_tile, + special_zone_1_region, + special_zone_1_exit_1, + special_zone_2_tile, + special_zone_2_region, + special_zone_2_exit_1, + special_zone_3_tile, + special_zone_3_region, + special_zone_3_exit_1, + special_zone_4_tile, + special_zone_4_region, + special_zone_4_exit_1, + special_zone_5_tile, + special_zone_5_region, + special_zone_5_exit_1, + special_zone_6_tile, + special_zone_6_region, + special_zone_6_exit_1, + special_zone_7_tile, + special_zone_7_region, + special_zone_7_exit_1, + special_zone_8_tile, + special_zone_8_region, + special_zone_8_exit_1, + special_complete, + ] + + + if world.dragon_coin_checks[player]: + add_location_to_region(world, player, active_locations, LocationName.yoshis_island_1_region, LocationName.yoshis_island_1_dragon, + lambda state: (state.has(ItemName.mario_spin_jump, player) and + state.has(ItemName.progressive_powerup, player, 1))) + add_location_to_region(world, player, active_locations, LocationName.yoshis_island_2_region, LocationName.yoshis_island_2_dragon, + lambda state: (state.has(ItemName.yoshi_activate, player) or + state.has(ItemName.mario_climb, player))) + add_location_to_region(world, player, active_locations, LocationName.yoshis_island_3_region, LocationName.yoshis_island_3_dragon, + lambda state: state.has(ItemName.p_switch, player)) + add_location_to_region(world, player, active_locations, LocationName.yoshis_island_4_region, LocationName.yoshis_island_4_dragon, + lambda state: (state.has(ItemName.yoshi_activate, player) or + state.has(ItemName.mario_swim, player) or + (state.has(ItemName.mario_carry, player) and state.has(ItemName.p_switch, player)))) + add_location_to_region(world, player, active_locations, LocationName.donut_plains_1_region, LocationName.donut_plains_1_dragon, + lambda state: (state.has(ItemName.mario_climb, player) or + state.has(ItemName.yoshi_activate, player) or + (state.has(ItemName.progressive_powerup, player, 3) and state.has(ItemName.mario_run, player)))) + add_location_to_region(world, player, active_locations, LocationName.donut_plains_2_region, LocationName.donut_plains_2_dragon) + add_location_to_region(world, player, active_locations, LocationName.donut_plains_3_region, LocationName.donut_plains_3_dragon, + lambda state: ((state.has(ItemName.mario_spin_jump, player) and state.has(ItemName.progressive_powerup, player, 1) and state.has(ItemName.mario_climb, player) or + state.has(ItemName.yoshi_activate, player) or + (state.has(ItemName.mario_run, player) and state.has(ItemName.progressive_powerup, player, 3))))) + add_location_to_region(world, player, active_locations, LocationName.donut_plains_4_region, LocationName.donut_plains_4_dragon) + add_location_to_region(world, player, active_locations, LocationName.donut_secret_1_region, LocationName.donut_secret_1_dragon, + lambda state: state.has(ItemName.mario_swim, player)) + add_location_to_region(world, player, active_locations, LocationName.donut_secret_2_region, LocationName.donut_secret_2_dragon, + lambda state: (state.has(ItemName.mario_climb, player) or state.has(ItemName.yoshi_activate, player))) + add_location_to_region(world, player, active_locations, LocationName.vanilla_dome_1_region, LocationName.vanilla_dome_1_dragon, + lambda state: (state.has(ItemName.mario_carry, player) and + state.has(ItemName.mario_run, player) and + (state.has(ItemName.super_star_active, player) or + state.has(ItemName.progressive_powerup, player, 1)))) + add_location_to_region(world, player, active_locations, LocationName.vanilla_dome_2_region, LocationName.vanilla_dome_2_dragon, + lambda state: (state.has(ItemName.mario_swim, player) and + state.has(ItemName.p_switch, player) and + (state.has(ItemName.mario_climb, player) or state.has(ItemName.yoshi_activate, player)))) + add_location_to_region(world, player, active_locations, LocationName.vanilla_dome_3_region, LocationName.vanilla_dome_3_dragon) + add_location_to_region(world, player, active_locations, LocationName.vanilla_dome_4_region, LocationName.vanilla_dome_4_dragon) + add_location_to_region(world, player, active_locations, LocationName.vanilla_secret_1_region, LocationName.vanilla_secret_1_dragon, + lambda state: (state.has(ItemName.mario_climb, player) and + state.has(ItemName.mario_carry, player))) + add_location_to_region(world, player, active_locations, LocationName.vanilla_secret_2_region, LocationName.vanilla_secret_2_dragon, + lambda state: (state.has(ItemName.mario_run, player) and + state.has(ItemName.progressive_powerup, player, 3))) + add_location_to_region(world, player, active_locations, LocationName.vanilla_secret_3_region, LocationName.vanilla_secret_3_dragon, + lambda state: state.has(ItemName.mario_swim, player)) + add_location_to_region(world, player, active_locations, LocationName.vanilla_ghost_house_region, LocationName.vanilla_ghost_house_dragon, + lambda state: state.has(ItemName.mario_climb, player)) + add_location_to_region(world, player, active_locations, LocationName.butter_bridge_1_region, LocationName.butter_bridge_1_dragon) + add_location_to_region(world, player, active_locations, LocationName.butter_bridge_2_region, LocationName.butter_bridge_2_dragon, + lambda state: (state.has(ItemName.yoshi_activate, player) or + state.has(ItemName.progressive_powerup, player, 3))) + add_location_to_region(world, player, active_locations, LocationName.cheese_bridge_region, LocationName.cheese_bridge_dragon, + lambda state: (state.has(ItemName.yoshi_activate, player) or + state.has(ItemName.mario_climb, player))) + add_location_to_region(world, player, active_locations, LocationName.cookie_mountain_region, LocationName.cookie_mountain_dragon, + lambda state: (state.has(ItemName.yoshi_activate, player) or + state.has(ItemName.mario_climb, player))) + add_location_to_region(world, player, active_locations, LocationName.soda_lake_region, LocationName.soda_lake_dragon, + lambda state: state.has(ItemName.mario_swim, player)) + add_location_to_region(world, player, active_locations, LocationName.forest_of_illusion_2_region, LocationName.forest_of_illusion_2_dragon, + lambda state: state.has(ItemName.mario_swim, player)) + add_location_to_region(world, player, active_locations, LocationName.forest_of_illusion_3_region, LocationName.forest_of_illusion_3_dragon, + lambda state: (state.has(ItemName.yoshi_activate, player) or + state.has(ItemName.mario_carry, player))) + add_location_to_region(world, player, active_locations, LocationName.forest_of_illusion_4_region, LocationName.forest_of_illusion_4_dragon, + lambda state: (state.has(ItemName.yoshi_activate, player) or + state.has(ItemName.mario_carry, player) or + state.has(ItemName.p_switch, player) or + state.has(ItemName.progressive_powerup, player, 2))) + add_location_to_region(world, player, active_locations, LocationName.forest_ghost_house_region, LocationName.forest_ghost_house_dragon, + lambda state: state.has(ItemName.p_switch, player)) + add_location_to_region(world, player, active_locations, LocationName.forest_secret_region, LocationName.forest_secret_dragon) + add_location_to_region(world, player, active_locations, LocationName.forest_castle_region, LocationName.forest_castle_dragon) + add_location_to_region(world, player, active_locations, LocationName.chocolate_island_1_region, LocationName.chocolate_island_1_dragon, + lambda state: state.has(ItemName.mario_swim, player)) + add_location_to_region(world, player, active_locations, LocationName.chocolate_island_2_region, LocationName.chocolate_island_2_dragon, + lambda state: (state.has(ItemName.blue_switch_palace, player) and + (state.has(ItemName.p_switch, player) or + state.has(ItemName.green_switch_palace, player) or + (state.has(ItemName.yellow_switch_palace, player) or state.has(ItemName.red_switch_palace, player))))) + add_location_to_region(world, player, active_locations, LocationName.chocolate_island_3_region, LocationName.chocolate_island_3_dragon) + add_location_to_region(world, player, active_locations, LocationName.chocolate_island_4_region, LocationName.chocolate_island_4_dragon, + lambda state: (state.has(ItemName.mario_run, player) and + state.has(ItemName.progressive_powerup, player, 3))) + add_location_to_region(world, player, active_locations, LocationName.chocolate_island_5_region, LocationName.chocolate_island_5_dragon, + lambda state: (state.has(ItemName.mario_swim, player) or + (state.has(ItemName.mario_carry, player) and state.has(ItemName.p_switch, player)))) + add_location_to_region(world, player, active_locations, LocationName.sunken_ghost_ship_region, LocationName.sunken_ghost_ship_dragon, + lambda state: (state.has(ItemName.mario_swim, player) and + state.has(ItemName.super_star_active, player) and + state.has(ItemName.progressive_powerup, player, 3))) + add_location_to_region(world, player, active_locations, LocationName.valley_of_bowser_1_region, LocationName.valley_of_bowser_1_dragon) + add_location_to_region(world, player, active_locations, LocationName.valley_of_bowser_2_region, LocationName.valley_of_bowser_2_dragon) + add_location_to_region(world, player, active_locations, LocationName.valley_of_bowser_3_region, LocationName.valley_of_bowser_3_dragon) + add_location_to_region(world, player, active_locations, LocationName.valley_ghost_house_region, LocationName.valley_ghost_house_dragon, + lambda state: state.has(ItemName.p_switch, player)) + add_location_to_region(world, player, active_locations, LocationName.valley_castle_region, LocationName.valley_castle_dragon) + add_location_to_region(world, player, active_locations, LocationName.star_road_1_region, LocationName.star_road_1_dragon, + lambda state: (state.has(ItemName.mario_spin_jump, player) and + state.has(ItemName.progressive_powerup, player, 1))) + add_location_to_region(world, player, active_locations, LocationName.special_zone_1_region, LocationName.special_zone_1_dragon, + lambda state: state.has(ItemName.mario_climb, player)) + add_location_to_region(world, player, active_locations, LocationName.special_zone_2_region, LocationName.special_zone_2_dragon, + lambda state: state.has(ItemName.p_balloon, player)) + add_location_to_region(world, player, active_locations, LocationName.special_zone_3_region, LocationName.special_zone_3_dragon, + lambda state: state.has(ItemName.yoshi_activate, player)) + add_location_to_region(world, player, active_locations, LocationName.special_zone_4_region, LocationName.special_zone_4_dragon, + lambda state: state.has(ItemName.progressive_powerup, player, 1)) + add_location_to_region(world, player, active_locations, LocationName.special_zone_5_region, LocationName.special_zone_5_dragon, + lambda state: state.has(ItemName.progressive_powerup, player, 1)) + add_location_to_region(world, player, active_locations, LocationName.special_zone_6_region, LocationName.special_zone_6_dragon, + lambda state: state.has(ItemName.mario_swim, player)) + add_location_to_region(world, player, active_locations, LocationName.special_zone_7_region, LocationName.special_zone_7_dragon, + lambda state: state.has(ItemName.progressive_powerup, player, 1)) + add_location_to_region(world, player, active_locations, LocationName.special_zone_8_region, LocationName.special_zone_8_dragon, + lambda state: state.has(ItemName.progressive_powerup, player, 1)) + + + +def connect_regions(world, player, level_to_tile_dict): + names: typing.Dict[str, int] = {} + + connect(world, player, names, "Menu", LocationName.yoshis_island_region) + connect(world, player, names, LocationName.yoshis_island_region, LocationName.yoshis_island_1_tile) + connect(world, player, names, LocationName.yoshis_island_region, LocationName.yoshis_island_2_tile) + + # Connect regions within levels using rules + connect(world, player, names, LocationName.yoshis_island_1_region, LocationName.yoshis_island_1_exit_1) + connect(world, player, names, LocationName.yoshis_island_2_region, LocationName.yoshis_island_2_exit_1) + connect(world, player, names, LocationName.yoshis_island_3_region, LocationName.yoshis_island_3_exit_1) + connect(world, player, names, LocationName.yoshis_island_4_region, LocationName.yoshis_island_4_exit_1) + connect(world, player, names, LocationName.yoshis_island_castle_region, LocationName.yoshis_island_castle, + lambda state: (state.has(ItemName.mario_climb, player))) + + connect(world, player, names, LocationName.donut_plains_1_region, LocationName.donut_plains_1_exit_1) + connect(world, player, names, LocationName.donut_plains_1_region, LocationName.donut_plains_1_exit_2, + lambda state: (state.has(ItemName.mario_carry, player) and + (state.has(ItemName.yoshi_activate, player) or + state.has(ItemName.green_switch_palace, player) or + (state.has(ItemName.mario_run, player) and state.has(ItemName.progressive_powerup, player, 3))))) + connect(world, player, names, LocationName.donut_plains_2_region, LocationName.donut_plains_2_exit_1) + connect(world, player, names, LocationName.donut_plains_2_region, LocationName.donut_plains_2_exit_2, + lambda state: (state.has(ItemName.mario_carry, player) and + (state.has(ItemName.yoshi_activate, player) or + (state.has(ItemName.mario_spin_jump, player) and state.has(ItemName.mario_climb, player) and state.has(ItemName.progressive_powerup, player, 1))))) + connect(world, player, names, LocationName.donut_secret_1_region, LocationName.donut_secret_1_exit_1, + lambda state: state.has(ItemName.mario_swim, player)) + connect(world, player, names, LocationName.donut_secret_1_region, LocationName.donut_secret_1_exit_2, + lambda state: (state.has(ItemName.mario_carry, player) and + state.has(ItemName.mario_swim, player) and + state.has(ItemName.p_switch, player))) + connect(world, player, names, LocationName.donut_ghost_house_region, LocationName.donut_ghost_house_exit_1, + lambda state: (state.has(ItemName.mario_run, player) and state.has(ItemName.progressive_powerup, player, 3))) + connect(world, player, names, LocationName.donut_ghost_house_region, LocationName.donut_ghost_house_exit_2, + lambda state: (state.has(ItemName.mario_climb, player) or + (state.has(ItemName.mario_run, player) and state.has(ItemName.progressive_powerup, player, 3)))) + connect(world, player, names, LocationName.donut_secret_house_region, LocationName.donut_secret_house_exit_1, + lambda state: state.has(ItemName.p_switch, player)) + connect(world, player, names, LocationName.donut_secret_house_region, LocationName.donut_secret_house_exit_2, + lambda state: (state.has(ItemName.p_switch, player) and state.has(ItemName.mario_carry, player) and + (state.has(ItemName.mario_climb, player) or + (state.has(ItemName.mario_run, player) and state.has(ItemName.progressive_powerup, player, 3))))) + connect(world, player, names, LocationName.donut_plains_3_region, LocationName.donut_plains_3_exit_1) + connect(world, player, names, LocationName.donut_plains_4_region, LocationName.donut_plains_4_exit_1) + connect(world, player, names, LocationName.donut_secret_2_region, LocationName.donut_secret_2_exit_1) + connect(world, player, names, LocationName.donut_plains_castle_region, LocationName.donut_plains_castle) + + connect(world, player, names, LocationName.vanilla_dome_1_region, LocationName.vanilla_dome_1_exit_1, + lambda state: (state.has(ItemName.mario_run, player) and + (state.has(ItemName.super_star_active, player) or + state.has(ItemName.progressive_powerup, player, 1)))) + connect(world, player, names, LocationName.vanilla_dome_1_region, LocationName.vanilla_dome_1_exit_2, + lambda state: (state.has(ItemName.mario_carry, player) and + ((state.has(ItemName.yoshi_activate, player) and state.has(ItemName.mario_climb, player)) or + (state.has(ItemName.yoshi_activate, player) and state.has(ItemName.red_switch_palace, player)) or + (state.has(ItemName.red_switch_palace, player) and state.has(ItemName.mario_climb, player))))) + connect(world, player, names, LocationName.vanilla_dome_2_region, LocationName.vanilla_dome_2_exit_1, + lambda state: (state.has(ItemName.mario_swim, player) and + (state.has(ItemName.mario_climb, player) or state.has(ItemName.yoshi_activate, player)))) + connect(world, player, names, LocationName.vanilla_dome_2_region, LocationName.vanilla_dome_2_exit_2, + lambda state: (state.has(ItemName.mario_swim, player) and + state.has(ItemName.p_switch, player) and + state.has(ItemName.mario_carry, player) and + (state.has(ItemName.mario_climb, player) or state.has(ItemName.yoshi_activate, player)))) + connect(world, player, names, LocationName.vanilla_secret_1_region, LocationName.vanilla_secret_1_exit_1, + lambda state: state.has(ItemName.mario_climb, player)) + connect(world, player, names, LocationName.vanilla_secret_1_region, LocationName.vanilla_secret_1_exit_2, + lambda state: (state.has(ItemName.mario_climb, player) and + (state.has(ItemName.mario_carry, player) and state.has(ItemName.blue_switch_palace, player)))) + connect(world, player, names, LocationName.vanilla_ghost_house_region, LocationName.vanilla_ghost_house_exit_1, + lambda state: state.has(ItemName.p_switch, player)) + connect(world, player, names, LocationName.vanilla_dome_3_region, LocationName.vanilla_dome_3_exit_1) + connect(world, player, names, LocationName.vanilla_dome_4_region, LocationName.vanilla_dome_4_exit_1) + connect(world, player, names, LocationName.vanilla_secret_2_region, LocationName.vanilla_secret_2_exit_1) + connect(world, player, names, LocationName.vanilla_secret_3_region, LocationName.vanilla_secret_3_exit_1, + lambda state: state.has(ItemName.mario_swim, player)) + connect(world, player, names, LocationName.vanilla_fortress_region, LocationName.vanilla_fortress, + lambda state: state.has(ItemName.mario_swim, player)) + connect(world, player, names, LocationName.vanilla_dome_castle_region, LocationName.vanilla_dome_castle) + + connect(world, player, names, LocationName.butter_bridge_1_region, LocationName.butter_bridge_1_exit_1) + connect(world, player, names, LocationName.butter_bridge_2_region, LocationName.butter_bridge_2_exit_1) + connect(world, player, names, LocationName.cheese_bridge_region, LocationName.cheese_bridge_exit_1, + lambda state: state.has(ItemName.mario_climb, player)) + connect(world, player, names, LocationName.cheese_bridge_region, LocationName.cheese_bridge_exit_2, + lambda state: (state.has(ItemName.mario_run, player) and state.has(ItemName.progressive_powerup, player, 3))) + connect(world, player, names, LocationName.soda_lake_region, LocationName.soda_lake_exit_1, + lambda state: state.has(ItemName.mario_swim, player)) + connect(world, player, names, LocationName.cookie_mountain_region, LocationName.cookie_mountain_exit_1) + connect(world, player, names, LocationName.twin_bridges_castle_region, LocationName.twin_bridges_castle, + lambda state: (state.has(ItemName.mario_run, player) and + state.has(ItemName.mario_climb, player))) + + connect(world, player, names, LocationName.forest_of_illusion_1_region, LocationName.forest_of_illusion_1_exit_1) + connect(world, player, names, LocationName.forest_of_illusion_1_region, LocationName.forest_of_illusion_1_exit_2, + lambda state: (state.has(ItemName.mario_carry, player) and + state.has(ItemName.p_balloon, player))) + connect(world, player, names, LocationName.forest_of_illusion_2_region, LocationName.forest_of_illusion_2_exit_1, + lambda state: state.has(ItemName.mario_swim, player)) + connect(world, player, names, LocationName.forest_of_illusion_2_region, LocationName.forest_of_illusion_2_exit_2, + lambda state: (state.has(ItemName.mario_swim, player) and + state.has(ItemName.mario_carry, player))) + connect(world, player, names, LocationName.forest_of_illusion_3_region, LocationName.forest_of_illusion_3_exit_1, + lambda state: (state.has(ItemName.mario_carry, player) or + state.has(ItemName.yoshi_activate, player))) + connect(world, player, names, LocationName.forest_of_illusion_3_region, LocationName.forest_of_illusion_3_exit_2, + lambda state: (state.has(ItemName.mario_swim, player) and + state.has(ItemName.mario_carry, player) and + state.has(ItemName.progressive_powerup, player, 1))) + connect(world, player, names, LocationName.forest_of_illusion_4_region, LocationName.forest_of_illusion_4_exit_1) + connect(world, player, names, LocationName.forest_of_illusion_4_region, LocationName.forest_of_illusion_4_exit_2, + lambda state: state.has(ItemName.mario_carry, player)) + connect(world, player, names, LocationName.forest_ghost_house_region, LocationName.forest_ghost_house_exit_1, + lambda state: state.has(ItemName.p_switch, player)) + connect(world, player, names, LocationName.forest_ghost_house_region, LocationName.forest_ghost_house_exit_2, + lambda state: state.has(ItemName.p_switch, player)) + connect(world, player, names, LocationName.forest_secret_region, LocationName.forest_secret_exit_1) + connect(world, player, names, LocationName.forest_fortress_region, LocationName.forest_fortress) + connect(world, player, names, LocationName.forest_castle_region, LocationName.forest_castle) + + connect(world, player, names, LocationName.chocolate_island_1_region, LocationName.chocolate_island_1_exit_1, + lambda state: state.has(ItemName.p_switch, player)) + connect(world, player, names, LocationName.chocolate_island_2_region, LocationName.chocolate_island_2_exit_1) + connect(world, player, names, LocationName.chocolate_island_2_region, LocationName.chocolate_island_2_exit_2, + lambda state: state.has(ItemName.mario_carry, player)) + connect(world, player, names, LocationName.chocolate_island_3_region, LocationName.chocolate_island_3_exit_1, + lambda state: (state.has(ItemName.mario_climb, player) or + (state.has(ItemName.mario_run, player) and state.has(ItemName.progressive_powerup, player, 3)))) + connect(world, player, names, LocationName.chocolate_island_3_region, LocationName.chocolate_island_3_exit_2, + lambda state: (state.has(ItemName.mario_run, player) and state.has(ItemName.progressive_powerup, player, 3))) + connect(world, player, names, LocationName.chocolate_island_4_region, LocationName.chocolate_island_4_exit_1) + connect(world, player, names, LocationName.chocolate_island_5_region, LocationName.chocolate_island_5_exit_1) + connect(world, player, names, LocationName.chocolate_ghost_house_region, LocationName.chocolate_ghost_house_exit_1) + connect(world, player, names, LocationName.chocolate_fortress_region, LocationName.chocolate_fortress) + connect(world, player, names, LocationName.chocolate_secret_region, LocationName.chocolate_secret_exit_1, + lambda state: state.has(ItemName.mario_run, player)) + connect(world, player, names, LocationName.chocolate_castle_region, LocationName.chocolate_castle, + lambda state: (state.has(ItemName.progressive_powerup, player, 1))) + + connect(world, player, names, LocationName.sunken_ghost_ship_region, LocationName.sunken_ghost_ship, + lambda state: state.has(ItemName.mario_swim, player)) + connect(world, player, names, LocationName.valley_of_bowser_1_region, LocationName.valley_of_bowser_1_exit_1) + connect(world, player, names, LocationName.valley_of_bowser_2_region, LocationName.valley_of_bowser_2_exit_1) + connect(world, player, names, LocationName.valley_of_bowser_2_region, LocationName.valley_of_bowser_2_exit_2, + lambda state: state.has(ItemName.mario_carry, player)) + connect(world, player, names, LocationName.valley_of_bowser_3_region, LocationName.valley_of_bowser_3_exit_1) + connect(world, player, names, LocationName.valley_of_bowser_4_region, LocationName.valley_of_bowser_4_exit_1, + lambda state: state.has(ItemName.mario_climb, player)) + connect(world, player, names, LocationName.valley_of_bowser_4_region, LocationName.valley_of_bowser_4_exit_2, + lambda state: (state.has(ItemName.mario_climb, player) and + state.has(ItemName.mario_carry, player) and + state.has(ItemName.yoshi_activate, player))) + connect(world, player, names, LocationName.valley_ghost_house_region, LocationName.valley_ghost_house_exit_1, + lambda state: state.has(ItemName.p_switch, player)) + connect(world, player, names, LocationName.valley_ghost_house_region, LocationName.valley_ghost_house_exit_2, + lambda state: (state.has(ItemName.p_switch, player) and + state.has(ItemName.mario_carry, player) and + state.has(ItemName.mario_run, player))) + connect(world, player, names, LocationName.valley_fortress_region, LocationName.valley_fortress, + lambda state: state.has(ItemName.progressive_powerup, player, 1)) + connect(world, player, names, LocationName.valley_castle_region, LocationName.valley_castle) + connect(world, player, names, LocationName.front_door, LocationName.bowser_region, + lambda state: (state.has(ItemName.mario_climb, player) and + state.has(ItemName.mario_run, player) and + state.has(ItemName.mario_swim, player) and + state.has(ItemName.progressive_powerup, player, 1) and + state.has(ItemName.koopaling, player, world.bosses_required[player].value))) + connect(world, player, names, LocationName.back_door, LocationName.bowser_region, + lambda state: state.has(ItemName.koopaling, player, world.bosses_required[player].value)) + + connect(world, player, names, LocationName.star_road_1_region, LocationName.star_road_1_exit_1, + lambda state: (state.has(ItemName.mario_spin_jump, player) and + state.has(ItemName.progressive_powerup, player, 1))) + connect(world, player, names, LocationName.star_road_1_region, LocationName.star_road_1_exit_2, + lambda state: (state.has(ItemName.mario_spin_jump, player) and + state.has(ItemName.mario_carry, player) and + state.has(ItemName.progressive_powerup, player, 1))) + connect(world, player, names, LocationName.star_road_2_region, LocationName.star_road_2_exit_1, + lambda state: state.has(ItemName.mario_swim, player)) + connect(world, player, names, LocationName.star_road_2_region, LocationName.star_road_2_exit_2, + lambda state: (state.has(ItemName.mario_swim, player) and + state.has(ItemName.mario_carry, player))) + connect(world, player, names, LocationName.star_road_3_region, LocationName.star_road_3_exit_1) + connect(world, player, names, LocationName.star_road_3_region, LocationName.star_road_3_exit_2, + lambda state: state.has(ItemName.mario_carry, player)) + connect(world, player, names, LocationName.star_road_4_region, LocationName.star_road_4_exit_1) + connect(world, player, names, LocationName.star_road_4_region, LocationName.star_road_4_exit_2, + lambda state: (state.has(ItemName.mario_carry, player) and + (state.has(ItemName.yoshi_activate, player) or + (state.has(ItemName.green_switch_palace, player) and state.has(ItemName.red_switch_palace, player))))) + connect(world, player, names, LocationName.star_road_5_region, LocationName.star_road_5_exit_1, + lambda state: state.has(ItemName.p_switch, player)) + connect(world, player, names, LocationName.star_road_5_region, LocationName.star_road_5_exit_2, + lambda state: (state.has(ItemName.mario_carry, player) and + state.has(ItemName.mario_climb, player) and + state.has(ItemName.p_switch, player) and + state.has(ItemName.yellow_switch_palace, player) and + state.has(ItemName.green_switch_palace, player) and + state.has(ItemName.red_switch_palace, player) and + state.has(ItemName.blue_switch_palace, player))) + + connect(world, player, names, LocationName.special_zone_1_region, LocationName.special_zone_1_exit_1, + lambda state: (state.has(ItemName.mario_climb, player) and + (state.has(ItemName.p_switch, player) or + (state.has(ItemName.mario_run, player) and state.has(ItemName.progressive_powerup, player, 3))))) + connect(world, player, names, LocationName.special_zone_2_region, LocationName.special_zone_2_exit_1, + lambda state: state.has(ItemName.p_balloon, player)) + connect(world, player, names, LocationName.special_zone_3_region, LocationName.special_zone_3_exit_1, + lambda state: (state.has(ItemName.mario_climb, player) or + state.has(ItemName.p_switch, player) or + (state.has(ItemName.mario_run, player) and state.has(ItemName.progressive_powerup, player, 3)))) + connect(world, player, names, LocationName.special_zone_4_region, LocationName.special_zone_4_exit_1, + lambda state: state.has(ItemName.progressive_powerup, player, 1)) + connect(world, player, names, LocationName.special_zone_5_region, LocationName.special_zone_5_exit_1, + lambda state: state.has(ItemName.progressive_powerup, player, 1)) + connect(world, player, names, LocationName.special_zone_6_region, LocationName.special_zone_6_exit_1, + lambda state: state.has(ItemName.mario_swim, player)) + connect(world, player, names, LocationName.special_zone_7_region, LocationName.special_zone_7_exit_1, + lambda state: state.has(ItemName.progressive_powerup, player, 1)) + connect(world, player, names, LocationName.special_zone_8_region, LocationName.special_zone_8_exit_1, + lambda state: state.has(ItemName.progressive_powerup, player, 1)) + + + + # Connect levels to each other + for current_level_id, current_level_data in level_info_dict.items(): + # Connect tile regions to correct level regions + + if current_level_id not in level_to_tile_dict.keys(): + continue + + current_tile_id = level_to_tile_dict[current_level_id] + current_tile_data = level_info_dict[current_tile_id] + current_tile_name = current_tile_data.levelName + if ("Star Road - " not in current_tile_name) and (" - Star Road" not in current_tile_name): + current_tile_name += " - Tile" + connect(world, player, names, current_tile_name, current_level_data.levelName) + # Connect Exit regions to next tile regions + if current_tile_data.exit1Path: + next_tile_id = current_tile_data.exit1Path.otherLevelID + if world.swap_donut_gh_exits[player] and current_tile_id == 0x04: + next_tile_id = current_tile_data.exit2Path.otherLevelID + next_tile_name = level_info_dict[next_tile_id].levelName + if ("Star Road - " not in next_tile_name) and (" - Star Road" not in next_tile_name): + next_tile_name += " - Tile" + current_exit_name = (current_level_data.levelName + " - Normal Exit") + connect(world, player, names, current_exit_name, next_tile_name) + if current_tile_data.exit2Path: + next_tile_id = current_tile_data.exit2Path.otherLevelID + if world.swap_donut_gh_exits[player] and current_tile_id == 0x04: + next_tile_id = current_tile_data.exit1Path.otherLevelID + next_tile_name = level_info_dict[next_tile_id].levelName + if ("Star Road - " not in next_tile_name) and (" - Star Road" not in next_tile_name): + next_tile_name += " - Tile" + current_exit_name = (current_level_data.levelName + " - Secret Exit") + connect(world, player, names, current_exit_name, next_tile_name) + + connect(world, player, names, LocationName.donut_plains_star_road, LocationName.star_road_donut) + connect(world, player, names, LocationName.star_road_donut, LocationName.donut_plains_star_road) + connect(world, player, names, LocationName.star_road_donut, LocationName.star_road_1_tile) + connect(world, player, names, LocationName.vanilla_dome_star_road, LocationName.star_road_vanilla) + connect(world, player, names, LocationName.star_road_vanilla, LocationName.vanilla_dome_star_road) + connect(world, player, names, LocationName.star_road_vanilla, LocationName.star_road_2_tile) + connect(world, player, names, LocationName.twin_bridges_star_road, LocationName.star_road_twin_bridges) + connect(world, player, names, LocationName.star_road_twin_bridges, LocationName.twin_bridges_star_road) + connect(world, player, names, LocationName.star_road_twin_bridges, LocationName.star_road_3_tile) + connect(world, player, names, LocationName.forest_star_road, LocationName.star_road_forest) + connect(world, player, names, LocationName.star_road_forest, LocationName.forest_star_road) + connect(world, player, names, LocationName.star_road_forest, LocationName.star_road_4_tile) + connect(world, player, names, LocationName.valley_star_road, LocationName.star_road_valley) + connect(world, player, names, LocationName.star_road_valley, LocationName.valley_star_road) + connect(world, player, names, LocationName.star_road_valley, LocationName.star_road_5_tile) + connect(world, player, names, LocationName.star_road_special, LocationName.special_star_road) + connect(world, player, names, LocationName.special_star_road, LocationName.star_road_special) + connect(world, player, names, LocationName.special_star_road, LocationName.special_zone_1_tile) + + connect(world, player, names, LocationName.star_road_valley, LocationName.front_door_tile) + + + +def create_region(world: MultiWorld, player: int, active_locations, name: str, locations=None): + ret = Region(name, RegionType.Generic, name, player) + ret.world = world + if locations: + for locationName in locations: + loc_id = active_locations.get(locationName, 0) + if loc_id: + location = SMWLocation(player, locationName, loc_id, ret) + ret.locations.append(location) + + return ret + +def add_location_to_region(world: MultiWorld, player: int, active_locations, region_name: str, location_name: str, + rule: typing.Optional[typing.Callable] = None): + region = world.get_region(region_name, player) + loc_id = active_locations.get(location_name, 0) + if loc_id: + location = SMWLocation(player, location_name, loc_id, region) + region.locations.append(location) + if rule: + add_rule(location, rule) + + + +def connect(world: MultiWorld, player: int, used_names: typing.Dict[str, int], source: str, target: str, + rule: typing.Optional[typing.Callable] = None): + source_region = world.get_region(source, player) + target_region = world.get_region(target, player) + + if target not in used_names: + used_names[target] = 1 + name = target + else: + used_names[target] += 1 + name = target + (' ' * used_names[target]) + + connection = Entrance(player, name, source_region) + + if rule: + connection.access_rule = rule + + source_region.exits.append(connection) + connection.connect(target_region) diff --git a/worlds/smw/Rom.py b/worlds/smw/Rom.py new file mode 100644 index 0000000000..6c750d74ff --- /dev/null +++ b/worlds/smw/Rom.py @@ -0,0 +1,846 @@ +import Utils +from Patch import read_rom, APDeltaPatch +from .Aesthetics import generate_shuffled_header_data +from .Locations import lookup_id_to_name, all_locations +from .Levels import level_info_dict, full_level_list, submap_level_list, location_id_to_level_id +from .Names.TextBox import generate_goal_text, title_text_mapping, generate_text_box + +USHASH = 'cdd3c8c37322978ca8669b34bc89c804' +ROM_PLAYER_LIMIT = 65535 + +import hashlib +import os +import math + + +ability_rom_data = { + 0xBC0003: [[0x1F2C, 0x7]], # Run 0x80 + 0xBC0004: [[0x1F2C, 0x6]], # Carry 0x40 + 0xBC0005: [[0x1F2C, 0x2]], # Swim 0x04 + 0xBC0006: [[0x1F2C, 0x3]], # Spin Jump 0x08 + 0xBC0007: [[0x1F2C, 0x5]], # Climb 0x20 + 0xBC0008: [[0x1F2C, 0x1]], # Yoshi 0x02 + 0xBC0009: [[0x1F2C, 0x4]], # P-Switch 0x10 + #0xBC000A: [[]] + 0xBC000B: [[0x1F2D, 0x3]], # P-Balloon 0x08 + 0xBC000D: [[0x1F2D, 0x4]], # Super Star 0x10 +} + + +item_rom_data = { + 0xBC0001: [0x18E4, 0x1], # 1-Up Mushroom + + 0xBC0002: [0x1F24, 0x1, 0x1F], # Yoshi Egg + 0xBC0012: [0x1F26, 0x1, 0x09], # Boss Token + + 0xBC000E: [0x1F28, 0x1, 0x1C], # Yellow Switch Palace + 0xBC000F: [0x1F27, 0x1, 0x1C], # Green Switch Palace + 0xBC0010: [0x1F2A, 0x1, 0x1C], # Red Switch Palace + 0xBC0011: [0x1F29, 0x1, 0x1C], # Blue Switch Palace + + 0xBC0013: [0x0086, 0x1, 0x0E], # Ice Trap + 0xBC0014: [0x18BD, 0x7F, 0x18], # Stun Trap +} + +music_rom_data = [ + +] + +level_music_ids = [ + +] + + +class SMWDeltaPatch(APDeltaPatch): + hash = USHASH + game = "Super Mario World" + patch_file_ending = ".apsmw" + + @classmethod + def get_source_data(cls) -> bytes: + return get_base_rom_bytes() + + +class LocalRom: + + def __init__(self, file, patch=True, vanillaRom=None, name=None, hash=None): + self.name = name + self.hash = hash + self.orig_buffer = None + + with open(file, 'rb') as stream: + self.buffer = read_rom(stream) + + def read_bit(self, address: int, bit_number: int) -> bool: + bitflag = (1 << bit_number) + return ((self.buffer[address] & bitflag) != 0) + + def read_byte(self, address: int) -> int: + return self.buffer[address] + + def read_bytes(self, startaddress: int, length: int) -> bytes: + return self.buffer[startaddress:startaddress + length] + + def write_byte(self, address: int, value: int): + self.buffer[address] = value + + def write_bytes(self, startaddress: int, values): + self.buffer[startaddress:startaddress + len(values)] = values + + def write_to_file(self, file): + with open(file, 'wb') as outfile: + outfile.write(self.buffer) + + def read_from_file(self, file): + with open(file, 'rb') as stream: + self.buffer = bytearray(stream.read()) + + +def handle_ability_code(rom): + # Lock Abilities + + #rom.write_byte(0xC581, 0x01) # No Stars + #rom.write_byte(0x62E6, 0x01) # No Star Music + #rom.write_byte(0xC300, 0x01) # No P-Balloons + #rom.write_byte(0xC305, 0x01) # No P-Balloons + + # Run + rom.write_bytes(0x5977, bytearray([0x22, 0x10, 0xBA, 0x03])) # JSL $03BA10 + rom.write_bytes(0x597B, bytearray([0xEA] * 0x04)) + + RUN_SUB_ADDR = 0x01BA10 + rom.write_bytes(RUN_SUB_ADDR + 0x00, bytearray([0xDA])) # PHX + rom.write_bytes(RUN_SUB_ADDR + 0x01, bytearray([0x08])) # PHP + rom.write_bytes(RUN_SUB_ADDR + 0x02, bytearray([0x90, 0x03])) # BCC +0x03 + rom.write_bytes(RUN_SUB_ADDR + 0x04, bytearray([0xC8])) # INY + rom.write_bytes(RUN_SUB_ADDR + 0x05, bytearray([0xA9, 0x70])) # LDA #70 + rom.write_bytes(RUN_SUB_ADDR + 0x07, bytearray([0xAA])) # TAX + rom.write_bytes(RUN_SUB_ADDR + 0x08, bytearray([0xAD, 0x2C, 0x1F])) # LDA $1F2C + rom.write_bytes(RUN_SUB_ADDR + 0x0B, bytearray([0x89, 0x80])) # BIT #80 + rom.write_bytes(RUN_SUB_ADDR + 0x0D, bytearray([0xF0, 0x04])) # BEQ +0x04 + rom.write_bytes(RUN_SUB_ADDR + 0x0F, bytearray([0x8A])) # TXA + rom.write_bytes(RUN_SUB_ADDR + 0x10, bytearray([0x8D, 0xE4, 0x13])) # STA $13E4 + rom.write_bytes(RUN_SUB_ADDR + 0x13, bytearray([0x8A])) # TXA + rom.write_bytes(RUN_SUB_ADDR + 0x14, bytearray([0x28])) # PLP + rom.write_bytes(RUN_SUB_ADDR + 0x15, bytearray([0xFA])) # PLX + rom.write_bytes(RUN_SUB_ADDR + 0x16, bytearray([0x6B])) # RTL + # End Run + + # Purple Block Carry + rom.write_bytes(0x726F, bytearray([0x22, 0x28, 0xBA, 0x03])) # JSL $03BA28 + rom.write_bytes(0x7273, bytearray([0xEA] * 0x02)) + + PURPLE_BLOCK_CARRY_SUB_ADDR = 0x01BA28 + rom.write_bytes(PURPLE_BLOCK_CARRY_SUB_ADDR + 0x00, bytearray([0x08])) # PHP + rom.write_bytes(PURPLE_BLOCK_CARRY_SUB_ADDR + 0x01, bytearray([0xAD, 0x2C, 0x1F])) # LDA $1F2C + rom.write_bytes(PURPLE_BLOCK_CARRY_SUB_ADDR + 0x04, bytearray([0x89, 0x40])) # BIT #40 + rom.write_bytes(PURPLE_BLOCK_CARRY_SUB_ADDR + 0x06, bytearray([0xF0, 0x09])) # BEQ +0x09 + rom.write_bytes(PURPLE_BLOCK_CARRY_SUB_ADDR + 0x08, bytearray([0x28])) # PLP + rom.write_bytes(PURPLE_BLOCK_CARRY_SUB_ADDR + 0x09, bytearray([0xAD, 0x8F, 0x14])) # LDA $148F + rom.write_bytes(PURPLE_BLOCK_CARRY_SUB_ADDR + 0x0C, bytearray([0x0D, 0x7A, 0x18])) # ORA $187A + rom.write_bytes(PURPLE_BLOCK_CARRY_SUB_ADDR + 0x0F, bytearray([0x80, 0x03])) # BRA +0x03 + rom.write_bytes(PURPLE_BLOCK_CARRY_SUB_ADDR + 0x11, bytearray([0x28])) # PLP + rom.write_bytes(PURPLE_BLOCK_CARRY_SUB_ADDR + 0x12, bytearray([0xA9, 0x01])) # LDA #01 + rom.write_bytes(PURPLE_BLOCK_CARRY_SUB_ADDR + 0x14, bytearray([0x6B])) # RTL + # End Purple Block Carry + + # Springboard Carry + rom.write_bytes(0xE6DA, bytearray([0x22, 0x40, 0xBA, 0x03])) # JSL $03BA40 + rom.write_bytes(0xE6DE, bytearray([0xEA] * 0x04)) + + SPRINGBOARD_CARRY_SUB_ADDR = 0x01BA40 + rom.write_bytes(SPRINGBOARD_CARRY_SUB_ADDR + 0x00, bytearray([0x48])) # PHA + rom.write_bytes(SPRINGBOARD_CARRY_SUB_ADDR + 0x01, bytearray([0x08])) # PHP + rom.write_bytes(SPRINGBOARD_CARRY_SUB_ADDR + 0x02, bytearray([0xAD, 0x2C, 0x1F])) # LDA $1F2C + rom.write_bytes(SPRINGBOARD_CARRY_SUB_ADDR + 0x05, bytearray([0x89, 0x40])) # BIT #40 + rom.write_bytes(SPRINGBOARD_CARRY_SUB_ADDR + 0x07, bytearray([0xF0, 0x08])) # BEQ +0x08 + rom.write_bytes(SPRINGBOARD_CARRY_SUB_ADDR + 0x09, bytearray([0xA9, 0x0B])) # LDA #0B + rom.write_bytes(SPRINGBOARD_CARRY_SUB_ADDR + 0x0B, bytearray([0x9D, 0xC8, 0x14])) # STA $14C8, X + rom.write_bytes(SPRINGBOARD_CARRY_SUB_ADDR + 0x0E, bytearray([0x9E, 0x02, 0x16])) # STZ $1602, X + rom.write_bytes(SPRINGBOARD_CARRY_SUB_ADDR + 0x11, bytearray([0x28])) # PLP + rom.write_bytes(SPRINGBOARD_CARRY_SUB_ADDR + 0x12, bytearray([0x68])) # PLA + rom.write_bytes(SPRINGBOARD_CARRY_SUB_ADDR + 0x13, bytearray([0x6B])) # RTL + # End Springboard Carry + + # Shell Carry + rom.write_bytes(0xAA66, bytearray([0xAD, 0x2C, 0x1F])) # LDA $1F2C + rom.write_bytes(0xAA69, bytearray([0x89, 0x40])) # BIT #40 + rom.write_bytes(0xAA6B, bytearray([0xF0, 0x07])) # BEQ +0x07 + rom.write_bytes(0xAA6D, bytearray([0x22, 0x60, 0xBA, 0x03])) # JSL $03BA60 + rom.write_bytes(0xAA71, bytearray([0xEA] * 0x02)) + + SHELL_CARRY_SUB_ADDR = 0x01BA60 + rom.write_bytes(SHELL_CARRY_SUB_ADDR + 0x00, bytearray([0x08])) # PHP + rom.write_bytes(SHELL_CARRY_SUB_ADDR + 0x01, bytearray([0xA9, 0x0B])) # LDA #0B + rom.write_bytes(SHELL_CARRY_SUB_ADDR + 0x03, bytearray([0x9D, 0xC8, 0x14])) # STA $14C8, X + rom.write_bytes(SHELL_CARRY_SUB_ADDR + 0x06, bytearray([0xEE, 0x70, 0x14])) # INC $1470 + rom.write_bytes(SHELL_CARRY_SUB_ADDR + 0x09, bytearray([0xA9, 0x0B])) # LDA #08 + rom.write_bytes(SHELL_CARRY_SUB_ADDR + 0x0B, bytearray([0x8D, 0x98, 0x14])) # STA $1498 + rom.write_bytes(SHELL_CARRY_SUB_ADDR + 0x0E, bytearray([0x28])) # PLP + rom.write_bytes(SHELL_CARRY_SUB_ADDR + 0x0F, bytearray([0x6B])) # RTL + # End Shell Carry + + # Yoshi Carry + rom.write_bytes(0xF309, bytearray([0x22, 0x70, 0xBA, 0x03])) # JSL $03BA70 + rom.write_bytes(0xF30D, bytearray([0xEA] * 0x06)) + + YOSHI_CARRY_SUB_ADDR = 0x01BA70 + rom.write_bytes(YOSHI_CARRY_SUB_ADDR + 0x00, bytearray([0x08])) # PHP + rom.write_bytes(YOSHI_CARRY_SUB_ADDR + 0x01, bytearray([0xAD, 0x2C, 0x1F])) # LDA $1F2C + rom.write_bytes(YOSHI_CARRY_SUB_ADDR + 0x04, bytearray([0x89, 0x40])) # BIT #40 + rom.write_bytes(YOSHI_CARRY_SUB_ADDR + 0x06, bytearray([0xF0, 0x0A])) # BEQ +0x0A + rom.write_bytes(YOSHI_CARRY_SUB_ADDR + 0x08, bytearray([0xA9, 0x12])) # LDA #12 + rom.write_bytes(YOSHI_CARRY_SUB_ADDR + 0x0A, bytearray([0x8D, 0xA3, 0x14])) # STA $14A3 + rom.write_bytes(YOSHI_CARRY_SUB_ADDR + 0x0D, bytearray([0xA9, 0x21])) # LDA #21 + rom.write_bytes(YOSHI_CARRY_SUB_ADDR + 0x0F, bytearray([0x8D, 0xFC, 0x1D])) # STA $1DFC + rom.write_bytes(YOSHI_CARRY_SUB_ADDR + 0x12, bytearray([0x28])) # PLP + rom.write_bytes(YOSHI_CARRY_SUB_ADDR + 0x13, bytearray([0x6B])) # RTL + # End Yoshi Carry + + # Climb + rom.write_bytes(0x4D72, bytearray([0x5C, 0x88, 0xBA, 0x03])) # JML $03BA88 + rom.write_bytes(0x4D76, bytearray([0xEA] * 0x03)) + + CLIMB_SUB_ADDR = 0x01BA88 + rom.write_bytes(CLIMB_SUB_ADDR + 0x00, bytearray([0x08])) # PHP + rom.write_bytes(CLIMB_SUB_ADDR + 0x01, bytearray([0xAD, 0x2C, 0x1F])) # LDA $1F2C + rom.write_bytes(CLIMB_SUB_ADDR + 0x04, bytearray([0x89, 0x20])) # BIT #20 + rom.write_bytes(CLIMB_SUB_ADDR + 0x06, bytearray([0xF0, 0x09])) # BEQ +0x09 + rom.write_bytes(CLIMB_SUB_ADDR + 0x08, bytearray([0xA5, 0x8B])) # LDA $8B + rom.write_bytes(CLIMB_SUB_ADDR + 0x0A, bytearray([0x85, 0x74])) # STA $74 + rom.write_bytes(CLIMB_SUB_ADDR + 0x0C, bytearray([0x28])) # PLP + rom.write_bytes(CLIMB_SUB_ADDR + 0x0D, bytearray([0x5C, 0x17, 0xDB, 0x00])) # JML $00DB17 + rom.write_bytes(CLIMB_SUB_ADDR + 0x11, bytearray([0x28])) # PLP + rom.write_bytes(CLIMB_SUB_ADDR + 0x12, bytearray([0x5C, 0x76, 0xCD, 0x00])) # JML $00CD76 + # End Climb + + # P-Switch + rom.write_bytes(0xAB1A, bytearray([0x22, 0xA0, 0xBA, 0x03])) # JSL $03BAA0 + rom.write_bytes(0xAB1E, bytearray([0xEA] * 0x01)) + + P_SWITCH_SUB_ADDR = 0x01BAA0 + rom.write_bytes(P_SWITCH_SUB_ADDR + 0x00, bytearray([0x08])) # PHP + rom.write_bytes(P_SWITCH_SUB_ADDR + 0x01, bytearray([0xAD, 0x2C, 0x1F])) # LDA $1F2C + rom.write_bytes(P_SWITCH_SUB_ADDR + 0x04, bytearray([0x89, 0x10])) # BIT #10 + rom.write_bytes(P_SWITCH_SUB_ADDR + 0x06, bytearray([0xF0, 0x04])) # BEQ +0x04 + rom.write_bytes(P_SWITCH_SUB_ADDR + 0x08, bytearray([0xA9, 0xB0])) # LDA #B0 + rom.write_bytes(P_SWITCH_SUB_ADDR + 0x0A, bytearray([0x80, 0x02])) # BRA +0x02 + rom.write_bytes(P_SWITCH_SUB_ADDR + 0x0C, bytearray([0xA9, 0x01])) # LDA #01 + rom.write_bytes(P_SWITCH_SUB_ADDR + 0x0E, bytearray([0x99, 0xAD, 0x14])) # STA $14AD + rom.write_bytes(P_SWITCH_SUB_ADDR + 0x11, bytearray([0x28])) # PLP + rom.write_bytes(P_SWITCH_SUB_ADDR + 0x12, bytearray([0x6B])) # RTL + # End P-Switch + + # Spin Jump + rom.write_bytes(0x5645, bytearray([0xAD, 0x2C, 0x1F])) # LDA $1F2C + rom.write_bytes(0x5648, bytearray([0x89, 0x08])) # BIT #08 + rom.write_bytes(0x564A, bytearray([0xF0, 0x12])) # BEQ +0x12 + rom.write_bytes(0x564C, bytearray([0x22, 0xB8, 0xBA, 0x03])) # JSL $03BAB8 + + SPIN_JUMP_SUB_ADDR = 0x01BAB8 + rom.write_bytes(SPIN_JUMP_SUB_ADDR + 0x00, bytearray([0x08])) # PHP + rom.write_bytes(SPIN_JUMP_SUB_ADDR + 0x01, bytearray([0x1A])) # INC + rom.write_bytes(SPIN_JUMP_SUB_ADDR + 0x02, bytearray([0x8D, 0x0D, 0x14])) # STA $140D + rom.write_bytes(SPIN_JUMP_SUB_ADDR + 0x05, bytearray([0xA9, 0x04])) # LDA #04 + rom.write_bytes(SPIN_JUMP_SUB_ADDR + 0x07, bytearray([0x8D, 0xFC, 0x1D])) # STA $1DFC + rom.write_bytes(SPIN_JUMP_SUB_ADDR + 0x0A, bytearray([0xA4, 0x76])) # LDY #76 + rom.write_bytes(SPIN_JUMP_SUB_ADDR + 0x0C, bytearray([0x28])) # PLP + rom.write_bytes(SPIN_JUMP_SUB_ADDR + 0x0D, bytearray([0x6B])) # RTL + # End Spin Jump + + # Spin Jump from Water + rom.write_bytes(0x6A89, bytearray([0x22, 0xF8, 0xBB, 0x03])) # JSL $03BBF8 + rom.write_bytes(0x6A8D, bytearray([0xEA] * 0x05)) + + SPIN_JUMP_WATER_SUB_ADDR = 0x01BBF8 + rom.write_bytes(SPIN_JUMP_WATER_SUB_ADDR + 0x00, bytearray([0x08])) # PHP + rom.write_bytes(SPIN_JUMP_WATER_SUB_ADDR + 0x01, bytearray([0xAD, 0x2C, 0x1F])) # LDA $1F2C + rom.write_bytes(SPIN_JUMP_WATER_SUB_ADDR + 0x04, bytearray([0x89, 0x08])) # BIT #08 + rom.write_bytes(SPIN_JUMP_WATER_SUB_ADDR + 0x06, bytearray([0xF0, 0x09])) # BEQ +0x09 + rom.write_bytes(SPIN_JUMP_WATER_SUB_ADDR + 0x08, bytearray([0x1A])) # INC + rom.write_bytes(SPIN_JUMP_WATER_SUB_ADDR + 0x09, bytearray([0x8D, 0x0D, 0x14])) # STA $140D + rom.write_bytes(SPIN_JUMP_WATER_SUB_ADDR + 0x0C, bytearray([0xA9, 0x04])) # LDA #04 + rom.write_bytes(SPIN_JUMP_WATER_SUB_ADDR + 0x0E, bytearray([0x8D, 0xFC, 0x1D])) # STA $1DFC + rom.write_bytes(SPIN_JUMP_WATER_SUB_ADDR + 0x11, bytearray([0x28])) # PLP + rom.write_bytes(SPIN_JUMP_WATER_SUB_ADDR + 0x12, bytearray([0x6B])) # RTL + # End Spin Jump from Water + + # Spin Jump from Springboard + rom.write_bytes(0xE693, bytearray([0x22, 0x0C, 0xBC, 0x03])) # JSL $03BC0C + rom.write_bytes(0xE697, bytearray([0xEA] * 0x01)) + + SPIN_JUMP_SPRING_SUB_ADDR = 0x01BC0C + rom.write_bytes(SPIN_JUMP_SPRING_SUB_ADDR + 0x00, bytearray([0x08])) # PHP + rom.write_bytes(SPIN_JUMP_SPRING_SUB_ADDR + 0x01, bytearray([0xAD, 0x2C, 0x1F])) # LDA $1F2C + rom.write_bytes(SPIN_JUMP_SPRING_SUB_ADDR + 0x04, bytearray([0x89, 0x08])) # BIT #08 + rom.write_bytes(SPIN_JUMP_SPRING_SUB_ADDR + 0x06, bytearray([0xF0, 0x05])) # BEQ +0x05 + rom.write_bytes(SPIN_JUMP_SPRING_SUB_ADDR + 0x08, bytearray([0xA9, 0x01])) # LDA #01 + rom.write_bytes(SPIN_JUMP_SPRING_SUB_ADDR + 0x0A, bytearray([0x8D, 0x0D, 0x14])) # STA $140D + rom.write_bytes(SPIN_JUMP_SPRING_SUB_ADDR + 0x0D, bytearray([0x28])) # PLP + rom.write_bytes(SPIN_JUMP_SPRING_SUB_ADDR + 0x0E, bytearray([0x6B])) # RTL + # End Spin Jump from Springboard + + # Swim + rom.write_bytes(0x5A25, bytearray([0x22, 0xC8, 0xBA, 0x03])) # JSL $03BAC8 + rom.write_bytes(0x5A29, bytearray([0xEA] * 0x04)) + + SWIM_SUB_ADDR = 0x01BAC8 + rom.write_bytes(SWIM_SUB_ADDR + 0x00, bytearray([0x48])) # PHA + rom.write_bytes(SWIM_SUB_ADDR + 0x01, bytearray([0x08])) # PHP + rom.write_bytes(SWIM_SUB_ADDR + 0x02, bytearray([0xAD, 0x2C, 0x1F])) # LDA $1F2C + rom.write_bytes(SWIM_SUB_ADDR + 0x05, bytearray([0x89, 0x04])) # BIT #04 + rom.write_bytes(SWIM_SUB_ADDR + 0x07, bytearray([0xF0, 0x0C])) # BEQ +0x0C + rom.write_bytes(SWIM_SUB_ADDR + 0x09, bytearray([0x28])) # PLP + rom.write_bytes(SWIM_SUB_ADDR + 0x0A, bytearray([0x68])) # PLA + rom.write_bytes(SWIM_SUB_ADDR + 0x0B, bytearray([0xDD, 0x84, 0xD9])) # CMP $D489, X + rom.write_bytes(SWIM_SUB_ADDR + 0x0E, bytearray([0xB0, 0x03])) # BCS +0x03 + rom.write_bytes(SWIM_SUB_ADDR + 0x10, bytearray([0xBD, 0x84, 0xD9])) # LDA $D489, X + rom.write_bytes(SWIM_SUB_ADDR + 0x13, bytearray([0x80, 0x0A])) # BRA +0x0A + rom.write_bytes(SWIM_SUB_ADDR + 0x15, bytearray([0x28])) # PLP + rom.write_bytes(SWIM_SUB_ADDR + 0x16, bytearray([0x68])) # PLA + rom.write_bytes(SWIM_SUB_ADDR + 0x17, bytearray([0xDD, 0xBE, 0xDE])) # CMP $DEBE, X + rom.write_bytes(SWIM_SUB_ADDR + 0x1A, bytearray([0xB0, 0x03])) # BCS +0x03 + rom.write_bytes(SWIM_SUB_ADDR + 0x1C, bytearray([0xBD, 0xBE, 0xDE])) # LDA $DEBE, X + rom.write_bytes(SWIM_SUB_ADDR + 0x1F, bytearray([0x6B])) # RTL + # End Swim + + # Item Swim + rom.write_bytes(0x59D7, bytearray([0x22, 0xE8, 0xBA, 0x03])) # JSL $03BAE8 + rom.write_bytes(0x59DB, bytearray([0xEA] * 0x02)) + + SWIM_SUB_ADDR = 0x01BAE8 + rom.write_bytes(SWIM_SUB_ADDR + 0x00, bytearray([0x48])) # PHA + rom.write_bytes(SWIM_SUB_ADDR + 0x01, bytearray([0x08])) # PHP + rom.write_bytes(SWIM_SUB_ADDR + 0x02, bytearray([0xAD, 0x2C, 0x1F])) # LDA $1F2C + rom.write_bytes(SWIM_SUB_ADDR + 0x05, bytearray([0x89, 0x04])) # BIT #04 + rom.write_bytes(SWIM_SUB_ADDR + 0x07, bytearray([0xF0, 0x0A])) # BEQ +0x0A + rom.write_bytes(SWIM_SUB_ADDR + 0x09, bytearray([0x28])) # PLP + rom.write_bytes(SWIM_SUB_ADDR + 0x0A, bytearray([0x68])) # PLA + rom.write_bytes(SWIM_SUB_ADDR + 0x0B, bytearray([0xC9, 0xF0])) # CMP #F0 + rom.write_bytes(SWIM_SUB_ADDR + 0x0D, bytearray([0xB0, 0x02])) # BCS +0x02 + rom.write_bytes(SWIM_SUB_ADDR + 0x0F, bytearray([0xA9, 0xF0])) # LDA #F0 + rom.write_bytes(SWIM_SUB_ADDR + 0x11, bytearray([0x80, 0x08])) # BRA +0x08 + rom.write_bytes(SWIM_SUB_ADDR + 0x13, bytearray([0x28])) # PLP + rom.write_bytes(SWIM_SUB_ADDR + 0x14, bytearray([0x68])) # PLA + rom.write_bytes(SWIM_SUB_ADDR + 0x15, bytearray([0xC9, 0xFF])) # CMP #FF + rom.write_bytes(SWIM_SUB_ADDR + 0x17, bytearray([0xB0, 0x02])) # BCS +0x02 + rom.write_bytes(SWIM_SUB_ADDR + 0x19, bytearray([0xA9, 0x00])) # LDA #00 + rom.write_bytes(SWIM_SUB_ADDR + 0x1B, bytearray([0x6B])) # RTL + # End Item Swim + + # Yoshi + rom.write_bytes(0x109FB, bytearray([0x22, 0x08, 0xBB, 0x03])) # JSL $03BB08 + rom.write_bytes(0x109FF, bytearray([0xEA] * 0x02)) + + YOSHI_SUB_ADDR = 0x01BB08 + rom.write_bytes(YOSHI_SUB_ADDR + 0x00, bytearray([0x08])) # PHP + rom.write_bytes(YOSHI_SUB_ADDR + 0x01, bytearray([0xAD, 0x2C, 0x1F])) # LDA $1F2C + rom.write_bytes(YOSHI_SUB_ADDR + 0x04, bytearray([0x89, 0x02])) # BIT #02 + rom.write_bytes(YOSHI_SUB_ADDR + 0x06, bytearray([0xF0, 0x06])) # BEQ +0x06 + rom.write_bytes(YOSHI_SUB_ADDR + 0x08, bytearray([0x28])) # PLP + rom.write_bytes(YOSHI_SUB_ADDR + 0x09, bytearray([0xB9, 0xA1, 0x88])) # LDA $88A1, Y + rom.write_bytes(YOSHI_SUB_ADDR + 0x0C, bytearray([0x80, 0x04])) # BRA +0x04 + rom.write_bytes(YOSHI_SUB_ADDR + 0x0E, bytearray([0x28])) # PLP + rom.write_bytes(YOSHI_SUB_ADDR + 0x0F, bytearray([0xB9, 0xA2, 0x88])) # LDA $88A2, Y + rom.write_bytes(YOSHI_SUB_ADDR + 0x12, bytearray([0x9D, 0x1C, 0x15])) # STA $151C, X + rom.write_bytes(YOSHI_SUB_ADDR + 0x15, bytearray([0x6B])) # RTL + # End Yoshi + + # Baby Yoshi + rom.write_bytes(0xA2B8, bytearray([0x22, 0x20, 0xBB, 0x03])) # JSL $03BB20 + rom.write_bytes(0xA2BC, bytearray([0xEA] * 0x01)) + + YOSHI_SUB_ADDR = 0x01BB20 + rom.write_bytes(YOSHI_SUB_ADDR + 0x00, bytearray([0x08])) # PHP + rom.write_bytes(YOSHI_SUB_ADDR + 0x01, bytearray([0x9C, 0x1E, 0x14])) # STZ $141E + rom.write_bytes(YOSHI_SUB_ADDR + 0x04, bytearray([0xAD, 0x2C, 0x1F])) # LDA $1F2C + rom.write_bytes(YOSHI_SUB_ADDR + 0x07, bytearray([0x89, 0x02])) # BIT #02 + rom.write_bytes(YOSHI_SUB_ADDR + 0x09, bytearray([0xF0, 0x05])) # BEQ +0x05 + rom.write_bytes(YOSHI_SUB_ADDR + 0x0B, bytearray([0x28])) # PLP + rom.write_bytes(YOSHI_SUB_ADDR + 0x0C, bytearray([0xA9, 0x35])) # LDA #35 + rom.write_bytes(YOSHI_SUB_ADDR + 0x0E, bytearray([0x80, 0x03])) # BRA +0x03 + rom.write_bytes(YOSHI_SUB_ADDR + 0x10, bytearray([0x28])) # PLP + rom.write_bytes(YOSHI_SUB_ADDR + 0x11, bytearray([0xA9, 0x70])) # LDA #70 + rom.write_bytes(YOSHI_SUB_ADDR + 0x13, bytearray([0x6B])) # RTL + # End Baby Yoshi + + # Midway Gate + rom.write_bytes(0x72E4, bytearray([0x22, 0x38, 0xBB, 0x03])) # JSL $03BB38 + + MIDWAY_GATE_SUB_ADDR = 0x01BB38 + rom.write_bytes(MIDWAY_GATE_SUB_ADDR + 0x00, bytearray([0x08])) # PHP + rom.write_bytes(MIDWAY_GATE_SUB_ADDR + 0x01, bytearray([0xAD, 0x2D, 0x1F])) # LDA $1F2D + rom.write_bytes(MIDWAY_GATE_SUB_ADDR + 0x04, bytearray([0x89, 0x01])) # BIT #01 + rom.write_bytes(MIDWAY_GATE_SUB_ADDR + 0x06, bytearray([0xF0, 0x07])) # BEQ +0x07 + rom.write_bytes(MIDWAY_GATE_SUB_ADDR + 0x08, bytearray([0x28])) # PLP + rom.write_bytes(MIDWAY_GATE_SUB_ADDR + 0x09, bytearray([0xA9, 0x01])) # LDA #01 + rom.write_bytes(MIDWAY_GATE_SUB_ADDR + 0x0B, bytearray([0x85, 0x19])) # STA $19 + rom.write_bytes(MIDWAY_GATE_SUB_ADDR + 0x0D, bytearray([0x80, 0x01])) # BRA +0x01 + rom.write_bytes(MIDWAY_GATE_SUB_ADDR + 0x0F, bytearray([0x28])) # PLP + rom.write_bytes(MIDWAY_GATE_SUB_ADDR + 0x10, bytearray([0x6B])) # RTL + # End Midway Gate + + # Mushroom + rom.write_bytes(0x5156, bytearray([0x22, 0x50, 0xBB, 0x03])) # JSL $03BB50 + rom.write_bytes(0x515A, bytearray([0xEA] * 0x04)) + + MUSHROOM_SUB_ADDR = 0x01BB50 + rom.write_bytes(MUSHROOM_SUB_ADDR + 0x00, bytearray([0x08])) # PHP + rom.write_bytes(MUSHROOM_SUB_ADDR + 0x01, bytearray([0xAD, 0x2D, 0x1F])) # LDA $1F2D + rom.write_bytes(MUSHROOM_SUB_ADDR + 0x04, bytearray([0x89, 0x01])) # BIT #01 + rom.write_bytes(MUSHROOM_SUB_ADDR + 0x06, bytearray([0xF0, 0x05])) # BEQ +0x05 + rom.write_bytes(MUSHROOM_SUB_ADDR + 0x08, bytearray([0x28])) # PLP + rom.write_bytes(MUSHROOM_SUB_ADDR + 0x09, bytearray([0xE6, 0x19])) # INC $19 + rom.write_bytes(MUSHROOM_SUB_ADDR + 0x0B, bytearray([0x80, 0x01])) # BRA +0x01 + rom.write_bytes(MUSHROOM_SUB_ADDR + 0x0D, bytearray([0x28])) # PLP + rom.write_bytes(MUSHROOM_SUB_ADDR + 0x0E, bytearray([0xA9, 0x00])) # LDA #00 + rom.write_bytes(MUSHROOM_SUB_ADDR + 0x10, bytearray([0x85, 0x71])) # STA $72 + rom.write_bytes(MUSHROOM_SUB_ADDR + 0x12, bytearray([0x64, 0x9D])) # STZ $9D + rom.write_bytes(MUSHROOM_SUB_ADDR + 0x14, bytearray([0x6B])) # RTL + # End Mushroom + + # Take Damage + rom.write_bytes(0x5142, bytearray([0x22, 0x65, 0xBB, 0x03])) # JSL $03BB65 + rom.write_bytes(0x5146, bytearray([0x60] * 0x01)) # RTS + + DAMAGE_SUB_ADDR = 0x01BB65 + rom.write_bytes(DAMAGE_SUB_ADDR + 0x00, bytearray([0x8D, 0x97, 0x14])) # STA $1497 + rom.write_bytes(DAMAGE_SUB_ADDR + 0x03, bytearray([0x80, 0xF4])) # BRA -0x0C + # End Take Damage + + # Fire Flower Cycle + rom.write_bytes(0x5187, bytearray([0x22, 0x6A, 0xBB, 0x03])) # JSL $03BB6A + rom.write_bytes(0x518B, bytearray([0x60] * 0x01)) # RTS + + PALETTE_CYCLE_SUB_ADDR = 0x01BB6A + rom.write_bytes(PALETTE_CYCLE_SUB_ADDR + 0x00, bytearray([0xCE, 0x9B, 0x14])) # DEC $149B + rom.write_bytes(PALETTE_CYCLE_SUB_ADDR + 0x03, bytearray([0xF0, 0xEF])) # BEQ -0x11 + rom.write_bytes(PALETTE_CYCLE_SUB_ADDR + 0x05, bytearray([0x6B])) # RTL + # End Fire Flower Cycle + + # Pipe Exit + rom.write_bytes(0x526D, bytearray([0x22, 0x70, 0xBB, 0x03])) # JSL $03BB70 + rom.write_bytes(0x5271, bytearray([0x60, 0xEA] * 0x01)) # RTS, NOP + + PIPE_EXIT_SUB_ADDR = 0x01BB70 + rom.write_bytes(PIPE_EXIT_SUB_ADDR + 0x00, bytearray([0x9C, 0x19, 0x14])) # STZ $1419 + rom.write_bytes(PIPE_EXIT_SUB_ADDR + 0x03, bytearray([0xA9, 0x00])) # LDA #00 + rom.write_bytes(PIPE_EXIT_SUB_ADDR + 0x05, bytearray([0x85, 0x71])) # STA $72 + rom.write_bytes(PIPE_EXIT_SUB_ADDR + 0x07, bytearray([0x64, 0x9D])) # STZ $9D + rom.write_bytes(PIPE_EXIT_SUB_ADDR + 0x09, bytearray([0x6B])) # RTL + # End Pipe Exit + + # Cape Transform + rom.write_bytes(0x5168, bytearray([0x22, 0x7A, 0xBB, 0x03])) # JSL $03BB7A + rom.write_bytes(0x516C, bytearray([0xEA] * 0x01)) # RTS, NOP + rom.write_bytes(0x516D, bytearray([0xF0, 0xD1])) # BEQ -0x2F + + CAPE_TRANSFORM_SUB_ADDR = 0x01BB7A + rom.write_bytes(CAPE_TRANSFORM_SUB_ADDR + 0x00, bytearray([0xA5, 0x19])) # LDA $19 + rom.write_bytes(CAPE_TRANSFORM_SUB_ADDR + 0x02, bytearray([0x4A])) # LSR + rom.write_bytes(CAPE_TRANSFORM_SUB_ADDR + 0x03, bytearray([0xD0, 0xDF])) # BNE -0x21 + rom.write_bytes(CAPE_TRANSFORM_SUB_ADDR + 0x05, bytearray([0x6B])) # RTL + # End Cape Transform + + # Fire Flower + rom.write_bytes(0xC5F7, bytearray([0x22, 0x80, 0xBB, 0x03])) # JSL $03BB80 + + FIRE_FLOWER_SUB_ADDR = 0x01BB80 + rom.write_bytes(FIRE_FLOWER_SUB_ADDR + 0x00, bytearray([0x08])) # PHP + rom.write_bytes(FIRE_FLOWER_SUB_ADDR + 0x01, bytearray([0xAD, 0x2D, 0x1F])) # LDA $1F2D + rom.write_bytes(FIRE_FLOWER_SUB_ADDR + 0x04, bytearray([0x89, 0x02])) # BIT #02 + rom.write_bytes(FIRE_FLOWER_SUB_ADDR + 0x06, bytearray([0xF0, 0x07])) # BEQ +0x07 + rom.write_bytes(FIRE_FLOWER_SUB_ADDR + 0x08, bytearray([0x28])) # PLP + rom.write_bytes(FIRE_FLOWER_SUB_ADDR + 0x09, bytearray([0xA9, 0x03])) # LDA #03 + rom.write_bytes(FIRE_FLOWER_SUB_ADDR + 0x0B, bytearray([0x85, 0x19])) # STA $19 + rom.write_bytes(FIRE_FLOWER_SUB_ADDR + 0x0D, bytearray([0x80, 0x01])) # BRA +0x01 + rom.write_bytes(FIRE_FLOWER_SUB_ADDR + 0x0F, bytearray([0x28])) # PLP + rom.write_bytes(FIRE_FLOWER_SUB_ADDR + 0x10, bytearray([0x6B])) # RTL + # End Fire Flower + + # Cape + rom.write_bytes(0xC598, bytearray([0x22, 0x91, 0xBB, 0x03])) # JSL $03BB91 + + CAPE_SUB_ADDR = 0x01BB91 + rom.write_bytes(CAPE_SUB_ADDR + 0x00, bytearray([0x08])) # PHP + rom.write_bytes(CAPE_SUB_ADDR + 0x01, bytearray([0xAD, 0x2D, 0x1F])) # LDA $1F2D + rom.write_bytes(CAPE_SUB_ADDR + 0x04, bytearray([0x89, 0x04])) # BIT #04 + rom.write_bytes(CAPE_SUB_ADDR + 0x06, bytearray([0xF0, 0x07])) # BEQ +0x07 + rom.write_bytes(CAPE_SUB_ADDR + 0x08, bytearray([0x28])) # PLP + rom.write_bytes(CAPE_SUB_ADDR + 0x09, bytearray([0xA9, 0x02])) # LDA #02 + rom.write_bytes(CAPE_SUB_ADDR + 0x0B, bytearray([0x85, 0x19])) # STA $19 + rom.write_bytes(CAPE_SUB_ADDR + 0x0D, bytearray([0x80, 0x01])) # BRA +0x01 + rom.write_bytes(CAPE_SUB_ADDR + 0x0F, bytearray([0x28])) # PLP + rom.write_bytes(CAPE_SUB_ADDR + 0x10, bytearray([0x6B])) # RTL + # End Cape + + # P-Balloon + rom.write_bytes(0xC2FF, bytearray([0x22, 0xA2, 0xBB, 0x03])) # JSL $03BBA2 + rom.write_bytes(0xC303, bytearray([0xEA] * 0x06)) + + P_BALLOON_SUB_ADDR = 0x01BBA2 + rom.write_bytes(P_BALLOON_SUB_ADDR + 0x00, bytearray([0x08])) # PHP + rom.write_bytes(P_BALLOON_SUB_ADDR + 0x01, bytearray([0xAD, 0x2D, 0x1F])) # LDA $1F2D + rom.write_bytes(P_BALLOON_SUB_ADDR + 0x04, bytearray([0x89, 0x08])) # BIT #08 + rom.write_bytes(P_BALLOON_SUB_ADDR + 0x06, bytearray([0xF0, 0x0D])) # BEQ +0x0D + rom.write_bytes(P_BALLOON_SUB_ADDR + 0x08, bytearray([0x28])) # PLP + rom.write_bytes(P_BALLOON_SUB_ADDR + 0x09, bytearray([0xA9, 0x09])) # LDA #09 + rom.write_bytes(P_BALLOON_SUB_ADDR + 0x0B, bytearray([0x8D, 0xF3, 0x13])) # STA $13F3 + rom.write_bytes(P_BALLOON_SUB_ADDR + 0x0E, bytearray([0xA9, 0xFF])) # LDA #FF + rom.write_bytes(P_BALLOON_SUB_ADDR + 0x10, bytearray([0x8D, 0x91, 0x18])) # STA $1891 + rom.write_bytes(P_BALLOON_SUB_ADDR + 0x13, bytearray([0x80, 0x0B])) # BRA +0x0B + rom.write_bytes(P_BALLOON_SUB_ADDR + 0x15, bytearray([0x28])) # PLP + rom.write_bytes(P_BALLOON_SUB_ADDR + 0x16, bytearray([0xA9, 0x01])) # LDA #01 + rom.write_bytes(P_BALLOON_SUB_ADDR + 0x18, bytearray([0x8D, 0xF3, 0x13])) # STA $13F3 + rom.write_bytes(P_BALLOON_SUB_ADDR + 0x1B, bytearray([0xA9, 0x01])) # LDA #01 + rom.write_bytes(P_BALLOON_SUB_ADDR + 0x1D, bytearray([0x8D, 0x91, 0x18])) # STA $1891 + rom.write_bytes(P_BALLOON_SUB_ADDR + 0x20, bytearray([0x6B])) # RTL + # End P-Balloon + + # Star + rom.write_bytes(0xC580, bytearray([0x22, 0xC8, 0xBB, 0x03])) # JSL $03BBC8 + rom.write_bytes(0xC584, bytearray([0xEA] * 0x01)) + + STAR_SUB_ADDR = 0x01BBC8 + rom.write_bytes(STAR_SUB_ADDR + 0x00, bytearray([0x08])) # PHP + rom.write_bytes(STAR_SUB_ADDR + 0x01, bytearray([0xAD, 0x2D, 0x1F])) # LDA $1F2D + rom.write_bytes(STAR_SUB_ADDR + 0x04, bytearray([0x89, 0x10])) # BIT #10 + rom.write_bytes(STAR_SUB_ADDR + 0x06, bytearray([0xF0, 0x08])) # BEQ +0x08 + rom.write_bytes(STAR_SUB_ADDR + 0x08, bytearray([0x28])) # PLP + rom.write_bytes(STAR_SUB_ADDR + 0x09, bytearray([0xA9, 0xFF])) # LDA #FF + rom.write_bytes(STAR_SUB_ADDR + 0x0B, bytearray([0x8D, 0x90, 0x14])) # STA $1490 + rom.write_bytes(STAR_SUB_ADDR + 0x0E, bytearray([0x80, 0x06])) # BRA +0x06 + rom.write_bytes(STAR_SUB_ADDR + 0x10, bytearray([0x28])) # PLP + rom.write_bytes(STAR_SUB_ADDR + 0x11, bytearray([0xA9, 0x01])) # LDA #01 + rom.write_bytes(STAR_SUB_ADDR + 0x13, bytearray([0x8D, 0x90, 0x14])) # STA $1490 + rom.write_bytes(STAR_SUB_ADDR + 0x16, bytearray([0x6B])) # RTL + # End Star + + # Star Timer + rom.write_bytes(0x62E3, bytearray([0x22, 0xE0, 0xBB, 0x03])) # JSL $03BBE0 + + STAR_TIMER_SUB_ADDR = 0x01BBE0 + rom.write_bytes(STAR_TIMER_SUB_ADDR + 0x00, bytearray([0x08])) # PHP + rom.write_bytes(STAR_TIMER_SUB_ADDR + 0x01, bytearray([0xAD, 0x2D, 0x1F])) # LDA $1F2D + rom.write_bytes(STAR_TIMER_SUB_ADDR + 0x04, bytearray([0x89, 0x10])) # BIT #10 + rom.write_bytes(STAR_TIMER_SUB_ADDR + 0x06, bytearray([0xF0, 0x07])) # BEQ +0x07 + rom.write_bytes(STAR_TIMER_SUB_ADDR + 0x08, bytearray([0x28])) # PLP + rom.write_bytes(STAR_TIMER_SUB_ADDR + 0x09, bytearray([0xA5, 0x13])) # LDA $13 + rom.write_bytes(STAR_TIMER_SUB_ADDR + 0x0B, bytearray([0xC0, 0x1E])) # CPY #1E + rom.write_bytes(STAR_TIMER_SUB_ADDR + 0x0D, bytearray([0x80, 0x05])) # BRA +0x05 + rom.write_bytes(STAR_TIMER_SUB_ADDR + 0x0F, bytearray([0x28])) # PLP + rom.write_bytes(STAR_TIMER_SUB_ADDR + 0x10, bytearray([0xA5, 0x13])) # LDA $13 + rom.write_bytes(STAR_TIMER_SUB_ADDR + 0x12, bytearray([0xC0, 0x01])) # CPY #01 + rom.write_bytes(STAR_TIMER_SUB_ADDR + 0x14, bytearray([0x6B])) # RTL + # End Star Timer + + return + + +def handle_yoshi_box(rom): + + rom.write_bytes(0xEC3D, bytearray([0xEA] * 0x03)) # NOP Lines that cause Yoshi Rescue Box normally + + rom.write_bytes(0x2B20F, bytearray([0x20, 0x60, 0xDC])) # JSR $05DC60 + + YOSHI_BOX_SUB_ADDR = 0x02DC60 + rom.write_bytes(YOSHI_BOX_SUB_ADDR + 0x00, bytearray([0x08])) # PHP + rom.write_bytes(YOSHI_BOX_SUB_ADDR + 0x01, bytearray([0xAD, 0x26, 0x14])) # LDA $1426 + rom.write_bytes(YOSHI_BOX_SUB_ADDR + 0x04, bytearray([0xC9, 0x03])) # CMP #03 + rom.write_bytes(YOSHI_BOX_SUB_ADDR + 0x06, bytearray([0xF0, 0x06])) # BEQ +0x06 + rom.write_bytes(YOSHI_BOX_SUB_ADDR + 0x08, bytearray([0x28])) # PLP + rom.write_bytes(YOSHI_BOX_SUB_ADDR + 0x09, bytearray([0xB9, 0xD9, 0xA5])) # LDA $A5B9, Y + rom.write_bytes(YOSHI_BOX_SUB_ADDR + 0x0C, bytearray([0x80, 0x08])) # BRA +0x08 + rom.write_bytes(YOSHI_BOX_SUB_ADDR + 0x0E, bytearray([0x28])) # PLP + rom.write_bytes(YOSHI_BOX_SUB_ADDR + 0x0F, bytearray([0xDA])) # PHX + rom.write_bytes(YOSHI_BOX_SUB_ADDR + 0x10, bytearray([0xBB])) # TYX + rom.write_bytes(YOSHI_BOX_SUB_ADDR + 0x11, bytearray([0xBF, 0x00, 0xC2, 0x7E])) # LDA $7EC200, X + rom.write_bytes(YOSHI_BOX_SUB_ADDR + 0x15, bytearray([0xFA])) # PLX + rom.write_bytes(YOSHI_BOX_SUB_ADDR + 0x16, bytearray([0x60])) # RTS + + return + + +def handle_bowser_damage(rom): + + rom.write_bytes(0x1A509, bytearray([0x20, 0x50, 0xBC])) # JSR $03BC50 + + BOWSER_BALLS_SUB_ADDR = 0x01BC50 + rom.write_bytes(BOWSER_BALLS_SUB_ADDR + 0x00, bytearray([0x08])) # PHP + rom.write_bytes(BOWSER_BALLS_SUB_ADDR + 0x01, bytearray([0xAD, 0x48, 0x0F])) # LDA $F48 + rom.write_bytes(BOWSER_BALLS_SUB_ADDR + 0x04, bytearray([0xCF, 0xA1, 0xBF, 0x03])) # CMP $03BFA1 + rom.write_bytes(BOWSER_BALLS_SUB_ADDR + 0x08, bytearray([0x90, 0x06])) # BCC +0x06 + rom.write_bytes(BOWSER_BALLS_SUB_ADDR + 0x0A, bytearray([0x28])) # PLP + rom.write_bytes(BOWSER_BALLS_SUB_ADDR + 0x0B, bytearray([0xEE, 0xB8, 0x14])) # INC $14B8 + rom.write_bytes(BOWSER_BALLS_SUB_ADDR + 0x0E, bytearray([0x80, 0x01])) # BRA +0x01 + rom.write_bytes(BOWSER_BALLS_SUB_ADDR + 0x10, bytearray([0x28])) # PLP + rom.write_bytes(BOWSER_BALLS_SUB_ADDR + 0x11, bytearray([0x60])) # RTS + + return + + +def handle_level_shuffle(rom, active_level_dict): + rom.write_bytes(0x37600, bytearray([0x00] * 0x800)) # Duplicate Level Table + + rom.write_bytes(0x2D89C, bytearray([0x00, 0xF6, 0x06])) # Level Load Pointer + rom.write_bytes(0x20F46, bytearray([0x00, 0xF6, 0x06])) # Mid Gate Pointer + rom.write_bytes(0x20E7B, bytearray([0x00, 0xF6, 0x06])) # Level Name Pointer + rom.write_bytes(0x21543, bytearray([0x00, 0xF6, 0x06])) # Also Level Name Pointer? + rom.write_bytes(0x20F64, bytearray([0x00, 0xF6, 0x06])) # Level Beaten Pointer + + ### Fix Translevel Check + rom.write_bytes(0x2D8AE, bytearray([0x20, 0x00, 0xDD])) # JSR $DD00 + rom.write_bytes(0x2D8B1, bytearray([0xEA, 0xEA, 0xEA, 0xEA, 0xEA, 0xEA])) # NOP NOP NOP NOP NOP + + rom.write_bytes(0x2D7CB, bytearray([0x20, 0x00, 0xDD])) # JSR $DD00 + rom.write_bytes(0x2D7CE, bytearray([0xEA, 0xEA, 0xEA, 0xEA, 0xEA, 0xEA])) # NOP NOP NOP NOP NOP + + rom.write_bytes(0x2DD00, bytearray([0xDA])) # PHX + rom.write_bytes(0x2DD01, bytearray([0x08])) # PHP + rom.write_bytes(0x2DD02, bytearray([0xE2, 0x30])) # SEP #30 + rom.write_bytes(0x2DD04, bytearray([0xAE, 0xBF, 0x13])) # LDX $13BF + rom.write_bytes(0x2DD07, bytearray([0xE0, 0x25])) # CPX #25 + rom.write_bytes(0x2DD09, bytearray([0x90, 0x04])) # BCC $DD0F + rom.write_bytes(0x2DD0B, bytearray([0xA2, 0x01])) # LDX #01 + rom.write_bytes(0x2DD0D, bytearray([0x80, 0x02])) # BRA $DD11 + rom.write_bytes(0x2DD0F, bytearray([0xA2, 0x00])) # LDX #00 + rom.write_bytes(0x2DD11, bytearray([0x86, 0x0F])) # STX $0F + rom.write_bytes(0x2DD13, bytearray([0x28])) # PLP + rom.write_bytes(0x2DD14, bytearray([0xFA])) # PLX + rom.write_bytes(0x2DD15, bytearray([0x60])) # RTS + ### End Fix Translevel Check + + ### Fix Snake Blocks + rom.write_bytes(0x192FB, bytearray([0x20, 0x1D, 0xBC])) # JSR $03BC1D + + SNAKE_BLOCKS_SUB_ADDR = 0x01BC1D + rom.write_bytes(SNAKE_BLOCKS_SUB_ADDR + 0x00, bytearray([0x08])) # PHP + rom.write_bytes(SNAKE_BLOCKS_SUB_ADDR + 0x01, bytearray([0xAD, 0xBF, 0x13])) # LDA $13BF + rom.write_bytes(SNAKE_BLOCKS_SUB_ADDR + 0x04, bytearray([0xC9, 0x20])) # CMP #20 + rom.write_bytes(SNAKE_BLOCKS_SUB_ADDR + 0x06, bytearray([0xF0, 0x05])) # BEQ +0x05 + rom.write_bytes(SNAKE_BLOCKS_SUB_ADDR + 0x08, bytearray([0x28])) # PLP + rom.write_bytes(SNAKE_BLOCKS_SUB_ADDR + 0x09, bytearray([0xA9, 0x01])) # LDA #01 + rom.write_bytes(SNAKE_BLOCKS_SUB_ADDR + 0x0B, bytearray([0x80, 0x03])) # BRA +0x03 + rom.write_bytes(SNAKE_BLOCKS_SUB_ADDR + 0x0D, bytearray([0x28])) # PLP + rom.write_bytes(SNAKE_BLOCKS_SUB_ADDR + 0x0E, bytearray([0xA9, 0x00])) # LDA #00 + rom.write_bytes(SNAKE_BLOCKS_SUB_ADDR + 0x10, bytearray([0x60])) # RTS + ### End Fix Snake Blocks + + for level_id, level_data in level_info_dict.items(): + if level_id not in active_level_dict.keys(): + continue + + tile_id = active_level_dict[level_id] + tile_data = level_info_dict[tile_id] + + if level_id > 0x80: + level_id = level_id - 0x50 + + rom.write_byte(tile_data.levelIDAddress, level_id) + rom.write_byte(0x2D608 + level_id, tile_data.eventIDValue) + + for level_id, tile_id in active_level_dict.items(): + rom.write_byte(0x37F70 + level_id, tile_id) + + +def handle_collected_paths(rom): + rom.write_bytes(0x1F5B, bytearray([0x22, 0x30, 0xBC, 0x03])) # JSL $03BC30 + rom.write_bytes(0x1F5F, bytearray([0xEA] * 0x02)) + + COLLECTED_PATHS_SUB_ADDR = 0x01BC30 + rom.write_bytes(COLLECTED_PATHS_SUB_ADDR + 0x00, bytearray([0x08])) # PHP + rom.write_bytes(COLLECTED_PATHS_SUB_ADDR + 0x01, bytearray([0xAD, 0x00, 0x01])) # LDA $0100 + rom.write_bytes(COLLECTED_PATHS_SUB_ADDR + 0x04, bytearray([0xC9, 0x0B])) # CMP #0B + rom.write_bytes(COLLECTED_PATHS_SUB_ADDR + 0x06, bytearray([0xD0, 0x04])) # BNE +0x04 + rom.write_bytes(COLLECTED_PATHS_SUB_ADDR + 0x08, bytearray([0x22, 0xAD, 0xDA, 0x04])) # JSL $04DAAD + rom.write_bytes(COLLECTED_PATHS_SUB_ADDR + 0x0C, bytearray([0x28])) # PLP + rom.write_bytes(COLLECTED_PATHS_SUB_ADDR + 0x0D, bytearray([0xEE, 0x00, 0x01])) # INC $0100 + rom.write_bytes(COLLECTED_PATHS_SUB_ADDR + 0x10, bytearray([0xAD, 0xAF, 0x0D])) # LDA $0DAF + rom.write_bytes(COLLECTED_PATHS_SUB_ADDR + 0x13, bytearray([0x6B])) # RTL + + +def handle_music_shuffle(rom, world, player): + from .Aesthetics import generate_shuffled_level_music, generate_shuffled_ow_music, level_music_address_data, ow_music_address_data + + shuffled_level_music = generate_shuffled_level_music(world, player) + for i in range(len(shuffled_level_music)): + rom.write_byte(level_music_address_data[i], shuffled_level_music[i]) + + shuffled_ow_music = generate_shuffled_ow_music(world, player) + for i in range(len(shuffled_ow_music)): + for addr in ow_music_address_data[i]: + rom.write_byte(addr, shuffled_ow_music[i]) + + +def handle_mario_palette(rom, world, player): + from .Aesthetics import mario_palettes, fire_mario_palettes, ow_mario_palettes + + chosen_palette = world.mario_palette[player].value + + rom.write_bytes(0x32C8, bytes(mario_palettes[chosen_palette])) + rom.write_bytes(0x32F0, bytes(fire_mario_palettes[chosen_palette])) + rom.write_bytes(0x359C, bytes(ow_mario_palettes[chosen_palette])) + + +def handle_swap_donut_gh_exits(rom): + rom.write_bytes(0x2567C, bytes([0xC0])) + rom.write_bytes(0x25873, bytes([0xA9])) + rom.write_bytes(0x25875, bytes([0x85])) + rom.write_bytes(0x25954, bytes([0x92])) + rom.write_bytes(0x25956, bytes([0x0A])) + rom.write_bytes(0x25E31, bytes([0x00, 0x00, 0xD8, 0x04, 0x24, 0x00, 0x98, 0x04, 0x48, 0x00, 0xD8, 0x03, 0x6C, 0x00, 0x56, 0x03, + 0x90, 0x00, 0x56, 0x03, 0xB4, 0x00, 0x56, 0x03, 0x10, 0x05, 0x18, 0x05, 0x28, 0x09, 0x24, 0x05, + 0x38, 0x0B, 0x14, 0x07, 0xEC, 0x09, 0x12, 0x05, 0xF0, 0x09, 0xD2, 0x04, 0xF4, 0x09, 0x92, 0x04])) + rom.write_bytes(0x26371, bytes([0x32])) + + +def patch_rom(world, rom, player, active_level_dict): + local_random = world.slot_seeds[player] + + goal_text = generate_goal_text(world, player) + + rom.write_bytes(0x2A6E2, goal_text) + rom.write_byte(0x2B1D8, 0x80) + + intro_text = generate_text_box("Bowser has stolen all of Mario's abilities. Can you help Mario travel across Dinosaur land to get them back and save the Princess from him?") + rom.write_bytes(0x2A5D9, intro_text) + + # Force all 8 Bowser's Castle Rooms + rom.write_byte(0x3A680, 0xD4) + rom.write_byte(0x3A684, 0xD4) + rom.write_byte(0x3A688, 0xD4) + rom.write_byte(0x3A68C, 0xD4) + rom.write_byte(0x3A705, 0xD3) + rom.write_byte(0x3A763, 0xD2) + rom.write_byte(0x3A800, 0xD1) + rom.write_byte(0x3A83D, 0xCF) + rom.write_byte(0x3A932, 0xCE) + rom.write_byte(0x3A9E1, 0xCD) + rom.write_byte(0x3AA75, 0xCC) + + # Prevent Title Screen Deaths + rom.write_byte(0x1C6A, 0x80) + + # Title Screen Text + player_name_bytes = bytearray() + player_name = world.get_player_name(player) + for i in range(16): + char = " " + if i < len(player_name): + char = world.get_player_name(player)[i] + upper_char = char.upper() + if upper_char not in title_text_mapping: + for byte in title_text_mapping["."]: + player_name_bytes.append(byte) + else: + for byte in title_text_mapping[upper_char]: + player_name_bytes.append(byte) + + rom.write_bytes(0x2B7F1, player_name_bytes) # MARIO A + rom.write_bytes(0x2B726, player_name_bytes) # MARIO A + + rom.write_bytes(0x2B815, bytearray([0xFC, 0x38] * 0x10)) # MARIO B + rom.write_bytes(0x2B74A, bytearray([0xFC, 0x38] * 0x10)) # MARIO B + rom.write_bytes(0x2B839, bytearray([0x71, 0x31, 0x74, 0x31, 0x2D, 0x31, 0x84, 0x30, + 0x82, 0x30, 0x6F, 0x31, 0x73, 0x31, 0x70, 0x31, + 0x71, 0x31, 0x75, 0x31, 0x83, 0x30, 0xFC, 0x38, + 0xFC, 0x38, 0xFC, 0x38, 0xFC, 0x38, 0xFC, 0x38])) # MARIO C + rom.write_bytes(0x2B76E, bytearray([0xFC, 0x38] * 0x10)) # MARIO C + rom.write_bytes(0x2B79E, bytearray([0xFC, 0x38] * 0x05)) # EMPTY + rom.write_bytes(0x2B7AE, bytearray([0xFC, 0x38] * 0x05)) # EMPTY + rom.write_bytes(0x2B8A8, bytearray([0xFC, 0x38] * 0x0D)) # 2 PLAYER GAME + + rom.write_bytes(0x2B85D, bytearray([0xFC, 0x38] * 0x0A)) # ERASE + + rom.write_bytes(0x2B88E, bytearray([0x2C, 0x31, 0x73, 0x31, 0x75, 0x31, 0x82, 0x30, 0x30, 0x31, 0xFC, 0x38, 0x31, 0x31, 0x73, 0x31, + 0x73, 0x31, 0x7C, 0x30, 0xFC, 0x38, 0xFC, 0x38, 0xFC, 0x38])) # 1 Player Game + + rom.write_bytes(0x2B6D7, bytearray([0xFC, 0x38, 0xFC, 0x38, 0x16, 0x38, 0x18, 0x38, 0x0D, 0x38, 0xFC, 0x38, 0x0B, 0x38, 0x22, 0x38, + 0xFC, 0x38, 0x19, 0x38, 0x18, 0x38, 0x1B, 0x38, 0x22, 0x38, 0x10, 0x38, 0x18, 0x38, 0x17, 0x38, + 0x0E, 0x38, 0xFC, 0x38, 0xFC, 0x38])) # Mod by PoryGone + + # Title Options + rom.write_bytes(0x1E6A, bytearray([0x01])) + rom.write_bytes(0x1E6C, bytearray([0x01])) + rom.write_bytes(0x1E6E, bytearray([0x01])) + + # Always allow Start+Select + rom.write_bytes(0x2267, bytearray([0xEA, 0xEA])) + + # Always bring up save prompt on beating a level + if world.autosave[player]: + rom.write_bytes(0x20F93, bytearray([0x00])) + + # Starting Life Count + rom.write_bytes(0x1E25, bytearray([world.starting_life_count[player].value - 1])) + + # Repurpose Bonus Stars counter for Boss Token or Yoshi Eggs + rom.write_bytes(0x3F1AA, bytearray([0x00] * 0x20)) + rom.write_bytes(0x20F9F, bytearray([0xEA] * 0x3B)) + + # Prevent Switch Palaces setting the Switch Palace flags + rom.write_bytes(0x6EC9A, bytearray([0xEA, 0xEA])) + rom.write_bytes(0x6EB1, bytearray([0xEA, 0xEA])) + rom.write_bytes(0x6EB4, bytearray([0xEA, 0xEA, 0xEA])) + + handle_ability_code(rom) + + handle_yoshi_box(rom) + handle_bowser_damage(rom) + + handle_collected_paths(rom) + + # Handle Level Shuffle + handle_level_shuffle(rom, active_level_dict) + + # Handle Music Shuffle + if world.music_shuffle[player] != "none": + handle_music_shuffle(rom, world, player) + + generate_shuffled_header_data(rom, world, player) + + if world.swap_donut_gh_exits[player]: + handle_swap_donut_gh_exits(rom) + + handle_mario_palette(rom, world, player) + + # Store all relevant option results in ROM + rom.write_byte(0x01BFA0, world.goal[player].value) + rom.write_byte(0x01BFA1, world.bosses_required[player].value) + required_yoshi_eggs = max(math.floor( + world.number_of_yoshi_eggs[player].value * (world.percentage_of_yoshi_eggs[player].value / 100.0)), 1) + rom.write_byte(0x01BFA2, required_yoshi_eggs) + #rom.write_byte(0x01BFA3, world.display_sent_item_popups[player].value) + rom.write_byte(0x01BFA4, world.display_received_item_popups[player].value) + rom.write_byte(0x01BFA5, world.death_link[player].value) + rom.write_byte(0x01BFA6, world.dragon_coin_checks[player].value) + rom.write_byte(0x01BFA7, world.swap_donut_gh_exits[player].value) + + + from Main import __version__ + rom.name = bytearray(f'SMW{__version__.replace(".", "")[0:3]}_{player}_{world.seed:11}\0', 'utf8')[:21] + rom.name.extend([0] * (21 - len(rom.name))) + rom.write_bytes(0x7FC0, rom.name) + + +def get_base_rom_bytes(file_name: str = "") -> bytes: + base_rom_bytes = getattr(get_base_rom_bytes, "base_rom_bytes", None) + if not base_rom_bytes: + file_name = get_base_rom_path(file_name) + base_rom_bytes = bytes(read_rom(open(file_name, "rb"))) + + basemd5 = hashlib.md5() + basemd5.update(base_rom_bytes) + if USHASH != basemd5.hexdigest(): + raise Exception('Supplied Base Rom does not match known MD5 for US(1.0) release. ' + 'Get the correct game and version, then dump it') + get_base_rom_bytes.base_rom_bytes = base_rom_bytes + return base_rom_bytes + +def get_base_rom_path(file_name: str = "") -> str: + options = Utils.get_options() + if not file_name: + file_name = options["smw_options"]["rom_file"] + if not os.path.exists(file_name): + file_name = Utils.local_path(file_name) + return file_name diff --git a/worlds/smw/Rules.py b/worlds/smw/Rules.py new file mode 100644 index 0000000000..bf9fedb805 --- /dev/null +++ b/worlds/smw/Rules.py @@ -0,0 +1,20 @@ +import math + +from BaseClasses import MultiWorld +from .Names import LocationName, ItemName +from ..AutoWorld import LogicMixin +from ..generic.Rules import add_rule, set_rule + + +def set_rules(world: MultiWorld, player: int): + + if world.goal[player] == "yoshi_egg_hunt": + required_yoshi_eggs = max(math.floor( + world.number_of_yoshi_eggs[player].value * (world.percentage_of_yoshi_eggs[player].value / 100.0)), 1) + + add_rule(world.get_location(LocationName.yoshis_house, player), + lambda state: state.has(ItemName.yoshi_egg, player, required_yoshi_eggs)) + else: + add_rule(world.get_location(LocationName.bowser, player), lambda state: state.has(ItemName.mario_carry, player)) + + world.completion_condition[player] = lambda state: state.has(ItemName.victory, player) diff --git a/worlds/smw/__init__.py b/worlds/smw/__init__.py new file mode 100644 index 0000000000..46ce4de7ae --- /dev/null +++ b/worlds/smw/__init__.py @@ -0,0 +1,253 @@ +import os +import typing +import math +import threading + +from BaseClasses import Item, MultiWorld, Tutorial, ItemClassification +from .Items import SMWItem, ItemData, item_table +from .Locations import SMWLocation, all_locations, setup_locations +from .Options import smw_options +from .Regions import create_regions, connect_regions +from .Levels import full_level_list, generate_level_list, location_id_to_level_id +from .Rules import set_rules +from ..generic.Rules import add_rule +from .Names import ItemName, LocationName +from ..AutoWorld import WebWorld, World +from .Rom import LocalRom, patch_rom, get_base_rom_path, SMWDeltaPatch +import Patch + + +class SMWWeb(WebWorld): + theme = "grass" + + setup_en = Tutorial( + "Multiworld Setup Guide", + "A guide to setting up the Super Mario World randomizer connected to an Archipelago Multiworld.", + "English", + "setup_en.md", + "setup/en", + ["PoryGone"] + ) + + tutorials = [setup_en] + + +class SMWWorld(World): + """ + Super Mario World is an action platforming game. + The Princess has been kidnapped by Bowser again, but Mario has somehow + lost all of his abilities. Can he get them back in time to save the Princess? + """ + game: str = "Super Mario World" + option_definitions = smw_options + topology_present = False + data_version = 1 + required_client_version = (0, 3, 5) + + item_name_to_id = {name: data.code for name, data in item_table.items()} + location_name_to_id = all_locations + + active_level_dict: typing.Dict[int,int] + web = SMWWeb() + + def __init__(self, world: MultiWorld, player: int): + self.rom_name_available_event = threading.Event() + super().__init__(world, player) + + @classmethod + def stage_assert_generate(cls, world): + rom_file = get_base_rom_path() + if not os.path.exists(rom_file): + raise FileNotFoundError(rom_file) + + def _get_slot_data(self): + return { + #"death_link": self.world.death_link[self.player].value, + "active_levels": self.active_level_dict, + } + + def _create_items(self, name: str): + data = item_table[name] + return [self.create_item(name)] * data.quantity + + def fill_slot_data(self) -> dict: + slot_data = self._get_slot_data() + for option_name in smw_options: + option = getattr(self.world, option_name)[self.player] + slot_data[option_name] = option.value + + return slot_data + + def generate_basic(self): + itempool: typing.List[SMWItem] = [] + + self.active_level_dict = dict(zip(generate_level_list(self.world, self.player), full_level_list)) + self.topology_present = self.world.level_shuffle[self.player] + + connect_regions(self.world, self.player, self.active_level_dict) + + # Add Boss Token amount requirements for Worlds + add_rule(self.world.get_region(LocationName.donut_plains_1_tile, self.player).entrances[0], lambda state: state.has(ItemName.koopaling, self.player, 1)) + add_rule(self.world.get_region(LocationName.vanilla_dome_1_tile, self.player).entrances[0], lambda state: state.has(ItemName.koopaling, self.player, 2)) + add_rule(self.world.get_region(LocationName.forest_of_illusion_1_tile, self.player).entrances[0], lambda state: state.has(ItemName.koopaling, self.player, 4)) + add_rule(self.world.get_region(LocationName.chocolate_island_1_tile, self.player).entrances[0], lambda state: state.has(ItemName.koopaling, self.player, 5)) + add_rule(self.world.get_region(LocationName.valley_of_bowser_1_tile, self.player).entrances[0], lambda state: state.has(ItemName.koopaling, self.player, 6)) + + total_required_locations = 96 + if self.world.dragon_coin_checks[self.player]: + total_required_locations += 49 + + itempool += [self.create_item(ItemName.mario_run)] + itempool += [self.create_item(ItemName.mario_carry)] + itempool += [self.create_item(ItemName.mario_swim)] + itempool += [self.create_item(ItemName.mario_spin_jump)] + itempool += [self.create_item(ItemName.mario_climb)] + itempool += [self.create_item(ItemName.yoshi_activate)] + itempool += [self.create_item(ItemName.p_switch)] + itempool += [self.create_item(ItemName.p_balloon)] + itempool += [self.create_item(ItemName.super_star_active)] + itempool += [self.create_item(ItemName.progressive_powerup)] * 3 + itempool += [self.create_item(ItemName.yellow_switch_palace)] + itempool += [self.create_item(ItemName.green_switch_palace)] + itempool += [self.create_item(ItemName.red_switch_palace)] + itempool += [self.create_item(ItemName.blue_switch_palace)] + + if self.world.goal[self.player] == "yoshi_egg_hunt": + itempool += [self.create_item(ItemName.yoshi_egg)] * self.world.number_of_yoshi_eggs[self.player] + self.world.get_location(LocationName.yoshis_house, self.player).place_locked_item(self.create_item(ItemName.victory)) + else: + self.world.get_location(LocationName.bowser, self.player).place_locked_item(self.create_item(ItemName.victory)) + + junk_count = total_required_locations - len(itempool) + trap_weights = [] + trap_weights += ([ItemName.ice_trap] * self.world.ice_trap_weight[self.player].value) + trap_weights += ([ItemName.stun_trap] * self.world.stun_trap_weight[self.player].value) + trap_weights += ([ItemName.literature_trap] * self.world.literature_trap_weight[self.player].value) + trap_count = 0 if (len(trap_weights) == 0) else math.ceil(junk_count * (self.world.trap_fill_percentage[self.player].value / 100.0)) + junk_count -= trap_count + + trap_pool = [] + for i in range(trap_count): + trap_item = self.world.random.choice(trap_weights) + trap_pool += [self.create_item(trap_item)] + + itempool += trap_pool + + itempool += [self.create_item(ItemName.one_up_mushroom)] * junk_count + + boss_location_names = [LocationName.yoshis_island_koopaling, LocationName.donut_plains_koopaling, LocationName.vanilla_dome_koopaling, + LocationName.twin_bridges_koopaling, LocationName.forest_koopaling, LocationName.chocolate_koopaling, + LocationName.valley_koopaling, LocationName.vanilla_reznor, LocationName.forest_reznor, LocationName.chocolate_reznor, LocationName.valley_reznor] + + for location_name in boss_location_names: + self.world.get_location(location_name, self.player).place_locked_item(self.create_item(ItemName.koopaling)) + + self.world.itempool += itempool + + + def generate_output(self, output_directory: str): + try: + world = self.world + player = self.player + + rom = LocalRom(get_base_rom_path()) + patch_rom(self.world, rom, self.player, self.active_level_dict) + + outfilepname = f'_P{player}' + outfilepname += f"_{world.player_name[player].replace(' ', '_')}" \ + if world.player_name[player] != 'Player%d' % player else '' + + rompath = os.path.join(output_directory, f'AP_{world.seed_name}{outfilepname}.sfc') + rom.write_to_file(rompath) + self.rom_name = rom.name + + patch = SMWDeltaPatch(os.path.splitext(rompath)[0]+SMWDeltaPatch.patch_file_ending, player=player, + player_name=world.player_name[player], patched_path=rompath) + patch.write() + except: + raise + finally: + if os.path.exists(rompath): + os.unlink(rompath) + self.rom_name_available_event.set() # make sure threading continues and errors are collected + + def modify_multidata(self, multidata: dict): + import base64 + # wait for self.rom_name to be available. + self.rom_name_available_event.wait() + rom_name = getattr(self, "rom_name", None) + # we skip in case of error, so that the original error in the output thread is the one that gets raised + if rom_name: + new_name = base64.b64encode(bytes(self.rom_name)).decode() + multidata["connect_names"][new_name] = multidata["connect_names"][self.world.player_name[self.player]] + + def extend_hint_information(self, hint_data: typing.Dict[int, typing.Dict[int, str]]): + if self.topology_present: + world_names = [ + LocationName.yoshis_island_region, + LocationName.donut_plains_region, + LocationName.vanilla_dome_region, + LocationName.twin_bridges_region, + LocationName.forest_of_illusion_region, + LocationName.chocolate_island_region, + LocationName.valley_of_bowser_region, + LocationName.star_road_region, + LocationName.special_zone_region, + ] + world_cutoffs = [ + 0x07, + 0x13, + 0x1F, + 0x26, + 0x30, + 0x39, + 0x44, + 0x4F, + 0x59 + ] + er_hint_data = {} + for loc_name, level_data in location_id_to_level_id.items(): + level_id = level_data[0] + + if level_id not in self.active_level_dict: + continue + + keys_list = list(self.active_level_dict.keys()) + level_index = keys_list.index(level_id) + for i in range(len(world_cutoffs)): + if level_index >= world_cutoffs[i]: + continue + + if self.world.dragon_coin_checks[self.player].value == 0 and "Dragon Coins" in loc_name: + continue + + location = self.world.get_location(loc_name, self.player) + er_hint_data[location.address] = world_names[i] + break + + hint_data[self.player] = er_hint_data + + def create_regions(self): + location_table = setup_locations(self.world, self.player) + create_regions(self.world, self.player, location_table) + + def create_item(self, name: str, force_non_progression=False) -> Item: + data = item_table[name] + + if force_non_progression: + classification = ItemClassification.filler + elif name == ItemName.yoshi_egg: + classification = ItemClassification.progression_skip_balancing + elif data.progression: + classification = ItemClassification.progression + elif data.trap: + classification = ItemClassification.trap + else: + classification = ItemClassification.filler + + created_item = SMWItem(name, classification, data.code, self.player) + + return created_item + + def set_rules(self): + set_rules(self.world, self.player) diff --git a/worlds/smw/docs/en_Super Mario World.md b/worlds/smw/docs/en_Super Mario World.md new file mode 100644 index 0000000000..87a96e558b --- /dev/null +++ b/worlds/smw/docs/en_Super Mario World.md @@ -0,0 +1,43 @@ +# Super Mario World + +## Where is the settings page? + +The [player settings page for this game](../player-settings) contains all the options you need to configure and export a config file. + +## What does randomization do to this game? + +Mario's basic abilities are removed, and placed into the item pool as items that any player can find. This includes: +- Carry +- Climb +- Run +- P-Switch +- Swim +- Spin Jump +- Yoshi + +Additionally, the ability to use powerups (Mushrooms, Fire Flowers, Capes, Stars, and P-Balloons) are shuffled into the item pool, as are the Four Switch Palaces. + +## What is the goal of Super Mario World when randomized? + +There are two goals which can be chosen: +- `Bowser`: Reach Bowser's Castle and defeat Bowser, after defeating a certain number of bosses. +- `Yoshi Egg Hunt`: Collect a certain number of Yoshi Eggs, then return to Yoshi's House + +## What items and locations get shuffled? + +Each unique level exit awards a location check. Optionally, collecting five Dragon Coins in each level can also award a location check. +Mario's various abilities and powerups as described above are placed into the item pool. +If the player is playing Yoshi Egg Hunt, a certain number of Yoshi Eggs will be placed into the item pool. +Any additional items that are needed to fill out the item pool with be 1-Up Mushrooms. + +## Which items can be in another player's world? + +Any shuffled item can be in other players' worlds. + +## What does another world's item look like in Super Mario World + +Items do not have an appearance in Super Mario World. + +## When the player receives an item, what happens? + +The player can choose to receive a text box in-game when they receive an item. Regardless of that choice, items will be queued, and granted when the player next enters a level. diff --git a/worlds/smw/docs/setup_en.md b/worlds/smw/docs/setup_en.md new file mode 100644 index 0000000000..178b7392b7 --- /dev/null +++ b/worlds/smw/docs/setup_en.md @@ -0,0 +1,149 @@ +# Super Mario World Randomizer Setup Guide + +## Required Software + +- [Archipelago](https://github.com/ArchipelagoMW/Archipelago/releases). Make sure to check the box for `SNI Client - Super Mario World Patch Setup` + + +- Hardware or software capable of loading and playing SNES ROM files + - An emulator capable of connecting to SNI such as: + - snes9x-rr from: [snes9x rr](https://github.com/gocha/snes9x-rr/releases), + - BizHawk from: [BizHawk Website](http://tasvideos.org/BizHawk.html) + - RetroArch 1.10.3 or newer from: [RetroArch Website](https://retroarch.com?page=platforms). Or, + - An SD2SNES, FXPak Pro ([FXPak Pro Store Page](https://krikzz.com/store/home/54-fxpak-pro.html)), or other + compatible hardware +- Your legally obtained Super Mario World ROM file, probably named `Super Mario World (USA).sfc` + +## Installation Procedures + +### Windows Setup + +1. During the installation of Archipelago, you will have been asked to install the SNI Client. If you did not do this, + or you are on an older version, you may run the installer again to install the SNI Client. +2. During setup, you will be asked to locate your base ROM file. This is your Super Mario World ROM file. +3. If you are using an emulator, you should assign your Lua capable emulator as your default program for launching ROM + files. + 1. Extract your emulator's folder to your Desktop, or somewhere you will remember. + 2. Right-click on a ROM file and select **Open with...** + 3. Check the box next to **Always use this app to open .sfc files** + 4. Scroll to the bottom of the list and click the grey text **Look for another App on this PC** + 5. Browse for your emulator's `.exe` file and click **Open**. This file should be located inside the folder you + extracted in step one. + +## Create a Config (.yaml) File + +### What is a config file and why do I need one? + +See the guide on setting up a basic YAML at the Archipelago setup +guide: [Basic Multiworld Setup Guide](/tutorial/Archipelago/setup/en) + +### Where do I get a config file? + +The Player Settings page on the website allows you to configure your personal settings and export a config file from +them. Player settings page: [Super Mario World Player Settings Page](/games/Super%20Mario%20World/player-settings) + +### Verifying your config file + +If you would like to validate your config file to make sure it works, you may do so on the YAML Validator page. YAML +validator page: [YAML Validation page](/mysterycheck) + +## Joining a MultiWorld Game + +### Obtain your patch file and create your ROM + +When you join a multiworld game, you will be asked to provide your config file to whomever is hosting. Once that is done, +the host will provide you with either a link to download your patch file, or with a zip file containing everyone's patch +files. Your patch file should have a `.apsmw` extension. + +Put your patch file on your desktop or somewhere convenient, and double click it. This should automatically launch the +client, and will also create your ROM in the same place as your patch file. + +### Connect to the client + +#### With an emulator + +When the client launched automatically, SNI should have also automatically launched in the background. If this is its +first time launching, you may be prompted to allow it to communicate through the Windows Firewall. + +##### snes9x-rr + +1. Load your ROM file if it hasn't already been loaded. +2. Click on the File menu and hover on **Lua Scripting** +3. Click on **New Lua Script Window...** +4. In the new window, click **Browse...** +5. Select the connector lua file included with your client + - Look in the Archipelago folder for `/SNI/lua/x64` or `/SNI/lua/x86` depending on if the + emulator is 64-bit or 32-bit. +6. If you see an error while loading the script that states `socket.dll missing` or similar, navigate to the folder of +the lua you are using in your file explorer and copy the `socket.dll` to the base folder of your snes9x install. + +##### BizHawk + +1. Ensure you have the BSNES core loaded. You may do this by clicking on the Tools menu in BizHawk and following these + menu options: + `Config --> Cores --> SNES --> BSNES` + Once you have changed the loaded core, you must restart BizHawk. +2. Load your ROM file if it hasn't already been loaded. +3. Click on the Tools menu and click on **Lua Console** +4. Click the button to open a new Lua script. +5. Select the `Connector.lua` file included with your client + - Look in the Archipelago folder for `/SNI/lua/x64` or `/SNI/lua/x86` depending on if the + emulator is 64-bit or 32-bit. Please note the most recent versions of BizHawk are 64-bit only. + +##### RetroArch 1.10.3 or newer + +You only have to do these steps once. Note, RetroArch 1.9.x will not work as it is older than 1.10.3. + +1. Enter the RetroArch main menu screen. +2. Go to Settings --> User Interface. Set "Show Advanced Settings" to ON. +3. Go to Settings --> Network. Set "Network Commands" to ON. (It is found below Request Device 16.) Leave the default + Network Command Port at 55355. + +![Screenshot of Network Commands setting](/static/generated/docs/A%20Link%20to%20the%20Past/retroarch-network-commands-en.png) +4. Go to Main Menu --> Online Updater --> Core Downloader. Scroll down and select "Nintendo - SNES / SFC (bsnes-mercury + Performance)". + +When loading a ROM, be sure to select a **bsnes-mercury** core. These are the only cores that allow external tools to +read ROM data. + +#### With hardware + +This guide assumes you have downloaded the correct firmware for your device. If you have not done so already, please do +this now. SD2SNES and FXPak Pro users may download the appropriate firmware on the SD2SNES releases page. SD2SNES +releases page: [SD2SNES Releases Page](https://github.com/RedGuyyyy/sd2snes/releases) + +Other hardware may find helpful information on the usb2snes platforms +page: [usb2snes Supported Platforms Page](http://usb2snes.com/#supported-platforms) + +1. Close your emulator, which may have auto-launched. +2. Power on your device and load the ROM. + +### Connect to the Archipelago Server + +The patch file which launched your client should have automatically connected you to the AP Server. There are a few +reasons this may not happen however, including if the game is hosted on the website but was generated elsewhere. If the +client window shows "Server Status: Not Connected", simply ask the host for the address of the server, and copy/paste it +into the "Server" input field then press enter. + +The client will attempt to reconnect to the new server address, and should momentarily show "Server Status: Connected". + +### Play the game + +When the client shows both SNES Device and Server as connected, you're ready to begin playing. Congratulations on +successfully joining a multiworld game! + +## Hosting a MultiWorld game + +The recommended way to host a game is to use our hosting service. The process is relatively simple: + +1. Collect config files from your players. +2. Create a zip file containing your players' config files. +3. Upload that zip file to the Generate page above. + - Generate page: [WebHost Seed Generation Page](/generate) +4. Wait a moment while the seed is generated. +5. When the seed is generated, you will be redirected to a "Seed Info" page. +6. Click "Create New Room". This will take you to the server page. Provide the link to this page to your players, so + they may download their patch files from there. +7. Note that a link to a MultiWorld Tracker is at the top of the room page. The tracker shows the progress of all + players in the game. Any observers may also be given the link to this page. +8. Once all players have joined, you may begin playing. From dd7d3a02a4cf6af1bb5a7dffb71feeb32d960366 Mon Sep 17 00:00:00 2001 From: Jarno Date: Thu, 29 Sep 2022 20:18:21 +0200 Subject: [PATCH 07/18] WebHost: Fixed Oculus Ring from showing up on tracker (#1065) --- WebHostLib/templates/timespinnerTracker.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WebHostLib/templates/timespinnerTracker.html b/WebHostLib/templates/timespinnerTracker.html index bd589e1068..82565316ab 100644 --- a/WebHostLib/templates/timespinnerTracker.html +++ b/WebHostLib/templates/timespinnerTracker.html @@ -41,7 +41,7 @@ {% endif %} - {% if 'FacebookMode' in options %} + {% if 'EyeSpy' in options %} {% else %} From bee1fd9b5acb4f94fd9f8a0742b59b808ea26727 Mon Sep 17 00:00:00 2001 From: recklesscoder <57289227+recklesscoder@users.noreply.github.com> Date: Thu, 29 Sep 2022 21:04:04 +0200 Subject: [PATCH 08/18] Subnautica: Updated Setup Guide (#1062) - Added sections for console commands and known issues. - Updated "Resuming" section to reflect current functionality. - Removed implication that one might have to create the QMods folder. (If it's missing, then you've already messed up step 1.) - Renamed "Connect Menu" to "connect form" to be less confusing. Generally fixed word capitalization to conform to standard English. Minor wording changes. --- worlds/subnautica/docs/setup_en.md | 43 +++++++++++++++++++----------- 1 file changed, 28 insertions(+), 15 deletions(-) diff --git a/worlds/subnautica/docs/setup_en.md b/worlds/subnautica/docs/setup_en.md index 665cb8b336..bd4a92cc7c 100644 --- a/worlds/subnautica/docs/setup_en.md +++ b/worlds/subnautica/docs/setup_en.md @@ -7,37 +7,50 @@ - Archipelago Mod for Subnautica from: [Subnautica Archipelago Mod Releases Page](https://github.com/Berserker66/ArchipelagoSubnauticaModSrc/releases) -## Installation Procedures +## Installation Procedure 1. Install QModManager4 as per its instructions. -2. The folder you installed QModManager4 into will now have a /QMods directory. It might appear after a start of - Subnautica. You can also create this folder yourself. +2. The Subnautica game directory should now contain a `QMods` folder. Unpack the Archipelago Mod into this folder, so that `Subnautica/QMods/Archipelago/` is a valid path. -3. Unpack the Archipelago Mod into this folder, so that Subnautica/QMods/Archipelago/ is a valid path. - -4. Start Subnautica. You should see a Connect Menu in the topleft of your main Menu. +3. Start Subnautica. You should see a connect form with three text boxes in the top left of your main menu. ## Connecting -Using the Connect Menu in Subnautica's Main Menu you enter your connection info to connect to an Archipelago Multiworld. -Menu points: +Use the connect form in Subnautica's main menu to enter your connection information to connect to an Archipelago multiworld. +Connection information consists of: - Host: the full url that you're trying to connect to, such as `archipelago.gg:38281`. - - PlayerName: your name in the multiworld. Can also be called Slot Name and is the name you entered when creating your settings. + - PlayerName: your name in the multiworld. Can also be called "slot name" and is the name you entered when creating your settings. - Password: optional password, leave blank if no password was set. After the connection is made, start a new game. You should start to see Archipelago chat messages to appear, such as a message announcing that you joined the multiworld. ## Resuming -When loading a savegame it will automatically attempt to resume the connection that was active when the savegame was made. -If that connection information is no longer valid, such as if the server's IP and/or port has changed, the Connect Menu will reappear after loading. Use the Connect Menu before or after loading the savegame to connect to the new instance. +Savegames store their connection information and automatically attempt to reestablish the connection upon loading. +If the connection information is no longer valid, such as if the server's IP and/or port have changed, +you need to use the connect form on the main menu beforehand. -Warning: Currently it is not checked if this is the correct multiworld belonging to that savegame, please ensure that yourself beforehand. +Warning: Currently it is not checked whether a loaded savegame belongs to the multiworld you are connecting to. Please ensure that yourself beforehand. + +## Console Commands + +The mod adds the following console commands: + - `silent` toggles Archipelago chat messages appearing. + - `deathlink` toggles death link. + +To enable the console in Subnautica, press `F3` and `F8`, then uncheck "Disable Console" in the top left. Press `F3` and `F8` again to close the menus. +To enter a console command, press `Enter`. + +## Known Issues + +- Do not attempt playing vanilla saves while the mod is installed, as the mod will override the scan information of the savegame. +- When exiting to the main menu the mod's state is not properly reset. Loading a savegame from here will break various things. + If you want to reload a save it is recommended you restart the game entirely. +- Attempting to load a savegame containing no longer valid connection information without entering valid information on the main menu will hang on the loading screen. ## Troubleshooting -If you don't see the Connect Menu within the Main Menu, check that you see a file named `qmodmanager_log-Subnautica.txt` in the Subnautica game directory. If not, -QModManager4 is not correctly installed, otherwise open it and look -for `[Info : BepInEx] Loading [Archipelago`. If it doesn't show this, then +If you don't see the connect form on the main menu screen, check whether you see a file named `qmodmanager_log-Subnautica.txt` in the Subnautica game directory. If not, +QModManager4 is not correctly installed, otherwise open it and look for `Loading [Archipelago`. If the file doesn't contain this text, then QModManager4 didn't find the Archipelago mod, so check your paths. From 0191df88d78830dd02ed2e37e3927c248995841a Mon Sep 17 00:00:00 2001 From: black-sliver <59490463+black-sliver@users.noreply.github.com> Date: Sun, 25 Sep 2022 15:21:39 +0200 Subject: [PATCH 09/18] Doc: network protocol: clarify want_reply --- docs/network protocol.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/network protocol.md b/docs/network protocol.md index d5c56a62b4..0e7a53f3cf 100644 --- a/docs/network protocol.md +++ b/docs/network protocol.md @@ -371,7 +371,7 @@ Used to write data to the server's data storage, that data can then be shared ac | ------ | ----- | ------ | | key | str | The key to manipulate. | | default | any | The default value to use in case the key has no value on the server. | -| want_reply | bool | If set, the server will send a [SetReply](#SetReply) response back to the client. | +| want_reply | bool | If true, the server will send a [SetReply](#SetReply) response back to the client. | | operations | list\[[DataStorageOperation](#DataStorageOperation)\] | Operations to apply to the value, multiple operations can be present and they will be executed in order of appearance. | Additional arguments sent in this package will also be added to the [SetReply](#SetReply) package it triggers. From d897aaade2a42205dcbb7f7cfd69c430d03ad733 Mon Sep 17 00:00:00 2001 From: Fabian Dill Date: Thu, 29 Sep 2022 23:15:12 +0200 Subject: [PATCH 10/18] Docs: Ensure Discord links are permanent. (#1064) --- WebHostLib/misc.py | 2 +- WebHostLib/static/assets/faq/faq_en.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/WebHostLib/misc.py b/WebHostLib/misc.py index 03cd03b624..6978e27c28 100644 --- a/WebHostLib/misc.py +++ b/WebHostLib/misc.py @@ -151,7 +151,7 @@ def favicon(): @app.route('/discord') def discord(): - return redirect("https://discord.gg/archipelago") + return redirect("https://discord.gg/8Z65BR2") @app.route('/datapackage') diff --git a/WebHostLib/static/assets/faq/faq_en.md b/WebHostLib/static/assets/faq/faq_en.md index cd144d7eff..6ad50a50f6 100644 --- a/WebHostLib/static/assets/faq/faq_en.md +++ b/WebHostLib/static/assets/faq/faq_en.md @@ -46,7 +46,7 @@ the website is not required to generate them. ## How do I get started? If you are ready to start randomizing games, or want to start playing your favorite randomizer with others, please join -our discord server at the [Archipelago Discord](https://discord.gg/archipelago). There are always people ready to answer +our discord server at the [Archipelago Discord](https://discord.gg/8Z65BR2). There are always people ready to answer any questions you might have. ## What are some common terms I should know? From 8ab0b410c33c108f4e02d1c2ec903830f5e2236b Mon Sep 17 00:00:00 2001 From: Yussur Mustafa Oraji Date: Thu, 29 Sep 2022 23:53:22 +0200 Subject: [PATCH 11/18] sm64ex: Document new connection status notifications --- worlds/sm64ex/docs/setup_en.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/worlds/sm64ex/docs/setup_en.md b/worlds/sm64ex/docs/setup_en.md index 1b4ae6dfb4..d77e091359 100644 --- a/worlds/sm64ex/docs/setup_en.md +++ b/worlds/sm64ex/docs/setup_en.md @@ -52,7 +52,8 @@ Optionally, add `--sm64ap_passwd "YourPassword"` if the room you are using requi The Name in this case is the one specified in your generated .yaml file. In case you are using the Archipelago Website, the IP should be `archipelago.gg`. -If everything worked out, you will see a textbox informing you the connection has been established after the story intro. +Should the connection fail (for example when using the wrong name or IP/Port combination) the game will inform you of that. +Additionally, any time the game is not connected (for example when the connection is unstable) it will attempt to reconnect and display a status text. # Playing offline From 61e39f355d2da15d58dc10557c56f56384e5527c Mon Sep 17 00:00:00 2001 From: Fabian Dill Date: Fri, 30 Sep 2022 00:36:30 +0200 Subject: [PATCH 12/18] Core remove legacy patch (#1047) Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com> --- LttPAdjuster.py | 6 +- OoTClient.py | 3 +- Patch.py | 418 ++----------------------------------- SNIClient.py | 20 +- Utils.py | 10 + WebHostLib/__init__.py | 7 +- WebHostLib/api/generate.py | 3 +- WebHostLib/downloads.py | 12 +- WebHostLib/tracker.py | 3 +- WebHostLib/upload.py | 19 +- data/basepatch.apbp | Bin 117900 -> 0 bytes data/basepatch.bsdiff4 | Bin 0 -> 114127 bytes docs/adding games.md | 4 +- worlds/Files.py | 156 ++++++++++++++ worlds/alttp/Rom.py | 48 ++--- worlds/dkc3/Rom.py | 9 +- worlds/factorio/Mod.py | 4 +- worlds/sm/Rom.py | 5 +- worlds/smw/Rom.py | 10 +- worlds/smw/__init__.py | 4 +- worlds/smz3/Rom.py | 7 +- worlds/soe/Patch.py | 2 +- 22 files changed, 259 insertions(+), 491 deletions(-) delete mode 100644 data/basepatch.apbp create mode 100644 data/basepatch.bsdiff4 create mode 100644 worlds/Files.py diff --git a/LttPAdjuster.py b/LttPAdjuster.py index 469e8920b3..9fab226c67 100644 --- a/LttPAdjuster.py +++ b/LttPAdjuster.py @@ -139,7 +139,7 @@ def adjust(args): vanillaRom = args.baserom if not os.path.exists(vanillaRom) and not os.path.isabs(vanillaRom): vanillaRom = local_path(vanillaRom) - if os.path.splitext(args.rom)[-1].lower() in {'.apbp', '.aplttp'}: + if os.path.splitext(args.rom)[-1].lower() == '.aplttp': import Patch meta, args.rom = Patch.create_rom_file(args.rom) @@ -195,7 +195,7 @@ def adjustGUI(): romEntry2 = Entry(romDialogFrame, textvariable=romVar2) def RomSelect2(): - rom = filedialog.askopenfilename(filetypes=[("Rom Files", (".sfc", ".smc", ".apbp")), ("All Files", "*")]) + rom = filedialog.askopenfilename(filetypes=[("Rom Files", (".sfc", ".smc", ".aplttp")), ("All Files", "*")]) romVar2.set(rom) romSelectButton2 = Button(romDialogFrame, text='Select Rom', command=RomSelect2) @@ -725,7 +725,7 @@ def get_rom_options_frame(parent=None): vars.auto_apply = StringVar(value=adjuster_settings.auto_apply) autoApplyFrame = Frame(romOptionsFrame) autoApplyFrame.grid(row=9, column=0, columnspan=2, sticky=W) - filler = Label(autoApplyFrame, text="Automatically apply last used settings on opening .apbp files") + filler = Label(autoApplyFrame, text="Automatically apply last used settings on opening .aplttp files") filler.pack(side=TOP, expand=True, fill=X) askRadio = Radiobutton(autoApplyFrame, text='Ask', variable=vars.auto_apply, value='ask') askRadio.pack(side=LEFT, padx=5, pady=5) diff --git a/OoTClient.py b/OoTClient.py index fbe2b35d1a..b3c58612f3 100644 --- a/OoTClient.py +++ b/OoTClient.py @@ -5,7 +5,8 @@ import multiprocessing import subprocess from asyncio import StreamReader, StreamWriter -from CommonClient import CommonContext, server_loop, gui_enabled, console_loop, \ +# CommonClient import first to trigger ModuleUpdater +from CommonClient import CommonContext, server_loop, gui_enabled, \ ClientCommandProcessor, logger, get_base_parser import Utils from worlds import network_data_package diff --git a/Patch.py b/Patch.py index 6ac75dc9dd..4ff0e9602a 100644 --- a/Patch.py +++ b/Patch.py @@ -1,274 +1,33 @@ from __future__ import annotations -import shutil -import json -import bsdiff4 # type: ignore -import yaml import os -import lzma -import threading -import concurrent.futures -import zipfile import sys -from typing import ClassVar, List, Tuple, Optional, Dict, Any, Union, BinaryIO +from typing import Tuple, Optional, TypedDict -import ModuleUpdate -ModuleUpdate.update() +if __name__ == "__main__": + import ModuleUpdate + ModuleUpdate.update() -import Utils - -current_patch_version = 5 +from worlds.Files import AutoPatchRegister, APDeltaPatch -class AutoPatchRegister(type): - patch_types: ClassVar[Dict[str, AutoPatchRegister]] = {} - file_endings: ClassVar[Dict[str, AutoPatchRegister]] = {} - - def __new__(cls, name: str, bases: Tuple[type, ...], dct: Dict[str, Any]) -> AutoPatchRegister: - # construct class - new_class = super().__new__(cls, name, bases, dct) - if "game" in dct: - AutoPatchRegister.patch_types[dct["game"]] = new_class - if not dct["patch_file_ending"]: - raise Exception(f"Need an expected file ending for {name}") - AutoPatchRegister.file_endings[dct["patch_file_ending"]] = new_class - return new_class - - @staticmethod - def get_handler(file: str) -> Optional[AutoPatchRegister]: - for file_ending, handler in AutoPatchRegister.file_endings.items(): - if file.endswith(file_ending): - return handler - return None - - -class APContainer: - """A zipfile containing at least archipelago.json""" - version: int = current_patch_version - compression_level: int = 9 - compression_method: int = zipfile.ZIP_DEFLATED - game: Optional[str] = None - - # instance attributes: - path: Optional[str] - player: Optional[int] - player_name: str - server: str - - def __init__(self, path: Optional[str] = None, player: Optional[int] = None, - player_name: str = "", server: str = ""): - self.path = path - self.player = player - self.player_name = player_name - self.server = server - - def write(self, file: Optional[Union[str, BinaryIO]] = None) -> None: - zip_file = file if file else self.path - if not zip_file: - raise FileNotFoundError(f"Cannot write {self.__class__.__name__} due to no path provided.") - with zipfile.ZipFile(zip_file, "w", self.compression_method, True, self.compression_level) \ - as zf: - if file: - self.path = zf.filename - self.write_contents(zf) - - def write_contents(self, opened_zipfile: zipfile.ZipFile) -> None: - manifest = self.get_manifest() - try: - manifest_str = json.dumps(manifest) - except Exception as e: - raise Exception(f"Manifest {manifest} did not convert to json.") from e - else: - opened_zipfile.writestr("archipelago.json", manifest_str) - - def read(self, file: Optional[Union[str, BinaryIO]] = None) -> None: - """Read data into patch object. file can be file-like, such as an outer zip file's stream.""" - zip_file = file if file else self.path - if not zip_file: - raise FileNotFoundError(f"Cannot read {self.__class__.__name__} due to no path provided.") - with zipfile.ZipFile(zip_file, "r") as zf: - if file: - self.path = zf.filename - self.read_contents(zf) - - def read_contents(self, opened_zipfile: zipfile.ZipFile) -> None: - with opened_zipfile.open("archipelago.json", "r") as f: - manifest = json.load(f) - if manifest["compatible_version"] > self.version: - raise Exception(f"File (version: {manifest['compatible_version']}) too new " - f"for this handler (version: {self.version})") - self.player = manifest["player"] - self.server = manifest["server"] - self.player_name = manifest["player_name"] - - def get_manifest(self) -> Dict[str, Any]: - return { - "server": self.server, # allow immediate connection to server in multiworld. Empty string otherwise - "player": self.player, - "player_name": self.player_name, - "game": self.game, - # minimum version of patch system expected for patching to be successful - "compatible_version": 4, - "version": current_patch_version, - } - - -class APDeltaPatch(APContainer, metaclass=AutoPatchRegister): - """An APContainer that additionally has delta.bsdiff4 - containing a delta patch to get the desired file, often a rom.""" - - hash: Optional[str] # base checksum of source file - patch_file_ending: str = "" - delta: Optional[bytes] = None - result_file_ending: str = ".sfc" - source_data: bytes - - def __init__(self, *args: Any, patched_path: str = "", **kwargs: Any) -> None: - self.patched_path = patched_path - super(APDeltaPatch, self).__init__(*args, **kwargs) - - def get_manifest(self) -> Dict[str, Any]: - manifest = super(APDeltaPatch, self).get_manifest() - manifest["base_checksum"] = self.hash - manifest["result_file_ending"] = self.result_file_ending - manifest["patch_file_ending"] = self.patch_file_ending - return manifest - - @classmethod - def get_source_data(cls) -> bytes: - """Get Base data""" - raise NotImplementedError() - - @classmethod - def get_source_data_with_cache(cls) -> bytes: - if not hasattr(cls, "source_data"): - cls.source_data = cls.get_source_data() - return cls.source_data - - def write_contents(self, opened_zipfile: zipfile.ZipFile): - super(APDeltaPatch, self).write_contents(opened_zipfile) - # write Delta - opened_zipfile.writestr("delta.bsdiff4", - bsdiff4.diff(self.get_source_data_with_cache(), open(self.patched_path, "rb").read()), - compress_type=zipfile.ZIP_STORED) # bsdiff4 is a format with integrated compression - - def read_contents(self, opened_zipfile: zipfile.ZipFile): - super(APDeltaPatch, self).read_contents(opened_zipfile) - self.delta = opened_zipfile.read("delta.bsdiff4") - - def patch(self, target: str): - """Base + Delta -> Patched""" - if not self.delta: - self.read() - result = bsdiff4.patch(self.get_source_data_with_cache(), self.delta) - with open(target, "wb") as f: - f.write(result) - - -# legacy patch handling follows: GAME_ALTTP = "A Link to the Past" GAME_SM = "Super Metroid" GAME_SOE = "Secret of Evermore" GAME_SMZ3 = "SMZ3" GAME_DKC3 = "Donkey Kong Country 3" + GAME_SMW = "Super Mario World" -supported_games = {"A Link to the Past", "Super Metroid", "Secret of Evermore", "SMZ3", "Donkey Kong Country 3"} - -preferred_endings = { - GAME_ALTTP: "apbp", - GAME_SM: "apm3", - GAME_SOE: "apsoe", - GAME_SMZ3: "apsmz", - GAME_DKC3: "apdkc3" -} -def generate_yaml(patch: bytes, metadata: Optional[dict] = None, game: str = GAME_ALTTP) -> bytes: - if game == GAME_ALTTP: - from worlds.alttp.Rom import LTTPJPN10HASH as HASH - elif game == GAME_SM: - from worlds.sm.Rom import SMJUHASH as HASH - elif game == GAME_SOE: - from worlds.soe.Patch import USHASH as HASH - elif game == GAME_SMZ3: - from worlds.alttp.Rom import LTTPJPN10HASH as ALTTPHASH - from worlds.sm.Rom import SMJUHASH as SMHASH - HASH = ALTTPHASH + SMHASH - elif game == GAME_DKC3: - from worlds.dkc3.Rom import USHASH as HASH - else: - raise RuntimeError(f"Selected game {game} for base rom not found.") - patch = yaml.dump({"meta": metadata, - "patch": patch, - "game": game, - # minimum version of patch system expected for patching to be successful - "compatible_version": 3, - "version": current_patch_version, - "base_checksum": HASH}) - return patch.encode(encoding="utf-8-sig") +class RomMeta(TypedDict): + server: str + player: Optional[int] + player_name: str -def generate_patch(rom: bytes, metadata: Optional[Dict[str, Any]] = None, game: str = GAME_ALTTP) -> bytes: - if metadata is None: - metadata = {} - patch = bsdiff4.diff(get_base_rom_data(game), rom) - return generate_yaml(patch, metadata, game) - - -def create_patch_file(rom_file_to_patch: str, - server: str = "", - destination: Optional[str] = None, - player: int = 0, - player_name: str = "", - game: str = GAME_ALTTP) -> str: - meta = {"server": server, # allow immediate connection to server in multiworld. Empty string otherwise - "player_id": player, - "player_name": player_name} - bytes = generate_patch(load_bytes(rom_file_to_patch), - meta, - game) - target = destination if destination else os.path.splitext(rom_file_to_patch)[0] + ( - ".apbp" if game == GAME_ALTTP - else ".apsmz" if game == GAME_SMZ3 - else ".apdkc3" if game == GAME_DKC3 - else ".apm3") - write_lzma(bytes, target) - return target - - -def create_rom_bytes(patch_file: str, ignore_version: bool = False) -> Tuple[Dict[str, Any], str, bytearray]: - data = Utils.parse_yaml(lzma.decompress(load_bytes(patch_file)).decode("utf-8-sig")) - game_name = data["game"] - if not ignore_version and data["compatible_version"] > current_patch_version: - raise RuntimeError("Patch file is incompatible with this patcher, likely an update is required.") - patched_data: bytearray = bsdiff4.patch(get_base_rom_data(game_name), data["patch"]) - rom_hash = patched_data[int(0x7FC0):int(0x7FD5)] - data["meta"]["hash"] = "".join(chr(x) for x in rom_hash) - target = os.path.splitext(patch_file)[0] + ".sfc" - return data["meta"], target, patched_data - - -def get_base_rom_data(game: str) -> bytes: - if game == GAME_ALTTP: - from worlds.alttp.Rom import get_base_rom_bytes - elif game == "alttp": # old version for A Link to the Past - from worlds.alttp.Rom import get_base_rom_bytes - elif game == GAME_SM: - from worlds.sm.Rom import get_base_rom_bytes - elif game == GAME_SOE: - from worlds.soe.Patch import get_base_rom_path - get_base_rom_bytes = lambda: bytes(read_rom(open(get_base_rom_path(), "rb"))) - elif game == GAME_SMZ3: - from worlds.smz3.Rom import get_base_rom_bytes - elif game == GAME_DKC3: - from worlds.dkc3.Rom import get_base_rom_bytes - else: - raise RuntimeError("Selected game for base rom not found.") - return get_base_rom_bytes() - - -def create_rom_file(patch_file: str) -> Tuple[Dict[str, Any], str]: +def create_rom_file(patch_file: str) -> Tuple[RomMeta, str]: auto_handler = AutoPatchRegister.get_handler(patch_file) if auto_handler: handler: APDeltaPatch = auto_handler(patch_file) @@ -277,157 +36,10 @@ def create_rom_file(patch_file: str) -> Tuple[Dict[str, Any], str]: return {"server": handler.server, "player": handler.player, "player_name": handler.player_name}, target - else: - data, target, patched_data = create_rom_bytes(patch_file) - with open(target, "wb") as f: - f.write(patched_data) - return data, target - - -def update_patch_data(patch_data: bytes, server: str = "") -> bytes: - data = Utils.parse_yaml(lzma.decompress(patch_data).decode("utf-8-sig")) - data["meta"]["server"] = server - bytes = generate_yaml(data["patch"], data["meta"], data["game"]) - return lzma.compress(bytes) - - -def load_bytes(path: str) -> bytes: - with open(path, "rb") as f: - return f.read() - - -def write_lzma(data: bytes, path: str): - with lzma.LZMAFile(path, 'wb') as f: - f.write(data) - - -def read_rom(stream: BinaryIO, strip_header: bool = True) -> bytearray: - """Reads rom into bytearray and optionally strips off any smc header""" - buffer = bytearray(stream.read()) - if strip_header and len(buffer) % 0x400 == 0x200: - return buffer[0x200:] - return buffer + raise NotImplementedError(f"No Handler for {patch_file} found.") if __name__ == "__main__": - host = Utils.get_public_ipv4() - options = Utils.get_options()['server_options'] - if options['host']: - host = options['host'] - - address = f"{host}:{options['port']}" - ziplock = threading.Lock() - print(f"Host for patches to be created is {address}") - with concurrent.futures.ThreadPoolExecutor() as pool: - for rom in sys.argv: - try: - if rom.endswith(".sfc"): - print(f"Creating patch for {rom}") - result = pool.submit(create_patch_file, rom, address) - result.add_done_callback(lambda task: print(f"Created patch {task.result()}")) - - elif rom.endswith(".apbp"): - print(f"Applying patch {rom}") - data, target = create_rom_file(rom) - # romfile, adjusted = Utils.get_adjuster_settings(target) - adjuster_settings = Utils.get_adjuster_settings(GAME_ALTTP) - adjusted = False - if adjuster_settings: - import pprint - from worlds.alttp.Rom import get_base_rom_path - adjuster_settings.rom = target - adjuster_settings.baserom = get_base_rom_path() - adjuster_settings.world = None - whitelist = {"music", "menuspeed", "heartbeep", "heartcolor", "ow_palettes", "quickswap", - "uw_palettes", "sprite", "sword_palettes", "shield_palettes", "hud_palettes", - "reduceflashing", "deathlink"} - printed_options = {name: value for name, value in vars(adjuster_settings).items() if name in whitelist} - if hasattr(adjuster_settings, "sprite_pool"): - sprite_pool = {} - for sprite in getattr(adjuster_settings, "sprite_pool"): - if sprite in sprite_pool: - sprite_pool[sprite] += 1 - else: - sprite_pool[sprite] = 1 - if sprite_pool: - printed_options["sprite_pool"] = sprite_pool - - adjust_wanted = str('no') - if not hasattr(adjuster_settings, 'auto_apply') or 'ask' in adjuster_settings.auto_apply: - adjust_wanted = input(f"Last used adjuster settings were found. Would you like to apply these? \n" - f"{pprint.pformat(printed_options)}\n" - f"Enter yes, no, always or never: ") - if adjuster_settings.auto_apply == 'never': # never adjust, per user request - adjust_wanted = 'no' - elif adjuster_settings.auto_apply == 'always': - adjust_wanted = 'yes' - - if adjust_wanted and "never" in adjust_wanted: - adjuster_settings.auto_apply = 'never' - Utils.persistent_store("adjuster", GAME_ALTTP, adjuster_settings) - - elif adjust_wanted and "always" in adjust_wanted: - adjuster_settings.auto_apply = 'always' - Utils.persistent_store("adjuster", GAME_ALTTP, adjuster_settings) - - if adjust_wanted and adjust_wanted.startswith("y"): - if hasattr(adjuster_settings, "sprite_pool"): - from LttPAdjuster import AdjusterWorld - adjuster_settings.world = AdjusterWorld(getattr(adjuster_settings, "sprite_pool")) - - adjusted = True - import LttPAdjuster - _, romfile = LttPAdjuster.adjust(adjuster_settings) - - if hasattr(adjuster_settings, "world"): - delattr(adjuster_settings, "world") - else: - adjusted = False - if adjusted: - try: - shutil.move(romfile, target) - romfile = target - except Exception as e: - print(e) - print(f"Created rom {romfile if adjusted else target}.") - if 'server' in data: - Utils.persistent_store("servers", data['hash'], data['server']) - print(f"Host is {data['server']}") - elif rom.endswith(".apm3") \ - or rom.endswith(".apsmz") \ - or rom.endswith(".apdkc3"): - print(f"Applying patch {rom}") - data, target = create_rom_file(rom) - print(f"Created rom {target}.") - if 'server' in data: - Utils.persistent_store("servers", data['hash'], data['server']) - print(f"Host is {data['server']}") - - elif rom.endswith(".zip"): - print(f"Updating host in patch files contained in {rom}") - - def _handle_zip_file_entry(zfinfo: zipfile.ZipInfo, server: str) -> str: - data = zfr.read(zfinfo) - if zfinfo.filename.endswith(".apbp") or \ - zfinfo.filename.endswith(".apm3") or \ - zfinfo.filename.endswith(".apdkc3"): - data = update_patch_data(data, server) - with ziplock: - zfw.writestr(zfinfo, data) - return zfinfo.filename - - futures: List[concurrent.futures.Future[str]] = [] - with zipfile.ZipFile(rom, "r") as zfr: - updated_zip = os.path.splitext(rom)[0] + "_updated.zip" - with zipfile.ZipFile(updated_zip, "w", compression=zipfile.ZIP_DEFLATED, - compresslevel=9) as zfw: - for zfname in zfr.namelist(): - futures.append(pool.submit(_handle_zip_file_entry, zfr.getinfo(zfname), address)) - for future in futures: - print(f"File {future.result()} added to {os.path.split(updated_zip)[1]}") - - except: - import traceback - - traceback.print_exc() - input("Press enter to close.") + for file in sys.argv[1:]: + meta_data, result_file = create_rom_file(file) + print(f"Patch with meta-data {meta_data} was written to {result_file}") diff --git a/SNIClient.py b/SNIClient.py index 9170c845e3..188822bce7 100644 --- a/SNIClient.py +++ b/SNIClient.py @@ -15,10 +15,13 @@ import typing from json import loads, dumps -from Utils import init_logging, messagebox +# CommonClient import first to trigger ModuleUpdater +from CommonClient import CommonContext, server_loop, ClientCommandProcessor, gui_enabled, get_base_parser + +import Utils if __name__ == "__main__": - init_logging("SNIClient", exception_logger="Client") + Utils.init_logging("SNIClient", exception_logger="Client") import colorama import websockets @@ -28,10 +31,9 @@ from worlds.alttp import Regions, Shops from worlds.alttp.Rom import ROM_PLAYER_LIMIT from worlds.sm.Rom import ROM_PLAYER_LIMIT as SM_ROM_PLAYER_LIMIT from worlds.smz3.Rom import ROM_PLAYER_LIMIT as SMZ3_ROM_PLAYER_LIMIT -import Utils -from CommonClient import CommonContext, server_loop, ClientCommandProcessor, gui_enabled, get_base_parser from Patch import GAME_ALTTP, GAME_SM, GAME_SMZ3, GAME_DKC3, GAME_SMW + snes_logger = logging.getLogger("SNES") from MultiServer import mark_raw @@ -1336,20 +1338,18 @@ async def main(): try: meta, romfile = Patch.create_rom_file(args.diff_file) except Exception as e: - messagebox('Error', str(e), True) + Utils.messagebox('Error', str(e), True) raise - if "server" in meta: - args.connect = meta["server"] + args.connect = meta["server"] logging.info(f"Wrote rom file to {romfile}") if args.diff_file.endswith(".apsoe"): import webbrowser - webbrowser.open("http://www.evermizer.com/apclient/" + - (f"#server={meta['server']}" if "server" in meta else "")) + webbrowser.open(f"http://www.evermizer.com/apclient/#server={meta['server']}") logging.info("Starting Evermizer Client in your Browser...") import time time.sleep(3) sys.exit() - elif args.diff_file.endswith((".apbp", ".apz3", ".aplttp")): + elif args.diff_file.endswith(".aplttp"): adjustedromfile, adjusted = get_alttp_settings(romfile) asyncio.create_task(run_game(adjustedromfile if adjusted else romfile)) else: diff --git a/Utils.py b/Utils.py index c5fc00035a..707415453a 100644 --- a/Utils.py +++ b/Utils.py @@ -11,6 +11,8 @@ import io import collections import importlib import logging +from typing import BinaryIO + from yaml import load, load_all, dump, SafeLoader try: @@ -632,3 +634,11 @@ def title_sorted(data: typing.Sequence, key=None, ignore: typing.Set = frozenset else: return element.lower() return sorted(data, key=lambda i: sorter(key(i)) if key else sorter(i)) + + +def read_snes_rom(stream: BinaryIO, strip_header: bool = True) -> bytearray: + """Reads rom into bytearray and optionally strips off any smc header""" + buffer = bytearray(stream.read()) + if strip_header and len(buffer) % 0x400 == 0x200: + return buffer[0x200:] + return buffer diff --git a/WebHostLib/__init__.py b/WebHostLib/__init__.py index b7bf4e38d1..f9c49c5a20 100644 --- a/WebHostLib/__init__.py +++ b/WebHostLib/__init__.py @@ -10,7 +10,6 @@ from flask_compress import Compress from werkzeug.routing import BaseConverter from Utils import title_sorted -from .models import * UPLOAD_FOLDER = os.path.relpath('uploads') LOGS_FOLDER = os.path.relpath('logs') @@ -73,8 +72,10 @@ def register(): """Import submodules, triggering their registering on flask routing. Note: initializes worlds subsystem.""" # has automatic patch integration - import Patch - app.jinja_env.filters['supports_apdeltapatch'] = lambda game_name: game_name in Patch.AutoPatchRegister.patch_types + import worlds.AutoWorld + import worlds.Files + app.jinja_env.filters['supports_apdeltapatch'] = lambda game_name: \ + game_name in worlds.Files.AutoPatchRegister.patch_types from WebHostLib.customserver import run_server_process # to trigger app routing picking up on it diff --git a/WebHostLib/api/generate.py b/WebHostLib/api/generate.py index faad50e1c6..45cca66ef7 100644 --- a/WebHostLib/api/generate.py +++ b/WebHostLib/api/generate.py @@ -7,7 +7,8 @@ from . import api_endpoints from flask import request, session, url_for from pony.orm import commit -from WebHostLib import app, Generation, STATE_QUEUED, Seed, STATE_ERROR +from WebHostLib import app +from WebHostLib.models import Generation, STATE_QUEUED, Seed, STATE_ERROR from WebHostLib.check import get_yaml_data, roll_options from WebHostLib.generate import get_meta diff --git a/WebHostLib/downloads.py b/WebHostLib/downloads.py index c3a373c2e9..0386d1b0ae 100644 --- a/WebHostLib/downloads.py +++ b/WebHostLib/downloads.py @@ -5,8 +5,9 @@ from io import BytesIO from flask import send_file, Response, render_template from pony.orm import select -from Patch import update_patch_data, preferred_endings, AutoPatchRegister -from WebHostLib import app, Slot, Room, Seed, cache +from worlds.Files import AutoPatchRegister +from . import app, cache +from .models import Slot, Room, Seed @app.route("/dl_patch//") @@ -41,12 +42,7 @@ def download_patch(room_id, patch_id): new_file.seek(0) return send_file(new_file, as_attachment=True, download_name=fname) else: - patch_data = update_patch_data(patch.data, server=f"{app.config['PATCH_TARGET']}:{last_port}") - patch_data = BytesIO(patch_data) - - fname = f"P{patch.player_id}_{patch.player_name}_{app.jinja_env.filters['suuid'](room_id)}." \ - f"{preferred_endings[patch.game]}" - return send_file(patch_data, as_attachment=True, download_name=fname) + return "Old Patch file, no longer compatible." @app.route("/dl_spoiler/") diff --git a/WebHostLib/tracker.py b/WebHostLib/tracker.py index fb5df81c9a..8bbf7465d3 100644 --- a/WebHostLib/tracker.py +++ b/WebHostLib/tracker.py @@ -8,7 +8,8 @@ import datetime from uuid import UUID from worlds.alttp import Items -from WebHostLib import app, cache, Room +from . import app, cache +from .models import Room from Utils import restricted_loads from worlds import lookup_any_item_id_to_name, lookup_any_location_id_to_name from MultiServer import Context diff --git a/WebHostLib/upload.py b/WebHostLib/upload.py index 6907bb2acd..173411bb64 100644 --- a/WebHostLib/upload.py +++ b/WebHostLib/upload.py @@ -1,6 +1,5 @@ import typing import zipfile -import lzma import json import base64 import MultiServer @@ -10,9 +9,10 @@ from io import BytesIO from flask import request, flash, redirect, url_for, session, render_template from pony.orm import flush, select -from WebHostLib import app, Seed, Room, Slot -from Utils import parse_yaml, VersionException, __version__ -from Patch import preferred_endings, AutoPatchRegister +from . import app +from .models import Seed, Room, Slot +from Utils import VersionException, __version__ +from worlds.Files import AutoPatchRegister from NetUtils import NetworkSlot, SlotType banned_zip_contents = (".sfc",) @@ -38,17 +38,6 @@ def upload_zip_to_db(zfile: zipfile.ZipFile, owner=None, meta={"race": False}, s player_name=patch.player_name, player_id=patch.player, game=patch.game)) - elif file.filename.endswith(tuple(preferred_endings.values())): - data = zfile.open(file, "r").read() - yaml_data = parse_yaml(lzma.decompress(data).decode("utf-8-sig")) - if yaml_data["version"] < 2: - return "Old format cannot be uploaded (outdated .apbp)" - metadata = yaml_data["meta"] - - slots.add(Slot(data=data, - player_name=metadata["player_name"], - player_id=metadata["player_id"], - game=yaml_data["game"])) elif file.filename.endswith(".apmc"): data = zfile.open(file, "r").read() diff --git a/data/basepatch.apbp b/data/basepatch.apbp deleted file mode 100644 index 2a30d9f8c2801a726f1f35c5c7f1e32882bede05..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 117900 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

oNAV_tAgsG^l^QUIFy zTywl^=6)wier0F2yeJ`Xj2xr(K_?~5fY<2Juh17{C;4=|H$9~{ll zF)fqSaj`W9b&>N7*bRPr3%0W(kz(Kl3#L-PZf!t^^lZsPKz_Ln2`@5yfrcFs^O6=w zpKc`(J=C}*@bcT+_iQjwIR}rZ+RS`q9lfvw-9|#?U%=?uTn5Dv@5nkbf~r%Ko0azhQ{N9L&8hDKLev{x5Pq4`O?c^ zNf)pYcEzqe8bPqd0OZw2!r8M9IC%Z$v%DDmX<(!HYZN@o;JXhtuK6+fj3V6sTMtLp zeBj~);Z9i>DPBM8QqTA?#lGAxFo~7t9^aygBJdyzEIVD=zw^6Z>9yg}Z?Lm*313zb z0uHyujZwsDCh$!Qz3^NgOm7R@Bl{n{A&nc=^x><@FU-zL{ z2WZ_fq4k&m8&0cAbfhp1OmX?jZTK$o(pmZR)8C99DeNQ;U(Fl4={*1~Q-)FUe1L9E`k4_&MFRp%IT2@l9ig1-a zO_gXv4GMYNP+rN%ZNWr_SR5_|KP!xY#KK^a6w$#Wj`wzJnXBn`%wA&f_>xNR5A-5x z=`{V;6;qfxPp)1&{xjTDQ?)T~EEk8ivMS;X7-ffob}_g8BiQQZ>YN+?5A3v8% zII6&zrpmrW*J5y+F4xq!qq2+m@nGyO-yH}|=6=^yP6X>?srRIig3MxO;AB0)s>Q14 zxkdM>nJsB$HOm@&H(D__h0!x#rmqGRzpLD{4AthQ{!-^tfMtLemx7haE#JMqG?f91Kwvu|9Fgo>L9R`b|$bL zWA7>{GosdO2^{0FtFLrJY2gD}$y3KGxh^XMez(@)(=t7kvnzTqBvDA$j?9-8g4 zP{>tc&R#y1Ub>(* z?Szrz+Y2J|#@$@`>;bBw7IryD+II2KkIrQb$oB@pW4lw+b(lj$j7VhY;=Qe;;qiz7 z{LxDG<@X`ttckmNUd?+#=2RfE9kT}p7+kZK$r zT*ls&U$7sgp5WO%QKtdPUaE_7z(rPfRdXaZO*Eu4-1)8sdhwcSFFkkj1nIsyc}rX3gQ(AY%)^Io1?gp!T;;*$0w=bw}t?_N0(Va=wJ=;t;+ zm}YB`@b|A|kFfl88?H+r=Gx z;}x)rjGfklQ_Tv;_DC;>B$#VA2&EEU^{!7y)O9tT#bW~9veQoA7yd;uc*QoT?vpG} zW{TGfTTu4}=nd}uRP@Q?)8Q+Q4hv^}Zgs0ocVmqdvgNl)zo z7;M-Uf8#QmFM3Wj!x5d}pHHQ&;Ov31NH`lD@e!%A*4q9SV~xck*)(NS^D$^E3=Wx4 zhzFIdI6%M;=9x0@exJ7tO!Y<%kEL)^=5zasY|DBlBMy{)nUaS>ork&K;anAU-fdnW zf7%%xoFOdjAR_aO8mtGnYk=7p!o|CD5{7ttS#h;J_lHYTHaoKKFhoT|{oM1yu( zIv+nY6jQlb%J`z5LXjpR0j2U2F^SU)iI&RvKngSrc>Ich_x-gPVnC^mtJg=WSM@>5 zd^|#4==p7tv8yeMzUu~B@VJ`Jp{Ib*MkVFzQoE?)CxN87WG~JwzZiWvPpG7IwnZG2 z6^&22e$lgh0`P5}%1cZSrG#uu+MI4$3;nQCix)SvXEWfbTtoXsuX0UMc1#ISe<= zd5PjsbdJ;>CD`vM#|X#}(%j{fXr!T@q_um9;zJAyUCif57iDUthU;}#fJ1Pjozwk0 zYt&!WMHkiX43HFzKL=5-`u6B2_W9^#n|h7)Ner-es2cYxarG`1;qc0~1thyeF6U>PY&*mEbyJ+TwPCgn4#?)nnlQ}wOm1I zC}h^FuEh>&ntNdz$@?R`_n#gW`H4SZRH6G;H2-(Bx62 z#a3fR8T6fx4oYr*gmO-4CezK{hIrEI#nYEsHE@g^!ws0r zwK75~PHTL1_#TTw4Vrz?+WE5z_T4e%j4WH0dT&yoT!EF%SVp-GkrxQ@w(8=G*R|dXgbY|fFqjW>*MAoF{jO#? z56*Xf=k<8q{qZF2Q>Sj-#PzXe+(sjLfi}M}wAQytYx^J3ar15mCe_QVr;O?`?dk)+ zZJe|xyA+;#U&W{w6;5Tuw>GER+D!latGbRuH#uR#A$7+il0PTt@oqOe9VFW5eOK)VV}Sb4kTs#Z-VyRuH4l7l!686S(e(Hu+@Mv zef(&n5(j8FGtte=pdvDzacb`%e1$Q6a~`T@dX+*ZR#9$`16X4Rq7VnwB{~xKeU0oh z6y4aorB1@yftq6hfwKz-&nId4E}`W&jUseaz39h+xx)@5tETZaYm| zI@p>A7ZFsuK;8voBTda`ZFoz%2hS^CKi(`==t}oig#N;|! zEqzk@upIgr*Lk-aPq_(_#W5Ijcc?b0RhLw2Uk(rqEKJ>{!o)%@@W^3XlFkB=E2D& zz&RJvR#C6aJ?my@QL!Dy;Z7ErNOWk_DJ-^6TR-ImD)(X_I}?fKF*5kkxGbHm%Jy(8 zf^^k(u*nyi@wwaN_rYn(M|q01l=CDmqdRSFTD-zMZ#^vpqj*}5kV*kk@&fdGCRUtg zeeXb63C&`!@g~Xa`C|)+2u7p%!}C+e#qbc{*Rk$ae+&tOGcTm|2>=1wQQ8+CB}(?Z z$>3GS#p=Q#Zy@@T-gA5{duROMLvA5!aR19Drbivbgc6;9UHE&}?qT)RDE-UNg&l3auOmH%Ofo`)-0$M@#)NZjR-K>lF&)m9 zK1LT$6wE#L?!1qvlDmPHdo=8JmD)CsKNtm^)xf|XMTWgtv$3rZfF>lZbrp6$dL>q4 z1eAqK#l2NtJ5uHAgkER^`sfj6zV^Np5eJSkk%Jj}>F~19bW7f0^Z?72YOt}CKwKS! z;^5)bWOXx+UWvycGAFWsU7v%cS?+f$YA+gDu*8UBZm}Rsf|kYfbIt>oYBLYW=LXgd^&(A^ofO%Fw5^GDlIW zK68*s4xYb<6pQAHV-=%zyf!S7_$XLXGhDfT=xL*n1JA*CnBQvJq%|bH-SLx1MESn} z(b;J5Q>W5&sk@2jcsa0lj{+E#7sAK1iT5|@FXwYz;l+ zJzRV_m)aM6U1}1+O>h*VJNH+IV)2&c@xRA*psb%U{}8%Xd*?z8y;Nq29{jVfHav(S zd7fHu1k6u8noqJw4OSf6#4FLC|D1jf-7{ySXR3*4ka$-DJ(00q(~zAq!ek^8h#h|= zDZUnZHDR#eVFZ7dAp$ytBOq6g-v@bUtA9u54AYDR4Zn-DejV(n8%u3Sd%sS?e#)rH z-^mYLx1Ad(qybxf4yx*>XIUMxPEyImNtI)G6`YiNurFUdxLg68GM&b|?^a zLqNs+p5dxeoWm>AIKG69mCCaqk8)wYT8`fczvR%3%CEUI>3Ij97^g&GUN?Jp5!x%zTT1;Kuprk(s|arUc?8DQEJ-uuAX38eTQ;@)Ox?ixRu4 z=@PJ4iWBKqByHk8zROydb1Z}tmWQsJge_!Jr+-3O$^e4JA&UsZsas9k7^nD}FZYSK zhv|BhITnFLT*2mN+;qPY#54E|+1mL>_pgS}BC=?U`?-$IQTUwuK%#h37<=T()gQuo z{rCnkO%o$XVOiW4R{PYj(Y8Xbq5xOJPoCiVuWWs-Pq32l!X2gHx0rLnTbIE8)bSFx z4ue&bYea^%%p~m4>0EmkgW_Z8v$q9%O|-Xs8?H6cpj;El(v3VE52RAyN!x?leysk=?si6A;S)){WlVJ*ZH>ieM<@d=2}rmk zB9ELvw=of2Y#g(G8U%z%X_}iW-Q7*ZXMF1TVjbMxUZrk|Yi5G3DV{}<}#=n$hs$MrV z5(2c5KvX1+ALKlLTMV7;F-?anO5nXg`ekLt&EHoPpN#?q@HDIU6y-W0Z(h)DyWaY8 znEiTH1wa6w<^Qt`kYva;a2Dl@*Mbusi@GhTjh=4Wx>?Y#*3_?g0F&ry=AEi3pM?ei z(%9|?Of0R>5sNi$$F4wJP6?F%A|FWKm2LnhK-j-4LmfTKg;(p)C~Qk+6CejU0&p=r zK!qUtBJ3b*P5`XO8JPMECgnWpi@alm{nDk@5N6 zWl8lZ8ojCk8%!GQ3c!w^c6cGQi$~>ZL5tUhrTdidT#rl5C~_okyPmt$`oBSqOro!) z#LctZAmo%MD^~ioN|x%8P<$;beYB)E(M`B66OFtFXMEJr4RUYwmcX(hAJPg6123Icg{y>&WOD|EEbxmp=YBt`X+7stW#R3kJ83$X^}Iw0iZhqR_$Y$DWAzz zhoK=aZZ5xLAI+8^xm*^W{oUJ@|OO!bO@tGP!zV=ae#a zhyNWqw!B*jU>lv9vr2jdWv$REw1Ds+lklU8F8d^BAA&L9T4rf#30DEM#G`6@fFUeD z?Od-6H-iOvmY}P&s7x*h&;wDSCytK_L%xdPqikD~lqTr}f^cMCCogSVO z+0wC~##oR(!Bi@D1f>gU`f&5a8|0`k+XwA-^N>y$9?k1Tm#-}FuyKz4vlk3(F=Z+V z2sRB@I{q8snqaPf+gvm;5O{!Bugg}CIqT_OtjOwu|1I%{O^cZJIvkK+_Q}PTi|vn2 zUW|!;HN#t>QjIHfz)0^`j>Plby7XY?3vW!E`YrS_E_Ocd{P^y1xNa-fXeF-k#CF0~ zX=F;Pg>+|oRgWDHoKXVHO$bbI>HXrRRC27aw}eQF?%k>uUr+-0DOh1P3SWq*GGRFX z_is9-R0YK{E8expz;Bw{xMK)2qxc^4vJn=Y4bi1gGGA`opfQ*Jyl01QFyCNa&;UoT zPDWW}9HNcU=J8^dF}=uLy%gd6t^66o`g0iF9co?l713{+P+pM)4w0=)FeBJL7=&BX ze?fDM!o>tlNzca|iNa63Lf<{7G9bP5w;xNwbIg6n-fhv!i~U1`cDV^G(N%JF!^3}N z+FW{oC($)fVFgJLvQAB63~$Q7ipfl{nNODOn^*2zYnqlwJ0N!p&~ODrUt9L2t>&eO zea!lU;l>q9@%U$l5U)mC;j%7X-gTYw(@V~7;TfZdBZGiV4g=uJ4bg;DYpvVPvh;0Z zqfn?Zej?{1;`GrOKy>S|;2F>-8uhJ|aR%VdkuAlwdc2C4disLFb^=nIGJo9wwIbp) zA&d0ArFN~e_9%SZ2XE66=Q zf%2fMxEPMZ^Tso26xWHE+WGj|roT@@@YE6q%~2XBDRfx~dtd9H-oiL^AMNF?IT}1< z8?UkB7%S6K(%fT(7Ek#(K*@)jH9GURFeSiP0pUvZ$HzJ>JO6N7NvTxo;TEPLlWxWZ z9tugwjxqI;E?2^b*>}B81OjGCbWIDJqhM)%<(IZz)CRLJDJGnLCXagm#;PcxIiEAqRhn6n`|evBH@(bu()OTzn;CX; zGs|ER7=Lgy!J|-GIxW$` zikDnXqbL=-J&^DvC`Cgu@#`^swgq%IR4X)(E7=w+0hz#!nD$9oG@$PWeq9hu^(yZ! zy+x9LG7T%I&3If|=dhMWk2KUvU? znBWDOU?$=t{8^cW^qmwGzLG(?rx_L~utZ+6z&^6r1WghL$qU{9uxD>Po=r}qEl-56 zO77NOZ#k~-?3dq&D-F!F6ST`^RdZRrcDybEDnK6;G(u0#-Y-8~_$BwtvpZKHn~#~+ zw*NKjVfNl#_DY%r2#FOTgtAw!WbKou8sAic8X~J8anxTKx%E?3n9@(F6sxVnglvog zkmkIBCu2XsHq@5BN=Dr6q0PQ3$w*yd;MxlUuVFs@S9H1Kvpg>p6xU1g7Xh|WR<-aj zSxrr{R5}u1cVg)V?eu+1H_oiodBNs)1}xkNWSw*&YfS=-28X;l&OKs%QU-Y^&VNX~ zD&wEs3T$ReR2a@D$k=lQ?%$ZGXIm=Dr6qscmN*rswCI0^+1BSPGDVS5)zHVAF3&(C zCw|N7JOU`a-0J>F*OxSfHm$q`uT}`5nv@+@p|}_W9ome9-5r^NcseCk1X?Ao1+)i* zMV6!Qgv*Jls@%3rA%cnb=o`(tH;!io9^ZPP)Az|=x>#A0dMl(N-q z7{~{c?#x_Yh1P!MXEj3QyWO{iqR-A{9L2xuHcX>~_K?-4Rk+%&1k2-(j)6J|SlW>w z7yLl6{DZ?bF!}U5c9S8SDDd%owskL3SI5H;LuI_BQ2%w`WYT*MVbAGVsdUZyA(mKU zUinm_T4voG?xW`CSfswLADXVKAd{xQkhcX^$Az71R_1Ll)wAOjgcOwb2m)>}SSyr6 zQHn6IaHrysol;VhKTFO7d|f<-am%Z%8Q@DnGIk0u`i2>B=Y^KPsOi3uG7JqF6erj| zrIKrW7l)kCC*uDjU1fsj6)(T_Tkl$LELmbCgw-#|)rlZ=Ft=QHL^jTV$lCr7rktx; zW$>5CgPEhvc1-gc2LYQbJM20!_ApzvHgop&s$>szWyOy8leyO$8?MA@>fn|0qOMzG zzS<{XmT%Q(VH;u*2Xg5oNtOT$xHO+bZy7B-`}2FC+pxny?6JTc-u5xtT(6&{Hw4Qg z4L!tv~v{ROXd+e2C2b<3F*O7l_P;C_iaKZC*x*@$_na-@1L({ ze=FUA9CX7P>u!4L+fDYId23xwU1YS=oi5J*Ux~kPb)$;NCLkUys?@eO(R^8CMCVX+ zD-THgs@|)cUvnTEDfZiDtBuc&m>330Yv<-RU5^^!W|O1?0myYDL|yMY1JlEyhz=|p z{m5x3ZT7=Nqmib48<$o#Wez*dow>_k?0ebNYbG_i?LxS+re)O^wD0C_!xbk$t$Mpn?ja(vn@ zjR0(02To5^l(biyk0t>ESe1RGH~F17DM`I+w{^yV$9$eIX?5{%I9pToeR0$9tY0|& z2vhWyaZN<+`%rDJbJNn<_*SHlJRN>MPaPn2FvpPSPiu9I%}b$BF^tO_QTGw#+>MD9 z(c0Z{gHF{BJny#nH`tY|%#oSt?=_kwS(F@7wD2A-Y<+Fs$dz2d27|UsP3q)aO~wf* z7!)D=YfEN|%h!e1K1vM_(uP?O1s|TLJB#%)k>CkYL16cCc+^yD-C6E{OagTcM(~W@ z3>z2Q2(84%v&LV2e%ct?%_E0)s))Js6DMeNL1{e00qRwPUpjxeM)#cQf~__Mz| zziof%E=HtZ)` z{%vg|`#j=&W}@Zi_xpqeKw-s2Vsw0Sm{eQx$jr+<7ACHC@^|Qj+Zbs_@yVmypaP}c zxs>cqBU!0WmdS0w?dH;wK}wHX7Ie~sp8Sgzn6;Nnq1;w05_+Bh6JREpHqk+?WwPNc z#I#JJ^OJB2ptDPaH!r!S2C=F%mN$CHP_hNz`+95E`&m`yWBFAY3oV5jv=`KmIr?cU zV%z}hS#Bpzw`>Q#i*hKaJrSpl$Z<#pDP8jzhvjwzE%a?gW*B*TQ<%NF;E;hOFD_w% z=nB~bt||G@e?qTY^;&5~C+t~z9i8oa%iLyGGHEYnND5XOq8Y}g8`~kAzQcQ<7-(u| zf-pLsnX)wdivaTy7!gcXTd5z1ncdC0VAnoeoUX_q)DyB^;930(%V4o)^4n8FH3z@I zy?UZXinAVrnF!vWE|2AOSl+|uBVR`q4xzn7mh4e?nFTo73uJS}nbnflsf!SVjHDAq zibY8?OZjv{%N@+G08a4xIzhl3yD_4SKe%vrkXPu5DwBcPN832J+Q_~*#TL`lEAY^| zHlQ6(WH=!8w%W!Pk!2J?VkbqJCuskRG33Ln<>?uOBt|9m7v{#{ zCbZSCI3RP?GQaG=N-T_>{P#R9JB@FK{KLoGL|pZ{$YmSlY2F+PmO>gTV@p3Q6@Z8) zMWK^p`-F}RW9P#hJ~Fny)d^zW3;t?-eCVFkoK#)9Z9Sis9n1hAG2oxoG@tZWvpg^(X^je1$ zHH_1|bBKR^@DeZ}z&p}uJ4)5QNlz15y4aJsT3oLt#nu=3e;iV`S0rchpRiDSsx9B2aP?8*6t0ur={udNi2RGxv_ z>}`L&tb2kXnlX51YTS$fE`v?tYqK3o`fnMG3nd{~DCrO#iLn^rKv@t z{cMWCshT|&$ZrMG0nAI_aIldyQ1MrzXp0vI9;L$MF%p_;rj~NPDPsKlu5LCZN4gac z&t>SxH3HN;jJ0lPqWEt>ZG3*-ze9`wGA5q`(qve0X)=_)q$C`8Yf5WzDJV$03}X-L zg#Pk>LS|CajC?LrQqORxu;-qAU~t!0_@ZGlGaJD27WiGXzfJuT{{@jhM{H&$3Ggg! zgo(u;58++dWv!neOwz&+g2s>pRN1z=X%VzKPbu;BGxP&$dn^eRcFqkUJ}relxnV9p zCQEOr6#3N|p)LdyQy0YgX^X2jw<%>%4Q2l#^>}C=X(}IL_H%?%l!&B4Cb}w<723`R zSeWX&9qnumv*=HD>N|(Gc`=6fw@LB?fohJh*_U?B7rb6>OL zAAU^sFgoGvg#EE%Kba1(vQUue+upx{?NxTQGatlWH9LqR7s`=v>L8T*2HOccXEdyJ z2eIe7H0NP_Ztr=(H@(1;6(oJeIPS?J5bnqWP#u8^Dl1DIfp%cxyPuHMB`&F;G%K>o z%33yIbMRkf=b^iI+s|q0<>xT{#_zKayt{{yhS|?agA*^6^mM^GI-mh|5!_>BJ+|ZR znqXc8c*#5nJfA5w;Xm&Ir0Bix!Vh}rb;u6aNSXrbbVs$!lLNggREIh(&J!zTWCpwk zMT-yFkmTV;9jBgBwaW}RkF**Qq?sb(H{863I1>oyYZ0A_kMplH0m$%0QmY>^tLPrP zn8-!Q;IT;t=r{ppZtYuD+!Jfv>%dj)Hx*UQ=`^NAtB!t*N3jlW^N$X(hMI_at=j%x z%O@BTp209&w8yEE5%>kkq%M#({>G8WX39g$<0(#v`DJ5!FN%%v1-9-wjb+N4Gg1-K zR@iAuN=vJ>J_g*GufGL<$R(YAf=maVye1g^tMYPapdm*q4oUt|lr1PA*mN$NSU~bh!v(!OD z=3FCmEV7tQLGiTDX`27$yLqCmF!=ZWMoQpo44?f&0W&;OWxQ>w=Ual(md@$>ASjmK z-wS>4^@|vkKqY@Qu9&O5^B}fVVtlUHq-Z9A}t2h7|@Q&t;+~df{C)iBiF>JXd>)?@gjW{~w`Znb* zxldbR6!b+z&=4kGP1~u5pb1_UViM~j%{GZNYq;;YTC=V*5l?$~{orr~r=PjxE;t*p z$J?<3^b`c+VZfs$^u?^{XD`*jnbK;CQzmXH(x|brm~meLSK&iGd%c2=SYO%Zm)YYI zEp)qSa$`opANdLQdb)5RIU?FJFR$*aC3of2mNq|{b5dId40@84_2;>7H*Z-A(-7oG z*#uLhpv@s8S2rFsp z;x`pLsAPNOy8PCZ8tB76q?L!jZz_YVXfz^#k9$DeSLiCOC0Rho|B}+XLr9&kaeo)o zbY;WX&*2HkzSAK zqkJ=s`YQ21nLde`RMcFoH*B{^;Q(@j0)81)1ocwoM|%sp;Wy19s(Rt!p_Z6Zu!Vk* z8$4jkR0x)=!X|;y@jo?_DM1`tWMG)Y)6xbR)R;p8f{2yR7}R8aRwg%>)qo!YU6;EZzA-QxIzsU+)KY1IEeh!oqsb%-sc+sXu&eT}gsF36~5pvs)k z_V3_b$IjHcp=;i#)VBh2=}>dtBIo3k6=)LQ zh9~IQ{?hh)1tlyXBA{*r37lMy2Y}czuBiEN&^uTtrs$N|ebPHNa z0CFw@U5aQd-aro*^(deVRu)q3^s77;4}oK1q6>Pr`vYesr?p! zs2gR~(rYkciDwYPS^hg4*$nu?xq~5*G{W7E*$4(DX7{S#T|?yBG4pw^P5BoaAhKvC z)0EZC#tDt<2rVnFea0CgJPpF&s{wi^qB!Gl$GpBWB{B6}Ya=9JEScaZP_*L1>eD33 zz*$AU3Wov&HhRejpbc(AoC5%D>095X$%?1mGMo~85gc9m^bmnhB<|P zNf+Ia7T0^~d7{L7*jz6dnI)@oK6vIUqkec4zjadz1FwpRLa#yMKn*B97uc3lnXKbT!hEGR?ZuCS%YmNxhO@lAW|o!1JkVf4srhuo2Cg3?eY~qd(Y;^!H`OJW z{fBBgj@iodu!29^W3OhkVL*g-4VKtHxTZwscIG(Fcyh#g-^m;(5C|<&clRhif+}+8 z&C9cI*ki0JhNuGi1AdYRO?M*2O~A&=x>eJge>B|sZFny``Cy6ndP%%286aNR;j#K^ z-oedYYlWxUMOX9iji&eP9-kco4Gu@bMcsy74IzPLRz39j2}`)zIqT`ZdKo}AO9yHS zZ0-bi5@VyuqV5Q>%_3=7Fe(-A2SIRe*#;3ZnI$ zaWbhR(PXJsf%b$&&*Myx4$=L4P7K= z))UP}qI^z?`f?B#x%q%8Wfz9h6(Z=BiwOhcD!-VH^a08+hu52j4egl)NVN0ZjdYZk zhEeM+h$0DP6Ii41sH?X7UHk2v{8)Kge)x0ujAq`eSRBKSnwF?s`((CqUhTBGi>q+@dKG&gH#CNv5Is1I?;9?x1VE$eg;8ZV*|Jo=$k z;z5aZqe0T;-&#zAif_=R#1Bv^{tcYPZfit%8}Ve}Jo+-MIa8;O!M;3YsNDRj;_AJm zw0wTJ%5K-D>cE6az#+X*1>?#QpA50Nl1fU%JU+fC159A(ihDy~hAf47>T+0t_?C(v zz&t!7d>gc|B|!)|ihOE1{W}N3s@}q#I^=VNu6I;B?RTdhMTHz;M9s=fc71zm2=SUI9jF3rLm1Q|UUR0GK9gZX5WhwSk#Y%J>r=7(SD z!R*1VQ4TgM&tyv|)HadxCA%uo=qz1zeKZ@?%N9jKuitzd%=R8>8uZzW5`@e!_6MNY zagWrkVJ#{c z6a4t%zCGEZ$FZtbCX1mB&%pi@IytT8MfNxtr4>%TbpROAT-L57w`I`v)KR=TXx`jE zD)_PQDG$fkrf~Z-@P5W;7ra-o4UQBW1G%G{YN;i^&>=~_@iFtQE}8>HFn0e*@>#5K z#AfefVQ)C^f}^y;ObGP6BfE6sf6>C^02{XeI78bu%dQl%AEhI_p-Zg%1_9-Bg2T#k z<+wwqR~)KsfyJ{HZw7LH5UyHy)i)!Tk>7jM1QyXqpfs_`fN-z1BfP{h<*&@hC=m33V!1;fU};+ z1+0#8TG zB9T}IqDl5aaUt!BN;QUD*ei+WzPW3>PA=B+&n@_-K84*74L@q*e5jgOP#z0M2*4a9;Z zP=d>=o3B7>Kp%1gWPl{P;j;%Vpob#`4PGf1a>n#$5LrLhq2$?u6f$DV5O=YL1MjzK z$wGrZ@p_5R^s`|e_`=jhI)}NO_aF0N%ZR0Dp9s7nCSjBw0@ ztfdzC-1k$co>!aOML}^t`2lFq0(49_@vyh7wb|0c;p|)=In9B8=*6H*q1v@{HoWV- zM=ei}n&`JPzUousghnD?siW?3>z9fj1pVwq=L`nE>}3|{%vws{a5Gmiu~HP8@|;~9 zjt_?yP*$C2m=7cGV|IqZ2ONI1^-#qY z#EW6K%ubT?j)%Ul7mrtbZ6`^XE2%?&h^o-MVAwuX%32jHFl0q&8$v8DK1Pa{tDhqK z0zw5g?Kx@Q$;NADM9_kregUS~-|%6dbPcd374-Ydat{kjWbp(7a{g1=hK&gIKb9fu zzUp~&#XjKMX)YtAQO}PfK|`ghaw@?8S-5@e6P1&$kkbKb%zG~T*Z8o6+Zw!~4Aq)w zbj?Rky=)W4fL`|l)$;XaEGbvnOm?f?@QP|zJpYqz)#(-`fZ<-RFK@nvH^(2&5Lhx? zrwWW}bw0lK{rA}Xns0Q%EnYR5RHOYe@GmU{Jks;rr(~W$r*@|5JbI+FQKxsgH1%$w z-bJBSJQnXqF@+wM#xr_V5m^?jc?+g43Eo^U*f0$*utY@{x?|NeJQ5bv4w&;-qCLl& z84fe1r3Ss! zCsUeW8A1f3z8} zAXAU1V}HHx9#ZuXMEq(=>6Q{@o=pru*|g97uT!%VM5O@!YEj9C;A%KYvGst!FeGQ( zo0AIplk&@tG2e&>(INRTB(Au$0&&jxpS;s%lwoeKUPBN7qAv^6 z#DQ%YZLnvpt{WlTUIo(Rr|l=81(rfr6_mD3*u`QQG*A)U?LnT_5Ttq{;&yk%8YSpc=0J~r{qJv z?loZ%_|WvFT_TU$<_7m!6q~G|VNdu!Wkp7{T~R#&rEeI2;?NpO_M7=cdM(3fJ;cIK z5R>Hp?ceiCj<->)Zo%8p<2$veF}k6R<8}T#t5?!JI5aYrTbqA-zcSOwWhDoPChIKK zbvPjkP)|D_V=H%z2UnaP_ zeiuya%3d*TmQ|&8OCjG6%QgQ8^Htk1s8Y-srG)0jls&NeC*lQn2}g%(K==?`dfLOk zgP}XFFax1y|LdH*gu#u#TkCHy6XRS)i@7@Z`R|_(;fZw3{!Y%ZFdB~fr} z3g06NQab`abhUGynF^&_3LE!%v8OlFJOsbNdo?Gm0LO|YVot{R?scvM>R48 zu*>l5tF9XfAfnj#?8%lRyMq?YDpd=VW{yt_{1G9Y_4vJ11qnI_0K4lvxWx%JDmP;6 zRgvPjJIJtN_@j2gqgF#Ao`ELy_Q% z843acz-K2#Eyte|@>Pv5FII8_pe?4CMg8{NzQ;rD{0QoD)ONa5K|y2!maMW=#;OFw z?>uv}AmYV|R_*-Mq4ro*J!=BkctI0w6zENdfR@Ih1m4@Om>QwAf&Ps*?3ya#U+cF9 zTAX>{aq436*HWjwHE|@q zuozhx?FWH3W0GFtT2Y7bdo~?>S}zQIGtf;}BBa>Qb2LmT0~#${+~f#wXXjjvSRcS7 z2lE0!BeW&38i^3>Pxb?F8;mZs278#IG{w2PAu$AGN$n53c(`ktJ( z;8|lJxKore4n-)PK4UCV!_d6}+s2jk&3t5^8*XtVaTUPf_-yrb!!~yPQ&=pseuv)8 z-_P}X<$4b?%}s@0Dx!~S7lbIc8j&-m74=uIb7n}?D)j@UZL*a~!n^wN* zoSdOg#b&}rD|<$)Z$^mNXq?_o2)ZqBOW{UD0cV{SOPc0`td{+}Y!?Ru&R&!at<2QlaT7voh zP`QXgElJchtyA;5JTji{Awk<^IWV#HHp2m%agYgbV*<-1c+|E&J2RI}DND4g2fq!! z3DKGv{Jm{j8;Gb2o4y{M&J!!-ZFX_Frln>NU~1rx#Rpeg0(ng)o)l?GO^c5saRgAq zB^~Z_C|Hra_V!z_?PY#yPN>wn^U);h4SP2bY|=IT5YRH&MV7=c7P-0bwr2?>>M|*f zGANZNXlFEQiOD%cZAyZF%m$bq&;Qw~ z$x2!3A5rk)4VWCe_FFrBd_rz<0F$=t>NFcrlk>T1zP%gW^VBGpW{*~H)p5s-uaj?c zX~&3fkIsIMD%cyCS9($YLTZWc*54UKlP7vep{t$r2ZX10>q!;wBqPgxlR)=^TN0mV2|KMM7-(V2q}NMJ8cqTHL=z_sQ(rtSk*x+o3% zMcfj2X!>bMagt>2mFXU=ekC+e%>j136t78m<+yt5;r`}=gVavZwuKHrK%=f{F>FVh@<9I8;E1NtlgkwP5k}7MJ}KKkQV{veZ1O zC%?cas)UQ5=(5xGCjecNO;i0c3)5+`b+6wliHK{2*W zL+zwVj$S`K4ZHiN&UK4d9k3xT*a?h{4owN#s?B177O_l_(O?YPmq()69~9G79<2c2 z2>mENmUU6K<~@pEiH`KrX@rxv;U#9hC^f_gH@SOnmg|z5K<<~LI2$mb@$$TY8KzH` z6=*i1a7KNU6Wx+zax`)bV2{A3Ma-H6`o-^!=2ik{h)u-?Q%l(|Q=-!Z-9#9F*}ruw zLCdbXxGepuz-!3V=$f`Y=^)6Tpd9v(!DJU;1U|hkWgNU&Z#-)-?NTtRDv=te(q`D5p_7)u>dP5?&{*Iyj>X@|LQ73$}H+QNu zY;n!&OL#;Pt#VF2%1-bNNy@1eB;EA(d*2cAI6N#ciR>kZPRppG@zntE9q!JF8nJ7+ zmnie9*)RecGkA)w(*yXE+by>U`4P-o z6JUi%h0x6t;i8W)z+ag6dS!g%Q?Mh&!Hvblqxr8%5((Lk`QX4G_FhmrAAE$i1&vyO zU8J~XUL~XW&@1mcW)G_9^Y-viaN4JtT!}%ZX9#DzoJlc)~?{^ z8O^`2P@)Mz1_&f-KB@cC@-o?7AqjGGvRxn`#+k`ihg)h}K(n3fT6tqDO}4gH1pPpi zLTF?z;h+i(C-zQ}P?otH8Wdb-)RXtqaR^V`8yW1VKepnEO}_hjw~e^0&zJTp)~FiAbUp2&{A5vm5}SdZM}Hc^Rs-eGbt((DTKC3mjm5-^p3zCGzUV z)yBDkW4sS;(NK#t3`d?DEi!lS1etN#mscUZLxs6kQTmqb}y7|7_obVJz8PWYV4dXqPEw@J2gvi?l1!^g7kc*5tTI#yy{~$J&dr} zRqzzVHy)wy!_Y%l&{y=ioCb$j*VyJKqLN9O?nYwpNItUC7W@$ zmI7^Rt)Bx0jH6x#c@Yo?gc9ywbsBdV4Le zo#68K(nv(097#>mfw&ZZiu-xkL6y=x0WL_76?B#1@=c5rIdW0nO*(3E4Xs5?!CRty zz;~k~5kd9Q6v9mEEeux@7|Cij{jk2dd>6MYf53VEdvAor;gcc#akIDZyEcMbwuNcv zT!k9OOjE-Xur#T6+BJ%Y{JFQL38vE(aLOFXeIKfR{=t)gVJ!d96lZHt8?g&)-xgz0 zp!9h=PI^yXU%_t~*AX*j-}i@*6&{Ea(}wtdpS`5cNRfW8yGwxNh_l+~$}khr_|}>2 zZ2@)y%aEOFp3tyHI8pMCThU{^yV-jUph3%CJr2B=Y}GXh^A|^h5@_J|p*3#2M1TPZ zMvP4Rm5ba00AEZP#i!JB1wnBLud4Lqxj(9c>+Mk!ew|8kzS|oIA~S4%7cApTZTGd1 z?WGO|AJGYv1?q=Dk2WH~YBeXU;UYK~=GuGFH60xG!rR=t+ndXfwc4?Jz^Z5+_3{=? zIPko&jvvHB_L~X@KKo1IO$#v!*dq=FWIO8ZLd{f|14XG`Kc4u5G&(%EqeX$uG)6FX zg7Z8$jl)Y>NPN46B+mS^HrSqCZ%=hw%x4^{8ZI!S7Zh~Iw9c^;4pK^PBXCbi;6Tml zOZujL_yy&SL-1WO5%K&e6&86G+`iySMr9Oxm{q0hwWl%%__S1In4n%UO>9CAa|M#@ z+SI}~5Sx)hOvfpV?5c3aR;y(F$9s4OUZGPdF5hg8?|(aW&3S%&x6vM zI>Nju(G}xT8AWcEN-u>df~{S#$&*d}>RF-O!k=Hc{A;>`rOYM7iQ6#Wl*I?=PD`oP z#w1c01|qR=5jZIUJwZfVwJhGEWyyz6FXE9kcWSXZ5w0vO-ah0vuneyslP3<$+x-#K%!;^Rn?-`D_-S#RxqU1#b zQbQ(a5a}bZdc0eFt3m&+gLNXwOhk_ITx?zb`h9v2vD$u0O!ai0 zw@txBTMR>p2j{Ippmu$&Ra3>VuRkpSv(LH$oMpladfU3N0ZC?nAC|{BI9-P>Dka@S zN;QhOWQ_9=xpjqqC6=i*$$S z9X4DUFe~(6Z_%`Nb45R~+!OAjHbDza>92NdwLI7re~J}}CGNQ5zxFYWmt5&g^Y zz@TjS=(Qy3jT+3o=+oiL+T`J-mS;d8OVD=Xn}GC(jGiy`zYv2`P9=Fot7 zW1dSGe+IlL3vZX#u_Ffs+SwLV+A3ClE_sPgKYI6Pw8aUXGhaia>HoOA`|7b90)yTq zVT0YYnT?{Z$t0S68`Q=BJQlm!$@dE(B%v<&7Df9n>muc>uAz1s3OOy_R(XUU)nF4Y z-@qn`IEf!yaK|Z5KC(6*VICRUGDVeYQ)TakL+XSVJ?1BbP7np&;?Lg(eY}>Ll1Sg= z0%sk=@@WKm`n?8dmSA3F?~*iP>Vxc@$7m|uAVG^krFW-hWOYC4=_$L00tq7vc#v)C z36M1QbU*3>$1s8M{~gD%MFG@P_;J1`A zhv&#L24QiclPY0d|L>)Zbt%E5lz}4ettE*g z5@wFn`LjTpdqc@!z9^*!e6p6otL(%#zOwuG0UY7u1sA5VQ}e9v^(rV9X?|cQA50Ln z{e>MjdH5RKuz3=Jg#BtaF+2=OE`&(%Xu8Jw8+_owCFhSDzj=C`8`Y(RucaQf7Abkz zvEQDQIxLEgH>H#Iuv}bzR!71_55)Rlaqlhbg~`uq?s7)9dT9EGEc$P(P|W2+9jX0D zgBPO(dloAV33dGNQ(!}c``^y~76wL3ogtDjDHrz887F$gC8bNp8`JE(Er#2nDSwW) zvak<_B7F4~Xx5YOJ5z$SCTr$!qymNe`abbc7IKfxe914<$&D5Tk2jn_Z?PNqMigBF z{zf|3-K|h)O29}6(SE~A1EvSs!uvQo9dMnEr^k4FT=)Y1$u9Ohgb+2RHNXk%QMKz| zw?)Q7iSm_@^@xJQ)RW(6@Ln~s(K~7v7{G03rYkyh`O)mQnSkd;zw^End95GgUJW9Q zI*Vx%iqCnY3_^QFR+mrL7dQ%SqsO`y^`DZ8$4wb`mZEW)ZofMq z>XWAp4^(p{&h*ZU$`({^;(d2}vc(%R|5fH)Sv{O=ME*6Q@`!1P;Dn1CbE!DSr{NS; zvBZ3}<^Vfl`@_3h!K1`KOUVe8Bkd>FAQu|nkCxZDzh~3{Pi<5Zip*e-#y=u$bbe$rnR*Aip!#KXCko^dOQH}kBh?pDhM&RD-40iw%iY&XGrm1g zG)Z3Fd?g6V6akE>!@xGr4Ofk9)EN4&d3F;~9c2z*CaY>0oBH@U;f)!xW zzds@{Z$7*%B|LJE+zf3qCe&bAy;;NDjOK#3^VF%p>V>~_pNc!NUS;&|>4k>r%fzFD z_}f}w!5YFsc0s>*7d*nx2nzM7V3@DkKHMK7SnNFsM_@?w0zK4J691cPCs73n$z|EI zr4)f#`DrZqMj$|b9F5u!C8PU$mkuevr0u5col|gQ^pBQbw2l;tZ5-$b!m{{Dl7-K$ zx1hc55Pa1~rUUqy$w6z*_F5#MzFcjK*|>WMvo&8nrLEX6C*LoG_onNK@XaDf&m$AW z>YupmW^qRa_%$$4($$B-j1EC^ls|^K^bHA!66fVMi>? z_fxwV&A@B*=%)%iz-GLt!)+crGxx16j_urIdUH;`8xV#osD0@~Z9zJb-wY%dTW?g9 z;G?M;Uxa(Il_dly|D1qo(tI_LTzV%|MN(Vu>vaK9H0ub0-t8`IxFc7=_UfbR7`PS^ z@wxVTo~39*7$(kAa9yVn`=C?>jKQ1+YJGh{C<)SfWi%mWk4}PbkSKRM&U6h09m}-%jLpT5^Vr`~#fAcO{ z&l<=CZyzu}^)^Xd^K*Tc#w8pZyQM8Q!cb_A%CjY`__z<^8B3bIG#1pB&-WaMSN>sGZaf5 zPm{zkla|N^Zp%JhG)S=$DqeaNvg&+@+C37_TwbS_M7B>9>BK#TgxwSOuCeRwjecxy4)^I#;cKC20nC`| zuphkBvPRxM4nVo+qtdP~6B@)7(%WF53GUI+Ps?2Fyj>1cU6-PM3+Qxdn%C@`{Mm;s zR7N#cOfLdb7lT4^v7TkZ=QHijfv^sd{{R{BhLbAj*{WKhwe|Ke(@%h6x)R)E1+Alx zm%JSmmE~L#;(p?e8}6jV13k=2eZxrb6if4zD=A-$WZ5J)X=Fn$xD8D*r^7huPVWm! zveHr(L!K{J3LfqnxZRB$;1aTQor|4WLYBYgtAvLv*)Ujo)E6gwO?*0Z-oig#H5p6Z zL~y#5bqT-O^kRTMvs-N-a_5X{^r)rRR08t}v2Ir!&u)*cq^y!@WFAu(eNGX`U6WCAy=mgWFrRWaO`9oXO?|w zQ9IWVkbA8Po*$Xz1M{i?j$S1e0z`rZ@|PEs=^64KH!Fih*B^r`oFGZo6R#|&qWcFn zo4$XgIoR#3m%Fhr$nuo2TYA*BmvkHi2tbiDgH*orNMtCi0V1!6VT*IDacmxPRMd;) zNwLaX^zYI}I@ZSTRGI^3{AvlQ9W4YnbbS>S|<8QRTwB|xy(=1+~EHz&`*b~v>^ ziX_gNwqyh$Vj5)ff7_+<^u!2(#6gUtL&)jx>7=3vyt1Rs)H_7U0Yp_$zqiy$g|8NU zJ9tYQ^AKH|x-nr3KM2qA@WHXP+`BMFDS8?`^8r4+*(S+pJp4^^1{QLuhW89v?2K54Jz$2 zeP=J5SY5%T5eyi7s)sz4WW_A>Tm2#JnRCJu1L+E-%AR<;OcfHub+OscGctHVgnzc%|1ut7mj!<|w@;_dY`wG+CZ zuSHR*4%(?LuRAS-;_GgzgA0uTp*1+~84ug?fjWol|)M&<%cDvA9my z(I(FiRp*DXsIVn-W@P)$NEud?+lS@Qt2>8EG0$+}W`CqOxXWc9hh|=mClT@6b;@^h zK>b*BUTUmaOAPK}+@_?4 zLe{i_VSr8D`KRt?uVEy-v~fs6N7(2}GLBv1$w^e(2lN@VgE4m3hv8^pyOGk*rqRId zMh5u!Upf@%Y2UP4yoY0O=?^IBMu(?pQf;rSE^g9p6-*Z}!LU^ijfo8zzzJr?9wn=h zR|FCjjrO&yH8ObLnWOokt~U#HdeoC{r}`C!cBt&gR~hS zNyO6ZWnc~PM?d&u2xIRX>Ds0EH>eg|CxcIhQ6V{JpWPY$vo#k2@d;8w;A zCO((C``IMSdLOeaQ2=im?CH_?JVLtdN66~0aQ+f+-P>%-&PdGggn8yuza@vFQh&zN z>XK}HA}Cl8+TD7he2@9X#8j`n;#L2~~v8#fNkb~11FY(;h$uCXFvZ;lw-Z(#l z?5@@6!mrvU>udC_SA?b`0l3ur#HjW)ej=)=a5N27(n__mNO{=$Qw;EGRKVhTxjsd7 z*F?GlG}$}ikx*W0V=9;j?Qf>_*GJDLSPT(ipM~$NKgATZ+1DM!Ms1zp&X|bpy^*&c z?>G}j$EINjv zf=D+@Pn=720qE+H%X_9iYiFO9=p#(1UE|hYIJhN@-E32E)RxMAGsH2*`6d{^x2n8Z zQtL~mqYJ@n8{~E>&j-CQ^8g2csGn_We7!|HaR3(VEZY8N%R8acgpMA$KO};HD<`?7 zFPiNa|3j5yOJoVgx-uLI?^WQWSi;(TfrBl+R%46v*uX=OOd}eHs+WX^hc&b+IIs-BL~;c}ym7{D%sA8WJ9C8CXy`|_ zjf15uQvQV7vn!|iu{G`=yM0k9U0=A1#kOebmpO<$_nAA z<@hr8_ugZ^Hh5VGNb+jnl4S`@&xG=C9(vDaI5(!}*)c(2wHR%x`zZXc3>qsR5?#E> zowV#EN{}dsx;#k`vWj$`qD^IUQq6Rc9vH!MM65oK>EwJffHyDYKuh8dnU)MX9c}!}L>vD@Cmc?P+ zZA|kioJJM7O`+8+gv}MsOO`ut6{go;KXPG|^^q>+Zq_yTq?;WS5-F7l`b;~-5=Hqs zTlxS$Z$WTt$-b-blH5Waq`yz_gHt{a<}LT@?%n1-e5lQ+krT z+8s#nG^wZ|3(?OgU`OSgrB&L{0jNpsvkB_88KG?k4%CvIH2D@v}v{B!LurJYepmEO!cJs+y3r+`IK zU2(xH&ZB{8(0^&FafVj!Sfg5G*IV}?$3JNQ?>w))WgwkXn1!MyYqH=RNE?P0SCRwp zCC?j=W+3Vd6lka;D82M{E3TK%`r;x{|h87GUY6)Bcj$9%Ke|h}7F6y9}Yy zlvbl_W9Sdq8tS5(b1-*07-vQ2$seM@A5OjM{j$vi=3|9>R!R(<)J6flfhmi{-1S0j zgtYW{W4l6ww;yH5DJew%PWWsr`n8gCX5FKlRVy5dGoGrdHcnif(prbWeCL9?>hRMC zeOKUAE9`kv$b@JG0+?50>0q3#jIdS-=whIQ>6B2l6mx1~670Y&Y1gAbhSMLHVPG`1 z3P5SEwPUVh4kR@c{O%^r6#JDy(<~YKj?rZ!bQ3@20hVLf&z)~W-_^88w+&d z6e20i6&lf#FFw`@PS6wr%x5-*mIQWJpnEq|OC1k0vwT45=iV9+p=0lsReXH*25QXh zAI_3=Whjo6Gu^Rh-RN>E`5-xC7LaOQ zf9I*#TE%P<32`vZpqT_SmYB-GOY~v*Eu_IqisAJ_ice3R%Ksu<>AG>aPBODhM+X<; zfPd#;T9i)bIdYx>Rarav^JVE|5V2l@ui0H=W3Oza>J+6BIcwQ+@!iFHXdv7tP{gzD zB#1@+dD_;3nlwP0lG~1VY`E$${bB7n87)kW$Yk1R_V%;f6)%!?r}@B&|TL z1P=0q)3NP|lshcY_zjzwP~=+@SZU~wUG_(g;vr1fk?8A*922}~*lunPCMUl?WWpXL z!fqZNh0|W;>Mc&SRD%S5Sxs3)EoouzQ#X-(2r%pg`r|~vM3H!e{`Zj`)%T* zFu+#PC7@`UE@DjHG&iA!ADT6%m|23*UHG4DG6;}qPVWX z`mJc_!gs#sOA!-y8d;GIU#Uv?i=uB!#oE^-l(H38CRx1ey$SO=6pZq7iBvKaW3A!T z>oiXuz9fo@G+eeDm!C&3_*5)ZceuPquFs^Nqz2gu=;2h>e0aLoI$&0G-7yxX63pfg zTYeaK6QS+5nNX_cc#p{?-HusAys#qs6?W9e`l*X4h~#0S^pj;3$czh$x3k|I`sTKX zuj45w9*LoROu6S6Xg(|KA3&iZ;o?wd+qnrKQlNNYL%&or=&XETvWq21_hi9)C>gm3uD-2LXpjvd6y|FOyfxAC6U6$tKl=P&aZg7Q_~~1W~vSOHWBJ`e{$FJ_7R|N?(*_VPkoHzo&75HKTBM zlo)DRGFN=)*j*hsb(+M9OMnGAz$&_!;V!#9aOH?8$OtbV)hJx_r@&xINFCGId>Ol-hjdiUv=Ww(|G4A_5uk2+DUa4~3tmRSI69zjRBNtrulzK5h-xvPDSC?ztsuxfj zw53jop_LWnW1s2hT}&Z3Oz2w+J)(nd%xW2JRU;e|nxP7CZcE9zGS6~-3x+|)L#WSQJgvtm&i75{&qi+#N^J45|!Io1LE z&T;040>R-fUoTrR3z>=5oWzXO63?=7ytV;IcxlawUUsKxnAB!>o+{_8C*dT z@1y!1aCD|Xm+b4r(9ovld}@gD1cp=W1mWIwAiWZT6bYwp6wS1fz=b|iN1>~?ouSIk zVV!G7Yw1Q}$7Wssiu9nb$oxb`9CFhe4^@-X61`R}&_~I(Cis#2cEye|4lrjldh5_*V9b7fDk*-k z)uGI6tE6+rV~2Vtn1HhHSppQv%$Hk>hIiQH7>1!T22KuObQ+~g(&swqBiTk(=vI(7ZU^bYNx1>sc-nZf-r zug!>#gI$TM_x++7t>L{YYet-&OnX8fr+r9G9NpRw-mzA<76U_HoI?(p1-MVm5B?0+ zA6nq>GUPf#hQkVq_03rf)Q$}VOEeI7Ar(k^!Dpex$393`i^>f;NyRAVW`DqbE??df ztPBw=wIY{$Q(>m}LXJ@dGUy1gn9i1>_Hy7>PQ0S;fH{$N>hiLA2Z7zgfN<|wr>kXW z_@7{&^cy7>BW0A^YW;y!vddH0pKlX4^{!HErNa3sCe~SsG-2zBnWRhMk*#v(9?l=4 z8U?ohOFPfNpk^ipDaZ1mUs{&c*%a(BV6HQbbk!L4XViR(I&%2{_AGBkx?YbWb zRq{>92#(mNc)={C5p`xaLT??!2+DUeIK57eJ&S&1hSsVB*=d<~d_qCjb3_E;N_qIL z!cpchfY>DN6_~@B2st(j2gjkJDP83~i*3#+wX}ebTwE?qk9ockDRz-8nrGMfu&gx# z1cSu&|MZa>*N&n%D_K@hF$;xE8Ag|zP*f!S%zsqd(JUVOE<%helwh}*a1pAy2=Q^! zdeO;DUv|dJXzz8vjhCY5tF3KquXqHK_2a0_baEW6D=zSU_+=kc7id}S;LowUtJXpU z8jQufVpB0PSr~HNt+B{#m#~+O>K4p zZLP>1Z=5tJS-k6v*sbW_aIZZ>bT1Vg^#rI+gT16quZHE@ib zj6qMBA+zEQGe}7G6L+H_YKzT#6KA>7MM3B@#5V^(#x z#DCc(huEmOC)^Owg4d}3B$2BS+oA1)Gx~zk62C!dvBlPWFUQ_HezFePHB>eoT+Uvn z%Bg@T17cC|D9tnl{ZySfM>uf@WyFQ3+&MDcBFj`b^>}}8tsrw8OM?}LS^f$Zb(uJS z#tv2Lkvftdbq4RVBU0y>n(D#dUVtN)A&j+5Dp#?cq|SfvY5oOJ|Dgg;`?8uE0npMz zTp&O`PSK}TZaVaQB(JeaEr=Qt!vCLs2RpN@3>Rob%*%u>3;`m3*K6Rk-H(Gw&^?4I z*}gYLl-tkr!9n38c?p_&Kky&%6g z2akLw-ESr9Lyc~yZkER6HyZ))<6Y+-$SJiXH=x>-9#7{d z9ruu`0P<-V${y&-@AU5K!-HL(3vjcP2hKM!(RxiQ^l(*nvo{8}#ukJu`)0$IiE+t4 zV%h>^iR_6TdpjyUx9@6u#+q!7a!`6=agQCrZIzCJO`Kt!daLfyeYI8Qo*C2yUllut zo}__YuS-=8zV&ewH)a~vg#R}H=HjPq3se7@V*J8j+<4IYTKISN@SrlbP`g7@nx(^T zvOfT#M9g@Y1|`?4UQ7{oaO_BmiT9RK;=sM51TuK($VRlVA5cj)*=01u@JYHm9M+7l z!u-PgL~C^W-&PxcSPAkoh2MbgjeamKl+lZph?S};=r8Y#G~)IueVH+vgRKLDjVsD1 zXZ0a^OaMm#RPplWG*2UTIwT(ehX*6MocxK91!)8Rxt6GP74{>d z8lIotQ^&6drI^9l*osI_7IQ;QyCcHTAefFYmd>cQ6a@s3A>gZ2Buef;7Kll$(?WIu zszuYQU#Ue|)r|~7{fO0|EWb7WV7*>d2bj;R@g%=rs=$MlI`6ICo*mt5hx#3|^0S0xHERL)YUc`{fv!kFguUu<$G0FjHBOhVRL#B2 z3C!|xc=VoG*D^;MoNOkEQ@))f-4cYr1bT$KBrHA zcqcC53|FKTh4@E8a^Esd%?%3;FLf|To>+MVLm^8nF)W`q`0mq$R$kVj z@jziUH=FFXhr%Myml@Jht-LEtD`=#Uz~WQuLthMQWta~pOa?)g4}{T#j8q+{viUVR zY#I-kWuQZ;-in_9?kdn`q^;X)=B|S9JS96@$PSZ14SrotNrAGCBpT07PS1u|c_LXYy!j5jcgQ~i) zEZ$=SHw)W)HojKE@5L(r0Vd7{;vD{sR{z@WWAbeh~&3{}~!t>P-346@+Q=ek42j&evlYy$WZQU?V z<7eriUQ>2KXwd$7wSPcdq{Y$M)9)1UD@_@xH7H#fqQk}S=Fwv02m^9!m#E1uWOzbE zO&ph;%M?o1hm~ukPM7o2t46_tHYTW0ud1I}@ zH6apQ-l(zKBrfM3yaNAT4-?Wn&xTXz zvakwvQ$W&mSh}oDnHwxQJFy5syP5%uga=+>VS{MG33)O^i%QvRkBSm)QQihBT_OxV zodX2fGw{?P*Meeq6%l_t6__)qH#)bxPt$fJUH-*KP9P9*8fMQNgZYcdsDHp%LH6dp z+o?z`#X^tye^B42Bi%>)bwKH>g8o0zlmOW zXP=$eXCYo+Ag%n+^@%cy*Z{qXF+i3lOpD=2CCy^N-ZI}?p~^{YwSTT zn%;sf$2Bc+eO}EIj&fUnbA`H_r6iGY^OUpivo~Q$%aK{=Ll&C2eA$@^rEm&=gV={p zjdGRX3-c3>VT2M)d5EzFV@k7pXzQY~a4QP2{RGK4>SfHhWymOtE9rX9URt11PBZ{a zBp|iH#=_acZll}Cy^iV>xDI#J?W$xwP%F@Pwwu zh5O#voG9`(USXGknPxh7k8I;MccGj4rMnjdo_Cvdu~)>OV$-&`t3MkYv#1NwoWJZ1 z2sAo}Ewi(>M9o^rB-*u=nfL_)3SziPf^|NDNs)XzMFngH z1BZ9ZD;&ERo#)h}uMiY)Lu&ve<1(*r>7(TH$- z`F(I47%Z4&!5!Po6bfvQtq&C#8G&!t!r;o=G9xj9I;CG6z|@L~%rlD7w!pf}sz+VU zD}ZzX1L%@?*gDR?o-171t%`6(13}~vgWe`NK3`eOl6)ez#W@#JFAymfvqYp4F`d{9x1$@#9AdXAHx!hFc`4faP0K6hn;y*Ew1IYVyF*bW!1a`rt=>VSb zt(-;$D6RkrGpv9)x2EWV&4)fzbuLn^H_!6=5k`el0}etAq>Jo6VLMF9^u!=py<;FK ztm?O8?&4~im?1wp=z#F#4z}GE#*?3v1U4km1{iUeYeD7Z%f-%W*yv5xj#CDUedaLV zo9fL`3+d#V2ZrNY?9^_o+PVpx^j=h5)+~+ubYpFGv)fQ!=wH3t?iuuMCv{H3y5B47@RiQYM(<+T%I4lDkMe6?D}<5;du65%pbhJBB=DwIi| z6g9+V$V2~We(&kW!hf6>{66Z#S78CDZAyhyJz0+la$n;C4r!qDUAFE}x6A&SuX#>L z&Gb^5jklcf>{n!|u`BTN##VeDcQr?;KIClN%1Y$F-?d%I&_+Qnx_seMV1^Gsn+6>NX|9N&<^*hYZ(0Nuzc20Mq{GDApR z#5Rq?YObN|B1&i8=|#FpkE^TVYMwHgzYASWd%{T#?M70r*;ka$_BgB$g1^%Q?f6*V zg`qvR=|HUCSd!2c^?T#H3@GcL?sl70}8yGVz@YT`0O*G~XH0EbdYm#Oah-&N+CV$7g+?ty`M;1(7 zImcyP=%zJW;mU&lpGMfL&W-7L*pa)@3tmvX zZ{U|wdYk3llneEWG(94Pyd?B1xPa!j1!6i%Wv69YX!Pme9cj85+4N_weU=2@V(o+! z;i9l7wG6i`0-CW5(se5I;?(41rOPWQi@n(RzKDt0JlwIw-lrF1sco5q1qD1$pex>Y zTK5B0r5l6Og>(rcdw96XxoI)@WQ0arTVN*}>7xyl?kHc2-Ec@Vx8>U5pv=y+A7wgt z8*8N=v)&b!p%OlGhuAhK?BzhIHfL`f7G@j3=f;>c6PtD(%fh5zt1|)tMPIA(C8dFoa zC-z-#bi9_beEu^SL2si}xlZ#71tEq-n18nf*HTxdh4*orTduh6ksk^{=Sb*l;h;v7 zV)KQ8ixCjpw$LFBs_+J_yq3;bt|3EiwM4BFbazc*x8Fn(MieE~%fjC|6*3){(i4o0 z{!?9$teHquk|fAqb|tm@Z{}=MKn@CR%7JqKQupvb2ZQ*dnfIj^ai$s%{u|a?7~81h z;ykcH&ar7%KU2_52f|jDs*Xx7IPLH-@0B6?ss-qZcY3N%TW6Jp+JR5oTZ3fA+@Q%W z*4|Qkb}w=N`C)SPeUq*2sJ)FLm2(1OA{NtSZ>OGNd`5g}|Hx?SObfQJuF_kg{?YlUod5-u! zou0BqtT+`D7k3k?^+jpGU1yzL#5Ha^{jqI>5ZBl?Y(G{ldL@&hR*pT(G5La(5R-`W zNR=R3TlXR!tJs)PjBs<>feB zOgWKUR5pbL4O_FdE-{zeIh_n6-_#~^sFW|Cv^JhjtK2=E$W9~ZV(w}AM)DO#E{70; zbqBioPO(7`vaIV8OTO4cBa0V0n4Lsx_UIj(?t7WUJjeh(P3%MUb{MTSR*V(y(QPn* z>bcEJ;4MpPSHVpwF}6Z1>b(@)IJU3{Mp6W zRJTxjn1a^R&fmbGQ=ZC3bd`+vx5H2@rh8Np(%gY5J=!qW7URgHaGtx|k92~B2#^{D zXPaaNEqudjljS=&rHKj6i1YSK8bc>39l@mG6ujiT*Il8119}zt6Yh;TwX85D9ef~|$#F9#BZ?Y-m#rrP zZz=ooiI#%tJdaakc(_cbF#6qwd%6Ep}zq zZB=FQ?xcDKmK7m@~a?kV07<5<<1#M}Ve&(EgyqUX5$H;NWw)56yQgzZz+(7=00fuTJ4ZWRT$>+$drnR@FlVs_3kEZP2mJXB=?htT83 zVNTi&!$aK^;KR7ET!lT>3UWRIW?Tp>&q*vleIxu!=!_k7v=N=&Ydz zxW!?NJU5jX$tiz9GA;rNFACxleb~oGGqwf74UhrSSdBP2&nj1fG7EsyobnFL#gDP0 z8phWNdes6|p<;O*>T~BnspV2CgH-qTKy=Qe)Jal_@5rg&+;WVI7{Bl@?jA5mA{$ zo^wGWa7(XyFCBMTx|Nx#<;@2evmsz@{Ax`F_sLvZD^Vp1|1&BfCEw&HCdYd z9s9z1b5Fev{ZG38s6c#-MU!XVsWVT*Twn%$banm0Qh0hooPm%a)xDp0Tz9U8OYk&` z9A!A#j1n<=v#O_atdF^HcN0%JI@cNGsM8H~EWpYd+UX|nL<*vZ%GaCe2oomPv?{D= zXbYB&7y;tddpsY0J^%2lFH6yf`K+Q0b2E%I+guCzU!!5(BEWimLOJ4wc9rWtg}dhP zE{(lEhv_Et|J?c1PStX)!BD4P6w#%5u4e$W<|J@{8-X0?$WP{?R7X`e;tie!J@U4D#rcB8y>A~e)vz1okTCZ75tZK{1a zJgK3(bNp(gObHPLFnUGEjT$XR@Nil9e8RVVcMDJ#A=>!IFPO$Ra#}^G zXqmg09#u`gBb9C(j^bBq$#V|N{mn)HjN>TP;^wFl_ZFz6BRu|X1UC(ar;(1XTtFHj z@UV=;`lkTBkQ?b4fG+dd4T3p2%Ahe+4jyZd(wLpdqBoQyPiCA!NvN;hJePK(S@+O~ zM%Lp}5jK|k{qVK5>0Tl%Jk~S0{?6rV*t|Iw{{dbml#7x7U=rY|{te9B7vQQzY+TA? zmYtMQe-64}?X@3j!G&WlM02z>O4O?_T=*{qP9WoL2guc8-7!?KU931U*SWU5^7D^* zo;~bsM9pSn6OC-YQ#{Ard`4ffNsqDd8lN3~j;qUfLUJxsWd$-0do!OS@Q*Gu5Pd7& zO;ExYZzANp`tUGI=xD5O8Wzk;(D`EI&{@slTMdj2XApC+w-9U$7K~Tt&r!E5L#P+} zYlcdffgHpG=o)ega8t#<_yYH@;?=c6FNIykMYkn+0` z++pt!RJXjv6?s#L<2yqvmwq|Ux@rg=DlN@8lJ4Ds>in=t`pz{dtq_{hp`W~Qn!3+$ zhKZl=1+d5TRJ|=Lz|W*mzfj|$Vqn{VSo+CDe z>@kS^C^t==UmI!BV>A4S^INQGd-UjY79+b%n##}!*aSOkE*Z^aw6ciokgqrms^f;Q z-`FbgPSS(fc*duy;?}1wX)IUi<`tC0WBVJm8XCd6aOpApFpzEst2v{(Y(_wA|m^BJmYh2c$x zwm>fJspt}r)SU>28z_Wg=9iT|L7LtS;r#k5DuDxQmb0pxVBFRCL}io zNY$F*%0D>0VT3m<-OM+EUc3hbu!V)>`1h*9kuF~+7E#%L#LW22G4v(gxZ!Wd5IWXU z)ZqGW@tIfgAJ&&+-;3DV?yQ3cpZU@*m~Y8wwM%6b<~dJZ7cJku#do+i3>6x75UR+{ zhJo+j*iW~;X4eR?kc*LP`a%=%He6HR8_zOJ8l3eO;OaDHiwzO%*IF5R>7s*8OTs~7 z=bJ#+=Yh3SPH8?KltP7h1QD;uRy$!dlRVggCdAxD0O(A4G4G^QwA}n^r}!yOa!TM( zyi0(DSDSJ8SJ?TuyhN=Zq$LhCN=oCMP{pH4^me3f1W<7kT0y>f-gk{i3G<>JH)%Ud zxVONQF_q4s?$+<5#!;0Z!UxaHOp#4jtca3d`x9v;Ch&Bg#a=60?sTR}Mx3&)?V3Qc zE?-=9lm0UPY>n|6*5xJt!i4qx`%Y#tCm=c3jRkgLVRfsqPu)RFvlGfHrcYJK+=~jz z19x7E2v(KYwVk`mcN|29ENSoQzdh*XObOhZtK~&kfp<-o)L_{Ae#Y#aa)bhq-KuOD z7+Ah8Ds7RE3*1E#&4_>V()l^;_K`K=fggPBkV(5DXsMv^ya3f4$P_{{gc_eQB{Q~I z;M9^amR_+h_3vMrs)V;Ut4Z@P;egnxerRq7Ob2lQWoYFEznmP^|IXT2OhY0vZx`!g z(N-?%4JHy%Yj}r?)*9;0pzRBvsNnd+TSg%q!v;dVUZ52XqSc|94JD3(V9rVKBTA%u zl7kT9R0o{p!T=x&P!G}iIR+Ku-zu;N40Qjtvzoc!ngat_**t7DzIDEaI~ze`e9Mmn z+XeS2tA53b3oSh!M7N0mmu-3S|OG8lYgLMXVi}M zyx?|NAK1ZG4rQOEyLHo3O8$LGvC8IxM*mMo3?H)-I@Au3WD0-S)7V(Pi-Q4HKl~3~ z@DPqufyMEKSMn>gyeKs7X+CxKCY;X0skw}gS@r5`39OEmCEnzh7GKCgQpfWaa0@f< zQn_rX_{L5WcowoMWm4;nVp&W)rki!+XR|Tuj!uEw6^g&VVt5 z<1#6aG^s>@Sd|7+ld^P+=NPE^@j}JWgFZ=mE9dfvS4vzMJ>p5U$OOwrdb~CWElm`X3_n||2#0I@Na2GH9+}xrcC^gb-N=f}a z(keh3ekXg1)TF>rP25u#joGLm-U`6;&P=Nvfq!4yfGEHn>9)ch4|y-p9Qsa$ zieb;e{^-y!H1{B>bv)wB5NM6iJ)LhD%Q& zHj8OgzEp29&z2+?ZYy(7Y4|@K`-)Hl*KO zUY!pQ^qp3nfUB!`cKnhJhvlgUUEo!uW`YWqjlM)%Zr`?Q>KjaanE~ws{hOSyFr-Q-5YSGOqaHufH0Z^(}GjJb7cv$ zA)NkJGfBQsy2pPaW7cbHGi?*=cc*OC<|6`=zehO1irn%x;UJ#f<5H$@AWWOinFtA0 zj3TRXay1MMz9OJ}QKI+%!IMEXP8ovUDP? zCQZF+A~9r9Pll73n}(;r=sFTr#7W`FUv@bok;zUBXOBlnEZh)F^w^MaDWXU~a$k>3 z;I&)cpB@b@yQZrRD#~Btq6gER^jNdcJfYCP(VUxPW~;*Mrz}_xafOtp$y37(wKP&B z&J5ywlaAy$$X)NJ3(rL-#j;Y}bl)Ez?_22m>N9}|Gk+{~)%#O@qCAlr$JGn#2gCr6 zNvos1&;f_ElQ{1yJ^^~jpqL3m69kAFaR>q~Z1-w%RW#+#^?iemaH@TNovU4=aEl12 zi7b8Vd6x4oyKV3ETuDN~a#(0iWSg)q@qGJ8&P51sTEswzg>QxKHDC#~3Bc&8mD5%H zI+!B!qNm}Z5sF8{!AnWta--ol+QwklA*oP8dYBM3E5W64-*F)J@uudegKU_~gmX@P zGX%Zn`ua$H@7XEAQ2z?zFBel9)a@*6@=wpniD-Sq;tDZVy#qd#4(xJVHGNIyxZ@S~ z*CZk=c@^P{?)&JlATpnfkoFyiM7EoS5k9`agsyb=zUHx)4cH=`*4-=7ShbvIV?IL5 zJ`5SXU3r^=76xpPr#hCkdX9Hh?GobLtWFJJg`M1<5M~t_z!-=YyU=gVuF!#r(VO!639~FXDMkFlKN0(lRFRYXT7_7cX>=oDnf`tih;F z7nWql_gEFyU&LHLqxM{#36!rn#7|6NBhDyT|O=eVs<#?Tae>}-3e%S@NbQlu?%9+v3e{dR9qdiCoQLV4HX&bZy z^Eayg5nxrxgtVX6ONH;nYoo3`=*A#Zn*)w|UM)dvp7iST z=PGi;_Sk9%h|8~l!KklDE*2>%pseAE*$vr^2vh&&nkO+#QjM zN?6sEc?SzTc+vx!u*>mRW6D4n8MP--Vp}zDbUA0D$1l3u|BI==s|KzH2bHn-`tX-e zVbFtI>X_x{H|o5h=qG|?5Zmz&YW@pIz99vmUSvld`&BPZLAT%KX@I9Mm$+z)>?X12 zh5^x(y)+kCc6)9f$Q-1ByZ|$M`Dl(s4wyFI-pAKEp1|T=mvBtrfO) zbV7mrqlgtwv+TkjEi0gy^%rmah&e@k(R8YjIoc1iUMT%8+Nv@1uGRyG7uXA#ZTkbx zjU$GSrDh>2;g@;c!>-tuuu%fEyEzbiqOG(}KNPL)Ixs90UtK&=3|XUf{WojH8tf98VsaF-eQ^e{db_>F zt1RbN%M`SCwN_L7+w{?~4;xy6iQD1SKb|t|)N|$H!+uh#nS2$n)h2o{6G{o%U08v| z$TNRfXE5Jf4w8_Xt*-dWBt)h(Ulb70O7~gGLip%^wHmr-y@i-FT12ppYsP{+$~$oylZ2}t%65w8Vh}IO+a?Yi<-=Vyzv9}-T3-WR2JhTH0A7U7PT!cD zG$>@j7pY;70~8R4g!A`_hKe2l3vvF((3>nm8sVv9~>acGfkFabb zQ(dc>yV7%kxrfsJjMC9%lK9mm1rNj=7cq4gO(K810%>3!E*fSeh-kn{9=!;}^za9J ztIu0*rZ;>CT{;|G%_EwkF(#Ms^!d(vURk zE@_MuYSwFLu(6BL$1oc$F#by)e6IaaDjSB8JwK9cbcLl)99)+91a*n;tLi(0@G%v0H;V0r2$YvrZ0kgu;`$kdj* zrQ`|{>-^j-L@(r%O#aO-5uXyjQ!~^ek(DRJbj?`|0~yO^^gmCe)RgF0fRgr73JnVj zaBVc}24Ioh9D<$5F9DzQrnpV9Jlt~y9Ha`fpV_sskpDD-?eugeg>@3H!c&65a@73H zw}c1ceM}(<;yqSGZq+PWt{%@=9H)OBqHiThvsEyqF$?)hfHdHzy7TITf>O>FX^cJ1 zb+K+-cSfO^9%5MGh$5FG>AguzoJj{)#->Le*5j+?*6U~ z|N9!__`u-D#PI{xjXrKH-=}ksCxPafE4}|tVkD)Y(tRf6p1E-raSCL7g`8>6y}g6f_&Oy%DE2EZPDHMnRZ;A-UFE%1aq4AXnoAQv)DQv7815GhlpEF@Nd#@5$emtmP{t-kf~faV&shEJFSMnsyM~dpLlVWxZT=TH(|z%p zlP6d&L>(0`2F-;GU8LZ`fGenG-JUorD4sAsD-o89p57kUrLq2FNff-&-3Q3lz3x^3 zT^E=4*N3N#Ghtec&UiL4-86c9R?NdsBB#;jAf+Ac6Qun5_D4b$SS@NBFW1Wy(yp(R zZ++n8#0nbTumNB^TS>x*ix^gn^dXizfch8@?X_BMhrbjOfVG)x53N>~GzZ+IFJxf1 z6V1`tmbSQv9a8SVtgSgyi~EP(U0IpkkkEABdWEFghK#=nIVHbkj5RQ0Lm{Ev9C!{# z$^$TBW`Fih?n)t>dNAQqLDL-V81X`irz)GEDjJ3w1yu=Js#D%;r7GL3(`OW5D1cOv zuTy+I)6I8{G*4e1iI#s}*;HZ#-0yexaLSWZwW8&2*ASAcA&@wdVSQ&wEh@dpG~_J- zJ$KBaob4ZhggMADKr3&IVR}ws9BGl1vpXe$eEr#x&2B6)p`zoJ;Z$7%yZ!iRSE$57 zStuE|2hEk~%5_JbT3-)GX4x&tcfH1cehy@w-(U$;iFQo*5qa~4OBkqt&vHpMqN6(V zE#|L?=ijmIz0w12-_NyD=WTI%YMP~a@D3xz&2ddhr~j_^C_ePo`r#V}nq+$5?Xx&F zW9|K;AC?&5>?_MA?Y&{kENp$?txv?Ug(sRO@|OT`kEu790#Q%I1adr`cz4l9hNlZi|;Q?h!opWF)Aum_#de+v5oPXN3%?a2#>f&tsi{! z2)(<2ba{>vMRZ|98rlC}zhgYeH##UheP0$KEw5O9Y%qBWhyf(DRI(O`^PypHFv^8m zyp|n@rmiR^*t{D5wpx_46x$3%u(vP5fjAgGDs7S96g8jXTNB8QdFztTEbT4n6KV+W_byl&f)=={fz}p2x@tsEC{sE` zjsn5ue1K01n5_w$=4JHxZYYSh3-3{zuDApskV|%WZ>fHddHccIP)x9&Q{Kj3;~>3B zIJF!D0gEl0AEM00`U=$v~Gmb_X^tk|%v56rlptHgqAz5DyTt*-LkP*hTg>##wbSHy4w18}+>l!{rzXwowY^b6cL=$O`*U_UDO zn6szlZx50Hi~{x$0X(m1WSBtTFF0>7T>dFcJ{g#S3iZ{|#G#6?VnIrnn9B=|e=i&4 z6u&5QqnGEEUg=;!%~YSR9yIKH zN{AINn?=DZ!dZ^&40X$}g|9dY*yjw15+=VI0`rRN zN3Ey;$yADD3(+8j(10Vo+e)-D>{rv%^?|)oz5uOY6VB6IT2lBeqVRm9=|NH3LYNRI zV27CAA{Gpt7>JE4UhI1|jx0q9-1k#`?r!05S4s z7^4V9#b7V5xVBvyfRQ1%CFVw0XMJ5LcApXMT8NV5e|`X11Xlpo<-tDakLhX=T{>Ob2OM#prc zjxT@^RuBQjbb^;kIQ7EL$w7mTG!@I2677UFhNAGJzC~{(b;^$EjqBiX5Kqu)C^xB4=2As>dud}^)g*N=#W+y}2>jEXMhMs`dz9%O7t!H2 zoZvN;ze7?6HJY>zAdgsWo)N>V70!y`^Msxc^mSGe8Up%~iMAN%RH2R9l!Gy87ZGI6 z9SuxA!bJ&Nu4BQARVP4xED5|n?#ei@JzB&~TmXsi(zi=$NtLqWe)F9@7Ee>e`sr_; z!Y|mQu0XQJBO9Pb?ej5BYAz`Q~k@C_VRCRb|@y zCb0gof0C$x|5Gs^pJe`h=4$KB4Vfq(_0g|cqG82*+D|SsMsizz#TASv*5+ojU<889 zbp-t%V8H?AVNnd_Hl~_@zAQ~La;a_xY>TK3?C{1?ubrpV7<294c~OIExK#pQ-RV|zBn0KrutWSc zF(|VG^dW2_+3U95+dL}BSCBsSNyBkS^Gk2F;hxT~A-g@k;*YAV{#adMNlL99I32af z&EsHS45rvTFesNZuad@Yoakn?S8y^6Mn4jo4>mWy3kYvXri?UQ8b3EF%_>t!`nfUG zsZYp$YMC=BPlsWwE+b3f1K8Ws9r=~CW*xit4MOui5&0X9WGIB3zt$$b( zOB=aO7qi4-{QD*<2u9gn)b$pv=KMNUXgSSbE-kg6ddX$Xr)+Yb4TI3g6^phPlWX?5 zqUjx!mP%36-xdR&S_H{yKHQyM1^zGkQPTsCU|?O{;!Rk)>MG7VCYbnI@srEBG>`CU za(*+&YiLY^+%!b?HPo$hD-ee4tp+xbNfx2mMp38}A-XxhqI3SakZgX}i$ zP#0W%LUc-Jes|q*C^DV9$`CUR^L3F}(UOSmbO`bDWR6vjT(1fvE zQ8Wl3Mu!bVqV?%qS2UdCryhmK>b(&3Pfawk-m5gXE8!p45}y4v4FY|FfIc_Y=sQ%} zQ%~I0JPuXY&9wnuyQJLQ*^!|f!a1q1BN0=>9!wocI-eUzg=-lG!mR*^OA8>D{<6aW zH}8bX7<1Q-clG#-c_S7JDwXpNy{n}myns7L0O4NdrqXXzO63G_gpfeNVn?EF(l7b}w^=cCy(oOHK$HdD z7S#bc4H!6ip?7aWVbF=pcVLK5a=}l5@r6HP7*NsPY+s~Fn+hdTcMe6t=OwOR( zuR(W)qa|NcyaGMkV_YolFI^P`>}Bh&&tr_PJZIV9f8nEkHuMY_Sh|%lDbQ;phzc6T zzP9-;ldpLbb@oq?1Xov<67TgJgb^rve`{O8efvo*)l7q z+`uj77{s=D(O@u^ya92k!zN_ZU{MDpR};@W&lrmNz_P^9l29trj7--MZ(k2z5>>RF zebUV>uBWuSh$KkUpv^;RmL{Da$D}D6CE-QenI?FOjyGp$CJ2DHZJ_IR*rvvG_A^M~ zM=Q(Pxu}hizE<9rJ@&ys)49fX3@1@hw>X_^EoXwKqLeCW1ivz#jhveEXAVC+(sWHcK#KL$0pF#gp$qQr%?STaj`0kN<6d zBlP7fH{W+#G>z`joLj{Ha)FC?K3mkQJ%BVt$o1_Cdi)xIaIXQ})LdldsX5N+BBTAy zVeT;5?gp`~hULhFCeuZ9NQac@o8%6ARV7ScTtKqG8};|VIHL9M3o^fioW+_~gu>ku z#c@I3zTBMEp`b(xI#2(g3v@NJb#2R3b6H}F9wpZ9(VL(`LRZ8klB?QZE;^dJU};pVv*+;6({|(ZyJn4%$MDSPpQcLTRk~(JH^rBV|bjrrK#RO z-Vn7)Y#Pvy(2zn6Ihvm9FfVbD2h9zu`%S9;JqHi@L?c#4)XhSqO)9OPAiBANK-FIM z*G-(Dt{2ucryf#W)WZ{;lml~Z8q+1??lerEfTcXy3fC>|Iu5mjRwMXuEQGVN2}B@K`pI41a@EJnBfNGj+}Z6IXdC z57$RP#zyZ$aI(u=pPVae>Qsi;0P7O0F}Tkl(cO}rr#HV#Kg7QSIH6DK+S83Nqs2(` zU74rCGofT~Iqxugoumii74G-KN#KY*raev)YgfQ^rOhqcCzo^j_O$-)|G@+4$q*Or z);{0RGt2%aQXj)olhK;S;BgJ+dR$bE35u58iD7Iy#xUwHR$Lg-n8coQmE}Y&rwLK`xfm1cz@*XVEodebmT|FBzY3_7JH9X z%R(*fq!N2`*fh_qZ=Bo)Dmw#@EYUT)dVR6aZUcpqbL7gQ+fxc&*Qucc>)gLegiXM$ z@s|j7#tg9J{I12DYwl+Ok8Jdjzsq(hK*It(&+yD!V7kcGd4U)Mo}{8?4tt9(25IHi z0)RhS@_fO$S`;uwePlp{)RW@z>V-4!gz5R{s`9Ry65F@v)Wt&2iC7=^>V#N1)YK?( z62;d1l)38QTfgyVA(oT`zOoNJ2ap3%EOLpwI7FhPd^@}%zMcY5UA$g+gj`-8v`g`| zmh=J}maw|b!EfTJs`!PKSYI>1z{DW=G1k-*Bln>7RSS|V#S=cyDh0xZCNw#F)9 z(PYo=ex(hiy-HwE-Q`m66qqsu1-p>)v^(94eFC$3u;4xB>m(j+Y@24h=4;=iP;6Lg z&5{QOYu=yOAl$1jWmxr0$+kJ+>7Wsq6;I`Q1Fcm>>z0b=!gEsD>xG#*zOk9Y*HfQx>1PN4pWa6w=2Tm7V&|zI5LvA?_zrWkJ7<|Y7&HbY$TEfyYQ}%Hi1<|&( zmbMugU|U#Wk9N^rdXX)*5DW#Y*$rs^y2+LsTD26}@KZ3Qe@(|z zK5eF0`wH==DjNnrfTX#3Nf-vUpLb`fbdGjoxY5cCHyW@h_!PNZQbQwPit${t3s@Hp_xK+WNEGK{D zX(*YNsxM-UP&^-w6bp`3LG6suH{X8MX=a|7Wdp_Zl~2zswoNUYr+>Wo;T>;Ro_i|{ zCWG6p03 zdW{qWNZ}PXsY|XCO$}rins{z=A!(YH;fevD!!vsKV|-VMDAWE6<4DoMjQosG0YMz< zTZStM!B@MF%2;NJ;K0a1l2`AfqhZh3nj$wHUn7r40}!Swucyt2$KHg+E<<3fsZf$X z3U8-yJt}ly+T0T&WVF!FXfdLEiFGdg-nP(OwSqZ9s%7D2dBTS1bZYI6Qr-*7ci5h6 zqJ8ECjGRLJP#w)VsxA6}J(^#Go0PqRp-cpQ^PA0Y+#~%?rR)nB%|=?HOCM*L#qe~1 z8_zW!f6Bn_x0I}cjZh_*bXMX<3miL8)XKaFqVXI{#q8Qcg&^44pavF5Fmd`HnTJIO`+NPJ!N< z7V8&MK;F~gTlS98X7)$;VQ}~YNB-m3KzKbg8WiVX+ z>@^+tP{UW16M}0ZfmH+5l;D1CX?lBc(x&|pzn^frp#+m;SvStfL^EZfW`ul_vjPzC zdXggnx76wbP*GTfxRPpq??{#NHt((U4JiLN9$?#b4F&kzxU2Hl;A1#X;2Ej2Kbv2` z#tHw0taR_)RnGQds&&Po&>_0S$aZx&JBi?N0F7|h)KO4MV94){v}B|Cw20%DM-t7Q zwQO$e6KruXnKG>^leGjN4(&xEe(Kf-6kY)vjs%gRiy+`pec3czpO|~T9(#d~xoIuI za;DxDkyskJIpBVdZ(nVxhNL9Ca)Df@b=A5k)12py-Xxvjtyt&ahUGjnIZ*<%gI$n}s&3f70S02Oa%ZLJA0mw0 z-ag*&a#>_qS&PQhKpw z`Df0yk*98)De9$Txv1CD4;e&&2JkGFoz0TVqKSR^J&}c1Z2N#T-8M-aD4i7pn{@M^ zSXC>u$y<#&lE~`h1WZa>RjK55@(mZy-5EBV0ZUl~&=ues)++H91)OH2ALj%4=soF0 zPOXSvcdEK}V`M-Q<<{AXq7ryxqzok9>2;$(j}>4aFJl_Ik85>8!X&)acRZ*W|Fl6xH>~4bU@0gMxHA!Fj5y}d~3b%{BTMQAKEV*rQBtI6i2{x5F*)EE&&u|-f7~M z{P1*cTA-#wz6gwpNs#SS!gEs>yAW53Az`UlFtz^DwjQlY7$O8~ zVLzXzW<8@XAB>MHU<6(1A*cZ?q@nErP4O>7^o??9ztioI4!0WX*mXkppLpiN8$B^* zZ;~G@;K)M?`%pc})8_}cCr0VHt`qEWxMS$#yD6D(L+-sX%dPH(q=X_@2Rb@3!B;wj z07@W-)>E(&a&Zytjy03ok3_Ny0Q@qcVU@xr?d60tTNex*e#GW@K0u;K5FU*7vFK4< z1qO>y=l{pL05d3k_)hZT(Ce;!mHAY%Q<`bBqdO&3C+>Tc(kG*Ic{~fOe@}w4B2#Zj zlg0rOsdyAR@VGv*VZC#CO!|DH`iL;31}Y%;y1HF^|GjM>iOzGP;|GnlY#R1j*+~Ht zRoe+pHWd=Kz-J0oX6C^Y+2N8Imo^Fu&!_{_$qRSD;-RMhog8EbzBnPzC@N?A^``BHzzt^=4w8*k}tkB-@^N>rb+(E-{lYDR#{bhFK3fT}0xs}K5Q~qulcL=<}U>?D&;Ft3zY)NHsS9FAB zxBm#|u0sh`cZH|7^)*Q88~p&7_#=HztMG-HN)+hj+x0LWiqr4xrJ-%bGtJ#`M+NIHXl6*&pc*vz z0bHq37PJoYAZfbTc$DkQ-*eUw@JevC4^cE#8uD2yAlhu2-n+yi9Z-(h;GQ7@sa!c7JjOY;mdF*zT4zTSO zkUiGVf4o#R!!$hbM0#r_gSfBa=bK*quI+A~3`V{~vWLA=5uB;!-4HAcQ!2UI5!gR{p=<0~F8uG)I-_cLR z`8;GRM8sVpGZk|L3i<9cV0e1+pmB*AY&&WK#b5!eneb^T_Lyb#uJ)MUkak8C+C>fZ zd6+X8sLacGWe;VQ>rjiEs1P|o-k^)iy9!m4qu!Kt9l1dZ_Xs&NRa6(=1>TQ`JfsGM zQFK5SBW4K&Y+e)AX5hPD8zF5gVSi@cLD z-33?$Fi1~<;f11h48-fPI>8;myzfmBLaNRc-t<&W=2g^y&w+%w%kA7mOarh&=bb&U zYVlb9CMh`CLJZV@mGkf}aDIDWH6%8l{}#-l4!>Ou*WbCn=%!r@!~x^j zuZgtx!t|l&g@J94o!d9uk?RNAXG#0U>+3zqr6Sy!DB%6_s7w5hn4UpwR3wE+D`&`KmAR z`S;10%u9;V*YJFiGW${mfu9_orD5IHa=i{D(q{lB$Ry}R5zf&*96+n6`r;B+yi177 zi-O-U@46c%fHW!U=n;>7rWqNFpgxDyG>f)XCl*lGn?3zNpL+SpTrk!Rx%ML%dVo{) z3>p&OGKK!yHuv)6|oTo3#Vhlo_Wl0FH9fs5D*mzB++%h3b3iOu12gY3-P@b#bu(<`KJ4{Tc5?+76AO+at4M!K&08Mr2>*sdN*et6R``!}-~& zS(b08E8toCKJ?a8x7@pAl@x#eAll}~cieXz&Bi<&E&W0cp>euab*=xGCdcv(%JIDK z2qnY&mNDnfs!Nx}Al`NlUL0@x=f{8EKHv`#)O3#B7Pq`ec|#hyPM#j|n%0k$S-f{? zF)E}laBx`HzYOsvBswq7hTgAe72|TEYy3dp_=LF>10sgGZ_!>=j!onh=A*r#;=TfT zTYmN;qr#o=^SMZYk-M!zZa|qixGM15>oq`n_`cUOr`dp0akDt2#wS4qA-x zq~COES~BXu!peZ`T}87pALya3WA0 z8HYxxR|7WvQfH&CE)*iXQjltq1cwE*)AXrQf?rp&XN8~3{z2}C)z*uZHQ7SpC4~T% z!FlW5f=n%wuypcr1^sV*zOxmScd?6?)O(E+yvz(EAZG zCa;coHD?Z)iC6XqP9}j~&)ZEHQ0^-L6bf+zClC)aX1 zDS3WJJs|QEm@)7>;l+iOs{r;%D;&|-sMx7Fp~MMn@S^1~A zxrUj(B`Eb44sxnYsJ<_u+}xM?KUD=PBZOO2DA(fOP!5tbEZY`b1llwp6L;5^fGmkTz|pMtmI8U$?~; z5~jai=p3X9tGjr>LOaCAcLON;!YyUBU;@`cbJ&$HRuXf^;m|P=@15`{Cse#9Fu1 z{yypwb?~~jSwn5kbIW44(CIN0dAQ#t-YLYs&*jukCeWM-JM~=ByjBm?A=O1jlO^lF z?g#$haJbVF`@cf8n292sFgVM-#hB z41SJ1K8Iq7th5try#b7;6PfG+?fEClm^d{*0{#zd;i!PS+xvP2l@k{C-E|a^gCdSd zO#6uQt1ksFSAu{oz2ES0<_g67D&(oHX^a?_X0X(XqBSu`!<-{rUna)K!fWjG4su+P zNyfl!95wZXP%KtQv$V3GVC@C@%%3}eNT+B2(42_7O6vc8T|U4Ay0!jgd^R$ywCBF3 zs&WvRqm}@0Pjw>|$|UD=RtJMG zeCT<^6_75#^K=n}#uR#h!Zdcq4`hlk<)SaA2@mgRx=8x$npKpN5C6W!p!M}6vJ`}a zDBUkQE-A*=pd~z%PZnTQA^(`WH zow#0+UZbo&>q0@?{I4B6YvctXXhm>~0T%(NfR|Z@i_q}w)#vVRhR-cl4;(|-dq&+Bwu4YKW=TWfHDL2-E^~cVtm|T#+p~qb(r?s?Bwnqg@C$K5q4*t z(hkS{>Q|#6mM^NH1Usp9o!Wm@jW4ZiwHbKyM5;}FwmZSf=vlcEnF~A^oT~OtDYx-nJD%k;B z#_TFo>%Dr&hM=s!m9Q?{>us-B*4RqSI3I8?x+ZK`JIupQLEXWN6{Jy6!Cv6%hj@Ml z<#~gQ2|VqH6uDaaWRyhr*(~&V$3H;owfa%b7F5kAMhqX7TCHWoqzOWe;9|Hba(=NS)rL9KVrM)1ix}g?6mMVVdeHBp zDMv-Na`=)HR75j#`;b7BRvMao%H`Yhy$HF#|Ao|gPFA?c1>(k*p*=-&9j6KB;^J_^ z7s(R$pGzmS&Z}WKN1jh|1ULB%m~%@WHKMmDJ%TwlUy%KWa_8yj<%D8XbfCD9fp!@> zw6T=g>f^+!5;dEgNcYsea3iu!&A1UfgNs4N?LnF%q$0+DPdj%XFMZg}SKby5*_SfZ z0Kokvbk~W{3vraiz*1ntWCtHosFkc+SqfMc@P(<}h=H#|~KghLUSmtHYJI#RuGpF|d|vdlUDU+4nm zdax@9&d@+zfEm{9%Y=PPRCF=4q*DoR;{MF?02F4gq~QyU4GTnfqOO9x$Qf^m8S0zv zik4g&VbHY&t~yQF;uyoEUJ3jVA`nb%-n*Z$16>E|&;>}|gDgS5loI;NTKEL}G{%Nm zjMHZfjN+t*Cq51D`S$(yC$luLDK(i4K zP+1V?wpD;O(|b2^LI@K^uo@}Fa{X_LIZ1B9RuZGCT1K7%VJkDGOZ`YH#NgDdAkyqB zVAQfrVq0zypqY^;94mc%I3M1Hiv4Zm(Tvp~6l){T^#lrjah!AJ9BT$V(GZa*f|R@= zc4S}~8P6lDX$^?POFDWSf)evh=s>r{e918YR_N>WGzy?WzU{N{fxXy8W!2R58tC}) z3wu|$0jNnM*9rkoq94f`SJKDOw!Tf@Y=<2SH*^tSngt8agbE&IFXQg3{+d#886{qQqZ5`&=9Rh@#m6vH`uT147huV?ik;Ua60(Z7rm3ZZ8uH6jE zkeL7`TuG?cbVi5uwC!$D6A!4lHzpU0qAAXEe!K{h7 z%~7A)HXwP%CnGq`{Bv2RDT5^Fcm0(Tlyo)fk>{2hk3|btg*| zW4SmkoT1MEQ)znB3A+L-a=kq!S0!@xCVI$&v1SEOSCwQ9HzdtylMm?&4ZCEOo}+<^ zYK~Rx^U`835GcHF9xSyl&J8SzoTOpU@B@n^hZ#eP40x+T*(XwbC=jeI=#fj@n)zX5 z?AltbH!mdbBqQfudH%H4-LzKCHT%$HK3s#V*1IrddOt_+jO@y3Hyyjg`u#}efgy@^ zN3vVivY9P~Q7g|x)cZq3CCSOnZ|a3>s_hBRrP#lBW$_!N}%;v-YWSz3s5On>M#kvVN9#K}@JM1L) zfXHos8W)}SH!gHG5sIA&U5)9~7lqD37t60;FOH!3QKHNgGPgu#Cc274x0cSNn=_Q@ z5$JL}JZXW&=LkxJYjsdh+RbPynUc2|H|kCz2Ag=+c^`{QH2|^r*lLo$^9!sw`d;LR z6gFHfInHZ9A3ky9s&2bI<;k2%D!qU#URZ0X*f~ONd-hbm-6t==_7j6g(HAFMf>fst z3{uWv&QINsQLRF&tEuG>nxzjR&SI^?(qXQ#RsUe~LY zsq71gbb#dihuG4Nb%7+D^j%{>>J3~h%^op<_$dbzJ`oqso{BHv4}tUbOQ$W}&8r+s z15AU*xF-m5&8H4Y-_hC6Rn30bO^p3Np;i+k(r)<+^3$;vVpd~uUkksac}@BIZR%o7dDLd~vgCVG!lQ4) zbp=@RBvlQS443lQ0@kIVz2Oi*rJ?V&loRv$E>~FR*TTvkLt4A}&Xo+kO!`3|;m@ z1eo_9(&FK+Hw3J{&%a2|&>vw``N7%#2aSI!-Jz>#k@Et<*+dGYN%QAi%*g^%VncY6 z<2Pqx{1d}o9ixN{rAb=6og-~!C=sNOc*@0tWhChvszg^X02rYoSVJ9tA=edMVU+xHOFx0NTEc;PvDg6 z_A$-%c1XZ)p{3Oii$RL$(OyHQVGpDK$)nR}zD9db*E!*rjNktg=Iw@*hevsek3>8WAIT%U9b6d(H2K(n-BE z6EYYQbKT4^$M`mdQzGg!85aLfDGLhWE&U7e{Nvy3)FUD0HJB=DC?VZ?&=5K)@k>nn zJRc`H^js4L=2H?E?d>^e`Fgspz%j>AHyh*3*V;+oP7~_jU9qtv)93T|YQsgX{PD=F zK*^z{42;tfp>bej`Cl1#wfn@u#JY~JF9Wx+SH^O|!xliIAB0~PtVrt0 z|GBr1hmVVC)FTNXNJ3@_0?lvuTm#kAD?J}W`4bj#Th^#Z6@2@G3$3ElVM!@mPjLsm zwV+d=wcRTyB|ncnpQA#HNk)*r>iwoFG!Kx5BYe_QjW7EpfL32g{5Gq=AGik%gV!G6 zyNn_AvEVe@W-;fiV{^YY@-smpU_ezF*MJO;sC&YcOS0>g4pi5vO9e$C)>#oqiMG@e zL!N;B%HC%*ArO~sp=#b#j?6NBlqn!?&-lVms??k|eD0BZnO+~5aFe`p96o1bJ*@$n z0|-k(CBcZdV2Z_O7U76{h$*Y-RQY2a|BqzYK=Czo$tVY^=|DJb7smGEHi&wXbDP2G z8rg#ir1cXc(9H_C!?VxU{7idR0QG(HrRgo5ma6TlwMZNHCOF&GP_`mkeB7KtS)teE zE!5#U(n%K06soJlx}6}Mid68Ro=Ngnqf)(<8smtc2~qSFmm#~X4WDIFs-oi+Z+^$? zt*MSPBMx|*ceu^{?}7-EJuX|K*!}e)H~r!Ih>df>zNYq26nOWFF9Jak0%SYrO;RP^ zEXTtZX=YU213qa2F3WTW0ZCf@yi?>MW$bIZ+3qh{39rS~%s| zD1DN;D41B{Ls0vY9=V1%Y-vFu+2RP0ZYKMAVW@yNY-O04{HAtBRFD&`7@S0h=!9B6 zQ;7?9wjis3KR_cn$%a=YC2e^qf#gdKa@vp8c>L|BglIEuPXH? zwtn0VZgo{5At?_?QXHo@+J6|Ax=XFgLRHL)!R;VQ07AxckSlUl^686m=NOl=>U0#a z+O6TZ+O-Y3{UfL9H8=%VxUNXwmRnNba9MiVsz!O0A)k1sm4(pWs?}2wpgoy7OTnfi z{Am>+uI7q2?>!m{JlFh+#18>pUlf~OSW5_XE5mT?J$^%Hw!Ymhvr7B)w=`iVFn^uV z3q%+5fARiAa_5)0#HMsHew?52eT7Jl2wh5TL^ujwynQD^(Stl$N<~aaznh3pY!?0L z!!)0pZ8B|47`+CZ@7WCXKZn)@bt!7&Ywes!R{wF|7LL!Sp$-vVI=simko#`x^}@(O z^c8R~YCp$@bTAJM&6Kz-v^kl*0acspFr5coWa!b*$u z$3X-ifCo&x^V?;Z@=hZd2R05$1>L=*L)@z0hU1cBkJ;*1pws>;L=SZ|c|YW-ao88= zGn0{yp>D_PJflxPPOnPay+HfL#OcN|WFvFuQyyTxQ`k^)ZLNLa!6AL8u1pSUM}nA( zBOxQ>rUCm`!yd*xJ2qJs&to^ix-4pCKK?)XKNwZL_&*igcpZfR6r2cdYG#24G8(^I z1Jy38FNSK$2Zm`hbNVP*Ee){Y6nXGM+{7luT6Ei3Zg)mU{ao)rpYAvuG6#2p=K@0u z8`$5Oig8{Z;kdh6EPb#b1PWX)jse;;f=;2ETB#cdDan0eUwC;H#lsDc9rFILB|)hi zm{_c)P!%K7D>n0N#Kce(S^#w&7d9Es;{~W(})u7WyI#w#!4@yeevg*`NCn<6a^jufFN5XYR)umrvx( zZ&!|(vDuPSYES>#^z#K*Deu6gisZf>)aL%`C`5OInbr(8&2d+Nk^(pCG0KZ$POZNUjpF! zX1@(H2Y+wsjvIiB?uI5n!+=1Ywkdw#LOe#CZ}c6YH-6*ceH5FgRw3eT8c1Z<0cHR; zZc)sq7Kp;29+uYv3|0Jj4ehEzNGrx$zeAYnR;=Xs6=55&D4rBCCP#98wfxW13O*vHM^`+^I z<^xLVB|$M`{Yx?<71i1rF6!<-L7D4H_c>qncmOSx%Ov~lRhX7^3%B{ue^?e&_?TE= zC9XcB!7;G0Xv84>E?zj{Q^sJ2aDQYnX+Mk{^G zeA5aCnt}L{D08}VDyo1)g?z+>DXRhLYN9bZH2eNO%xyGl-fP?xlKO)W z_KO{n`wb7At2!>jRTED4AN%|r(Z}t7pmKWj`xE+}^mn;%if;p~8DJw@)>{%n$ ztGt=y+0}3!w+1JywWPnV&u)~n>13n`ii%`+cI{~eer73ZF7rRmhL4(|yt({@8SRBj z@~sR0EZMd#iN)O7&ybOG`v?@NAATN%ph~*{fGZ%PQ zt3f}-ile`n$oBmky9xVHc9WJblra7Z(%BvAESDUQ%NtfKG$wOrDG5l45?Bj)Jm;8? z%(zo1NSs0(f8j-Q_Ku+V$A_0{z-obxk(rZ}$=Um9h6NmBycwY4(wq+(!>1Sj@k_dUE6#?eTlRzLIo zj@3Rd^hz9Tq3^I_12PUTD#KjJ(B_Mp$aISs72x0FnF4YnG&ex6)?9ZIY!_ll;C|PH zB)xpK{)ZP(CVVu1fMSD9bXLNqpJFpuML`T4|-%w};Ip3VkkvNr#XCs%M07Vh8EC>=x+7F;N zWi4u$1JofDo{iqGwwl$GGniocRhfsPtv;r$LZG?CBpqPLe(?noQ7)YZUjsV+jp+R) zpyt>fkVsH^B_fQXt0!DLVo@2WOq()UE_n&D6COw7_##oRp+D>CPv9#iZao*|%{uwW zA160#;`?mQBzx%=*f*e1;X{Rin-i?yPPfgrKCzXXYbKZqxF$W)T95Pj$EMgw!Iw#1 zpL9+!b)j^K=C`|WwHoKjp>AwCbTx4_-SH5y$B3Z&PfABq?@L1t<(q%1mC;`{llMxs27OSI5*-Zy0PW{dseU231?r3!@Y$ zQ|07aUC^V|@e^sDdV37NLz*RQ8D1(>^6WOp`;D|$4uo4(1tv zeV)_UcCe|rpipp*?A0}l=&?&>;Ew4|gc7uLTFnHF$A2Tui$%9r3!V)`_Z&V9j+^jQ zJo=gqGSS}K;~-fn(hs=WwdDl;NE^B(yxVCnqDB-n!?gOiTMaGJ$->4+m#);iY|Wd< zJ9);+*k#e4>u{C=2?OMVDO}Sg+lUN)D6}yKHw+*I?*M zaHz`V+U^}-Zg7mhW2@uUwHIx^u{fHLTSm*P$;sm_wV(T?D{eDFv7jV?5JmZ9^PbqG zfJ-uxlQ#XEFvV=Mb9go8BZyy+q`l~_E_06F*EKAk>8u|*{CEXqVMSf}!Xil5YmMGg zH{)^GRW&72So1da5V#{i*HU{PQ*E%5nl6RKm*B_dr1D$#IjSR3;c-79)IWdW9*jsL zw(c#%EJ()HEpg6X)0Yo%eXq@EN0&!b;G zQ`fX~a#nhW=l*Ec zZsue1JwO2?fQHD66c(MLXV*ZWOj4gnavQy|Y)lEcrG+i>m#1tNS>!Hh9ycMs`dKkg zaZ^>oE?ZVp9ZF)7c}xC~>zDum{PJb)R-b68udHU!<||%d+?i&}fUCE^4G+d>c|fjc z!sY2`&uWH_?QfTNI0Q18 zUxT-z&=h^up|SM2z&a-c6wa?`Und1@Qq17Xo>(v3C< z)(}Y|Bm3v=c`Opcp`|mrRut_=mY9^HGn0EL+-5xR<`moYM-V=uQ~h-eZ0FIA-ek;b zsdB*H_OX>;kVI*-A!=R6;$OLiG2r zU9)Y$3PO9PpQ4zO^H3TK3&XAf{EVK|&c8 zVSOQhDrJ%lp?j5I4oY^&m!A@j_ZVaJxJ*%HrQ;+`&Y+>--3R1*rKx-veFzyidj6QFf<&(F?h@sEb_f!RZL5p^3Ax&nAlZR1Q z>PqfOq}uTRP)bMmkh@4ay17?8N!+)PU8nwd+l}zQkdVpePCfRIrTh+sOfC_gEdH3^ zS0}rN+`0=C5?K5tKBor#DK+gw#s@#&-r59QMk!_1DjSLyl_H=CPBJ1!YuHf6=`bvM zQ*;LQuo1|~5{hC3Jk=$8LmJhUNdCCRj|)@ zM-Ius3#;OV(K^dt3$?vkn$*8(41UMOjd1y`6;dj?_@F*}H z8=#Gyblnsg1mU@1J_xuYFP1pZp*tm`D^PRurU{1aDWi(mt2pkE=H(@HTys50E~tnL52ceFN?fEncV$TgcgwYMk} zT?6GB526=H;P82j$at1P@^Z7OQf@)_1> z{Khz-cJxGxE#uxMezLO9Xw60lUchbUo{0A|Y{iO4=aliQVfd$ebQw7b$ zpP5g{@Wt@+?q`&~z`hBCTdDPCOY z0F@x`+i}z>nT7qxMab{%wQF?|?g`xh)7=Fau(g0!%RA2-_?RYp!a5AIChuXucQk+f zpKr!t$}>i^N{-J`+_URQ71Z*#$myl`^_bXun>b?@rEd?K+Fue`mIF{x!-7b{yS`U>MorPi_DD$*ZgC5)1%DoxjrFjGNvO15m7+-41feiBO3A4$pw90( zD)g%(fG!F|+_CQ$RxBOY4pSn_2g!o0dC-WBy@mLt(q>>r?#&gLk`rI%!JLO8FzfD@ zeUnKQ!F&hSsgge1p&^e$QmaUt6LvNLZcw#6ATx)18e(_XBDUj=k|GR^23p5DnQ355P^Oz_c3R2UH`?tjP0G#ByyM|uT#VF~+)xgL!Eh>rrZ-1i1WQom z6aZtFU^kKyYhUj^)V{!c_{<{mu5x3Yp0&9m5Kt~A!&1cv4*27tvzm!gh*I@8dIK%_ z4zU+bQ#DcFPb)lP9BnBjWw3-zVwVWJM|GH*wPKUUj$UKs@TV4Fg-d@nk&XZKXPP;0 zQ8r~9W($%BH@z>u>=j+2^7gV%`l$pfS3XPY^%7mCMUE#B2T3y8Y|x>;v>HYb6dsj; zc|!agrokjL>ay}8Y4xk z2kA>R9Y*3^TBYY5l!~E_irIm%Ds1XP^pCuz^nR+Wab*+7A4>c)vNE(d*WzU{eLKBA zw?b+Q9Q*=ix=HR%v0aXBbi*Wi{h-ND#)Q3w-;6nj*u`A^ca-RdZA*=$0r%hJEr4)o zHl1^m;yxQylEo0Wwtvu6wA^rL8i#m@K__Vt(n17srgppgO9E=yO-+RP?DG#@W*EY^ z-VhyZFR+3>Y;~$xlp%$1owx2qU|O4myz-6ojAaoI`gE2Yz)-ELhK`TjVW1VP@~PKn zWqVS>Cv+qElC{_Fm9gCQw3?R_uGFJwlT1E{D?vG>`Dvqx&F(`KnI1RO>SD*N)KCx5`uByk5d@+<;)%tDdv-`w#W*Ufr;fmXv<*gB( zwbYG?2d(6HlNo6w{!~eAoA6%H4sy!e#g`@8LPJbE)4RMZq8w0+!t-tvT{1dQi54a&Nt{pFw){W$efN zbMre*q*Y>`Twn#ENp45;LKD^y$WN9;Qsa55U3L$X(CIR4Vy$Ij21e8$`_ zaLeLWMBy^2P2lL^n`H<;K15s7n72y4;J;62_X zQ|wOpY*4{eQrD97d{m9>sU)*2mWGWlHTJZ1y_91`>IDCA&{qMb{BSP!x>2C`-GI*w z_oqTPFB}lu?YrVdSZfV(Kx^aEdxL#!fYlK`is45kga5(#Mq>RCqYa~~>z&vfZjzbUSqp01Wco zF|(~e2oEO*>OZC&%rjsZ5F~DnlNVTl(y%VR9iL45+EJ9!Y3vXH;%}8_r`&0vXPEEe z8#8E*=bACg>g(fLc>18>I3|WLI+WmuY}evF%XH(7%G4%1fC2U%gVa2Po2)@y$&~a z6OHoHKFKhCIrKT5d=CA{{Y>}Tp=^E%DjA2vTFzjH^%Ck>QN6FG-{%v_$)Db|Ww^-^ z-``l4)QAu)_$-%%Xc&=|F` zSjY63h0Gsnld0aT;s=jLehS{-HyG#e(&AY|(|;qKnXlaV6~`Ao2IDt|X7MEAxsaW= zcEBM$`rN3bk+>;jp|IwzWMJjFL*6SE6Bw{?v*|vi_@ozX4;M6$&JhuTU8q84m~0S$ zLxS0r*af>mlw~PNX3S}Z!a4@d0sUTU6D8-{`K%y)cxAq& zZWy&&KXY!l+q-K!k!*N=jl)fOjOEu|IPyF~u^C#Ewe%%D+a!Zlii~#bRKYB^(hQh3 ztQ3FJOi1Lel*&uBsV8$+|LE>k4<1cQcH)yh4G8EF@ra$|ez1PDHn@djo$=p=*TG&-|1uFT=Z5ovBp;^&j#;X?W!PM-~ox?QIjk zzh-hUn+nX4pA`0xo9x7n!pJwfiK=+Xiou%J^vS8^$Glj7UXfBAguEtltz3zHW|}g? zFcU?UM7l`JGvM^{(%(q>SHv{*d7Hc_v|gjHa;T&6cSHjeNs8i<1e*<;=P&d2aLe&* z`fa?BB6Ju_%*g;_fbK6GJ`s>|R2Uz(9ZtQMH4vwlAD}#(@YxN0ZIJsDY(ZFuC_+xL z2Q3)!yM)DVlS2?3&?X_(ZtC{G?)1ljTaic$hhWld{D>gE+p#1bIScK8U5ig*2a7Yz z*61oxK;Mqut&tu8tc($j>fHSB=Pq`yocVIBgpyS)R?V%})mRCYYDB&YGh4@jxmPv) zb^4XBg+iO~2uGLIG*vm$7db40EUgo0Pn#Ts4Nm5xMPW&rp{04mybh~Z@%auMndiwQ-C zo@W2>ae@D&-PXw7r~(elz`@R0Tn|2|iLJ`cpeLA3YgNw@E!@n-!Vml94PCjWY}SmnM_(Y(w~$&HHbuJ(`?yXcHzR-P8|btz3T&VciUaJvU{7PTFIeh$5;a zO{kIU|GAi$yFK1V8Q#qwGW7^R!IKRqiZQ7d2flu5A=aw=&RmGK!Ngw>TVksPfnb7g z)6JMLJEQfq^<)!<{3WJwqnp^aOm7H>9lePIu_K~NKx`(Cq9r-h5AQR;|KN+MKM-pr zde5!tlcWy%s*_hdop!($FhM1Hwl3RR>OaXhq~4Ya_RP(})~OXxIUYQDIjd73t;Qo$ zEA_OfQ}eAOIp;bx2+-dat&Awj{+!FMFYmdF{mlLX=l&LhF|D!Js%?L|Dx?LZw2^2Rl5MxzCoZ`rdT9t2f+^lp`rH`mTSMl9R(Et-s4Vr6afIuJOk#V#MxqBdtd^Y#*#_tLa47> zkofz6G48;&KPcRVRH&#hw1t=EkVN}@o^gD@paYh1_jy;5lpDTyi9E2!6RpfqPlZ`x zK$ehOEFx{y+q<7BEP@!@mUbjk3SPQnW&``eQ=| z8h(^1)UwDj(En_za*K>~*&tBsq`)RR3(MNo-ot4%*e8j_2;q@S@+M)f#xQ>v zG_1_lO@3}2uRLxCJ-u!n6>A}sJ;6*dl@BAu@x z=KarK@Srkm|Ko-M6foZDproCvvEUvAX^TeE6lqOiQzBg}&O>~eIbd)0b%QbI^|L8C>Hy!V^lD=Rvi-|}AOO_cAAP*B94%}HJ#F>%Oiyw>GFhxZdkJ?AE`Bd5_k# zhHY4nR6%PnZu}n7`^)to1Kc*>JPIn=KHGz3omb#e(PSdvP}1a zNpAH0ODy(CTuc}p`kf?S^D4?!m?Ta`sA;F=dqHRFdNGX&X>3dXvS85)1Yx~sR|I}-S5M$-(if+i$M@{#(i}`-<1!-aU zl1JBhr~Tv*v{&P)RAC1=W(5@;Oxye^&gg2prkq-{jjOWyg zMot7war`Cwx;&ASJPq*)h=Ztr>bodtgtH4U%5Q>xLF14PvV4hiY%KVKi_pvPKlV2c$bUSZXqiV0@hUaF4`$ zv(>5Q1G88EDS00X^TNMeQd|+8e;D#RF9M4NlsmDE7Kjy5vI)pN?m?lZhsR4*b65m) zj+~DIxB)uqZ=A_}p`pn`?5;0Chz@!5mhrZJ`0qVDJV9qW?E88Gf<=;!<8@r~Dp^T@ zL!`Ih{cR(f&NcISdcyM=0eyYRP75iC0DSsAZ1)Eb?ICxK00NpPt$QbCPXRoNsmt?p zUliEQJ(pEClz8ik?Yz?3NGHLm3KT?CI$9EF6wy=N3%0hDwKDjN?>K(|HCsWzfMJyT zr+Li5TxvYXmf!OX$#Wx)5MDhm0Fy|O<@`r%S0db z(mKJym{#2I5-g-=aWDfL#}*V56=I+L6^Af@(`KR z!sy$JO-TQ5p>AMJmLhB0v+T=kl0{tz6r0mL*`crR)_nu^LxAacx*rBt;*D?Wd4$qh zn~u_-v|$)4xZ~n7WM$Ywuh8xI&Eu@*I#t#r>pV4g>1(D&4VeFHjS0=szmF(omuNb^ z3t%7jXexizmZrZHOY{4^p2yvH7?R{%j0plmJ}TIbR=7n*B`Iz^o`sB&KzCm-ZJ&M! zbcYEp$C;!1VGU)D0`G>ZDbpsKIh}kx9L0A>JNb%`<^@3Fkgs7Qi69aC9K?@+-Hr|v z5qsJWEvZEs<3Y@eL(!2F2LHGWhudzXasf^wS%;+PIkEBv4|1S;^hytY(LstkjgmdG^3p zHD{`F5%!7&^FNsl^tJZ61eB*0Uqbb?iP+GAX0wyJqJ;BjwEC;Z03xD9s1#HT#y&@r z!cMX>6z==k`6Dj=p%7gZeYIFHgsQndjqxu^DY z#-LA34Bt#V#Act&WvUR#noeKrwNbNreNQQ$b>8cf|zm)(#HERsaJJh0(0oXoR}JtT%%z+>1ReGhpEvU0ISV@A=70D zhS!yijx)9Ej<|l&*G%#mIuLN$hz}ufXmPCJvANFb6vDRn?&DpsU_fZPo>8mLNX{w@P}R%YJod{HHVy zY#_yR{44sm@LG)f&M5>GyZzZWfM!`M&gNmF*#m_cP7l^g_{Q~2Q7`(W;V5aa1>_}r z4I^}r-?AAe;Ktuu(Zt44=9a)6Qi}4bP=2Sh>&)!X>*6~byYaJ0i-ERlW!{#88wbu% zB4(gofOqd462a+EP8UpWQpxWFZZbNL>Ky6Q@Zf~lJ{*yZdHHjc|1a50T8L=f?M9;smFx5f)f+u{gPu5+=F+cu=> z1RD>(j4F=U!c2DnQT_okFKZ5b9vd%AOcbbL*oM}0Fo`hMAj}4Sk1FzYR6TFwI0!aD zbQ1a)vOp^*Bca|YtJBw&M=8rQk(b%>kYf)cZ`m)3r0RDI>N9D6F2%&Q{Cb=Un?O)2 zzaGEiP7XJl;Muot3d~nkz>H@@$RLErCtDQWC(H2ST*O8TtiEoC;eBX~fRPCh+6w*5 zzkbF@7TO}HQ(;YMWVp5eNCYiFQcrO-=t{@FkKKYt(Zpyf2Q-vm*S75mB0_nUF*dO1 zgPN9nqj3)6=oB+mE$`@_9ri{nN<)UIyk*Bh%OB*Kn8?jyJQq@Jc3YX)VS#tnH6N0= zFGlapQIQce_d2NZ;8(tdm_es7U9b1y5_r^NAx*LpS)~YHVPlS*$l@yRnkHfn&1QG+ z`BL;{BoeW8XUM$jgloF!8yBhLu*c@$5?N3%+ZhRant|Nt9l%0Dpfj4Y*Qma32f6B5 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 J00004Sz3sRll}kz diff --git a/data/basepatch.bsdiff4 b/data/basepatch.bsdiff4 new file mode 100644 index 0000000000000000000000000000000000000000..a578b248f57721cfc7e8c31da233bf732ed27db8 GIT binary patch literal 114127 zcmaf)Ra9KT(za)C*TD(S;4Z=4-5r9vyE_C31b0Hv!7aGEySux)g%JLn^Iv?Ir*3wy zRr{vi?p3{aJyl}r60%ZKoUG$Gfd5(!;s1XE03!cgi0N7LvWe<~)padmLu&x9Zb2Xa z%XY9jfbk_wKfXtUQ~)HmNNSPsA!A*ssAK^VS9~5jn3K=M(c7McG1iOh<^z*-uj&sd zDR(rH5dw6-)y;FWd`nK|;vi$nbd8_Oy|c1>a|>;KeWA?cd?@E(7Djhz3RTa1l)(!N zkLYx#5ZO}>x&~W&NH*wJS^becc;2S$zXGH*)=mjz$M&aE;UMUrPeVhIs}=7NitnSovaLPX<4 zX3qe|S=NH0Q7|P%WfZ8WoNLj^3g8R?7%{wuQUMzPWCb9YaL!xL0Kk-hOwNIMXi?Tk zFhD^eQVI;^tB?UEnpn0!qSjb z5#%?(pXJVA4|gw$2-s*%%H6^Am9u#oi|hp2coGvJ$7~ww#xW~;jL9p>SOdMzuxK-IZmdGn;rWaW6Q@wloEF$_ga zTunj*emJTdlh;n3*-fS)A~O=oWomDcN4E5&k##Jn!^KIxnWsZeKL>%ectHN@?-oEbKbQ5P4!%EX4 zL$>#4MPSJFuBH7ws?wx0LNMD?g5gMwNg$cd%1rYdJ1d*uWnGKN-fDQ@b!o10K-i8l zA`zROfnQgXVGL^;lmC*brYInys#gw@D*y=e0+VDnSR~4s?vLAd;JPyphId$5N81ks zb3f9%L5m{5#jeqOj|pj|!Ga~K6iWXY>Zv*e88Ng%%6ZE_UM7l=QZLMqE5&E|V_5}ZWdcP_%Gx>`;`xBc&N5()6X8sY6=k?Efb;;Kse z%vama9}hS&y|nVlWt*!oV0U~EU%VdBVBoXa!w7B)NtPZl{2TK8a0qF4~CUDUPurry|%aM}g5tW5+N70SeJ{&a{y{V`|wp z$Czh<2$!Z)?TwiHTZ6ZF{Cs10I+kJopL<3GQ%&IuOiy083|iKH+=Z{BohE+Y7l!am zG-@w0d9lSLK9sstku&NBJRGGwJLgfh->Lq*c@PQM{ZY|tIsCJL2Fqa#m?{Nq3^UF_ueXI zMKKlIoAT>OSEuT)J=~N=;$#={m4S>&C?c?^B`~VBU1W!PxI-)|IANOa`22n=X9q@% z^F`mu^U>5$&SKmAo)=M);5p!nfZx{Q-=B@fy6r5m)NF7Z|EcOR)}CruUGJKhFi?Mg z$KpPai`WgH6O@UlgL`gK&B~-8Hz}}5@%s~H7!sDWQP#Uj`UR@Jh%=`9)|lu@0>*Hj z0R#uv-bk3m_*+i)vqV!Un+$>Y^%UyfH(@r@WsBCsFUK)$aawB;!D9{8iMMTqUVquGD4qc9>Ln z;7i{;-KVeupWHl@m=#WO8=v8?<+~}|=h4e7j~WD%U+WxWceVu6UR-&FEhc+^DDgQS zG5U~IA4K5ZW=q%UfidR?LSrs6GK{~FOAobINvE}16E7-!Pmi*@uRcxt8GzhBd+p%T4Undj)oav`pmk%l3`Z-S6)v(O;b7B^Re1)_-c( zKNfyfZyMwuaTk-av**MmO_70DJa3V3Sd}PeCe5)kA3|eYf3Vp9bHbaB=aAgtCDh&2q6~unB@n?Ln&-ImeXDUI>N-)rEDVMH(>! z6AS%ciUBzA!my#j`K@kR9HDS>(tML4x6}BE7uj!)cEy(|R|`i3p2KcGDms|19fmd;qLt65Hl?MC<&Ew&3%!B>o|ZPxa~<0dFHuXqZPnn|ue#_*ZyF2V z#N;(u|M;^UgWc-)qjf_D6JipPQ zbev(39^J!5-`O9#i_eKC$NSS+71^bqt6%?pU2y*Z0Dj*lY(z;YjavHj0D$B<=ei@% zV|8On@-ryn_MB7v+FD>c!jn>6rj7^s2_v zcCC;P={J-OGegwH5zBiXn#c=`!S-n^(msyf(bTv`&FNTTau@v)Fw$7UooZO zCof-S{DwMRF=?G|jq%lEdgTX=VRtv-Pw2jiQL|FD6mAaZMwHItX4_)M>Fh4S>?6fF zhnIb?-Jjz`Xnn{vz{bsWO839!FEz++FC|;Nv2Ccng34=a<{zB_19x=HHbq_A!hVWL zE`%G=W?27J<%Xh8i)C;!qPvGQf(=Zs&SIiJ57yYdX@FFZ<)}t?&v6tJ} z_~{l|wqiD2J1eWFo?~(;QLP%bn9p7E>MT6?)OOFcRXTDMVFHv?5D*p{l~CxmM~06_ zsQ`XUA;^DNWwGYOfQ0XnZ{r~9$j@rYKl9gAJet(j+iLjt3`5;M$wX|iq^;TUpIp&$?jwn(*)B+nKA zct!8!71q8{J{yA@wCF;^MAP9Q8%@*b}mhGfTWOzBRX-;=a>6dlT{@UZ^w5dKJ z83R!PAoN^ZpWqZIf7)_}gkSjpphVt%Jv& zd3}gAP4csyPCS+zd6M;A+g3jOrZE7@i$iy1!(+qghxx1p!L$ekA!IE!X%X= z<0}6}XPV!fG0jPr;BDg1dqsnM1)cf0Wnu!QR)Xaq+{5#3C_K2aX!yf9*4NBvWfEqwk6&Yw?STbKS=L=rJ53Es&cT8P=J2>v+i>(a|T!CGs=V@!1c&2 zg(JX(*<{Sve*FAn**-C}UAO&(En|b$xyY3wf%$5Py9}nbj@p2FEfyuRBz;*m8jSnl z_O<%*ZsQ}U2L&#eWv{?RWp|V{nSwkShAHX7BCZs7R+D<1V!zIf?=O$!o=&+Uy(-x( zy8;jaTWNv+=^5MAM<_jhT?t`iE;P-W{p^&PUF<*-2WHu6rVW~YKtVz)N=h6FC2gpn zSZzp>sSyA&&;RnN-!CYlZfTH3e77c&4hgv)jG{}?n+a!`C!?>@rXj^>S}KX5fA$d= zzqIH2L*F;W42l+!%A_<@p)ZOGiwb_en&sq!!Rh#HuMk!R-dHb#nYbJZIo;Mdp$yQA zNMSudUVP~OLvr97G6M#Fo;a%BLSPFDN;n-AOs~I#%+Af<;XTa|bWcKbgk#Z?vbFQ-gJY4P&o1wfB{&om-F9#;jkq>6XB-RVjGqRR zfaoyLw77tQDGPI+{@%7ONUu){bj6)-QH|TkvLC%*v$v(Hdtv8iDMjXmke@Nen?+ChOy&5r5Os*Go545k29uKFX~x#x`@DX544Y0i zrK@{TRTrgWKiPY58L+Ua@shw@fHkS@=4XeC4Q^3*=U34d;u#`D1@wsFPsOdBmTV6q zl{Z`Ef=U_z1P>x8n-XdD*~v@F6;?WncJ;wMj?>aqua3Wc#w7i#-Kz4{wc~lW4qUbE z^rd1+x-x>H2um2r7L^G(7N;zoMv_W}gc%llnVDKz zqAq=mIf1;qn%YoG5)#7P0Hi!Cj{=J#Pc$^VD5+SCBClL+E(wz`ycE++Y*bMeE^i2jQUMGq+F@^~_1f-QTQKc{#$uW3MfbMokf` z&WE@A`^el{%z={)?hBFCU#^}+xUSIELCe>ywaK`q&POZ3wZ3Csh8KRH-Z7&;jAOjK~FL+s@PaSY$mT4Wquc(2MM z_NfEcT(WkaiMoF(GFz_ha(WX(P&HVU2eF=SA9J#|tcv&5Ai(dz%cH++JXq#U#Kh{) z*4Gx*(l_5qX}L~x9jC%4lL^=0Jx7I&&g|;G^9GWN^IL5Ja8?dunS1iY+X$Btl12DM zp)XloDO8RS?sF&C73E`MpmYmS}F-cTudZe3|N#5g&$>&ff|Y>%MAr=2!oI-_Sp2a9~nq%TD)u2t!d`KCd^8--v`VrMKJc#v#XpH(d; zz$nze=a9_qdjW@1npeJ{r#A_(Hs8b;iQ;r}md`maWz%@b81qm-KMb7s<<7Ck7~h=CJ8#t?CA zMb1fVx=v{bB+GiXq>MYu)|X3IHCT~QP~0{*n^GOfu58Pe^0QnZw+ynD6+6c*=Af8l zCM7ZRt`0^#6o_u*>?4w~fn-Yvl0zuL7?PZGoYs_z%Ge8v5XhKdx%ouGXtJrT37i7V zh_(3$Yh)ZMU|uuc z>p)W|b7xa_GUlV0c&bbnYHgEj!S1UpW^Fxt2A2^T$GH{>7AJ>43QciS5n$m{_xG-^WNHR9r42+yCCugRhC?smlyyPr5 z7ou1$tnvO&KXD*U%37`>J3GGWnN3OQBd4{!x|F#2!Xxy8gH&X^ihZUO*$l8zPy~1e zAp8dcfO)7zzF!gVf7S545qDsxHhQZM>YYCt>eMw7T52=A+$Ug20d$G~mag%BsRd)c z(dcbTb$Jvb+9`g+-0?#+om}L!r%SJY{&cVwD*dR$G5MhomYBu!o?W286b=wYNZ1`7 z6q`lw4vYH3mTTYHH@sn$y7BpX{|m;e7{-@jfX&fu!t&BFtrkb66C;cBPrPbD?x%L* zSF_$HRrVm0n_75Q%Hg#*_g?A^?v53fLJz)Hd^8|Q!Oe07VVfX;t=9(oGkAZFx#kzQ z==1)&qa@!+D3^Xx_}!wi;RF-gux@^;Bp!Igj>J4RJS8eLaMmIX0}zWqo(#`BSf(Ip z)EM1x9bRppGdD{FmLb*_Q(~-7l=R7OW2?3gp*>w<=mbak4dI=TEZ5qeL*%Z; zpf%Q-s|;`5`cA*n4?d1^GJ0&UBU0K@G z>0}hr#(FHE=l+mQK)X=FoA6%If#vdXVo^AFIR;RP_B?4qcm(<@f!prfcQdqv{3j4R zjC*YNw_;K9v?ex$%(cv!6SdnDb#WWAW!)h&;VCALPbk--5juNPlHs7e@Akp6)caCM z7I(I?c!O}VB~y&Zh<@&oG3U~iKeSqYrCJe;XP%XFu%y=vX>dUs55DA*4LCXjD_pD0Z4%R>(M?gFhLg_o{TD}!ZjH5}IBk||Wg;caIfrv8~J z!F^FxG@cS)B&o*G?_|jJ5SF9ojdLmCFv>cV+@G-$yyX**&_Ks(HfFL83!!XdB`V8M zp+y3Wl;eL@I6peH+9Lwtw%{^oQ5NK-V) zf91CGN~zX!mM_;aTK}8Wo2obqNfu&d^n_?fM_%m$W16fJHxkS$_Ejni^04h%w74t7 zm@&ZzYhVO6uEZ?n2m&1Alk=r~``wuf$L8f<^LdL@S*jadmikXc}U3Lk%O4{0s5G*va!F&EXHKhKM4A ziBmG;!_rL=8;pSo{e_}z#JRpwQY3)Q^>w*8IiM-JCoaQq>WplTFylF~JESX$0KYjA z#oYj(9h?N*2S*afSQflnXRIdGN$9tHzCE$B!kiwopNm=!e!4j|CiFHlhUDUiG)0%k z{MfpS6eCZKkBGsgkcnQCSQ=DoZY)TnFk?Oew#TgZ$d}T|ApVG8=VZwPdr~Y#WuK6L zh2c_lFlJH_n?oX*0;D@jM+411HkA;4yD@CgXbF$BBr_Zm1+Y=Du)wT| zLfgR*odhq3Htq=#qIQ7TW=R|oa7|o*-k8{*L2fg|_yB`JKpl)In=$>RX?AIFS^~5Q ztamL&pvGYy4VVn!JP+V%3~<^33(;qd6u8acsEdOnFAAbfp@KbYF+|ZqkqWYT0Kp6^ z%LNEQE3gglSbS0B<;PD)ffSn(C|78tEtSD&W3*-MQPsgw;6O6#ya;Q=6;Ur%?XMV6 zN3@%mqODOOdt}hj0QOmsbAibq`-W+zAT|7Pg4#jwZ~Qz8q$8@`lWVyV#A!*5D>B~| zS~MwoA#Q~4S`?sQegtmQQX2|#XAzPk%6So$JxNp=775P;aSM1q%nq`DoWcdhaDfvO zx0H4S4sI(-6K_RgW|$mlRzw14!y`p5GeS%)37Tx9FZezs&-@sPOMcY>rySqKnX3W$ z^>J6>%Cwls{t-vq>Px`+IlaLeoLFVJM0b_Mr~GAD7}RDHu{CldC{i`#?5OnP>fzZk z`6R&xX*8w_tO-@79q6;?2K5MTR+SpeI<^cWwc=xAX3*(g@P)BQtyY4-!bDojS)@qU zSJAG#%ux8>{}i<>fn!+wh=F^exCP5$(ajIqM!6I$M_bBd%Al2Si1-gEWA~J_o>^P) z+U++N%IdfzQxg)7ea1YA-V(Xm(GK>w9TC5o=ulk1JhIdxqHE$@)603* z6=5OU8(__I_ezl;UxNiJ@y8jmANu0GqndFbRnWi@R0+pCYJQbKWr&dD(38>#k43|? z6+xG8cHC@8vS2vFaiQldaz=L)Y4W%guW&F{M^YD@pe3CR^--DZ5~8k^neQT(!Oeh$ z2US=YB_f33MB)SO$LX{Zbu>bX`TG#5&@4q}h`&?8tkA-{r)!esg*NK{MymGYc6%&q zfS?=&;7sa$(=#3GXk;K<6_YN*CQ*2hqC+_zGX3QC%h)E0tQV7Z(zphll5pK@I`^@zLfe)(B)g2vL;qdMW$HWAzigQ2&w{6@MU1%<_5GmE5; zyA$#JRX+#LuZ1MXf+obBYWk1gH$B5_}#{Tj6$ym(=yP6Mhq^yvR zx9|?oP)kd8ECM9*N{kc3zf0OvArasy3?;|=WX*OxNv{5AR=4NV|hUtN{ogEyYa3u!L{AjfZIit z(xmZ#;xnpTSiqf6IDmYFC!szuveKaessxYXJ`5RHJmL@yS25z=!(I&IBAQW3%B3l8 zoGAG@8ZIP?F^Z(KZ_Zh0C<_W8r1^muWLczn@aLFWSG=r#zI+KdH9A%-*%JizK8WxH zTPS5XY%m)pi>3&ho3I$@sPT3(*dj1hK;?_@)qZD^;baxjl(gZR3}I0r352IWgN1Ze zloImz2x6!ReDxDs3zBtsqCIKroYcc7nqtBl;9=!|G1R5AB2pTs=`Q1To>GQ(kcSE3 z)wAW5U;k^N_g7Ac%u$iN7c+$tM*6iTa)Rv}MM@m6sHqFl>ZZ8oCLQ`^K>}FD9braT zJ#HeCc*wlQ%A1&fnLqdkLY3#?KqM4Ku6C2zpo%Mk+2}2DXfvQ8(_e3;uhx=sdac05 zc9zNhFBT(}<^+kKsb?!CVHkumnxd5b70^-~(A*Un%5V=l8j%nEX@l&fi2RaLOq}*#~+K?g) z;Q`Usy@bT~rwYD%ZTMX!gljgyvSS+72cHc8$~U%I=*B&{(=+kzIJkAut!3iXIJ9mdjwR2Jj9agw zp>h``u!N(s8|;Nu^9C12z(Wm3z6cKA&(MY~5wna?{q52_Y<+?G(zy7^!`?DAvkumr zb>S;pp+}oDh9h5V>%l{t;t7r_%|ZIMU#7@aLN;B7eaD&`uT6o7FZm?wE?*_Rl}Wo5wB{ zfk9^$He7h=LjYudK)J|M!duM6z4bFprk%}C`K_GLig-sG=%9#DvGu2AwX8W#g{ns4 z)3DbD^y#~I2a38)v`}nWG#sWH2%6Pvt9vp-B$6d<& ztgq4nvDTNp7lS>=PxHfGg=Zs74H$PahN_dnd?p6m2#CmN!2SmbCGNi4iILIWqRfEA< z@YYCrc}F7&u0^L;bJXssE$_&Viviq|g1?PqB6<*{Y!7@NoMA99g+pt*^g^G^3S&+A z*Y=LS%1yMM@m>+?(&ORPGr039!m3!?M?Fmwhfq4U@df~fflJ)OsPpZL9e(Ok%$?mFhrDgX|Rn6L- z_UO>M_lo*rdHN=cddMy+HUvCD8VtCp-+3}AyI(SeHV^KhP^ZgdIyq%$OwWBszy9-c zysg-%*iD-r>2J=5!uEcd*=<&X7?qJn^AK>2D-s%rrZne6sL1>JF4ZTglzT8NbLN}| zv6nliyE|8QO(lS}n)vnXzi@fdwBE|3NKG-${0(Lt`5Gx*O{-9mG$AQB?m}iYw@o{7 z?_M)8PlM9?Ub$`3dp(7fLr zzC;+4Cpv0*MO5D^XiI@<1QBKuC-#A*$sSY(eM*+GtcVK|<;1avq6_+!(0MA$ebpZ)WOgkx=&z&bkNJ{f7m8jlwr()nA zM++rrxg8ufd(6LE=ocbv3C^!eY*bk)Ytj`1a`NKqw6jju0_wjiU7T;C@~O3td@RD& z)IEw>3)jEj*2AimoIB3WOdLi!FRV#{-31>(sl(knde_%8sUYqW+!HbMtf?6G_1BG% za$=C3@m|MT;Vrq1+svuLRPXF(LJ`#WX@2)YNPC~L$#j9s32#9}Wtf+QL4d+?Q}Zk? zDV!%!7N|i3*3zs1=dA97OOkZ6;>WJgU-~D>8J3?MO$D^)YJct?I2On>49ZszKHH{E-NoZX%gsMLr{63uwRxn{!bc>mM_ z+|{HhH1Meu>tTqLZ7bPE{xsdECe3kBa>HnD=4)Y5IKo&iOijMDyx-Pm_2p3#?8usn zjlm*FjEkwyr3aV4{*u+_{Q)^F3ex$mG=*;)DGZrSl;bYy03EH1`EE;~>Xy+NJ^!}H z>dt<}G1oFtJemwgSE1sl$o`=4HiPOnolh*9&Zf_Q-~-`8pQ<0IgolD8B8$o`@n656 zZiIi>jmds56n5|#?(3ckxxp8YK_j1B9qx5eTYUdRnS&Hb!@XSGE#6W5P9)7yP}s)= zx`HlCdLuulbp7r*{s2|$`gr)~f!GvBcSs%vn;YtYJGUz1T)dsy0PFGB@${Guyk;PY zb<}?PPol09%O*6s4v_m3&CfB|Ii?E=!*VF|$ydM`L$eOPk%U{aJu#|-71|C^%+?$V zI&T_XrkVWEy(Z<*S*Az;OUO@?Yr&wM!LN(2MCMqWgLHy zDJ*Kqz3;o)P+^e2<+d9&t>HSDBaG&I?yjSj=xJY1C?xey{gv;%=R&0 zTg7`F;t64Qc1_eoVsj}_WqhIE$e(L4U65K13+QO37m+FjX7H-c8knzS?>5kj7K>pX zwM!*0l}G5Q>r$m^E6f*IN65&+=ls)!g$2yLBz2~ynxOqeC5wu? z%{Pn9%{jdEUMXYpum_U-$|)JVJlg6;tC03Ot+j$*Z>xx!5^w1Xrp3nqgg{05rYl$^ z8=Z!md0GFl4p6j$;-gQ)_#D8z{y?EUd468tFF6w5K+f?vH#e55I+TmMLarYwhiIwD zdquu*H6`l|5N zp?u)II~Zh3-3+%s^Nd#LG=3APrWG)=Rv{37(s?8=ee2bZxQi(7Zn2liAIZ(kV1HDK z2zEbNHfA`Y^vJ#UWbiGqvHO)cKFm4Fqrv%{;DP+tWe)eQ8=Z2TqQ#^W%Cb4JITo8u ztRPgBf_}^+jyu({F~;As)oCdAku>z)09VOO3VK-|bjx>Mr^Prz6mT$qc}GGi-tSqz zWg6?x4CSBFfSk;8VfY~5JbT^En1K=H_+?L@J#XpvS4x+;D>b6X8Rl=F-F!47IeYAA@W2`5L#l3PrL`L4gi(r0B_itW&x^7udyHU1RC-nb zEKgCYs*J^=SS3|kN3bg269m2Z2`S18oH!!xlsY2#y_G3jE~! zHt(~lqSJZKYMEa=jXW-j@Payn3>1_IT6>1^G3%Y9x}?QjTpVOHnblZ>BJ@_L|Zn$w}z z>v3ROX-Z&|x48R<7k9JriQRK!KnbP6F*?=N%hk93Lf(F~<7t%BA(jqGF^QR#@a)-( z1o5fxO+w!+_MW|;5&O3O z5^R8I09i`MC;nOvRsk{L2QgI6T4mo=b3wSjF(4~1`qNXz9JFXJCu7O(#3KC?-k{2r zo7eQtFvu|66@wfb`&DT7$aaPEn+JBQobiKZx$QI)y}ihp)J7HBKe4|F%3iO>l<{xdA=!N&qLc^3*yYb@Qbp)5es%FTo(ttVkIJ0t~Ew$-I~uhx^s9{YiX^vf5>WmjNy}FSfE~C*{yJU zQ)_EJA-klMde*bxGX93v+Z}Z-|62R^!sXG@^r?u&P|;2 zTvjzhOg>4?e_>0W6K=#P``!8=127{^0>;NcMKWSYiu$~L^5F`+X;>An+JEAS1m9{5 zG@p(u(}vsmHGLAmJN3nsr?22(TM0Va39Bp~|H6yCuiJ{cUm&)zYdhff4qqC0VHOB) zj-~X7A#oWaa*r1j4%7B z3bk_@JNSI?14(UlpnisFxXW$q4vI~IYjuOF7;|-vD=dlkDQjoD9(h!iHtpo_4Y&;x zS*l;Z&HD}p9^=vlFysSw$oPsmo0oUeZFH)qaKWn$tG4Su9(>0?zU;KSH#;xC5Vje$ zP3;n-aK?W5z;l)4QAzj@jru<}l_-1v05C25-)w4^tI}&rWClH7I&c2Ex%1hv&&N#g z>EyG~)3VPrJA-L^whXMmGR)zQ*Kd*{91gq_)3c{~*m zFc$3VD)>mipoq$)_xyzokAHKJ)5*jhxth|M@UlG8;;BY6;HjC&EY9AcWqEVT*TkUR zsB@<&GosUP^IUM{@JFZL(zBQ6(sAc`=SCynQM+MV?v+Pp+h=Dq+h#9~B(C++$<2Uc zuexTmU8d?y7YkFAYL`vVOZcg0-wOWf6FXfeKbZUVoN@-7rlz}m_(c)FtxrGYGoGX} zP5&V_FWcR5J)};LxiSX5Ww*jIgJs_>L0&$`swKynXDbrZt9Hlv;?pUUt3CUwc4?$9z-u6?FmaVzy2It#l}H82SJMF-0ncV%^EkbhH=i4;%>4}X zzi})PPCL9f{Y^rdGJ2$r+&+Q!0E|u2S`I{@Ywhh`tCPdZpmxZo?v9o88>n=Pl!|c4)M%+z2=bDuIq0P&JBcH={g~ zZh+rj9XrHNPJXWFxsL%{haI~Y&z;Xr`sw)oNZV|cT(b7`~D@7inI@`6jL%~Oy+cmCJes9V6b zq5i5@)zg)`%G>%?L9?J%)0OWs-R8!odshHH|H$ig%NxOGMtT+x7k0{5ID!m(BzH{hb0Fnw9mNp1llX z-!8A7|3$VKx4Ja!sc(1Ob?<&|<@XsL6FS?rtuRDktApL)+fKQ>};1~!)%S;)V2+`pj}zzncZhpx;(V^f`<>kaDA{XZk zWCUM*p*|OH+0*%91 znhXHQ_@@ZFO*lJTNZ!ARn}3ZF0BkUfDd#+9A!2@XIAKWxJE64#lEEQgxK1*2Wi6cD zcyHBoP-wFFNXofk`aTim8;@@Rou5>A0rIbpb9VA>I&xh+L`9Ei;aAsI5?+U<4Cz^6 ze6=T<@{)Pk@11n{l6d>#4OI0b%%qOLWyRBru=T1UndJp5?!&V{w1jE#}%)U3<{DQ(%7XJHZ)sJTE$iskUey z$?qWpLGn@JtKEESe{9^l)VvM)H zVCnVeW#M9(NEDMy4V|Z2PM%0CZR!D;$P15ygF1b!H$;oL4gnBb05hKeWqeBy5bdBX z#sDga0p%<~04Nomn)VWF<>GRt5v;xX(qUehy{KS>DB_g$Aw(-KE^WHJ1}7=Cm&Kdo z!+`ItssGv+y`A6$1Z|6F^Bp87i-fV8lIcmIt;P2xO~lQYp{Oy(H;-9~X6oP_q#U+u|<>!-N{#qWbR5{+OVL#4wj1`z(j#ITAH=8{%)Y{k!6ij6Dgr%v2T z$gD48;EVseVJ~R}d^Iv4efXGfIWY_QrPF1yo#ij%Z?*nW0w6l()WhgYAWEjI ziRpu{2tV5<{>t=_G#?uu}u1%wigd`BOTPItU`LOTljw4 zFHxQ9G1`%I`Q$#a-#X)6e^~mXRc5-s>CeOF;KaTrVdCCFGit+kMvh`r5K5D;$hvWb zb6NtqkH-KUfrJWOaS9~v8%V86RbCWB@2&jV#RMoY7lQp4?u0zj5;4P8a3EudywaEG z1Rux$Sk%J)eO4|@mZgF3hkA^s?l()-++Sl_7_Og^0V64>u#6B|Q@mqeEz|rSEZz+~ zGaFpPLyow6+@MD-%ZXLWHEb)=hsNgpFMvIhZ*p$09+y9OiI{aeBOM(s z-nnKeFypL)kV+)EGjD;z8cdrdAI)lI?ntjUxVpKLW_YH~kpm4MrkiTf#~`EpO8v+( z3bXNv{f&O-iV*sKr(Igb63fzC7z`P%aNi>P5F%zz-dX@6l6W$%y7`1YmQb4mzgxy9 zdDs-dPNHAzB>9M#Rk*|6uLZpVv|EBN?WtZGC8%FVI=-MH^eWT zbli>`^+?d}2%Ah`)za58Sw6Gi?E_(tSWw)Dg!Y6Ym{9GpJ8f;sr2cVN`CDmGZ)ux* zc+6P;t8`CEkBUF7RD-~cv6$R1Pjxg)Ec#DVd^LFqSt&F$2n`QWI^c_NzN+yqy4q12 z1qm=*;1FHC(i#5G=bSxaL^*^0-?GYizlksF&gqQh@KfNH(kF#m%@gC*j~$v)Yb*Xp zAK!LaL^o9f+$cBdM37CA+}4p~^)I z#v`c$OsNCk=jCo7!Yw7MISn0LhO3!=Bi;huAKQ0c_M`1n*D1c8PF>^Z_uH8-yoLpz zoxC$~fCtNBFf)G-C|mOx^%l~@xxJ2uv&w37jQz`b!iN|J3@Qs@k zGzeHSeK}!FYAGz#Vs}MfkG>sp_kPK20f%BR1O9hN!r zVHX!~@ELN$xeH9k%eb!;<*{~0ZeGVYwqX+EohBHgDGi^j3ME$uiBskdZwz}eRj>@j zZRcAh@m|4ebIX0>t2HW_ z#S?$JEduyq!+A>TJW6@QX&%}7!G0RhN8jJg5l)lQqo+2n{Y2dSV`bGN2@|ZtT)PZf z$K$edyZ`?c?{&=BrFOzg=iTfcclgq?$4 z6+)GK#q>^$ube^qL|HM(e_x#7q$%>Xk}*@+caZX3#u*b~v0_bNe(S&XcpBThW(Zz1 zd6D^(yi&0{lAO){*{mHom@3mSlS6bl^8Ec*k**;WIzMf~$M9s7;nczAz`*B(=fJSdUo$Iao6SM(oP4=LuGr7D zwOj($5ucua<+xbrn78I|S_CeUSUC1DCUVPHatgz&uPgA9m(YpnL}>-*W6^z$M!#Is z=_R?79=?GAcvNwx_IoX#e!L=gfyB1DL0!wIFsk2aDKdU9=AZ>`du{QaTUbpEA@daf zo#I{i8i!_wE+Y8nU+PNE{h;Ue9AEh14|Ne|0{Vp;xaurVv&dB0L-Al z6R3TPQyX~u-sDP2kLJ6*`PkmL?ev|vp)83qNKWRd8M7GVzi;k;p49K2UBGHBCec51 zHZB`vu16%kEG(n4Fq6IyO(o4%x@H5Rtp-OI%`xfDMI{WpJvZ?~;}rUG0;wsQ;9bA> z4D^{v4h8wQE1W|dZkW}2lUHruS4oaEJH#;b{swiOuS^_SxbbyQ7LnH6-#bz~n?UKZ zFUetGW~PKLk{>p+pUq`Q}bTM_P}Hx2P8oJNX!CyIk*JLnrmb$UrnS{$_ZR=uxrnNNC! zqRe&JDw#vVb+j9#gNCC+Uc67I`#oEu%k)ET~6Ok_D4F^?>CcqeDHB`^V&u&2PVP?&-r6uZ^zV{^095Xx zOu=d4Nqud4u7vBvZhP0y93LAvKe5Nv@u@7WoaM`I`plck|6}eQgER|*wZXP++qP{R z(>A7U+qP|M+O}icVV9^~X6|Bw7c_hbi;_g)(P>zix9 z@|TlCM>i|`LrbPWWN8nfGMKQxS_%K{dF+t-?4nJUk9NA2_mpcDyK;vdg_{qlQNy#K zsH_G1o?@kZ5jcuXJj($R_X^N3mOZv}W6dL<`4?i29^}C+(p_bv-Ak==F9*lvF67%f zRFRH74Dw4}hC9rlhERz~`{r5l6MA<9Bbh2iVPvZZd#)*L)spC>!!Z6>6R#*ls5Le` zp8Zr{DStNNf>fVDWEfgWAI_|nW3w6r#pT6JD`eedcuFxA;u&EwjH#;$cKR~#Pn+K_ zt+wmn`n~r(?7B>n9YF3GPO1n4k3z@sn-eznX-oZ|n^_jF*)=eIJ!6-+biwN{_Pnv$ zYV&HWYaRE#uis~uxEY4gV5VNXfj~W9Q_GR=qXQ891HbGQ5xEJx?`8hjNNZ?*iFcP| z+!4q!J{hRjhaXs!S{6qHXf+sC8%G`M0oRbiNLn6aK3`yCj{+>IDj&NpUF+4|E2 zzx-Kv$g_YNr(&ZJt^Uc<&YmOAMl);N9F=hq13_+8ITDdN2Ss02Nn5u{3EmS})WHv3 zM27i-B#3n&OJu#8C-uMRfQ(kYu@OE^F&Bv#TT|@i88f~_Yn@b>Z)E5YubtTi7tTBn zBgYzRm6khfb`XvWZ`Ty4Svm*gkc7>c6&naX?xI4N zTde6nz$bn`q)1^FWQY}yN(c!d2d07+`cI||1)Det#fSn0`+pE|lA#H3j0h5xiF(tQ_$w>o$*X{h zpx|uJ_61#Z{)ICS*%VEzz7i@|OpLpzM$m7c-)t$0ej+@-NKs!G!NNf2xGem>;{)gm z$XYql{;ENLVg1XLfBP7PyU|{E5 zYOq2{!Qh~PYlu7sGoP^cKa>;~7XOR#ro*5@M==$jKFTl=$$if4aC4s1-@CB-EwU-0 z7#2AGrgL|7&_)SrVUw*;lCL(Z)`XzGKs6cCZ8YU)!xCf9 zHfqwKTYj(=j%THK=s zD7cnXz|EA?*QLiucYPWiUvw6Z#s#5Ou(InyT-L*%vBlT^K<>j}%DE~grb0w6-YT&d zP=D4>;89R{>o@t zV088T-Jg<7#+sfPXv<>Mitf4H#0@glk@*$Bxzg5JDHcrxQhh^zJoWp>zUNq7Y3rP2 z2@-$==2-wNVh> zeqj=0kmAWs{N>fhi$ckfgk&W+NF=zkQg}MiS1fDbzQf(hycWPfFFK_YeqoaYB>73? zri-}*Qf^9Tlfo_f<|+~0eL5~8I4{kP_Z*FvH0^SW8gra=LVQg~WNcC1MV7g2Ndf|p z5)(yeA8^5}mJ%7o7BN?lu$SEoZ5Z{I)|E;!=B8r2FZ1~>dB=o{k0{CKU)&p66G*^D znHVkpLQb(c#CbsCR>zpQvXE3LF1x{tZbq+P z=<>jZz>@`NP@$kBK=`j(Z_Ru4g17eXKM=hdPe=t!G!?WKOw2g2E^4=)8fweN-Ua#! z3b+W@lL+#SEO7U$KY+~nEt_))(bT|dCXt0aH{Ioj-v}m zv7QEk<$BxTkXv|sFGIvY5d2lgI#G7c-3(n-`P{UlLt1qGMZhJqvo>c)kCB`pT@;k( z^OPZk8EO5G)FB{fMcHm$tho>mI}h?5HRAHe)Zj)#ufMS}V!iD+6K;D6Eeznoc`Q#P zRUbHd+f43H%>TjtTt+3C^&ePHxo4eo{^x_4KiD2qg86~>Go!BR{{{66M-NUgGm&=; zdiIn(o;JCJd|mj#AhKzDPXf|}{(vrjjhNBe@9*yqRwvK8);5q zZkCamnPzNmt`(V?30ffke@?;Rq_-|q2xT-B4$qnZ1O)V71$SQ7bzY|b{kWd{T|RFC zut#j)sP@md1#lht?p%%T?(S~?cNPCr@uq-*~>FXwj7jvvMHp+g7n$I}n~ z*cc8DivKF?e~#6rhXHicv$M0gx!KRRy`Q(ecj*7sb?WMxpJW=CpKb8}wEH)^-1Pqw zA9JBHDw&U`bEY-_%z^pu_EeuKOggg6X1;u>*l*q+ST$$SQwVJ zY*=8EElvbC-&qSPisDu{{M0(fiie4irM69R+QX-!on5<7pOuhj;^ChiQTQ}SWgx&* z(xJ@qGTN^s>(-;=x_a)IAM!xagaYu_7*S{NOUev(Z}Up}3GLs>tg@;F%MUw_tA`Wx zNbTx>F5)33@k9Uoq=(>L+S)`2#t?DFw(OW33moJ zD333TrjW5A(TVKq!8mGv;Qn?a8W9DUodLJz_4`X zkNEyFtP(GO^G>X66YbV58wY+58v4;cx!{XZG3U6d&jY}^M~7VR5uxG{`?Q}ITLfPf%w5E}WB z(?9eBUeXl&=K4G08p*p1s4dP<7g8NMDKgbRU7S7M$%tyDa16om1 z;Oo!TRALHs22Rd_Z|0a?5XJSI)x!Zs3t#Yt!kb=D!A)}X0X`)sPoueNA4x2aa}xc zm;jABrX9s%K(`7Qgw8@#kPGw=Qpk|)QV`1}0^-u5e64Nlkenu93(4{3LH4@#pRiE}9$W&1E?j&4IHvfnru=iX_K*1+AAYn_eXeb2<52>ii4BpO5%mD4 zEB*ic3dA%J0!asVr`tod8kD44tyu`5ZN!C+3S&J5BMR|4l@RVm-}Q=!PqJFSb`FbT zm5?46M?_SQo-&=0M6o6OoF<-fWmOS^He7<3>2xi8ryD!PQ0mvZuWuU!?fPV9 zpv3%H_>64_czWjeSlahEy4bEyoezj*4V%d?H)BEwXh+AZ`{!e|T#+0Df!uzTjcEco5TU;h_eD-J3a~nHc6xtUTmt~Al ziEjM76@RIA?o)hFTXRL~K|zv^Gn~cFMry5M_^v=Q5{<-Q^AF!U=J8(q^qS?IfIVuq z0z2TqxvVqK{+CwK<#xT*yd=-|AEaa`S+VNt$v0Z zU-xEwl>drL(hE;Hrg`d%vG5~Q2+FOfyT*g_j zl`a=$JUU2q{agZ_+Af&Ngv<3SD(SL|4XyQzpkjw{tnN830o%r)kXm#X4ziC3Quixh zz<6RE4+tuDE$KYl$B8_|h4Na?Agt&5E|aqmaE04VV+o;p%TM{prLVCf+Mk%?EOgU+ zN!T=yFyN(XtJl)!y_&lk-vxCuuZ1AkILmpTh$SYexNgVai--&O!+1e|bA+ND`Q$+4 zEqmFQK&R8(akLj(2~29DrN6m7MHcj<^Zs1AQ-9r)Y-SkI=Buk}Azr)mw;!PPkuHQF zF1aBhdtdKv#Y(Upxf z`9V%QsF`?_B}bUTST+Uac;(#V+RkE1u1Z2P)ng`6OOa z7#YAgF3k9aU}>ogESse&EaTM57~SW?KP31lF`9JpCE6%8+V_ODvVADHH&iz%tvA89 zIoT?BBM~1U@ZvuAkW6eJUdIWW1n0Klf9RKD&f20rRk^JjU{+W%oR9HREEj8^iXMYu z)s(=NPCw6y_K|1OxIcAnqy7o)UA)LE^}rO1ERnl>Zc|xVVL{@xK$gg`S9)k7s@LOB zFG+8Q95wpKZg*5{wbBAigAojAtY1TdF$W|aE#V%FWjvtUiUW+zP%w3mHgE{EuJVBS zFZ^Kd`aIRozKZD?at_K7Ji~bf8Z%iWFNv&PH%p}Bei#Ax!=G;2g!GK4lzKv+UJX;2 zu|6?!V3{!vZu^isgu$wmp0(NKiG27*=DRo(GDQsHmmD&_R6K<%=m?<2MBr|G9tBIy zApsaU45cHR!+So_x*WI+55_>jln4Uar6l6}GHv~5-zfZE=!;0=l{z}mD&dLHpLa2i zs%XelVsoL{J+V<}99Y}OT)L1}jubQo(<;gP$+EXtV1V$t%8@YF*cZ zyA&|3u#}AMDxO8LL3|G}$^loAh%r*ZHq4N#lZ)vhn|@gmhiR$Lta_eYs=Z~}5pbaA zB$2=*iX~vgP$15%AR*h7-ANDs>Ur$&H`IM{`sBfBWJth*)sQ4S;f&ypGVd$}n|4It zFQ6^uB->8^jHnhf3*unV4n&o%gtMSJQ_C5W;JLvLG&{$EW+}fFUSctF;g_gB=eT-gMI2O>>rBKumeZLZ$$6!A zc0|o$SpH#>UPc1nMoUf~-jSzYW&;hL!sIE%)RJOzCEc73! z2&BTyFe!7RB`r{Q%@KPp#UV> zXc#+qPatV3N+deMeeNPhP|XN6jA0mvl%ybat1WdJ7?HTH+6GH#W%D8-!GMwpdlMa5 zlmbfn$q|{2@**t2*c^bda@xl}Ugf`^VE?~45)u-ksXMLUkU{~L1Ov6+1tG#aW&SIm zk;a;f6|vHdzgc|FMWjI1lgd=65kl=G4{nS+TZ_SkeR^5k~4LUUdd%lgOdfAn zAhKzTjcjK%pm#q~JymCf9cgNe!4w}|o@PYDSWi9KNn~vEa%$#W@mzB$j~-Gh@K=ut z;1DN2Jiq30ol{+7#O`ryrw@IzmKBkD$QeeoShgt6*H|7`-LJ`FDuE^C?Y+)OEyr@~ zD>4r(KnbQySHnxJb$Uj(Mpk@K;);Q;A`7pmz}8kr_?BVr;We45IO5c3~b2f7iMoVt(Z+iT&8bM&~f^R-#fz};b| zY3j+5O#TFOpkP}tW|&9f{J%YDSZl1w#VW`FK*nZ{lH)h+F%cR@m8tilGc%Cbu=VF$ zI|S^wcj@=ncg?3qEBT@=lj_bIST~E3snLfCiKFqFxTw7(!y)bLAy#)Nh0XyL=WCI% z`jaXJ!8-*52~;?W{mlRbm8~z{1CqQy655SPA7-M zQe>Kmu#B+`j^i5xs;bViwaP0>^M^~`J&?SmanE_2ys=5_CSWuSAa z{P9CLOoOYe(pd3$CU_}M0j=2e&3dt`xbsy4=`yq39c_)j4x&|VCb#Q=U=*mqavXRG zl<>=tPSTYGBiN&o>;7brj4TxuD*}t^J&nU}hA)Bnh|X|VW4g^j8u;ftu|?*F=Y?sl zeV69ChB>sGg{kSdmmz;_%mC3lu?d=ehb>B$a||()-uOYD9_Hhg7yolst!e6$r7%g% zhK-a~z}oNpP3hMYYuNhD?GgdIG9*h>Ow7|`VTpqxswAr4YsvPxMXIu@y72A3dEyFLEsSZ`66LaR&OJ zK4#+lkPoAU*V9xQ2p1<#LX+c?ehQqNZcFY~U~~yk>VyvgF<2%6Q`-31cwjKMleMQ62f$lW?D@+!~vZs`rhiL3tvcwWhr}k#@ zeRb?dk4bp71J*t2yCr5=PbJr%O#Bf+BUpDt&@((Y?kI8zn%XnXbSS=idOGgw?(DeV z>o#h;oM6;Z$*Ijdj=av5AdWW|@ z$Odylcjg(PIojPn*s(3jIM3t_phPZkR1lEhmf;n^X(hwt1}s|R(M{27I41Sz?L|NK zC;l9cOj|z~P?-Qal>WcTvG0CX7@7-{cN(^LoXfKw?zpHEa9eDeYT@WW(IML#)+jL}J-p%Bgv zbGGu|vS%c}uz=!?#h?|&^v{phO;JG!UGU`Td#CM;G#AztD=WPTDu-Gjl?)|=HWVB&Wavs+ z@SwX^`^)cMp}MMN^{l@4myEM2#KSgN(6v5>g)4Beb$2~VyVEmGQ^qv-adR%;cZ^)U z>G$7~H{!C(zSYSreHp}1+L&mTjr;vT=J^G_x398g6Kf)KAaeCyPm9}CHX^{r?HBj7 zo9ge?_7XJs#Su&py}Gx8WX`$% zFzpLm3WZ+W$?=T+9Uk}5(+bWg?Heh$@K$@wQOr^avp%R!sdsVrhezBsM;gqyL8@IA zvTHPm_Y#b|_TGCj^vT1%d~r(7E&?}&uq&=c2gJbSUfbs<6YjOY)ghLC^WW#f$scXb z{fiG=`YyJTk3AL8wdD&achP@R)NX_(T z1_)t6hG(0Af~yyr&-CsHndx}?W6l|xXdt5c@4c2h_c$*tb|NDHi6+NJKKV1BF1Y{6 zD_($H<--40a+4|D#v%bK`;9p}2Vj4qKXI4yV>aS`)Vr2313Txx1$*u zeZYSI1-cJE#N1Z`=tCIc{9bpLB23R!ECIocchI|KYb3_)G@Z;5V1!kaJeO0H;Y_a` zv``a5E+o!<^62T$LqkeV-)6zwH5G}bW+LDjOjrmpu7C z=b(c{*m&>Ox4$IZ+18SX5JIQBaFl~{_5Tgx_h(K1DXRL$o7^$X8#mrpxtAGHYI;ep zD~;|8Ft*dj(3W3j5p8g8!rc>J&daypr=jF15OYMMb~yBcbm+bm2E7nBDWsOMZ|Ujy zTNY8M1Cu+*0_S4YMwoBi>&N0WPU{37#PT=^8>4fZMIuTf|?$K)GVD^y2q-vF2nIf8-^?*g_} zM82ZcFK#L;{m!gxAlgBWEY*L%YC>icCuGI?CDqEl`cO`WK;xqN7jEHX1@fak4F6X2 zi-$gb4u$+!j#nbeh9oSJ(sqe9TL{hQBKp)NAd2$yq`~3t#Pi4BZ3`nurzV_5b?@3L z-b?@6-1sh@BECqD44P2&RWL8-0q?MU_5gV<;+*%s}N;|TD%d(_gWyYp84-j zo9IpB@gp{N{))}#o{3`2Z$5rWL1_*I910(BuvNaz)85K0f6-a?iA;`XMTFxj2+>-@ zH1L2ttPVTMiI82^gGUHaN|J)Rl8H)$PaI`M8lhl1rS4xQG2Fesak-0e7^W++mL;f4 zN)ox|^=up5(Ws*YqSP(qa+RK-Kd5HFD*{drXs|}Xv5>P!+{f-Oaxct;*RKJjg)bk- zE7%LUxCTa9LV}ljqGyqE9$=b0n7SLC;uj<+VzZ53a{H@ODk&x}5>RB2GZW@9c#gqP z-7U_$6}+1%MOgkNNE~z0rO5pUVu6Kyuy4tr&je@8A2^?~e!eE$9!s6QmFtVnTlC1w z3E+M(J72xn{t#I1Aeu)Yu%B<%uYUr?vTy13jK)oqn8YHw^U|C@7^4@6DDid@WE%#6 zD*5a-q1g1ppA2u7$N3vnvBcThjlwvAh+fqxCl}XVg!rCX#uP%r8DXcGv5wr^w+7_; zKH9H-VRunw7nwb6y^@@acKa~lGF8{(-2>b7_ca3rgYrH6tbg}4xVLgQ_9`gDjbzx^ zAFM8M@uTPEbL~8Cr9osF@*mc(etmOZE)#xNFPSV8(G)t|#|i3oyD~Ht|10~Qzx+8e znTAr>Thp<12%pj;mHOLb5e*lPa>}<{WwgP#A4=r_vtK)a2n=6=I_)$bRk{ckcW=v& zgEojouoMeQ1ZgmNUDY5Cj$IYXyU*rfi^03+JQ^n-|XcQ^Ps3Sly=R*XbcxDj{q}vV8S|bJOqvqbL^>nT(h7 zJDw@b;#ntmX#H6y{-QjjjJGI6X8WeC8FD!IH#65jOc0aNUkp?8?3@&uIK)nk*P#F^ z2FaBd03Z1~Q@n4{lS$q|(x{XGg3WlPBk#fE+b)XeWjDkEHNwaQ@U{n;*yVz^xvHmm z3IXKJYq$T&H=s0QEa>*ICNO@#HaUSIu2EcgclY_Fwp4Ua_}(Zg6m+rzmATxKd*wPJ&V*k!T~#zc7zs+%Ck+Uh=&+2?xq6Wgcd_e@=G+e|C!%8Ax^R7Lm5_Fh(#!A&OlR0jCWHBkuCA+VVQ zLI{PWQ!z*wjWglNJtnWgM!H~`1i_eG_zi2xmYK2`T`}hUdz#N8VyIIKD`QDmjJ4w8 z>hgkhJ!QB-T@Zi48v0bWSUTsBbkX#=?>*L2TRQZG@#ie99f!AzJJom<$Rj|y)g{0l zFp{1|Wt`$YiE~GTX42yNZLi!~_LcN3+Cdf|x*KaF3!3q2J7#kREqZFbqhSc#>!t50 zByihUrS|WaF|8}BB)ka?49O54xj;LZSeG-IO!@1+q;W^5H}{kzH4lNIYdqy~@KX4B zJf>!b%PvXPygHG|ksC=*mY?7q_s-Nkz@RfHl{U`)uD+*mqHY-_=7BH}!2h7J;@0hB zod4u5e@S2hVd6xYx%5sY2s!OJbu|0;?T%~A>yKW}GrwutBVE{ch^1&*h*SE4Jay%Q z^A?`0KP`LIngrCQL267l4u0NQ2DdREC=Clp3KE0;#^u#U0EQC#Phf6<-^s)&MB>vQ zcVeF1U@6>!NKotq+6v8nz$9)NSjlb#3!3X!jWe|GWRdl+(Rj@E>+jxr$ulEkWKa{l zBq`o%YDL8A;UN0%wxU^i&Mhh<@;zz zM9og&Zsr@>Sv(a3qWgM|3qY&A6hs_rUp_zn;uEiobNy{`=JKvhh;P$YNnUY>UnVyW>=qwb7rj({hrjPV zt{g&?pmcB-y0qgZit{)(E2yCYpp*mZJTt>d`ti9Oc5riFpKA&Y5qN`w2O1c^rRM5{ zB#$_Jf8$xhYJ96V@gX)OG!zbHha*5$N!-KAcu?t7X2ryD#?Ot z!1UiXa_TgxT`MshkumoGdpY_5?#?39+=VTY^pmzs-HVKS<)@HH$mw39*e` zAv0=3hsQwRLty_x2eXl`Z9|^V9lqwjZfDrD1rojsfW)cl9nx5Q}aWbd2K-$M9X`lfAs^wzT^XJF?0uwc@gl{EOZYskip?*PjyV% zKj8YuUgq!Jq?c+sI+i-DTuGw=l{TFUXYjCg!eS@=y^X|D0TqM3kxSLIbO9h{{EYXv zor-@WPfP0j4PlS0gCykJsMM!Z$Wlk9nyUAAvBVq_-J&)I80DRCp)DjLlmkY=(S)!K zM}Y=;N@p8@?#NOUez%--)?@Ml|CxEQ1?>L)GeoxKUwsbPiO$Y}96U}A-t4I8xWd6t z7Z?jIJ=;Tlj6c3P*@c94cKVC${5H6KcAtr(&iF%lE2s)JQBwfOW4Pv(yFr}h+KN@< zpP89O2D_H@l7J6}6pkD>WRCEX;QUvJ;eqxTAN?ip$@j^f*MarAT_^jC-2Fqys&WqT zU&*nVxr37vkP~qC1?+s^(@XaSBVeFm~=e8<)>&buAMpP7l zMc&2yb9=X@J@NA2CO?xO{`Ly0pc>#0t%kjE6v*W-bH#r=Mkv^8W5Mbqz%&v3x z2RU`D(WtN0Aq4lQt5&C%MtK%RfV96Oh!8dL?)sQtCdzU8D1C9}nnu^JE9d53)uo97 zEkw3dX;rP3A)frkOw|lewfSE+j^d|!fm;Q{@6TO;X`aZ0$N0>`yVtEO?) zY3CKeRqZlsA(ZID*Hv#DI6fd7IdKy+NK3EBUJ0IJHMgd7wt3B4(aK6 z#2zDpU{g5r)w9F9k>ZPlCE>TTHJPyh%dF=D`HpVPYAQr5Vp#-fG-3f)^5weT^IFHX zGy%g!3d`sYc9(x#S%)|VqU&08>6&P&@Vtft{Y!_6vB}Vjrda|E`2O4nyhkOkJs6@H&K-sJ*EUSG>KxC zM7`!%FjlJZFssi|zX}tx0~#0>lnLraw2g`ap4%RF{Hp#{>zjo!I{vBspIQ6@f`=@y zS@BI23NFY3F47!;%pkwhny=@oNtIJWcEg<=#X5LNOY@1ZCn&U|9GS(TFnNXWF35wa zJb_};Devvx$f~Cr#k!(N&ha5bpoV7HlkZk;II{xr z8QI|Vu9K1c#-(N(M$14;HOFyPLT<;A`JhG}<{)6mN#?2}!%w=Y8#Stqo~P++B)`3@ z6)(oHB2toJwXAB!3LnpmG3zq#ZA1L6Fg{eKAc~?EqeCjFh{Q|($dZd|{Nnb>(dI$O zLRZBdlg7^Q0wh$=&kRtP7IwHgw(>NdA!N|Hj87Kckt)(UYKH!bB4vn*)pZ_GEB;VI zX-7!W&5G)URTJE{K4pktPPGLaEw|#^9O->9UrT4&n9nHADJY;#v3)9~_^G^7AocZB zb&R0#(?_~-dx!Hf79zRaI`)R!Hcgr?<{h? zZv{Ma?uVz^rl};cGLPAUD6|KTz#NbhHMx<{CZAWFCqc zE-<JGIt|ZrMcobk=KB@W(86XDC>iSA5Cc)urys>q&vKcC zQje%emcV1$<{!Bx74EB3^*i~Z^5EdRl0HL$&4`}iJ~k%od44A)0W}(Ch=8H zLo!?$Q7cw8%+E4YWm&;!k}qn3>4YfdssvNaYeVKEKW3fjcJ$kdxwMb)YAJs9<(aLX zwf)Xh0tF0CW*!Jvwh}QD=)ATqLD^mK8wxD#>2jZGjgS;k#KlY}s=aoF_(!_^#yu9$ zx9dl&QD3>M{n~2^FD{GX&JETM37FO{ir*E&bQ(#IE0)Z(4Hx;UeG1@ovtPVnZ9A}P znCpuQ)6_b9%h0=2>+#OO&-$7+At=klczFgJ=2@HH(^Efw!(Aqd0tg@&;u=qG85D$i zqa*!k{zX%%guvLUT>I4Ptn=Bj{hlUhIJ~_XO6gv7&mM8q$P!s0!c5XWnQU-*xY5s| zuY#pA@Qmy97K{?l8r@Kql46#8*Ah71gE6a;?}tGJP-^3pgXkyhEMJv<_Y1hqciEw6DY_;Da0+(o;4N4uGDIV^ScVp__t;YlFW_@fzT{tiC! zGF8WQ?ie1^9$&-oh3()!q%MNt)*%h0GBV4!gcge0&wW%SNCm&`e?_hy*LiS51}_0A zRaNZ^0|h}-VPT1Y4L(N&;vGAo>FBIh_4a+X{RyparS z%MHn{+r6uh%GMK4cAB&nSo5pEiwE;i!;H@o8n)KuLs$omJM=^38OeXJ8Ag#H{;f79 zE|39HgyjqUqv%a>bwrDHqcbVC*tz0e^Rt5ZjVE`&Hli~|0ahHge)hM>s4?9WL&3>o z?kz+H>(~0;S8)w7<9O=&Z~s$CVm<)>&+w9$J=Edep%^T!<#R7YJy^EY!g}N%KA5K3 zQ8z166U4R;4n8uD{&61PTm`dgylLe^;I>1+ukntSKFJCx=a)2@ftB7nwKdYT*FXt?QcKet zs^3572!83jq<&aNjf3#q{UKRV;lb-Eo7e_V_W^>3a0oCIq<}QAPrJg`03pe%*GSGiV!*A};I>lj`Rs)rpL{IWcNPO)T=-?1 zSIcL*lkP_;Wnss1{;&YC?xolfAwgo4jS%XkX)Xs@E5Na9oZmEdLDsv-bmZ#5?eKePm=}K6!;geK9shT~22hc@;5*#4 z0@Dv<9|Y^w(yuWCj~(55;6n)}tWk#LwM}|+;9LDRCcSX0ZMwnvGl|h zmElSV1%>ldB3gu`f}(B&L){C(5r%XjwnbZcmbF1R!>9IU`Nd%oyz;U_^}iy>_kWTg-iltCgMg*lDNvXf~7WL;WvI~3}D1`?c3(Do>Cd#@B8eR zu3|w=_mN_|-YfM86i{{pLM`loc<`aN)88!L8}kZYJwU@8lP9~5Syu%`jjZ_pLNfO@ z7pg9MBd=#IJ-emGfiKQ48`R(yG*!r1E%ytdld2L6@J5bdQ^MAC{@mJC_Qvg+mf*~l zrDal)_$xo)2w{oQ6c|SM#GHb5mr+gF?Ys72s;_%4P|%#5To0y6x>+LXUoZ-`(+{*VRjwkX%q1b50`YZ%770I&!vz0~GzIYToN8@qCq<7h86Bqt zdMqNY8zx@3D~h-M!XCG>OdHut2>Sr1!hk4wKHDKlCezM6ECCMVi`ew$@Zc{hP~MD} zZy?W~dQVxqxdPkYqTCVmTv%5gDhnJQjMewfl@B=0zE7V0DBdQDw5282YT&W$B3 zSrB@?#S#dh1nUY06AqHo+32z>JDXpY{GdNl0NqTL%r`%ts;iHFc%QFns{>~G6xiA} z!5U?bs-7acPXKxsGQ;Iub|F1qbq*Bz)V6nL0n)XQj9TDmV*Ftp#x-NA1#qBX@c_rz zc-Jn2N_e8RzrSBwX(4@Jwp=xJxlS}+Vu;kDr8Z35NnEQI4J?nINkGA3F(a`EF|%sM zmP1+Jf=z8{-2f9K!OW2}s?riv`*y)WIAOn{FE30Tq^=wLX=Yo7Oq~hHFq6LPYf}>( zR66HV>IV3tZaeW*TxD%!j3;fC|Fsa*Z95&PHx!Fv=-2vp9No=@E*FLzH)|0-nha7( zkTDkG)q8f!YP|8`tFP4^zV?k-F(PD02U8O6T;DARs}3y951lPYyvM4@$B~A;RSlQv zaN2MoUK`~<&|cnFCqm6}}-T)0%RwEzu6cd~{izp7vmC)CZ6FD5=A7 zUjzb+2(SjWQ_ukd(h3HP;KtkH#!OSlQVLTr6T{)6IU&YHzVy7^R$LSY4)M$AKaHZ0 zDggy&K;hV^NPy3jAr}S{>JEmeZ?S?vl0lIHNy)|W?8LjU<%xOSl|5VZ5LG^9qO@e6 z9R^{i7lU~#IZt6y8xpAqM28qJ_Wc}N-8G^W5n{4+-0rG5@k`gV*qoAL_9TQeoPOQl z*Orf*?UOun9_--<` zVEL=JfZ#cTbRN#MOq`EAxah}a-$#V5Id^1j|F)RuD&YCo>Cd!inTRC;S5(Mbl?~B2 z5{>EbV5x?g^LNA{cZGJFtalj1>gTya zak+GGZxyU;dIe)ulL6l8K+!Jo+BFV5lT#7lyg&rUJ-mPHxO@y1Lm)~%2^1;&Ft{7J zfLL6a!stb@%e49W?ipWA5^*c)X=#%gY)zl<2g;liVvCDj@7ym9RD;VJh{?qI+vwa! z`Zr#G@ht)BOWJd-cDo1-cvkxr&G6oVs8f`TD}kH&e=gs<^#}rnIo1Mzw3Dny_U1z| zF&x0VbnL}*!g4Z1w>dm3yxPZ3;G;`DdaTo#r`gy_ZWi#mcF#6Y_~g}LJvMH!R&nHU z<)}@rQXM3!$m_^Yc*=d$7e5NxsW8I3yT7*4aBat8#D2#Lq$9^T%9qBZeKW#y{tFcF+lwOeie|X2V@(R_tbW zIj0ZiT>WYjVTKmSStw&$`rMgO4VFN}K0zD>wAd$(0pkkF?Logrl|A#=$emP~L1hu! z+&w2h{jYs^?-!AoTDlEeSPjH4;=XUBa?$o7qN;)e5=uNi9(}#1#E!L?o-n-UbGrn$ zTKqy$D~{!i8@;nLMRhkRiYW~M6HY)vl!OXI423te*@?O86Z80_RahCY6Ty=4UET-r z_BHd8#!Wfw)8k4PNc9B)gM_xI)H&BvKr|b2-fSJ`JPu^gM`H_5sMt7_ZLHLzP$Pi z)mV4m#2Ub2&*v-wsRv!DQLuV;(Ds4|^;0HlpYilrLwN8qpd{i!4WAZQOOiWlqTc%( zCH&zP04reUKdz_TUFo=5V>q(phHhJbOc=MRpmlPl9#7dX>G$AA(8Q1p5vN$&lsb0q zZ2puWB=;x3Jl@CTm%0I$gz6^Xw6jYA{DkUFI6Ug=o~e1XD)NPj;&D?VJEi(Gitjt(~eG|@|`*`O{lH*OX{G!wk^ zS;)<&G!A#S0&#MYCn|}MGs1%_B7YpF6aejIGPrPGu^*RjW^OQofT2H_k@arhp4XE2 zj=XrzXou^>ZY9$6G6E+OF`UgGg0vbW0E+nP=9AZt%|nV8`@vL7(fU|R_-U$y1rA5J zjubK^ZBm8&8{#Ed{UJ&!A=OA@6L$*6Z%@A0JzEq|(*OzwgJ_4{IW0S5x`su7YS#9{ z{w6Qm-SqQQej?u^sSnFjR;>b-+$PkEJtf*jgU^fwjO1&HJs7Os?%OFhd}gwuB*R`} zuoi_dm$enp9w35#$D3~Vp03DR9T`DLwi5?-v)hWn2j);&Rq_601{O5bX8EoQJ_ zhpGU+s~ji<%g0 z-}L2l5EE-OK$AP_b?yTp$Scyo{ix~ui?ue(fb%MXAhfi_CC$S1|03(0VnhwLEZnwj z+qP}nHc#8OPTRI^+qP|6r@N=`eVWXyhd-%GRVDRQ+4=YS7MpPC!!9iQN~hH1q6i+k zT?Tw$zAgXo{I`1R*VsifCpt)kxGu~#-ohhCG)f_Z6KXlTghj=|t zp0|o>6yULe?q)srDNLF5{%>f*at_r&BA%~ z!a=|&koWCsntEDcyr0)2LBljZ-1gK82R&o_@duiNhQ?CA0*2Qt;^XcgQ%Y_M|gir6TxwzJ^d zN*F8m?qOm~uI>v{p9sbXM?^J!X$bVd=dtvAle8OsfAPxeF(s@I_PTnX1DV;N=#4ml zV6PlY|C3#d#Cfi|nB-__mZ z7QVDAMrdtgs1L2lATY@9RF6htjd~7-n;UqgWHp&ZhL)LtyvejV(%OS!xN<|_fi)ll z!&sMA)hHI>EGG#M z8XEVDpuYP+3@rDsw6)Jl5Ae0>`;$rl+yROyMD^hg^;NA`Cycsi4BJkll?<0L)IMhU z5VLuH=unw`j{pxV5ojR*P^HO~#b@KW%57XM9mvvj=pHD$+ZbgI1QJVn>apSIw+P!O z+#+K0S>(FQ&?IMdUG-+)Ae=4quGv%gR0FLa;sF>fKp3zVvj|!q@V!qw)7#E? zN`cCmE4q-j&BR!Fs3ktW?cvV^$k&9dt%NEtbB7{VuQf56e!=z?m&Ji00EZ19FzTHB z1OD}hcr5^I5#fwnlwZozX&&EE@5+90;(+UyIX+z$B-5{tbx>khv+r>b{0A)x62U#$ zrvGnLTXWPk_bg!uawVm)Of?h40j9ZeU(ussm$0BH>Ks))$ZdMGkix@OBY=^J{dtn< z-3J)Kl?g?#K|6lqnY|YtdyEq*BdTh24NVdxMryeUd%>MX%+LxC(4hK)adus0q^x)@ z=^f3Eh!XT@MBQv-)skmoOMz&{^rqv|%Rnt55#~xFq7<<9ii!=(YYTkeLj`b>H6qPM zK4J)}P%4^c69CNwpLZ~bIwPo}w|@mCPbjDj$JlOQQc7A%qYzH9&=48;0J_ zkFF1vzQF?Zkz;a*IRM5q!+ZB%{?cd~2n9YiBAZNN=tOX&AbCDjTlOOaHO)W}c}NRL3)4L*jk;E@SPO{rg6iB?q{Tc+XS0mgiZsvK9zTLv9RRv(Clm zD9@3&%Ts8So3VyP^P_hC2S#1)SXKdP{y1KghLDym=PFo=Y2Ed188*bu`0)50I zgf{31faI!$yDipoYSnTk$~GO88=lp^n^p+Cys%|MT*LWgh9iy&)oa-IM+^Raw6zkw zR&UWgS0K_gMNc}tGf~#(_q_eHeNOIQNAFGrLoW;vWtE=}ip=6tj>?%Kf_YAPlv0|o zL`E=Px{VZxPV|k;JTik-;KmnT>LCorRt>tv z!$M#_5bmd+z>HROrdt;6m&)-79w%RigYj3c?M%;W1O+&u>#ATKTEVM;uBy68yZ_WZX9A{Ogp-1HLE|+g(-4MH`GLz2e*V6?eVe72Dx?l zBdWd3ygqCarEjU2WvH^26I+~POZ~9YcVELNK3htq%06JVOU4L>UI-M*vMRT}rp;i) zxcAu=f#RZOXId)v2HR3_JB<}QC{kKF2tadS&<3n!9#gnW6I7|#?ef^8JLbwgwD!jJ zi24)s#r0DGRVqmfjeX7pl0BF6G6Se!DBDaqSORk}67DUgLPH9j`C{Kvfol0oAaMBi z)q&G~?3nPk(#+(CCu+$|k{xad?zF<_YKN2sj^W^~J?_G~Nv7t7@9$)J>ksrn0e+Ki z$`{#;sbP9<^3BK+1(wdoALJIjRwMD`=_>mMrqLkzCkLH9u4j$bmKy*i!A{WgPg2B>m9?BLX$y2Z6?Z6v>r@wn#*Z0O zT417Nmgz`;7!Ott9`}0tCR0`lrdR7CV$=1YA;*nwdIWmKs$MV6mJRMCt=9*UQqfQ- zEu!KGE*is#P;Eb^MtYPOg;97lpe8GD>=h`cnUkX$_FTuOeI4hQIBQn_^HdetJORPy zwWoY0g9h6o$lyHclg;!0lY^XlYy1=Y$003XcXYvwFNrTz;spum$te*P)(4N7i;2_5 z4cx~8zUl(H7VkN1pKthuC8}&~r!UXxM1U?3PyRqi0S9)MO=AR*x|c-`b-Yr58w7Nw zV^g$`qspK-N7TR&fFk$octN~VE&1He;2Wemjium%A9=Oq+cli&n?r@tUbSu7M%>K4 z@QXa{krrLY7k8kR=GoSl@ML#CC->E(a|ug=Dp#8+a5GWY61VaKNZ|`A3qmbj6%yeu zB$V}p#Os_IAhFKB%>_!JgpRUn}Jw9$RoZStZv3wn!p(MFRYQAf!V};j5 zy|%+rAAXplERoI9Tr}p(wAinn8z)SccB-}t-%}^rfxical{~du-6y`8mA!AnTgSQB zC#^y?5X#(>rS-%_w_dY8Q=hf<$EEZCa$dI{kbfB>6Xw5Qo9USaE0zvhwnD{Bx3earRR$z2n_mmS- z9uhx(jOjml{Km6Jtu`}=anrRufKVNxAt|h*Qh!G|uSuC)fw3w2{L7c<9}mDxR#vX^ z4?yV*jh4f$*QVWG2T$|X%({nu;b;+huu#<&UbxR|_ zh`%8?;MyN)KvjS&^p-5c+*DyDm`|p`w^^bi`20{S@r7hDMf*# ze=6htP#3+ge=zsB^mFiE@Ur-;^t%NWj4Z{Vg}XLVIIrL zd834;F+!e~{K8IzHdt=mOag3){zW;R;;k5J6^(~7`i}{sfGLCu`P3QTyYsYWOe`%! z?SZPi*pu@)-rV~suN!mL$@Ojyj^6$INq7T!pMzgzzgBU)HDVkDwwg!dM7|3Iq+n_D zWos4%cU+H;MKp}6JFQcUhA_ye7SLZS^GX0V11Fvf6eReYuHL951PVzsU|^oEgs+5! zm8K}1nZF59C8Sa3B$QK9BtZc2q4Aqn*KNujI24;if%?m1E=9$~5JwT_!-l3Kl_Tm0 zqe@&yoIyR9OhOxuX%BR70#V5c*7P?*Vl}ePj3`;96>0-PKvC;4fIBrNAxH%9K;MCE zN9?wl&@E7qS);2PLo%UzJ3oP84Ya$~0#so$S7VeJhq6I-R*5vSXFIq6r#e02#Q7Lh zZpyovvZ66v`I#*ei_o)h1i)~>p!jnxRkNxj{QNOy+fK6wVGvFTA}|pd*hGUqhol*& z9Z!VzZuKw{5SNj5whkBy$KeyqU|bDP;QqdjvoiTfy0Ye+@k%u{WZWP&;ydla#?gZ8 z%O_38P*X&=KWbzm`TfwNp#>*|DYDdRoO?T|vh5-{1WrBlTMrs9JlS#}o9Hi~@UvC`;nmMMkCew8{AL7T<9xKVZn-j8f} zSDRZ)+<#EXr<2!*h_{ma4^h%0to-0jB{o|dzNhTQ zj#6lzUBNchXkZEJX~97+?MZCrY|fxsH&qHZe}s;|y>sRzPDIzI^zcE35(d>E1UB#( zJxHxe(aUHoi(wTzX{2m7&iWT?vhETG`LHf+eNo2QB-R|q@{R+b49Pljs;tbmdq`Ti z{W|;4WRJHURulP26%$ME>>m(TuUvgix*EQ-j6oDq+8^P5t^d*`h<`uqzyGPNi-a%oO% zt+HZ}0?_!UZywyCY_i+cjA@|z3O2v(Ru8C61F97mO!=l(?uoEOElKRxPHVFqkQc9!E$t}wmsbry@~GcJND|F@p24JZwrWXeShH5Q{5E<03t9{ zwPKXOdSHJ*B&-?hv|5@0FKdDzG^s4?BN#Cvp-D&|c0WhGL-iLi>J-lKATtI1Za#=#pk;<2c}P*sfiKJ%h@D z55Dn@&YC{!fY&}07kdMoN$**7uCk| z^}vL(Z{8d-6}YmSrt*iKgDFh_inXuj7nvIv=!3uydbxs?{R>!xtOy8x+~vbyZXdak z-Tq`rG&Sx1)BU*dR)-+8ct{`Pn*#8OI%onEh4UG6#FfUEs>hubWwjO)I!=BVTu2vlB78T~>doH|sYa0@{Geyf{B)j=&8CtLlN)v#?Y*;Q(CKr^ z<8OWU6mzJC@TTfxj45ZP2nv8Q%0|eIneA_e?^}B+g{pwEB|2g7G)Z_~{e?304@=hY z5;}Dj>FMPj{#(r9l6=uqnGMsI<~ZQji(Y)~q3J~dEi7yIx~z1?v{c(Z2thAFN1EPj z;;{EN+Q9IvF2Mc~@!sqHZR{`B-^CDMD>60U@meWmJ9jHy(|IVTDtASNV1K{JLV1&uqDLf}P}CnUOrS8%u{pIWbzEbcm#?2tpu z0b2MwOR|2cKWFE7qh6M7@Pcw)ob0o; zsAQW5ya%#+^}uz3b)oH@Jxe$IIkqE^+f76XVB~CZG{sU@`)4*GVKcWQba6F$*0iq( zJ5{nS03(z-ky!UjDOmGTrEr|UkR5+Yl<%RoVp1y76G%xv4P$`qqL5)&%ueK8g>D2y z{(BR92F5{{#@mfDml{n?7kWwm?`fz40HA@ePf3+ePSHpO1u=}l%lk59UakrTfOJPL zWklhtN4+6Hq7;xN8Hh-5;_?=OOmS`0iScWS&kR&5(z_`BgDr zxW9N3)ZdEWf^r!7AbhWbZBjoD)uP3RY75>*+a7pwP}VIQXW{YPJr4S6|JRq#Fc|<5 z2uZD+pZbZNu%rXSWN&qVGR9NObo^q(I0W!>LI5SeW>lfI5aOlFv#z(%WXPMV)rBdF zKX7GbAcRsAlc9wIwdL2vcE<}*w1)Py%do{`p^k|RiOx4yQ^90*U9Ol}kn*FWc^%oD zj;_if!vEZJK9{6nHaJ#J4w#jjrmwq#|5f|JJ-8!j3qDBhxbtkE?KIkovs-0JK<34` z#PDgUf7R6L%Y{Viurc|!LpmVuL6$+Uz+uWYo=B{Omaq6&f4C+f=n{n!K}N2kC``^H zk~_ghM7?R>j zsqP}W2%pD&z&&if<$RYvWsz%}q9vAP(y(#dL5uZcGinw%rtal;Hp?L-%r;9K@~FC+ zeSCx*6KgB5I*Epeqer9^MjR$1QnuvLO9A4-_1ZW+4~SA^TL}cfGdKQKFg!n~JTPSB z=Hc8+s=X5`0dbSwJP z4ZeoxvRix+#b=oYZGe||ENB}eNv`I&hK}z2mo_rCs=piHunG5s6%S$`%w- z*Rp2IY)8eXSOiE8Zf5}4$6EZ(2MPP?Qd`)lY0_!c0P|h{oe{$qF3MStgxyXROo{zQ zrpgMcjk{U9*yMBpE7F>rQ1SiO>W>O0#HU-op$llVGDp+x_!Q$|4EjUx*c54$fy=-) ze}GXlCK&sjm8Cp~iyyNx4D?MP=v!y(gA4wuA4kSXH``usGNIGaiRXhy|p!H+7e6Unq3T(J#$nZPzr|Hu^t9jDI^0JVR69in@VYfm^Hk|gmMR5 z#wx5RJ-EeGO*Sqlu$v~LI;#s1ScD_scz;*pu2O_YRkv5r$ZrRhq4HQ`c`SQP5w7hz zTnyJPXe4*D ztYwD_JpRh4qHMC{4DygGG4!w-i|lH)p#*R`HDEnb;xy24lciW~Pq~_Y0e>MBw89#V5GH_);)_e>YkctfV2;WrB$)C}`Do8sB(Ug!O&aWqL=(CC}kpZ*@9Q zQ&+Ms1pb8x2wg#dQKpWlGf{xr^3IhL{?jzEK-5nfd`yC1Il|93yH`QC*T{^yt1`-1 zGz9sTX<-=C1mTR(!)f*?0wdw>5ZjSbqm`_B4rH7%GLR#oFDl>0SkZy*Xo^YO(d2(n z-kSA1G5Z~caTsBIc~|0y+n-wd?z^_hw-b3jKZ!+*_R zaBAR!ANoOgKec)I0Ba1>y*hKiP=OB+OtF#gWOMB)Q7JjUNXc8o%p6#AxjGND`KDrT z5d;DyK);5(V?Nqi+^zqp!XQ(E9Ed<9425VoJ}E#sBG$MYUtleeV1&0-wV8vDbu8Bq zTmRN#AH3!KXUjF^r%vVoO_d_97Lj`w^k`L~!{zQR3I!eLF>m&SA65~p08aX3p030p$)1*K;(sBt)khh8Y-+`~NYqa&z!r(Tuys`GY_YjXU_|+cWS_9?e|!UKi06ZfjCSgIW#Z z1z>9at%>TpjO;$7{`GT`i=q5M7{W>j#K`7DWrrORZMld{K^(_Q0V)A`_=J3+V-n|l zL=)7|TQ7|5X-jDYTLKX+fm^ezZB|`aLQs(C3)ug~Z++1(b8~8s&JQK)m^!r?P%Rf3 zE8XW$=I6i3FV_q7m#$Z?X3Q9-2PUS6KY#3cvh?w#$$>|^_O&dSSn#lUva%fs2V}cS zWa{f(+AVOTJ}~!|fZ;$QfLuOFjb}a;a{!0)U^CI$);@w21$$3bH}2fI>HaW)3J&+{ z`PUj*@|&XECE%AH>_80~0t&-mhN6cn&vKAEJ%zT2(93(nFub=@*ba2%(RhD>MOhL- zkbx5;=xdKbk1zm2u`dY%l>7`NUwXXBli8195oV(Y6 zEryJRVsNs;6uYr~m`Pn)s}#7UWxMwvOyE25I$d@#!ETag|2Q1i z_zb%6JaUhXK$|hH(~C)g%|utuDH1hVcJA+)H8}%%6!sij6C;#PbTp?9IW!BVV*wy> z=9TS`&VueNQ)X+9ORakqA2M_y6~+fg=WEC;TfAp0Tj2TE`7#4?&A^(aQ`?@nyJ@Ez z`{rQD!Ex|Jm~^L>T@II#4|E1r98LxAsQ{mKttX00Zn6a6YuF4Y|DrY5{>?Y4Y`DbR z{>I;x^TH_IIOVNQ6@fv)bV)ST#CXxu_^j{`EY*HrcfX{-Cs>Zuxb?@V?@_=>8;BWq z4DL99VKkVH_M)fG8t!g}N54fhy>>3b1ZY$icZCW6(+M&2>4w(gmNjcut7_&SHwzx> zN1%wzIDuT_L4ACbn85rAHXHZWrt+pR$pY?dnuLbf*e)^rYMJ=#d}C?@W+1>i3`w*J0Y7s9XHOB-6qP{NA2fBo=h_=#_z-I8T!Xn{EY2s!#`axDnYiBCrDd6aLtc%k z-f4zQ_{U3%s7aNvg9mPkor#195U7gCqdK|AoxiE#SKpfZUiclZPZ0JiwK>PHnsFdn zlb5;Hho5{M|D{C}5t*5p?ml#wt)BOc-?jUt-%>h}`+l~+f@cn-^Kr`8?@<}35rhok z3iBoQUh@U0%ySGxn(nEvAPpnb2c@5f5dxcG>+i-@*q_B1cn5Jr?-Gt9VW?KTaWNGI2V|U2$M}F6 zFITy^9qdeY5_z-uhYzO%=?nvcG=`<}I6!+bz22{kC)k)s`V-0uwIJoCVv2$Qv}K5g z`94nvXJX%e5C7=s8+gA*_6d?CNP^|Hubmb+%lTqpgD47k{6Ogr{IUS0zk`f^+Ouyy zKz^@ZyY&092ff;VWWK&wTTTCun$vzF!7wwkKK1vXe}|dIuuBP&B*7%Ldm_et=n?;z$P0Q{)cjZWwNki@+s@+e2x%C~3{l(5*>bN!iyJG|!BXU(#Rp5VZw$jFs;DYp7M7p`T z$zE*BaxR6YG}*Up>RD@`_Ic97?huUs&mH57(kdE&6r+t%owANq6_KNHjyxDuwGNq? zn0|?O>|i|z@bEzd1bCD`t3Gkd2hgT}D#|o<6cK-gG*&<~jEF`NUN0gVNkpTFXcQiW zBBJrXCIuCdp#IMk5se|Jgy^laK1Kn_|6Bk64yg-9G-Lq2Q?Ft^Za*M zw*oFRoGB!LDMwVt$Ks4l!zxZ$ejcNM31AUjtc=-TXCfm}x!ic%WwhpBj9r-Uq^8SY z9x?(9Cj_;!5Lf^Zk%Tz8+?AdjNX48*9zqsfkIV|dnk3~@FDak_$zNu}DTCk;(Edm) zk*2b<#+z$aw4#0=7N|S`iWot5?^&LEDT8O{QSUi=R_drvZ?Wz3G}UbEO-<${#<1q^ zh)G3}e{V4qg(ZtNnL&3NDoaqH{2uvgg>5#TfAk;qOMty5Lyinri(I&jh%%sB7rf0m*tFYe7jc#=7o44gb(C9I-z-*F;x`qH9$qnHT@*kJp!Qmz zCtD`nV5-;~z$c6->LySq>_e==HJ$%XbaUuEsQzVcSB+W~*Q>bLzo@vhIL{$^*C}6} zO}^j1-ZaH9;ePCAQKIGbupU#~re?-}Qv;HR= z0F0;HMPLTyE2a5Ii!=$2L(BYtUmqwi3Ptp0DNhmGW~u^#OHCgTeC?z-dAy;c`R>Zy zK@SO|RH=4b0E0$8*vvKHM#C_MbII~AO5@Fjpq|wOhV!=BL#oS1htznp5BRWB2L>sGQH~WJ6Dl(7VjJ6r?3J+{_jMoX1^95;SFcw{$k42a@U@177huv$1?2DWfij)j_7wJ{Uk6jzZeBV0^t_ZW_j-u~JJGiBi_3Pm%2D85(}iXIr?a~(I;UK!gH@!}sl(BFt+ zvFfyW<|IFuNjO}2nU>Mxiu}j4Ce-9x zrx$CQt1Fvp&EV)GR-f$5>+I^{>MHYl*oR(2%ORjkVNP8SpvJKFpzO1mE#K9(;;tidgZnmpb9n%;!4rP>U``s%VF@WcF zH;b?Vmbvy#W;!iVOic(*7fdW-JNGnJ!+0LzMVFE$rSJQfLa$1*E4Y2h$j39PQcpe~ z`I9fD5`N*A*{<*fAMYQpi0e^}m1J}^(b=h7;seaB9aRy5aa~0bel2a?vpOTbzB7}? zXr;xm*|;&y#eBFYXrv>gbA)km9wN!>noi<#ZB;ZHK{_G21a`K~ry)IgMx}m6YzK$; zrL&9r66=7yej>2sIQgzJ>j5f?81iH&*OU*yxeUXxAm&Y=mc-i%3*Y+qxa@+GP*xMt zy=v)dm&YluovIaEGkxbrX06+|nt5kO{P@jMY*)Ec5{gsQmxdZo(m+I;^ zLYLmP>Vl&&96L9`aG)Owa2OEEg@}<_`^VZHo}c z2wDTYJAY||^ac@&`NEg~>OKfP%(S8=xsKo3I& zwPZb|#{0bXZvBl^@vkV52FBnK+!$8tR2_FGTtLz-FqeHs=lyg&mXXm%rZulQS)I)2 z{UNyYmOo!TF+dInCcRhvIvPXTkB>EuXDlT4I`7(L|4NlG%!@1k`-Gy$ovp|t8v>Op ze>(i#|K(H1kXlyom@_yL#7Ux;CnDqzXb~QiwPKrTZNAU+6k)sK=*$2K1Sl{7P(Ebi zIHyksy!gz$ll+~alN(l{0z7J`0h64O;Lll<0_3j-C%f}KJ+AJii7Z423bx(rDyrW5 zZ#PwU!^wXCWV?V7%J&cQXte?&VgWC!MXh=DM8M@aYfz?YFM$0&LNY9pRu_S*RgoA}M{JHk3l@Tty*} z!!A_+3lu~B3(t><6i`oAmf1jQ{c{% znZWna`as310bghLxjXY9Hl;3sgU5@X2GBi&o?bSl?-6+6K=JVuE3kIzh(s34W#$+^ zeE90^KPh3(#%efH*IEtd>zXW_sB1xqLxh>u%}hpgXXzcqAw3*Hg7YF;#EZe>DFz?5P*WgNM4p2A6N9@Dd(t!9LM7zZ-AU?dY8`p z&*vT`OzXLiJh%yhB2LIE;OJp+zy4^@5&5BEt0;OqTr4ZaEX~tSuKXg{oca7W`K}}I`&^|SEZuL+7ddST!Cx!hopSUxAJ{DwO7c_7_t=dZr$*oas7S{6e!Lw*@zq z-5iO;&%Q@0r87m29%;A4QAshJ^~0Q2^8+=|Q>ZnL-{c8%0=hepxj z4Zs$=QEeN~>Ee7o-0%ID00@qeHNo_W#AEz1n@(# z9k1WqS{pu$Lu!V+l@2yl{0|U=P5Hk)-zbtrYL5f{anCTBf$z@OYF)v5bj>Q^XUf6G z{egIB08N#t1wXPy95pS{;aNfagO|hlI%Q@B%=pwtx^8{&7Y?CMcKTiffFLCdOM678 znPN!NjRwyRExwIep<|r*0geZ|mpG>3pH<;~wXQC;@>%ix93!>=qnSac5<*nKQDNvvLkNBBB0BzOK zj07(5(ADmzy=BUgZT)6OO?2?fwpLT3O*kg&@sIQJwY^I%#}2>7UeI|BR;+f6b@GBy zNsfMTY+^Dbld-mQX(;)yYbUR^D~rwNlb@*VRjo)%g|`lkC2C;u;E>Th31eiOJb=H$ z3(pb+)ww(^fz1TkFqj2`21F19`y~WH>wm&{`x26^lu1q6c5|W1SURW-x_gN9_>=+f zV~j#7AQixkKL*=dflm+Jf?;}8seULM1T(Z5m>#1Lzk)EP#-|(QjxA0%{XNPiRLSx3y=ixf%n2 zN^~o*tUr85vPY`l(wbhiWL~<=x-%z4`9wGZbYKgC4b!vvJ+ybeoNPyoRusLSt9K+Lhi^%id3OFF!zaY?gInHpE(J>M+E z&sJ)2)aDEZ8KH3*emNt3Nz(TY9PjR)b+_WhU0(U5@b6Uev&KY>3_=|xvfx~1%PyW& zm!Po#{n`Q`?yA>>gF&6dmk;7rIZ~0S0QD5-gI!!o0_b2%3m_= zTpe*m$$`MKw6e0Zt$hza*A@n@Gj@9FaZHPGysx6S*deK}&VGCs%zh`gueGDSEqz{7 z)9`SYdqB3VbJfnx?*ShB!Idi(D1&*k#bBF_2G5%=H|8vU3=>h0Z6`ZACucCV%{Ze6 z7>sMYKH-ER6l3Tu)74a=thcJk*N*qp>rSqOZ-&TUM`JJ75Ss{&koM85?e z@4T+_vMQ&PNp_sHzR|!Dz%G9%Rl}vZ3;7aSv%>g&-35v%V8Afm*4lpc_I#uEJ`I6e zZI0^EMh-!YsUx$yciLqs`+77Q@nOe1B|nH{6#t|E?2i#5MfjS@Wesuk*bvy+TlzE& z@GM)O&9vpBu*ZAb5nOY*Q~lhMr6X%k+f8}D^Acw^Zte$`9rj14w457_9c=#I)M?BZ zYL&0k-M+Im{)s)+EhB8h^E8?Ds-TtrOdEaKePwn=F6<3c58TRkG*?zzYosGOvHl{c z>lL-&p_{UVuqIqa}PkGr4h-QX=D8ZYEzz z^+wYlurxp|6Ftp`MVuDG+C-27gy~)gOv94(y-S9tvdk-cGp!z zzP79&g5jgL7tZcSExY~sJl8id@^~R4Zg!l9K_-}rT5HW)+&VW&3m_EREq2&4Q<-Tq zi2&AV-Qrc!FPDsNG94%$nJ!Z(3B#Iw7~49O%8?M93uAqtA8#O=(!**1&Y>X>TqOAk zL(TzH&P`z~0%d9>>wydx9tIyV>D*WTDRJqWyQgnF!rIQObNXD#@^V!I>n^30$5`6M zDJ~A@5edB!vBU1hmcE`V3^5n_Ok=jHJZbM-h5a8+UhGA&ruT3RF81XUl&{ladGNe5 zc`@~&DVfH6(j1;P%=;i4Z5vPjbZfm67;2>Mj!K%R8>szq>E$$p1EPh)3572Q)9PF% zz^VShRZU{0(awS^f`x5Y0mthu{L%2PKL54&fa_5JQf#@*a~JQQ`0q0AM7H7AhIo$k z`q|Q?C4u!)Vl=X5<`<6=hGiKNfk}{HXvb?FzK_H}7fA+JtQG}*R?vd}u<6XSXN#5H z$I1(n+yWYW)xR))YhK^5%pZeF^iFng16lCtrI1AM9N(HRzok)b02Q>BH1@)Fi59jW zes)bJvGDT)GL0#ZA|;d>=8+uXFzCmIU{B0ctX4BMvdwv6wfR+hyUXjO`K9?YEDsJo z4*rw*8uxSK0@Hpp9akC)!tN^TZb}U@o@p0)yP~@#I>ccYn^2_mBBmG@zFSpxATlH+_ zU4Q+BS@Cx~w;fX;;n_xO=)dCvw#`Y8)!3%d+D{qVp^U0d>0Xgl?>~nC4lYX(qB3lh z`_JAv+!JfrDU1I2cQ80XW*XqQd<&nNnzZ+!4tCY?8IB!$R(HI#REwc++z@{Sr=hV_ zT2gLe$Z5@gyM-WexwD?=bMV(y%tvC37HyD1b(NMZ)-|cUu?YHk>DzZyC)nOD(Rxl} zGZjGHBo)7=Cq+Z>nKV%wA$I$%u+jEG;DHf@rI>EZdsa=NV{gavcoo38C`eWx5@>oF zJ3YP{KdtUpqRnxrlOg^b5r+(eIj)k`{l0a_)`%M(m zXbv$_%51_km5y$%`XzQ0hV9WlrM%IJ0JyV{TBPKVt-|doc&J0_@zcOW`#=tdQl9

}sK9;s|SN<>zRX$in|5-2;cBjThdPY+%)C7y>)6m~h|4iqWK8hxk(kwbYF zabj*UcVs3(As=+BXS>+{r6K5JDT7fmoSB!ZcsgJjz9$tE7TJWNGJ*tt^u9cv_e149OT=|fc)@-h0Tdj+JFS=QyP3fm94v<_Dv2W44{J% z?Pxp9QH(|kng}HEe80sFD+K5{A;csKl#uxdc5*r39n34gHgVaOsxzU-IqgCC z`I$AJF`z~U7hm$^q$M)~f?KFySg2A9zZF`X<8wbk9Krw*J;DIAD*G^)Npyrd{dK28 ze}oQH+at)6lhQe*K&ZGbtZGy_=*v3@z}O$R{)|cu6b6WY*OrI_%dW}_%eBhwDcm5f z&wU6ajUon;W&W4~mkE~CutwsLoU_;XU&eLVQ_A8oamu9B=@_tQoMtBW1$h_#mI$y& z${t|L^uzc+_Ss>!gKK}BJL1S424Mh(wW4Ae9A-5f00WYH*?+=8HV=ndn<=tM9>01c z%dbU=ls*`cNkKb$cGRV{*Q}*r%Sw%V>URW0I?kC*y{2PRaG2aAnbAw^Ajxf+w>*}DqYXHKX^OU$#NkQlPp3^Sb@1RSoAh02UVyM><(j0`m$R)2JIg(}5 zuhdutUnP`CXw3Iw{?hNZUjfduY!QXi8Ij|ieIb3Qj971LRPWFNUb zdKC^KfR~zH=i0ZnQpjpiqLx<%Fy$Q=I|$(H-Je|fiT`ZY+WiP*ud#O~G$1wylH zlpUmFHOZO$Z7(&^+?_3A7Z>XJhH>ldyI{1IT1^)nXJXX_*R4L?CG6c zM(puW3?YPIQoqK=TbU-9yMDlMP^^#VvQ8ktvUH}b&+dOu{>IxXr`N8mx0J^Ac# z5)@EIdshasX?}TweYkha2}A$u%k{aD7q{B1!lt-ME5;muRcsP?F`DK`=)2@C=(gmP zS+bu3iptI?#bO?LV9T>xw((qZOCle44?#A-s(=tRHMdQ$71ryVix^^Bp4QB9IoSPLMA~onN_t->s`A~vr0GP(|8HDDEJ%WFnpDXmiR@0%rx!a!_IZ?Wf z9+`Uz&B^w?8RsZvR_Ys-r+<>~UH{!|COD*bCrp9Z7!b7W{$&z@Y40L%&Fsnc_iFL+ z+bW#o?Mc>U>+DpMe`@%VaFCaIBqhnoDUXJv$zLlQs5c4Z@}gj+8FQ_vKVn7q#!nr( zC7HZwSd<=AZK{Ohim}b8ttp8i&g63;bt!KzIC#)&Lt?itgpqriP7sabIzi@big#yC zFZlSd;rjS%rE$Ey}3?8YAV>prEpfSxJd4G2(iO?3^R-Pst0y6~vE zJ0@_`q~sbpOzG)aNr`@3<P<%<6SV?dn0 zVa(p^7q^Tvvh&fN2#7_k+@V@RVMOPbY$dE_TTkaYdlc(%5;E(Ip2OeFkb_no1dv)SBlz4U`(`;YAz|*Ap>mr=u(Kj{! z;)E9st-Uj3VM4$CBt_gk;|npV?DskFfla{06|{JI)t9l<7RuX6FzqMa(F3qAOVZr2g1o<#$#Z8081xi;5XxPi(45E)5h`|y`z1P+snFKNnQ{A zZ8kEqAu|9RfGZ@($5ObVJXHyp>Q~c#t%6~UfddyswhA3t{9t{7!{qK4IERb~MKn14 z2LJ6k;2)VrqW6uy5FlhjE|jDN z!ADN zaCvz^wAHvxIA+Xbar5@8Wp3a=Tn;c8;~`nV^wYlhe=f_=zyb-Jjmq0b1SK`K{5#jK zbCxq*(EhQUVG}M7S6rns@Tfz8I(04Hn}@H?l-vJ^C1- z2%mX7_UK*sX6=p~0mqP?ApnAb@*~=aaJV$SXD7+;GCYpr<3j~RH;fWI?{IoaS@482kA@Grhtz>+~bBB>=|BkuU?~w8{6_tZSDvZ7#5>l zzvK|dAqF2Sh_geiKXpD~&MPD4rmsq9G|2e-ZtU>Y-%$bM20flvw9RMT@EkyK z751M<517tHIyNh)yjl~mrqUARUmnBFFU{;y(lpn^(WYR=%VBj~<>oL2jYpi#&=KmEm&l2FF>i*(d^x+28Ut=sK5}b$)QGI5E9_w=?nnDG=a{eAfJZ?eWzJWXo80@fz%qWlfybu z0W_;zsbz*Lrd@9J$4krjir9CIg&S!A$DRZBz%q>1{+wHw_YXBF>D7X4Jd*D=-fWM2 z8U?{W=nA_QScW(8hBSQPv;ChL()2F>1-m>t2+(@*DfU$I=9MKyHlQZUj+k4OX6tl` zUFXV7KY~{)ec{FFybztVsUR!v6;N5uA7Aq*Sw9_N2>Y7%;wn=^#O0=An-56Q>G@w} zk1QcT8&p=;OL5z4B&r_Yk)lEEDpay*!UIY$$lv~Tf!gT|wq89~t-Z8! zen*^rDjO?UEd23c*6S;Zbh>9}-?%Rgdo`tR0Wi6~Kz_}rfI^Q-e=;R6GM1@=w+?6o z(tV7DQNjTTV1UF1S&(^SN)T-%eplaX(@m;tjLl->-v7O+*k)!JL|8+J?K`}-lVfYE3)Oc5qe=y>DIDdTLTot!Ff zGDr7GdsLiX{jTyS6Dlg{P)R8)fIb-&wZHexjCY0kQ#z{TpoAWYaB(~b$WDOmfOfo3 z2e9WZ7VW|GOCWD@HvU)TgT_A!t!#+rb&;J!*f_LF!QiOGe#>;ul6aac-3PPPy#|m+E{s*{Ys^#{B~K%aFOA7%bMh@ z8T0;+_Wvi~bD45z+2ifTt0kf#-QC}yAhd&|D0W4K)UxlB(|)adv-`oz7@G)Vk#_ko zkU=05)HPv4&mBWx%=;Q1Hp9QC&++;z#>B6kj}kMAdo)ozI^Ss}*jomLwGV?Pyl^P!tOn-WB4Vjk3`H|4NELCzEkVDujvJvF`}q1gJYe+S*(oHt#QvuE?A z%foZ6pD}-Oj{14-Todq=52qgZyUx0-s&X?H!U#QZ5@etA?b2}3^;>%5nFDcX000I7 zT$vT;6|wm;?CbTh{spK)1Zu8V*34H5Ic`)}Rdy}{mW#u`hpHP2+;dcaBaJem$dkw@zrd(z9qGM+M#cpJ zqz3Pm_9)5}7z5=L7JPgBJJ|5~81=H}nQ}$*tse~;no9b7pc-rrHJiugrMNZj=naHt z7YYAem-)4@m@=u&@(pR)k+|+hj2H*H1_CAaIMw2pdt^q;%Iog))O&fI*BqK*_btC2 z*LjZ9)m6-prE0LT&0H{09S=~0wVibgiZIsx>z;lF-Mqw?H=sD2KqQj&W(C0T6}41X zw8GRepgpC^^pc@FrRf=6BPRa$W7ABwtBbS6@?i%5mpk6u_4#gc+}$+UMVRo`ci6X=X1x+YS=R^HR_(^$m=K6Io& zYG}g8$Qa)-Lr6))B%cjvlognj0mTE>1*4d4MFo8v(6xFx>iW8C%(?ns_I=h#;duDZ zisb_AGt4P7_s`Vmp*H#5V(_;}(wUYYZTfb-cRgO;@XOUQ#mDJCU3x2fSktu0Mw^iu zP3n;F=RIXf$-%Spm+8f8b}y>Wi-nQWadlrx9pX8W$nWRbWwX>{YHL7MmOp+fBuU=K zY^IS-pxqgcM7yRS?4O=fOLW#@Nx~F*Vj$7!X{P@!^1v~HWmk|J6JwjYbn=z_hO~~r zOM7;KOn}?$6TUNmgY6)%mT1=Oi zwOLOW30HvSTA}>rsvEa~ZUld~B2-YrA={rFpaaTDZH!kji0QG&<}`&=$rklS3zVWi zre34znsjl$xrw9_Vhr`;?xdW{gGj{CBq114&6Oa&GsppEIT@gC-Obc%DjN2;mDfMO zJADI!YjjeoJ~pTJH$DjP^*fvHmvJ_p`+~-k^0ch5I?GT(JsfjnO%OnCqb2q-o<{cN zM2hD_-;1rhPMqQw;we$IT7AZlhU08O!u+2zUe^bHMmzI=7ISLj*V<1e8~V8~r+Z|6 z-e0x%TRfabr>6Xugg}A@Eb`mZ9e(>5&b@tN;*z;7Mr7_KfgM9{S4d~4Qgh4(L|oq~1NSU{fn9NfZ9O#(REPp*p}LyRR;X@+0CfHx$&3(I6!Ap`N}pY|+cWDbs7R=7zoI{N zNFi7oS6WzLGo%E7F|LTxv$8QRvZZnGK2Oa-U!;?kY!yYyU58083ytaat%Z`aY!xKe z@w-0zBL#=&BA@yMp1~DUv!j*}ZQHsQOw@S4S_Pm+I+HXuSIL>S_qsrm2Pu zLe`&@tC}}&8sEr16zXrni2Fs?+?zTG8b+0qi}Pl0=*>~` zUS1?&h`qAyv@@6^@Dywe6QzGD_g7>0%;yY)*x#)4*Sj>{UES?^pZ#Nf#l&+GAfTcF z39)Rr#s*VD=Yn-F^r_b6PWUAIj`NR#{)bkKpN`#`2oP_qWS3|+2JXshHVxaWJZNS4 z_w?v5OyROJ$0}*c2~&J?0jqAu%$S=26~$4?!%8{olAmwFLQaZ;pz`$^?$^kA#N#@v zXmXN4I-}KKVzREnGoF7~X|*bYH1JWno}$hpwTvcu+OFkI3L1_(n<#OE8p9C2kj&o}WKQ{p(n2Tui^4H)oo%E0yfqKk5 znX@g1BZd=I^5qpy*o^P%X!nyISKrEvVw(4**o~0&F!{I6+-_QTZ0-IA)^XDPpEVN4 z-_i&_E4z$8UNUJMn!1XT*G$;A*z=$mjB1Z`q!CCx&#K&sk;@PwB4iq94>lQNOK!ph zMF-<$!obO5cx}x3uel%G{TlI;!|6K&szSUfttV@*)hG5=pZfljQXj_Fz$B67D zF8~6JJCdI+(R8@;@ z^TRyA9ZxC(qP}!4VM++9Q{}BTM{0f8+3uf-r^I5aL4MMzQ5GC#M0f{CzYcnr`@`>M zC%w&P7|-bzszwDlsus-vqX|>nd=|=Thv<*eZD=ou#I%Y~Cy0{1madIA%!oSRZ#b-j ze-layBB*fX5pWlXOCU@MBf847Y1SqHs_3Fuy0pV=8Yz+mI{0d2;%NIIZAYb4tZlQ( zsYH|Ge5tnAb-1;2xrz6wd;3-lI&<0lEtcjn+4H2l6^qyX_UyAyG|)Wn8_(m4-$h_} zJq_bU%4q(fOc0p_xn-R+6#BtKSvi!2)0P$-Sa9`i;v5+gIfxLgnLQv(x^`4e=c4xG zO;c;>2QxKjtR_=ktl5F-f@h|^PIdwTs~D`#CFp$VH|WI5@1CdUF9-3=tLy2SsQzQ+ zX~<}y)E$lH34K^3sCp0fcplT7c1!wVkEi59`II!izPx-fzMUo+h|1NmDu>9;huhC{ z0U|y2wP21kWXUY_)HuBe$I`06z);N)6Vpob*+;HKF#{AFW%4YKo|y1aYY-0xC>!H% zwISgdJ{jvkve8 zBCgI0Kp2BKFgoq;{aNT7u`LW9gQzR9w!yty{!a>qP-4u?R`Q$UXa-F*xdn+qyw*{&IX=`Q!v$Ys?EW5irVAI5?V7(4?V_ z$1@jjdO%MmNW8^722(FFzo7T}Jn5j*c<*EYd@!Elx@_U0a}J%xIPZ!^NM>yy_>q)u zS-Nu0&EWC$o+UO_Oov?Rb>M*#^VNJqS`;RfnU~o1@( z*1J}6$yE&iEqa_UD*n-YD&${>r3QkEYZjNM)+3>0_TMA(15E+QXDm#>Frk&yr@tCz zdkEGxvC^YjQ1U%AR;0V_#Q4a|<>pUDUGc5svW7pW{qEk~OOyoQ(lYvoXXxQ^b`U)^ zUnyRtX+4AQQ#C#l(A22tPdq@NFzdh|PAOnqP;|u<6hdu{(F{D|{==Fx-B41TfJmBA zB%eJ0k2mS$smbUuymS46cB2xlc}uVE7qqMLRi6#W(aBG&{X4Y@@)3*TlG9X{IZgtk zhh{3@G=&QQv}Eryp7EZ9iw5Y;=DY`)Bg5BUG6hZOOFuH1UMWi)R;!EJ~R2ogE7K*5elpm+Szz_XQ0(&@12n^ zQlPk@I_g&k3kAwgB6ujJNJ`^U+?&IRQ<8H~CynEiB&A=d7D3I8|Ku4b)DM+L*3XQK0t@w5l1$o{Ui)q%|kP%uAz7-544(MwMVyO0q$) zgd&j^X#fl!gJ0$_gHmj>34Q)(wxWnanNLmXgaTyrJgH z2gIs-gP%Whv-_e?=bf`e0005yaeI?c3i$Lj-V*tYY2;}6 zS#_6MFw>kD+QlD|sCz-@Bn&xS90+xTE)Gwi*L}oF0m2_~0#XGdQVNi2w=NclrUM!4 zB+R9ajz-Ka49@*NbpA;`JQJ5^^MLXXK_bS9++eg~sUE3>vV!smY-3dfYlh`)f}R@T zTI2sYK3M+St^XT63$6BrC=8q;qwHSq=kn_%i}uhq zJ)6CnvIKj@?nA>cmK4h0H90XN@hAE$slYpGq=Ugz4bmW)3)6b+E0Y^V!(Y?}>eMD=evX;5R)%<;j1r@L2PQD9hr5N>260^qh2+YcY*>+E@x$sTFQK(V0$ zOkqHluv}K9-dqn@0E{@8l6bG4ZC}yLz`WgqsJP!h(8hMmplxv*S&72Q`Lp0tpZ_$8n1{p4z-F`oQ zY*vSdW1q{%HvbsdxI8XI`&utI*?zP_HkEwd8wK1}dALEx2t6i-z(8(YQm7CRXoy5W zGuSTILlB2B`Yk{0LHM{0Z~$B6NB(c`^QaGV#!v(KYPK@^K>g`R0)3t%ig{dBp+NdL z_ISAY6VTN75m!*OJ*7*7|H&{aZ)ZJK{HhN-LBZRg$6VgOji_^pZuMtzc{roHiLvuu zVTi^kL!7eC8SL8}Aav?!S3qt)wSn={wa>g$$bPrW*vHG<8t37NOv`rJ5JHX#08kiQ z@dc!!aB3I^tZ<_j8MqtgL$J~kPnmOlxR2}wN6ZQCcqt{)PVx*o(G&^gfm9F%1G8*a zzYgyP54>594|WK}JT=(fvcDgB6HQkCLA|gJ{7m|P$ z2K4a)^g#M1%VV0^Lx{{9)<(St=*UUf3GP!%bKMX%s~u?wAmHQeuS2qNXRYpedU1=k z@^9R4QcnVhOZ~4U>xr@Crg-%T;f(2)M`7t;s(e~OuTm`(UiR{7qpR7iZxT~#*pl0nsl_-e8Oe+Xa%2k;#_c5nnC*W_l&k3nS{udN)9 zG$sImKrshy4IO`W4x41?P`}2TM;F*Baf6W&s$xe7H5%*9P^=Z1EnHz_O_&-gNEQFs zp>*9_h7$nQ^A!tcE|GA|X<8nqC3s2pO`O*@>_L)uUt7mYwmFBHX8fEoKwv6(C!&^q zWAu!BxDbP;F?6-?cnUj)Mzakw5#!J%oG7ufRLBt0JCm#+3G=&)%%rfOx#-b+XS{E;Ho3z8O_|&(m1yhk^*!SZ8LZj^g z(=Q#dUB(p<%7SRgos8A_#_Wl{hlit2Q`bLMNnqvZD{d1fL;wD@61t z=i~P`dF8q?~?4?l*I==@6%LTkUpS--?B1%<$PGxU6~>u9l`Q6L4xU zr1{8GKzGW52bp+{IKxkau669h-Xa&#vbeBWel}v*MzXPR(0blpT%OHMll;`R25}zH2m*!sizzQn50(CRmQ0=LGEw9v z_jEEDLKB&j4IFq~-E-CT0I#D-TDp(YIj=}H2@@ErJHg>-s~@-kRUQF$Qq-Z3!{ls!`yJ zqL)VGAvaylay-*CHYeMcN{>Wl0jecQ<(!QI$HwbHPE`|V9G)X+bULGzK%WqK1h*I| z{o|>wXZyR}UQDGC)PYoNTd9XfXHE27c{J$-?r7zvv;i zafRu^XZz2wG2vZ|$Ty@FHeYR}!bU@(MI15qbTMW`RilCN656yEY}vGRGG{75fK9!& z6>b?ahyxD+xr{&*BicYU9^Y*NcJ=}}(B3GEpE^=YLz`gM%-NHU(kZmTMKLkEtX)Gb zJM+dt^_VvWdkOZ8&H2@*ro^|MU8hGD>;o7KVg9r*L1W)_!tbu7@5>*b8~yl#y^P3M zL4l7a+e~d13&cJlqz=;}ERFH9E}q@RK9>O;00#g{vu1ixH-n(%XY?J1U4gK3r62<+ zK&N9!VIL(HAOcqnTvDzT5TnlR8O9!(_xaTM)z8~2I!BtJS6QmAv(1z=E9*!T0==qw zxK!wRBzJx@v&0z0gyHM35orywTc9#2asAG7H5dD2_{$<|xSN2ji-0)5eROS^I>@8N zx-g-PV;I0DtpZA{OITOiTD|v4z`tj~m{UckqIGiT$Yr1PA7p3RCs7a;{pAF(W0@;TEP4XE(fZ)x}?rR^_gnIvrBk_L!Of9aDm)~ zN)rI}lU=ob3WkXaHuw7IaR_~TS8841v%?UBYU}mf(bv}`HU(lUzmD;0m5~F`w#BH! z^epZ06_%qmFhx587Qu|SmJ)*^4;%~pjaTBm^FD8PeU<3r-pXY#8N%CIl6Tai@oK*K zaR?Pk8W9ycmLd?G)9kkp{EWYWOItUG)zq7KzHb(aHXsVtM4r_S5a9`{cnC~O>Ud#C z@LW+u9c-t*j`94n)!9EiDxU#D&}r_yyshaXZqsudBUP6VPzw02&Kpe^ypo%ZmIp-g zC`)0+0YUtq74G9n2~E(g2S5O?!QDI0;IIZU<%SWgGvg6r|69Uh8wc;`Tt&NzI_Okz zA(rdXf}8QCN%(U_*r>!NEFC9vtPWD5*-3b(*WI+Pe(z|GiNWLN1?O{Fr&WY+1@3W- z!T|_d`ImhKTAA-)_VGba_iRkIJh=?`u3_ zn97eegD7?yMM|%4>kN2=z@i{TM6>)V0IzjWc!{#6yEE73VD7EB+qoScZ69VgmHO$c zw6nKbNt@s!rH5vSh=?L|KF(h{6??>6H6QPf#`DLV8 zo)Od(M)lBlm~~CoBAh2iQ3NB>dsIb`s)R%T+wE606VoIL4~YXpm)V3`q#|Qe#Pyz@ zA$0zl)}XDc)l3=K_F851{yv7xD;VB1y&g9zJ(C2^d%c6>>@xsq2z;#Duc};R(jGAq z^kE$G{uPJsE_a0=jZ=}_L~2!O>FlZ0ax_(ftN;qg=E4w#-s^8OmRB$VRt<8XzrPZk z4|%D_Bom=ELp)$C8)5-Q5}tW5RakDA({Vem3lnE8Tv=K=*1bwC-3?&ETAvGK&$f1{ zXOkKr5QB`VFYy2Lva?meoYj3_3M%EDH9UJDT>ga)ASKFvh)tzU@n*akO40VHZw%Z`2c9@W3_IR&>AK-RUuEr zBbN{Yb-21_;(NO;mkuhMrG4q{mC$-=hMj=@nPiOM3j&({z5hVXI64B!lmf2^rkvY6 zwFhar>23@GXBTW=OD~Oh1{8NBZPe>ZWOe6?nBgK*i?LvThF}~Hhm>_6Moz?|$obgJ zE5DOHAaplm;+O(@)T?-(wl1BZfH1reATH~77B03oji^O2pIqM(0pS=1*MKhr7T%6} z+qTTpBmf}@1OIzWcfDXL;$5+QR^Y#fGxvZ*EPVvKiZzPg}!3I;T>lL{pb6 z@KvzGw#fM9JGY$s5P6>(^UMs45Zo!G|0`a#5{%8D)Xm)y=3kxpG%V)z+MZ&xP#jh; zmv&*voWb60fWP-MoET~mq#PY0X)o>cjGa>V?cufd$oRgs+t=|r0rXJUK6BuG={^Q^k3d|)Vrp6eaWwLy z1d)YxaBu#tP=!g->W3IVs%w8wxTPq7jz7kZ=AZX$!g-3@U;q%3Cr{?x`&evlf2Gnh zZ9fA#U=f2^o*;F4a0Mf7TS8Wq~P4k--l237+b`qOAoc@>+(HEM)Nd|C03lU8QzO{;@UMGL%6xnn z|6;*3POEv7%6&ftn!KoZtAOjpel~YY1lA@9uOko{hfX4l1~P#_pag&{+~PYs?%GF@ zu)yb`@eXxN)e9D9<~BQkx<0^c-!#GyWRi!uRlJKw80wSezvHbEmc6o)vh^ zqY2uOm-#8yfF!ZcceG8!s8fO2)V4h3=`sKh)H~E?tZEnh3Q+)nPQ5%2zA0(7=^c2n zTG(9AwM$vK&AsOs*H+2MTstgC$ro~2Q=#GkVNwLOH#541pN)6>DWs=N>=LsJy{waa zk)Zudpd(n>3)6NwWUM1U&v!{K+NyRZMfV-)CH8Sa1qlFx zhbjsMLK2$O4nt@-Tjwq_o`>#wNd^$1R3K1Lp#?M8_96x#_|7mdzaR4n9ho)m=(H1y zxyB_gVmmri2LcEt)8_X@HkbN9kC*`}6*a*W1_DX~gdia5$v`+JWRbj?#&N_XItOuw zOcKn&AyJ@`0RVylD1d_Epr9bP4ROJ9i2(G(DhW4~A98Jay!)T}Ai*Gk5z!&KcasVkXejY1v+EYP6#~-j}dI0uFtB7UsxT$N-Z%8A<_2 zLKK2h-)caA0PQPF1SY6~L;F59pVWQa#(q;K?`N=003{L$4}a!;Hm+eI68SF>fW!@_ z;K6@>QSLSenwQDkPT8u!1g+%T!oh31JG?7&UAwJ)t zai-SRuC9i&Vu9sEMM@zBbAQ}hr`rjab231bP)T^9LQxSogd&z}Pl|!rilBCv2OM!E z$(&??$4SuYh@g;E5I`Uhs!A%ALHWBJb|vSKzn~qSaj*D_m@jtVKRPiZIazc8<-s7l-DRgN+3WGlfu&PA;vs` z06+q%fh53?$pDiANdU=p+5I_je%*QckncL`b z5Q%O^!1_j4C!#EK>LVfJMwRIWvr_zq1rYYj$(E2eWp)91V_6K?Nxyyl z9uIE6I~s-#&8=yfkyETIWmj>VOgk@U^}oJ%uvd?|o(DlG>C90wb~SBb$shzBK-C;) zzK-Vl*h@0=7MZ16mul_=3z&a`8nFJ)TmZi7I~D{4fY~Z4@+_<2E4`UGWMjaO_)!U( zQJ#N!?7j!>=x^eLSM!P`%2Yr&nA8Y?P#_+Kk3sY`@dr~y{@S(f{tgQ5dzs`{l`<`K0Y_Tkb$pV(P0cHj+Ao7Coflkd*UvLrpnRvF|@zNzlo>M*>f&^J2gZ^ zL`3dq(zyT|EDe&Vq29g>xBo%#bagO~CN|g;FnZ zS%wd?ZQqszd?;?D(46q^D)q28Z(nQRHT_VbF|_0~i75Kg78o)Xti@Z;Lv@6?Z=Pmy zh=oV2YVhewzhN79*SnurXVUFY&88^E-jlm$#Dx$t$4m~pL6?&DW1McX7%>;g?%#9n zGvHZawR^``@gV~+*9-ubPx^Sj4Ve`p;J173t4)L^^Q^bEytqy#Ax2OWl@RQP00VqR zv>rdm^IJcthCQk|9xt}&|M{j_w@mtlD)lI5hc3D5n49LZ+o?U5(RmtS7sYOvdfAKH zj9C^yvpLWc28_TO9~Bv1D*v2 zFc1?&Mo<(N)K{pq!{sP+T*iJmmS3VpxyMPi{{7#UqgSQI^0khg#m%x|6v#kGfFJ>!7=S=gj2cE=Ms?ONLLhYePxn*b_fQVC zd)9d%fm>j)$hclNDK!|LC%7KBZ)k+S*%~bc6Rx-5OfGrQ$XA)@)l+@%=~V!PLIfd2 zhswr7s>-lr$jTf+Oj(GfGtyblb;~5<^YU0QQ=!StatiY?S9l1Q1Gnw4J-o^s8Z+Ls z#3;d^tl1m-sTA!&OhAuo1ZqZ2$0e6L#&}5(ogC%^m!5xe2=3j7R(*xls~HN15E(Iw z$Ilkk&$W6Ij~vwX0;7n$9mSXMXnsnp1ZLP0kc8V>_RNdD*kT*`3T#M|N8IpQ5~pdH zVgMf_f^&pXhWZ}NJ5A+2(o@m4dpqSSCWwCT37e=;dl&aC*>oZxl(XX2Erw$T_k*z9 z=x=+PAguAV#HQaL>ap~8Ndt=V0Tf%(d|Pv@0QtVdtE5n$s@X&8H*FOX|L-6pA;DO{ zV{%NH8~x34QAXBs{K|Yo*akrNN0^jpHXiI_drL-yUEk1z(GbuPg*DU?lrVEy2VjKb z%x;Xo*bQ)suWOk0)Ti?na@isiJNf{G+0m+CFtVeY^J6J+wG~)d#B-MZ%Itw<;KOe( z_wIE^#c2GwZYwCF*cMZ`S9GWm5?jdN2>U9lYxZpEYFtC_-!(a`o)SjxC66WYKbfx9 z)LK*SwtQ-u4lzCa`y7I)w6s zf$(rin#S650zT8P)O0!VC!FXyNUklTUH-9xq+>?L#K(p(Eq5qBb&hw&f0wr)1voHc{1167auhX>FQlS zd>$5GsM7>O5TGcj;`U%EW_;*<1PnZK21-u))^VC(t>}XMh zl}v43crin1Q}H(Jdvm_%W;E6D=Dt0ljA9T=7%i~`gtfPnc(Tk&{7_7B-u7tIRrBCmasNBP1+ z-|#3jc{eyQrjx?_BR3RV2-zgG3N*PxcCyBJI}ZCW<@VT`OtX>G+z&*R-Dqv>+C^y7 z^Ur;s)7pdTQf@@3$iX-cSK?d2$A_y8U!Z?GKL;gy#?6sy6-viUr=5}U>}sB3S&=Pp zm1e@X@Z!mL;RTS!2<>*MHFJweonVm=0Ak(>=v@;Gp}bkg=(7(qCr={kJZLFbWnF9a z@A2toFr?J4=&{^$2=A=HH=<1C9?8>|RHf1rAA^)zc}BihB#TsLZx$R162n=x<2SLU zWoglNze76tUuD46q0k$mRZZNTMJ4?I7t>GJ$`i=E%SHV0o-ey*Q03?lC|C})5OUq+ zap1o=grkRGrt~;E7&8sy^3;Kihx8ATj7(wIzbg4zqd_6FB7ny*NdZvqbbpN#9gEK5 z!U^ubsF)xX&=vT>wIxw>nLEH75FbGk>FS<%)9^Xonnw%WN9T^On(v^DO(_%}9O}#< zYmZTLLm+E5_-dMi7^IdIefCft{IMbJ)E5G>_{KiQXVv?EA&$_JNd$sP1QJOgkU=%5 z1}m7+Thh{o+3a}eP$0VANk!6Ibx80+5Q#iJOp^|d%R0?R4zB_JBFH}}_4ahtc`=wZ zLG%4DiMqm_I~PT5rLI8@ub}VRkeyJ602Wt7i4oADt$hkeFsutCMPGw&&UjO4MiC+_DH+t^qX=MPg(gewtDrzL^NqDQnuwyN?^?F}-FWOz$4Jo|g)mZ+h2bQ#y< zsds)Zy8)$!WBU{P*e5eiG?>g9+5R{`Rje-*ruVQi?`yNDX=YLJzCiV!0+t=rs?s7T zuE5}a0DdqfOY@X7uAgN%wyt(l!1y&E*FgeKG6qr@q?0 zd+x_SVLeV~U*4T}JzR5LE0;ZI^63OSdfmwH`?}Obe+TyZIuu#g%7->86p^1QLzf$m zY~wq6#vpisM1+t=MBkLDg;P9!W2%9Fv}!MHRTw;Vd8^)#GtE&~qfzjm+s`a#BM#An zlypUeT<6CdET|0_ATb3P`e;U!v6Z9Ji<)p!IwK1b`UFg|NgOW&%f)lg43(- zUd=&NUPC(88kT~c?o-FUY9+B65r_kJFxJnP2?S+Bs|xwK^tza`bb`;<*Ee0Q6|0=S z(Jpw1cH*8F=5cr|w>w2o9QSzGEAGwR;do79bOvA3NH&!}$$YKHzT1|9F-Uiqb09hp zsq?$OL~9F3ob>8636C49B8oT_5R(KVGgR!W9c3%~fcss?uz1kqoA=er@O6%M+&nRx$IS|*gEi_BwTI8WJY+D)yL z&}6TPm^cWf;GH6|A5gCgisDu$=V*Iw+AJS*ymGxR2Ne_xEYk=-9 zA<29Z@c;rDvie_S)Tj^HR!-vL`))qv(5&G97lieskeerR zm>}5y@&zrpN)KNqlvlKhA4|JyQeD}_y}x{aTQlj1V>0{QZgxMtg&o;q#^GPg9>G^RDqTj)tb9)V7D;jOzN1gvYfoKQtfESDv)0Q| zYtyZbS+aK-yRat;|6;5Mu2# z2`&GB&|kIFqKg|&wBDU{zA{d@sJ>>t(>s)+axVI|qbF;z!LnY80=<-Me{&>uGgML# zDDZ}Qt^Sg3c;>TP^@#1I8{eC5P)kAwrbR&58=602!t$dS@@n&~h&MeUMh-?a2h#(_ zw8~%0rN=}`l-eC%FdTD;4t@0pl)xB92o@>0O2b4jW97Mymnlj@d?k?2nJ_V689i-w zE|Iq!%^Mmp<4F(^Ly%Y0#enihF2mq_4|i-s>d`rQ7zE($5+V#1Y-$M`FVgR~b(g=e z8Clqv6$bB(>-)LhbDqu0&P=P4l1VA|Vq$y+5Dj!UMU~(3dy}6}_c@>QHioaI{x>vb zR(WauxpN&qzdz^lTsZOp1?%w+YOhG$ZxK-N9Pz#EuBKJTuuSsuJ43@|lIi?^r~s}1 zLobE);W!iu!M`Z7qa%kX7yTXC1aS{|i=g)U8DDP!k^|fWPS+3Vzp!(DS~tnMfq9@o z1Q19lK|)DFfPx4hf(jt})Sq9&-!R7Rf9JCMm@lu>s?5s#y$=X|vOkSj_6WvRufabr z%5+Zdd1R>6yx=))_euqX;%rc_Yex8jSE30$1|`<6kt;hTOSKp%}Bq=L`^pBz*T{fj9u&NnvNnJJbM^tO2p|AVdm;ZkA3ZQ-jn^?V_-bBHUtW?dI?G%5JCOW{dCG+{{<<>g@ zQb4W0rxn8=K0dC+98QBltEzbRD*lo;U`9OubAu|J*4@2=EkNjAPzT`ygZ#PdNUt17 zusC2FSAPxP!1lRKx<1EygC)C2Q^aCEmsBf5L|FiGT(w;ZNL0crt{2fu0b3BrG=$J5 z29LR1unsncOw9g#MVZOp4U2UbrFzouW!()VuA-CuO6aF*^wfG12a+cA#mIpYT$^GP zpqe}f-rYbP03ivf%m*pcfP|e(X#4&~Q^QvHeI95dH}sqlLmTS=09*h7teP|hj~IY3 zo&5gZB5AT+cD^;X3RC6%FRO^_!-UAj9ON?T3C2~-c;$2PwfDofH$jn`4&Ht^%3cM< zHlJk30s6qisn7bb9g|<%KdQ>6X&Q`mmS%SvtuWvakJU1W1R->JKM=*MZ|ysmn|ys{ zPN-sk5J`s2IkY(S8qGW%%9qVPJ_d9m<@_FP<9N^+0rS@WioUQE(Ogi*nZo#D#4~Vj z+sVF!6oeJ*G^|CHHw35oZF**uLKW>JE)nIW+nh$@--UZN5! zs3RFS_3SIS%I0+68{kx@YO^kcVMG7`030jHA}VTS@3Crk6R*%!Hjc&{s6qk@n-||?R0!K%UrAi~Z#js^e=DnMWzL&^omAM!^X{MWvxScq57 z{Q#>SaS9ZHk6=_F&Y2)sSBS+FkxzUpAo@eEFw*c;|lPxP;OEs8mCm2=Fx6&3QoI zWqXH4>GP$!jJ5aua|*4VOElgWXa8VpjD^)OQ9q8*GfYgfC?5xQG7MswLnOysno!c2 zyfn~(Aff@`*rIRJ?wy0?AP^DsfH8nQ%B=p?ZL7?^0RBk2b#vF?^z2||NW$RF{0Bpg zyyjiqloCGIDa&Rv(k1Ud6tCCIR;6SgHHx1{F{$`p&P!9p$;8xEKw?!R{6f3BAC;Gz zY3brTOtEK}!yEqr2C4#_E0QP_C8eidh*N+FANLj@;G*XQ-vb>0;|v#J*!g%vlE3cX z)h|Yi52(hpgSz2sFlz*;LKVB_bhn+fZPi!8lGU=~PzL(%zaY=1zG}RD85o7BwbS-6hn#HN|elw!D44|3XMmH)cQ*5M0hlce!$tj!+5P=0!0MZ^FmXl|A=lt~ph*YMNrcw+C*5Ocx0XgkgVaiCo zz41RQpO@R*;M6EliDMW^Ad+8`n(gC;`b`+o3C-aX50lyj(y^f=m+FH|@i^6&F8tC?)W6FAF>>9uXE4kCRXfqMIV0jhVj=Xm|Rw~NjHg^?f`{Zkq7bdG~*MPuOC+9zD zaAHm4qC|)D2&DI8~VB->gR*z@CWdn&d<^X09jG?n%F;QDpOl(6vS0 z#S$K>{)*U|Jcf!5yaT184>Qp;p>5`NYd`~DZxts>SFo!RX6_srRcj?6Dfb_ns^1a9 zO4RSeaPOUd^@O%|xDDYWmK|&ybyT?RvphPy|wfl#J?b zL%leRMZPD8 zxzo@@qaLiMD5{x`g+Rm()42WE=S=>J`03V*5lARIgIUw+m^8=y^r{6Q6oCi}?pH2r z@v08!=Sp+lPGje2toc{d9p6~QVL*xfiXh_@08Xklcb>~{^DoIF+Mm7FH}`25OsQ%cmw5&dLDC_DMJNefVu@tILUToJtJ#Jyk4?uWp; z{F`sG*zc0=Q-Xiom?={ofyX$&lZjECx6TSDp_#KLP&q7)5n4`5U$d1EL!M9?7LYL z;8A6!(xNk6`aqDcM2dPy?`tQO>PnJ{W+`qkJMo<8KOfJXSyytcSXy=#Oz2S~!pxc> zl+Dvlq{e;$P1j38w|xH! z7(8c{l#1G}-{Uoz2wGRY@B2Duz~WHnv-q4Un%1=@lk2niIiGJ^8s&!}q}F3Te}X%> zx(ge-#C$)B8fsB69tE0V&b33A;e3eo=<}p=q z>7_5tnDOD)7sVJ?l1~B^q9$@cmKx_4Yt`vE6kz<1=nh0BY9h3rPkVf?=h*=A=kp~8 z6jPeJqI_BPbWH2;Y~fQ=A0gOSZGc1}rh@SHZ8Gm_1h16$YCg_@g1@ zfk3O!cXO}!z7Ic4(?erl9kPDD+ikbYfY$+%^;PjY&sOEjNLUSaUVS`?{xX!Tkd>47 zVn-sBr0Gghlfi;SL`+ePN>U&I87rgm8T57(l{A$!(@i6r{Jy7`O(jhwO*GR^_Nd1v z0{{gW+*BfjB#8)KEKCamgR@@<>FydfHK%oaLF`VZIGk49)8;1wI z?|n~t#t?ve`%X)=-Ziaitff89=X1H-<6>2{&bZlh?|xg!@9CU~n_M>s9fpKXW0X}~ zh~)-Q0x9lSftjaCN<~Csg&D^LV?{2ms^>ZCD~}MVnN8E2=Q+-}&#?Jfn6fsJ*kHwr z;d}Hm#rquX{0}T0ZAXw<7}*&JFz5@1%U8)FKqCS0fWEbGUAGsP+@0u)Z^k6w=3CZ9>wv_b_G z8E&nP?60~<_Id;08D)Ful_xeWIwEXW0Go&aLm0*a(wB)%z$i-Jz&L*3i~RblceoVc zsEfCvcO$5T0PG+QyVIUaX)Ng?FbD!bB=lP4$o+*y?rCz~-CM{a1O+{|*~~{DaeM9T zzT(!4Cqu2smaB=Q*ZH#Buf0C3w%YxN!?!mQ-gW77(|k1=6i1_b&MsA4tXgkh%=IT+ zvr3vJ(7Ih7^7FdO^fNE8oXGl3WA$1&c^L|@d{QfBMMZqemY=@(Qyk0diK!f;`xQ3i zOZX@CX~BA~Jmh)!+7WR>IW@%pX?(0KSwixNc}r)dr6?4Li~nlar-|w>TpksD=6sqr zE{2S=r!Lg%e~Z32abp{)-0nnLQ!0oT2jaX4%CAXgx&LKPwtipRZ^pZHnV$u)jqLYk z+EJRbW!gG;GepS*YEiEEx0>v>F2_j?mmM!$U(awiX2$wVC#Oc*X+NmA9;(RS{imlJ z?X&%_$hx*rP1@+Bltlw8o6#gHKH}EQwA6pZrKJrcbTmYsSM|-;oC=VkB>= zL0R^!;dPK_jwV~fQyT}t`^)?MnB-#%6fwxp zqF|YXM5hprQikY48NwH>U3Vp*Cw!$nj?O*?EE4iXR=xdf#Up3qpVFmBB&4N03t4>M z2WRYhoI{JV$l+K#lR+YHc3%RFwvy=Swrv~Ob#yqWmU zVutULmLCgmv8@UXltSmSe!-@3E5(d%e3gBnL(FtPa`z){$4V4?uBIFEuGHdceBsvU z8i#$icYyY{KiY4;j*pSssvA1xM|pEkdA#T8le!32V;fwbokoc$=4#x^0FCm7&YK`0 z*#P2bJAO)cm{*7S!THKG5Q$^ds(Ia3S}hg)tY6*Ty980G(b@OEx1f7JwgRwHT8>xl z2^l$rvny4ZWn?T(-}A?YG`y_%$fG5I9&Qyn(?AzC%>dXyM#O|nAmnwR~i9QXzi_&=L zK-JT5!ml;XRg%*_NL`GJYteyBJrTmn;>`oXDf;}K2FBD<+udo}&aSE-tsFq^c87@N zNk5&fqA_~UX@jg+)&J|P{*r3)2~~`xNNRud)CjOf0#$Z7sd<)h<(cc^c<7GH=PZ}g z3|_vXq+l)m+{UT2N|a&wj9X6^51Smm-R@GjaS6osHLx_^xcEt5(g1&H0!AWJ4NgXE zh_o?R&&XTicN&`%8B_FbP%VJ9-ZaWZxwndl*DHxE5;#Q=SLU(`MeVsisWO$6v1WbF zRbq*t%5vGQ5#0U)knEROAddQUIz|Sub{u*eK8*2iy1%JuGqW7*$zc+CL?lW8g}-2= zx6byTLvy`O%b&lo@`lS?reGi>(a-b#$l|xhkgvlF0AW;9ruU`e1g+jRg4!(AN@I;d zvN;@R6!^N#U~4glIb7}&+E==NwP%7TEcXf%yd!b-@P`Jd3&aS7A}0rf;=!lsyh(r4 z+koGg0sN;I=@)SL&_a4*b5qQwIExU#GU<+#@!{1V%_N(EKr#H<*pu@Lm(uv! zDQDBtjC#ok*x2$tjHX332gTB;kl`Yc9gSwI8_SS-M`5<9;5ntVdhu}=t;L2(YIb4b zsR%7wOJf`49)-B7Nh-3bJ?Uu%Fc|HO>5f-5q$q>tKnb*_EhJ?42bqllKhT)L91RPK z>10k#quefVY0;nnJMC( zk$@}?)u^X@mjVKa1S&-{+{0H-{QR5GJ+>eRqn|}8C&{^tNxR6iLBz=e)gUWsM}rPN zZNpP5o;6&$3MvzqEt92B;(h8)PF)?qS4duqZp)}vNxT=+kRu~DfZ0&8CIKNqrx*#L z+9wO2&gS~&29NT8j}bZShsl0&6L)=KtLJ*AIbp4E$$PYv*685CGUryrsM13R4m)w88y8MdF;5Z7qjeCPf~2|=30IcrjRgV|0Rw;Hs%NXS zygy39uVsW?#W-HC-Rjw!%yGdp-qz>Pqd|{5$K6K!5?%Kr#hJypiJPbEEIKAFta?3HN2G!2u74mQA7Z0a$0|8 zr`NsuBky&wZ9DGsi>#l~Rb(18&D3W6#{h9gBq@mVo`T)GA5l3Q88+T(Wb7+1*0V?Z z_j-3UU7hTf0-TKpDgyGxic^;m)@O}Bl4i%bVm}ad^)lm~tgx%=*N=#ZKo%jS3mYd( zFnxPY(468&?{n1|j6}s*pVoAw~0Lp!juhStpdU2*82z~#H+5K6TdwN^Zdpi3O z-sT1`@nm zxBlX>bRuNwUYnU6T+)E-hlXSkSa_A&9tPtczep@@ODkpykKanHCrC|;WTddHf(97dWUn34o+WNynnG0)d|6u z;JgiDpQx9Qpo6@;#$RhD{wX7;`C10|7s&vf#~LtJ>I(!=d{j9gkCUH>g^Gpxn(RW0 z2ZE^pcsvL(mpMyDqW=u$Lx0I}aORqbi`S+=s>G$4*d{=2Xe6a0Mh?`bB8hZGz6^ZmwuOuKcGpsG>+d(qcPvuv`RQoIBWF>gETR^tRVK0hs7-z8*tb&3&B z$7;4nI0Dx>wdI~?AAw*=DzeU>)KHZV4vz++)0e^@+p||-YZ+P$y8LFs#!e{Gvw(KK_WfZdn$45#?9ciBF^7>t z+Yf4k#}TSs`MIx%mCbFzGHR`>>tw=R&X_*zFXhpbV={WJCLHP$mQnGf*j$VygZ(iU z%RpN5Pu!D;tG1{e!R-T&(RGbhAP|KJNA986E!Sr4POU0g{|tNAd-)!_^y#g&wP}AN zq&UpAmFwu>2q0WtX8msBR~i^Lk65BcE0_JfY-iJw1A0#&>4F0VldB2mNkON=%S|`t zl0}WY_3tx3*>QTds7y){zk^uA1Lz{PV|L3S9SAH`^bv*tTRXiynP7^%q0U~HQ_g>h z$3V6O43E|rS}6wJali!nZ+@f9M$9D#&T&haK7}tTM;$09Cu}p&rOyA;h>88?M1TgM zD4)fMJcVT_Q8lsv2wJ7;h!KOz!vnxHk^i(xT34Brn!Sw+S5)$V@+@CRV<`IB=L~Gk zra$~=q=+VXkOOQTGy0|4CvE}E+v0A;m6sum0iG1C_zvU_N6ejc@Hgv+cwpD0K%5(J z2fZ|2W7RvQbfhF*GPe}g_-#FsQKNv*z`3GgKm`0zMdeqSEbU+d;*3B75-GRI=h+DG znHxU>dIs(qiPk75V>onueEJe607VNPY$O?`(bvI0)K)%GM+{BD|Nng~!UWq`qiBr2&hTO`;0&||G_|-Q99E?ilimXRvNvEiU8`S6WdlXfubTMeWhAo`oKJi z9@`uOA}6LoB4@z?0&TSYrtnhdyWZmJ?hG%>bC%$C__H!<)qHy?^V+vcEHJy*ohKh4 z6)j!nZtK4ORg34GzHfRNuU3p81Sa)&eDExO0prT$)>j#y6OgVL|LFEr3_2>jS7)i( zwjkuHq@Bnh*WsgoHX<1oWNT)zYf3CZ+Z(#!gt*JO%2V{qzsn*u`$9UA?W;dwYnwt5!0}S~LXE4! zCTB&h9cj%{?qZdV)wz>fD`DXr0Re_r=tOqr9<3Rrhg0L7>G7}EAWPu|SZ)f&;O=)w zECuQ=o*@-xBHLi+T>V&y7dKuRC7kpimF|vlt=eWf^5*9$28hP0qg5(iJ|MnG!Azm; zv~GBKUc$kTeZVUWfb!^o-?1gpX5*UBf3by=0EVS%s508RIUn5B~GttR= zkm@yVaM4JAf1q%b)fG=F47(H!gs)fr#P2}oersx6^oj%k1RxASh(H3ecg)@yTkqjf z=b$?}0)i-AwsOw%^*|t_GxBI54yBAO179<%W=)!S80fR{4o9n+U>{q!cv9O=c{E^( zi-Et(SE;>&u1g49m?*TjRV78Px8iIxLX4EvkB&KQ1l)dgXtQ3Z)}2Yi(qsWdK!9N8 zB7lJ;;!7FrEb+7PR=xOCh_bn>`0>M$058>qEEth7W&=gV0H42KJa~b#Q;SX9v0Ru! z1+ml3z&C5Np|{QEVDjgTX7YwFo4@ooR`G#>zR6iNU)D5y-uA7&Ub}+ zSI$+Y0EQGR6UZVEo#;7E)X~e|W1rFWfwvdctf>eBBL>d~?EfX>Xd=*EAOK|uK%cIj z9t=u=05O?m5D*Y1BT9rKCHd;u002%7cS$!|C1ttkUHskuJ4@Em(nyOFFPZ@H(hy90 zfB_Oq=pttewW)B8A{0$2%1O?IH4=H};R0w^<4weh-fFs2Mzj%0ve#tv@~w;{P=yg| ztmCe5FRpgJ&6o(315qQ;9th!$AB^6e6aUlmn}Y02Y=Nqk3A#ca7;YnW5dAT$pEL=6H7Ne#CGZXyIH zklrVK0c>nVM6TO!TOeQY$0kTVh@Ft0lGZ-e15Pw`G@$&N$}wa5D;XaxT3C-RUGAJ@ zC-r37uIK!barP!J`yTH}7SmPu+GhThLK*M}U5jxYXSmD1KW;8AFv z^%g+XU=sgKlX&?F2*#T3)D_)xUmKz0CjWwj1O??ND`41gI?g(;SH9N}D`?tTo%Bvr z8l1c}Dkm{+;wltioJ38vpf$bIyppa$Ve!t<8Y){`L{bk98dx4KrcF&@b zMH=7FX}T-v*j+ZaG2Rs%&R(1iHG{!{@W_+kLViHa?MHp)_+iI*?G2o5nS2{LX`*(O zo}`<7(U0%1B&nCLU1NbBu#l&Eil^9CruX4Zm)E^E>-u1M*rF#&Q8Ae&!$))*5wy_= zXN^j2qQ(FaVVC|N$Qku>8GR61;#^Pec8S1;@(@^U-t3k1|C_m!I{!ro6ZSd=ThTWbzOtW+2(bw{R?GnYhiy#1Bxd^kS#a%uVgj_JE6cQ*V^NE~XB zn@k6J?K-VLGl#J53i2RSrO-F~vm$u@ob1kX#)+|=o_3>MY-2gse%q{T27yhr8<%r5 z!+jAJkX}dievV7&_8u$059j>mHus;~y}L>`9r)|KmhTfn`AgKU6uWU(ZNq+qgt_LF zV#Eq9sM5yJChtn7eMI0JNUPb3upGgwm7Xv!F~@}Ka7#6kZsY&}7eedfQ?<^y$iG;; zSBn%iCu5g-Uh?$CA>9`$9;@?i6OpwPwCxo4R&RiDH`(tH|0D2PR{~t+e+`*B?U9Xd zS>kA9&T{?QqOJY>RcNnkiM0?E;YX1XmY5<^Wc#{t-Wf!Vo!wz;aW9Qsj%gVC^v8OE zgFYF0XeaeX*IK@e(K7>WI4o3(R*6&k;?oXZ!Y5MNaEKJmQ0ad&oZD)TK7BZj+LS>o zFcLtbst7@sH5Rz(o#bAuO4+OySzzd`EyX7y(Q7@qYI#+52&Wi#vB3S~PaB}}l_o&e z;K*9unD9QF-G`Ab;)ODq{qu3r$3BI5?IlHQ>Y9=R+s0#NjWhxXHEy*JngASX5THqJ z0H7xcQiv%3r(TF8;h7b7Z4A2EH@C^ncBaB%6`OI_>hsG>pEm(PUgF{_v*x3sikTIb zZ4^~lB_g*2CVw9D7UqXrf}HgfRKy+z zuWfTvTT~z&InKn?*60yBeu*e?)hv4l(M!XVV@#4sPbb22p9%5F1HZ-_@c@*$5XK=Q zDh-E+&rhs&lY^gzW#J;+kj1NN!mga?_HM$|BjIsLhbFjSrjM%2bV^r*5-{^|^l`(2 z%R_>G;AR>)^Qm-fr7*FzEg$zEX!H|%ol>21)G&sbhIvTd5VOStG9~McMIkgnR$0_ zSBeqWXv0~qw~;SPQ7Yv(WL%0-C|^GsEpyZ=r;GQY6gBT^u3@S`IMyOECWy#V5_97~ zWsD5FR!gUp5O>@vYbZbt01pL#bK6*#|LR;-tDT8abJ3=W>w*+QLzDB6F7WipwN^5xLQwc z9>c8e?ot@LY*V2^lK3qtV#23?Z7xVDdz=8U2;Ytjs$il(sP0A8);TwFQ0B}bBvF}1 zAwr)SH}UJNo&|1jHFPEff`}{tW$2<{G3q!8*KMikBQ<1_=_(D?1|CHNQW90KD*p*5 zxr8?j?JldZ;(Xc0h|0KD)IBw!Uotz|cGheL^l3CY7Bdl&u*_iwO0K|(=bN9Xx_T$9 zB`QL6G16M$XHAkzd2N7Th->ugCc_uPQ3iYL2gYt##S~?5p!6&b11Gp+9e0Okb)UCj zN#+1@hLEJP0+g`J^K2R-^-BJt6BIzLA|jULB2*A1(V)SpB69qyz%@nbYKJrtkqzcy z>_8;fqQ*dLa2yx_JPn##CA9D3byD->Q$vk+TTg zt;JIp&~@`|Orr6X$IwXMFSAH!Ph8mw{{8V5Lw98Mrm7(SzcJw0sTGXo)lg$pb2*65 z4ITpv$D(}O7kxZWtq~mn-K_R}d`*$XJ{JF(%O&5C}%j@J`6&S1&I>RZ`}uikLZUBpR#)>3ORI)xq}p*ptdwDg{YaVJ>T zzNdL#9E6tN(BfL?Vcu_-LHgWI_42T5c8`hujX$SRJK*Qs!!fp~qFyRJu7B+{+0_{@ zrTe#=tW@u4W10lr7M)Tyjg&|f01u?9nh68+Rj=0=c(8<0L_(1;l>X(Q{7~Y@+&a)S zABQMA-#>oLSviog@su(mTNix#zAy~Q+y9;XyD|MXyBw3|{4Lf&; zY_Wm2ftvD=oq(zuaB+_@Z@Yq6%&jfBeut#{bg$7x>odSeY^>>PX@<-$XwG-2vYbT% zhNPY!0XfxO-uvcoQZ=+xm$<1N1 z5>vj3M7&JGh^25+WajnloQp+1Ix!H?*6` z0)cobC2(zBXKX&Jmj)`3MX^#8s{{;Xz-TN`7!(E~1#jg~T-6XfU^1P{(dpnh;@8#o z7H_9PYXl!0w3~Bh-q5JN*CmoPQ9U*=gPaBDq(h!$JSD=D@JE~+rNKT*-3kF?o>H!e z9u6-xccC(B0G+!cv`O1t|5#5uX)G&al7K}=5KEQamrWFDL?HdEu!}G-l^tw1-&=-i zk%2Nw;Ld>GQ{eHiS;K2q+{{cYw^b+)Z-upBX^4uZ3_})*hfMHYgxfs8nd9!1hY`kN zQ79x7zP(h77rlw0{o05KPE3sXVD(!L1ru4a@}YW^&C{Uetl1u@LKxpoKGo0YqPDV4 z7RKwfVsPlFM+ca-&3_EoUvdqR)SnQ6fCio=#FyU-EXw9o3m>qrkxFxAAkJlJu(wyX zmN0~+xif6932yd`-&wz-2IQ$|N3Z8*vU#ceb{Q+iX18WoNF^rXh| zsQ7-rxR^LYkwVNX?QFrQikU`MrbNHPwdMj;VY z#Gb>ye_Bn@l1U3#|2#K5BXgc&345gCunTT?8|A>suwK|c|9TV&`Vm?n@C z=`e@Li|75u>PCKYf-nDd9+F_F)<~r+QWb%h@KXAq4xjfw7Pw$9+5+7wdNgd|SmZw$ zF`9lw1Y`@tY5j@hV9iJkf83Z%w|Y(iXqBfp)5WgoJ>(lV6xC6 zgS0s69wEq1fD$3u(t+HJZ!N)%IURmcTp4x#+LdcGr>)QJ+A5*0PEdHjS}Q&x=6ud* zV60nQZUV}f;R?zO zG>&jygzWinUCt1S<^dTVnW8LZo*uLuRAqhE>c^w!|C>%C{RDCA}yM!ezr>_4Z(T_sf{9&*?#4wMT4T) zV9Dn0B22!Lq~yxZ7b_j+*oaLo?CH@p{-#U`At+OLjf6&0_wq{BgOg&Zyo=#Yjhk4) zVHJi!_%RDC)(4+&-}d~^*X!)h(DQu=zE_v>cph&V9lh6e?dDB3zvm;!QT~>O*xY^S zc)W;ccP&i@+_XXSz3ar$JSIXyXGLem>npyvY^)HggQXWtLZrAsbC|%MCDJ%Hv-W7K z9h=o!iBMYZUgMxt74dl2O`St=R^TV4Sv}(WdzC>ZSXypdTEsvQ056Il{?t9WIK6b5 ztKAe!xZct48nCs9=5@Y}stSO9>MJmjxs593-Wyx7g`N{GhqQPLSd5%jBoo->FFvbG z9lFL$z3ps=)PgXx$`5UtFX_czA2&4u7=+xSp^6uP39roDPjca~{YxUb7eV2C-=mJ2 z0Fp(M_$){Gwd*HqCUGOm6oed&RKcAn4F2Ku&FoPSRDGacXXZf znIAkG{+x^jc1k$AO8kfvD3K+Rn-(_k=u<4u*WbmWmi)cw;o=x0GQJ$l#Hsb2c^9X#ku4WG+J3*D8dPczgB#{_o0~Z7=hoVY)n(Ea*z>hY zUvWyyp-90Oxjcs%A(DGcsS|u4SVW1_q^6i@oKh~LHbkq5y|hwvLmHAZRI)D=E9}MY z33;+dgjXg65b%_3MIzd~D0?Y{KRX6Y)1E78Ypzif1?Gn`&~pqevaW>bXLx|AO7%rY zw|z?JXZ6h+XN5l%aV6X=bz?^W_DBIvj1bYa8=c${UR}ZS>f{ly*1D!{p;!mlDYrj( z%8YU#;&<~Xz5ty)cn4p{h^nN^oDu^K7{!8WTu(~5W6F``KBsep4?WaIvmxx6AY;#^ z_!KSQ)5Ra2?eYHWMh}>M)@bU#tNVK!;{p3St6>niM7)kN`~PULprc0{jX0hSx7v4E z$YwE9lpj|L`C1@o@bR29meg{C*1U%ER({= zo>b>GZD02PSEA$f^!HIHh=JhHN#@YK&2d>a6*RUC6mk+-*3#g}!GP-bn?J^-Wk6$o z0)TCOJNMSZYp_(4o0j57KCLIDu8+&9Z5_JQ(Sl1vY-KpX zD$QGH!lkBCn0qd)cu7MeF3DJG;IpS)J+d&?jLfZLosp##Ih@1R^g0$?bYe)%*5oaS z<1aT84Yt~LEhnILiU~_k{+SS3z(nrla@fgLpB^nHsu~)G0d!6%Ut%PAf_ODPu1Uc4 zFs?vZn_QX@h8hZ2MFr~RvLl^pP?f!>Dq#ExS^T5Bhg8zpp-*LXx%lPS9uA@Nzc@9T zUG|%rP+3S`lrLsmTqyFm=Kji%9bW16-K)A$J?05l1q68}iTL%jlP-6kK8%3?d?sqY zK+k!ZbNw|KA{_CnyECg=6SftFoC!ia8P&@x4F3fP^R&2O2S!0TF2F zCORt4>`E5t*yHgE_OJQ6R#MR`^l^#Po_4hZ3J>bq`q9)mFB9KlIaFc6g{J4%ZYMVg z_Zqv0%Z-IefhH8F~YP*PBj2+t=$YXcA zIan*)5$!$wGdLSci`==7OWCWjTSbsvY^fxKX*cMV>|xlD2dYxvFGOgLhMndjCrYe9 zc^dT|u}L+!?AKp`^|}hcC7)+3GW#q}cS{F8h&pR?(XM|}oVe4#KxCcY1uZ2|U&{Jr z*>AA+70f7C6evshGZdpDW8#Q(mRL^OE1n?|nWqmgjeh!evXIP8$*xUZv+ zM`VJ^J2Q=nwhB=@1OO>Oib4QMDZ_QJrpLMG!jK_Qh+j~kqB3G)K_q}kVIvFuK2fXO zP&rw!hbhvSV#Xu5=o-jqXd@DB3KlAfy0z108LYVtYZh6H+_K-6nj(7hYd-$Rd$Z>_ zb=bpQ8TDzCGtv704zf{LC970Qgb@M(g|I2{+!??G^qLLqr@(ghznJqA>twUb&UI&0 zs^;M5MgSLms^Yjj^jv-K$l6uGKxNMIS!L||N@rixL+b#U4>(Yie;trYKSYCosRrXZ zZMnf5d^tQx#2tXB3ZV>P?c|3BWQsWBaprDc7Or}Fl~6>fGlx=STs=Lj-<9xesfN{% z|1FJ-O?K(??EScviJja+fZuHLF{#kKqSbMinUswP#SWW8UhGP?8Le9beLS#6Y}JZT z&kkg=mRC1nVFc-d`nd?cxK`)OQ#wd;%(1t$ZbjK!MJZatwzkzKL{SXz?k#v7V&Nzu zl-l<;L>+@`Y-ixx86CqAwW0C^i(fs}EHTx2l>!if2{vTx$skUhgb$t=fWIyf+7M)H zQ!-3!Gk^Z(XES%6$n+g^hrCtwzuH&lxmzi%-9N4M zRPLRJEhWsDTmu2!&f$g|kq%6&>_;fq2Lcc(6evm(n-;?94BQbE5W^f8+)AS19uJQp zu?C32CL4K71P+;XZd6B(O>z@$8&Qe!r^OH~?;v2$u}?-U0v+00pST8Y`1Nlq;yUIDhqO%%|8IlV7o0KmD%{vV4kpN;cv~RKb zmNgJ`VguF(@=23W2?(VWgjLBjKak&!u27KnSE-Ovr>l-_GpGBw>tA)zGfo*ji6?rF?W%bpgUcj;3GvNaqrUW^;L#6IB?Ad*okrNShXNh>KH69D$q zLIr6TR!W_O)-pzFoG@FIk+tX2w{gGA%byNg206^VBaoKXRIvrA2@EWv(Ks3aq9jC= z7{LLQFaa1G@yTa1iNerg700Hj>FxUU%&BzQ0BtC#`_{DJ&yoN~K)Anb{;H3|*=Py@ zaz@GP9^_Ub*~f^sz!2l&;`h^?k5vKTi$|R5{60-x?4?EqR%QSJXyaeG@||rcs_r22 zZ$40l8YRbu>d0P{xm^+kaJE_pR1!|~lr*&*FQcUiB``ps;MB&p-tAPbp>cxZ`5W4n z*>80p262W(`SBf{+WjB1ZMM6fckDPck)%+i9x@{|Br33+L%$y^=LgCk?9Yy`JwCcf z))<%d!#Ln1({)dX5VV3JR1><(k@o!`DJc*lG$MdWPV0l3`8*8fZ-=9Ai&k0ds6V{! z2G?fehov$`>3zCW2R+ybqMg5y0Xf+0y=nJ~lJndEfH0Nh9i$d1xqJT89rt7J^mRSg zD?7Hwq>+_eXsjn52n@%Dh6bom3qq0y6vN6A0VZL@k`p>-NkSpw28Vvrt^Ve=!VbwW z;fq>$V)eA(38h-HHCZfK@<8nfA>I=LQ4pOw1>#_Y($ehA=oulB8?fyxEqePl$S1KD z-Hf^*0u0Vs7W|v_Rx4W1`a@(yQYjM7$V2Ao`+NFIwcm>AS|(1e z+2L+~*R0mE*LmxSs)Xb0qa4-vM|1H27dl!sLz>gsnzIwjuC+#VJhsX|758C2%^6MS- zX{`DmGtugAvjqpJa?^&TN(-vd*{3&RH8TALa z#VK+L5>O=uVyOu*l4NCNv1+lk!zLw7ac46&j4fU!!FD<0xJE|S)n=BpB9Zq5mYKtD zL?qcXrXI;m;%hQIneerT0_bf**N3&qMwWoYky=Ii7K1}YjBAA}3-WR!W-kWV&YD^n z)}gXXU}ttQOxaVooJcOorCMCmEIl&P=DQ2XXO~oFw)!xHPDyBTx($qNsZ7zVm~8z|0NKxRxC)@0U(yH3SgTTa5*Dq1rP-yagoXsuf2Z*#4hbB^Y@3deO|(_G0T z2X=Btbfk2Q^Uv{*NXql|e(b6gVjVxFRVCvrgi*=Xq=J(uY;-y%Sv^dW3JqYIvWQSd zvVr)p%_%Be?Q&)4_%&nuG6T}BY7%G?TIr=3b%jFmyg@>Qr5BY0hqV8qjv zD~mF10zP zqA4X}lF}dolRZB5n~Z{q2eu;j&x<(FF@_-5gt=~QPI97&nZMnnqGNNNdgS{&M95v# zQ8wp$JMaL(02nqUNk~ybMH8bVBvqSDT+yqRlL{1|-o>wOr$b|rJVtUyE-`!+Lh01`az?-WIOp7aNXwkKVO#9J3uY z_>TF$#nPrQj*Qe0$MFp6jXZz7f=xOqe(GhS`bUFSl>IvuP1y*TbDeom0006Y9;%KH z17EzJ%$Dvb{1KQ|02(y}Fv}kg6E=@Hj97*vcCo*D-pi}ht40mjX@HA=LlyL4R?q5y zJsG4OUD9iYoE**_jn0!o$#{4^CpoX3yuvXpciNbCo&v-U2n8v@pt9gt_gk`SM!yUB zs}VCK3=g#}KCH$Sgr_-PJB=z}IHJ~jwEHX|-*&HYPkO?#A{~a*6;)^}Vxk|Mg#@B0 zQVKEKeQGF-fXQJ|k`vN<@ko|aDcdWxbERTOEn?4zZ0j+h52eOQp)85mhZM8Je+0ou zc5f-aQ`Xp0ELiB?&9Sah(?kY%-jetEGiH*rJ)3Xq!Qb-xXxN~qdff#I>IK14i(X{4cmBk^+qfj4P{5;jS6{bk{-^VUKsOyJAyw^W@sUhw^bhX;m; zVcp3^*FvL_be6ADJAjG8Ei5g$+(ud#mpJ4YME&A)EG-@v^ka5HOcd6T&_Z(*#meTR z@M_Q}g3rd18l~J@^1wJqnJ8r5`yN9Y*>Bdq_JuEvxo{?hTNis(N^t^eJ z354uC5d=R&W}?xk;ECacTZDh%oE7G|wW-d{(5YKOUjgOhw zGP|^(ltY%-BFA(nraUZ++H&toCbTc~Lo&t(FZrQ6w(2Kz) zcP9;lRmSO$Ytw!1I$Z(H-Kh7c>WLDRaGph;C9J?kI*S=#=CWJ!*!XgS%3YKyUZ+*G zU4!;s3~~x;Xlh=5;(gUE7~>Dmn#vAyRH>R2G8d7MC%B&Cj#|YDB2FVa*XZqz1n5Yj zQPjFg_bMX0#g%eBvh@W=9eg023v_`cEv5h=?<{2 z7MKYbaFx}S7naS}I-~80&^}+qh;PgC2Qy*$iXg+1K+|Hs73ChUJG`Nnk^Xc1<(DIm z_&sN*j$KSD{HVQuPY)KXzzxgirU6oq2vAV0*%^#yAOjpD=?V>y&eefG=~o;rvfd0t zGA=XjsGbVKx!!RNTde^~kXzOf9BedQq}K z9CbQ0kHSp~oI#91TCzldBz)R9#AIv6sqhdvkLUKcJ1Sul?EANqZ6F zXB06KUg<;n1(rffAikKp;Qk$<;j1ZyJgp*u)n&aP$Iqhj$~@l-c)>1JTk;$LQd2wD zQZXOLVBh~_RFPZPM0(Bdtaxt(F5XDqt|3|}Ze)CxzC&Ck9KoWt*$oaU2%|(FhF_`q zF~e3L;S0vy2a;Sq`Z$vt8#0FN_hA((Q32@Uw|Iku48*Rf%N!swb}on!ijP(AqkrFc z&Xr}$q*Ons$QvR8m_pT!V9Y7djgmtxv&}!;M7ytkq1;q7;d!rUzlDy|x*6%2CeKjs z0E}+w##xiw6g?rgEM?r=szcRq^YhAftWZ?OkxUv7P)ppA23=Og0s+2Y()DEl6~Ur; zNQxx1WxDnyU<^7t!n$J&*5Ur9!0!Hy6Dt%V66fww@2zBzYPKI5&ue&X@YlzEq!gkc zoTDJ;AZaiPudp@_y!KzV*a#%b%e+YMet>g z%WF{ZAC*VBWAd?+vdUc=4pUe%U|Z4&<8(lN*za!Oo2sviGcS9Dmc^JhnoO0XgHkmy zK>$etLO{?~Y59}GQU8oMd7S&nbl{gD@J3fW^YRL&q+`d>2Vv=ay_ys*keC(*1~p<% zTGI}eql`k063KPM3Pd0cHFKO|6l(ydEFOhP4_8a zm6)kubn$YKYfhGbh13i~?MCv(e9TO?3@xJoKDqFtt?CTKz z$Dg$HQI_h&;ZmYrD488$VtWWf3XIrNBMxW z&er@22YXT!5B`Yjw1qDnEZm$TZ~A%(pcjoDE&geU$lJspMqw(3^kLWzl9i&5Vrz1* zp6y#z(#mq#QRg@-$<*5g$KPm>M|PQg9Ni_m&d)XmWDgX+MpB(>=hm`8&k+%W+)$aa z9+IdnNI{4p`h^*p${!-U!?#pP#&MHjg<^e@DUThRP~#k|M~2x2+5+?KWd#VW#FzBh zuYaj*?qJVmG^9m5A`n>|+EM2Gc7+$?648z94@-xJ3?ciZcb+`(!z9jutJc+cjlqNF z95o+&-)QKw;gK2dxjvKAVBaoeg%1X3{BTEd!u&bff+ldl=SR!cyl*Y1IQ-ZZj=`{SetmO5O5dwbPjZmcFKNn}(I zR71J2r|rH;tDyFDGTOOIJkY7|$S~W)6SCV@$3cTR)%v~lL}PKr(Xa(0y(+vW0K_PJ zwF(bt9#$H42}8PnYj^WN{JwVb!j344HLgla^uGUMC6ih@2gO1lNdW0vgGYuZK`FD< z;775u)PMZocJv2}T(zhX$(A|OYW`wK z8L++KOG)S@UStPpIFV1oo1@&P2HwOi;FtmS+~HarH}s&Y*d#si;33gU4cssG>9wRWj|TPv!;WQKJs_k^g@oo2$D=Bbi--C_8^RFKxWBk5o{yCjR$K4~n@i+fU zQRF;&yuNSOF8{!nQO)0jaXJ7b7q}Fl$Bv4mEa_><69!8sVNy8a>Z~PoIF9nFOpxLF zJnjGTjbG=8O#$aln2W#lnWsR200x|=+Wq}JBkw({ZN|12zouoAwLUJ}1EH7V1L_L) z1lji?0Qow8OTf!!UD1S9l*UA-dL!@1pqGHA8>hQ{K-O8GVWFH)2Jl8WD$qv4xi$#J z!*(eOGL)hSBE|PU`x2ah3;-B6oWc~lGKhhOM7bmm+LIKcOvI3wR8H>*fzb#Mh$TEJ zCM1O-q`bK?&~bF0m-e;EPMIkI4-?imILL6=$>W{O@4u#wOVt0wRS`AGKgO8A(=ebQ z*Yr4){T)5N-viL!W;0OguU(zMa&m9woh#X9$I6_Sl*2WAPSmfYq~%f8$3E6U?@oUdW~J_}dD zUGJpY+}?9Dz3Nzyg1N9fCP;}kP5>@55uU6 z%uA}^z@|wmH8>{KA*#~`NMjX+?dP=^FbGkqi;Xtz%1`V&wHl1ROLx4#rb1h{nej`kIlJZYqIIJ!;HJ6BOleaynIg@uzPX;L3ZeLbd z4|?M@-bxB$5m3YEYd;0_zM?hq45A1?GJV3U3mSV4c+~squ_tFe5!LU2CzvZIkobZK z)G(1Q#la+GjddUAqS3@g!5#d0k1@i27;E#2ZScNL=DF*|?MBy@ZS10M?uD>`q=Frr_1f`ec}c>Q#|PV_lvj}*J0bMFz89%9Ql zr}@tf_Lb! z^V4bcMkKR$qmqHgU#XR1*xIGI4cCYlurn#KUqn6M@UZSK!n|VdUs3|RR(Bb$PfSjm z?$d0CPN^afzfQ|{wXIu0MYw;CdywM5fWxORbpC6`4}AE628QHBWN7q!WUg`%nos!Fb~(U?W&+CGo+th zZ)tEkt8kT9{swOt|0{3F<+a=t*uV4P>DZFOuA?fcilxT)6DECcgyw7q!h?$?924~C zEK?~z0pG-tphVS0drDVb<@kTvZk_Hprng5tWRQYKtM|q~F^dA~MM@K>wOhSt*Lyq7 zGteJp8uiz>!z$U8iIrzAq68Sk(p7nV-cmDJT%c2CsWeeruyci*oak?X4ALiw zo|fG0b4v_y9~<`(6ngH-eA<>S_6mkwh?{PROs*c-7uah=O};M?CX4mOhvtEGVZ^SyM*4lG ze0%GR+#P1F8vz$dCSsHb$@3`R!mYpeeFk|ui%YM`@r1tTDKhO~4b(UG!x!c5LC>=7 zB@kQ3FZE5r+8tadi<8J@aA7V2heu1;smmPaYW18(O}vv%%Iem-JS-Zke`Z(bgq6D7 zr*kxtnOTlX++IC4{;imvyq9N@M$PXfzv(jdMNR9Pae9=Xo1NrDz2|L*56^r@d-QUP z6&ko_4eZ|C)fqcBYetY#fYAI2ppxQ``H7=QT+u&~S{df`)Sk&d*~qUqoO{2}6Y%95 zJGkDzV0VUqMXpA3B>A~v-7UwsdQqYA0YHU(HdHlv%>(ivKwsXyZaJFE|E1HudAB~3 z1~$c}?_C4Q#3#{P5Y|l1qXz%(HgqrkSgIT2F?maIVx+W}==T0~l&XELe7r?a*gqwg z{)^&crz;+~F#XTi!KXF*SZjNo1+2d9;mO|CUxY6o?P|Q&6as=6t?r&t5JOH1geda@ z(Aa5O9O+kdpDqJL`Zx;*@#xIUHGZpLg7J&7N$kGoWp9N5>TIl1h0e!qvTP4?7lD+V z^M|=QG$>P1P5B@IKh>Z_DKEcR@m=GZ zSu$~iG*j57X90;U|HZD=HOlZ*AN|m%51+YI1NG9S93s62qJ%COlStYT2%#0jCL}2C zb*Ry@Qk5cTtAk3UZZK**jNTuv_od$eV@Xq{PB=|#ayrgs*U2EkfVE-PG_(T8m2N(U zi%gDP!DeP&%+YwZy1?li`nYV#Pn*_oo&=O)lhc&gIdDidvv=lP&Q+k^=^%qK7Uz(- z+dSMA=CEE4$dWbwYLkSJQj{=hLXBEk8C0pt);{GxSWx)nl261HlAf9OpYVRS%J;VT z8$JIsoBS-j#>w~FHa7g&^4@)YP1nOOW(rcxPKMtR7#ZHB=|V~ZQ{u6-=BkpYRSd;n zH;ZGz(a|S37u%|w(0z01G65x{nWxDBAByE%m8)}TNhb6mDjDDTn1s*>q2+ze_&RyW z5qqsV5YKTqOnHmA5_c@jMNp1(6pRNTr0b}yoo9qX5Tz+dLJ}|Vz7ZfYw0^ALPUK)P z1px>M8|$g)AM(hXp|hEq`wSJCzr;Q)ZV@oGZdmnlvixlQz^SF@7~68L#RGFrP%tfL zvxZr7$2s@6_`bmUdbu9=9N0Fi-UX4c?lwk@V432V{L++I_+<+fDdS7tm# zZr6GFbo6?swc+lerHQG8@H0+DJaauWj$zOcOtftqI1Uv@wYr`k*Dc^66`oMl^k=1) zy0ss^-uliD zn-HN|LF63vqQxTqc^=h*z2~?gWp7(PuNfxu44R+c;w^;X`1kR& zwXT>fzHnN!^oI9#-AHJs*7#ay-b}_!P1l)%YqXI&Ioz4A`*kZF*8pnRrQ(+01|(2*tB1}qK?L?Swzodh$Y_w4;p zI(1bxg=5}@N6+V^?;`SgF{w3UYy@KIYIO~oHqA!6Jm8AAaGKgQz+2NM)6-MsJ+F7_`TP)^=bSX)&sd3WM`R~b_Sj=Za!JH zw;NSc=*!ndkV^xQ?__$?U;?^?U2t~_U`j!{Wh7M8aANeu)3|Kp|4PY|yXITy*)0D3 za1(i7Qg7yy%=Jb@igO^?&Ki9yrHFji7SzbUoWYcRJtkcL-_qx`Y7`sX#*-0^e&w*b zKAwJJt=XJIj|Jlk8JSS-B04`y$Htj8uTSW{Z%L(D)K%Lio%g8JR9xb6Sv+Thjy08l)nWb0 zUJJzqh2hVfz%Do|MdnIi$srLWi~v9&$^Y#3BUqH-v@8s4gX8d=Y6&WwRN)42hLTrn)c*D(bNG|6T7e@7G_3$@VS?Veg1v#Na@DRQ*`w)g{zd>Lo9v#0hJbz1;oO5s#;!K z&a=V8#p~NX7Ng22HUESM0{bWwS>ID9RLZ&}dyDafcvF}Brf>wqi6n$haU%q9@yew} zT(&_eTigIuI%-mA23v-SR0OF5stT- zKNx^4E>9|FCc)SyBD63ycEZ6Zv-*DGc`oDV&pA0DcArVhs1VLRk$|8Etk~nqbl0W2 za76&}mX^T+-eiVZ%4@p_-Nk*R>6KXhqm|YglmX3yxZPhluPoi&9#5Btt9?&T7$N{D z0}u!=pEQ?W6EcmugsQa7+;>aPTOkH~UB#Ae^=TWgRt5FTO&wQRJr00B z(WigEqD3Ubjch;oXAEp{pS;!i4B*;vW#(T!950)+z@m>+Z|^F`l^{tDwZ_3;!exNRam1;$3Q>fbfrtflb};~jc@OkNJPQ4%;*diMSBePj zh=X*7O#~5E7`yTjj2SDwsMc^u1ssrmX$~s4DhBM&E^DO89IZxb>qDEMntInENEv~J z+nK|B?{`N*EA7EesdMk2_yk*~QZDTmQQE8Ok>%=Sd_kZ8D$OEFs zw@_d6?MKQ`-g1ZxPGdHG^xJE%WgRS|b99;ud*xHgHswsQ zkHcf{?1(N zBG+?afTHN)kb5p3|L=*N$?EkgY^s-G9PgkF?}P;^!?WouGouhm3^UUV%)=G3f@RWX z5Ed&*mOr(HvAu>K@wIkLqm4L`&pF=qJuhkb_YlT55}LJdny*0{gbw?b!)Ju9Yw2y@ zXqM%3lN&!yGygJ&B1O%TfpoTI5{wvuA30!6kj#9{KxB{93fC?4J!Bn&K=#0^!DT?zagyUVB8K(LO~<$gct z|1yXK9zU^Y1recOMc?kH02@j4`bY){!qVNP_!&OP2?@L)uFNB^T!F8(pp60;Q4o~L z(!W(GKe1Atru6@{UW(1;A>ftSMbxO3`2J_7paRd6m5cVJrGK)Y5kQ|n&ai!gJyJ$X zyZ+c_ha>2T3?q1DvB?mZu97@mC<;d$MDco;s_Xs66lN0*d$js0GtuokGrmvo07OuxYHRd6A^mTxB3{R(SHW0=Yg7by zIRRaCHs~aE**gbSP#e~M_%RK{$nc0EDB&P0SQO5W4)pq>+x9tem?H3Egm z6D4Cw!f0gjMdb@J>% zG43=%F|Dg<0B%ramOxdO(2>3&AAzJ%2PA#;PnQsqEMgSk;r?jZA*3F2qQ}62nV@{n z{+cz7g|Fxx9;A34Q34eiplEGYhbr*ySPzPe*xs~agJt|=gs8*M^b3_@TPDr^=2LTr z!jwPWA{B<6U;$*^)Oxz#`H)S+Y$BJ$1H7--!<%IY!&`KgMowRH`2m7>9xyXKI!kBc zS5F!OIuFk(f>08h;VlX@S>fpt~-_WZ)MJ}kQ{rt=iArsGd zJ7?tWWPf|tXM3X~%%ojg0&dOayR>$S1pF{FcN_K3G2A#`y~EncC`(D&ho*1e)R1pzFP@+KsPzRHg@*2$jnO>nm0Hid#VTR1%WsFLr_e~LFY~tqwz;VCZyUH(nz#P_#ZTzcv zH#AULA`Ejyw1_3gQQWx513sJJ^0}4VX&H)=@X2H7^3|!qj}6D$lN#f{ld2n@M*bB8pvLitKh-LhEWU4xU;EN20(fj7s!_}+36#9 zd}<@l_qHPK*0NdKqR)N#9jv@Zd&*#7Zu6||1 z`#HB~zFNTw?-SI8Ta9jUg2^3$Gz7|+GVAb4SCNPAw5}?a04r(VqY95=^9Pn7Gp7X4!uP7)9I{WV7!jsUv+GP&dl{1K=IxG2 zfq45&9tC*{mw=cje)u?ny%d;e=&j6&<1SL|h~V9D%{9ec;wL0)o@NVVYQ%b@0Q@DBRyH#-Yp<({*I ziJ3ui_}j0*ASaO}3ud-N z7XSmg1IP$9#>I}#&_Zb$+$K8O{`e&2Po50TA;3+=MsCe2&w}Q&Li#bxf?L^LuMtLs zq{~b1EuUX7@cjp!^O*!9#3o{iCP{>;cQ2aQUi!}yE1Zy(>!p2-mjwnElUL_$hM?BFB%L9(Q*Ix$8+Pl)#)^V-Y?Dd<$H~P zt;)7bvZd25MFE4fZT;${4>6<9nU||83!H8|PNOy%cUrM9=QrMZGb~nhv=y$nQ0=bS zZ5y_BIzwy6=>!aTU|$Dl!Ek>#6z@zBKRCNay(|HZPn(q>7>O}T@>EuS<{8BKVuU5Y z{lt<&h%$KB-o{~1^?t&|-ekXisJVX5-(A4t_^WyW?c{5J%s=nE7lB8)$_02s*8JrD zL7=SBTw-sc74UE4{)RBRy#k>-S3E-O(pY-~n8LUFDn9w93g)+cPd1CiI0AeF%Eb0L z4d8#>5(QStC7nLstzEl9=d0zRXZ4d~A4JJwqB#CZQ(uv3$v1CyxFAHsNw|b761|uf z2Z9?iigq$oB722jh722E(8_(CNRpFoj4Ujjk(ZjK=u+0c=pe=yCRHlZ3|pM+de-9r zQhtD2 zWY(JLu=oxmMYpQoX(e(Ych+wr_5J>bqyYfwaozGE>gVzKojQN5-$pXRZKNM61}{ON zvM#b%fy{v;f|7;pAa4c|utc3nAW_Wp@?3Gp>kKH+5!C!-egu_822M|p6U2skcsKw| zn7i-l1tm^rnq$BV$%vOvA>+rWOY1}!@W;Hu<#i@C5n{^HVtQ=XQwE#ih`c=ik*Yu7 z9)<$|B?LB$BhiNWm-r@G09XJlqPF9*e4MQ**1jUic|8whwsKJ;S70&#!w!;8cAO0A z*Ki}XZO`(QKX=}aLT34)K>_SQ66xVNraGGA4wt8JBQMa%GM9OE*hyF!EZTe6*)$1$ zO8U=ROhtp#zecW67d)29dNe0B95`9db(*nco09MZWTg=0%<{-D4D4r){2WKAVKgHOjN}1 zzb#Z6C+3p8bj0yGdkU!n+Fz< z=4w7;O9{tjp+D*D3O-W10mBpUxG}G%I!x_;g+Gbr-o#cWSceN$VHt6NGk6&&5@&i< z2ZB=M5{FZXP#yMevRbLru)dF3VHgqD_t_T9^+NU<8*ggkv@ zVM0PW7i|im>fZfMfw26u!Eg^}boKO@?-_j0OH)A5`YgY^wBr=<+2ONhK@?yX-|GI6 zo)R=Pq05bMefacae^hw$&wRx2fQbsEYKoZRPc7gdZj3}nXLuLp33WAc@9TWX&&3cA zDTV8ddFN(W3fh5iMT`D+rUQBu&XXn40(bT~)l;6#c`42ac^sb#57u9NY{x37DMTKi;S2K@L<7HL4m zaoc3{Q4eCGkJsYKy`TLyQvR%S8H$KGA}opli7D4rsJkH=LSN5Y;b_WSmKxF3tji&_;s|8{?jwasSD`@sOKk~Z zf3i$g3nb~Lxc6IEDwNb@9{5S&tvMvJYcK_mM1vCNFvPIVq11~EMHy<=*>0L{FYK$H zpmgWEn?=GsJA5VQWsUn?W-1QG*N2wTR$uNQ0|}w&DNpQoR&C1*h%Gd>twE)Ic@RSS z^{%Z|96P(nOn(&M&t{~n)dGj%E>~&Po5=SxCxh%owDq5tXRTQs4dSJlgg~dicO7=_ zId8q*>w7W&KPP{$u>2pFZE#yBkRVo(NK-+NRO61=^G6LHB11;G@a^4FxqD$Le?>Rp zcdHpvXyyV+ut07s3bT_XtD`^*E5hG9=eA4+5yJr*#d7_IeKFywUrD+=Ni+lDFupl( z{LwP|Y$$#8^R>-uRl_slo%o)Qra!8e4K?7{Ehw@pfBWR4HPH{;XNzyFLnm)z&v z9PV?W)UBg4kUZNiv}Ak*$>i+SOK%K)#Cx?I!G__FY%vG=F!jc!|J#o>$vCjGvj}=) z&78N&0|71M*@*BlBsfT3m0>wG1twS@d43+g1LiSIcn}4H;4?UZQrcCeNy;&_77Qpz z6_L2a3v|Znte}0-&^%)Y0{}#FWNWK4lX$z-Ff4brm_|JN(xb)ytgfQMam8jMk%~H%0*a#+|2NfRROZzoM0U ztg9C)`EH;VOqgCJ-!j^IoLa9u<>GQH_8@LVh{N3{O7}WJZ1D{243tJmeWA zKf8A(ew1|R!8aEg#w>-g9c{auyB+@zihPreG^7rnBQb#JreRpz85n9aTcmPvh`fzL zk!_V%<54&e=OXpQ>DpeDmZc1bf{3%eH^nKnD6QHrMlhPWZoI>>g5tt4D1+_8CP9!^ zvGXj36#p6P2{J3qYO=Ofd|c(Z5VRYEqbQ|T~9VC~Yz;jTOm8v?{sfAVTW z{(N$BUm3a~(c_Q;YyKTMMcOJ!C1nhw5X%hqd4NyAkm`kdlm3CJKQ>T|kFihJo zfj^_&TH!z&i`nYumzvk9z*8({bt_y}A32Enc%1142sS{zB((InH-oetLJVVH()K^? zvM8~KHoiv=<%7KRzhOqbYYsshK0q{YToN?M0)`kxUyx^ z3VxCVJ#wJOzY7vZZgdUNS2q0*W%+h|ijPIdMsA6ZGyV2@>G`ncx3GiY&hI+^N7p@7 z=qW?6>iD|N(~W-bm2sUQcwy$u>=XufH#jQ*1G@%Gru&oq*5{7bLM_u~?V*52nC&nl zE->hm-Tj==25{j4v;tgUUHjyM*TW&=s~!q~$I?2V^ecspY%jdJKa%1iNPc$F>mRc} z-|t0*x%p5e{=xqjk;R#E+xA)Q&bg{x>zO)4j`g0M(PI;96VL z)Dv^p^URse)%o54CNTiU0p`;0SmxPp&Ox$7bw$&FWNv~#L%{nuwig&*DUihRdw^b=-m$cb8lRVRuQKL{p8E8H6+w zqVlwv7%x#hb0m~!u9209h?%yAcli3$&vQtVtQbcdFksYHp74wiy&amiy4<3{di#Fj z1Q&O|6F9r=JtkKPcVOS_j6j2+ZdGx_$H7$h>M2x>wKiRPO6mwUHUglgcurBxxW;&A z{2Ml~FS88a6L%*L<<%A+WMyaP4#+lzzfw3~y1Ih@ykPdjzB51WylnWf0x(9Q8*l{+ zQevO}aPOUS16`9=o8OGK^E7v%xn&UT;)~(ugtg}`HyG9HvmXns z-gb%MOO-Mp+MJ$1!hb)qvQOW;&4ZZ$mG}&Lsixe&{>r1`Y112M2MM*-@z``2KzEbp zs)pl8I^BN|ie^mT{Oi-t&fg1@hCwjE6O01MvS6Vl7=hCobKBG8KweC|8X`cTSy0ZB z?K<($A9PB^?|TLd@u8*C_p1X<4 zW$_ppQ)FrYy!h#FqzovF}mm>$h7T&tdEuVWqBo;9@?&~*`tndIXcX0^CBSP@%X9SYd7 zY)k~1aj4m7yS$Hq#QfR*54ZJxerjGP=rpwT6(IGPVf9QDaZ(7RB><)g6e)=sFU2~R zRYG>Qw4hNY+MbxR+qePxI0eV0R+UhaAApiwg2Yh*IRY_Hi=zM*MuMor{hr5I(JUM2 zP8oq@3h#RnbhLT)p>R+KVFlDU zf2&}95UwM-0)rr`umK1FqxH#qay%A4H+ld&t|mh`hnmg%QeLGsaMOqcj^6mCdAJ7d zCRKUOzI1}G%a~R66EW8@XT#acz3ZU-{_S(3-r^K>P)(KBx5R~CoQ5`aaAVI8$m=BV zeW46#-;j`k5OzdBc=?J0kA34HUKP)b-=zI7#oqR&fMrv2C zqk+iqk>5*tIpe&}pK0%Un8@9cfdo>LDRB3pt9!)!d;Ved=h znMcC1@APezvf5jB9mnP*l~p8_Rq^~yU+n8VpHsZF`QMS+3Q-=k8Z>EqNhrhY z68qk+_s7i0YBL+J3rjXu?b+7EG)ZgV85oUXKdZAIU3}I_j@H`W^s1>+2@F6~DOqev z;sV#&+S=OM+UsrKxa)GXq-?pDLWB~IBQqCv?dsE5W!L?9@T`@W=Sv>Vb>MeKl}ivH zfX2(&{RaH}?*VOc&wsh-3Vm)s#O|@;0J+*}C}^()S#I9?8zKcr~$f|HVnCi18XA5Q|){Goe@af0=q7d#~P< zF!`8aA6dSnKxy+|-zB09+WavY(J&Xhv1U7@#@G8wt!`HhVp4A@hX6DT2-JW-U}x~3 z=h&CV`Wq&6CNdv#jHv-i6FE_OJl2W&x-W)5&MyWzU6J5`fH|+%(qdLTKe*9!Jfbj_ z_Zfq*Mhp(jp>g~gfr0?QyDxX#(L?0z=EU`T7#tU578HMO?_EA3XS2Ql6fdhvoez^= z1M`KQ|C?gX9I^3S*P(hLU`pxUp_a&|x=w@RZPHsGWaU|)fTfBYS>QGlSbeE~6)E)aLUpFw=i#kpB)erdM)EqJkWf zMfrZ8|5?;|P~>1wvPXexq5N(85^3y6&(}hR{JRk{7)c}P}?fEFTiDG>7@|A=|^gG9uZ*+LWMgtBQ@A}9?Hw=r~aCfFh?-hzs@Tml9(-+QF zA@n`AV@Mg|JxQWjb=dh=%WUL&sh7%%S6&-3{*-H`@I+dHL!^^ogZ(8rSM@+q70ZH( z&CqS3(3MfDUA3+rn7JFQTu zIR&i{j>9d5OxQ(dYk_3Vb>b!B2;Oqo;zJJ(1rci0q+CH!(O6SbMD_>KiC|2%aTc9WT%Cp_{z}PN)6eNIhuG0|Hcog9nqDTnN_dZjYpff#VnMu7qt6%|BD;OSPijL zUV0py#QP89cd(7}^6d0l22Y^%JyEWA>#b|!N}qg=omS~Z(q`NTtE%Zkk4!JM{8oAbry*5X#N(}e`XkVFudF{WxyFmu`D1v zl%Qi-H0r4SvvZOPM|&H2Bw0RB2DaX!-7Hi;N;T-{roAfDW3+f{4X>qM>ZffUD6!cd z!j0V4A<|=frRtmfl@hS)-D1`ChM;Xu#bw{rV;~GuFz(#UVTvl2Nbu?Y1s`@g?Z&e& z8-}_L#vbu0JA;D8&>tclX^@I%=P>SeZm}TTHT9U-qn-l0hgS*m%eUDnk(h8lG-w_P z@tS|rGo1M2AY$RKx_F}ZIY)q`%^+7~3FD6YbDMVS`#3ZEDzS!Frq({9aZUNvb1KTL zO!>i}0M%Wa-OmYIy_0XR0fKIgYG^fO&_ zjqD>5O2Czg6EJ5B*t^SWnoOczg_}r?>PG`nKzK)r>+ZP8QfV&byQl2fw_IWPO+A!SYVR+Kx!ir4~QsawT0>++x@NR{5nggcEk^eGF}C zt-^rL&TUzi(;THl{i|AYCL}s{F(AT*Uj|P#+aJOazJ^&1gB!>`m{`cSph&Nv#$#M< z&z&~mq(P+V(AKlkl$@1dZQO-<9G1GTwyFzXGG6VW+A&VQ#_&&4Z$}WJzxq$6h1$v@ z6+pq09O+8s9f^R-ag$4-DrO~R`l$GKCe{4kr zU|oPB=5+eId$2j6LB6?cewIx9ZWHQ4)+@|RW+Oz*3<*pFCJ_-xicBDWB0G|hT|-O| zui#iD1JG1rdOVF4!vx)pFF@~rbH`mHSDpXz-=ZT_61jz}H7YPRCxdAH+iCv_K3S;? zC%kp(L(Vfx+<4WbVvzHfBiP@hEt54|nh3*A`!pNdeci8*`Mb$b@316IE-nSM5mxSF z!+~RQ!rmp{ds4K+n%VMXdgiI5xi1Op^b2jZ;%8CMa4qK<+?QXG?P0C$_voyQ_`Qu5 zRBhsR)b>*0y2OdA%IP&{KXL8vz4q^pGA`nq#CF!in+oJ++-llDrdu&=%g(tRCxp<; z4C^Xh_f<#e6qtc0a97ypUrCeizZGuj86BSYBdf=|T}2ah%8T`!sYo{LHdP*(>I2sJ zOA{dpJrW3mXH>2~g>#qPji6_+y|udY^2D#OQoBPEX=cVt!sSM3Jz6i$3hRH=q1fG;z^b2?A%Eh!zk?xSUvFD)L4+oPa^t71Kq#x6qL z!bt+;j!3%{)yxdEJlp;?Lm;l&VHi2P+Tz58wb@3EffzF^hlu?}1fOAbQx@`}k{h+w z-YjZ1*(BNYeub$RjVcV)Nl8dBH-^(J27=H}V4K@L{lDgu0LCGzV?k;|Y7lAB0f{oH zv0I06iqI}$>OJ%wo$RqY&y|U72QsurfEM1TDS9V&ULeLa#0+`P0obWYw%(+!ol_0( zZtm-RB)@R+@py84?i6kR@j5BrMJPd!ygeh_SF0Ne@C}ddc%lE+wS;3=5L17-@>$t)r0I+0zN0KtIUDDuTKe!XCF$$0OhC9*_{hhAe zoGzmkpu+Cj4p=sZfy`Z_ytra%g*ptCyN58}}_379|=1(2Dy~$lryvh?=m$=i5VQEpwjNJhYG0vtOOs>QiO# zxaYb$X4(5M?H?wNdXZ6Gqh;^;*K>JfobS-Cs`D93n7B3=?nAWwk)Z7`Ypp2>oKz7s zqRUZQL`G){TCmmoY#5_~I-g3%tmyJ=$ayuaWA*HW?YUJ;Ca8$qkUSHzm8)v zEjh*5l<1}3vrRf>ddsrkx$&=ARo4v86=iiLepCGRlpzrm)lJsR?mS8Y!UydsL8Ooi zH;t)uwAe2+7j2tp96RqLRT{=AUnaOHA(iV6e9FPAuyb+&Q|Wu%Qz zxu}7bL2*Wa>Bs-6$Qffz1$j0^scf|t_}6B~G#mCxhgZbFlhq44hIy}fN>-GA2Feqj zfkVpBcxsE!sJiiTZVmp0K_U=OlEomhCFoBpxqFWLE*jTntf-wM`&pfz3-j%wND|F# zRWzxe>6!nBU&gK;Tk<|lye?)=;4s%F(Z57Rqpul#&2cZ5_2;{$?5r0a@2)8mZwa<5 zeTvtFejm-s>6?7thsM`UM{W{twhKza_NrI&8`gxe3VZgB%Pz>FW z7P;!cxWQ)Go36vz@^9#`2}VaV8SMO+6ObFIdlJSw$aZdYLPvApj6SF0T}>p{kvrSD zXQv3qGC+anFxt|M0*&>$fYX2&TYWBz6~{yt1!sHsT*&V)#i&b*vUV>}oia?$fgn|g zg<_=43LX1*d6+1_?3kh`m}nAkQlIreNjpYe&qU&)x3XvK@UHjSb7Fy{b`;Dc|VSK};OPO+WUbSTOQ@ zp9eqKN0#AWuKvRxtz~|&`f+xyXZN_fEx(E4){X1y0`PB&ZPFnRm2ZEQa)H2MytlLg zLAyIT1Xu{}AAX}gpdKdE^>O4WLV<&x%~eSbF^P&8v{syZx|>k7!rHcPa2ybdu^ z!?=zk5`NUoGjmp!;%xjO+#$G7?yu$GWEeD-m5Fr0rm7G&9*}HNqF7M9%tpVwbjuwbE@Nk?k$;@mG*QyPWEuzb@;&Ntn-oI%+El}9*$-WnKpwg?i0F? zy3XQt`)7W?w6k%Rl4T|N+Q-f?Kxah8AQv_2f$~tMWR3p$dUz>islzJMuZUYb9%YmN zm0YzQx^xupUWl`0#*gdxkzzROX0Od*2d3B9nPYW1L!A4XE3_X~8C;^g4(gBNBT^9x>aE|DWR~Xi2)Vt_2(0H28b+FXWXQScc4CJ!SwT28G@PL10L{dR&g3QA2 zspCHmK*M&NnNaPNu|_3P%%rmRz90x0+2xgj<&jHtF+g}|8XI+~Q%Y|6WnA=}l81!X zOHG?2_dQb-jOzC^+zVJuJ`ly1l3!nV%?(Z4>%L>r0|gwnvpV4b+r6?xdkfL`S_U*( z=Tle)(vQ%2i5(}#dW}V5jx#z4p%G~Q_}4G{@quQWV$Q4LJ^D0ZG*O@t%=GZ zIU=K;f@F014Z_R1!ww4vq~KRqYp8wR>qCN`>fQ_8WJNO^yDTF8sG0YnXEkU9EA zV(YWQrc&hnqx7M1l1jE zrm&Pt0{U*cyN7o&JbeSEh2^|#YIHusx9Q_00LcuTce*=&4;H!~3*}wb&bCq39UWI2 zUt@{cH%`?8IDAOu-sjjwy+e*u*E~1guR*cP*R2=aMH5ccbTy+;{wc8S)Ow2a&`oV` z_)x>~u=NDJ{8Nn2_tWKO=->Q#)c=1EBkMh$Ds~`;?-clh})UtSsSq# z==Vj+(Rg#9kk})mXj^#jN*$|mG~J?s{llWLFa<~0zx)ex$Rj@3f``Dy#;)d?mwU1Q+f&YT^mdAg^3dV7{>21Mx%b9QSQHsWEP>=Ht4hu-(vIb<&y^)gs9FZQE8 z7t>mO!0FIh<92r&inKrARrUcoe}TH)kZo&N6#Md1fzAK~;E0F^y3s)aM|(=+B;EDC z1W1Sx2XvT{SSal*{%(4`^RDz@P#A!112gxK$xEv!7x^7E+2l?ILGMdg91{+t+mRw0 z*?BZFrBSD-Ervy3sEH(^b3Y;))WMKj4r|4E@a)6+f}hson>&NTVKCY1-UrAI_)hhy zw~W2@WTk$r2+TCQ&a4hSF3umB3nclEtWC_72N+(TvDNFyh(EYhRyO*CVS$X7G>l)cRyfedt^6lJ_o|cmmkJ zlt^3v2KNqf*i}Zh2oGIvvCMz%ry$MhG`QzjLkzQ(S-M{+Y#q znB;60B=c`)E*7Gj*tbhE>2$?=laP+5mR+N&Zsnz(aAaTehf9@daGR6fL%CW>--*1H zEU|7pKPv=e=*$mSM=;&#=n#M8eNzN@-*BD#EvsPQ zyf$ARVeXmr6>;k97l{~AFAl-$ZCb(E14QGBZm~*|@}`}Fn2c}AsFa5YMc=cJWkDQH zkw)N$&SJAJ#DftAS+#xdV8;k}jvJN>#s{sN>}`Gj%n}+GtuBNducIZVI_ngV!if%;$RXe<+5gu>4r(R0vJ? z3DQ6&HkZo9`NZlO(4x$=ASo>Oj8vvo%{fB^u&cCH1Wn6kSl8G0kpJtxd+F|89;T^4 zaG5EC0#vk%B8ko$me#V>lh@eggYRnHQw6bcs4+b$%_K+*rN6p#1Zdz&tWDMY5qP95pqO^Y+o0HkTFRhLUt`isv7rsi;RB!lWlRZoZc`g?bv0Me-O zdbONf3SC^P29d=o3S1F{zEpsWGTN|R5XDqU%(;=t-D@APj$C$B9tlK@=}rliQwD{! z4F9h+u#Jv0=T94XYnd zMvEvwr`T7eE<(oPh~o6%3>T81Z%+P9h)MtuAf?BuAP5A<)eu)asDkBWvOxhkLM4>Y z>R&;DBI<^x;mq}pI|(EnOU?hsJomhJW6gCpL6`A|ZIsfRG98eF2mk`3Cu^K_lfgw2 zwIl^LHwYScxJ{OnZBC)^lMKabIx;;9UksjX|?t` zt&1w?i7XJ1NXrl*Vv0jjM);-K6U@2`gfSmZD+XrbnJDnTjqAAWn1>Pe>GtU#H>7fj zytioi=c`&2-+T1>s|K4N97Ql_!OldqD2#kzS3to0tmaZqUSrds4ziDo+P#|htM?iG z-P=jrdWl3xSldno49TB!cmrQ*7-o)-&@jmFOY zYtK^6W(8C*fpkaMKcYN4&xdp#^CpneNwGc|X|^drozu;H;+&*|tWYi~ceXMgctUXG zW5^cd41owL2o*#K3IM{1p|0OWA;1oFfHG$9z)&M2vmq+V0001A@@qm;rEwO7SQ%tY zWs4G>%SjT6WO0j#OatLWak+wlK=EM|#v^SpMULC4s#n5$FTTPi=TubkZ&Eaw(`j?9 zOKKG+p`#*RYWxIpUDwpa0nc;{s>w=c1&d8}MTzg9?X{}bw|^mUCYG}UBg%CeHKN zoANv?Qm05#{pxsdE(zCPt263>)oOtNOSua~A6&Gjf>1tDDM(5da5=FQGE_;?C>Rr$ zAPX1fOW!+TZ)EESXe|Fj2G{yOaJ)M+!dqRljk3DhK4;@#rJwV zU;;ypZ8wC=r>huic^S6uK@$dkJ!6F_c=C&LwbHnBL<(Xk5p;J%0H+ZZO9TZku9Yh3 zf~I?9wZ#Ac05Gz`&LJ>VfJDSP;`yv>hD~H%LW-Tv{=%962aN)BxBA;dDZ@5vjVDy8 z(omQVi2?A_a(peH?^9l@o9N;Kt-(`!i5|@bQOQlE6c)-h*0zUcwH3MA@6&z=Cb- z$Qh!TAEAsu;)P1CnSp{qB^QFAEm1@g+|e zFG{|=iSW@7dkNfY;PbYTcM;<2vyy#wxMeUdPGF`yfFpb0R$xt2f{8xU)Vlte_xTf> zB9>d2g!g0%%EbW}Q|YlT<=50jWb#~;W%(W%7fr_D`rE6Tj(%smMk)sG1(k6*L7uog zlQZ$FR(}F;yvc91A2ks6P8oZt;%^|fM+EokB6DJaq_(gg`a-VSm9m<$pmadSM6W{$ zk^Y8whgdLeqWZ;ecw#3}k%8(^+%sO%k5I zM(>qvnUf`PtmL2(I1C3C1~eB7TVbtiL#J2s3&zo_sTU|C9*GJ$XQQq$PDHkbc%@DK zD;!#xO{+y*3;1c4Sn*jn>`H!4I2Wr;R*Ge*$pPkhwO**>*VUEx><%w`R(WsF0!pLG zb=htRwFw^?snh4q!3G~!z}B{B;rpgh91P^n+SyMp`kCAIyB#VM<(q}kx7We#q3z>p za~V9=0jdt>GW${V$3?Pu1b0rN!8YP&mwSN4TYEC3OP>QIi!vo>cf|vW2A^Unqh!gV zwQPBUxS5p$6YLV2&3Q3hcc>o;xI~C`ONIQLe{Ao@huvbRf}w|}kv+95jy-c{^BQW% zT)fi#QecfQaQ5uZ22AB{7fsBoC#d2Pm_6PBVIs$1Rqx26x=zuY*@FAUV2!;^Fp%6= z8SlRK-d0;nVDDyErbe@`aNZXLppib~N(93IBdHz=&B7J}YKLE;EFg^3(rdia;Fszd z1X!y4ky;attJe}K3AJ_N6Wb!CLa!9ihE@YNb< z&1?1hoS$Deq;&%VsspQ5?}SN+C0*M(c!3Vh6s2306yfqtPsRTS&TDyDK;_ey>gcw$ zFYKxrRAcFTm1QQSih>zHs$b`@0hNNgH5h|aOSh@#u6*T#x-F^_f1cBN>f{a!t0vOW z&LGe6W{QkDI>wwR2Je51gvj_*K{mP==Q*#i&!(x}xnau&W~N7OSYzmphD=2XyAPI> z?EYIXoP483hF(6sgp^Hk}x~9Vamp8TCfa&zXCY4;QO9@!nm82 zR73_#(Ov{|XC7hcADj`SoO_Zi9x$G<7|s*kJzmmFa=h%Roy3fx58BP!H?o77ID^hQ zvh-?v@dK(zn)Hy%0$qPMyOc7}W!4p01DiL-c!BtY_IbzgNG4%p&+SJ%2N{VXzma3V z{P*ZOlbHw+JU4+uWA_E4Uy+o?EtbtXPz)c=7AEaef#hFQTRJPeVEP`}zL~!#v=?e1&Fsg`VLT8t)!k-*AuD*k8CLM3-lz|@3k|-MN=>omsb7#OQRVN6Q-HvjSUiyaNjm8*{3)}b z@&Fqk06rgFyWiv`&3&3WX8{&AZWp%K)kqj)pw?r(?VFOw6g*S8mtyZoPg%y5J)_y{ zle6?~j)wlaw-ppE=-U5Okvx0n+--6uYs-CZ+lN!V%Wn(ZX6T5evs98$6$-I*aNJk+YV&34bQ^Ky1N z6=18uYh{zJvA@u4qK{*>{EaMH3f482WZ8`T*e=iXRr@*cB#B*%vqVV^z||jKFx`AE zV(qLGxNwk$R_GWqpq9s-B1a2$h+y^x_s!%Fy6REk9j7KD<|NLrnWzjs^Q#PG==4rS>PgM*GCN-#R_bmQb^khxw^tX;o@gle z5`2<;M{Q!85toLbrTUXZzac=+g7t0L*Wjn<#n#wGB9(_$`xr@iR_5%0SEqw%YiHk~ww>OlQ;=Dxqt|c_`#E6K9iIHbb*(}3>GCGR3Nysl8Y)UJG z?Ha6_F-$-dM}QeU40F$$B%udK81ZiopA~_W85oLtejhO5dmQk0NZQU(Tccb#(VaQ6 zzLz-G(`_FPHxK@I+kwq5o?<@o6S;z7NGB3b9LKCsfr_Dt$61eYRLui*G?bz4XS+&n{+@4jNL5eZHS zIl=%vFc~5E_q6@=;_4{J@Sh26;Sm|``+>8=_FfIh0 z6j#MZ={=${3jO5GmRmplM5>-2-TNE*cM;uwd#w|i3%8i9WWTD~E*Jn8gEL<>QH|dG zl>nMGw&bW|jTq6xR?6^QYcfED7%RgX{}HqzkXA5#YcM&ZZvkjS-r0Gms?=CD3d9>{ z0=QFjLyo9ts`{Pa#RaNHzP%*7wiGEQC@)p{j&Sr``NM=GJgVhv> zG5|H9ZB~jw(qmYtGB7E$#Ju=xo+frAcHzq*%jxFIhI4{T84EVZgR)&jX#z@4 zQ_>@dB!Rf*1_3yj?>YJ*U@<~KhI5`@ecx!_qap7z-u9c!#y1NTSHfkmVs3n#2AU@M$_@wrvU!0c8{n=s+K@;s0(;y>@h_d{KBPLsjgPLt zVIESDe~pVck&Z~RRMj75_BIt)r)Cv^SDr}UOsILdyq#>H@-t%2qv*%)zigE`!H^+k z9C!Bn(0`^DYRzn;ecW}DFu29$wAQeytdG)Z*VQ)8Fl&j5;*oF`1W~=|X+J}1;3h?v z_nybg)Yra{vsBCH$est51~G_g58yNFBYB;ha`=A-fyz%w^yaSHVGD)J`S>Kh)irQd zhl55IJGOsh+MMCROH_}>exsmpyO~asS)BB+GW3*@k&AzjKo(R32VgZ3pm78|WjxOl zdfB)7CJq;8NV=4N8=l(PuN&O)`Tm2+MD$%u;m7S~lE3}Ot5~{q4skv+PWV|X!^%*W zFuwj~qB&<|Kc@}+s7wO@HK8%)eJ7(a#g$s;L z4Xs^Ox+Q70C{;yiVN!}iT1}pc$?;}0PYE9+=6EArBscr0qR6g&u~sFa`z7&HS@X@s z@0nM8I&d5+^^H?q4~q7+@IgJG9cVvr+5?3EXs+CC%mH%#A zvv6@{ql0+X3dEwM!cnC>t?tS$LSgbBRo81g49O#9xoRR;VsO5#*5<2i>e-Q+QYDj# zIrx&)D$#dkpKBwxnO!$3eJ9>-`SE`hyqUU>E-sJIayeM`Hhc)~V{EVzB8*0vn~yTf9%&8Jm0)4UoAm36UF z7AbWai0-SmZQ1p#CY%lX?0j^&NwV89&cN%CBA7?{uWEY$NO3MplDU%G_jgB)5tCxJ z-P}&9Tb|yRE6K3wV#!Z;>^2^5t#gt@=gX$du*9jBVzkNZD3fF_lX4mKO}R|7CmHiQ zfa{PZFcsrNAajMxRLP%-NDiVuU>uELbW!HHso8TlJM9=jkNWTq`fzv*Ku}_A0=@_@ z^M8-=_(b=C{!z^|`dJfKkS7v^S3u~bcvKjgfl>r2krCjSBq_-d9N>wJDwB}|s1BNg zmhO;95F3$%F@;`n2y8Hhl57n}KZQ{cSFuXGtRyX4^c>(E0l-sK2kS3s_7#Dx%>A>B zqGSS~I4BfyN?`o-_lsGZQuZ_O!GfmRz?L_z+qjx>UH8+=OCborm`MvL2&!{wGgCR@ zuV=l~D^+>7+AB?z;TI7WptnN_+j#36#ch=ARO}npHYm$ufvi#4Por(T9Zrw`uhI8B zwyF}sS?!}r))UEdSR8ZS>9hbTnIZ$*A~(xovhh2=f*2{ECrthNcL!CS<~WDw-C;qP zFv>U}(Vr(4{b0F*7yIF#iOBk@>+^uoW{v2C>5QF|VulMj!D2@$u91vsJfW$pQDt;P zQe-;*kNUiS!Huh&>#&aP`brJ0aC47__z-KKhE5HVVnxd?RiwbaF5fGVE2AoZ-(J~pbh5Zp$g?7lYaW*lsaDo(_8ycrIW$9O1?&y@T ztyaP-p35rqchI8Mb-sET;+oiN9s?U|732xqrol!R^0T3V%M2HCLA(=sx$&TU)wio% zEwf!mz3lh;>!>g;TW;3-c3(fOz38LwJf8st7knCHqJ^1x*}BZ>(3DM!+RgkTbRe6? zcK#zGy`M}X@42Il)$lHFy<{r?0*r~^+48rtD^1gjNs;L+`5z+2`->FRJCZR0_(o22 zbHU_{h827rUVYPqP?%9iRGP-Xd!lzcGSY8#~!4? zW)-pAqCJ;ua=SNfRDfJI(z0o)A##}o^7?<}#ng2yEyfOoaQ#M64;9TydKSww^q*tX z^4@>-@E3`nu(S>IIAWQk5J&?KIVB7yju__TU&~7Pf(~LXR?3`IkRj&ByQt8r70`2# zBAryo(Fr2pWb$b#L9;piO>{DST%F;NR~2r~=%<*q_lF9QkZ>+KPzUhGN}^8|e7_QlLF z5b9%vMSN^jGn|Hkk57SW#Jz^m_tmC{{WF+!ifEn_WDQS?5)z1*U$XWF zty~5%UP>1uxczt?_D5v(OBAYd*U-!qVLiiQZMEtvZ&5O;nY_Dx4oM zw#+lwE(m62dJb7>2G$8C-oDOuF!ZRfL$e=!8~VRt<}{*aYZ@T27?`f{D3ut{ffaB_ z6l_^5d9WJyyW=5alF%yUDItaOC?)}my*!S>`GZZj>a#Vu2eO*l5JnrTQG^V()WafY zrlL$VVSH^KUt+Fnfc5zW0gu%Ey1Y2-gaVdWqzps_3;!&iyjl^)2tgHAzZK_)pO&E6 z#?1dw9=%gWRF5DEmhTQGXz33}jNM)yNapOGWCy=cprG+EnG6Laa)kl*Cu4&YXP36m zt^9|0eQUO1@BU>qE62*op4tu8>JO_@Dgru;gTv83o?pKGOZJjABzWO(lBC*8?Gu3! z0;6^S@yA(d=B$KEJ^v&`l`v!ez)aICm=F>vf0+PJ<=nVBm}vZX*>{S=R0TsRUHU8^ zBf`l>?Lh@aSo?~-mV3dEg?&oX*-p<^vl7uXW9~}6sNm8jDn^TqeK6+ja@_?%;h`>( zP&a3g<<0GB(}_VkDsF2QI;;Qz>xRZfnlCTsH;zKY?A1TUENWK9cUPHbXIopQJ#j45{c6U$$>_^C`li=<(`$(Ih97lHHjB02i?j?^CSlqjGwy>fzji& zma?cgKP`uA=-)BQP6}UW%EEC)i!bm?aK5?w~I^;4fZYHE4HL~kfnJ;`ye&8=j7zhcqNcX07-bbg&AM4^a50o146GsSQi ztU+70n)=57gzk}YlXcCo7_5WvSiT3|M=<1A05LU=zZXLG>egDgoQjmLG*{|R^MjFU zYJG{EN_5*dQfQ!_R{e>FqHBnCsfgh&r-(hOw;>v*e`n!bD1Yp$1Zu`;){r7iH1Z`T z=!WBXnb+y-_9$gIq?))pS^Hq6P@nNkeN9yA>kh@;UiS&WJ>cnkN@%d$RA@;UtE79D z@1TM~ztPfe&ct(Vew9?uUfNSb1@~&Hi9J4A8n*XUampb~j8<{J^@xQ&H|S&Yd^|I# zK#NQ@t-!mX(k)*lBiZ_z@5fxmc~bKmx2iI4jqu7kQTQCGoTv-Z@;LdGJZp6FpYZQ3 zhT^C^Yco&E(ceK&Md`gX-jCaNIsX54XHtD03>;0_bkKI}*VuNA^Gu-@T)|UHo92jk zYGFY;Zm!;b((6(tM}|!C7MbhA?r8;4ypAt}Fpzi~2W5bLujO`c6E)sneegB*_&$%V z-g2J6|8vvy+EOIiSX%dde4F+jo)Br>IOy-{o=}ZiI)}SH);^1U=!Kt4HV>%RIj!*S zmu0Rqsk{T}cz<`O>v@UUV;I8Uw$2rO24y}CnZ+2#wO9z?`r2moRDUxsUT%{Keg)Mf zH+x-f4+b`Rt4m$oP07)*#DJ0?fxLTM3$$sl;v;MSJZ48i-*cl+`M&}T>8klWV+F+c z;`4J%Kpw^r6rx6|NE~rcM&<@F0R#ei5{v!RB9yPiswP~tch9Q;0s< zM4VDiDQkRXLU|@I7WH+us+P7;vQ!F`uy8Ib;CP?n3Jy!4WgJqQ zC?cP4*I(d20g_{NRc3bVPV0|F=zE%)d4>IDqLcZ}kZx01v{$c&dCVfSB|o~rxI79T zf!5LFB&+8hBvLQURvwYEYo zwVt($%&t9&Vo)JS702*OP%u0N36&}LaSNU!?a?E9Pr84MaDu#aVfBSts^<2i5l>3D zjjch#>dAyh3cW5F0vWjv9FhK{TKuP5DocQ~DytD-p>_C1MXu14&(-$9f+*$D$ve~c zmywdcddoeqXxHv~6<~X5ie>HU%`}vIHY6uSg0s8d3WvjSI9NdJ_p=*J>9#`7L7d?I zayt=##}bcLjm4S5VxC9%G)jo87!Me3c}*73V0JRNq6h=5zjfBse?MgYl&6tPtYQSM zs05)1YM3icS=L<2yqxZ?`EQ6)n75TMgj31EXt;&RXGpIdacy$QVxST*xY^!wrSc0T zpr*vrywPZP6leXuayN;^U{;0Wm>{~y_!1>>Ur}7BDkMe1W+ovGrYt{AUbd3N^C=(BfQJ*M@a!?Z^ z%Tb;vfip!A2Gc{fn5_;#h@0|=1kUL~B4x-#L>YsLBkst=*wat1<8`_C8%9^*roZf_ z=vRG;?36Y~3)tWeui#NqAUkNc$yL>mmthoaEQs+{cADGd;8NIvA@Z^CHtc)lsJF0& z>8yUT)8b@x{?*gT%ox*tnX}&Ly&W;%9zt^sfy=F@ARc%k@dZZ-1oYS>lM0<4iwtUu zdrTBZ)K^i6cFl!@#bFBmK``(q4S7ATUHDwxSvXhq59?!XOPnuVGM2}pU#gei@2Z__qu4%>O|Sp0*Sg)p*)2LDkMR%a@9)LvUQm4rGrJRFt)8 zc=nIA?pT;{UMV285h(&|6Hk|tCM!7I2njwTVZz>*cgyRTWw&I68aIp?KVMp6cXm{! ze+$e^>n8~!)r3!OvPv|ALjAY3uJVJ;$HK6O+7$~skp;-WVtGp5yCDzC{{9v^M3kDd z8h_l7b5J1^JB>t%ot9NR0N})BLt;+V!9VQQJijGUpd$T8`eB9vie?$S(n(Ki@U9x0 zUd67lq^(fNoVug~$E$3@e~HqwtDP+1-N7_AaYf_?Xkb{tU20gId98LSssdt(UYAN-eom=Nhk{MU! wa4TS7uXZs4i)~=>>)= AutoPatchRegister: + # construct class + new_class = super().__new__(mcs, name, bases, dct) + if "game" in dct: + AutoPatchRegister.patch_types[dct["game"]] = new_class + if not dct["patch_file_ending"]: + raise Exception(f"Need an expected file ending for {name}") + AutoPatchRegister.file_endings[dct["patch_file_ending"]] = new_class + return new_class + + @staticmethod + def get_handler(file: str) -> Optional[AutoPatchRegister]: + for file_ending, handler in AutoPatchRegister.file_endings.items(): + if file.endswith(file_ending): + return handler + return None + + +current_patch_version: int = 5 + + +class APContainer: + """A zipfile containing at least archipelago.json""" + version: int = current_patch_version + compression_level: int = 9 + compression_method: int = zipfile.ZIP_DEFLATED + game: Optional[str] = None + + # instance attributes: + path: Optional[str] + player: Optional[int] + player_name: str + server: str + + def __init__(self, path: Optional[str] = None, player: Optional[int] = None, + player_name: str = "", server: str = ""): + self.path = path + self.player = player + self.player_name = player_name + self.server = server + + def write(self, file: Optional[Union[str, BinaryIO]] = None) -> None: + zip_file = file if file else self.path + if not zip_file: + raise FileNotFoundError(f"Cannot write {self.__class__.__name__} due to no path provided.") + with zipfile.ZipFile(zip_file, "w", self.compression_method, True, self.compression_level) \ + as zf: + if file: + self.path = zf.filename + self.write_contents(zf) + + def write_contents(self, opened_zipfile: zipfile.ZipFile) -> None: + manifest = self.get_manifest() + try: + manifest_str = json.dumps(manifest) + except Exception as e: + raise Exception(f"Manifest {manifest} did not convert to json.") from e + else: + opened_zipfile.writestr("archipelago.json", manifest_str) + + def read(self, file: Optional[Union[str, BinaryIO]] = None) -> None: + """Read data into patch object. file can be file-like, such as an outer zip file's stream.""" + zip_file = file if file else self.path + if not zip_file: + raise FileNotFoundError(f"Cannot read {self.__class__.__name__} due to no path provided.") + with zipfile.ZipFile(zip_file, "r") as zf: + if file: + self.path = zf.filename + self.read_contents(zf) + + def read_contents(self, opened_zipfile: zipfile.ZipFile) -> None: + with opened_zipfile.open("archipelago.json", "r") as f: + manifest = json.load(f) + if manifest["compatible_version"] > self.version: + raise Exception(f"File (version: {manifest['compatible_version']}) too new " + f"for this handler (version: {self.version})") + self.player = manifest["player"] + self.server = manifest["server"] + self.player_name = manifest["player_name"] + + def get_manifest(self) -> Dict[str, Any]: + return { + "server": self.server, # allow immediate connection to server in multiworld. Empty string otherwise + "player": self.player, + "player_name": self.player_name, + "game": self.game, + # minimum version of patch system expected for patching to be successful + "compatible_version": 4, + "version": current_patch_version, + } + + +class APDeltaPatch(APContainer, metaclass=AutoPatchRegister): + """An APContainer that additionally has delta.bsdiff4 + containing a delta patch to get the desired file, often a rom.""" + + hash: Optional[str] # base checksum of source file + patch_file_ending: str = "" + delta: Optional[bytes] = None + result_file_ending: str = ".sfc" + source_data: bytes + + def __init__(self, *args: Any, patched_path: str = "", **kwargs: Any) -> None: + self.patched_path = patched_path + super(APDeltaPatch, self).__init__(*args, **kwargs) + + def get_manifest(self) -> Dict[str, Any]: + manifest = super(APDeltaPatch, self).get_manifest() + manifest["base_checksum"] = self.hash + manifest["result_file_ending"] = self.result_file_ending + manifest["patch_file_ending"] = self.patch_file_ending + return manifest + + @classmethod + def get_source_data(cls) -> bytes: + """Get Base data""" + raise NotImplementedError() + + @classmethod + def get_source_data_with_cache(cls) -> bytes: + if not hasattr(cls, "source_data"): + cls.source_data = cls.get_source_data() + return cls.source_data + + def write_contents(self, opened_zipfile: zipfile.ZipFile): + super(APDeltaPatch, self).write_contents(opened_zipfile) + # write Delta + opened_zipfile.writestr("delta.bsdiff4", + bsdiff4.diff(self.get_source_data_with_cache(), open(self.patched_path, "rb").read()), + compress_type=zipfile.ZIP_STORED) # bsdiff4 is a format with integrated compression + + def read_contents(self, opened_zipfile: zipfile.ZipFile): + super(APDeltaPatch, self).read_contents(opened_zipfile) + self.delta = opened_zipfile.read("delta.bsdiff4") + + def patch(self, target: str): + """Base + Delta -> Patched""" + if not self.delta: + self.read() + result = bsdiff4.patch(self.get_source_data_with_cache(), self.delta) + with open(target, "wb") as f: + f.write(result) diff --git a/worlds/alttp/Rom.py b/worlds/alttp/Rom.py index 9ca3a355e1..24a1588c52 100644 --- a/worlds/alttp/Rom.py +++ b/worlds/alttp/Rom.py @@ -1,11 +1,12 @@ from __future__ import annotations import Utils -from Patch import read_rom +import worlds.AutoWorld +import worlds.Files -LTTPJPN10HASH = '03a63945398191337e896e5771f77173' -RANDOMIZERBASEHASH = '9952c2a3ec1b421e408df0d20c8f0c7f' -ROM_PLAYER_LIMIT = 255 +LTTPJPN10HASH: str = "03a63945398191337e896e5771f77173" +RANDOMIZERBASEHASH: str = "9952c2a3ec1b421e408df0d20c8f0c7f" +ROM_PLAYER_LIMIT: int = 255 import io import json @@ -34,7 +35,7 @@ from worlds.alttp.Text import KingsReturn_texts, Sanctuary_texts, Kakariko_texts DeathMountain_texts, \ LostWoods_texts, WishingWell_texts, DesertPalace_texts, MountainTower_texts, LinksHouse_texts, Lumberjacks_texts, \ SickKid_texts, FluteBoy_texts, Zora_texts, MagicShop_texts, Sahasrahla_names -from Utils import local_path, user_path, int16_as_bytes, int32_as_bytes, snes_to_pc, is_frozen, parse_yaml +from Utils import local_path, user_path, int16_as_bytes, int32_as_bytes, snes_to_pc, is_frozen, parse_yaml, read_snes_rom from worlds.alttp.Items import ItemFactory, item_table, item_name_groups, progression_items from worlds.alttp.EntranceShuffle import door_addresses from worlds.alttp.Options import smallkey_shuffle @@ -57,13 +58,13 @@ class LocalRom(object): self.orig_buffer = None with open(file, 'rb') as stream: - self.buffer = read_rom(stream) + self.buffer = read_snes_rom(stream) if patch: self.patch_base_rom() self.orig_buffer = self.buffer.copy() if vanillaRom: with open(vanillaRom, 'rb') as vanillaStream: - self.orig_buffer = read_rom(vanillaStream) + self.orig_buffer = read_snes_rom(vanillaStream) def read_byte(self, address: int) -> int: return self.buffer[address] @@ -123,29 +124,24 @@ class LocalRom(object): return expected == buffermd5.hexdigest() def patch_base_rom(self): - if os.path.isfile(local_path('basepatch.sfc')): - with open(local_path('basepatch.sfc'), 'rb') as stream: + if os.path.isfile(user_path('basepatch.sfc')): + with open(user_path('basepatch.sfc'), 'rb') as stream: buffer = bytearray(stream.read()) if self.verify(buffer): self.buffer = buffer - if not os.path.exists(local_path('data', 'basepatch.apbp')): - Patch.create_patch_file(local_path('basepatch.sfc')) return - if not os.path.isfile(local_path('data', 'basepatch.apbp')): - raise RuntimeError('Base patch unverified. Unable to continue.') + with open(local_path("data", "basepatch.bsdiff4"), "rb") as f: + delta = f.read() - if os.path.isfile(local_path('data', 'basepatch.apbp')): - _, target, buffer = Patch.create_rom_bytes(local_path('data', 'basepatch.apbp'), ignore_version=True) - if self.verify(buffer): - self.buffer = bytearray(buffer) - with open(user_path('basepatch.sfc'), 'wb') as stream: - stream.write(buffer) - return - raise RuntimeError('Base patch unverified. Unable to continue.') - - raise RuntimeError('Could not find Base Patch. Unable to continue.') + buffer = bsdiff4.patch(get_base_rom_bytes(), delta) + if self.verify(buffer): + self.buffer = bytearray(buffer) + with open(user_path('basepatch.sfc'), 'wb') as stream: + stream.write(buffer) + return + raise RuntimeError('Base patch unverified. Unable to continue.') def write_crc(self): crc = (sum(self.buffer[:0x7FDC] + self.buffer[0x7FE0:]) + 0x01FE) & 0xFFFF @@ -544,7 +540,7 @@ class Sprite(): def get_vanilla_sprite_data(self): file_name = get_base_rom_path() - base_rom_bytes = bytes(read_rom(open(file_name, "rb"))) + base_rom_bytes = bytes(read_snes_rom(open(file_name, "rb"))) Sprite.sprite = base_rom_bytes[0x80000:0x87000] Sprite.palette = base_rom_bytes[0xDD308:0xDD380] Sprite.glove_palette = base_rom_bytes[0xDEDF5:0xDEDF9] @@ -2906,7 +2902,7 @@ hash_alphabet = [ ] -class LttPDeltaPatch(Patch.APDeltaPatch): +class LttPDeltaPatch(worlds.Files.APDeltaPatch): hash = LTTPJPN10HASH game = "A Link to the Past" patch_file_ending = ".aplttp" @@ -2920,7 +2916,7 @@ def get_base_rom_bytes(file_name: str = "") -> bytes: base_rom_bytes = getattr(get_base_rom_bytes, "base_rom_bytes", None) if not base_rom_bytes: file_name = get_base_rom_path(file_name) - base_rom_bytes = bytes(read_rom(open(file_name, "rb"))) + base_rom_bytes = bytes(read_snes_rom(open(file_name, "rb"))) basemd5 = hashlib.md5() basemd5.update(base_rom_bytes) diff --git a/worlds/dkc3/Rom.py b/worlds/dkc3/Rom.py index 2bb5221a60..6308e95860 100644 --- a/worlds/dkc3/Rom.py +++ b/worlds/dkc3/Rom.py @@ -1,5 +1,6 @@ import Utils -from Patch import read_rom, APDeltaPatch +from Utils import read_snes_rom +from worlds.Files import APDeltaPatch from .Locations import lookup_id_to_name, all_locations from .Levels import level_list, level_dict @@ -440,13 +441,13 @@ class LocalRom(object): self.orig_buffer = None with open(file, 'rb') as stream: - self.buffer = read_rom(stream) + self.buffer = read_snes_rom(stream) #if patch: # self.patch_rom() # self.orig_buffer = self.buffer.copy() #if vanillaRom: # with open(vanillaRom, 'rb') as vanillaStream: - # self.orig_buffer = read_rom(vanillaStream) + # self.orig_buffer = read_snes_rom(vanillaStream) def read_bit(self, address: int, bit_number: int) -> bool: bitflag = (1 << bit_number) @@ -724,7 +725,7 @@ def get_base_rom_bytes(file_name: str = "") -> bytes: base_rom_bytes = getattr(get_base_rom_bytes, "base_rom_bytes", None) if not base_rom_bytes: file_name = get_base_rom_path(file_name) - base_rom_bytes = bytes(read_rom(open(file_name, "rb"))) + base_rom_bytes = bytes(read_snes_rom(open(file_name, "rb"))) basemd5 = hashlib.md5() basemd5.update(base_rom_bytes) diff --git a/worlds/factorio/Mod.py b/worlds/factorio/Mod.py index 89666ffbdd..018816d90a 100644 --- a/worlds/factorio/Mod.py +++ b/worlds/factorio/Mod.py @@ -11,6 +11,8 @@ import shutil import Utils import Patch +import worlds.AutoWorld +import worlds.Files from . import Options from .Technologies import tech_table, recipes, free_sample_exclusions, progressive_technology_table, \ @@ -57,7 +59,7 @@ recipe_time_ranges = { } -class FactorioModFile(Patch.APContainer): +class FactorioModFile(worlds.Files.APContainer): game = "Factorio" compression_method = zipfile.ZIP_DEFLATED # Factorio can't load LZMA archives diff --git a/worlds/sm/Rom.py b/worlds/sm/Rom.py index e2957fe00f..e5f5bc7a37 100644 --- a/worlds/sm/Rom.py +++ b/worlds/sm/Rom.py @@ -3,7 +3,8 @@ import os import json import Utils -from Patch import read_rom, APDeltaPatch +from Utils import read_snes_rom +from worlds.Files import APDeltaPatch SMJUHASH = '21f3e98df4780ee1c667b84e57d88675' ROM_PLAYER_LIMIT = 65535 # max archipelago player ID. note, SM ROM itself will only store 201 names+ids max @@ -22,7 +23,7 @@ def get_base_rom_bytes(file_name: str = "") -> bytes: base_rom_bytes = getattr(get_base_rom_bytes, "base_rom_bytes", None) if not base_rom_bytes: file_name = get_base_rom_path(file_name) - base_rom_bytes = bytes(read_rom(open(file_name, "rb"))) + base_rom_bytes = bytes(read_snes_rom(open(file_name, "rb"))) basemd5 = hashlib.md5() basemd5.update(base_rom_bytes) diff --git a/worlds/smw/Rom.py b/worlds/smw/Rom.py index 6c750d74ff..34bdd2f0ea 100644 --- a/worlds/smw/Rom.py +++ b/worlds/smw/Rom.py @@ -1,8 +1,7 @@ import Utils -from Patch import read_rom, APDeltaPatch +from worlds.Files import APDeltaPatch from .Aesthetics import generate_shuffled_header_data -from .Locations import lookup_id_to_name, all_locations -from .Levels import level_info_dict, full_level_list, submap_level_list, location_id_to_level_id +from .Levels import level_info_dict from .Names.TextBox import generate_goal_text, title_text_mapping, generate_text_box USHASH = 'cdd3c8c37322978ca8669b34bc89c804' @@ -69,7 +68,7 @@ class LocalRom: self.orig_buffer = None with open(file, 'rb') as stream: - self.buffer = read_rom(stream) + self.buffer = Utils.read_snes_rom(stream) def read_bit(self, address: int, bit_number: int) -> bool: bitflag = (1 << bit_number) @@ -827,7 +826,7 @@ def get_base_rom_bytes(file_name: str = "") -> bytes: base_rom_bytes = getattr(get_base_rom_bytes, "base_rom_bytes", None) if not base_rom_bytes: file_name = get_base_rom_path(file_name) - base_rom_bytes = bytes(read_rom(open(file_name, "rb"))) + base_rom_bytes = bytes(Utils.read_snes_rom(open(file_name, "rb"))) basemd5 = hashlib.md5() basemd5.update(base_rom_bytes) @@ -837,6 +836,7 @@ def get_base_rom_bytes(file_name: str = "") -> bytes: get_base_rom_bytes.base_rom_bytes = base_rom_bytes return base_rom_bytes + def get_base_rom_path(file_name: str = "") -> str: options = Utils.get_options() if not file_name: diff --git a/worlds/smw/__init__.py b/worlds/smw/__init__.py index 46ce4de7ae..c31d4af7f6 100644 --- a/worlds/smw/__init__.py +++ b/worlds/smw/__init__.py @@ -14,7 +14,6 @@ from ..generic.Rules import add_rule from .Names import ItemName, LocationName from ..AutoWorld import WebWorld, World from .Rom import LocalRom, patch_rom, get_base_rom_path, SMWDeltaPatch -import Patch class SMWWeb(WebWorld): @@ -146,6 +145,7 @@ class SMWWorld(World): def generate_output(self, output_directory: str): + rompath = "" # if variable is not declared finally clause may fail try: world = self.world player = self.player @@ -167,9 +167,9 @@ class SMWWorld(World): except: raise finally: + self.rom_name_available_event.set() # make sure threading continues and errors are collected if os.path.exists(rompath): os.unlink(rompath) - self.rom_name_available_event.set() # make sure threading continues and errors are collected def modify_multidata(self, multidata: dict): import base64 diff --git a/worlds/smz3/Rom.py b/worlds/smz3/Rom.py index a355636fed..3fec151dc6 100644 --- a/worlds/smz3/Rom.py +++ b/worlds/smz3/Rom.py @@ -2,7 +2,8 @@ import hashlib import os import Utils -from Patch import read_rom, APDeltaPatch +from Utils import read_snes_rom +from worlds.Files import APDeltaPatch SMJUHASH = '21f3e98df4780ee1c667b84e57d88675' LTTPJPN10HASH = '03a63945398191337e896e5771f77173' @@ -23,7 +24,7 @@ def get_base_rom_bytes() -> bytes: base_rom_bytes = getattr(get_base_rom_bytes, "base_rom_bytes", None) if not base_rom_bytes: sm_file_name = get_sm_base_rom_path() - sm_base_rom_bytes = bytes(read_rom(open(sm_file_name, "rb"))) + sm_base_rom_bytes = bytes(read_snes_rom(open(sm_file_name, "rb"))) basemd5 = hashlib.md5() basemd5.update(sm_base_rom_bytes) @@ -31,7 +32,7 @@ def get_base_rom_bytes() -> bytes: raise Exception('Supplied Base Rom does not match known MD5 for SM Japan+US release. ' 'Get the correct game and version, then dump it') lttp_file_name = get_lttp_base_rom_path() - lttp_base_rom_bytes = bytes(read_rom(open(lttp_file_name, "rb"))) + lttp_base_rom_bytes = bytes(read_snes_rom(open(lttp_file_name, "rb"))) basemd5 = hashlib.md5() basemd5.update(lttp_base_rom_bytes) diff --git a/worlds/soe/Patch.py b/worlds/soe/Patch.py index 21bdd94220..f6a0a69f55 100644 --- a/worlds/soe/Patch.py +++ b/worlds/soe/Patch.py @@ -2,7 +2,7 @@ import bsdiff4 import yaml from typing import Optional import Utils -from Patch import APDeltaPatch +from worlds.Files import APDeltaPatch import os From 060a04700d8a1edf974a7da084b11eb0152e5814 Mon Sep 17 00:00:00 2001 From: Fabian Dill Date: Fri, 30 Sep 2022 04:58:19 +0200 Subject: [PATCH 13/18] Core: allow generic access to indirect_connections (#1056) --- BaseClasses.py | 14 ++++++++++---- test/minor_glitches/TestMinor.py | 6 ++---- test/owg/TestVanillaOWG.py | 6 ++---- test/vanilla/TestVanilla.py | 6 ++---- worlds/alttp/__init__.py | 8 ++++++-- 5 files changed, 22 insertions(+), 18 deletions(-) diff --git a/BaseClasses.py b/BaseClasses.py index 634c2a833f..c5e7640b17 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -51,8 +51,10 @@ class MultiWorld(): non_local_items: Dict[int, Options.NonLocalItems] progression_balancing: Dict[int, Options.ProgressionBalancing] completion_condition: Dict[int, Callable[[CollectionState], bool]] + indirect_connections: Dict[Region, Set[Entrance]] exclude_locations: Dict[int, Options.ExcludeLocations] + class AttributeProxy(): def __init__(self, rule): self.rule = rule @@ -89,6 +91,7 @@ class MultiWorld(): self.customitemarray = [] self.shuffle_ganon = True self.spoiler = Spoiler(self) + self.indirect_connections = {} self.fix_trock_doors = self.AttributeProxy( lambda player: self.shuffle[player] != 'vanilla' or self.mode[player] == 'inverted') self.fix_skullwoods_exit = self.AttributeProxy( @@ -406,6 +409,11 @@ class MultiWorld(): def clear_entrance_cache(self): self._cached_entrances = None + def register_indirect_condition(self, region: Region, entrance: Entrance): + """Report that access to this Region can result in unlocking this Entrance, + state.can_reach(Region) in the Entrance's traversal condition, as opposed to pure transition logic.""" + self.indirect_connections.setdefault(region, set()).add(entrance) + def get_locations(self) -> List[Location]: if self._cached_locations is None: self._cached_locations = [location for region in self.regions for location in region.locations] @@ -610,7 +618,6 @@ class CollectionState(): self.collect(item, True) def update_reachable_regions(self, player: int): - from worlds.alttp.EntranceShuffle import indirect_connections self.stale[player] = False rrp = self.reachable_regions[player] bc = self.blocked_connections[player] @@ -618,7 +625,7 @@ class CollectionState(): start = self.world.get_region('Menu', player) # init on first call - this can't be done on construction since the regions don't exist yet - if not start in rrp: + if start not in rrp: rrp.add(start) bc.update(start.exits) queue.extend(start.exits) @@ -638,8 +645,7 @@ class CollectionState(): self.path[new_region] = (new_region.name, self.path.get(connection, None)) # Retry connections if the new region can unblock them - if new_region.name in indirect_connections: - new_entrance = self.world.get_entrance(indirect_connections[new_region.name], player) + for new_entrance in self.world.indirect_connections.get(new_region, set()): if new_entrance in bc and new_entrance not in queue: queue.append(new_entrance) diff --git a/test/minor_glitches/TestMinor.py b/test/minor_glitches/TestMinor.py index 81c09cfb27..41cb13161a 100644 --- a/test/minor_glitches/TestMinor.py +++ b/test/minor_glitches/TestMinor.py @@ -23,10 +23,8 @@ class TestMinor(TestBase): self.world.set_default_common_options() self.world.logic[1] = "minorglitches" self.world.difficulty_requirements[1] = difficulties['normal'] - create_regions(self.world, 1) - create_dungeons(self.world, 1) - create_shops(self.world, 1) - link_entrances(self.world, 1) + self.world.worlds[1].er_seed = 0 + self.world.worlds[1].create_regions() self.world.worlds[1].create_items() self.world.required_medallions[1] = ['Ether', 'Quake'] self.world.itempool.extend(get_dungeon_item_pool(self.world)) diff --git a/test/owg/TestVanillaOWG.py b/test/owg/TestVanillaOWG.py index e5489117a7..a4367fb55e 100644 --- a/test/owg/TestVanillaOWG.py +++ b/test/owg/TestVanillaOWG.py @@ -24,10 +24,8 @@ class TestVanillaOWG(TestBase): self.world.set_default_common_options() self.world.difficulty_requirements[1] = difficulties['normal'] self.world.logic[1] = "owglitches" - create_regions(self.world, 1) - create_dungeons(self.world, 1) - create_shops(self.world, 1) - link_entrances(self.world, 1) + self.world.worlds[1].er_seed = 0 + self.world.worlds[1].create_regions() self.world.worlds[1].create_items() self.world.required_medallions[1] = ['Ether', 'Quake'] self.world.itempool.extend(get_dungeon_item_pool(self.world)) diff --git a/test/vanilla/TestVanilla.py b/test/vanilla/TestVanilla.py index e5ee73406a..c9fa3f763b 100644 --- a/test/vanilla/TestVanilla.py +++ b/test/vanilla/TestVanilla.py @@ -22,10 +22,8 @@ class TestVanilla(TestBase): self.world.set_default_common_options() self.world.logic[1] = "noglitches" self.world.difficulty_requirements[1] = difficulties['normal'] - create_regions(self.world, 1) - create_dungeons(self.world, 1) - create_shops(self.world, 1) - link_entrances(self.world, 1) + self.world.worlds[1].er_seed = 0 + self.world.worlds[1].create_regions() self.world.worlds[1].create_items() self.world.required_medallions[1] = ['Ether', 'Quake'] self.world.itempool.extend(get_dungeon_item_pool(self.world)) diff --git a/worlds/alttp/__init__.py b/worlds/alttp/__init__.py index bbdd941127..88d2c2f29f 100644 --- a/worlds/alttp/__init__.py +++ b/worlds/alttp/__init__.py @@ -7,7 +7,7 @@ import typing import Utils from BaseClasses import Item, CollectionState, Tutorial from .Dungeons import create_dungeons -from .EntranceShuffle import link_entrances, link_inverted_entrances, plando_connect +from .EntranceShuffle import link_entrances, link_inverted_entrances, plando_connect, indirect_connections from .InvertedRegions import create_inverted_regions, mark_dark_world_regions from .ItemPool import generate_itempool, difficulties from .Items import item_init_table, item_name_groups, item_table, GetBeemizerItem @@ -19,7 +19,7 @@ from .Rom import LocalRom, patch_rom, patch_race_rom, check_enemizer, patch_enem from .Rules import set_rules from .Shops import create_shops, ShopSlotFill from .SubClasses import ALttPItem -from ..AutoWorld import World, WebWorld, LogicMixin +from worlds.AutoWorld import World, WebWorld, LogicMixin lttp_logger = logging.getLogger("A Link to the Past") @@ -223,6 +223,10 @@ class ALTTPWorld(World): world.random = old_random plando_connect(world, player) + for region_name, entrance_name in indirect_connections.items(): + world.register_indirect_condition(self.world.get_region(region_name, player), + self.world.get_entrance(entrance_name, player)) + def collect_item(self, state: CollectionState, item: Item, remove=False): item_name = item.name if item_name.startswith('Progressive '): From 4943d26160a04d26b66472e9626d146eb809db84 Mon Sep 17 00:00:00 2001 From: black-sliver <59490463+black-sliver@users.noreply.github.com> Date: Sat, 1 Oct 2022 02:47:31 +0200 Subject: [PATCH 14/18] APWorld: make it behave more like a regular world - set sys.modules so it can be imported with worlds.* - overwrite __package__ so it can reference ..generic - fix deprecation warning --- worlds/__init__.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/worlds/__init__.py b/worlds/__init__.py index e36eb275a3..039f12eb93 100644 --- a/worlds/__init__.py +++ b/worlds/__init__.py @@ -1,7 +1,9 @@ import importlib -import zipimport import os +import sys import typing +import warnings +import zipimport folder = os.path.dirname(__file__) @@ -39,7 +41,14 @@ world_sources.sort() for world_source in world_sources: if world_source.is_zip: importer = zipimport.zipimporter(os.path.join(folder, world_source.path)) - importer.load_module(world_source.path.split(".", 1)[0]) + spec = importer.find_spec(world_source.path.split(".", 1)[0]) + mod = importlib.util.module_from_spec(spec) + mod.__package__ = f"worlds.{mod.__package__}" + mod.__name__ = f"worlds.{mod.__name__}" + sys.modules[mod.__name__] = mod + with warnings.catch_warnings(): + warnings.filterwarnings("ignore", message="__package__ != __spec__.parent") + importer.exec_module(mod) else: importlib.import_module(f".{world_source.path}", "worlds") From e9e15e854df37146920f9da697c91cd378f83f3b Mon Sep 17 00:00:00 2001 From: Fabian Dill Date: Sat, 1 Oct 2022 15:24:05 +0200 Subject: [PATCH 15/18] SC2: make apworld compatible (#1024) Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com> --- Starcraft2Client.py | 42 ++++++++++++++++++------------------- worlds/sc2wol/LogicMixin.py | 2 +- worlds/sc2wol/Regions.py | 4 ++-- worlds/sc2wol/__init__.py | 5 ++--- 4 files changed, 26 insertions(+), 27 deletions(-) diff --git a/Starcraft2Client.py b/Starcraft2Client.py index d91adffb08..c1eed74b4c 100644 --- a/Starcraft2Client.py +++ b/Starcraft2Client.py @@ -12,21 +12,9 @@ import typing import queue from pathlib import Path -import nest_asyncio -import sc2 -from sc2.bot_ai import BotAI -from sc2.data import Race -from sc2.main import run_game -from sc2.player import Bot - -import NetUtils -from MultiServer import mark_raw +# CommonClient import first to trigger ModuleUpdater +from CommonClient import CommonContext, server_loop, ClientCommandProcessor, gui_enabled, get_base_parser from Utils import init_logging, is_windows -from worlds.sc2wol import SC2WoLWorld -from worlds.sc2wol.Items import lookup_id_to_name, item_table, ItemData, type_flaggroups -from worlds.sc2wol.Locations import SC2WOL_LOC_ID_OFFSET -from worlds.sc2wol.MissionTables import lookup_id_to_mission -from worlds.sc2wol.Regions import MissionInfo if __name__ == "__main__": init_logging("SC2Client", exception_logger="Client") @@ -34,10 +22,21 @@ if __name__ == "__main__": logger = logging.getLogger("Client") sc2_logger = logging.getLogger("Starcraft2") -import colorama +import nest_asyncio +import sc2 +from sc2.bot_ai import BotAI +from sc2.data import Race +from sc2.main import run_game +from sc2.player import Bot +from worlds.sc2wol import SC2WoLWorld +from worlds.sc2wol.Items import lookup_id_to_name, item_table, ItemData, type_flaggroups +from worlds.sc2wol.Locations import SC2WOL_LOC_ID_OFFSET +from worlds.sc2wol.MissionTables import lookup_id_to_mission +from worlds.sc2wol.Regions import MissionInfo -from NetUtils import ClientStatus, RawJSONtoTextParser -from CommonClient import CommonContext, server_loop, ClientCommandProcessor, gui_enabled, get_base_parser +import colorama +from NetUtils import ClientStatus, NetworkItem, RawJSONtoTextParser +from MultiServer import mark_raw nest_asyncio.apply() max_bonus: int = 8 @@ -357,8 +356,9 @@ class SC2Context(CommonContext): self.ui = SC2Manager(self) self.ui_task = asyncio.create_task(self.ui.async_run(), name="UI") - - Builder.load_file(Utils.local_path(os.path.dirname(SC2WoLWorld.__file__), "Starcraft2.kv")) + import pkgutil + data = pkgutil.get_data(SC2WoLWorld.__module__, "Starcraft2.kv").decode() + Builder.load_string(data) async def shutdown(self): await super(SC2Context, self).shutdown() @@ -440,8 +440,8 @@ wol_default_categories = [ ] -def calculate_items(items: typing.List[NetUtils.NetworkItem]) -> typing.List[int]: - network_item: NetUtils.NetworkItem +def calculate_items(items: typing.List[NetworkItem]) -> typing.List[int]: + network_item: NetworkItem accumulators: typing.List[int] = [0 for _ in type_flaggroups] for network_item in items: diff --git a/worlds/sc2wol/LogicMixin.py b/worlds/sc2wol/LogicMixin.py index 7a08142672..52bb6b09a8 100644 --- a/worlds/sc2wol/LogicMixin.py +++ b/worlds/sc2wol/LogicMixin.py @@ -1,5 +1,5 @@ from BaseClasses import MultiWorld -from ..AutoWorld import LogicMixin +from worlds.AutoWorld import LogicMixin class SC2WoLLogic(LogicMixin): diff --git a/worlds/sc2wol/Regions.py b/worlds/sc2wol/Regions.py index 4e20752982..8219a982c9 100644 --- a/worlds/sc2wol/Regions.py +++ b/worlds/sc2wol/Regions.py @@ -1,8 +1,8 @@ -from typing import List, Set, Dict, Tuple, Optional, Callable, NamedTuple +from typing import List, Set, Dict, Tuple, Optional, Callable from BaseClasses import MultiWorld, Region, Entrance, Location, RegionType from .Locations import LocationData from .Options import get_option_value -from worlds.sc2wol.MissionTables import MissionInfo, vanilla_shuffle_order, vanilla_mission_req_table, \ +from .MissionTables import MissionInfo, vanilla_shuffle_order, vanilla_mission_req_table, \ no_build_regions_list, easy_regions_list, medium_regions_list, hard_regions_list import random diff --git a/worlds/sc2wol/__init__.py b/worlds/sc2wol/__init__.py index 4f9b33609f..6d056df808 100644 --- a/worlds/sc2wol/__init__.py +++ b/worlds/sc2wol/__init__.py @@ -1,15 +1,14 @@ import typing -from typing import List, Set, Tuple, NamedTuple +from typing import List, Set, Tuple from BaseClasses import Item, MultiWorld, Location, Tutorial, ItemClassification -from ..AutoWorld import WebWorld +from worlds.AutoWorld import WebWorld, World from .Items import StarcraftWoLItem, item_table, filler_items, item_name_groups, get_full_item_list, \ basic_unit from .Locations import get_locations from .Regions import create_regions from .Options import sc2wol_options, get_option_value from .LogicMixin import SC2WoLLogic -from ..AutoWorld import World class Starcraft2WoLWebWorld(WebWorld): From 411cd51a9296b5f705a445403d2cce5f05a3a725 Mon Sep 17 00:00:00 2001 From: Fabian Dill Date: Sat, 1 Oct 2022 16:22:24 +0200 Subject: [PATCH 16/18] SC2: dynamically create Beat Events, preventing copy-paste errors (#1023) --- worlds/sc2wol/Locations.py | 76 +++++++------------------------------- 1 file changed, 13 insertions(+), 63 deletions(-) diff --git a/worlds/sc2wol/Locations.py b/worlds/sc2wol/Locations.py index f69abd48e3..14dd25fd52 100644 --- a/worlds/sc2wol/Locations.py +++ b/worlds/sc2wol/Locations.py @@ -27,13 +27,10 @@ def get_locations(world: Optional[MultiWorld], player: Optional[int]) -> Tuple[L LocationData("Liberation Day", "Liberation Day: Fourth Statue", SC2WOL_LOC_ID_OFFSET + 104), LocationData("Liberation Day", "Liberation Day: Fifth Statue", SC2WOL_LOC_ID_OFFSET + 105), LocationData("Liberation Day", "Liberation Day: Sixth Statue", SC2WOL_LOC_ID_OFFSET + 106), - LocationData("Liberation Day", "Beat Liberation Day", None), LocationData("The Outlaws", "The Outlaws: Victory", SC2WOL_LOC_ID_OFFSET + 200, lambda state: state._sc2wol_has_common_unit(world, player)), LocationData("The Outlaws", "The Outlaws: Rebel Base", SC2WOL_LOC_ID_OFFSET + 201, lambda state: state._sc2wol_has_common_unit(world, player)), - LocationData("The Outlaws", "Beat The Outlaws", None, - lambda state: state._sc2wol_has_common_unit(world, player)), LocationData("Zero Hour", "Zero Hour: Victory", SC2WOL_LOC_ID_OFFSET + 300, lambda state: state._sc2wol_has_common_unit(world, player)), LocationData("Zero Hour", "Zero Hour: First Group Rescued", SC2WOL_LOC_ID_OFFSET + 301), @@ -41,26 +38,20 @@ def get_locations(world: Optional[MultiWorld], player: Optional[int]) -> Tuple[L lambda state: state._sc2wol_has_common_unit(world, player)), LocationData("Zero Hour", "Zero Hour: Third Group Rescued", SC2WOL_LOC_ID_OFFSET + 303, lambda state: state._sc2wol_has_common_unit(world, player)), - LocationData("Zero Hour", "Beat Zero Hour", None, - lambda state: state._sc2wol_has_common_unit(world, player)), LocationData("Evacuation", "Evacuation: Victory", SC2WOL_LOC_ID_OFFSET + 400, - lambda state: state._sc2wol_has_anti_air(world, player)), + lambda state: state._sc2wol_has_common_unit(world, player) and + state._sc2wol_has_competent_anti_air(world, player)), LocationData("Evacuation", "Evacuation: First Chysalis", SC2WOL_LOC_ID_OFFSET + 401), LocationData("Evacuation", "Evacuation: Second Chysalis", SC2WOL_LOC_ID_OFFSET + 402, lambda state: state._sc2wol_has_common_unit(world, player)), LocationData("Evacuation", "Evacuation: Third Chysalis", SC2WOL_LOC_ID_OFFSET + 403, lambda state: state._sc2wol_has_common_unit(world, player)), - LocationData("Evacuation", "Beat Evacuation", None, - lambda state: state._sc2wol_has_common_unit(world, player) and - state._sc2wol_has_competent_anti_air(world, player)), LocationData("Outbreak", "Outbreak: Victory", SC2WOL_LOC_ID_OFFSET + 500, lambda state: state._sc2wol_has_common_unit(world, player) or state.has("Reaper", player)), LocationData("Outbreak", "Outbreak: Left Infestor", SC2WOL_LOC_ID_OFFSET + 501, lambda state: state._sc2wol_has_common_unit(world, player) or state.has("Reaper", player)), LocationData("Outbreak", "Outbreak: Right Infestor", SC2WOL_LOC_ID_OFFSET + 502, lambda state: state._sc2wol_has_common_unit(world, player) or state.has("Reaper", player)), - LocationData("Outbreak", "Beat Outbreak", None, - lambda state: state._sc2wol_has_common_unit(world, player) or state.has("Reaper", player)), LocationData("Safe Haven", "Safe Haven: Victory", SC2WOL_LOC_ID_OFFSET + 600, lambda state: state._sc2wol_has_common_unit(world, player) and state._sc2wol_has_competent_anti_air(world, player)), @@ -73,9 +64,6 @@ def get_locations(world: Optional[MultiWorld], player: Optional[int]) -> Tuple[L LocationData("Safe Haven", "Safe Haven: South Nexus", SC2WOL_LOC_ID_OFFSET + 603, lambda state: state._sc2wol_has_common_unit(world, player) and state._sc2wol_has_competent_anti_air(world, player)), - LocationData("Safe Haven", "Beat Safe Haven", None, - lambda state: state._sc2wol_has_common_unit(world, player) and - state._sc2wol_has_competent_anti_air(world, player)), LocationData("Haven's Fall", "Haven's Fall: Victory", SC2WOL_LOC_ID_OFFSET + 700, lambda state: state._sc2wol_has_common_unit(world, player) and state._sc2wol_has_competent_anti_air(world, player)), @@ -88,9 +76,6 @@ def get_locations(world: Optional[MultiWorld], player: Optional[int]) -> Tuple[L LocationData("Haven's Fall", "Haven's Fall: South Hive", SC2WOL_LOC_ID_OFFSET + 703, lambda state: state._sc2wol_has_common_unit(world, player) and state._sc2wol_has_competent_anti_air(world, player)), - LocationData("Haven's Fall", "Beat Haven's Fall", None, - lambda state: state._sc2wol_has_common_unit(world, player) and - state._sc2wol_has_competent_anti_air(world, player)), LocationData("Smash and Grab", "Smash and Grab: Victory", SC2WOL_LOC_ID_OFFSET + 800, lambda state: state._sc2wol_has_common_unit(world, player) and state._sc2wol_has_competent_anti_air(world, player)), @@ -101,9 +86,6 @@ def get_locations(world: Optional[MultiWorld], player: Optional[int]) -> Tuple[L LocationData("Smash and Grab", "Smash and Grab: Fourth Relic", SC2WOL_LOC_ID_OFFSET + 804, lambda state: state._sc2wol_has_common_unit(world, player) and state._sc2wol_has_anti_air(world, player)), - LocationData("Smash and Grab", "Beat Smash and Grab", None, - lambda state: state._sc2wol_has_common_unit(world, player) and - state._sc2wol_has_anti_air(world, player)), LocationData("The Dig", "The Dig: Victory", SC2WOL_LOC_ID_OFFSET + 900, lambda state: state._sc2wol_has_common_unit(world, player) and state._sc2wol_has_anti_air(world, player) and @@ -114,10 +96,6 @@ def get_locations(world: Optional[MultiWorld], player: Optional[int]) -> Tuple[L lambda state: state._sc2wol_has_common_unit(world, player)), LocationData("The Dig", "The Dig: Right Cliff Relic", SC2WOL_LOC_ID_OFFSET + 903, lambda state: state._sc2wol_has_common_unit(world, player)), - LocationData("The Dig", "Beat The Dig", None, - lambda state: state._sc2wol_has_common_unit(world, player) and - state._sc2wol_has_anti_air(world, player) and - state._sc2wol_has_heavy_defense(world, player)), LocationData("The Moebius Factor", "The Moebius Factor: Victory", SC2WOL_LOC_ID_OFFSET + 1000, lambda state: state._sc2wol_has_air(world, player) and state._sc2wol_has_anti_air(world, player)), LocationData("The Moebius Factor", "The Moebius Factor: South Rescue", SC2WOL_LOC_ID_OFFSET + 1003, @@ -132,8 +110,6 @@ def get_locations(world: Optional[MultiWorld], player: Optional[int]) -> Tuple[L lambda state: state._sc2wol_able_to_rescue(world, player)), LocationData("The Moebius Factor", "The Moebius Factor: Brutalisk", SC2WOL_LOC_ID_OFFSET + 1008, lambda state: state._sc2wol_has_air(world, player)), - LocationData("The Moebius Factor", "Beat The Moebius Factor", None, - lambda state: state._sc2wol_has_air(world, player)), LocationData("Supernova", "Supernova: Victory", SC2WOL_LOC_ID_OFFSET + 1100, lambda state: state._sc2wol_beats_protoss_deathball(world, player)), LocationData("Supernova", "Supernova: West Relic", SC2WOL_LOC_ID_OFFSET + 1101), @@ -142,8 +118,6 @@ def get_locations(world: Optional[MultiWorld], player: Optional[int]) -> Tuple[L lambda state: state._sc2wol_beats_protoss_deathball(world, player)), LocationData("Supernova", "Supernova: East Relic", SC2WOL_LOC_ID_OFFSET + 1104, lambda state: state._sc2wol_beats_protoss_deathball(world, player)), - LocationData("Supernova", "Beat Supernova", None, - lambda state: state._sc2wol_beats_protoss_deathball(world, player)), LocationData("Maw of the Void", "Maw of the Void: Victory", SC2WOL_LOC_ID_OFFSET + 1200, lambda state: state.has('Battlecruiser', player) or state._sc2wol_has_air(world, player) and @@ -170,19 +144,12 @@ def get_locations(world: Optional[MultiWorld], player: Optional[int]) -> Tuple[L state._sc2wol_has_air(world, player) and state._sc2wol_has_competent_anti_air(world, player) and state.has('Science Vessel', player)), - LocationData("Maw of the Void", "Beat Maw of the Void", None, - lambda state: state.has('Battlecruiser', player) or - state._sc2wol_has_air(world, player) and - state._sc2wol_has_competent_anti_air(world, player) and - state.has('Science Vessel', player)), LocationData("Devil's Playground", "Devil's Playground: Victory", SC2WOL_LOC_ID_OFFSET + 1300, lambda state: state._sc2wol_has_anti_air(world, player) and ( state._sc2wol_has_common_unit(world, player) or state.has("Reaper", player))), LocationData("Devil's Playground", "Devil's Playground: Tosh's Miners", SC2WOL_LOC_ID_OFFSET + 1301), LocationData("Devil's Playground", "Devil's Playground: Brutalisk", SC2WOL_LOC_ID_OFFSET + 1302, lambda state: state._sc2wol_has_common_unit(world, player) or state.has("Reaper", player)), - LocationData("Devil's Playground", "Beat Devil's Playground", None, - lambda state: state._sc2wol_has_common_unit(world, player) or state.has("Reaper", player)), LocationData("Welcome to the Jungle", "Welcome to the Jungle: Victory", SC2WOL_LOC_ID_OFFSET + 1400, lambda state: state._sc2wol_has_common_unit(world, player) and state._sc2wol_has_competent_anti_air(world, player)), @@ -193,28 +160,21 @@ def get_locations(world: Optional[MultiWorld], player: Optional[int]) -> Tuple[L LocationData("Welcome to the Jungle", "Welcome to the Jungle: North-East Relic", SC2WOL_LOC_ID_OFFSET + 1403, lambda state: state._sc2wol_has_common_unit(world, player) and state._sc2wol_has_competent_anti_air(world, player)), - LocationData("Welcome to the Jungle", "Beat Welcome to the Jungle", None, - lambda state: state._sc2wol_has_common_unit(world, player) and - state._sc2wol_has_competent_anti_air(world, player)), LocationData("Breakout", "Breakout: Victory", SC2WOL_LOC_ID_OFFSET + 1500), LocationData("Breakout", "Breakout: Diamondback Prison", SC2WOL_LOC_ID_OFFSET + 1501), LocationData("Breakout", "Breakout: Siegetank Prison", SC2WOL_LOC_ID_OFFSET + 1502), - LocationData("Breakout", "Beat Breakout", None), LocationData("Ghost of a Chance", "Ghost of a Chance: Victory", SC2WOL_LOC_ID_OFFSET + 1600), LocationData("Ghost of a Chance", "Ghost of a Chance: Terrazine Tank", SC2WOL_LOC_ID_OFFSET + 1601), LocationData("Ghost of a Chance", "Ghost of a Chance: Jorium Stockpile", SC2WOL_LOC_ID_OFFSET + 1602), LocationData("Ghost of a Chance", "Ghost of a Chance: First Island Spectres", SC2WOL_LOC_ID_OFFSET + 1603), LocationData("Ghost of a Chance", "Ghost of a Chance: Second Island Spectres", SC2WOL_LOC_ID_OFFSET + 1604), LocationData("Ghost of a Chance", "Ghost of a Chance: Third Island Spectres", SC2WOL_LOC_ID_OFFSET + 1605), - LocationData("Ghost of a Chance", "Beat Ghost of a Chance", None), LocationData("The Great Train Robbery", "The Great Train Robbery: Victory", SC2WOL_LOC_ID_OFFSET + 1700, lambda state: state._sc2wol_has_train_killers(world, player) and state._sc2wol_has_anti_air(world, player)), LocationData("The Great Train Robbery", "The Great Train Robbery: North Defiler", SC2WOL_LOC_ID_OFFSET + 1701), LocationData("The Great Train Robbery", "The Great Train Robbery: Mid Defiler", SC2WOL_LOC_ID_OFFSET + 1702), LocationData("The Great Train Robbery", "The Great Train Robbery: South Defiler", SC2WOL_LOC_ID_OFFSET + 1703), - LocationData("The Great Train Robbery", "Beat The Great Train Robbery", None, - lambda state: state._sc2wol_has_train_killers(world, player)), LocationData("Cutthroat", "Cutthroat: Victory", SC2WOL_LOC_ID_OFFSET + 1800, lambda state: state._sc2wol_has_common_unit(world, player)), LocationData("Cutthroat", "Cutthroat: Mira Han", SC2WOL_LOC_ID_OFFSET + 1801, @@ -224,10 +184,9 @@ def get_locations(world: Optional[MultiWorld], player: Optional[int]) -> Tuple[L LocationData("Cutthroat", "Cutthroat: Mid Relic", SC2WOL_LOC_ID_OFFSET + 1803), LocationData("Cutthroat", "Cutthroat: Southwest Relic", SC2WOL_LOC_ID_OFFSET + 1804, lambda state: state._sc2wol_has_common_unit(world, player)), - LocationData("Cutthroat", "Beat Cutthroat", None, - lambda state: state._sc2wol_has_common_unit(world, player)), LocationData("Engine of Destruction", "Engine of Destruction: Victory", SC2WOL_LOC_ID_OFFSET + 1900, - lambda state: state._sc2wol_has_competent_anti_air(world, player)), + lambda state: state._sc2wol_has_competent_anti_air(world, player) and + state._sc2wol_has_common_unit(world, player) or state.has('Wraith', player)), LocationData("Engine of Destruction", "Engine of Destruction: Odin", SC2WOL_LOC_ID_OFFSET + 1901), LocationData("Engine of Destruction", "Engine of Destruction: Loki", SC2WOL_LOC_ID_OFFSET + 1902, lambda state: state._sc2wol_has_competent_anti_air(world, player) and @@ -239,9 +198,6 @@ def get_locations(world: Optional[MultiWorld], player: Optional[int]) -> Tuple[L LocationData("Engine of Destruction", "Engine of Destruction: Southeast Devourer", SC2WOL_LOC_ID_OFFSET + 1905, lambda state: state._sc2wol_has_competent_anti_air(world, player) and state._sc2wol_has_common_unit(world, player) or state.has('Wraith', player)), - LocationData("Engine of Destruction", "Beat Engine of Destruction", None, - lambda state: state._sc2wol_has_competent_anti_air(world, player) and - state._sc2wol_has_common_unit(world, player) or state.has('Wraith', player)), LocationData("Media Blitz", "Media Blitz: Victory", SC2WOL_LOC_ID_OFFSET + 2000, lambda state: state._sc2wol_has_competent_comp(world, player)), LocationData("Media Blitz", "Media Blitz: Tower 1", SC2WOL_LOC_ID_OFFSET + 2001, @@ -251,8 +207,6 @@ def get_locations(world: Optional[MultiWorld], player: Optional[int]) -> Tuple[L LocationData("Media Blitz", "Media Blitz: Tower 3", SC2WOL_LOC_ID_OFFSET + 2003, lambda state: state._sc2wol_has_competent_comp(world, player)), LocationData("Media Blitz", "Media Blitz: Science Facility", SC2WOL_LOC_ID_OFFSET + 2004), - LocationData("Media Blitz", "Beat Media Blitz", None, - lambda state: state._sc2wol_has_competent_comp(world, player)), LocationData("Piercing the Shroud", "Piercing the Shroud: Victory", SC2WOL_LOC_ID_OFFSET + 2100, lambda state: state.has_any({'Combat Shield (Marine)', 'Stabilizer Medpacks (Medic)'}, player)), LocationData("Piercing the Shroud", "Piercing the Shroud: Holding Cell Relic", SC2WOL_LOC_ID_OFFSET + 2101), @@ -264,44 +218,34 @@ def get_locations(world: Optional[MultiWorld], player: Optional[int]) -> Tuple[L lambda state: state.has_any({'Combat Shield (Marine)', 'Stabilizer Medpacks (Medic)'}, player)), LocationData("Piercing the Shroud", "Piercing the Shroud: Brutalisk ", SC2WOL_LOC_ID_OFFSET + 2105, lambda state: state.has_any({'Combat Shield (Marine)', 'Stabilizer Medpacks (Medic)'}, player)), - LocationData("Piercing the Shroud", "Beat Piercing the Shroud", None, - lambda state: state.has_any({'Combat Shield (Marine)', 'Stabilizer Medpacks (Medic)'}, player)), LocationData("Whispers of Doom", "Whispers of Doom: Victory", SC2WOL_LOC_ID_OFFSET + 2200), LocationData("Whispers of Doom", "Whispers of Doom: First Hatchery", SC2WOL_LOC_ID_OFFSET + 2201), LocationData("Whispers of Doom", "Whispers of Doom: Second Hatchery", SC2WOL_LOC_ID_OFFSET + 2202), LocationData("Whispers of Doom", "Whispers of Doom: Third Hatchery", SC2WOL_LOC_ID_OFFSET + 2203), - LocationData("Whispers of Doom", "Beat Whispers of Doom", None), LocationData("A Sinister Turn", "A Sinister Turn: Victory", SC2WOL_LOC_ID_OFFSET + 2300, lambda state: state._sc2wol_has_protoss_medium_units(world, player)), LocationData("A Sinister Turn", "A Sinister Turn: Robotics Facility", SC2WOL_LOC_ID_OFFSET + 2301), LocationData("A Sinister Turn", "A Sinister Turn: Dark Shrine", SC2WOL_LOC_ID_OFFSET + 2302), LocationData("A Sinister Turn", "A Sinister Turn: Templar Archives", SC2WOL_LOC_ID_OFFSET + 2303, lambda state: state._sc2wol_has_protoss_common_units(world, player)), - LocationData("A Sinister Turn", "Beat A Sinister Turn", None, - lambda state: state._sc2wol_has_protoss_medium_units(world, player)), LocationData("Echoes of the Future", "Echoes of the Future: Victory", SC2WOL_LOC_ID_OFFSET + 2400, lambda state: state._sc2wol_has_protoss_medium_units(world, player)), LocationData("Echoes of the Future", "Echoes of the Future: Close Obelisk", SC2WOL_LOC_ID_OFFSET + 2401), LocationData("Echoes of the Future", "Echoes of the Future: West Obelisk", SC2WOL_LOC_ID_OFFSET + 2402, lambda state: state._sc2wol_has_protoss_common_units(world, player)), - LocationData("Echoes of the Future", "Beat Echoes of the Future", None, - lambda state: state._sc2wol_has_protoss_medium_units(world, player)), LocationData("In Utter Darkness", "In Utter Darkness: Defeat", SC2WOL_LOC_ID_OFFSET + 2500), LocationData("In Utter Darkness", "In Utter Darkness: Protoss Archive", SC2WOL_LOC_ID_OFFSET + 2501, lambda state: state._sc2wol_has_protoss_medium_units(world, player)), LocationData("In Utter Darkness", "In Utter Darkness: Kills", SC2WOL_LOC_ID_OFFSET + 2502, lambda state: state._sc2wol_has_protoss_common_units(world, player)), - LocationData("In Utter Darkness", "Beat In Utter Darkness", None), LocationData("Gates of Hell", "Gates of Hell: Victory", SC2WOL_LOC_ID_OFFSET + 2600, lambda state: state._sc2wol_has_competent_comp(world, player)), LocationData("Gates of Hell", "Gates of Hell: Large Army", SC2WOL_LOC_ID_OFFSET + 2601, lambda state: state._sc2wol_has_competent_comp(world, player)), - LocationData("Gates of Hell", "Beat Gates of Hell", None), LocationData("Belly of the Beast", "Belly of the Beast: Victory", SC2WOL_LOC_ID_OFFSET + 2700), LocationData("Belly of the Beast", "Belly of the Beast: First Charge", SC2WOL_LOC_ID_OFFSET + 2701), LocationData("Belly of the Beast", "Belly of the Beast: Second Charge", SC2WOL_LOC_ID_OFFSET + 2702), LocationData("Belly of the Beast", "Belly of the Beast: Third Charge", SC2WOL_LOC_ID_OFFSET + 2703), - LocationData("Belly of the Beast", "Beat Belly of the Beast", None), LocationData("Shatter the Sky", "Shatter the Sky: Victory", SC2WOL_LOC_ID_OFFSET + 2800, lambda state: state._sc2wol_has_competent_comp(world, player)), LocationData("Shatter the Sky", "Shatter the Sky: Close Coolant Tower", SC2WOL_LOC_ID_OFFSET + 2801, @@ -314,9 +258,15 @@ def get_locations(world: Optional[MultiWorld], player: Optional[int]) -> Tuple[L lambda state: state._sc2wol_has_competent_comp(world, player)), LocationData("Shatter the Sky", "Shatter the Sky: Leviathan", SC2WOL_LOC_ID_OFFSET + 2805, lambda state: state._sc2wol_has_competent_comp(world, player)), - LocationData("Shatter the Sky", "Beat Shatter the Sky", None, - lambda state: state._sc2wol_has_competent_comp(world, player)), LocationData("All-In", "All-In: Victory", None) ] - return tuple(location_table) + beat_events = [] + + for location_data in location_table: + if location_data.name.endswith((": Victory", ": Defeat")): + beat_events.append( + location_data._replace(name="Beat " + location_data.name.rsplit(": ", 1)[0], code=None) + ) + + return tuple(location_table + beat_events) From b8e467fbb8125b6382b1d8482d2c202d9c613009 Mon Sep 17 00:00:00 2001 From: black-sliver <59490463+black-sliver@users.noreply.github.com> Date: Sat, 1 Oct 2022 17:38:39 +0200 Subject: [PATCH 17/18] ModuleUpdate: skip disabled/hidden folders (#1070) * ModuleUpdate: skip non-worlds * ModuleUpdate: don't skip _* folders - _* folders may be used for libraries - this means to properly disable a world, it has to be renamed with a preceding `.` --- ModuleUpdate.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/ModuleUpdate.py b/ModuleUpdate.py index 17eb0906b1..1fe7030e4e 100644 --- a/ModuleUpdate.py +++ b/ModuleUpdate.py @@ -13,10 +13,12 @@ update_ran = getattr(sys, "frozen", False) # don't run update if environment is if not update_ran: for entry in os.scandir(os.path.join(local_dir, "worlds")): - if entry.is_dir(): - req_file = os.path.join(entry.path, "requirements.txt") - if os.path.exists(req_file): - requirements_files.add(req_file) + # skip .* (hidden / disabled) folders + if not entry.name.startswith("."): + if entry.is_dir(): + req_file = os.path.join(entry.path, "requirements.txt") + if os.path.exists(req_file): + requirements_files.add(req_file) def update_command(): From fdd7ffb0895a36689d3cd5fa092668a1915ad360 Mon Sep 17 00:00:00 2001 From: Doug Hoskisson Date: Sun, 2 Oct 2022 07:53:18 -0700 Subject: [PATCH 18/18] Core: move output file name logic into core (#1066) * move output file name logic into core I see the same logic with small variations in each different world implementation. It seems to me, it would be better in the core to keep it consistent. * missed a few * remove review comment * + smw * double quote strings Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com> * revert change to DS3 output file name Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com> --- BaseClasses.py | 7 +++++++ worlds/alttp/__init__.py | 6 +----- worlds/dkc3/__init__.py | 6 +----- worlds/minecraft/__init__.py | 2 +- worlds/oot/__init__.py | 2 +- worlds/sm/__init__.py | 6 ++---- worlds/sm64ex/__init__.py | 2 +- worlds/smw/__init__.py | 6 +----- worlds/smz3/__init__.py | 6 ++---- worlds/soe/__init__.py | 3 +-- worlds/v6/__init__.py | 2 +- 11 files changed, 19 insertions(+), 29 deletions(-) diff --git a/BaseClasses.py b/BaseClasses.py index c5e7640b17..d32749f5f1 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -300,6 +300,13 @@ class MultiWorld(): def get_file_safe_player_name(self, player: int) -> str: return ''.join(c for c in self.get_player_name(player) if c not in '<>:"/\\|?*') + def get_out_file_name_base(self, player: int) -> str: + """ the base name (without file extension) for each player's output file for a seed """ + return f"AP_{self.seed_name}_P{player}" \ + + (f"_{self.get_file_safe_player_name(player).replace(' ', '_')}" + if (self.player_name[player] != f"Player{player}") + else '') + def initialize_regions(self, regions=None): for region in regions if regions else self.regions: region.world = self diff --git a/worlds/alttp/__init__.py b/worlds/alttp/__init__.py index 88d2c2f29f..169d21ab8c 100644 --- a/worlds/alttp/__init__.py +++ b/worlds/alttp/__init__.py @@ -400,11 +400,7 @@ class ALTTPWorld(World): deathlink=world.death_link[player], allowcollect=world.allow_collect[player]) - outfilepname = f'_P{player}' - outfilepname += f"_{world.get_file_safe_player_name(player).replace(' ', '_')}" \ - if world.player_name[player] != 'Player%d' % player else '' - - rompath = os.path.join(output_directory, f'AP_{world.seed_name}{outfilepname}.sfc') + rompath = os.path.join(output_directory, f"{self.world.get_out_file_name_base(self.player)}.sfc") rom.write_to_file(rompath) patch = LttPDeltaPatch(os.path.splitext(rompath)[0]+LttPDeltaPatch.patch_file_ending, player=player, player_name=world.player_name[player], patched_path=rompath) diff --git a/worlds/dkc3/__init__.py b/worlds/dkc3/__init__.py index 5c575b85b5..1389f83efd 100644 --- a/worlds/dkc3/__init__.py +++ b/worlds/dkc3/__init__.py @@ -146,11 +146,7 @@ class DKC3World(World): self.active_level_list.append(LocationName.rocket_rush_region) - outfilepname = f'_P{player}' - outfilepname += f"_{world.player_name[player].replace(' ', '_')}" \ - if world.player_name[player] != 'Player%d' % player else '' - - rompath = os.path.join(output_directory, f'AP_{world.seed_name}{outfilepname}.sfc') + rompath = os.path.join(output_directory, f"{self.world.get_out_file_name_base(self.player)}.sfc") rom.write_to_file(rompath) self.rom_name = rom.name diff --git a/worlds/minecraft/__init__.py b/worlds/minecraft/__init__.py index 6e7addb2d0..fd5752bd40 100644 --- a/worlds/minecraft/__init__.py +++ b/worlds/minecraft/__init__.py @@ -150,7 +150,7 @@ class MinecraftWorld(World): def generate_output(self, output_directory: str): data = self._get_mc_data() - filename = f"AP_{self.world.seed_name}_P{self.player}_{self.world.get_file_safe_player_name(self.player)}.apmc" + filename = f"{self.world.get_out_file_name_base(self.player)}.apmc" with open(os.path.join(output_directory, filename), 'wb') as f: f.write(b64encode(bytes(json.dumps(data), 'utf-8'))) diff --git a/worlds/oot/__init__.py b/worlds/oot/__init__.py index b65882c874..2536c3d4c9 100644 --- a/worlds/oot/__init__.py +++ b/worlds/oot/__init__.py @@ -819,7 +819,7 @@ class OOTWorld(World): # Seed hint RNG, used for ganon text lines also self.hint_rng = self.world.slot_seeds[self.player] - outfile_name = f"AP_{self.world.seed_name}_P{self.player}_{self.world.get_file_safe_player_name(self.player)}" + outfile_name = self.world.get_out_file_name_base(self.player) rom = Rom(file=get_options()['oot_options']['rom_file']) if self.hints != 'none': buildWorldGossipHints(self) diff --git a/worlds/sm/__init__.py b/worlds/sm/__init__.py index 0bf12ca7eb..d901303215 100644 --- a/worlds/sm/__init__.py +++ b/worlds/sm/__init__.py @@ -505,10 +505,8 @@ class SMWorld(World): romPatcher.writeRandoSettings(self.variaRando.randoExec.randoSettings, itemLocs) def generate_output(self, output_directory: str): - outfilebase = 'AP_' + self.world.seed_name - outfilepname = f'_P{self.player}' - outfilepname += f"_{self.world.get_file_safe_player_name(self.player).replace(' ', '_')}" - outputFilename = os.path.join(output_directory, f'{outfilebase}{outfilepname}.sfc') + outfilebase = self.world.get_out_file_name_base(self.player) + outputFilename = os.path.join(output_directory, f"{outfilebase}.sfc") try: self.variaRando.PatchRom(outputFilename, self.APPrePatchRom, self.APPostPatchRom) diff --git a/worlds/sm64ex/__init__.py b/worlds/sm64ex/__init__.py index 88b2261a23..8cf2f74350 100644 --- a/worlds/sm64ex/__init__.py +++ b/worlds/sm64ex/__init__.py @@ -173,7 +173,7 @@ class SM64World(World): } } } - filename = f"AP_{self.world.seed_name}_P{self.player}_{self.world.get_file_safe_player_name(self.player)}.apsm64ex" + filename = f"{self.world.get_out_file_name_base(self.player)}.apsm64ex" with open(os.path.join(output_directory, filename), 'w') as f: json.dump(data, f) diff --git a/worlds/smw/__init__.py b/worlds/smw/__init__.py index c31d4af7f6..77931b7c72 100644 --- a/worlds/smw/__init__.py +++ b/worlds/smw/__init__.py @@ -153,11 +153,7 @@ class SMWWorld(World): rom = LocalRom(get_base_rom_path()) patch_rom(self.world, rom, self.player, self.active_level_dict) - outfilepname = f'_P{player}' - outfilepname += f"_{world.player_name[player].replace(' ', '_')}" \ - if world.player_name[player] != 'Player%d' % player else '' - - rompath = os.path.join(output_directory, f'AP_{world.seed_name}{outfilepname}.sfc') + rompath = os.path.join(output_directory, f"{self.world.get_out_file_name_base(self.player)}.sfc") rom.write_to_file(rompath) self.rom_name = rom.name diff --git a/worlds/smz3/__init__.py b/worlds/smz3/__init__.py index b796c2a43c..753fb556ae 100644 --- a/worlds/smz3/__init__.py +++ b/worlds/smz3/__init__.py @@ -426,11 +426,9 @@ class SMZ3World(World): base_combined_rom[addr + offset] = byte offset += 1 - outfilebase = 'AP_' + self.world.seed_name - outfilepname = f'_P{self.player}' - outfilepname += f"_{self.world.get_file_safe_player_name(self.player).replace(' ', '_')}" \ + outfilebase = self.world.get_out_file_name_base(self.player) - filename = os.path.join(output_directory, f'{outfilebase}{outfilepname}.sfc') + filename = os.path.join(output_directory, f"{outfilebase}.sfc") with open(filename, "wb") as binary_file: binary_file.write(base_combined_rom) patch = SMZ3DeltaPatch(os.path.splitext(filename)[0]+SMZ3DeltaPatch.patch_file_ending, player=self.player, diff --git a/worlds/soe/__init__.py b/worlds/soe/__init__.py index a0dc41c3ce..4885fd3179 100644 --- a/worlds/soe/__init__.py +++ b/worlds/soe/__init__.py @@ -333,8 +333,7 @@ class SoEWorld(World): switches.extend(('--available-fragments', str(self.available_fragments), '--required-fragments', str(self.required_fragments))) rom_file = get_base_rom_path() - out_base = output_path(output_directory, f'AP_{self.world.seed_name}_P{self.player}_' - f'{self.world.get_file_safe_player_name(self.player)}') + out_base = output_path(output_directory, self.world.get_out_file_name_base(self.player)) out_file = out_base + '.sfc' placement_file = out_base + '.txt' patch_file = out_base + '.apsoe' diff --git a/worlds/v6/__init__.py b/worlds/v6/__init__.py index 38690e5a00..9c5fdbc8a1 100644 --- a/worlds/v6/__init__.py +++ b/worlds/v6/__init__.py @@ -91,6 +91,6 @@ class V6World(World): } } } - filename = f"AP_{self.world.seed_name}_P{self.player}_{self.world.get_file_safe_player_name(self.player)}.apv6" + filename = f"{self.world.get_out_file_name_base(self.player)}.apv6" with open(os.path.join(output_directory, filename), 'w') as f: json.dump(data, f)