From f10163e7d2ba79f512f39ba47d21c40a89774006 Mon Sep 17 00:00:00 2001 From: black-sliver <59490463+black-sliver@users.noreply.github.com> Date: Wed, 29 Sep 2021 09:12:23 +0200 Subject: [PATCH 01/20] SoE: implement logic --- worlds/soe/Logic.py | 52 ++++++++++++++ worlds/soe/Options.py | 7 ++ worlds/soe/__init__.py | 149 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 208 insertions(+) create mode 100644 worlds/soe/Logic.py create mode 100644 worlds/soe/Options.py create mode 100644 worlds/soe/__init__.py diff --git a/worlds/soe/Logic.py b/worlds/soe/Logic.py new file mode 100644 index 00000000..90d52cf1 --- /dev/null +++ b/worlds/soe/Logic.py @@ -0,0 +1,52 @@ +from BaseClasses import MultiWorld +from ..AutoWorld import LogicMixin +from typing import Set +# TODO: import Options +# TODO: Options may preset certain progress steps (i.e. P_ROCK_SKIP), set in generate_early? + +from . import pyevermizer + +# TODO: resolve/flatten/expand rules to get rid of recursion below where possible +# Logic.rules are all rules including locations, excluding those with no progress (i.e. locations that only drop items) +rules = [rule for rule in pyevermizer.get_logic() if len(rule.provides) > 0] +# Logic.items are all items excluding non-progression items and duplicates +item_names: Set[str] = set() +items = [item for item in filter(lambda item: item.progression, pyevermizer.get_items()) + if item.name not in item_names and not item_names.add(item.name)] + + +# when this module is loaded, this mixin will extend BaseClasses.CollectionState +class SecretOfEvermoreLogic(LogicMixin): + def _soe_count(self, progress: int, world: MultiWorld, player: int, max_count: int = 0) -> int: + """ + Returns reached count of one of evermizer's progress steps based on + collected items. i.e. returns 0-3 for P_DE based on items giving CHECK_BOSS,DIAMOND_EYE_DROP + """ + n = 0 + for item in items: + for pvd in item.provides: + if pvd[1] == progress: + if self.has(item.name, player): + n += self.item_count(item.name, player) * pvd[0] + if n >= max_count > 0: + return n + for rule in rules: + for pvd in rule.provides: + if pvd[1] == progress and pvd[0] > 0: + has = True + for req in rule.requires: + if not self._soe_has(req[1], world, player, req[0]): + has = False + break + if has: + n += pvd[0] + if n >= max_count > 0: + return n + return n + + def _soe_has(self, progress: int, world: MultiWorld, player: int, count: int = 1) -> bool: + """ + Returns True if count of an evermizer progress steps are reached based + on collected items. i.e. 2 * P_DE + """ + return self._soe_count(progress, world, player, count) >= count diff --git a/worlds/soe/Options.py b/worlds/soe/Options.py new file mode 100644 index 00000000..57b32bd3 --- /dev/null +++ b/worlds/soe/Options.py @@ -0,0 +1,7 @@ +import typing +from Options import Option + +# TODO: add options + +soe_options: typing.Dict[str, type(Option)] = { +} diff --git a/worlds/soe/__init__.py b/worlds/soe/__init__.py new file mode 100644 index 00000000..9628ba50 --- /dev/null +++ b/worlds/soe/__init__.py @@ -0,0 +1,149 @@ +from .Options import soe_options +from ..AutoWorld import World +from ..generic.Rules import set_rule +from BaseClasses import Region, Location, Entrance, Item +import typing +from . import Logic # load logic mixin + +try: + import pyevermizer # from package +except ImportError: + from . import pyevermizer # as part of the source tree + +""" +In evermizer: + +Items are uniquely defined by a pair of (type, id). +For most items this is their vanilla location (i.e. CHECK_GOURD, number). + +Items have `provides`, which give the actual progression +instead of providing multiple events per item, we iterate through them in Logic.py + e.g. Found any weapon + +Locations have `requires` and `provides`. +Requirements have to be converted to (access) rules for AP + e.g. Chest locked behind having a weapon +Provides could be events, but instead we iterate through the entire logic in Logic.py + e.g. NPC available after fighting a Boss + +Rules are special locations that don't have a physical location +instead of implementing virtual locations and virtual items, we simply use them in Logic.py + e.g. 2DEs+Wheel+Gauge = Rocket + +Rules and Locations live on the same logic tree returned by pyevermizer.get_logic() + +TODO: for balancing we may want to generate Regions (with Entrances) for some +common rules, place the locations in those Regions and shorten the rules. +""" + +GAME_NAME = "Secret of Evermore" +ID_OFF_BASE = 64000 +ID_OFFS: typing.Dict[int,int] = { + pyevermizer.CHECK_ALCHEMY: ID_OFF_BASE + 0, # alchemy 64000..64049 + pyevermizer.CHECK_BOSS: ID_OFF_BASE + 50, # bosses 64050..6499 + pyevermizer.CHECK_GOURD: ID_OFF_BASE + 100, # gourds 64100..64399 + pyevermizer.CHECK_NPC: ID_OFF_BASE + 400, # npc 64400..64499 + # TODO: sniff 64500..64799 +} + + +def _get_locations(): + locs = pyevermizer.get_locations() + for loc in locs: + if loc.type == 3: # TODO: CHECK_GOURD + loc.name = f'{loc.name} #{loc.index}' + return locs + + +def _get_location_ids(): + m = {} + for loc in _get_locations(): + m[loc.name] = ID_OFFS[loc.type] + loc.index + m['Done'] = None + return m + + +def _get_items(): + return pyevermizer.get_items() + + +def _get_item_ids(): + m = {} + for item in _get_items(): + if item.name in m: continue + m[item.name] = ID_OFFS[item.type] + item.index + m['Victory'] = None + return m + + +class SoEWorld(World): + """ + TODO: insert game description here + """ + game: str = GAME_NAME + # options = soe_options + topology_present: bool = True + + item_name_to_id = _get_item_ids() + location_name_to_id = _get_location_ids() + + remote_items: bool = True # False # True only for testing + + def generate_basic(self): + print('SoE: generate_basic') + itempool = [item for item in map(lambda item: self.create_item(item), _get_items())] + self.world.itempool += itempool + self.world.get_location('Done', self.player).place_locked_item(self.create_event('Victory')) + + def create_regions(self): + # TODO: generate *some* regions from locations' requirements + r = Region('Menu', None, 'Menu', self.player, self.world) + r.exits = [Entrance(self.player, 'New Game', r)] + self.world.regions += [r] + + r = Region('Ingame', None, 'Ingame', self.player, self.world) + r.locations = [SoELocation(self.player, loc.name, self.location_name_to_id[loc.name], r) + for loc in _get_locations()] + r.locations.append(SoELocation(self.player, 'Done', None, r)) + self.world.regions += [r] + + self.world.get_entrance('New Game', self.player).connect(self.world.get_region('Ingame', self.player)) + + def create_event(self, event: str) -> Item: + progression = True + return SoEItem(event, progression, None, self.player) + + def create_item(self, item) -> Item: + # TODO: if item is string: look up item by name + return SoEItem(item.name, item.progression, self.item_name_to_id[item.name], self.player) + + def set_rules(self): + print('SoE: set_rules') + self.world.completion_condition[self.player] = lambda state: state.has('Victory', self.player) + # set Done from goal option once we have multiple goals + set_rule(self.world.get_location('Done', self.player), + lambda state: state._soe_has(pyevermizer.P_FINAL_BOSS, self.world, self.player)) + set_rule(self.world.get_entrance('New Game', self.player), lambda state: True) + for loc in _get_locations(): + set_rule(self.world.get_location(loc.name, self.player), self.make_rule(loc.requires)) + + def make_rule(self, requires): + def rule(state): + for count, progress in requires: + if not state._soe_has(progress, self.world, self.player, count): + return False + return True + + return rule + + +class SoEItem(Item): + game: str = GAME_NAME + + +class SoELocation(Location): + game: str = GAME_NAME + + def __init__(self, player: int, name: str, address: typing.Optional[int], parent): + super().__init__(player, name, address, parent) + self.event = not address From 5d0d9c28900739f7d75420e29947cc0af184f94b Mon Sep 17 00:00:00 2001 From: black-sliver <59490463+black-sliver@users.noreply.github.com> Date: Sun, 7 Nov 2021 14:00:13 +0100 Subject: [PATCH 02/20] allow requirements to point to urls --- ModuleUpdate.py | 31 +++++++++++++++++++------------ 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/ModuleUpdate.py b/ModuleUpdate.py index 32cb5c25..77ea2599 100644 --- a/ModuleUpdate.py +++ b/ModuleUpdate.py @@ -35,18 +35,25 @@ def update(yes = False, force = False): if not os.path.exists(path): path = os.path.join(os.path.dirname(__file__), req_file) with open(path) as requirementsfile: - requirements = pkg_resources.parse_requirements(requirementsfile) - for requirement in requirements: - requirement = str(requirement) - try: - pkg_resources.require(requirement) - except pkg_resources.ResolutionError: - if not yes: - import traceback - traceback.print_exc() - input(f'Requirement {requirement} is not satisfied, press enter to install it') - update_command() - return + for line in requirementsfile: + if line.startswith('https://'): + # extract name and version from url + url = line.split(';')[0] + wheel = line.split('/')[-1] + name, version, _ = wheel.split('-',2) + line = f'{name}=={version}' + requirements = pkg_resources.parse_requirements(line) + for requirement in requirements: + requirement = str(requirement) + try: + pkg_resources.require(requirement) + except pkg_resources.ResolutionError: + if not yes: + import traceback + traceback.print_exc() + input(f'Requirement {requirement} is not satisfied, press enter to install it') + update_command() + return if __name__ == "__main__": From 655d14ed6e228f0e331ed8945ac5febfc7c4081b Mon Sep 17 00:00:00 2001 From: black-sliver <59490463+black-sliver@users.noreply.github.com> Date: Sun, 7 Nov 2021 15:38:02 +0100 Subject: [PATCH 03/20] SoE: implement everything else --- worlds/soe/.gitignore | 3 + worlds/soe/Logic.py | 8 +- worlds/soe/Options.py | 151 ++++++++++++++++++++++++- worlds/soe/Patch.py | 52 +++++++++ worlds/soe/__init__.py | 212 +++++++++++++++++++++++++----------- worlds/soe/requirements.txt | 14 +++ 6 files changed, 372 insertions(+), 68 deletions(-) create mode 100644 worlds/soe/.gitignore create mode 100644 worlds/soe/Patch.py create mode 100644 worlds/soe/requirements.txt diff --git a/worlds/soe/.gitignore b/worlds/soe/.gitignore new file mode 100644 index 00000000..e346fd15 --- /dev/null +++ b/worlds/soe/.gitignore @@ -0,0 +1,3 @@ +dumpy.py +pyevermizer +.pyevermizer diff --git a/worlds/soe/Logic.py b/worlds/soe/Logic.py index 90d52cf1..f25f2ada 100644 --- a/worlds/soe/Logic.py +++ b/worlds/soe/Logic.py @@ -1,7 +1,6 @@ from BaseClasses import MultiWorld from ..AutoWorld import LogicMixin from typing import Set -# TODO: import Options # TODO: Options may preset certain progress steps (i.e. P_ROCK_SKIP), set in generate_early? from . import pyevermizer @@ -19,8 +18,8 @@ items = [item for item in filter(lambda item: item.progression, pyevermizer.get_ class SecretOfEvermoreLogic(LogicMixin): def _soe_count(self, progress: int, world: MultiWorld, player: int, max_count: int = 0) -> int: """ - Returns reached count of one of evermizer's progress steps based on - collected items. i.e. returns 0-3 for P_DE based on items giving CHECK_BOSS,DIAMOND_EYE_DROP + Returns reached count of one of evermizer's progress steps based on collected items. + i.e. returns 0-3 for P_DE based on items providing CHECK_BOSS,DIAMOND_EYE_DROP """ n = 0 for item in items: @@ -46,7 +45,6 @@ class SecretOfEvermoreLogic(LogicMixin): def _soe_has(self, progress: int, world: MultiWorld, player: int, count: int = 1) -> bool: """ - Returns True if count of an evermizer progress steps are reached based - on collected items. i.e. 2 * P_DE + Returns True if count of one of evermizer's progress steps is reached based on collected items. i.e. 2 * P_DE """ return self._soe_count(progress, world, player, count) >= count diff --git a/worlds/soe/Options.py b/worlds/soe/Options.py index 57b32bd3..9eddd0e7 100644 --- a/worlds/soe/Options.py +++ b/worlds/soe/Options.py @@ -1,7 +1,154 @@ import typing -from Options import Option +from Options import Option, Range, Choice, Toggle, DefaultOnToggle + + +class EvermizerFlags: + flags: typing.List[str] + + def to_flag(self) -> str: + return self.flags[self.value] + + +class EvermizerFlag: + flag: str + + def to_flag(self) -> str: + return self.flag if self.value != self.default else '' + + +class OffOnChaosChoice(Choice): + option_off = 0 + option_on = 1 + option_chaos = 2 + alias_false = 0 + alias_true = 1 + + +class Difficulty(EvermizerFlags, Choice): + """Changes relative spell cost and stuff""" + displayname = "Difficulty" + option_easy = 0 + option_normal = 1 + option_hard = 2 + option_chaos = 3 # random is reserved pre 0.2 + default = 1 + flags = ['e', 'n', 'h', 'x'] + + +class MoneyModifier(Range): + """Money multiplier in %""" + displayname = "Money Modifier" + range_start = 1 + range_end = 2500 + default = 200 + + +class ExpModifier(Range): + """EXP multiplier for Weapons, Characters and Spells in %""" + displayname = "Exp Modifier" + range_start = 1 + range_end = 2500 + default = 200 + + +class FixSequence(EvermizerFlag, DefaultOnToggle): + """Fix some sequence breaks""" + displayname = "Fix Sequence" + flag = '1' + + +class FixCheats(EvermizerFlag, DefaultOnToggle): + """Fix cheats left in by the devs (not desert skip)""" + displayname = "Fix Cheats" + flag = '2' + + +class FixInfiniteAmmo(EvermizerFlag, Toggle): + """Fix infinite ammo glitch""" + displayname = "Fix Infinite Ammo" + flag = '5' + + +class FixAtlasGlitch(EvermizerFlag, Toggle): + """Fix atlas underflowing stats""" + displayname = "Fix Atlas Glitch" + flag = '6' + + +class FixWingsGlitch(EvermizerFlag, Toggle): + """Fix wings making you invincible in some areas""" + displayname = "Fix Wings Glitch" + flag = '7' + + +class ShorterDialogs(EvermizerFlag, Toggle): + """Cuts some dialogs""" + displayname = "Shorter Dialogs" + flag = '9' + + +class ShortBossRush(EvermizerFlag, Toggle): + """Start boss rush at Magmar, cut HP in half""" + displayname = "Short Boss Rush" + flag = 'f' + + +class Ingredienizer(EvermizerFlags, OffOnChaosChoice): + """Shuffles or randomizes spell ingredients""" + displayname = "Ingredienizer" + default = 1 + flags = ['i', '', 'I'] + + +class Sniffamizer(EvermizerFlags, OffOnChaosChoice): + """Shuffles or randomizes drops in sniff locations""" + displayname = "Sniffamizer" + default = 1 + flags = ['s', '', 'S'] + + +class Callbeadamizer(EvermizerFlags, OffOnChaosChoice): + """Shuffles call bead characters or spells""" + displayname = "Callbeadamizer" + default = 1 + flags = ['c', '', 'C'] + + +class Musicmizer(EvermizerFlag, Toggle): + """Randomize music for some rooms""" + displayname = "Musicmizer" + flag = 'm' + + +class Doggomizer(EvermizerFlags, OffOnChaosChoice): + """On shuffles dog per act, Chaos randomizes dog per screen, Pupdunk gives you Everpupper everywhere""" + displayname = "Doggomizer" + option_pupdunk = 3 + default = 0 + flags = ['', 'd', 'D', 'p'] + + +class TurdoMode(EvermizerFlag, Toggle): + """Replace offensive spells by Turd Balls with varying strength and make weapons weak""" + displayname = "Turdo Mode" + flag = 't' -# TODO: add options soe_options: typing.Dict[str, type(Option)] = { + "difficulty": Difficulty, + "money_modifier": MoneyModifier, + "exp_modifier": ExpModifier, + "fix_sequence": FixSequence, + "fix_cheats": FixCheats, + "fix_infinite_ammo": FixInfiniteAmmo, + "fix_atlas_glitch": FixAtlasGlitch, + "fix_wings_glitch": FixWingsGlitch, + "shorter_dialogs": ShorterDialogs, + "short_boss_rush": ShortBossRush, + "ingredienizer": Ingredienizer, + "sniffamizer": Sniffamizer, + "callbeadamizer": Callbeadamizer, + "musicmizer": Musicmizer, + "doggomizer": Doggomizer, + "turdo_mode": TurdoMode, } diff --git a/worlds/soe/Patch.py b/worlds/soe/Patch.py new file mode 100644 index 00000000..a9c1bade --- /dev/null +++ b/worlds/soe/Patch.py @@ -0,0 +1,52 @@ +import bsdiff4 +import yaml +from typing import Optional +import Utils + + +def read_rom(stream, strip_header=True) -> bytes: + """Reads rom into bytearray and optionally strips off any smc header""" + data = stream.read() + if strip_header and len(data) % 0x400 == 0x200: + return data[0x200:] + return data + + +def generate_yaml(patch: bytes, metadata: Optional[dict] = None) -> bytes: + patch = yaml.dump({"meta": metadata, + "patch": patch, + "game": "Secret of Evermore", + # minimum version of patch system expected for patching to be successful + "compatible_version": 1, + "version": 1}) + return patch.encode(encoding="utf-8-sig") + + +def generate_patch(vanilla_file, randomized_file, metadata: Optional[dict] = None) -> bytes: + with open(vanilla_file, "rb") as f: + vanilla = read_rom(f) + with open(randomized_file, "rb") as f: + randomized = read_rom(f) + if metadata is None: + metadata = {} + patch = bsdiff4.diff(vanilla, randomized) + return generate_yaml(patch, metadata) + + +if __name__ == '__main__': + import argparse + import pathlib + import lzma + parser = argparse.ArgumentParser(description='Apply patch to Secret of Evermore.') + parser.add_argument('patch', type=pathlib.Path, help='path to .absoe file') + args = parser.parse_args() + with open(args.patch, "rb") as f: + data = Utils.parse_yaml(lzma.decompress(f.read()).decode("utf-8-sig")) + if data['game'] != 'Secret of Evermore': + raise RuntimeError('Patch is not for Secret of Evermore') + with open(Utils.get_options()['soe_options']['rom_file'], 'rb') as f: + vanilla_data = read_rom(f) + patched_data = bsdiff4.patch(vanilla_data, data["patch"]) + with open(args.patch.parent / (args.patch.stem + '.sfc'), 'wb') as f: + f.write(patched_data) + diff --git a/worlds/soe/__init__.py b/worlds/soe/__init__.py index 9628ba50..42b74fdb 100644 --- a/worlds/soe/__init__.py +++ b/worlds/soe/__init__.py @@ -1,15 +1,23 @@ -from .Options import soe_options from ..AutoWorld import World -from ..generic.Rules import set_rule +from ..generic.Rules import set_rule, add_item_rule from BaseClasses import Region, Location, Entrance, Item +from Utils import get_options, output_path import typing -from . import Logic # load logic mixin +import lzma +import os +import threading try: import pyevermizer # from package except ImportError: + import traceback + traceback.print_exc() from . import pyevermizer # as part of the source tree +from . import Logic # load logic mixin +from .Options import soe_options +from .Patch import generate_patch + """ In evermizer: @@ -36,99 +44,115 @@ TODO: for balancing we may want to generate Regions (with Entrances) for some common rules, place the locations in those Regions and shorten the rules. """ -GAME_NAME = "Secret of Evermore" -ID_OFF_BASE = 64000 -ID_OFFS: typing.Dict[int,int] = { - pyevermizer.CHECK_ALCHEMY: ID_OFF_BASE + 0, # alchemy 64000..64049 - pyevermizer.CHECK_BOSS: ID_OFF_BASE + 50, # bosses 64050..6499 - pyevermizer.CHECK_GOURD: ID_OFF_BASE + 100, # gourds 64100..64399 - pyevermizer.CHECK_NPC: ID_OFF_BASE + 400, # npc 64400..64499 +_id_base = 64000 +_id_offset: typing.Dict[int, int] = { + pyevermizer.CHECK_ALCHEMY: _id_base + 0, # alchemy 64000..64049 + pyevermizer.CHECK_BOSS: _id_base + 50, # bosses 64050..6499 + pyevermizer.CHECK_GOURD: _id_base + 100, # gourds 64100..64399 + pyevermizer.CHECK_NPC: _id_base + 400, # npc 64400..64499 # TODO: sniff 64500..64799 } - -def _get_locations(): - locs = pyevermizer.get_locations() - for loc in locs: - if loc.type == 3: # TODO: CHECK_GOURD - loc.name = f'{loc.name} #{loc.index}' - return locs +# cache native evermizer items and locations +_items = pyevermizer.get_items() +_locations = pyevermizer.get_locations() +# fix up texts for AP +for _loc in _locations: + if _loc.type == pyevermizer.CHECK_GOURD: + _loc.name = f'{_loc.name} #{_loc.index}' -def _get_location_ids(): - m = {} - for loc in _get_locations(): - m[loc.name] = ID_OFFS[loc.type] + loc.index - m['Done'] = None - return m +def _get_location_mapping() -> typing.Tuple[typing.Dict[str, int], typing.Dict[int, pyevermizer.Location]]: + name_to_id = {} + id_to_raw = {} + for loc in _locations: + apid = _id_offset[loc.type] + loc.index + id_to_raw[apid] = loc + name_to_id[loc.name] = apid + name_to_id['Done'] = None + return name_to_id, id_to_raw -def _get_items(): - return pyevermizer.get_items() - - -def _get_item_ids(): - m = {} - for item in _get_items(): - if item.name in m: continue - m[item.name] = ID_OFFS[item.type] + item.index - m['Victory'] = None - return m +def _get_item_mapping() -> typing.Tuple[typing.Dict[str, int], typing.Dict[int, pyevermizer.Item]]: + name_to_id = {} + id_to_raw = {} + for item in _items: + if item.name in name_to_id: + continue + apid = _id_offset[item.type] + item.index + id_to_raw[apid] = item + name_to_id[item.name] = apid + name_to_id['Victory'] = None + return name_to_id, id_to_raw class SoEWorld(World): """ - TODO: insert game description here + Secret of Evermore is a SNES action RPG. You learn alchemy spells, fight bosses and gather rocket parts to visit a + space station where the final boss must be defeated. """ - game: str = GAME_NAME - # options = soe_options + game: str = "Secret of Evermore" + options = soe_options topology_present: bool = True + remote_items: bool = False # True only for testing + data_version = 0 - item_name_to_id = _get_item_ids() - location_name_to_id = _get_location_ids() + item_name_to_id, item_id_to_raw = _get_item_mapping() + location_name_to_id, location_id_to_raw = _get_location_mapping() - remote_items: bool = True # False # True only for testing + evermizer_seed: int + restrict_item_placement: bool = False # placeholder to force certain item types to certain pools - def generate_basic(self): - print('SoE: generate_basic') - itempool = [item for item in map(lambda item: self.create_item(item), _get_items())] - self.world.itempool += itempool - self.world.get_location('Done', self.player).place_locked_item(self.create_event('Victory')) + def __init__(self, *args, **kwargs): + self.connect_name_available_event = threading.Event() + super(SoEWorld, self).__init__(*args, **kwargs) + + def create_event(self, event: str) -> Item: + progression = True + return SoEItem(event, progression, None, self.player) + + def create_item(self, item: typing.Union[pyevermizer.Item, str], force_progression: bool = False) -> Item: + if type(item) is str: + item = self.item_id_to_raw[self.item_name_to_id[item]] + return SoEItem(item.name, force_progression or item.progression, self.item_name_to_id[item.name], self.player) def create_regions(self): - # TODO: generate *some* regions from locations' requirements + # TODO: generate *some* regions from locations' requirements? r = Region('Menu', None, 'Menu', self.player, self.world) r.exits = [Entrance(self.player, 'New Game', r)] self.world.regions += [r] r = Region('Ingame', None, 'Ingame', self.player, self.world) r.locations = [SoELocation(self.player, loc.name, self.location_name_to_id[loc.name], r) - for loc in _get_locations()] + for loc in _locations] r.locations.append(SoELocation(self.player, 'Done', None, r)) self.world.regions += [r] self.world.get_entrance('New Game', self.player).connect(self.world.get_region('Ingame', self.player)) - def create_event(self, event: str) -> Item: - progression = True - return SoEItem(event, progression, None, self.player) - - def create_item(self, item) -> Item: - # TODO: if item is string: look up item by name - return SoEItem(item.name, item.progression, self.item_name_to_id[item.name], self.player) + def create_items(self): + # clear precollected items since we don't support them yet + if type(self.world.precollected_items) is dict: + self.world.precollected_items[self.player] = [] + # add items to the pool + self.world.itempool += [item for item in + map(lambda item: self.create_item(item, self.restrict_item_placement), _items)] def set_rules(self): - print('SoE: set_rules') self.world.completion_condition[self.player] = lambda state: state.has('Victory', self.player) # set Done from goal option once we have multiple goals set_rule(self.world.get_location('Done', self.player), lambda state: state._soe_has(pyevermizer.P_FINAL_BOSS, self.world, self.player)) set_rule(self.world.get_entrance('New Game', self.player), lambda state: True) - for loc in _get_locations(): - set_rule(self.world.get_location(loc.name, self.player), self.make_rule(loc.requires)) + for loc in _locations: + location = self.world.get_location(loc.name, self.player) + set_rule(location, self.make_rule(loc.requires)) + # limit location pool by item type + if self.restrict_item_placement: + add_item_rule(location, self.make_item_type_limit_rule(loc.type)) - def make_rule(self, requires): - def rule(state): + def make_rule(self, requires: typing.List[typing.Tuple[int]]) -> typing.Callable[[typing.Any], bool]: + def rule(state) -> bool: for count, progress in requires: if not state._soe_has(progress, self.world, self.player, count): return False @@ -136,13 +160,79 @@ class SoEWorld(World): return rule + def make_item_type_limit_rule(self, item_type: int): + return lambda item: item.player != self.player or self.item_id_to_raw[item.code].type == item_type + + def generate_basic(self): + # place Victory event + self.world.get_location('Done', self.player).place_locked_item(self.create_event('Victory')) + # generate stuff for later + self.evermizer_seed = self.world.random.randint(0, 2**16-1) # TODO: make this an option for "full" plando? + + def post_fill(self): + # fix up the advancement property of items so they are displayed correctly in other games + if self.restrict_item_placement: + for location in self.world.get_locations(): + item = location.item + if item.code and item.player == self.player and not self.item_id_to_raw[location.item.code].progression: + item.advancement = False + + def generate_output(self, output_directory: str): + player_name = self.world.get_player_name(self.player) + self.connect_name = player_name[:32] + while len(self.connect_name.encode('utf-8')) > 32: + self.connect_name = self.connect_name[:-1] + self.connect_name_available_event.set() + placement_file = None + out_file = None + try: + money = self.world.money_modifier[self.player].value + exp = self.world.exp_modifier[self.player].value + rom_file = get_options()['soe_options']['rom_file'] + out_base = output_path(output_directory, f'AP_{self.world.seed_name}_P{self.player}_{player_name}') + out_file = out_base + '.sfc' + placement_file = out_base + '.txt' + patch_file = out_base + '.apsoe' + flags = 'l' # spoiler log + for option_name in self.options: + option = getattr(self.world, option_name)[self.player] + if hasattr(option, 'to_flag'): + flags += option.to_flag() + + with open(placement_file, "wb") as f: # generate placement file + for location in filter(lambda l: l.player == self.player, self.world.get_locations()): + item = location.item + if item.code is None: + continue # skip events + loc = self.location_id_to_raw[location.address] + if item.player != self.player: + line = f'{loc.type},{loc.index}:{pyevermizer.CHECK_NONE},{item.code},{item.player}\n' + else: + item = self.item_id_to_raw[item.code] + line = f'{loc.type},{loc.index}:{item.type},{item.index}\n' + f.write(line.encode('utf-8')) + + if (pyevermizer.main(rom_file, out_file, placement_file, self.world.seed_name, self.connect_name, self.evermizer_seed, + flags, money, exp)): + raise RuntimeError() + with lzma.LZMAFile(patch_file, 'wb') as f: + f.write(generate_patch(rom_file, out_file)) + except: + raise + finally: + try: + os.unlink(placement_file) + os.unlink(out_file) + os.unlink(out_file[:-4]+'_SPOILER.log') + except: + pass class SoEItem(Item): - game: str = GAME_NAME + game: str = "Secret of Evermore" class SoELocation(Location): - game: str = GAME_NAME + game: str = "Secret of Evermore" def __init__(self, player: int, name: str, address: typing.Optional[int], parent): super().__init__(player, name, address, parent) diff --git a/worlds/soe/requirements.txt b/worlds/soe/requirements.txt new file mode 100644 index 00000000..c0ac8ae7 --- /dev/null +++ b/worlds/soe/requirements.txt @@ -0,0 +1,14 @@ +https://github.com/black-sliver/pyevermizer/releases/download/v0.39.0/pyevermizer-0.39-cp38-cp38-win_amd64.whl#egg=pyevermizer; sys_platform == 'win32' and platform_machine == 'AMD64' and python_version == '3.8' +https://github.com/black-sliver/pyevermizer/releases/download/v0.39.0/pyevermizer-0.39-cp39-cp39-win_amd64.whl#egg=pyevermizer; sys_platform == 'win32' and platform_machine == 'AMD64' and python_version == '3.9' +https://github.com/black-sliver/pyevermizer/releases/download/v0.39.0/pyevermizer-0.39-cp310-cp310-win_amd64.whl#egg=pyevermizer; sys_platform == 'win32' and platform_machine == 'AMD64' and python_version == '3.10' +https://github.com/black-sliver/pyevermizer/releases/download/v0.39.0/pyevermizer-0.39-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl#egg=pyevermizer; sys_platform == 'linux' and platform_machine == 'x86_64' and python_version == '3.8' +https://github.com/black-sliver/pyevermizer/releases/download/v0.39.0/pyevermizer-0.39-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl#egg=pyevermizer; sys_platform == 'linux' and platform_machine == 'x86_64' and python_version == '3.9' +https://github.com/black-sliver/pyevermizer/releases/download/v0.39.0/pyevermizer-0.39-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl#egg=pyevermizer; sys_platform == 'linux' and platform_machine == 'x86_64' and python_version == '3.10' +https://github.com/black-sliver/pyevermizer/releases/download/v0.39.0/pyevermizer-0.39-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl#egg=pyevermizer; sys_platform == 'linux' and platform_machine == 'aarch64' and python_version == '3.8' +https://github.com/black-sliver/pyevermizer/releases/download/v0.39.0/pyevermizer-0.39-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl#egg=pyevermizer; sys_platform == 'linux' and platform_machine == 'aarch64' and python_version == '3.9' +https://github.com/black-sliver/pyevermizer/releases/download/v0.39.0/pyevermizer-0.39-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl#egg=pyevermizer; sys_platform == 'linux' and platform_machine == 'aarch64' and python_version == '3.10' +https://github.com/black-sliver/pyevermizer/releases/download/v0.39.0/pyevermizer-0.39-cp38-cp38-macosx_10_9_amd64.whl#egg=pyevermizer; sys_platform == 'darwin' and python_version == '3.8' +#https://github.com/black-sliver/pyevermizer/releases/download/v0.39.0/pyevermizer-0.39-cp39-cp39-macosx_10_9_amd64.whl#egg=pyevermizer; sys_platform == 'darwin' and python_version == '3.9' +https://github.com/black-sliver/pyevermizer/releases/download/v0.39.0/pyevermizer-0.39-cp39-cp39-macosx_10_9_universal2.whl#egg=pyevermizer; sys_platform == 'darwin' and python_version == '3.9' +https://github.com/black-sliver/pyevermizer/releases/download/v0.39.0/pyevermizer-0.39-cp310-cp310-macosx_10_9_universal2.whl#egg=pyevermizer; sys_platform == 'darwin' and python_version == '3.10' +bsdiff4>=1.2.1 \ No newline at end of file From 79041bdf2115b96d3161dd8b10b3cf52f277445a Mon Sep 17 00:00:00 2001 From: black-sliver <59490463+black-sliver@users.noreply.github.com> Date: Sun, 7 Nov 2021 15:43:07 +0100 Subject: [PATCH 04/20] update host.yaml for SoE --- host.yaml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/host.yaml b/host.yaml index 66392259..dd88d269 100644 --- a/host.yaml +++ b/host.yaml @@ -98,4 +98,7 @@ minecraft_options: max_heap_size: "2G" oot_options: # File name of the OoT v1.0 ROM - rom_file: "The Legend of Zelda - Ocarina of Time.z64" \ No newline at end of file + rom_file: "The Legend of Zelda - Ocarina of Time.z64" +soe_options: + # File name of the SoE US ROM + rom_file: "Secret of Evermore (USA).sfc" From 449f4ee92fee948a2a0bd47a94e8584539a7eab2 Mon Sep 17 00:00:00 2001 From: black-sliver <59490463+black-sliver@users.noreply.github.com> Date: Sun, 7 Nov 2021 15:56:43 +0100 Subject: [PATCH 05/20] SoE: apply cut slot name to multidata --- worlds/soe/__init__.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/worlds/soe/__init__.py b/worlds/soe/__init__.py index 42b74fdb..7e554356 100644 --- a/worlds/soe/__init__.py +++ b/worlds/soe/__init__.py @@ -102,6 +102,7 @@ class SoEWorld(World): evermizer_seed: int restrict_item_placement: bool = False # placeholder to force certain item types to certain pools + connect_name: str def __init__(self, *args, **kwargs): self.connect_name_available_event = threading.Event() @@ -227,6 +228,16 @@ class SoEWorld(World): except: pass + def modify_multidata(self, multidata: dict): + # wait for self.connect_name to be available. + self.connect_name_available_event.wait() + # we skip in case of error, so that the original error in the output thread is the one that gets raised + if self.connect_name and self.connect_name != self.world.player_name[self.player]: + payload = multidata["connect_names"][self.world.player_name[self.player]] + multidata["connect_names"][self.connect_name] = payload + del (multidata["connect_names"][self.world.player_name[self.player]]) + + class SoEItem(Item): game: str = "Secret of Evermore" From c32f3d6e966ce6e2111e204fbd48d2428d607bcc Mon Sep 17 00:00:00 2001 From: black-sliver <59490463+black-sliver@users.noreply.github.com> Date: Sun, 7 Nov 2021 23:15:35 +0100 Subject: [PATCH 06/20] SoE: data_version bump, disable topology, clean up --- worlds/soe/.gitignore | 2 +- worlds/soe/__init__.py | 21 ++++----------------- 2 files changed, 5 insertions(+), 18 deletions(-) diff --git a/worlds/soe/.gitignore b/worlds/soe/.gitignore index e346fd15..aa3bbd16 100644 --- a/worlds/soe/.gitignore +++ b/worlds/soe/.gitignore @@ -1,3 +1,3 @@ -dumpy.py +dump.py pyevermizer .pyevermizer diff --git a/worlds/soe/__init__.py b/worlds/soe/__init__.py index 7e554356..104f2e86 100644 --- a/worlds/soe/__init__.py +++ b/worlds/soe/__init__.py @@ -93,15 +93,14 @@ class SoEWorld(World): """ game: str = "Secret of Evermore" options = soe_options - topology_present: bool = True - remote_items: bool = False # True only for testing - data_version = 0 + topology_present: bool = False + remote_items: bool = False + data_version = 1 item_name_to_id, item_id_to_raw = _get_item_mapping() location_name_to_id, location_id_to_raw = _get_location_mapping() evermizer_seed: int - restrict_item_placement: bool = False # placeholder to force certain item types to certain pools connect_name: str def __init__(self, *args, **kwargs): @@ -136,8 +135,7 @@ class SoEWorld(World): if type(self.world.precollected_items) is dict: self.world.precollected_items[self.player] = [] # add items to the pool - self.world.itempool += [item for item in - map(lambda item: self.create_item(item, self.restrict_item_placement), _items)] + self.world.itempool += list(map(lambda item: self.create_item(item), _items)) def set_rules(self): self.world.completion_condition[self.player] = lambda state: state.has('Victory', self.player) @@ -148,9 +146,6 @@ class SoEWorld(World): for loc in _locations: location = self.world.get_location(loc.name, self.player) set_rule(location, self.make_rule(loc.requires)) - # limit location pool by item type - if self.restrict_item_placement: - add_item_rule(location, self.make_item_type_limit_rule(loc.type)) def make_rule(self, requires: typing.List[typing.Tuple[int]]) -> typing.Callable[[typing.Any], bool]: def rule(state) -> bool: @@ -170,14 +165,6 @@ class SoEWorld(World): # generate stuff for later self.evermizer_seed = self.world.random.randint(0, 2**16-1) # TODO: make this an option for "full" plando? - def post_fill(self): - # fix up the advancement property of items so they are displayed correctly in other games - if self.restrict_item_placement: - for location in self.world.get_locations(): - item = location.item - if item.code and item.player == self.player and not self.item_id_to_raw[location.item.code].progression: - item.advancement = False - def generate_output(self, output_directory: str): player_name = self.world.get_player_name(self.player) self.connect_name = player_name[:32] From 9ada4df15113fb5ebfa3dd2afa1c852b4b626105 Mon Sep 17 00:00:00 2001 From: black-sliver <59490463+black-sliver@users.noreply.github.com> Date: Wed, 10 Nov 2021 09:17:27 +0100 Subject: [PATCH 07/20] SoE: include base_checksum in apbp --- worlds/soe/Patch.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/worlds/soe/Patch.py b/worlds/soe/Patch.py index a9c1bade..0812c3f1 100644 --- a/worlds/soe/Patch.py +++ b/worlds/soe/Patch.py @@ -4,6 +4,10 @@ from typing import Optional import Utils +USHASH = '6e9c94511d04fac6e0a1e582c170be3a' +current_patch_version = 2 + + def read_rom(stream, strip_header=True) -> bytes: """Reads rom into bytearray and optionally strips off any smc header""" data = stream.read() @@ -18,7 +22,8 @@ def generate_yaml(patch: bytes, metadata: Optional[dict] = None) -> bytes: "game": "Secret of Evermore", # minimum version of patch system expected for patching to be successful "compatible_version": 1, - "version": 1}) + "version": current_patch_version, + "base_checksum": USHASH}) return patch.encode(encoding="utf-8-sig") From 0d6c23e4f29c9a640220aa5aa27761ce611f6168 Mon Sep 17 00:00:00 2001 From: black-sliver <59490463+black-sliver@users.noreply.github.com> Date: Thu, 11 Nov 2021 00:06:30 +0100 Subject: [PATCH 08/20] SoE: add documentation to webhost --- .../assets/gameInfo/en_Secret of Evermore.md | 26 ++++ .../secret-of-evermore/multiworld_en.md | 116 ++++++++++++++++++ .../static/assets/tutorial/tutorials.json | 19 +++ 3 files changed, 161 insertions(+) create mode 100644 WebHostLib/static/assets/gameInfo/en_Secret of Evermore.md create mode 100644 WebHostLib/static/assets/tutorial/secret-of-evermore/multiworld_en.md diff --git a/WebHostLib/static/assets/gameInfo/en_Secret of Evermore.md b/WebHostLib/static/assets/gameInfo/en_Secret of Evermore.md new file mode 100644 index 00000000..f744d7cb --- /dev/null +++ b/WebHostLib/static/assets/gameInfo/en_Secret of Evermore.md @@ -0,0 +1,26 @@ +# Secret of Evermore + +## Where is the settings page? +The player settings page for this game is located here. It contains all the options +you need to configure and export a config file. + +## What does randomization do to this game? +Items which the player would normally acquire throughout the game have been moved around. Logic remains, so the game +is always able to be completed, but because of the item shuffle the player may need to access certain areas before +they would in the vanilla game. + +## What items and locations get shuffled? +All gourds/chests/pots, boss drops and alchemists are shuffled. Additionally you may choose to also shuffle alchemy +ingredients, sniff spot items, call bead spells and the dog. + +## Which items can be in another player's world? +Any of the items which can be shuffled may also be placed into another player's world. It is possible to choose to +limit certain items to your own world. + +## What does another world's item look like in Secret of Evermore? +Secret of Evermore will just display "Sent an Item". Check the client output if you want to know which. + +## When the player receives an item, what happens? +When the player receives an item, a borderless text will appear to show which item was received. You can not receive +items while a script is active, for example in Nobilia Market or during most Boss Fights. You will receive the items +once no more scripts are running. diff --git a/WebHostLib/static/assets/tutorial/secret-of-evermore/multiworld_en.md b/WebHostLib/static/assets/tutorial/secret-of-evermore/multiworld_en.md new file mode 100644 index 00000000..0d6005ec --- /dev/null +++ b/WebHostLib/static/assets/tutorial/secret-of-evermore/multiworld_en.md @@ -0,0 +1,116 @@ +# Secret of Evermore Setup Guide + +## Required Software +- [SNI](https://github.com/alttpo/sni/releases) (included in Archipelago if already installed) +- Hardware or software capable of loading and playing SNES ROM files + - An emulator capable of connecting to SNI with ROM access + - [snes9x-rr win32.zip](https://github.com/gocha/snes9x-rr/releases) + + [socket.dll](http://www.nyo.fr/~skarsnik/socket.dll) + + [connector.lua](https://raw.githubusercontent.com/alttpo/sni/main/lua/Connector.lua) + - or [BizHawk](http://tasvideos.org/BizHawk.html) + - or [bsnes-plus-nwa](https://github.com/black-sliver/bsnes-plus) + - Or SD2SNES, [FXPak Pro](https://krikzz.com/store/home/54-fxpak-pro.html), or other compatible hardware +- Your Secret of Evermore US ROM file, probably named `Secret of Evermore (USA).sfc` + +## Create a Config (.yaml) File + +### What is a config file and why do I need one? +Your config file contains a set of configuration options which provide the generator with information about how +it should generate your game. Each player of a multiworld will provide their own config file. This setup allows +each player to enjoy an experience customized for their taste, and different players in the same multiworld +can all have different options. + +### Where do I get a config file? +The [Player Settings](/games/Secret%20of%20Evermore/player-settings) page on the website allows you to configure your +personal settings and export a config file from them. + +### 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](/mysterycheck) page. + +## Generating a Single-Player Game +Stand-alone "Evermizer" has a way of balancing single-player games, but may not always be on par feature-wise. +Head over to [evermizer.com](https://evermizer.com) if you want to try the official stand-alone, otherwise read below. + +1. Navigate to the [Player Settings](/games/Secret%20of%20Evermore/player-settings) page, configure your options, and + click the "Generate Game" button. +2. You will be presented with a "Seed Info" page. +3. Click the "Create New Room" link. +4. You will be presented with a server page, from which you can download your patch file. +5. Run your patch file through [apbpatch](https://evermizer.com/apbpatch) and load it in your emulator or console. + +## 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 whoever 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 `.apsoe` extension. + +Put your patch file on your desktop or somewhere convenient, open [apbpatch](https://evermizer.com/apbpatch) and +generate your ROM from it. Load the ROM file in your emulator or console. + +### Connect to SNI + +#### With an emulator +Start SNI either from the Archipelago install folder or the stand-alone version. +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 you downloaded above +6. If the script window complains about missing `socket.dll` make sure the DLL is in snes9x or the lua file's directory. + +##### 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 you downloaded above + +##### bsnes-plus-nwa +This should automatically connect to SNI. +If this is its first time launching, you may be prompted to allow it to communicate through the Windows Firewall. + +#### 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 +[here](https://github.com/RedGuyyyy/sd2snes/releases). Other hardware may find helpful information +[on this page](http://usb2snes.com/#supported-platforms). + +1. Copy the ROM file to your SD card. +2. Load the ROM file from the menu. + +### Open the client +Open [ap-soeclient](https://evermizer.com/apclient) in a modern browser. Do not switch tabs, open it in a new window +if you want to use the browser while playing. Do not minimize the window with the client. + +The client should automatically connect to SNI, the "SNES" status should change to green. + +### Connect to the Archipelago Server +Enter `/connect server:port` in the client's command prompt and press enter. You'll find `server:port` on the same page +that had the patch file. + +### Play the game +When the game is loaded but not yet past the intro, the "Game" status is yellow. The intro can be skipped pressing +START. When the client shows both "Game" and "AP" as green, you're ready to play. +Congratulations on successfully joining a multiworld game! + +## Hosting a MultiWorld game +The recommended way to host a game is to use our [hosting service](/generate). 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 website linked above. +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. diff --git a/WebHostLib/static/assets/tutorial/tutorials.json b/WebHostLib/static/assets/tutorial/tutorials.json index bdf15025..50e1964f 100644 --- a/WebHostLib/static/assets/tutorial/tutorials.json +++ b/WebHostLib/static/assets/tutorial/tutorials.json @@ -290,5 +290,24 @@ ] } ] + }, + { + "gameTitle": "Secret of Evermore", + "tutorials": [ + { + "name": "Multiworld Setup Guide", + "description": "A guide to playing Secret of Evermore randomizer. This guide covers single-player, multiworld and related software.", + "files": [ + { + "language": "English", + "filename": "secret-of-evermore/multiworld_en.md", + "link": "secret-of-evermore/multiworld/en", + "authors": [ + "Black Sliver" + ] + } + ] + } + ] } ] From 3ed7b9f60c1f72a0a7f875efacaf878c737a27ca Mon Sep 17 00:00:00 2001 From: black-sliver <59490463+black-sliver@users.noreply.github.com> Date: Thu, 11 Nov 2021 01:03:31 +0100 Subject: [PATCH 09/20] SoE: reword webhost doc Thanks Fainspirit --- .../assets/gameInfo/en_Secret of Evermore.md | 31 ++++++++++--------- .../secret-of-evermore/multiworld_en.md | 6 ++-- 2 files changed, 20 insertions(+), 17 deletions(-) diff --git a/WebHostLib/static/assets/gameInfo/en_Secret of Evermore.md b/WebHostLib/static/assets/gameInfo/en_Secret of Evermore.md index f744d7cb..209a739e 100644 --- a/WebHostLib/static/assets/gameInfo/en_Secret of Evermore.md +++ b/WebHostLib/static/assets/gameInfo/en_Secret of Evermore.md @@ -1,26 +1,29 @@ # Secret of Evermore ## Where is the settings page? -The player settings page for this game is located here. It contains all the options -you need to configure and export a config file. +The player settings page for this game is located here. It contains all options +necessary to configure and export a config file. ## What does randomization do to this game? -Items which the player would normally acquire throughout the game have been moved around. Logic remains, so the game -is always able to be completed, but because of the item shuffle the player may need to access certain areas before -they would in the vanilla game. +Items which would normally be acquired throughout the game have been moved around! Progression logic remains, +so the game is always able to be completed. However, because of the item shuffle, the player may need to access certain +areas before they would in the vanilla game. For example, the Windwalker (flying machine) is accessible as soon as any +weapon is obtained. + +Additional help can be found in the [guide](https://github.com/black-sliver/evermizer/blob/feat-mw/guide.md). ## What items and locations get shuffled? -All gourds/chests/pots, boss drops and alchemists are shuffled. Additionally you may choose to also shuffle alchemy -ingredients, sniff spot items, call bead spells and the dog. +All gourds/chests/pots, boss drops and alchemists are shuffled. Alchemy ingredients, sniff spot items, call bead spells +and the dog can be randomized using yaml options. ## Which items can be in another player's world? -Any of the items which can be shuffled may also be placed into another player's world. It is possible to choose to -limit certain items to your own world. +Any of the items which can be shuffled may also be placed in another player's world. +Specific items can be limited to your own world using plando. ## What does another world's item look like in Secret of Evermore? -Secret of Evermore will just display "Sent an Item". Check the client output if you want to know which. +Secret of Evermore will display "Sent an Item". Check the client output if you want to know which. -## When the player receives an item, what happens? -When the player receives an item, a borderless text will appear to show which item was received. You can not receive -items while a script is active, for example in Nobilia Market or during most Boss Fights. You will receive the items -once no more scripts are running. +## What happens when the player receives an item? +When the player receives an item, a popup will appear to show which item was received. Items won't be recieved while a +script is active such as when visiting Nobilia Market or during most Boss Fights. Once all scripts have ended, items +will be recieved. diff --git a/WebHostLib/static/assets/tutorial/secret-of-evermore/multiworld_en.md b/WebHostLib/static/assets/tutorial/secret-of-evermore/multiworld_en.md index 0d6005ec..f0610ab6 100644 --- a/WebHostLib/static/assets/tutorial/secret-of-evermore/multiworld_en.md +++ b/WebHostLib/static/assets/tutorial/secret-of-evermore/multiworld_en.md @@ -97,9 +97,9 @@ Enter `/connect server:port` in the client's command prompt and press enter. You that had the patch file. ### Play the game -When the game is loaded but not yet past the intro, the "Game" status is yellow. The intro can be skipped pressing -START. When the client shows both "Game" and "AP" as green, you're ready to play. -Congratulations on successfully joining a multiworld game! +When the game is loaded but not yet past the intro cutscene, the "Game" status is yellow. When the client shows "AP" as +green and "Game" as yellow, you're ready to play. The intro can be skipped pressing the START button and "Game" should +change to green. Congratulations on successfully joining a multiworld game! ## Hosting a MultiWorld game The recommended way to host a game is to use our [hosting service](/generate). The process is relatively simple: From 34cfe7d1dfd37141f33d9b2283cfbfd1ea566dd1 Mon Sep 17 00:00:00 2001 From: CaitSith2 Date: Fri, 12 Nov 2021 06:48:23 -0800 Subject: [PATCH 10/20] Fix error in SNIClient --- SNIClient.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/SNIClient.py b/SNIClient.py index b90903e5..e7f4b8ae 100644 --- a/SNIClient.py +++ b/SNIClient.py @@ -156,7 +156,7 @@ class Context(CommonContext): self.killing_player_task = asyncio.create_task(deathlink_kill_player(self)) super(Context, self).on_deathlink(data) - def handle_deathlink_state(self, currently_dead: bool): + async def handle_deathlink_state(self, currently_dead: bool): # in this state we only care about triggering a death send if self.death_state == DeathState.alive: if currently_dead: @@ -935,7 +935,7 @@ async def game_watcher(ctx: Context): gamemode = await snes_read(ctx, WRAM_START + 0x10, 1) if "DeathLink" in ctx.tags and gamemode and ctx.last_death_link + 1 < time.time(): currently_dead = gamemode[0] in DEATH_MODES - ctx.handle_deathlink_state(currently_dead) + await ctx.handle_deathlink_state(currently_dead) gameend = await snes_read(ctx, SAVEDATA_START + 0x443, 1) game_timer = await snes_read(ctx, SAVEDATA_START + 0x42E, 4) @@ -1004,7 +1004,7 @@ async def game_watcher(ctx: Context): gamemode = await snes_read(ctx, WRAM_START + 0x0998, 1) if "DeathLink" in ctx.tags and gamemode and ctx.last_death_link + 1 < time.time(): currently_dead = gamemode[0] in SM_DEATH_MODES - ctx.handle_deathlink_state(currently_dead) + await ctx.handle_deathlink_state(currently_dead) if gamemode is not None and gamemode[0] in SM_ENDGAME_MODES: if not ctx.finished_game: await ctx.send_msgs([{"cmd": "StatusUpdate", "status": ClientStatus.CLIENT_GOAL}]) From 34af785e8770f6625e69610a26c06f4dd5d922d5 Mon Sep 17 00:00:00 2001 From: espeon65536 Date: Fri, 12 Nov 2021 08:20:40 -0600 Subject: [PATCH 11/20] OoT: fixed a bug where free_scarecrow and entrance shuffles could not be rolled together --- worlds/oot/__init__.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/worlds/oot/__init__.py b/worlds/oot/__init__.py index 9a3e4da0..a97a6a0b 100644 --- a/worlds/oot/__init__.py +++ b/worlds/oot/__init__.py @@ -951,6 +951,9 @@ class OOTWorld(World): # Remove all events and checked locations all_state.locations_checked = {loc for loc in all_state.locations_checked if loc.player != self.player} all_state.events = {loc for loc in all_state.events if loc.player != self.player} + # If free_scarecrow give Scarecrow Song + if self.free_scarecrow: + all_state.collect(self.create_item("Scarecrow Song"), event=True) # Invalidate caches all_state.child_reachable_regions[self.player] = set() From cd3f0eabfb5a515401d13e51eef7232dfbd293a2 Mon Sep 17 00:00:00 2001 From: CaitSith2 Date: Fri, 12 Nov 2021 08:31:46 -0800 Subject: [PATCH 12/20] Actually require military science pack for rocket silo on military or higher. --- worlds/factorio/Technologies.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/worlds/factorio/Technologies.py b/worlds/factorio/Technologies.py index 04736a2e..fbf283d3 100644 --- a/worlds/factorio/Technologies.py +++ b/worlds/factorio/Technologies.py @@ -85,7 +85,8 @@ class CustomTechnology(Technology): def __init__(self, origin: Technology, world, allowed_packs: Set[str], player: int): ingredients = origin.ingredients & allowed_packs military_allowed = "military-science-pack" in allowed_packs \ - and (ingredients & {"chemical-science-pack", "production-science-pack", "utility-science-pack"}) + and ((ingredients & {"chemical-science-pack", "production-science-pack", "utility-science-pack"}) + or origin.name == "rocket-silo") self.player = player if origin.name not in world.worlds[player].static_nodes: if military_allowed: From 24596899c9ce5d01b17aa342d2df9662282a1891 Mon Sep 17 00:00:00 2001 From: black-sliver <59490463+black-sliver@users.noreply.github.com> Date: Fri, 12 Nov 2021 21:53:43 +0100 Subject: [PATCH 13/20] SoE doc: change apclient link to http:// for now --- .../static/assets/tutorial/secret-of-evermore/multiworld_en.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WebHostLib/static/assets/tutorial/secret-of-evermore/multiworld_en.md b/WebHostLib/static/assets/tutorial/secret-of-evermore/multiworld_en.md index f0610ab6..0ada1d9f 100644 --- a/WebHostLib/static/assets/tutorial/secret-of-evermore/multiworld_en.md +++ b/WebHostLib/static/assets/tutorial/secret-of-evermore/multiworld_en.md @@ -87,7 +87,7 @@ done so already, please do this now. SD2SNES and FXPak Pro users may download th 2. Load the ROM file from the menu. ### Open the client -Open [ap-soeclient](https://evermizer.com/apclient) in a modern browser. Do not switch tabs, open it in a new window +Open [ap-soeclient](http://evermizer.com/apclient) in a modern browser. Do not switch tabs, open it in a new window if you want to use the browser while playing. Do not minimize the window with the client. The client should automatically connect to SNI, the "SNES" status should change to green. From 49371560212ba479122df7d8bacb5a223895d6bc Mon Sep 17 00:00:00 2001 From: Fabian Dill Date: Fri, 12 Nov 2021 23:43:22 +0100 Subject: [PATCH 14/20] Setup: revamp for SNIClient and Super Metroid --- Generate.py | 2 +- inno_setup_310.iss | 93 ++++++++++++++++++++++++++++++++++++++-------- inno_setup_38.iss | 92 +++++++++++++++++++++++++++++++++++++-------- 3 files changed, 155 insertions(+), 32 deletions(-) diff --git a/Generate.py b/Generate.py index 81ef2989..99631165 100644 --- a/Generate.py +++ b/Generate.py @@ -9,10 +9,10 @@ from collections import Counter import string import ModuleUpdate -import Utils ModuleUpdate.update() +import Utils from worlds.alttp import Options as LttPOptions from worlds.generic import PlandoItem, PlandoConnection from Utils import parse_yaml, version_tuple, __version__, tuplize_version, get_options diff --git a/inno_setup_310.iss b/inno_setup_310.iss index bd73b58a..c89fe0b2 100644 --- a/inno_setup_310.iss +++ b/inno_setup_310.iss @@ -34,7 +34,6 @@ SignTool= signtool LicenseFile= LICENSE WizardStyle= modern SetupLogging=yes -MinVersion=6.3.9200 [Languages] Name: "english"; MessagesFile: "compiler:Default.isl" @@ -51,11 +50,14 @@ Name: "custom"; Description: "Custom installation"; Flags: iscustom [Components] Name: "core"; Description: "Core Files"; Types: full hosting playing custom; Flags: fixed Name: "generator"; Description: "Generator"; Types: full hosting +Name: "generator/sm"; Description: "Super Metroid ROM Setup"; Types: full hosting; ExtraDiskSpaceRequired: 3145728 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 Name: "server"; Description: "Server"; Types: full hosting Name: "client"; Description: "Clients"; Types: full playing -Name: "client/lttp"; Description: "A Link to the Past"; Types: full playing +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 +Name: "client/sni/sm"; Description: "SNI Client - Super Metroid Patch Setup"; Types: full playing Name: "client/factorio"; Description: "Factorio"; Types: full playing Name: "client/minecraft"; Description: "Minecraft"; Types: full playing; ExtraDiskSpaceRequired: 226894278 Name: "client/text"; Description: "Text, to !command and chat"; Types: full playing @@ -64,18 +66,19 @@ Name: "client/text"; Description: "Text, to !command and chat"; Types: full play NAME: "{app}"; Flags: setntfscompression; Permissions: everyone-modify users-modify authusers-modify; [Files] -Source: "{code:GetROMPath}"; DestDir: "{app}"; DestName: "Zelda no Densetsu - Kamigami no Triforce (Japan).sfc"; Flags: external; Components: client/lttp or generator/lttp +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:GetOoTROMPath}"; DestDir: "{app}"; DestName: "The Legend of Zelda - Ocarina of Time.z64"; Flags: external; Components: generator/oot Source: "{#sourcepath}\*"; Excludes: "*.sfc, *.log, data\sprites\alttpr, SNI, EnemizerCLI, Archipelago*.exe"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs -Source: "{#sourcepath}\SNI\*"; Excludes: "*.sfc, *.log"; DestDir: "{app}\SNI"; Flags: ignoreversion recursesubdirs createallsubdirs; Components: client/lttp +Source: "{#sourcepath}\SNI\*"; Excludes: "*.sfc, *.log"; DestDir: "{app}\SNI"; Flags: ignoreversion recursesubdirs createallsubdirs; Components: client/sni Source: "{#sourcepath}\EnemizerCLI\*"; Excludes: "*.sfc, *.log"; DestDir: "{app}\EnemizerCLI"; Flags: ignoreversion recursesubdirs createallsubdirs; Components: generator/lttp Source: "{#sourcepath}\ArchipelagoGenerate.exe"; DestDir: "{app}"; Flags: ignoreversion; Components: generator Source: "{#sourcepath}\ArchipelagoServer.exe"; DestDir: "{app}"; Flags: ignoreversion; Components: server Source: "{#sourcepath}\ArchipelagoFactorioClient.exe"; DestDir: "{app}"; Flags: ignoreversion; Components: client/factorio Source: "{#sourcepath}\ArchipelagoTextClient.exe"; DestDir: "{app}"; Flags: ignoreversion; Components: client/text -Source: "{#sourcepath}\ArchipelagoLttPClient.exe"; DestDir: "{app}"; Flags: ignoreversion; Components: client/lttp -Source: "{#sourcepath}\ArchipelagoLttPAdjuster.exe"; DestDir: "{app}"; Flags: ignoreversion; Components: client/lttp or generator/lttp +Source: "{#sourcepath}\ArchipelagoSNIClient.exe"; DestDir: "{app}"; Flags: ignoreversion; Components: client/sni +Source: "{#sourcepath}\ArchipelagoLttPAdjuster.exe"; DestDir: "{app}"; Flags: ignoreversion; Components: client/sni/lttp or generator/lttp Source: "{#sourcepath}\ArchipelagoMinecraftClient.exe"; DestDir: "{app}"; Flags: ignoreversion; Components: client/minecraft Source: "vc_redist.x64.exe"; DestDir: {tmp}; Flags: deleteafterinstall @@ -85,19 +88,19 @@ Source: "{tmp}\forge-installer.jar"; DestDir: "{app}"; Flags: skipifsourcedoesnt [Icons] Name: "{group}\{#MyAppName} Folder"; Filename: "{app}"; Name: "{group}\{#MyAppName} Server"; Filename: "{app}\{#MyAppExeName}"; Components: server -Name: "{group}\{#MyAppName} Text Client"; Filename: "{app}\ArchipelagoTextClient.exe"; Components: client/lttp -Name: "{group}\{#MyAppName} LttP Client"; Filename: "{app}\ArchipelagoLttPClient.exe"; Components: client/lttp +Name: "{group}\{#MyAppName} Text Client"; Filename: "{app}\ArchipelagoTextClient.exe"; Components: client/text +Name: "{group}\{#MyAppName} SNI Client"; Filename: "{app}\ArchipelagoLttPClient.exe"; Components: client/sni Name: "{group}\{#MyAppName} Factorio Client"; Filename: "{app}\ArchipelagoFactorioClient.exe"; Components: client/factorio Name: "{group}\{#MyAppName} Minecraft Client"; Filename: "{app}\ArchipelagoMinecraftClient.exe"; Components: client/minecraft Name: "{commondesktop}\{#MyAppName} Folder"; Filename: "{app}"; Tasks: desktopicon Name: "{commondesktop}\{#MyAppName} Server"; Filename: "{app}\{#MyAppExeName}"; Tasks: desktopicon; Components: server -Name: "{commondesktop}\{#MyAppName} LttP Client"; Filename: "{app}\ArchipelagoLttPClient.exe"; Tasks: desktopicon; Components: client/lttp +Name: "{commondesktop}\{#MyAppName} SNI Client"; Filename: "{app}\ArchipelagoSNIClient.exe"; Tasks: desktopicon; Components: client/sni Name: "{commondesktop}\{#MyAppName} Factorio Client"; Filename: "{app}\ArchipelagoFactorioClient.exe"; Tasks: desktopicon; Components: client/factorio [Run] Filename: "{tmp}\vc_redist.x64.exe"; Parameters: "/passive /norestart"; Check: IsVCRedist64BitNeeded; StatusMsg: "Installing VC++ redistributable..." -Filename: "{app}\ArchipelagoLttPAdjuster"; Parameters: "--update_sprites"; StatusMsg: "Updating Sprite Library..."; Components: client/lttp or generator/lttp +Filename: "{app}\ArchipelagoLttPAdjuster"; Parameters: "--update_sprites"; StatusMsg: "Updating Sprite Library..."; Components: client/sni/lttp or generator/lttp Filename: "{app}\jre8\bin\java.exe"; Parameters: "-jar ""{app}\forge-installer.jar"" --installServer ""{app}\Minecraft Forge server"""; Flags: runhidden; Check: IsForgeNeeded(); StatusMsg: "Installing Forge Server..."; Components: client/minecraft [UninstallDelete] @@ -105,10 +108,15 @@ Type: dirifempty; Name: "{app}" [Registry] -Root: HKCR; Subkey: ".apbp"; ValueData: "{#MyAppName}patch"; Flags: uninsdeletevalue; ValueType: string; ValueName: ""; Components: client/lttp -Root: HKCR; Subkey: "{#MyAppName}patch"; ValueData: "Archipelago Binary Patch"; Flags: uninsdeletekey; ValueType: string; ValueName: ""; Components: client/lttp -Root: HKCR; Subkey: "{#MyAppName}patch\DefaultIcon"; ValueData: "{app}\ArchipelagoLttPClient.exe,0"; ValueType: string; ValueName: ""; Components: client/lttp -Root: HKCR; Subkey: "{#MyAppName}patch\shell\open\command"; ValueData: """{app}\ArchipelagoLttPClient.exe"" ""%1"""; ValueType: string; ValueName: ""; Components: client/lttp +Root: HKCR; Subkey: ".apbp"; ValueData: "{#MyAppName}patch"; Flags: uninsdeletevalue; ValueType: string; ValueName: ""; Components: client/sni +Root: HKCR; Subkey: "{#MyAppName}patch"; ValueData: "Archipelago Binary Patch"; Flags: uninsdeletekey; ValueType: string; ValueName: ""; Components: client/sni +Root: HKCR; Subkey: "{#MyAppName}patch\DefaultIcon"; ValueData: "{app}\ArchipelagoSNIClient.exe,0"; ValueType: string; ValueName: ""; Components: client/sni +Root: HKCR; Subkey: "{#MyAppName}patch\shell\open\command"; ValueData: """{app}\ArchipelagoSNIClient.exe"" ""%1"""; ValueType: string; ValueName: ""; Components: client/sni + +Root: HKCR; Subkey: ".apm3"; ValueData: "{#MyAppName}smpatch"; Flags: uninsdeletevalue; ValueType: string; ValueName: ""; Components: client/sni +Root: HKCR; Subkey: "{#MyAppName}patch"; ValueData: "Archipelago Super Metroid Patch"; Flags: uninsdeletekey; ValueType: string; ValueName: ""; Components: client/sni +Root: HKCR; Subkey: "{#MyAppName}patch\DefaultIcon"; ValueData: "{app}\ArchipelagoSNIClient.exe,0"; ValueType: string; ValueName: ""; Components: client/sni +Root: HKCR; Subkey: "{#MyAppName}patch\shell\open\command"; ValueData: """{app}\ArchipelagoSNIClient.exe"" ""%1"""; ValueType: string; ValueName: ""; Components: client/sni Root: HKCR; Subkey: ".apmc"; ValueData: "{#MyAppName}mcdata"; Flags: uninsdeletevalue; ValueType: string; ValueName: ""; Components: client/minecraft Root: HKCR; Subkey: "{#MyAppName}mcdata"; ValueData: "Archipelago Minecraft Data"; Flags: uninsdeletekey; ValueType: string; ValueName: ""; Components: client/minecraft @@ -190,11 +198,17 @@ begin ZipFile.Items, SHCONTCH_NOPROGRESSBOX or SHCONTCH_RESPONDYESTOALL); end; -var ROMFilePage: TInputFileWizardPage; var R : longint; + var rom: string; +var ROMFilePage: TInputFileWizardPage; + +var smrom: string; +var SMRomFilePage: TInputFileWizardPage; + var ootrom: string; var OoTROMFilePage: TInputFileWizardPage; + var MinecraftDownloadPage: TDownloadWizardPage; procedure AddRomPage(); @@ -225,6 +239,34 @@ begin '.sfc'); end; +procedure AddSMRomPage(); +begin + smrom := FileSearch('Super Metroid (JU).sfc', WizardDirValue()); + if Length(smrom) > 0 then + begin + log('existing SM ROM found'); + log(IntToStr(CompareStr(GetMD5OfFile(smrom), '21f3e98df4780ee1c667b84e57d88675'))); + if CompareStr(GetMD5OfFile(smrom), '21f3e98df4780ee1c667b84e57d88675') = 0 then + begin + log('existing SM ROM verified'); + exit; + end; + log('existing SM ROM failed verification'); + end; + smrom := '' + SMROMFilePage := + CreateInputFilePage( + wpSelectComponents, + 'Select ROM File', + 'Where is your Super Metroid located?', + 'Select the file, then click Next.'); + + SMROMFilePage.Add( + 'Location of Super Metroid ROM file:', + 'SNES ROM files|*.sfc|All files|*.*', + '.sfc'); +end; + procedure AddMinecraftDownloads(); begin MinecraftDownloadPage := CreateDownloadPage(SetupMessage(msgWizardPreparing), SetupMessage(msgPreparingDesc), @OnDownloadMinecraftProgress); @@ -295,6 +337,7 @@ procedure InitializeWizard(); begin AddOoTRomPage(); AddRomPage(); + AddSMRomPage(); AddMinecraftDownloads(); end; @@ -303,7 +346,9 @@ function ShouldSkipPage(PageID: Integer): Boolean; begin Result := False; if (assigned(ROMFilePage)) and (PageID = ROMFilePage.ID) then - Result := not (WizardIsComponentSelected('client/lttp') or WizardIsComponentSelected('generator/lttp')); + Result := not (WizardIsComponentSelected('client/sni/lttp') or WizardIsComponentSelected('generator/lttp')); + if (assigned(SMROMFilePage)) and (PageID = SMROMFilePage.ID) then + Result := not (WizardIsComponentSelected('client/sni/sm') or WizardIsComponentSelected('generator/sm')); if (assigned(OoTROMFilePage)) and (PageID = OoTROMFilePage.ID) then Result := not (WizardIsComponentSelected('generator/oot')); end; @@ -324,6 +369,22 @@ begin Result := ''; end; +function GetSMROMPath(Param: string): string; +begin + if Length(smrom) > 0 then + Result := smrom + else if Assigned(SMRomFilePage) then + begin + R := CompareStr(GetMD5OfFile(SMROMFilePage.Values[0]), '21f3e98df4780ee1c667b84e57d88675') + if R <> 0 then + MsgBox('Super Metroid ROM validation failed. Very likely wrong file.', mbInformation, MB_OK); + + Result := SMROMFilePage.Values[0] + end + else + Result := ''; + end; + function GetOoTROMPath(Param: string): string; begin if Length(ootrom) > 0 then diff --git a/inno_setup_38.iss b/inno_setup_38.iss index 49caf36e..bfca68f8 100644 --- a/inno_setup_38.iss +++ b/inno_setup_38.iss @@ -50,11 +50,14 @@ Name: "custom"; Description: "Custom installation"; Flags: iscustom [Components] Name: "core"; Description: "Core Files"; Types: full hosting playing custom; Flags: fixed Name: "generator"; Description: "Generator"; Types: full hosting +Name: "generator/sm"; Description: "Super Metroid ROM Setup"; Types: full hosting; ExtraDiskSpaceRequired: 3145728 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 Name: "server"; Description: "Server"; Types: full hosting Name: "client"; Description: "Clients"; Types: full playing -Name: "client/lttp"; Description: "A Link to the Past"; Types: full playing +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 +Name: "client/sni/sm"; Description: "SNI Client - Super Metroid Patch Setup"; Types: full playing Name: "client/factorio"; Description: "Factorio"; Types: full playing Name: "client/minecraft"; Description: "Minecraft"; Types: full playing; ExtraDiskSpaceRequired: 226894278 Name: "client/text"; Description: "Text, to !command and chat"; Types: full playing @@ -63,18 +66,19 @@ Name: "client/text"; Description: "Text, to !command and chat"; Types: full play NAME: "{app}"; Flags: setntfscompression; Permissions: everyone-modify users-modify authusers-modify; [Files] -Source: "{code:GetROMPath}"; DestDir: "{app}"; DestName: "Zelda no Densetsu - Kamigami no Triforce (Japan).sfc"; Flags: external; Components: client/lttp or generator/lttp +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:GetOoTROMPath}"; DestDir: "{app}"; DestName: "The Legend of Zelda - Ocarina of Time.z64"; Flags: external; Components: generator/oot Source: "{#sourcepath}\*"; Excludes: "*.sfc, *.log, data\sprites\alttpr, SNI, EnemizerCLI, Archipelago*.exe"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs -Source: "{#sourcepath}\SNI\*"; Excludes: "*.sfc, *.log"; DestDir: "{app}\SNI"; Flags: ignoreversion recursesubdirs createallsubdirs; Components: client/lttp +Source: "{#sourcepath}\SNI\*"; Excludes: "*.sfc, *.log"; DestDir: "{app}\SNI"; Flags: ignoreversion recursesubdirs createallsubdirs; Components: client/sni Source: "{#sourcepath}\EnemizerCLI\*"; Excludes: "*.sfc, *.log"; DestDir: "{app}\EnemizerCLI"; Flags: ignoreversion recursesubdirs createallsubdirs; Components: generator/lttp Source: "{#sourcepath}\ArchipelagoGenerate.exe"; DestDir: "{app}"; Flags: ignoreversion; Components: generator Source: "{#sourcepath}\ArchipelagoServer.exe"; DestDir: "{app}"; Flags: ignoreversion; Components: server Source: "{#sourcepath}\ArchipelagoFactorioClient.exe"; DestDir: "{app}"; Flags: ignoreversion; Components: client/factorio Source: "{#sourcepath}\ArchipelagoTextClient.exe"; DestDir: "{app}"; Flags: ignoreversion; Components: client/text -Source: "{#sourcepath}\ArchipelagoLttPClient.exe"; DestDir: "{app}"; Flags: ignoreversion; Components: client/lttp -Source: "{#sourcepath}\ArchipelagoLttPAdjuster.exe"; DestDir: "{app}"; Flags: ignoreversion; Components: client/lttp or generator/lttp +Source: "{#sourcepath}\ArchipelagoSNIClient.exe"; DestDir: "{app}"; Flags: ignoreversion; Components: client/sni +Source: "{#sourcepath}\ArchipelagoLttPAdjuster.exe"; DestDir: "{app}"; Flags: ignoreversion; Components: client/sni/lttp or generator/lttp Source: "{#sourcepath}\ArchipelagoMinecraftClient.exe"; DestDir: "{app}"; Flags: ignoreversion; Components: client/minecraft Source: "vc_redist.x64.exe"; DestDir: {tmp}; Flags: deleteafterinstall @@ -84,19 +88,19 @@ Source: "{tmp}\forge-installer.jar"; DestDir: "{app}"; Flags: skipifsourcedoesnt [Icons] Name: "{group}\{#MyAppName} Folder"; Filename: "{app}"; Name: "{group}\{#MyAppName} Server"; Filename: "{app}\{#MyAppExeName}"; Components: server -Name: "{group}\{#MyAppName} Text Client"; Filename: "{app}\ArchipelagoTextClient.exe"; Components: client/lttp -Name: "{group}\{#MyAppName} LttP Client"; Filename: "{app}\ArchipelagoLttPClient.exe"; Components: client/lttp +Name: "{group}\{#MyAppName} Text Client"; Filename: "{app}\ArchipelagoTextClient.exe"; Components: client/text +Name: "{group}\{#MyAppName} SNI Client"; Filename: "{app}\ArchipelagoLttPClient.exe"; Components: client/sni Name: "{group}\{#MyAppName} Factorio Client"; Filename: "{app}\ArchipelagoFactorioClient.exe"; Components: client/factorio Name: "{group}\{#MyAppName} Minecraft Client"; Filename: "{app}\ArchipelagoMinecraftClient.exe"; Components: client/minecraft Name: "{commondesktop}\{#MyAppName} Folder"; Filename: "{app}"; Tasks: desktopicon Name: "{commondesktop}\{#MyAppName} Server"; Filename: "{app}\{#MyAppExeName}"; Tasks: desktopicon; Components: server -Name: "{commondesktop}\{#MyAppName} LttP Client"; Filename: "{app}\ArchipelagoLttPClient.exe"; Tasks: desktopicon; Components: client/lttp +Name: "{commondesktop}\{#MyAppName} SNI Client"; Filename: "{app}\ArchipelagoSNIClient.exe"; Tasks: desktopicon; Components: client/sni Name: "{commondesktop}\{#MyAppName} Factorio Client"; Filename: "{app}\ArchipelagoFactorioClient.exe"; Tasks: desktopicon; Components: client/factorio [Run] Filename: "{tmp}\vc_redist.x64.exe"; Parameters: "/passive /norestart"; Check: IsVCRedist64BitNeeded; StatusMsg: "Installing VC++ redistributable..." -Filename: "{app}\ArchipelagoLttPAdjuster"; Parameters: "--update_sprites"; StatusMsg: "Updating Sprite Library..."; Components: client/lttp or generator/lttp +Filename: "{app}\ArchipelagoLttPAdjuster"; Parameters: "--update_sprites"; StatusMsg: "Updating Sprite Library..."; Components: client/sni/lttp or generator/lttp Filename: "{app}\jre8\bin\java.exe"; Parameters: "-jar ""{app}\forge-installer.jar"" --installServer ""{app}\Minecraft Forge server"""; Flags: runhidden; Check: IsForgeNeeded(); StatusMsg: "Installing Forge Server..."; Components: client/minecraft [UninstallDelete] @@ -104,10 +108,15 @@ Type: dirifempty; Name: "{app}" [Registry] -Root: HKCR; Subkey: ".apbp"; ValueData: "{#MyAppName}patch"; Flags: uninsdeletevalue; ValueType: string; ValueName: ""; Components: client/lttp -Root: HKCR; Subkey: "{#MyAppName}patch"; ValueData: "Archipelago Binary Patch"; Flags: uninsdeletekey; ValueType: string; ValueName: ""; Components: client/lttp -Root: HKCR; Subkey: "{#MyAppName}patch\DefaultIcon"; ValueData: "{app}\ArchipelagoLttPClient.exe,0"; ValueType: string; ValueName: ""; Components: client/lttp -Root: HKCR; Subkey: "{#MyAppName}patch\shell\open\command"; ValueData: """{app}\ArchipelagoLttPClient.exe"" ""%1"""; ValueType: string; ValueName: ""; Components: client/lttp +Root: HKCR; Subkey: ".apbp"; ValueData: "{#MyAppName}patch"; Flags: uninsdeletevalue; ValueType: string; ValueName: ""; Components: client/sni +Root: HKCR; Subkey: "{#MyAppName}patch"; ValueData: "Archipelago Binary Patch"; Flags: uninsdeletekey; ValueType: string; ValueName: ""; Components: client/sni +Root: HKCR; Subkey: "{#MyAppName}patch\DefaultIcon"; ValueData: "{app}\ArchipelagoSNIClient.exe,0"; ValueType: string; ValueName: ""; Components: client/sni +Root: HKCR; Subkey: "{#MyAppName}patch\shell\open\command"; ValueData: """{app}\ArchipelagoSNIClient.exe"" ""%1"""; ValueType: string; ValueName: ""; Components: client/sni + +Root: HKCR; Subkey: ".apm3"; ValueData: "{#MyAppName}smpatch"; Flags: uninsdeletevalue; ValueType: string; ValueName: ""; Components: client/sni +Root: HKCR; Subkey: "{#MyAppName}smpatch"; ValueData: "Archipelago Super Metroid Patch"; Flags: uninsdeletekey; ValueType: string; ValueName: ""; Components: client/sni +Root: HKCR; Subkey: "{#MyAppName}smpatch\DefaultIcon"; ValueData: "{app}\ArchipelagoSNIClient.exe,0"; ValueType: string; ValueName: ""; Components: client/sni +Root: HKCR; Subkey: "{#MyAppName}smpatch\shell\open\command"; ValueData: """{app}\ArchipelagoSNIClient.exe"" ""%1"""; ValueType: string; ValueName: ""; Components: client/sni Root: HKCR; Subkey: ".apmc"; ValueData: "{#MyAppName}mcdata"; Flags: uninsdeletevalue; ValueType: string; ValueName: ""; Components: client/minecraft Root: HKCR; Subkey: "{#MyAppName}mcdata"; ValueData: "Archipelago Minecraft Data"; Flags: uninsdeletekey; ValueType: string; ValueName: ""; Components: client/minecraft @@ -189,11 +198,17 @@ begin ZipFile.Items, SHCONTCH_NOPROGRESSBOX or SHCONTCH_RESPONDYESTOALL); end; -var ROMFilePage: TInputFileWizardPage; var R : longint; + var rom: string; +var ROMFilePage: TInputFileWizardPage; + +var smrom: string; +var SMRomFilePage: TInputFileWizardPage; + var ootrom: string; var OoTROMFilePage: TInputFileWizardPage; + var MinecraftDownloadPage: TDownloadWizardPage; procedure AddRomPage(); @@ -224,6 +239,34 @@ begin '.sfc'); end; +procedure AddSMRomPage(); +begin + smrom := FileSearch('Super Metroid (JU).sfc', WizardDirValue()); + if Length(smrom) > 0 then + begin + log('existing SM ROM found'); + log(IntToStr(CompareStr(GetMD5OfFile(smrom), '21f3e98df4780ee1c667b84e57d88675'))); + if CompareStr(GetMD5OfFile(smrom), '21f3e98df4780ee1c667b84e57d88675') = 0 then + begin + log('existing SM ROM verified'); + exit; + end; + log('existing SM ROM failed verification'); + end; + smrom := '' + SMROMFilePage := + CreateInputFilePage( + wpSelectComponents, + 'Select ROM File', + 'Where is your Super Metroid located?', + 'Select the file, then click Next.'); + + SMROMFilePage.Add( + 'Location of Super Metroid ROM file:', + 'SNES ROM files|*.sfc|All files|*.*', + '.sfc'); +end; + procedure AddMinecraftDownloads(); begin MinecraftDownloadPage := CreateDownloadPage(SetupMessage(msgWizardPreparing), SetupMessage(msgPreparingDesc), @OnDownloadMinecraftProgress); @@ -294,6 +337,7 @@ procedure InitializeWizard(); begin AddOoTRomPage(); AddRomPage(); + AddSMRomPage(); AddMinecraftDownloads(); end; @@ -302,7 +346,9 @@ function ShouldSkipPage(PageID: Integer): Boolean; begin Result := False; if (assigned(ROMFilePage)) and (PageID = ROMFilePage.ID) then - Result := not (WizardIsComponentSelected('client/lttp') or WizardIsComponentSelected('generator/lttp')); + Result := not (WizardIsComponentSelected('client/sni/lttp') or WizardIsComponentSelected('generator/lttp')); + if (assigned(SMROMFilePage)) and (PageID = SMROMFilePage.ID) then + Result := not (WizardIsComponentSelected('client/sni/sm') or WizardIsComponentSelected('generator/sm')); if (assigned(OoTROMFilePage)) and (PageID = OoTROMFilePage.ID) then Result := not (WizardIsComponentSelected('generator/oot')); end; @@ -323,6 +369,22 @@ begin Result := ''; end; +function GetSMROMPath(Param: string): string; +begin + if Length(smrom) > 0 then + Result := smrom + else if Assigned(SMRomFilePage) then + begin + R := CompareStr(GetMD5OfFile(SMROMFilePage.Values[0]), '21f3e98df4780ee1c667b84e57d88675') + if R <> 0 then + MsgBox('Super Metroid ROM validation failed. Very likely wrong file.', mbInformation, MB_OK); + + Result := SMROMFilePage.Values[0] + end + else + Result := ''; + end; + function GetOoTROMPath(Param: string): string; begin if Length(ootrom) > 0 then From 83a40d43946f98bf6c0226f65bfa7e6ea001dfe9 Mon Sep 17 00:00:00 2001 From: Fabian Dill Date: Fri, 12 Nov 2021 23:47:52 +0100 Subject: [PATCH 15/20] Setup: delete LttPClient --- inno_setup_310.iss | 5 ++++- inno_setup_38.iss | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/inno_setup_310.iss b/inno_setup_310.iss index c89fe0b2..c5be9691 100644 --- a/inno_setup_310.iss +++ b/inno_setup_310.iss @@ -89,7 +89,7 @@ Source: "{tmp}\forge-installer.jar"; DestDir: "{app}"; Flags: skipifsourcedoesnt Name: "{group}\{#MyAppName} Folder"; Filename: "{app}"; Name: "{group}\{#MyAppName} Server"; Filename: "{app}\{#MyAppExeName}"; Components: server Name: "{group}\{#MyAppName} Text Client"; Filename: "{app}\ArchipelagoTextClient.exe"; Components: client/text -Name: "{group}\{#MyAppName} SNI Client"; Filename: "{app}\ArchipelagoLttPClient.exe"; Components: client/sni +Name: "{group}\{#MyAppName} SNI Client"; Filename: "{app}\ArchipelagoSNIClient.exe"; Components: client/sni Name: "{group}\{#MyAppName} Factorio Client"; Filename: "{app}\ArchipelagoFactorioClient.exe"; Components: client/factorio Name: "{group}\{#MyAppName} Minecraft Client"; Filename: "{app}\ArchipelagoMinecraftClient.exe"; Components: client/minecraft Name: "{commondesktop}\{#MyAppName} Folder"; Filename: "{app}"; Tasks: desktopicon @@ -106,6 +106,9 @@ Filename: "{app}\jre8\bin\java.exe"; Parameters: "-jar ""{app}\forge-installer.j [UninstallDelete] Type: dirifempty; Name: "{app}" +[InstallDelete] +Type: files; Name: "{app}\ArchipelagoLttPClient.exe" + [Registry] Root: HKCR; Subkey: ".apbp"; ValueData: "{#MyAppName}patch"; Flags: uninsdeletevalue; ValueType: string; ValueName: ""; Components: client/sni diff --git a/inno_setup_38.iss b/inno_setup_38.iss index bfca68f8..f5fd9c53 100644 --- a/inno_setup_38.iss +++ b/inno_setup_38.iss @@ -89,7 +89,7 @@ Source: "{tmp}\forge-installer.jar"; DestDir: "{app}"; Flags: skipifsourcedoesnt Name: "{group}\{#MyAppName} Folder"; Filename: "{app}"; Name: "{group}\{#MyAppName} Server"; Filename: "{app}\{#MyAppExeName}"; Components: server Name: "{group}\{#MyAppName} Text Client"; Filename: "{app}\ArchipelagoTextClient.exe"; Components: client/text -Name: "{group}\{#MyAppName} SNI Client"; Filename: "{app}\ArchipelagoLttPClient.exe"; Components: client/sni +Name: "{group}\{#MyAppName} SNI Client"; Filename: "{app}\ArchipelagoSNIClient.exe"; Components: client/sni Name: "{group}\{#MyAppName} Factorio Client"; Filename: "{app}\ArchipelagoFactorioClient.exe"; Components: client/factorio Name: "{group}\{#MyAppName} Minecraft Client"; Filename: "{app}\ArchipelagoMinecraftClient.exe"; Components: client/minecraft Name: "{commondesktop}\{#MyAppName} Folder"; Filename: "{app}"; Tasks: desktopicon @@ -106,6 +106,9 @@ Filename: "{app}\jre8\bin\java.exe"; Parameters: "-jar ""{app}\forge-installer.j [UninstallDelete] Type: dirifempty; Name: "{app}" +[InstallDelete] +Type: files; Name: "{app}\ArchipelagoLttPClient.exe" + [Registry] Root: HKCR; Subkey: ".apbp"; ValueData: "{#MyAppName}patch"; Flags: uninsdeletevalue; ValueType: string; ValueName: ""; Components: client/sni From 62e0e0bb555bb4a7219b654fc78c02fa3da8b609 Mon Sep 17 00:00:00 2001 From: black-sliver <59490463+black-sliver@users.noreply.github.com> Date: Sat, 13 Nov 2021 00:42:40 +0100 Subject: [PATCH 16/20] SoE: update pyevermizer to 0.39.1 * Fix softlock when talking to drain guy again * Disable receiving items while screen is fading (avoids crashes while closing fullscreen windows) --- worlds/soe/requirements.txt | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/worlds/soe/requirements.txt b/worlds/soe/requirements.txt index c0ac8ae7..f37a4a44 100644 --- a/worlds/soe/requirements.txt +++ b/worlds/soe/requirements.txt @@ -1,14 +1,14 @@ -https://github.com/black-sliver/pyevermizer/releases/download/v0.39.0/pyevermizer-0.39-cp38-cp38-win_amd64.whl#egg=pyevermizer; sys_platform == 'win32' and platform_machine == 'AMD64' and python_version == '3.8' -https://github.com/black-sliver/pyevermizer/releases/download/v0.39.0/pyevermizer-0.39-cp39-cp39-win_amd64.whl#egg=pyevermizer; sys_platform == 'win32' and platform_machine == 'AMD64' and python_version == '3.9' -https://github.com/black-sliver/pyevermizer/releases/download/v0.39.0/pyevermizer-0.39-cp310-cp310-win_amd64.whl#egg=pyevermizer; sys_platform == 'win32' and platform_machine == 'AMD64' and python_version == '3.10' -https://github.com/black-sliver/pyevermizer/releases/download/v0.39.0/pyevermizer-0.39-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl#egg=pyevermizer; sys_platform == 'linux' and platform_machine == 'x86_64' and python_version == '3.8' -https://github.com/black-sliver/pyevermizer/releases/download/v0.39.0/pyevermizer-0.39-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl#egg=pyevermizer; sys_platform == 'linux' and platform_machine == 'x86_64' and python_version == '3.9' -https://github.com/black-sliver/pyevermizer/releases/download/v0.39.0/pyevermizer-0.39-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl#egg=pyevermizer; sys_platform == 'linux' and platform_machine == 'x86_64' and python_version == '3.10' -https://github.com/black-sliver/pyevermizer/releases/download/v0.39.0/pyevermizer-0.39-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl#egg=pyevermizer; sys_platform == 'linux' and platform_machine == 'aarch64' and python_version == '3.8' -https://github.com/black-sliver/pyevermizer/releases/download/v0.39.0/pyevermizer-0.39-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl#egg=pyevermizer; sys_platform == 'linux' and platform_machine == 'aarch64' and python_version == '3.9' -https://github.com/black-sliver/pyevermizer/releases/download/v0.39.0/pyevermizer-0.39-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl#egg=pyevermizer; sys_platform == 'linux' and platform_machine == 'aarch64' and python_version == '3.10' -https://github.com/black-sliver/pyevermizer/releases/download/v0.39.0/pyevermizer-0.39-cp38-cp38-macosx_10_9_amd64.whl#egg=pyevermizer; sys_platform == 'darwin' and python_version == '3.8' -#https://github.com/black-sliver/pyevermizer/releases/download/v0.39.0/pyevermizer-0.39-cp39-cp39-macosx_10_9_amd64.whl#egg=pyevermizer; sys_platform == 'darwin' and python_version == '3.9' -https://github.com/black-sliver/pyevermizer/releases/download/v0.39.0/pyevermizer-0.39-cp39-cp39-macosx_10_9_universal2.whl#egg=pyevermizer; sys_platform == 'darwin' and python_version == '3.9' -https://github.com/black-sliver/pyevermizer/releases/download/v0.39.0/pyevermizer-0.39-cp310-cp310-macosx_10_9_universal2.whl#egg=pyevermizer; sys_platform == 'darwin' and python_version == '3.10' +https://github.com/black-sliver/pyevermizer/releases/download/v0.39.1/pyevermizer-0.39.1-cp38-cp38-win_amd64.whl#egg=pyevermizer; sys_platform == 'win32' and platform_machine == 'AMD64' and python_version == '3.8' +https://github.com/black-sliver/pyevermizer/releases/download/v0.39.1/pyevermizer-0.39.1-cp39-cp39-win_amd64.whl#egg=pyevermizer; sys_platform == 'win32' and platform_machine == 'AMD64' and python_version == '3.9' +https://github.com/black-sliver/pyevermizer/releases/download/v0.39.1/pyevermizer-0.39.1-cp310-cp310-win_amd64.whl#egg=pyevermizer; sys_platform == 'win32' and platform_machine == 'AMD64' and python_version == '3.10' +https://github.com/black-sliver/pyevermizer/releases/download/v0.39.1/pyevermizer-0.39.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl#egg=pyevermizer; sys_platform == 'linux' and platform_machine == 'x86_64' and python_version == '3.8' +https://github.com/black-sliver/pyevermizer/releases/download/v0.39.1/pyevermizer-0.39.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl#egg=pyevermizer; sys_platform == 'linux' and platform_machine == 'x86_64' and python_version == '3.9' +https://github.com/black-sliver/pyevermizer/releases/download/v0.39.1/pyevermizer-0.39.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl#egg=pyevermizer; sys_platform == 'linux' and platform_machine == 'x86_64' and python_version == '3.10' +https://github.com/black-sliver/pyevermizer/releases/download/v0.39.1/pyevermizer-0.39.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl#egg=pyevermizer; sys_platform == 'linux' and platform_machine == 'aarch64' and python_version == '3.8' +https://github.com/black-sliver/pyevermizer/releases/download/v0.39.1/pyevermizer-0.39.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl#egg=pyevermizer; sys_platform == 'linux' and platform_machine == 'aarch64' and python_version == '3.9' +https://github.com/black-sliver/pyevermizer/releases/download/v0.39.1/pyevermizer-0.39.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl#egg=pyevermizer; sys_platform == 'linux' and platform_machine == 'aarch64' and python_version == '3.10' +https://github.com/black-sliver/pyevermizer/releases/download/v0.39.1/pyevermizer-0.39.1-cp38-cp38-macosx_10_9_amd64.whl#egg=pyevermizer; sys_platform == 'darwin' and python_version == '3.8' +#https://github.com/black-sliver/pyevermizer/releases/download/v0.39.1/pyevermizer-0.39.1-cp39-cp39-macosx_10_9_amd64.whl#egg=pyevermizer; sys_platform == 'darwin' and python_version == '3.9' +https://github.com/black-sliver/pyevermizer/releases/download/v0.39.1/pyevermizer-0.39.1-cp39-cp39-macosx_10_9_universal2.whl#egg=pyevermizer; sys_platform == 'darwin' and python_version == '3.9' +https://github.com/black-sliver/pyevermizer/releases/download/v0.39.1/pyevermizer-0.39.1-cp310-cp310-macosx_10_9_universal2.whl#egg=pyevermizer; sys_platform == 'darwin' and python_version == '3.10' bsdiff4>=1.2.1 \ No newline at end of file From 82b8b313f0a54090ac76dc9f48cdf5a9c9d8ffc6 Mon Sep 17 00:00:00 2001 From: Fabian Dill Date: Sat, 13 Nov 2021 03:33:25 +0100 Subject: [PATCH 17/20] Setup: add Secret of Evermore --- inno_setup_310.iss | 63 ++++++++++++++++++++++++++++++++++++++++++---- inno_setup_38.iss | 53 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 111 insertions(+), 5 deletions(-) diff --git a/inno_setup_310.iss b/inno_setup_310.iss index c5be9691..aefe4a98 100644 --- a/inno_setup_310.iss +++ b/inno_setup_310.iss @@ -51,6 +51,7 @@ Name: "custom"; Description: "Custom installation"; Flags: iscustom Name: "core"; Description: "Core Files"; Types: full hosting playing custom; Flags: fixed Name: "generator"; Description: "Generator"; Types: full hosting Name: "generator/sm"; Description: "Super Metroid ROM Setup"; Types: full hosting; ExtraDiskSpaceRequired: 3145728 +Name: "generator/soe"; Description: "Secret of Evermore ROM Setup"; Types: full hosting; ExtraDiskSpaceRequired: 3145728 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 Name: "server"; Description: "Server"; Types: full hosting @@ -68,6 +69,7 @@ NAME: "{app}"; Flags: setntfscompression; Permissions: everyone-modify users-mod [Files] 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: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: generator/oot Source: "{#sourcepath}\*"; Excludes: "*.sfc, *.log, data\sprites\alttpr, SNI, EnemizerCLI, Archipelago*.exe"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs Source: "{#sourcepath}\SNI\*"; Excludes: "*.sfc, *.log"; DestDir: "{app}\SNI"; Flags: ignoreversion recursesubdirs createallsubdirs; Components: client/sni @@ -117,9 +119,9 @@ Root: HKCR; Subkey: "{#MyAppName}patch\DefaultIcon"; ValueData: "{app}\A Root: HKCR; Subkey: "{#MyAppName}patch\shell\open\command"; ValueData: """{app}\ArchipelagoSNIClient.exe"" ""%1"""; ValueType: string; ValueName: ""; Components: client/sni Root: HKCR; Subkey: ".apm3"; ValueData: "{#MyAppName}smpatch"; Flags: uninsdeletevalue; ValueType: string; ValueName: ""; Components: client/sni -Root: HKCR; Subkey: "{#MyAppName}patch"; ValueData: "Archipelago Super Metroid Patch"; Flags: uninsdeletekey; ValueType: string; ValueName: ""; Components: client/sni -Root: HKCR; Subkey: "{#MyAppName}patch\DefaultIcon"; ValueData: "{app}\ArchipelagoSNIClient.exe,0"; ValueType: string; ValueName: ""; Components: client/sni -Root: HKCR; Subkey: "{#MyAppName}patch\shell\open\command"; ValueData: """{app}\ArchipelagoSNIClient.exe"" ""%1"""; ValueType: string; ValueName: ""; Components: client/sni +Root: HKCR; Subkey: "{#MyAppName}smpatch"; ValueData: "Archipelago Super Metroid Patch"; Flags: uninsdeletekey; ValueType: string; ValueName: ""; Components: client/sni +Root: HKCR; Subkey: "{#MyAppName}smpatch\DefaultIcon"; ValueData: "{app}\ArchipelagoSNIClient.exe,0"; ValueType: string; ValueName: ""; Components: client/sni +Root: HKCR; Subkey: "{#MyAppName}smpatch\shell\open\command"; ValueData: """{app}\ArchipelagoSNIClient.exe"" ""%1"""; ValueType: string; ValueName: ""; Components: client/sni Root: HKCR; Subkey: ".apmc"; ValueData: "{#MyAppName}mcdata"; Flags: uninsdeletevalue; ValueType: string; ValueName: ""; Components: client/minecraft Root: HKCR; Subkey: "{#MyAppName}mcdata"; ValueData: "Archipelago Minecraft Data"; Flags: uninsdeletekey; ValueType: string; ValueName: ""; Components: client/minecraft @@ -209,6 +211,9 @@ var ROMFilePage: TInputFileWizardPage; var smrom: string; var SMRomFilePage: TInputFileWizardPage; +var soerom: string; +var SoERomFilePage: TInputFileWizardPage; + var ootrom: string; var OoTROMFilePage: TInputFileWizardPage; @@ -270,6 +275,34 @@ begin '.sfc'); end; +procedure AddSoERomPage(); +begin + soerom := FileSearch('Secret of Evermore (USA).sfc', WizardDirValue()); + if Length(soerom) > 0 then + begin + log('existing SoE ROM found'); + log(IntToStr(CompareStr(GetMD5OfFile(soerom), '6e9c94511d04fac6e0a1e582c170be3a'))); + if CompareStr(GetMD5OfFile(soerom), '6e9c94511d04fac6e0a1e582c170be3a') = 0 then + begin + log('existing SoE ROM verified'); + exit; + end; + log('existing SM ROM failed verification'); + end; + soerom := '' + SoEROMFilePage := + CreateInputFilePage( + wpSelectComponents, + 'Select ROM File', + 'Where is your Secret of Evermore located?', + 'Select the file, then click Next.'); + + SoEROMFilePage.Add( + 'Location of Secret of Evermore ROM file:', + 'SNES ROM files|*.sfc|All files|*.*', + '.sfc'); +end; + procedure AddMinecraftDownloads(); begin MinecraftDownloadPage := CreateDownloadPage(SetupMessage(msgWizardPreparing), SetupMessage(msgPreparingDesc), @OnDownloadMinecraftProgress); @@ -337,10 +370,11 @@ begin end; procedure InitializeWizard(); -begin +begin AddOoTRomPage(); AddRomPage(); AddSMRomPage(); + AddSoeRomPage; AddMinecraftDownloads(); end; @@ -352,6 +386,8 @@ begin Result := not (WizardIsComponentSelected('client/sni/lttp') or WizardIsComponentSelected('generator/lttp')); if (assigned(SMROMFilePage)) and (PageID = SMROMFilePage.ID) then Result := not (WizardIsComponentSelected('client/sni/sm') or WizardIsComponentSelected('generator/sm')); + if (assigned(SoEROMFilePage)) and (PageID = SoEROMFilePage.ID) then + Result := not (WizardIsComponentSelected('generator/soe')); if (assigned(OoTROMFilePage)) and (PageID = OoTROMFilePage.ID) then Result := not (WizardIsComponentSelected('generator/oot')); end; @@ -365,7 +401,7 @@ begin R := CompareStr(GetMD5OfFile(ROMFilePage.Values[0]), '03a63945398191337e896e5771f77173') if R <> 0 then MsgBox('ALttP ROM validation failed. Very likely wrong file.', mbInformation, MB_OK); - + Result := ROMFilePage.Values[0] end else @@ -388,6 +424,23 @@ begin Result := ''; end; +function GetSoEROMPath(Param: string): string; +begin + if Length(soerom) > 0 then + Result := soerom + else if Assigned(SoERomFilePage) then + begin + R := CompareStr(GetMD5OfFile(SoEROMFilePage.Values[0]), '6e9c94511d04fac6e0a1e582c170be3a') + log(GetMD5OfFile(SoEROMFilePage.Values[0])) + if R <> 0 then + MsgBox('Secret of Evermore ROM validation failed. Very likely wrong file.', mbInformation, MB_OK); + + Result := SoEROMFilePage.Values[0] + end + else + Result := ''; + end; + function GetOoTROMPath(Param: string): string; begin if Length(ootrom) > 0 then diff --git a/inno_setup_38.iss b/inno_setup_38.iss index f5fd9c53..db45c578 100644 --- a/inno_setup_38.iss +++ b/inno_setup_38.iss @@ -51,6 +51,7 @@ Name: "custom"; Description: "Custom installation"; Flags: iscustom Name: "core"; Description: "Core Files"; Types: full hosting playing custom; Flags: fixed Name: "generator"; Description: "Generator"; Types: full hosting Name: "generator/sm"; Description: "Super Metroid ROM Setup"; Types: full hosting; ExtraDiskSpaceRequired: 3145728 +Name: "generator/soe"; Description: "Secret of Evermore ROM Setup"; Types: full hosting; ExtraDiskSpaceRequired: 3145728 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 Name: "server"; Description: "Server"; Types: full hosting @@ -68,6 +69,7 @@ NAME: "{app}"; Flags: setntfscompression; Permissions: everyone-modify users-mod [Files] 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: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: generator/oot Source: "{#sourcepath}\*"; Excludes: "*.sfc, *.log, data\sprites\alttpr, SNI, EnemizerCLI, Archipelago*.exe"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs Source: "{#sourcepath}\SNI\*"; Excludes: "*.sfc, *.log"; DestDir: "{app}\SNI"; Flags: ignoreversion recursesubdirs createallsubdirs; Components: client/sni @@ -209,6 +211,9 @@ var ROMFilePage: TInputFileWizardPage; var smrom: string; var SMRomFilePage: TInputFileWizardPage; +var soerom: string; +var SoERomFilePage: TInputFileWizardPage; + var ootrom: string; var OoTROMFilePage: TInputFileWizardPage; @@ -270,6 +275,34 @@ begin '.sfc'); end; +procedure AddSoERomPage(); +begin + soerom := FileSearch('Secret of Evermore (USA).sfc', WizardDirValue()); + if Length(soerom) > 0 then + begin + log('existing SoE ROM found'); + log(IntToStr(CompareStr(GetMD5OfFile(soerom), '6e9c94511d04fac6e0a1e582c170be3a'))); + if CompareStr(GetMD5OfFile(soerom), '6e9c94511d04fac6e0a1e582c170be3a') = 0 then + begin + log('existing SoE ROM verified'); + exit; + end; + log('existing SM ROM failed verification'); + end; + soerom := '' + SoEROMFilePage := + CreateInputFilePage( + wpSelectComponents, + 'Select ROM File', + 'Where is your Secret of Evermore located?', + 'Select the file, then click Next.'); + + SoEROMFilePage.Add( + 'Location of Secret of Evermore ROM file:', + 'SNES ROM files|*.sfc|All files|*.*', + '.sfc'); +end; + procedure AddMinecraftDownloads(); begin MinecraftDownloadPage := CreateDownloadPage(SetupMessage(msgWizardPreparing), SetupMessage(msgPreparingDesc), @OnDownloadMinecraftProgress); @@ -341,6 +374,7 @@ begin AddOoTRomPage(); AddRomPage(); AddSMRomPage(); + AddSoeRomPage; AddMinecraftDownloads(); end; @@ -352,6 +386,8 @@ begin Result := not (WizardIsComponentSelected('client/sni/lttp') or WizardIsComponentSelected('generator/lttp')); if (assigned(SMROMFilePage)) and (PageID = SMROMFilePage.ID) then Result := not (WizardIsComponentSelected('client/sni/sm') or WizardIsComponentSelected('generator/sm')); + if (assigned(SoEROMFilePage)) and (PageID = SoEROMFilePage.ID) then + Result := not (WizardIsComponentSelected('generator/soe')); if (assigned(OoTROMFilePage)) and (PageID = OoTROMFilePage.ID) then Result := not (WizardIsComponentSelected('generator/oot')); end; @@ -388,6 +424,23 @@ begin Result := ''; end; +function GetSoEROMPath(Param: string): string; +begin + if Length(soerom) > 0 then + Result := soerom + else if Assigned(SoERomFilePage) then + begin + R := CompareStr(GetMD5OfFile(SoEROMFilePage.Values[0]), '6e9c94511d04fac6e0a1e582c170be3a') + log(GetMD5OfFile(SoEROMFilePage.Values[0])) + if R <> 0 then + MsgBox('Secret of Evermore ROM validation failed. Very likely wrong file.', mbInformation, MB_OK); + + Result := SoEROMFilePage.Values[0] + end + else + Result := ''; + end; + function GetOoTROMPath(Param: string): string; begin if Length(ootrom) > 0 then From 452026165ffed492dff9ae7ebb569f402d71d76c Mon Sep 17 00:00:00 2001 From: lordlou <87331798+lordlou@users.noreply.github.com> Date: Sat, 13 Nov 2021 09:40:20 -0500 Subject: [PATCH 18/20] [SM] added support for more than 255 players (will print Archipelago for higher player number) (#130) * added support for more than 255 players (will print Archipelago for higher player number) --- SNIClient.py | 4 ++-- worlds/sm/Rom.py | 3 ++- worlds/sm/__init__.py | 11 ++++++----- worlds/sm/variaRandomizer/rom/rompatcher.py | 4 ++-- 4 files changed, 12 insertions(+), 10 deletions(-) diff --git a/SNIClient.py b/SNIClient.py index e7f4b8ae..5afda798 100644 --- a/SNIClient.py +++ b/SNIClient.py @@ -22,6 +22,7 @@ from NetUtils import * from worlds.alttp import Regions, Shops from worlds.alttp import Items from worlds.alttp.Rom import ROM_PLAYER_LIMIT +from worlds.sm.Rom import ROM_PLAYER_LIMIT as SM_ROM_PLAYER_LIMIT import Utils from CommonClient import CommonContext, server_loop, console_loop, ClientCommandProcessor, gui_enabled, get_base_parser from Patch import GAME_ALTTP, GAME_SM @@ -1048,7 +1049,7 @@ async def game_watcher(ctx: Context): item = ctx.items_received[itemOutPtr] itemId = item.item - items_start_id - playerID = (item.player-1) if item.player != 0 else (len(ctx.player_names)-1) + playerID = item.player if item.player <= SM_ROM_PLAYER_LIMIT else 0 snes_buffered_write(ctx, SM_RECV_PROGRESS_ADDR + itemOutPtr * 4, bytes([playerID & 0xFF, (playerID >> 8) & 0xFF, itemId & 0xFF, (itemId >> 8) & 0xFF])) itemOutPtr += 1 snes_buffered_write(ctx, SM_RECV_PROGRESS_ADDR + 0x602, bytes([itemOutPtr & 0xFF, (itemOutPtr >> 8) & 0xFF])) @@ -1057,7 +1058,6 @@ async def game_watcher(ctx: Context): ctx.location_name_getter(item.location), itemOutPtr, len(ctx.items_received))) await snes_flush_writes(ctx) - async def run_game(romfile): auto_start = Utils.get_options()["lttp_options"].get("rom_start", True) if auto_start is True: diff --git a/worlds/sm/Rom.py b/worlds/sm/Rom.py index efddc17a..5d7ab709 100644 --- a/worlds/sm/Rom.py +++ b/worlds/sm/Rom.py @@ -2,6 +2,7 @@ import Utils from Patch import read_rom JAP10HASH = '21f3e98df4780ee1c667b84e57d88675' +ROM_PLAYER_LIMIT = 255 import hashlib import os @@ -27,4 +28,4 @@ def get_base_rom_path(file_name: str = "") -> str: file_name = options["sm_options"]["rom_file"] if not os.path.exists(file_name): file_name = Utils.local_path(file_name) - return file_name \ No newline at end of file + return file_name diff --git a/worlds/sm/__init__.py b/worlds/sm/__init__.py index db156dcd..808f1bde 100644 --- a/worlds/sm/__init__.py +++ b/worlds/sm/__init__.py @@ -11,7 +11,7 @@ from .Items import lookup_name_to_id as items_lookup_name_to_id from .Regions import create_regions from .Rules import set_rules, add_entrance_rule from .Options import sm_options -from .Rom import get_base_rom_path +from .Rom import get_base_rom_path, ROM_PLAYER_LIMIT import Utils from BaseClasses import Region, Entrance, Location, MultiWorld, Item, RegionType, CollectionState @@ -242,7 +242,7 @@ class SMWorld(World): idx += 1 (w0, w1) = self.getWord(0 if itemLoc.item.player == self.player else 1) (w2, w3) = self.getWord(itemId) - (w4, w5) = self.getWord(itemLoc.item.player - 1) + (w4, w5) = self.getWord(itemLoc.item.player if itemLoc.item.player <= ROM_PLAYER_LIMIT else 0) (w6, w7) = self.getWord(0 if itemLoc.item.advancement else 1) multiWorldLocations[0x1C6000 + locationsDict[itemLoc.name].Id*8] = [w0, w1, w2, w3, w4, w5, w6, w7] @@ -268,9 +268,10 @@ class SMWorld(World): romPatcher.applyIPSPatchDict(patchDict) playerNames = {} - for p in range(1, self.world.players + 1): - playerNames[0x1C5000 + (p - 1) * 16] = self.world.player_name[p][:16].upper().center(16).encode() - playerNames[0x1C5000 + (self.world.players) * 16] = "Archipelago".upper().center(16).encode() + playerNames[0x1C5000] = "Archipelago".upper().center(16).encode() + for p in range(1, min(self.world.players, ROM_PLAYER_LIMIT) + 1): + playerNames[0x1C5000 + p * 16] = self.world.player_name[p][:16].upper().center(16).encode() + romPatcher.applyIPSPatch('PlayerName', { 'PlayerName': playerNames }) diff --git a/worlds/sm/variaRandomizer/rom/rompatcher.py b/worlds/sm/variaRandomizer/rom/rompatcher.py index 471f982a..22b83ceb 100644 --- a/worlds/sm/variaRandomizer/rom/rompatcher.py +++ b/worlds/sm/variaRandomizer/rom/rompatcher.py @@ -573,12 +573,12 @@ class RomPatcher: self.writeCreditsStringBig(address, line, top=False) address += 0x80 - value = " "+settings.progSpeed.upper() + value = " "+"NA" # settings.progSpeed.upper() line = " PROGRESSION SPEED ....%s " % value.rjust(8, '.') self.writeCreditsString(address, 0x04, line) address += 0x40 - line = " PROGRESSION DIFFICULTY %s " % settings.progDiff.upper() + line = " PROGRESSION DIFFICULTY %s " % value.rjust(7, '.') # settings.progDiff.upper() self.writeCreditsString(address, 0x04, line) address += 0x80 # skip item distrib title From 4e43166e1f4be1bbfb92a577ab5f7dd6c08dbce9 Mon Sep 17 00:00:00 2001 From: Fabian Dill Date: Sat, 13 Nov 2021 16:32:19 +0100 Subject: [PATCH 19/20] Setup: consolidate some SNES rom handling --- inno_setup_310.iss | 152 +++++++++++++++++---------------------------- inno_setup_38.iss | 150 ++++++++++++++++---------------------------- 2 files changed, 111 insertions(+), 191 deletions(-) diff --git a/inno_setup_310.iss b/inno_setup_310.iss index aefe4a98..1da72694 100644 --- a/inno_setup_310.iss +++ b/inno_setup_310.iss @@ -205,8 +205,8 @@ end; var R : longint; -var rom: string; -var ROMFilePage: TInputFileWizardPage; +var lttprom: string; +var LttPROMFilePage: TInputFileWizardPage; var smrom: string; var SMRomFilePage: TInputFileWizardPage; @@ -219,87 +219,37 @@ var OoTROMFilePage: TInputFileWizardPage; var MinecraftDownloadPage: TDownloadWizardPage; -procedure AddRomPage(); +function CheckRom(name: string; hash: string): string; +var rom: string; begin - rom := FileSearch('Zelda no Densetsu - Kamigami no Triforce (Japan).sfc', WizardDirValue()); + log('Handling ' + name) + rom := FileSearch(name, WizardDirValue()); if Length(rom) > 0 then begin log('existing ROM found'); - log(IntToStr(CompareStr(GetMD5OfFile(rom), '03a63945398191337e896e5771f77173'))); - if CompareStr(GetMD5OfFile(rom), '03a63945398191337e896e5771f77173') = 0 then + log(IntToStr(CompareStr(GetMD5OfFile(rom), hash))); + if CompareStr(GetMD5OfFile(rom), hash) = 0 then begin log('existing ROM verified'); + Result := rom; exit; end; log('existing ROM failed verification'); end; - rom := '' - ROMFilePage := +end; + +function AddRomPage(name: string): TInputFileWizardPage; +begin + Result := CreateInputFilePage( wpSelectComponents, 'Select ROM File', - 'Where is your Zelda no Densetsu - Kamigami no Triforce (Japan).sfc located?', + 'Where is your ' + name + ' located?', 'Select the file, then click Next.'); - ROMFilePage.Add( + Result.Add( 'Location of ROM file:', - 'SNES ROM files|*.sfc|All files|*.*', - '.sfc'); -end; - -procedure AddSMRomPage(); -begin - smrom := FileSearch('Super Metroid (JU).sfc', WizardDirValue()); - if Length(smrom) > 0 then - begin - log('existing SM ROM found'); - log(IntToStr(CompareStr(GetMD5OfFile(smrom), '21f3e98df4780ee1c667b84e57d88675'))); - if CompareStr(GetMD5OfFile(smrom), '21f3e98df4780ee1c667b84e57d88675') = 0 then - begin - log('existing SM ROM verified'); - exit; - end; - log('existing SM ROM failed verification'); - end; - smrom := '' - SMROMFilePage := - CreateInputFilePage( - wpSelectComponents, - 'Select ROM File', - 'Where is your Super Metroid located?', - 'Select the file, then click Next.'); - - SMROMFilePage.Add( - 'Location of Super Metroid ROM file:', - 'SNES ROM files|*.sfc|All files|*.*', - '.sfc'); -end; - -procedure AddSoERomPage(); -begin - soerom := FileSearch('Secret of Evermore (USA).sfc', WizardDirValue()); - if Length(soerom) > 0 then - begin - log('existing SoE ROM found'); - log(IntToStr(CompareStr(GetMD5OfFile(soerom), '6e9c94511d04fac6e0a1e582c170be3a'))); - if CompareStr(GetMD5OfFile(soerom), '6e9c94511d04fac6e0a1e582c170be3a') = 0 then - begin - log('existing SoE ROM verified'); - exit; - end; - log('existing SM ROM failed verification'); - end; - soerom := '' - SoEROMFilePage := - CreateInputFilePage( - wpSelectComponents, - 'Select ROM File', - 'Where is your Secret of Evermore located?', - 'Select the file, then click Next.'); - - SoEROMFilePage.Add( - 'Location of Secret of Evermore ROM file:', - 'SNES ROM files|*.sfc|All files|*.*', + 'SNES ROM files|*.sfc;*.smc|All files|*.*', '.sfc'); end; @@ -369,40 +319,17 @@ begin Result := True; end; -procedure InitializeWizard(); -begin - AddOoTRomPage(); - AddRomPage(); - AddSMRomPage(); - AddSoeRomPage; - AddMinecraftDownloads(); -end; - - -function ShouldSkipPage(PageID: Integer): Boolean; -begin - Result := False; - if (assigned(ROMFilePage)) and (PageID = ROMFilePage.ID) then - Result := not (WizardIsComponentSelected('client/sni/lttp') or WizardIsComponentSelected('generator/lttp')); - if (assigned(SMROMFilePage)) and (PageID = SMROMFilePage.ID) then - Result := not (WizardIsComponentSelected('client/sni/sm') or WizardIsComponentSelected('generator/sm')); - if (assigned(SoEROMFilePage)) and (PageID = SoEROMFilePage.ID) then - Result := not (WizardIsComponentSelected('generator/soe')); - if (assigned(OoTROMFilePage)) and (PageID = OoTROMFilePage.ID) then - Result := not (WizardIsComponentSelected('generator/oot')); -end; - function GetROMPath(Param: string): string; begin - if Length(rom) > 0 then - Result := rom - else if Assigned(RomFilePage) then + if Length(lttprom) > 0 then + Result := lttprom + else if Assigned(LttPRomFilePage) then begin - R := CompareStr(GetMD5OfFile(ROMFilePage.Values[0]), '03a63945398191337e896e5771f77173') + R := CompareStr(GetMD5OfFile(LttPROMFilePage.Values[0]), '03a63945398191337e896e5771f77173') if R <> 0 then MsgBox('ALttP ROM validation failed. Very likely wrong file.', mbInformation, MB_OK); - Result := ROMFilePage.Values[0] + Result := LttPROMFilePage.Values[0] end else Result := ''; @@ -450,9 +377,42 @@ begin R := CompareStr(GetMD5OfFile(OoTROMFilePage.Values[0]), '5bd1fe107bf8106b2ab6650abecd54d6') * CompareStr(GetMD5OfFile(OoTROMFilePage.Values[0]), '6697768a7a7df2dd27a692a2638ea90b') * CompareStr(GetMD5OfFile(OoTROMFilePage.Values[0]), '05f0f3ebacbc8df9243b6148ffe4792f'); if R <> 0 then MsgBox('OoT ROM validation failed. Very likely wrong file.', mbInformation, MB_OK); - + Result := OoTROMFilePage.Values[0] end else Result := ''; end; + +procedure InitializeWizard(); +begin + AddOoTRomPage(); + + lttprom := CheckRom('Zelda no Densetsu - Kamigami no Triforce (Japan).sfc', '03a63945398191337e896e5771f77173'); + if Length(lttprom) = 0 then + LttPROMFilePage:= AddRomPage('Zelda no Densetsu - Kamigami no Triforce (Japan).sfc'); + + smrom := CheckRom('Super Metroid (JU).sfc', '21f3e98df4780ee1c667b84e57d88675'); + if Length(smrom) = 0 then + SMRomFilePage:= AddRomPage('Super Metroid (JU).sfc'); + + soerom := CheckRom('Secret of Evermore (USA).sfc', '6e9c94511d04fac6e0a1e582c170be3a'); + if Length(soerom) = 0 then + SoEROMFilePage:= AddRomPage('Secret of Evermore (USA).sfc'); + + AddMinecraftDownloads(); +end; + + +function ShouldSkipPage(PageID: Integer): Boolean; +begin + Result := False; + if (assigned(LttPROMFilePage)) and (PageID = LttPROMFilePage.ID) then + Result := not (WizardIsComponentSelected('client/sni/lttp') or WizardIsComponentSelected('generator/lttp')); + if (assigned(SMROMFilePage)) and (PageID = SMROMFilePage.ID) then + Result := not (WizardIsComponentSelected('client/sni/sm') or WizardIsComponentSelected('generator/sm')); + if (assigned(SoEROMFilePage)) and (PageID = SoEROMFilePage.ID) then + Result := not (WizardIsComponentSelected('generator/soe')); + if (assigned(OoTROMFilePage)) and (PageID = OoTROMFilePage.ID) then + Result := not (WizardIsComponentSelected('generator/oot')); +end; \ No newline at end of file diff --git a/inno_setup_38.iss b/inno_setup_38.iss index db45c578..f9387c1a 100644 --- a/inno_setup_38.iss +++ b/inno_setup_38.iss @@ -205,8 +205,8 @@ end; var R : longint; -var rom: string; -var ROMFilePage: TInputFileWizardPage; +var lttprom: string; +var LttPROMFilePage: TInputFileWizardPage; var smrom: string; var SMRomFilePage: TInputFileWizardPage; @@ -219,87 +219,37 @@ var OoTROMFilePage: TInputFileWizardPage; var MinecraftDownloadPage: TDownloadWizardPage; -procedure AddRomPage(); +function CheckRom(name: string; hash: string): string; +var rom: string; begin - rom := FileSearch('Zelda no Densetsu - Kamigami no Triforce (Japan).sfc', WizardDirValue()); + log('Handling ' + name) + rom := FileSearch(name, WizardDirValue()); if Length(rom) > 0 then begin log('existing ROM found'); - log(IntToStr(CompareStr(GetMD5OfFile(rom), '03a63945398191337e896e5771f77173'))); - if CompareStr(GetMD5OfFile(rom), '03a63945398191337e896e5771f77173') = 0 then + log(IntToStr(CompareStr(GetMD5OfFile(rom), hash))); + if CompareStr(GetMD5OfFile(rom), hash) = 0 then begin log('existing ROM verified'); + Result := rom; exit; end; log('existing ROM failed verification'); end; - rom := '' - ROMFilePage := +end; + +function AddRomPage(name: string): TInputFileWizardPage; +begin + Result := CreateInputFilePage( wpSelectComponents, 'Select ROM File', - 'Where is your Zelda no Densetsu - Kamigami no Triforce (Japan).sfc located?', + 'Where is your ' + name + ' located?', 'Select the file, then click Next.'); - ROMFilePage.Add( + Result.Add( 'Location of ROM file:', - 'SNES ROM files|*.sfc|All files|*.*', - '.sfc'); -end; - -procedure AddSMRomPage(); -begin - smrom := FileSearch('Super Metroid (JU).sfc', WizardDirValue()); - if Length(smrom) > 0 then - begin - log('existing SM ROM found'); - log(IntToStr(CompareStr(GetMD5OfFile(smrom), '21f3e98df4780ee1c667b84e57d88675'))); - if CompareStr(GetMD5OfFile(smrom), '21f3e98df4780ee1c667b84e57d88675') = 0 then - begin - log('existing SM ROM verified'); - exit; - end; - log('existing SM ROM failed verification'); - end; - smrom := '' - SMROMFilePage := - CreateInputFilePage( - wpSelectComponents, - 'Select ROM File', - 'Where is your Super Metroid located?', - 'Select the file, then click Next.'); - - SMROMFilePage.Add( - 'Location of Super Metroid ROM file:', - 'SNES ROM files|*.sfc|All files|*.*', - '.sfc'); -end; - -procedure AddSoERomPage(); -begin - soerom := FileSearch('Secret of Evermore (USA).sfc', WizardDirValue()); - if Length(soerom) > 0 then - begin - log('existing SoE ROM found'); - log(IntToStr(CompareStr(GetMD5OfFile(soerom), '6e9c94511d04fac6e0a1e582c170be3a'))); - if CompareStr(GetMD5OfFile(soerom), '6e9c94511d04fac6e0a1e582c170be3a') = 0 then - begin - log('existing SoE ROM verified'); - exit; - end; - log('existing SM ROM failed verification'); - end; - soerom := '' - SoEROMFilePage := - CreateInputFilePage( - wpSelectComponents, - 'Select ROM File', - 'Where is your Secret of Evermore located?', - 'Select the file, then click Next.'); - - SoEROMFilePage.Add( - 'Location of Secret of Evermore ROM file:', - 'SNES ROM files|*.sfc|All files|*.*', + 'SNES ROM files|*.sfc;*.smc|All files|*.*', '.sfc'); end; @@ -369,40 +319,17 @@ begin Result := True; end; -procedure InitializeWizard(); -begin - AddOoTRomPage(); - AddRomPage(); - AddSMRomPage(); - AddSoeRomPage; - AddMinecraftDownloads(); -end; - - -function ShouldSkipPage(PageID: Integer): Boolean; -begin - Result := False; - if (assigned(ROMFilePage)) and (PageID = ROMFilePage.ID) then - Result := not (WizardIsComponentSelected('client/sni/lttp') or WizardIsComponentSelected('generator/lttp')); - if (assigned(SMROMFilePage)) and (PageID = SMROMFilePage.ID) then - Result := not (WizardIsComponentSelected('client/sni/sm') or WizardIsComponentSelected('generator/sm')); - if (assigned(SoEROMFilePage)) and (PageID = SoEROMFilePage.ID) then - Result := not (WizardIsComponentSelected('generator/soe')); - if (assigned(OoTROMFilePage)) and (PageID = OoTROMFilePage.ID) then - Result := not (WizardIsComponentSelected('generator/oot')); -end; - function GetROMPath(Param: string): string; begin - if Length(rom) > 0 then - Result := rom - else if Assigned(RomFilePage) then + if Length(lttprom) > 0 then + Result := lttprom + else if Assigned(LttPRomFilePage) then begin - R := CompareStr(GetMD5OfFile(ROMFilePage.Values[0]), '03a63945398191337e896e5771f77173') + R := CompareStr(GetMD5OfFile(LttPROMFilePage.Values[0]), '03a63945398191337e896e5771f77173') if R <> 0 then MsgBox('ALttP ROM validation failed. Very likely wrong file.', mbInformation, MB_OK); - Result := ROMFilePage.Values[0] + Result := LttPROMFilePage.Values[0] end else Result := ''; @@ -456,3 +383,36 @@ begin else Result := ''; end; + +procedure InitializeWizard(); +begin + AddOoTRomPage(); + + lttprom := CheckRom('Zelda no Densetsu - Kamigami no Triforce (Japan).sfc', '03a63945398191337e896e5771f77173'); + if Length(lttprom) = 0 then + LttPROMFilePage:= AddRomPage('Zelda no Densetsu - Kamigami no Triforce (Japan).sfc'); + + smrom := CheckRom('Super Metroid (JU).sfc', '21f3e98df4780ee1c667b84e57d88675'); + if Length(smrom) = 0 then + SMRomFilePage:= AddRomPage('Super Metroid (JU).sfc'); + + soerom := CheckRom('Secret of Evermore (USA).sfc', '6e9c94511d04fac6e0a1e582c170be3a'); + if Length(soerom) = 0 then + SoEROMFilePage:= AddRomPage('Secret of Evermore (USA).sfc'); + + AddMinecraftDownloads(); +end; + + +function ShouldSkipPage(PageID: Integer): Boolean; +begin + Result := False; + if (assigned(LttPROMFilePage)) and (PageID = LttPROMFilePage.ID) then + Result := not (WizardIsComponentSelected('client/sni/lttp') or WizardIsComponentSelected('generator/lttp')); + if (assigned(SMROMFilePage)) and (PageID = SMROMFilePage.ID) then + Result := not (WizardIsComponentSelected('client/sni/sm') or WizardIsComponentSelected('generator/sm')); + if (assigned(SoEROMFilePage)) and (PageID = SoEROMFilePage.ID) then + Result := not (WizardIsComponentSelected('generator/soe')); + if (assigned(OoTROMFilePage)) and (PageID = OoTROMFilePage.ID) then + Result := not (WizardIsComponentSelected('generator/oot')); +end; \ No newline at end of file From c178006accc4811dc4b374da173caeee94b24b14 Mon Sep 17 00:00:00 2001 From: Fabian Dill Date: Sat, 13 Nov 2021 16:35:24 +0100 Subject: [PATCH 20/20] Readme: add new games --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 142e746a..8135798d 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,8 @@ Currently, the following games are supported: * Risk of Rain 2 * The Legend of Zelda: Ocarina of Time * Timespinner +* Super Metroid +* Secret of Evermore 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