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