diff --git a/README.md b/README.md
index 975f0ce75a7..2c0c164b53c 100644
--- a/README.md
+++ b/README.md
@@ -62,6 +62,7 @@ Currently, the following games are supported:
* Kirby's Dream Land 3
* Celeste 64
* Zork Grand Inquisitor
+* Castlevania 64
For setup and instructions check out our [tutorials page](https://archipelago.gg/tutorial/).
Downloads can be found at [Releases](https://github.com/ArchipelagoMW/Archipelago/releases), including compiled
diff --git a/docs/CODEOWNERS b/docs/CODEOWNERS
index 90b1dabb6dc..9c801f04af0 100644
--- a/docs/CODEOWNERS
+++ b/docs/CODEOWNERS
@@ -28,6 +28,9 @@
# Bumper Stickers
/worlds/bumpstik/ @FelicitusNeko
+# Castlevania 64
+/worlds/cv64/ @LiquidCat64
+
# Celeste 64
/worlds/celeste64/ @PoryGone
diff --git a/inno_setup.iss b/inno_setup.iss
index 5a6d6083066..9f4c9d1678e 100644
--- a/inno_setup.iss
+++ b/inno_setup.iss
@@ -169,6 +169,11 @@ Root: HKCR; Subkey: "{#MyAppName}pkmnepatch"; ValueData: "Ar
Root: HKCR; Subkey: "{#MyAppName}pkmnepatch\DefaultIcon"; ValueData: "{app}\ArchipelagoBizHawkClient.exe,0"; ValueType: string; ValueName: "";
Root: HKCR; Subkey: "{#MyAppName}pkmnepatch\shell\open\command"; ValueData: """{app}\ArchipelagoBizHawkClient.exe"" ""%1"""; ValueType: string; ValueName: "";
+Root: HKCR; Subkey: ".apcv64"; ValueData: "{#MyAppName}cv64patch"; Flags: uninsdeletevalue; ValueType: string; ValueName: "";
+Root: HKCR; Subkey: "{#MyAppName}cv64patch"; ValueData: "Archipelago Castlevania 64 Patch"; Flags: uninsdeletekey; ValueType: string; ValueName: "";
+Root: HKCR; Subkey: "{#MyAppName}cv64patch\DefaultIcon"; ValueData: "{app}\ArchipelagoBizHawkClient.exe,0"; ValueType: string; ValueName: "";
+Root: HKCR; Subkey: "{#MyAppName}cv64patch\shell\open\command"; ValueData: """{app}\ArchipelagoBizHawkClient.exe"" ""%1"""; ValueType: string; ValueName: "";
+
Root: HKCR; Subkey: ".apladx"; ValueData: "{#MyAppName}ladxpatch"; Flags: uninsdeletevalue; ValueType: string; ValueName: "";
Root: HKCR; Subkey: "{#MyAppName}ladxpatch"; ValueData: "Archipelago Links Awakening DX Patch"; Flags: uninsdeletekey; ValueType: string; ValueName: "";
Root: HKCR; Subkey: "{#MyAppName}ladxpatch\DefaultIcon"; ValueData: "{app}\ArchipelagoLinksAwakeningClient.exe,0"; ValueType: string; ValueName: "";
diff --git a/worlds/cv64/__init__.py b/worlds/cv64/__init__.py
new file mode 100644
index 00000000000..ca4697bce8d
--- /dev/null
+++ b/worlds/cv64/__init__.py
@@ -0,0 +1,327 @@
+import os
+import typing
+import settings
+import base64
+import logging
+
+from BaseClasses import Item, Region, MultiWorld, Tutorial, ItemClassification
+from .items import CV64Item, filler_item_names, get_item_info, get_item_names_to_ids, get_item_counts
+from .locations import CV64Location, get_location_info, verify_locations, get_location_names_to_ids, base_id
+from .entrances import verify_entrances, get_warp_entrances
+from .options import CV64Options, CharacterStages, DraculasCondition, SubWeaponShuffle
+from .stages import get_locations_from_stage, get_normal_stage_exits, vanilla_stage_order, \
+ shuffle_stages, generate_warps, get_region_names
+from .regions import get_region_info
+from .rules import CV64Rules
+from .data import iname, rname, ename
+from ..AutoWorld import WebWorld, World
+from .aesthetics import randomize_lighting, shuffle_sub_weapons, rom_empty_breakables_flags, rom_sub_weapon_flags, \
+ randomize_music, get_start_inventory_data, get_location_data, randomize_shop_prices, get_loading_zone_bytes, \
+ get_countdown_numbers
+from .rom import LocalRom, patch_rom, get_base_rom_path, CV64DeltaPatch
+from .client import Castlevania64Client
+
+
+class CV64Settings(settings.Group):
+ class RomFile(settings.UserFilePath):
+ """File name of the CV64 US 1.0 rom"""
+ copy_to = "Castlevania (USA).z64"
+ description = "CV64 (US 1.0) ROM File"
+ md5s = [CV64DeltaPatch.hash]
+
+ rom_file: RomFile = RomFile(RomFile.copy_to)
+
+
+class CV64Web(WebWorld):
+ theme = "stone"
+
+ tutorials = [Tutorial(
+ "Multiworld Setup Guide",
+ "A guide to setting up the Archipleago Castlevania 64 randomizer on your computer and connecting it to a "
+ "multiworld.",
+ "English",
+ "setup_en.md",
+ "setup/en",
+ ["Liquid Cat"]
+ )]
+
+
+class CV64World(World):
+ """
+ Castlevania for the Nintendo 64 is the first 3D game in the franchise. As either whip-wielding Belmont descendant
+ Reinhardt Schneider or powerful sorceress Carrie Fernandez, brave many terrifying traps and foes as you make your
+ way to Dracula's chamber and stop his rule of terror!
+ """
+ game = "Castlevania 64"
+ item_name_groups = {
+ "Bomb": {iname.magical_nitro, iname.mandragora},
+ "Ingredient": {iname.magical_nitro, iname.mandragora},
+ }
+ location_name_groups = {stage: set(get_locations_from_stage(stage)) for stage in vanilla_stage_order}
+ options_dataclass = CV64Options
+ options: CV64Options
+ settings: typing.ClassVar[CV64Settings]
+ topology_present = True
+ data_version = 1
+
+ item_name_to_id = get_item_names_to_ids()
+ location_name_to_id = get_location_names_to_ids()
+
+ active_stage_exits: typing.Dict[str, typing.Dict]
+ active_stage_list: typing.List[str]
+ active_warp_list: typing.List[str]
+
+ # Default values to possibly be updated in generate_early
+ reinhardt_stages: bool = True
+ carrie_stages: bool = True
+ branching_stages: bool = False
+ starting_stage: str = rname.forest_of_silence
+ total_s1s: int = 7
+ s1s_per_warp: int = 1
+ total_s2s: int = 0
+ required_s2s: int = 0
+ drac_condition: int = 0
+
+ auth: bytearray
+
+ web = CV64Web()
+
+ @classmethod
+ def stage_assert_generate(cls, multiworld: MultiWorld) -> None:
+ rom_file = get_base_rom_path()
+ if not os.path.exists(rom_file):
+ raise FileNotFoundError(rom_file)
+
+ def generate_early(self) -> None:
+ # Generate the player's unique authentication
+ self.auth = bytearray(self.multiworld.random.getrandbits(8) for _ in range(16))
+
+ self.total_s1s = self.options.total_special1s.value
+ self.s1s_per_warp = self.options.special1s_per_warp.value
+ self.drac_condition = self.options.draculas_condition.value
+
+ # If there are more S1s needed to unlock the whole warp menu than there are S1s in total, drop S1s per warp to
+ # something manageable.
+ if self.s1s_per_warp * 7 > self.total_s1s:
+ self.s1s_per_warp = self.total_s1s // 7
+ logging.warning(f"[{self.multiworld.player_name[self.player]}] Too many required Special1s "
+ f"({self.options.special1s_per_warp.value * 7}) for Special1s Per Warp setting: "
+ f"{self.options.special1s_per_warp.value} with Total Special1s setting: "
+ f"{self.options.total_special1s.value}. Lowering Special1s Per Warp to: "
+ f"{self.s1s_per_warp}")
+ self.options.special1s_per_warp.value = self.s1s_per_warp
+
+ # Set the total and required Special2s to 1 if the drac condition is the Crystal, to the specified YAML numbers
+ # if it's Specials, or to 0 if it's None or Bosses. The boss totals will be figured out later.
+ if self.drac_condition == DraculasCondition.option_crystal:
+ self.total_s2s = 1
+ self.required_s2s = 1
+ elif self.drac_condition == DraculasCondition.option_specials:
+ self.total_s2s = self.options.total_special2s.value
+ self.required_s2s = int(self.options.percent_special2s_required.value / 100 * self.total_s2s)
+
+ # Enable/disable character stages and branching paths accordingly
+ if self.options.character_stages == CharacterStages.option_reinhardt_only:
+ self.carrie_stages = False
+ elif self.options.character_stages == CharacterStages.option_carrie_only:
+ self.reinhardt_stages = False
+ elif self.options.character_stages == CharacterStages.option_both:
+ self.branching_stages = True
+
+ self.active_stage_exits = get_normal_stage_exits(self)
+
+ stage_1_blacklist = []
+
+ # Prevent Clock Tower from being Stage 1 if more than 4 S1s are needed to warp out of it.
+ if self.s1s_per_warp > 4 and not self.options.multi_hit_breakables:
+ stage_1_blacklist.append(rname.clock_tower)
+
+ # Shuffle the stages if the option is on.
+ if self.options.stage_shuffle:
+ self.active_stage_exits, self.starting_stage, self.active_stage_list = \
+ shuffle_stages(self, stage_1_blacklist)
+ else:
+ self.active_stage_list = [stage for stage in vanilla_stage_order if stage in self.active_stage_exits]
+
+ # Create a list of warps from the active stage list. They are in a random order by default and will never
+ # include the starting stage.
+ self.active_warp_list = generate_warps(self)
+
+ def create_regions(self) -> None:
+ # Add the Menu Region.
+ created_regions = [Region("Menu", self.player, self.multiworld)]
+
+ # Add every stage Region by checking to see if that stage is active.
+ created_regions.extend([Region(name, self.player, self.multiworld)
+ for name in get_region_names(self.active_stage_exits)])
+
+ # Add the Renon's shop Region if shopsanity is on.
+ if self.options.shopsanity:
+ created_regions.append(Region(rname.renon, self.player, self.multiworld))
+
+ # Add the Dracula's chamber (the end) Region.
+ created_regions.append(Region(rname.ck_drac_chamber, self.player, self.multiworld))
+
+ # Set up the Regions correctly.
+ self.multiworld.regions.extend(created_regions)
+
+ # Add the warp Entrances to the Menu Region (the one always at the start of the Region list).
+ created_regions[0].add_exits(get_warp_entrances(self.active_warp_list))
+
+ for reg in created_regions:
+
+ # Add the Entrances to all the Regions.
+ ent_names = get_region_info(reg.name, "entrances")
+ if ent_names is not None:
+ reg.add_exits(verify_entrances(self.options, ent_names, self.active_stage_exits))
+
+ # Add the Locations to all the Regions.
+ loc_names = get_region_info(reg.name, "locations")
+ if loc_names is None:
+ continue
+ verified_locs, events = verify_locations(self.options, loc_names)
+ reg.add_locations(verified_locs, CV64Location)
+
+ # Place event Items on all of their associated Locations.
+ for event_loc, event_item in events.items():
+ self.get_location(event_loc).place_locked_item(self.create_item(event_item, "progression"))
+ # If we're looking at a boss kill trophy, increment the total S2s and, if we're not already at the
+ # set number of required bosses, the total required number. This way, we can prevent gen failures
+ # should the player set more bosses required than there are total.
+ if event_item == iname.trophy:
+ self.total_s2s += 1
+ if self.required_s2s < self.options.bosses_required.value:
+ self.required_s2s += 1
+
+ # If Dracula's Condition is Bosses and there are less calculated required S2s than the value specified by the
+ # player (meaning there weren't enough bosses to reach the player's setting), throw a warning and lower the
+ # option value.
+ if self.options.draculas_condition == DraculasCondition.option_bosses and self.required_s2s < \
+ self.options.bosses_required.value:
+ logging.warning(f"[{self.multiworld.player_name[self.player]}] Not enough bosses for Bosses Required "
+ f"setting: {self.options.bosses_required.value}. Lowering to: {self.required_s2s}")
+ self.options.bosses_required.value = self.required_s2s
+
+ def create_item(self, name: str, force_classification: typing.Optional[str] = None) -> Item:
+ if force_classification is not None:
+ classification = getattr(ItemClassification, force_classification)
+ else:
+ classification = getattr(ItemClassification, get_item_info(name, "default classification"))
+
+ code = get_item_info(name, "code")
+ if code is not None:
+ code += base_id
+
+ created_item = CV64Item(name, classification, code, self.player)
+
+ return created_item
+
+ def create_items(self) -> None:
+ item_counts = get_item_counts(self)
+
+ # Set up the items correctly
+ self.multiworld.itempool += [self.create_item(item, classification) for classification in item_counts for item
+ in item_counts[classification] for _ in range(item_counts[classification][item])]
+
+ def set_rules(self) -> None:
+ # Set all the Entrance rules properly.
+ CV64Rules(self).set_cv64_rules()
+
+ def pre_fill(self) -> None:
+ # If we need more Special1s to warp out of Sphere 1 than there are locations available, then AP's fill
+ # algorithm may try placing the Special1s anyway despite placing the stage's single key always being an option.
+ # To get around this problem in the fill algorithm, the keys will be forced early in these situations to ensure
+ # the algorithm will pick them over the Special1s.
+ if self.starting_stage == rname.tower_of_science:
+ if self.s1s_per_warp > 3:
+ self.multiworld.local_early_items[self.player][iname.science_key2] = 1
+ elif self.starting_stage == rname.clock_tower:
+ if (self.s1s_per_warp > 2 and not self.options.multi_hit_breakables) or \
+ (self.s1s_per_warp > 8 and self.options.multi_hit_breakables):
+ self.multiworld.local_early_items[self.player][iname.clocktower_key1] = 1
+ elif self.starting_stage == rname.castle_wall:
+ if self.s1s_per_warp > 5 and not self.options.hard_logic and \
+ not self.options.multi_hit_breakables:
+ self.multiworld.local_early_items[self.player][iname.left_tower_key] = 1
+
+ def generate_output(self, output_directory: str) -> None:
+ active_locations = self.multiworld.get_locations(self.player)
+
+ # Location data and shop names, descriptions, and colors
+ offset_data, shop_name_list, shop_colors_list, shop_desc_list = \
+ get_location_data(self, active_locations)
+ # Shop prices
+ if self.options.shop_prices:
+ offset_data.update(randomize_shop_prices(self))
+ # Map lighting
+ if self.options.map_lighting:
+ offset_data.update(randomize_lighting(self))
+ # Sub-weapons
+ if self.options.sub_weapon_shuffle == SubWeaponShuffle.option_own_pool:
+ offset_data.update(shuffle_sub_weapons(self))
+ elif self.options.sub_weapon_shuffle == SubWeaponShuffle.option_anywhere:
+ offset_data.update(rom_sub_weapon_flags)
+ # Empty breakables
+ if self.options.empty_breakables:
+ offset_data.update(rom_empty_breakables_flags)
+ # Music
+ if self.options.background_music:
+ offset_data.update(randomize_music(self))
+ # Loading zones
+ offset_data.update(get_loading_zone_bytes(self.options, self.starting_stage, self.active_stage_exits))
+ # Countdown
+ if self.options.countdown:
+ offset_data.update(get_countdown_numbers(self.options, active_locations))
+ # Start Inventory
+ offset_data.update(get_start_inventory_data(self.player, self.options,
+ self.multiworld.precollected_items[self.player]))
+
+ cv64_rom = LocalRom(get_base_rom_path())
+
+ rompath = os.path.join(output_directory, f"{self.multiworld.get_out_file_name_base(self.player)}.z64")
+
+ patch_rom(self, cv64_rom, offset_data, shop_name_list, shop_desc_list, shop_colors_list, active_locations)
+
+ cv64_rom.write_to_file(rompath)
+
+ patch = CV64DeltaPatch(os.path.splitext(rompath)[0] + CV64DeltaPatch.patch_file_ending, player=self.player,
+ player_name=self.multiworld.player_name[self.player], patched_path=rompath)
+ patch.write()
+ os.unlink(rompath)
+
+ def get_filler_item_name(self) -> str:
+ return self.random.choice(filler_item_names)
+
+ def extend_hint_information(self, hint_data: typing.Dict[int, typing.Dict[int, str]]):
+ # Attach each location's stage's position to its hint information if Stage Shuffle is on.
+ if not self.options.stage_shuffle:
+ return
+
+ stage_pos_data = {}
+ for loc in list(self.multiworld.get_locations(self.player)):
+ stage = get_region_info(loc.parent_region.name, "stage")
+ if stage is not None and loc.address is not None:
+ num = str(self.active_stage_exits[stage]["position"]).zfill(2)
+ path = self.active_stage_exits[stage]["path"]
+ stage_pos_data[loc.address] = f"Stage {num}"
+ if path != " ":
+ stage_pos_data[loc.address] += path
+ hint_data[self.player] = stage_pos_data
+
+ def modify_multidata(self, multidata: typing.Dict[str, typing.Any]):
+ # Put the player's unique authentication in connect_names.
+ multidata["connect_names"][base64.b64encode(self.auth).decode("ascii")] = \
+ multidata["connect_names"][self.multiworld.player_name[self.player]]
+
+ def write_spoiler(self, spoiler_handle: typing.TextIO) -> None:
+ # Write the stage order to the spoiler log
+ spoiler_handle.write(f"\nCastlevania 64 stage & warp orders for {self.multiworld.player_name[self.player]}:\n")
+ for stage in self.active_stage_list:
+ num = str(self.active_stage_exits[stage]["position"]).zfill(2)
+ path = self.active_stage_exits[stage]["path"]
+ spoiler_handle.writelines(f"Stage {num}{path}:\t{stage}\n")
+
+ # Write the warp order to the spoiler log
+ spoiler_handle.writelines(f"\nStart :\t{self.active_stage_list[0]}\n")
+ for i in range(1, len(self.active_warp_list)):
+ spoiler_handle.writelines(f"Warp {i}:\t{self.active_warp_list[i]}\n")
diff --git a/worlds/cv64/aesthetics.py b/worlds/cv64/aesthetics.py
new file mode 100644
index 00000000000..cbf2728c829
--- /dev/null
+++ b/worlds/cv64/aesthetics.py
@@ -0,0 +1,666 @@
+import logging
+
+from BaseClasses import ItemClassification, Location, Item
+from .data import iname, rname
+from .options import CV64Options, BackgroundMusic, Countdown, IceTrapAppearance, InvisibleItems, CharacterStages
+from .stages import vanilla_stage_order, get_stage_info
+from .locations import get_location_info, base_id
+from .regions import get_region_info
+from .items import get_item_info, item_info
+
+from typing import TYPE_CHECKING, Dict, List, Tuple, Union, Iterable
+
+if TYPE_CHECKING:
+ from . import CV64World
+
+rom_sub_weapon_offsets = {
+ 0x10C6EB: (0x10, rname.forest_of_silence), # Forest
+ 0x10C6F3: (0x0F, rname.forest_of_silence),
+ 0x10C6FB: (0x0E, rname.forest_of_silence),
+ 0x10C703: (0x0D, rname.forest_of_silence),
+
+ 0x10C81F: (0x0F, rname.castle_wall), # Castle Wall
+ 0x10C827: (0x10, rname.castle_wall),
+ 0x10C82F: (0x0E, rname.castle_wall),
+ 0x7F9A0F: (0x0D, rname.castle_wall),
+
+ 0x83A5D9: (0x0E, rname.villa), # Villa
+ 0x83A5E5: (0x0D, rname.villa),
+ 0x83A5F1: (0x0F, rname.villa),
+ 0xBFC903: (0x10, rname.villa),
+ 0x10C987: (0x10, rname.villa),
+ 0x10C98F: (0x0D, rname.villa),
+ 0x10C997: (0x0F, rname.villa),
+ 0x10CF73: (0x10, rname.villa),
+
+ 0x10CA57: (0x0D, rname.tunnel), # Tunnel
+ 0x10CA5F: (0x0E, rname.tunnel),
+ 0x10CA67: (0x10, rname.tunnel),
+ 0x10CA6F: (0x0D, rname.tunnel),
+ 0x10CA77: (0x0F, rname.tunnel),
+ 0x10CA7F: (0x0E, rname.tunnel),
+
+ 0x10CBC7: (0x0E, rname.castle_center), # Castle Center
+ 0x10CC0F: (0x0D, rname.castle_center),
+ 0x10CC5B: (0x0F, rname.castle_center),
+
+ 0x10CD3F: (0x0E, rname.tower_of_execution), # Character towers
+ 0x10CD65: (0x0D, rname.tower_of_execution),
+ 0x10CE2B: (0x0E, rname.tower_of_science),
+ 0x10CE83: (0x10, rname.duel_tower),
+
+ 0x10CF8B: (0x0F, rname.room_of_clocks), # Room of Clocks
+ 0x10CF93: (0x0D, rname.room_of_clocks),
+
+ 0x99BC5A: (0x0D, rname.clock_tower), # Clock Tower
+ 0x10CECB: (0x10, rname.clock_tower),
+ 0x10CED3: (0x0F, rname.clock_tower),
+ 0x10CEDB: (0x0E, rname.clock_tower),
+ 0x10CEE3: (0x0D, rname.clock_tower),
+}
+
+rom_sub_weapon_flags = {
+ 0x10C6EC: 0x0200FF04, # Forest of Silence
+ 0x10C6FC: 0x0400FF04,
+ 0x10C6F4: 0x0800FF04,
+ 0x10C704: 0x4000FF04,
+
+ 0x10C831: 0x08, # Castle Wall
+ 0x10C829: 0x10,
+ 0x10C821: 0x20,
+ 0xBFCA97: 0x04,
+
+ # Villa
+ 0xBFC926: 0xFF04,
+ 0xBFC93A: 0x80,
+ 0xBFC93F: 0x01,
+ 0xBFC943: 0x40,
+ 0xBFC947: 0x80,
+ 0x10C989: 0x10,
+ 0x10C991: 0x20,
+ 0x10C999: 0x40,
+ 0x10CF77: 0x80,
+
+ 0x10CA58: 0x4000FF0E, # Tunnel
+ 0x10CA6B: 0x80,
+ 0x10CA60: 0x1000FF05,
+ 0x10CA70: 0x2000FF05,
+ 0x10CA78: 0x4000FF05,
+ 0x10CA80: 0x8000FF05,
+
+ 0x10CBCA: 0x02, # Castle Center
+ 0x10CC10: 0x80,
+ 0x10CC5C: 0x40,
+
+ 0x10CE86: 0x01, # Duel Tower
+ 0x10CD43: 0x02, # Tower of Execution
+ 0x10CE2E: 0x20, # Tower of Science
+
+ 0x10CF8E: 0x04, # Room of Clocks
+ 0x10CF96: 0x08,
+
+ 0x10CECE: 0x08, # Clock Tower
+ 0x10CED6: 0x10,
+ 0x10CEE6: 0x20,
+ 0x10CEDE: 0x80,
+}
+
+rom_empty_breakables_flags = {
+ 0x10C74D: 0x40FF05, # Forest of Silence
+ 0x10C765: 0x20FF0E,
+ 0x10C774: 0x0800FF0E,
+ 0x10C755: 0x80FF05,
+ 0x10C784: 0x0100FF0E,
+ 0x10C73C: 0x0200FF0E,
+
+ 0x10C8D0: 0x0400FF0E, # Villa foyer
+
+ 0x10CF9F: 0x08, # Room of Clocks flags
+ 0x10CFA7: 0x01,
+ 0xBFCB6F: 0x04, # Room of Clocks candle property IDs
+ 0xBFCB73: 0x05,
+}
+
+rom_axe_cross_lower_values = {
+ 0x6: [0x7C7F97, 0x07], # Forest
+ 0x8: [0x7C7FA6, 0xF9],
+
+ 0x30: [0x83A60A, 0x71], # Villa hallway
+ 0x27: [0x83A617, 0x26],
+ 0x2C: [0x83A624, 0x6E],
+
+ 0x16C: [0x850FE6, 0x07], # Villa maze
+
+ 0x10A: [0x8C44D3, 0x08], # CC factory floor
+ 0x109: [0x8C44E1, 0x08],
+
+ 0x74: [0x8DF77C, 0x07], # CC invention area
+ 0x60: [0x90FD37, 0x43],
+ 0x55: [0xBFCC2B, 0x43],
+ 0x65: [0x90FBA1, 0x51],
+ 0x64: [0x90FBAD, 0x50],
+ 0x61: [0x90FE56, 0x43]
+}
+
+rom_looping_music_fade_ins = {
+ 0x10: None,
+ 0x11: None,
+ 0x12: None,
+ 0x13: None,
+ 0x14: None,
+ 0x15: None,
+ 0x16: 0x17,
+ 0x18: 0x19,
+ 0x1A: 0x1B,
+ 0x21: 0x75,
+ 0x27: None,
+ 0x2E: 0x23,
+ 0x39: None,
+ 0x45: 0x63,
+ 0x56: None,
+ 0x57: 0x58,
+ 0x59: None,
+ 0x5A: None,
+ 0x5B: 0x5C,
+ 0x5D: None,
+ 0x5E: None,
+ 0x5F: None,
+ 0x60: 0x61,
+ 0x62: None,
+ 0x64: None,
+ 0x65: None,
+ 0x66: None,
+ 0x68: None,
+ 0x69: None,
+ 0x6D: 0x78,
+ 0x6E: None,
+ 0x6F: None,
+ 0x73: None,
+ 0x74: None,
+ 0x77: None,
+ 0x79: None
+}
+
+music_sfx_ids = [0x1C, 0x4B, 0x4C, 0x4D, 0x4E, 0x55, 0x6C, 0x76]
+
+renon_item_dialogue = {
+ 0x02: "More Sub-weapon uses!\n"
+ "Just what you need!",
+ 0x03: "Galamoth told me it's\n"
+ "a heart in other times.",
+ 0x04: "Who needs Warp Rooms\n"
+ "when you have these?",
+ 0x05: "I was told to safeguard\n"
+ "this, but I dunno why.",
+ 0x06: "Fresh off a Behemoth!\n"
+ "Those cows are weird.",
+ 0x07: "Preserved with special\n"
+ " wall-based methods.",
+ 0x08: "Don't tell Geneva\n"
+ "about this...",
+ 0x09: "If this existed in 1094,\n"
+ "that whip wouldn't...",
+ 0x0A: "For when some lizard\n"
+ "brain spits on your ego.",
+ 0x0C: "It'd be a shame if you\n"
+ "lost it immediately...",
+ 0x10C: "No consequences should\n"
+ "you perish with this!",
+ 0x0D: "Arthur was far better\n"
+ "with it than you!",
+ 0x0E: "Night Creatures handle\n"
+ "with care!",
+ 0x0F: "Some may call it a\n"
+ "\"Banshee Boomerang.\"",
+ 0x10: "No weapon triangle\n"
+ "advantages with this.",
+ 0x12: "It looks sus? Trust me,"
+ "my wares are genuine.",
+ 0x15: "This non-volatile kind\n"
+ "is safe to handle.",
+ 0x16: "If you can soul-wield,\n"
+ "they have a good one!",
+ 0x17: "Calls the morning sun\n"
+ "to vanquish the night.",
+ 0x18: "1 on-demand horrible\n"
+ "night. Devils love it!",
+ 0x1A: "Want to study here?\n"
+ "It will cost you.",
+ 0x1B: "\"Let them eat cake!\"\n"
+ "Said no princess ever.",
+ 0x1C: "Why do I suspect this\n"
+ "was a toilet room?",
+ 0x1D: "When you see Coller,\n"
+ "tell him I said hi!",
+ 0x1E: "Atomic number is 29\n"
+ "and weight is 63.546.",
+ 0x1F: "One torture per pay!\n"
+ "Who will it be?",
+ 0x20: "Being here feels like\n"
+ "time is slowing down.",
+ 0x21: "Only one thing beind\n"
+ "this. Do you dare?",
+ 0x22: "The key 2 Science!\n"
+ "Both halves of it!",
+ 0x23: "This warehouse can\n"
+ "be yours for a fee.",
+ 0x24: "Long road ahead if you\n"
+ "don't have the others.",
+ 0x25: "Will you get the curse\n"
+ "of eternal burning?",
+ 0x26: "What's beyond time?\n"
+ "Find out your",
+ 0x27: "Want to take out a\n"
+ "loan? By all means!",
+ 0x28: "The bag is green,\n"
+ "so it must be lucky!",
+ 0x29: "(Does this fool realize?)\n"
+ "Oh, sorry.",
+ "prog": "They will absolutely\n"
+ "need it in time!",
+ "useful": "Now, this would be\n"
+ "useful to send...",
+ "common": "Every last little bit\n"
+ "helps, right?",
+ "trap": "I'll teach this fool\n"
+ " a lesson for a price!",
+ "dlc coin": "1 coin out of... wha!?\n"
+ "You imp, why I oughta!"
+}
+
+
+def randomize_lighting(world: "CV64World") -> Dict[int, int]:
+ """Generates randomized data for the map lighting table."""
+ randomized_lighting = {}
+ for entry in range(67):
+ for sub_entry in range(19):
+ if sub_entry not in [3, 7, 11, 15] and entry != 4:
+ # The fourth entry in the lighting table affects the lighting on some item pickups; skip it
+ randomized_lighting[0x1091A0 + (entry * 28) + sub_entry] = \
+ world.random.randint(0, 255)
+ return randomized_lighting
+
+
+def shuffle_sub_weapons(world: "CV64World") -> Dict[int, int]:
+ """Shuffles the sub-weapons amongst themselves."""
+ sub_weapon_dict = {offset: rom_sub_weapon_offsets[offset][0] for offset in rom_sub_weapon_offsets if
+ rom_sub_weapon_offsets[offset][1] in world.active_stage_exits}
+
+ # Remove the one 3HB sub-weapon in Tower of Execution if 3HBs are not shuffled.
+ if not world.options.multi_hit_breakables and 0x10CD65 in sub_weapon_dict:
+ del (sub_weapon_dict[0x10CD65])
+
+ sub_bytes = list(sub_weapon_dict.values())
+ world.random.shuffle(sub_bytes)
+ return dict(zip(sub_weapon_dict, sub_bytes))
+
+
+def randomize_music(world: "CV64World") -> Dict[int, int]:
+ """Generates randomized or disabled data for all the music in the game."""
+ music_array = bytearray(0x7A)
+ for number in music_sfx_ids:
+ music_array[number] = number
+ if world.options.background_music == BackgroundMusic.option_randomized:
+ looping_songs = []
+ non_looping_songs = []
+ fade_in_songs = {}
+ # Create shuffle-able lists of all the looping, non-looping, and fade-in track IDs
+ for i in range(0x10, len(music_array)):
+ if i not in rom_looping_music_fade_ins.keys() and i not in rom_looping_music_fade_ins.values() and \
+ i != 0x72: # Credits song is blacklisted
+ non_looping_songs.append(i)
+ elif i in rom_looping_music_fade_ins.keys():
+ looping_songs.append(i)
+ elif i in rom_looping_music_fade_ins.values():
+ fade_in_songs[i] = i
+ # Shuffle the looping songs
+ rando_looping_songs = looping_songs.copy()
+ world.random.shuffle(rando_looping_songs)
+ looping_songs = dict(zip(looping_songs, rando_looping_songs))
+ # Shuffle the non-looping songs
+ rando_non_looping_songs = non_looping_songs.copy()
+ world.random.shuffle(rando_non_looping_songs)
+ non_looping_songs = dict(zip(non_looping_songs, rando_non_looping_songs))
+ non_looping_songs[0x72] = 0x72
+ # Figure out the new fade-in songs if applicable
+ for vanilla_song in looping_songs:
+ if rom_looping_music_fade_ins[vanilla_song]:
+ if rom_looping_music_fade_ins[looping_songs[vanilla_song]]:
+ fade_in_songs[rom_looping_music_fade_ins[vanilla_song]] = rom_looping_music_fade_ins[
+ looping_songs[vanilla_song]]
+ else:
+ fade_in_songs[rom_looping_music_fade_ins[vanilla_song]] = looping_songs[vanilla_song]
+ # Build the new music array
+ for i in range(0x10, len(music_array)):
+ if i in looping_songs.keys():
+ music_array[i] = looping_songs[i]
+ elif i in non_looping_songs.keys():
+ music_array[i] = non_looping_songs[i]
+ else:
+ music_array[i] = fade_in_songs[i]
+ del (music_array[0x00: 0x10])
+
+ # Convert the music array into a data dict
+ music_offsets = {}
+ for i in range(len(music_array)):
+ music_offsets[0xBFCD30 + i] = music_array[i]
+
+ return music_offsets
+
+
+def randomize_shop_prices(world: "CV64World") -> Dict[int, int]:
+ """Randomize the shop prices based on the minimum and maximum values chosen.
+ The minimum price will adjust if it's higher than the max."""
+ min_price = world.options.minimum_gold_price.value
+ max_price = world.options.maximum_gold_price.value
+
+ if min_price > max_price:
+ min_price = world.random.randint(0, max_price)
+ logging.warning(f"[{world.multiworld.player_name[world.player]}] The Minimum Gold Price "
+ f"({world.options.minimum_gold_price.value * 100}) is higher than the "
+ f"Maximum Gold Price ({max_price * 100}). Lowering the minimum to: {min_price * 100}")
+ world.options.minimum_gold_price.value = min_price
+
+ shop_price_list = [world.random.randint(min_price * 100, max_price * 100) for _ in range(7)]
+
+ # Convert the price list into a data dict. Which offset it starts from depends on how many bytes it takes up.
+ price_dict = {}
+ for i in range(len(shop_price_list)):
+ if shop_price_list[i] <= 0xFF:
+ price_dict[0x103D6E + (i*12)] = 0
+ price_dict[0x103D6F + (i*12)] = shop_price_list[i]
+ elif shop_price_list[i] <= 0xFFFF:
+ price_dict[0x103D6E + (i*12)] = shop_price_list[i]
+ else:
+ price_dict[0x103D6D + (i*12)] = shop_price_list[i]
+
+ return price_dict
+
+
+def get_countdown_numbers(options: CV64Options, active_locations: Iterable[Location]) -> Dict[int, int]:
+ """Figures out which Countdown numbers to increase for each Location after verifying the Item on the Location should
+ increase a number.
+
+ First, check the location's info to see if it has a countdown number override.
+ If not, then figure it out based on the parent region's stage's position in the vanilla stage order.
+ If the parent region is not part of any stage (as is the case for Renon's shop), skip the location entirely."""
+ countdown_list = [0 for _ in range(15)]
+ for loc in active_locations:
+ if loc.address is not None and (options.countdown == Countdown.option_all_locations or
+ (options.countdown == Countdown.option_majors
+ and loc.item.advancement)):
+
+ countdown_number = get_location_info(loc.name, "countdown")
+
+ if countdown_number is None:
+ stage = get_region_info(loc.parent_region.name, "stage")
+ if stage is not None:
+ countdown_number = vanilla_stage_order.index(stage)
+
+ if countdown_number is not None:
+ countdown_list[countdown_number] += 1
+
+ # Convert the Countdown list into a data dict
+ countdown_dict = {}
+ for i in range(len(countdown_list)):
+ countdown_dict[0xBFD818 + i] = countdown_list[i]
+
+ return countdown_dict
+
+
+def get_location_data(world: "CV64World", active_locations: Iterable[Location]) \
+ -> Tuple[Dict[int, int], List[str], List[bytearray], List[List[Union[int, str, None]]]]:
+ """Gets ALL the item data to go into the ROM. Item data consists of two bytes: the first dictates the appearance of
+ the item, the second determines what the item actually is when picked up. All items from other worlds will be AP
+ items that do nothing when picked up other than set their flag, and their appearance will depend on whether it's
+ another CV64 player's item and, if so, what item it is in their game. Ice Traps can assume the form of any item that
+ is progression, non-progression, or either depending on the player's settings.
+
+ Appearance does not matter if it's one of the two NPC-given items (from either Vincent or Heinrich Meyer). For
+ Renon's shop items, a list containing the shop item names, descriptions, and colors will be returned alongside the
+ regular data."""
+
+ # Figure out the list of possible Ice Trap appearances to use based on the settings, first and foremost.
+ if world.options.ice_trap_appearance == IceTrapAppearance.option_major_only:
+ allowed_classifications = ["progression", "progression skip balancing"]
+ elif world.options.ice_trap_appearance == IceTrapAppearance.option_junk_only:
+ allowed_classifications = ["filler", "useful"]
+ else:
+ allowed_classifications = ["progression", "progression skip balancing", "filler", "useful"]
+
+ trap_appearances = []
+ for item in item_info:
+ if item_info[item]["default classification"] in allowed_classifications and item != "Ice Trap" and \
+ get_item_info(item, "code") is not None:
+ trap_appearances.append(item)
+
+ shop_name_list = []
+ shop_desc_list = []
+ shop_colors_list = []
+
+ location_bytes = {}
+
+ for loc in active_locations:
+ # If the Location is an event, skip it.
+ if loc.address is None:
+ continue
+
+ loc_type = get_location_info(loc.name, "type")
+
+ # Figure out the item ID bytes to put in each Location here. Write the item itself if either it's the player's
+ # very own, or it belongs to an Item Link that the player is a part of.
+ if loc.item.player == world.player or (loc.item.player in world.multiworld.groups and
+ world.player in world.multiworld.groups[loc.item.player]['players']):
+ if loc_type not in ["npc", "shop"] and get_item_info(loc.item.name, "pickup actor id") is not None:
+ location_bytes[get_location_info(loc.name, "offset")] = get_item_info(loc.item.name, "pickup actor id")
+ else:
+ location_bytes[get_location_info(loc.name, "offset")] = get_item_info(loc.item.name, "code")
+ else:
+ # Make the item the unused Wooden Stake - our multiworld item.
+ location_bytes[get_location_info(loc.name, "offset")] = 0x11
+
+ # Figure out the item's appearance. If it's a CV64 player's item, change the multiworld item's model to
+ # match what it is. Otherwise, change it to an Archipelago progress or not progress icon. The model "change"
+ # has to be applied to even local items because this is how the game knows to count it on the Countdown.
+ if loc.item.game == "Castlevania 64":
+ location_bytes[get_location_info(loc.name, "offset") - 1] = get_item_info(loc.item.name, "code")
+ elif loc.item.advancement:
+ location_bytes[get_location_info(loc.name, "offset") - 1] = 0x11 # Wooden Stakes are majors
+ else:
+ location_bytes[get_location_info(loc.name, "offset") - 1] = 0x12 # Roses are minors
+
+ # If it's a PermaUp, change the item's model to a big PowerUp no matter what.
+ if loc.item.game == "Castlevania 64" and loc.item.code == 0x10C + base_id:
+ location_bytes[get_location_info(loc.name, "offset") - 1] = 0x0B
+
+ # If it's an Ice Trap, change its model to one of the appearances we determined before.
+ # Unless it's an NPC item, in which case use the Ice Trap's regular ID so that it won't decrement the majors
+ # Countdown due to how I set up the NPC items to work.
+ if loc.item.game == "Castlevania 64" and loc.item.code == 0x12 + base_id:
+ if loc_type == "npc":
+ location_bytes[get_location_info(loc.name, "offset") - 1] = 0x12
+ else:
+ location_bytes[get_location_info(loc.name, "offset") - 1] = \
+ get_item_info(world.random.choice(trap_appearances), "code")
+ # If we chose a PermaUp as our trap appearance, change it to its actual in-game ID of 0x0B.
+ if location_bytes[get_location_info(loc.name, "offset") - 1] == 0x10C:
+ location_bytes[get_location_info(loc.name, "offset") - 1] = 0x0B
+
+ # Apply the invisibility variable depending on the "invisible items" setting.
+ if (world.options.invisible_items == InvisibleItems.option_vanilla and loc_type == "inv") or \
+ (world.options.invisible_items == InvisibleItems.option_hide_all and loc_type not in ["npc", "shop"]):
+ location_bytes[get_location_info(loc.name, "offset") - 1] += 0x80
+ elif world.options.invisible_items == InvisibleItems.option_chance and loc_type not in ["npc", "shop"]:
+ invisible = world.random.randint(0, 1)
+ if invisible:
+ location_bytes[get_location_info(loc.name, "offset") - 1] += 0x80
+
+ # If it's an Axe or Cross in a higher freestanding location, lower it into grab range.
+ # KCEK made these spawn 3.2 units higher for some reason.
+ if loc.address & 0xFFF in rom_axe_cross_lower_values and loc.item.code & 0xFF in [0x0F, 0x10]:
+ location_bytes[rom_axe_cross_lower_values[loc.address & 0xFFF][0]] = \
+ rom_axe_cross_lower_values[loc.address & 0xFFF][1]
+
+ # Figure out the list of shop names, descriptions, and text colors here.
+ if loc.parent_region.name != rname.renon:
+ continue
+
+ shop_name = loc.item.name
+ if len(shop_name) > 18:
+ shop_name = shop_name[0:18]
+ shop_name_list.append(shop_name)
+
+ if loc.item.player == world.player:
+ shop_desc_list.append([get_item_info(loc.item.name, "code"), None])
+ elif loc.item.game == "Castlevania 64":
+ shop_desc_list.append([get_item_info(loc.item.name, "code"),
+ world.multiworld.get_player_name(loc.item.player)])
+ else:
+ if loc.item.game == "DLCQuest" and loc.item.name in ["DLC Quest: Coin Bundle",
+ "Live Freemium or Die: Coin Bundle"]:
+ if getattr(world.multiworld.worlds[loc.item.player].options, "coinbundlequantity") == 1:
+ shop_desc_list.append(["dlc coin", world.multiworld.get_player_name(loc.item.player)])
+ shop_colors_list.append(get_item_text_color(loc))
+ continue
+
+ if loc.item.advancement:
+ shop_desc_list.append(["prog", world.multiworld.get_player_name(loc.item.player)])
+ elif loc.item.classification == ItemClassification.useful:
+ shop_desc_list.append(["useful", world.multiworld.get_player_name(loc.item.player)])
+ elif loc.item.classification == ItemClassification.trap:
+ shop_desc_list.append(["trap", world.multiworld.get_player_name(loc.item.player)])
+ else:
+ shop_desc_list.append(["common", world.multiworld.get_player_name(loc.item.player)])
+
+ shop_colors_list.append(get_item_text_color(loc))
+
+ return location_bytes, shop_name_list, shop_colors_list, shop_desc_list
+
+
+def get_loading_zone_bytes(options: CV64Options, starting_stage: str,
+ active_stage_exits: Dict[str, Dict[str, Union[str, int, None]]]) -> Dict[int, int]:
+ """Figure out all the bytes for loading zones and map transitions based on which stages are where in the exit data.
+ The same data was used earlier in figuring out the logic. Map transitions consist of two major components: which map
+ to send the player to, and which spot within the map to spawn the player at."""
+
+ # Write the byte for the starting stage to send the player to after the intro narration.
+ loading_zone_bytes = {0xB73308: get_stage_info(starting_stage, "start map id")}
+
+ for stage in active_stage_exits:
+
+ # Start loading zones
+ # If the start zone is the start of the line, have it simply refresh the map.
+ if active_stage_exits[stage]["prev"] == "Menu":
+ loading_zone_bytes[get_stage_info(stage, "startzone map offset")] = 0xFF
+ loading_zone_bytes[get_stage_info(stage, "startzone spawn offset")] = 0x00
+ elif active_stage_exits[stage]["prev"]:
+ loading_zone_bytes[get_stage_info(stage, "startzone map offset")] = \
+ get_stage_info(active_stage_exits[stage]["prev"], "end map id")
+ loading_zone_bytes[get_stage_info(stage, "startzone spawn offset")] = \
+ get_stage_info(active_stage_exits[stage]["prev"], "end spawn id")
+
+ # Change CC's end-spawn ID to put you at Carrie's exit if appropriate
+ if active_stage_exits[stage]["prev"] == rname.castle_center:
+ if options.character_stages == CharacterStages.option_carrie_only or \
+ active_stage_exits[rname.castle_center]["alt"] == stage:
+ loading_zone_bytes[get_stage_info(stage, "startzone spawn offset")] += 1
+
+ # End loading zones
+ if active_stage_exits[stage]["next"]:
+ loading_zone_bytes[get_stage_info(stage, "endzone map offset")] = \
+ get_stage_info(active_stage_exits[stage]["next"], "start map id")
+ loading_zone_bytes[get_stage_info(stage, "endzone spawn offset")] = \
+ get_stage_info(active_stage_exits[stage]["next"], "start spawn id")
+
+ # Alternate end loading zones
+ if active_stage_exits[stage]["alt"]:
+ loading_zone_bytes[get_stage_info(stage, "altzone map offset")] = \
+ get_stage_info(active_stage_exits[stage]["alt"], "start map id")
+ loading_zone_bytes[get_stage_info(stage, "altzone spawn offset")] = \
+ get_stage_info(active_stage_exits[stage]["alt"], "start spawn id")
+
+ return loading_zone_bytes
+
+
+def get_start_inventory_data(player: int, options: CV64Options, precollected_items: List[Item]) -> Dict[int, int]:
+ """Calculate and return the starting inventory values. Not every Item goes into the menu inventory, so everything
+ has to be handled appropriately."""
+ start_inventory_data = {0xBFD867: 0, # Jewels
+ 0xBFD87B: 0, # PowerUps
+ 0xBFD883: 0, # Sub-weapon
+ 0xBFD88B: 0} # Ice Traps
+
+ inventory_items_array = [0 for _ in range(35)]
+ total_money = 0
+
+ items_max = 10
+
+ # Raise the items max if Increase Item Limit is enabled.
+ if options.increase_item_limit:
+ items_max = 99
+
+ for item in precollected_items:
+ if item.player != player:
+ continue
+
+ inventory_offset = get_item_info(item.name, "inventory offset")
+ sub_equip_id = get_item_info(item.name, "sub equip id")
+ # Starting inventory items
+ if inventory_offset is not None:
+ inventory_items_array[inventory_offset] += 1
+ if inventory_items_array[inventory_offset] > items_max and "Special" not in item.name:
+ inventory_items_array[inventory_offset] = items_max
+ if item.name == iname.permaup:
+ if inventory_items_array[inventory_offset] > 2:
+ inventory_items_array[inventory_offset] = 2
+ # Starting sub-weapon
+ elif sub_equip_id is not None:
+ start_inventory_data[0xBFD883] = sub_equip_id
+ # Starting PowerUps
+ elif item.name == iname.powerup:
+ start_inventory_data[0xBFD87B] += 1
+ if start_inventory_data[0xBFD87B] > 2:
+ start_inventory_data[0xBFD87B] = 2
+ # Starting Gold
+ elif "GOLD" in item.name:
+ total_money += int(item.name[0:4])
+ if total_money > 99999:
+ total_money = 99999
+ # Starting Jewels
+ elif "jewel" in item.name:
+ if "L" in item.name:
+ start_inventory_data[0xBFD867] += 10
+ else:
+ start_inventory_data[0xBFD867] += 5
+ if start_inventory_data[0xBFD867] > 99:
+ start_inventory_data[0xBFD867] = 99
+ # Starting Ice Traps
+ else:
+ start_inventory_data[0xBFD88B] += 1
+ if start_inventory_data[0xBFD88B] > 0xFF:
+ start_inventory_data[0xBFD88B] = 0xFF
+
+ # Convert the inventory items into data.
+ for i in range(len(inventory_items_array)):
+ start_inventory_data[0xBFE518 + i] = inventory_items_array[i]
+
+ # Convert the starting money into data. Which offset it starts from depends on how many bytes it takes up.
+ if total_money <= 0xFF:
+ start_inventory_data[0xBFE517] = total_money
+ elif total_money <= 0xFFFF:
+ start_inventory_data[0xBFE516] = total_money
+ else:
+ start_inventory_data[0xBFE515] = total_money
+
+ return start_inventory_data
+
+
+def get_item_text_color(loc: Location) -> bytearray:
+ if loc.item.advancement:
+ return bytearray([0xA2, 0x0C])
+ elif loc.item.classification == ItemClassification.useful:
+ return bytearray([0xA2, 0x0A])
+ elif loc.item.classification == ItemClassification.trap:
+ return bytearray([0xA2, 0x0B])
+ else:
+ return bytearray([0xA2, 0x02])
diff --git a/worlds/cv64/client.py b/worlds/cv64/client.py
new file mode 100644
index 00000000000..ff9c79f578b
--- /dev/null
+++ b/worlds/cv64/client.py
@@ -0,0 +1,207 @@
+from typing import TYPE_CHECKING, Set
+from .locations import base_id
+from .text import cv64_text_wrap, cv64_string_to_bytearray
+
+from NetUtils import ClientStatus
+import worlds._bizhawk as bizhawk
+import base64
+from worlds._bizhawk.client import BizHawkClient
+
+if TYPE_CHECKING:
+ from worlds._bizhawk.context import BizHawkClientContext
+
+
+class Castlevania64Client(BizHawkClient):
+ game = "Castlevania 64"
+ system = "N64"
+ patch_suffix = ".apcv64"
+ self_induced_death = False
+ received_deathlinks = 0
+ death_causes = []
+ currently_shopping = False
+ local_checked_locations: Set[int]
+
+ def __init__(self) -> None:
+ super().__init__()
+ self.local_checked_locations = set()
+
+ async def validate_rom(self, ctx: "BizHawkClientContext") -> bool:
+ from CommonClient import logger
+
+ try:
+ # Check ROM name/patch version
+ game_names = await bizhawk.read(ctx.bizhawk_ctx, [(0x20, 0x14, "ROM"), (0xBFBFD0, 12, "ROM")])
+ if game_names[0].decode("ascii") != "CASTLEVANIA ":
+ return False
+ if game_names[1] == b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00':
+ logger.info("ERROR: You appear to be running an unpatched version of Castlevania 64. "
+ "You need to generate a patch file and use it to create a patched ROM.")
+ return False
+ if game_names[1].decode("ascii") != "ARCHIPELAGO1":
+ logger.info("ERROR: The patch file used to create this ROM is not compatible with "
+ "this client. Double check your client version against the version being "
+ "used by the generator.")
+ return False
+ except UnicodeDecodeError:
+ return False
+ except bizhawk.RequestFailedError:
+ return False # Should verify on the next pass
+
+ ctx.game = self.game
+ ctx.items_handling = 0b001
+ ctx.want_slot_data = False
+ ctx.watcher_timeout = 0.125
+ return True
+
+ async def set_auth(self, ctx: "BizHawkClientContext") -> None:
+ auth_raw = (await bizhawk.read(ctx.bizhawk_ctx, [(0xBFBFE0, 16, "ROM")]))[0]
+ ctx.auth = base64.b64encode(auth_raw).decode("utf-8")
+
+ def on_package(self, ctx: "BizHawkClientContext", cmd: str, args: dict) -> None:
+ if cmd != "Bounced":
+ return
+ if "tags" not in args:
+ return
+ if "DeathLink" in args["tags"] and args["data"]["source"] != ctx.slot_info[ctx.slot].name:
+ self.received_deathlinks += 1
+ if "cause" in args["data"]:
+ cause = args["data"]["cause"]
+ if len(cause) > 88:
+ cause = cause[0x00:0x89]
+ else:
+ cause = f"{args['data']['source']} killed you!"
+ self.death_causes.append(cause)
+
+ async def game_watcher(self, ctx: "BizHawkClientContext") -> None:
+
+ try:
+ read_state = await bizhawk.read(ctx.bizhawk_ctx, [(0x342084, 4, "RDRAM"),
+ (0x389BDE, 6, "RDRAM"),
+ (0x389BE4, 224, "RDRAM"),
+ (0x389EFB, 1, "RDRAM"),
+ (0x389EEF, 1, "RDRAM"),
+ (0xBFBFDE, 2, "ROM")])
+
+ game_state = int.from_bytes(read_state[0], "big")
+ save_struct = read_state[2]
+ written_deathlinks = int.from_bytes(bytearray(read_state[1][4:6]), "big")
+ deathlink_induced_death = int.from_bytes(bytearray(read_state[1][0:1]), "big")
+ cutscene_value = int.from_bytes(read_state[3], "big")
+ current_menu = int.from_bytes(read_state[4], "big")
+ num_received_items = int.from_bytes(bytearray(save_struct[0xDA:0xDC]), "big")
+ rom_flags = int.from_bytes(read_state[5], "big")
+
+ # Make sure we are in the Gameplay or Credits states before detecting sent locations and/or DeathLinks.
+ # If we are in any other state, such as the Game Over state, set self_induced_death to false, so we can once
+ # again send a DeathLink once we are back in the Gameplay state.
+ if game_state not in [0x00000002, 0x0000000B]:
+ self.self_induced_death = False
+ return
+
+ # Enable DeathLink if the bit for it is set in our ROM flags.
+ if "DeathLink" not in ctx.tags and rom_flags & 0x0100:
+ await ctx.update_death_link(True)
+
+ # Scout the Renon shop locations if the shopsanity flag is written in the ROM.
+ if rom_flags & 0x0001 and ctx.locations_info == {}:
+ await ctx.send_msgs([{
+ "cmd": "LocationScouts",
+ "locations": [base_id + i for i in range(0x1C8, 0x1CF)],
+ "create_as_hint": 0
+ }])
+
+ # Send a DeathLink if we died on our own independently of receiving another one.
+ if "DeathLink" in ctx.tags and save_struct[0xA4] & 0x80 and not self.self_induced_death and not \
+ deathlink_induced_death:
+ self.self_induced_death = True
+ if save_struct[0xA4] & 0x08:
+ # Special death message for dying while having the Vamp status.
+ await ctx.send_death(f"{ctx.player_names[ctx.slot]} became a vampire and drank your blood!")
+ else:
+ await ctx.send_death(f"{ctx.player_names[ctx.slot]} perished. Dracula has won!")
+
+ # Write any DeathLinks received along with the corresponding death cause starting with the oldest.
+ # To minimize Bizhawk Write jank, the DeathLink write will be prioritized over the item received one.
+ if self.received_deathlinks and not self.self_induced_death and not written_deathlinks:
+ death_text, num_lines = cv64_text_wrap(self.death_causes[0], 96)
+ await bizhawk.write(ctx.bizhawk_ctx, [(0x389BE3, [0x01], "RDRAM"),
+ (0x389BDF, [0x11], "RDRAM"),
+ (0x18BF98, bytearray([0xA2, 0x0B]) +
+ cv64_string_to_bytearray(death_text, False), "RDRAM"),
+ (0x18C097, [num_lines], "RDRAM")])
+ self.received_deathlinks -= 1
+ del self.death_causes[0]
+ else:
+ # If the game hasn't received all items yet, the received item struct doesn't contain an item, the
+ # current number of received items still matches what we read before, and there are no open text boxes,
+ # then fill it with the next item and write the "item from player" text in its buffer. The game will
+ # increment the number of received items on its own.
+ if num_received_items < len(ctx.items_received):
+ next_item = ctx.items_received[num_received_items]
+ if next_item.flags & 0b001:
+ text_color = bytearray([0xA2, 0x0C])
+ elif next_item.flags & 0b010:
+ text_color = bytearray([0xA2, 0x0A])
+ elif next_item.flags & 0b100:
+ text_color = bytearray([0xA2, 0x0B])
+ else:
+ text_color = bytearray([0xA2, 0x02])
+ received_text, num_lines = cv64_text_wrap(f"{ctx.item_names[next_item.item]}\n"
+ f"from {ctx.player_names[next_item.player]}", 96)
+ await bizhawk.guarded_write(ctx.bizhawk_ctx,
+ [(0x389BE1, [next_item.item & 0xFF], "RDRAM"),
+ (0x18C0A8, text_color + cv64_string_to_bytearray(received_text, False),
+ "RDRAM"),
+ (0x18C1A7, [num_lines], "RDRAM")],
+ [(0x389BE1, [0x00], "RDRAM"), # Remote item reward buffer
+ (0x389CBE, save_struct[0xDA:0xDC], "RDRAM"), # Received items
+ (0x342891, [0x02], "RDRAM")]) # Textbox state
+
+ flag_bytes = bytearray(save_struct[0x00:0x44]) + bytearray(save_struct[0x90:0x9F])
+ locs_to_send = set()
+
+ # Check for set location flags.
+ for byte_i, byte in enumerate(flag_bytes):
+ for i in range(8):
+ and_value = 0x80 >> i
+ if byte & and_value != 0:
+ flag_id = byte_i * 8 + i
+
+ location_id = flag_id + base_id
+ if location_id in ctx.server_locations:
+ locs_to_send.add(location_id)
+
+ # Send locations if there are any to send.
+ if locs_to_send != self.local_checked_locations:
+ self.local_checked_locations = locs_to_send
+
+ if locs_to_send is not None:
+ await ctx.send_msgs([{
+ "cmd": "LocationChecks",
+ "locations": list(locs_to_send)
+ }])
+
+ # Check the menu value to see if we are in Renon's shop, and set currently_shopping to True if we are.
+ if current_menu == 0xA:
+ self.currently_shopping = True
+
+ # If we are currently shopping, and the current menu value is 0 (meaning we just left the shop), hint the
+ # un-bought shop locations that have progression.
+ if current_menu == 0 and self.currently_shopping:
+ await ctx.send_msgs([{
+ "cmd": "LocationScouts",
+ "locations": [loc for loc, n_item in ctx.locations_info.items() if n_item.flags & 0b001],
+ "create_as_hint": 2
+ }])
+ self.currently_shopping = False
+
+ # Send game clear if we're in either any ending cutscene or the credits state.
+ if not ctx.finished_game and (0x26 <= int(cutscene_value) <= 0x2E or game_state == 0x0000000B):
+ await ctx.send_msgs([{
+ "cmd": "StatusUpdate",
+ "status": ClientStatus.CLIENT_GOAL
+ }])
+
+ except bizhawk.RequestFailedError:
+ # Exit handler and return to main loop to reconnect.
+ pass
diff --git a/worlds/cv64/data/APLogo-LICENSE.txt b/worlds/cv64/data/APLogo-LICENSE.txt
new file mode 100644
index 00000000000..69d1e3ecd13
--- /dev/null
+++ b/worlds/cv64/data/APLogo-LICENSE.txt
@@ -0,0 +1,3 @@
+The Archipelago Logo is © 2022 by Krista Corkos and Christopher Wilson is licensed under Attribution-NonCommercial 4.0 International. To view a copy of this license, visit http://creativecommons.org/licenses/by-nc/4.0/
+
+Logo modified by Liquid Cat to fit artstyle and uses within the mod.
diff --git a/worlds/cv64/data/ap_icons.bin b/worlds/cv64/data/ap_icons.bin
new file mode 100644
index 00000000000..0a4129ac409
Binary files /dev/null and b/worlds/cv64/data/ap_icons.bin differ
diff --git a/worlds/cv64/data/ename.py b/worlds/cv64/data/ename.py
new file mode 100644
index 00000000000..26cd7151de8
--- /dev/null
+++ b/worlds/cv64/data/ename.py
@@ -0,0 +1,71 @@
+forest_dbridge_gate = "Descending bridge gate"
+forest_werewolf_gate = "Werewolf gate"
+forest_end = "Dracula's drawbridge"
+
+cw_portcullis_c = "Central portcullis"
+cw_lt_skip = "Do Left Tower Skip"
+cw_lt_door = "Left Tower door"
+cw_end = "End portcullis"
+
+villa_dog_gates = "Front dog gates"
+villa_snipe_dogs = "Orb snipe the dogs"
+villa_to_storeroom = "To Storeroom door"
+villa_to_archives = "To Archives door"
+villa_renon = "Villa contract"
+villa_to_maze = "To maze gate"
+villa_from_storeroom = "From Storeroom door"
+villa_from_maze = "From maze gate"
+villa_servant_door = "Servants' door"
+villa_copper_door = "Copper door"
+villa_copper_skip = "Get Copper Skip"
+villa_bridge_door = "From bridge door"
+villa_end_r = "Villa Reinhardt (daytime) exit"
+villa_end_c = "Villa Carrie (nighttime) exit"
+
+tunnel_start_renon = "Tunnel start contract"
+tunnel_gondolas = "Gondola ride"
+tunnel_end_renon = "Tunnel end contract"
+tunnel_end = "End Tunnel door"
+
+uw_final_waterfall = "Final waterfall"
+uw_waterfall_skip = "Do Waterfall Skip"
+uw_renon = "Underground Waterway contract"
+uw_end = "End Waterway door"
+
+cc_tc_door = "Torture Chamber door"
+cc_renon = "Castle Center contract"
+cc_lower_wall = "Lower sealed cracked wall"
+cc_upper_wall = "Upper cracked wall"
+cc_elevator = "Activate crystal and ride elevator"
+cc_exit_r = "Castle Center Reinhardt (Medusa Head) exit"
+cc_exit_c = "Castle Center Carrie (Ghost) exit"
+
+dt_start = "Duel Tower start passage"
+dt_end = "Duel Tower end passage"
+
+toe_start = "Tower of Execution start passage"
+toe_gate = "Execution gate"
+toe_gate_skip = "Just jump past the gate from above, bro!"
+toe_end = "Tower of Execution end staircase"
+
+tosci_start = "Tower of Science start passage"
+tosci_key1_door = "Science Door 1"
+tosci_to_key2_door = "To Science Door 2"
+tosci_from_key2_door = "From Science Door 2"
+tosci_key3_door = "Science Door 3"
+tosci_end = "Tower of Science end passage"
+
+tosor_start = "Tower of Sorcery start passage"
+tosor_end = "Tower of Sorcery end passage"
+
+roc_gate = "Defeat boss gate"
+
+ct_to_door1 = "To Clocktower Door 1"
+ct_from_door1 = "From Clocktower Door 1"
+ct_to_door2 = "To Clocktower Door 2"
+ct_from_door2 = "From Clocktower Door 2"
+ct_renon = "Clock Tower contract"
+ct_door_3 = "Clocktower Door 3"
+
+ck_slope_jump = "Slope Jump to boss tower"
+ck_drac_door = "Dracula's door"
diff --git a/worlds/cv64/data/iname.py b/worlds/cv64/data/iname.py
new file mode 100644
index 00000000000..9b9e225ca5c
--- /dev/null
+++ b/worlds/cv64/data/iname.py
@@ -0,0 +1,49 @@
+# Items
+white_jewel = "White jewel"
+special_one = "Special1"
+special_two = "Special2"
+red_jewel_s = "Red jewel(S)"
+red_jewel_l = "Red jewel(L)"
+roast_chicken = "Roast chicken"
+roast_beef = "Roast beef"
+healing_kit = "Healing kit"
+purifying = "Purifying"
+cure_ampoule = "Cure ampoule"
+pot_pourri = "pot-pourri"
+powerup = "PowerUp"
+permaup = "PermaUp"
+holy_water = "Holy water"
+cross = "Cross"
+axe = "Axe"
+knife = "Knife"
+wooden_stake = "Wooden stake"
+rose = "Rose"
+ice_trap = "Ice Trap"
+the_contract = "The contract"
+engagement_ring = "engagement ring"
+magical_nitro = "Magical Nitro"
+mandragora = "Mandragora"
+sun_card = "Sun card"
+moon_card = "Moon card"
+incandescent_gaze = "Incandescent gaze"
+five_hundred_gold = "500 GOLD"
+three_hundred_gold = "300 GOLD"
+one_hundred_gold = "100 GOLD"
+archives_key = "Archives Key"
+left_tower_key = "Left Tower Key"
+storeroom_key = "Storeroom Key"
+garden_key = "Garden Key"
+copper_key = "Copper Key"
+chamber_key = "Chamber Key"
+execution_key = "Execution Key"
+science_key1 = "Science Key1"
+science_key2 = "Science Key2"
+science_key3 = "Science Key3"
+clocktower_key1 = "Clocktower Key1"
+clocktower_key2 = "Clocktower Key2"
+clocktower_key3 = "Clocktower Key3"
+
+trophy = "Trophy"
+crystal = "Crystal"
+
+victory = "The Count Downed"
diff --git a/worlds/cv64/data/lname.py b/worlds/cv64/data/lname.py
new file mode 100644
index 00000000000..09db86b3808
--- /dev/null
+++ b/worlds/cv64/data/lname.py
@@ -0,0 +1,479 @@
+# Forest of Silence main locations
+forest_pillars_right = "Forest of Silence: Grab practice pillars - Right"
+forest_pillars_top = "Forest of Silence: Grab practice pillars - Top"
+forest_king_skeleton = "Forest of Silence: King Skeleton's bridge"
+forest_lgaz_in = "Forest of Silence: Moon gazebo inside"
+forest_lgaz_top = "Forest of Silence: Moon gazebo roof"
+forest_hgaz_in = "Forest of Silence: Sun gazebo inside"
+forest_hgaz_top = "Forest of Silence: Sun gazebo roof"
+forest_weretiger_sw = "Forest of Silence: Were-tiger switch"
+forest_weretiger_gate = "Forest of Silence: Dirge maiden gate"
+forest_dirge_tomb_u = "Forest of Silence: Dirge maiden crypt - Upper"
+forest_dirge_plaque = "Forest of Silence: Dirge maiden pedestal plaque"
+forest_corpse_save = "Forest of Silence: Tri-corpse save junction"
+forest_dbridge_wall = "Forest of Silence: Descending bridge wall side"
+forest_dbridge_sw = "Forest of Silence: Descending bridge switch side"
+forest_dbridge_gate_r = "Forest of Silence: Tri-corpse gate - Right"
+forest_dbridge_tomb_uf = "Forest of Silence: Three-crypt plaza main path crypt - Upper-front"
+forest_bface_tomb_lf = "Forest of Silence: Three-crypt plaza back-facing crypt - Lower-front"
+forest_bface_tomb_u = "Forest of Silence: Three-crypt plaza back-facing crypt - Upper"
+forest_ibridge = "Forest of Silence: Invisible bridge platform"
+forest_werewolf_tomb_r = "Forest of Silence: Werewolf crypt - Right"
+forest_werewolf_plaque = "Forest of Silence: Werewolf statue plaque"
+forest_werewolf_tree = "Forest of Silence: Werewolf path near tree"
+forest_final_sw = "Forest of Silence: Three-crypt plaza switch"
+
+# Forest of Silence empty breakables
+forest_dirge_tomb_l = "Forest of Silence: Dirge maiden crypt - Lower"
+forest_dbridge_tomb_l = "Forest of Silence: Three-crypt plaza main path crypt - Lower"
+forest_dbridge_tomb_ur = "Forest of Silence: Three-crypt plaza main path crypt - Upper-rear"
+forest_bface_tomb_lr = "Forest of Silence: Three-crypt plaza back-facing crypt - Lower-rear"
+forest_werewolf_tomb_lf = "Forest of Silence: Werewolf crypt - Left-front"
+forest_werewolf_tomb_lr = "Forest of Silence: Werewolf crypt - Left-rear"
+
+# Forest of Silence 3-hit breakables
+forest_dirge_rock1 = "Forest of Silence: Dirge maiden rock - Item 1"
+forest_dirge_rock2 = "Forest of Silence: Dirge maiden rock - Item 2"
+forest_dirge_rock3 = "Forest of Silence: Dirge maiden rock - Item 3"
+forest_dirge_rock4 = "Forest of Silence: Dirge maiden rock - Item 4"
+forest_dirge_rock5 = "Forest of Silence: Dirge maiden rock - Item 5"
+forest_bridge_rock1 = "Forest of Silence: Bat archway rock - Item 1"
+forest_bridge_rock2 = "Forest of Silence: Bat archway rock - Item 2"
+forest_bridge_rock3 = "Forest of Silence: Bat archway rock - Item 3"
+forest_bridge_rock4 = "Forest of Silence: Bat archway rock - Item 4"
+
+# Forest of Silence sub-weapons
+forest_pillars_left = "Forest of Silence: Grab practice pillars - Left"
+forest_dirge_ped = "Forest of Silence: Dirge maiden pedestal"
+forest_dbridge_gate_l = "Forest of Silence: Tri-corpse gate - Left"
+forest_werewolf_island = "Forest of Silence: Werewolf path island switch platforms"
+
+
+# Castle Wall main locations
+cw_ground_middle = "Castle Wall: Ground gatehouse - Middle"
+cw_rrampart = "Castle Wall: Central rampart near right tower"
+cw_lrampart = "Castle Wall: Central rampart near left tower"
+cw_dragon_sw = "Castle Wall: White Dragons switch door"
+cw_drac_sw = "Castle Wall: Dracula cutscene switch door"
+cw_shelf_visible = "Castle Wall: Sandbag shelf - Visible"
+cw_shelf_sandbags = "Castle Wall: Sandbag shelf - Invisible"
+
+# Castle Wall towers main locations
+cwr_bottom = "Castle Wall: Above bottom right tower door"
+cwl_bottom = "Castle Wall: Above bottom left tower door"
+cwl_bridge = "Castle Wall: Left tower child ledge"
+
+# Castle Wall 3-hit breakables
+cw_save_slab1 = "Castle Wall: Central rampart savepoint slab - Item 1"
+cw_save_slab2 = "Castle Wall: Central rampart savepoint slab - Item 2"
+cw_save_slab3 = "Castle Wall: Central rampart savepoint slab - Item 3"
+cw_save_slab4 = "Castle Wall: Central rampart savepoint slab - Item 4"
+cw_save_slab5 = "Castle Wall: Central rampart savepoint slab - Item 5"
+cw_drac_slab1 = "Castle Wall: Dracula cutscene switch slab - Item 1"
+cw_drac_slab2 = "Castle Wall: Dracula cutscene switch slab - Item 2"
+cw_drac_slab3 = "Castle Wall: Dracula cutscene switch slab - Item 3"
+cw_drac_slab4 = "Castle Wall: Dracula cutscene switch slab - Item 4"
+cw_drac_slab5 = "Castle Wall: Dracula cutscene switch slab - Item 5"
+
+# Castle Wall sub-weapons
+cw_ground_left = "Castle Wall: Ground gatehouse - Left"
+cw_ground_right = "Castle Wall: Ground gatehouse - Right"
+cw_shelf_torch = "Castle Wall: Sandbag shelf floor torch"
+cw_pillar = "Castle Wall: Central rampart broken pillar"
+
+
+# Villa front yard main locations
+villafy_outer_gate_l = "Villa: Outer front gate - Left"
+villafy_outer_gate_r = "Villa: Outer front gate - Right"
+villafy_inner_gate = "Villa: Inner front gate dog food"
+villafy_dog_platform = "Villa: Outer front gate platform"
+villafy_gate_marker = "Villa: Front yard cross grave near gates"
+villafy_villa_marker = "Villa: Front yard cross grave near porch"
+villafy_tombstone = "Villa: Front yard visitor's tombstone"
+villafy_fountain_fl = "Villa: Midnight fountain - Front-left"
+villafy_fountain_fr = "Villa: Midnight fountain - Front-right"
+villafy_fountain_ml = "Villa: Midnight fountain - Middle-left"
+villafy_fountain_mr = "Villa: Midnight fountain - Middle-right"
+villafy_fountain_rl = "Villa: Midnight fountain - Rear-left"
+villafy_fountain_rr = "Villa: Midnight fountain - Rear-right"
+
+# Villa foyer main locations
+villafo_sofa = "Villa: Foyer sofa"
+villafo_pot_r = "Villa: Foyer upper-right pot"
+villafo_pot_l = "Villa: Foyer upper-left pot"
+villafo_rear_r = "Villa: Foyer lower level - Rear-right"
+villafo_rear_l = "Villa: Foyer lower level - Rear-left"
+villafo_mid_l = "Villa: Foyer lower level - Middle-left"
+villafo_front_r = "Villa: Foyer lower level - Front-right"
+villafo_front_l = "Villa: Foyer lower level - Front-left"
+villafo_serv_ent = "Villa: Servants' entrance"
+
+# Villa empty breakables
+villafo_mid_r = "Villa: Foyer lower level - Middle-right"
+
+# Villa 3-hit breakables
+villafo_chandelier1 = "Villa: Foyer chandelier - Item 1"
+villafo_chandelier2 = "Villa: Foyer chandelier - Item 2"
+villafo_chandelier3 = "Villa: Foyer chandelier - Item 3"
+villafo_chandelier4 = "Villa: Foyer chandelier - Item 4"
+villafo_chandelier5 = "Villa: Foyer chandelier - Item 5"
+
+# Villa living area main locations
+villala_hallway_stairs = "Villa: Rose garden staircase bottom"
+villala_bedroom_chairs = "Villa: Bedroom near chairs"
+villala_bedroom_bed = "Villa: Bedroom near bed"
+villala_vincent = "Villa: Vincent"
+villala_slivingroom_table = "Villa: Mary's room table"
+villala_storeroom_l = "Villa: Storeroom - Left"
+villala_storeroom_r = "Villa: Storeroom - Right"
+villala_storeroom_s = "Villa: Storeroom statue"
+villala_diningroom_roses = "Villa: Dining room rose vase"
+villala_archives_table = "Villa: Archives table"
+villala_archives_rear = "Villa: Archives rear corner"
+villala_llivingroom_lion = "Villa: Living room lion head"
+villala_llivingroom_pot_r = "Villa: Living room - Right pot"
+villala_llivingroom_pot_l = "Villa: Living room - Left pot"
+villala_llivingroom_light = "Villa: Living room ceiling light"
+villala_llivingroom_painting = "Villa: Living room clawed painting"
+villala_exit_knight = "Villa: Maze garden exit knight"
+
+# Villa maze main locations
+villam_malus_torch = "Villa: Front maze garden - Malus start torch"
+villam_malus_bush = "Villa: Front maze garden - Malus's hiding bush"
+villam_frankieturf_r = "Villa: Front maze garden - Frankie's right dead-end"
+villam_frankieturf_l = "Villa: Front maze garden - Frankie's left dead-end"
+villam_frankieturf_ru = "Villa: Front maze garden - Frankie's right dead-end urn"
+villam_fgarden_f = "Villa: Rear maze garden - Iron Thorn Fenced area - Front"
+villam_fgarden_mf = "Villa: Rear maze garden - Iron Thorn Fenced area - Mid-front"
+villam_fgarden_mr = "Villa: Rear maze garden - Iron Thorn Fenced area - Mid-rear"
+villam_fgarden_r = "Villa: Rear maze garden - Iron Thorn Fenced area - Rear"
+villam_rplatform_de = "Villa: Rear maze garden - Viewing platform dead-end"
+villam_exit_de = "Villa: Rear maze garden - Past-exit dead-end"
+villam_serv_path = "Villa: Servants' path small alcove"
+villam_crypt_ent = "Villa: Crypt entrance"
+villam_crypt_upstream = "Villa: Crypt bridge upstream"
+
+# Villa crypt main locations
+villac_ent_l = "Villa: Crypt - Left from entrance"
+villac_ent_r = "Villa: Crypt - Right from entrance"
+villac_wall_l = "Villa: Crypt - Left wall"
+villac_wall_r = "Villa: Crypt - Right wall"
+villac_coffin_r = "Villa: Crypt - Right of coffin"
+
+# Villa sub-weapons
+villala_hallway_l = "Villa: Hallway near rose garden stairs - Left"
+villala_hallway_r = "Villa: Hallway near rose garden stairs - Right"
+villala_slivingroom_mirror = "Villa: Mary's room corner"
+villala_archives_entrance = "Villa: Archives near entrance"
+villam_fplatform = "Villa: Front maze garden - Viewing platform"
+villam_rplatform = "Villa: Rear maze garden - Viewing platform"
+villac_coffin_l = "Villa: Crypt - Left of coffin"
+
+
+# Tunnel main locations
+tunnel_landing = "Tunnel: Landing point"
+tunnel_landing_rc = "Tunnel: Landing point rock crusher"
+tunnel_stone_alcove_l = "Tunnel: Stepping stone alcove - Left"
+tunnel_twin_arrows = "Tunnel: Twin arrow signs"
+tunnel_lonesome_bucket = "Tunnel: Near lonesome bucket"
+tunnel_lbucket_quag = "Tunnel: Lonesome bucket poison pit"
+tunnel_lbucket_albert = "Tunnel: Lonesome bucket-Albert junction"
+tunnel_albert_camp = "Tunnel: Albert's campsite"
+tunnel_albert_quag = "Tunnel: Albert's poison pit"
+tunnel_gondola_rc_sdoor_r = "Tunnel: Gondola rock crusher sun door - Right"
+tunnel_gondola_rc_sdoor_m = "Tunnel: Gondola rock crusher sun door - Middle"
+tunnel_gondola_rc = "Tunnel: Gondola rock crusher"
+tunnel_rgondola_station = "Tunnel: Red gondola station"
+tunnel_gondola_transfer = "Tunnel: Gondola transfer point"
+tunnel_corpse_bucket_quag = "Tunnel: Corpse bucket poison pit"
+tunnel_corpse_bucket_mdoor_r = "Tunnel: Corpse bucket moon door - Right"
+tunnel_shovel_quag_start = "Tunnel: Shovel poison pit start"
+tunnel_exit_quag_start = "Tunnel: Exit door poison pit start"
+tunnel_shovel_quag_end = "Tunnel: Shovel poison pit end"
+tunnel_exit_quag_end = "Tunnel: Exit door poison pit end"
+tunnel_shovel = "Tunnel: Shovel"
+tunnel_shovel_save = "Tunnel: Shovel zone save junction"
+tunnel_shovel_mdoor_l = "Tunnel: Shovel zone moon door - Left"
+tunnel_shovel_sdoor_l = "Tunnel: Shovel zone sun door - Left"
+tunnel_shovel_sdoor_m = "Tunnel: Shovel zone sun door - Middle"
+
+# Tunnel 3-hit breakables
+tunnel_arrows_rock1 = "Tunnel: Twin arrow signs rock - Item 1"
+tunnel_arrows_rock2 = "Tunnel: Twin arrow signs rock - Item 2"
+tunnel_arrows_rock3 = "Tunnel: Twin arrow signs rock - Item 3"
+tunnel_arrows_rock4 = "Tunnel: Twin arrow signs rock - Item 4"
+tunnel_arrows_rock5 = "Tunnel: Twin arrow signs rock - Item 5"
+tunnel_bucket_quag_rock1 = "Tunnel: Lonesome bucket poison pit rock - Item 1"
+tunnel_bucket_quag_rock2 = "Tunnel: Lonesome bucket poison pit rock - Item 2"
+tunnel_bucket_quag_rock3 = "Tunnel: Lonesome bucket poison pit rock - Item 3"
+
+# Tunnel sub-weapons
+tunnel_stone_alcove_r = "Tunnel: Stepping stone alcove - Right"
+tunnel_lbucket_mdoor_l = "Tunnel: Lonesome bucket moon door"
+tunnel_gondola_rc_sdoor_l = "Tunnel: Gondola rock crusher sun door - Left"
+tunnel_corpse_bucket_mdoor_l = "Tunnel: Corpse bucket moon door - Left"
+tunnel_shovel_mdoor_r = "Tunnel: Shovel zone moon door - Right"
+tunnel_shovel_sdoor_r = "Tunnel: Shovel zone sun door - Right"
+
+
+# Underground Waterway main locations
+uw_near_ent = "Underground Waterway: Near entrance corridor"
+uw_across_ent = "Underground Waterway: Across from entrance"
+uw_poison_parkour = "Underground Waterway: Across poison parkour ledges"
+uw_waterfall_alcove = "Underground Waterway: Waterfall alcove ledge"
+uw_carrie1 = "Underground Waterway: Carrie crawlspace corridor - First left"
+uw_carrie2 = "Underground Waterway: Carrie crawlspace corridor - Second left"
+uw_bricks_save = "Underground Waterway: Brick platforms save corridor"
+uw_above_skel_ledge = "Underground Waterway: Above skeleton crusher ledge"
+
+# Underground Waterway 3-hit breakables
+uw_first_ledge1 = "Underground Waterway: First poison parkour ledge - Item 1"
+uw_first_ledge2 = "Underground Waterway: First poison parkour ledge - Item 2"
+uw_first_ledge3 = "Underground Waterway: First poison parkour ledge - Item 3"
+uw_first_ledge4 = "Underground Waterway: First poison parkour ledge - Item 4"
+uw_first_ledge5 = "Underground Waterway: First poison parkour ledge - Item 5"
+uw_first_ledge6 = "Underground Waterway: First poison parkour ledge - Item 6"
+uw_in_skel_ledge1 = "Underground Waterway: Inside skeleton crusher ledge - Item 1"
+uw_in_skel_ledge2 = "Underground Waterway: Inside skeleton crusher ledge - Item 2"
+uw_in_skel_ledge3 = "Underground Waterway: Inside skeleton crusher ledge - Item 3"
+
+
+# Castle Center basement main locations
+ccb_skel_hallway_ent = "Castle Center: Entrance hallway"
+ccb_skel_hallway_jun = "Castle Center: Basement hallway junction"
+ccb_skel_hallway_tc = "Castle Center: Torture chamber hallway"
+ccb_behemoth_l_ff = "Castle Center: Behemoth arena - Left far-front torch"
+ccb_behemoth_l_mf = "Castle Center: Behemoth arena - Left mid-front torch"
+ccb_behemoth_l_mr = "Castle Center: Behemoth arena - Left mid-rear torch"
+ccb_behemoth_l_fr = "Castle Center: Behemoth arena - Left far-rear torch"
+ccb_behemoth_r_ff = "Castle Center: Behemoth arena - Right far-front torch"
+ccb_behemoth_r_mf = "Castle Center: Behemoth arena - Right mid-front torch"
+ccb_behemoth_r_mr = "Castle Center: Behemoth arena - Right mid-rear torch"
+ccb_behemoth_r_fr = "Castle Center: Behemoth arena - Right far-rear torch"
+ccb_mandrag_shelf_l = "Castle Center: Mandragora shelf - Left"
+ccb_mandrag_shelf_r = "Castle Center: Mandragora shelf - Right"
+ccb_torture_rack = "Castle Center: Torture chamber instrument rack"
+ccb_torture_rafters = "Castle Center: Torture chamber rafters"
+
+# Castle Center elevator room main locations
+ccelv_near_machine = "Castle Center: Near elevator room machine"
+ccelv_atop_machine = "Castle Center: Atop elevator room machine"
+ccelv_pipes = "Castle Center: Elevator pipe device"
+ccelv_staircase = "Castle Center: Elevator room staircase"
+
+# Castle Center factory floor main locations
+ccff_redcarpet_knight = "Castle Center: Red carpet hall knight"
+ccff_gears_side = "Castle Center: Gear room side"
+ccff_gears_mid = "Castle Center: Gear room center"
+ccff_gears_corner = "Castle Center: Gear room corner"
+ccff_lizard_knight = "Castle Center: Lizard locker knight"
+ccff_lizard_pit = "Castle Center: Lizard locker room near pit"
+ccff_lizard_corner = "Castle Center: Lizard locker room corner"
+
+# Castle Center lizard lab main locations
+ccll_brokenstairs_floor = "Castle Center: Broken staircase floor"
+ccll_brokenstairs_knight = "Castle Center: Broken staircase knight"
+ccll_brokenstairs_save = "Castle Center: Above broken staircase savepoint"
+ccll_glassknight_l = "Castle Center: Stained Glass Knight room - Left"
+ccll_glassknight_r = "Castle Center: Stained Glass Knight room - Right"
+ccll_butlers_door = "Castle Center: Butler bros. room near door"
+ccll_butlers_side = "Castle Center: Butler bros. room inner"
+ccll_cwhall_butlerflames_past = "Castle Center: Past butler room flamethrowers"
+ccll_cwhall_flamethrower = "Castle Center: Inside cracked wall hallway flamethrower"
+ccll_cwhall_cwflames = "Castle Center: Past upper cracked wall flamethrowers"
+ccll_cwhall_wall = "Castle Center: Inside upper cracked wall"
+ccll_heinrich = "Castle Center: Heinrich Meyer"
+
+# Castle Center library main locations
+ccl_bookcase = "Castle Center: Library bookshelf"
+
+# Castle Center invention area main locations
+ccia_nitro_crates = "Castle Center: Nitro room crates"
+ccia_nitro_shelf_h = "Castle Center: Magical Nitro shelf - Heinrich side"
+ccia_nitro_shelf_i = "Castle Center: Magical Nitro shelf - Invention side"
+ccia_nitrohall_torch = "Castle Center: Past nitro room flamethrowers"
+ccia_nitrohall_flamethrower = "Castle Center: Inside nitro hallway flamethrower"
+ccia_inventions_crusher = "Castle Center: Invention room spike crusher door"
+ccia_inventions_maids = "Castle Center: Invention room maid sisters door"
+ccia_inventions_round = "Castle Center: Invention room round machine"
+ccia_inventions_famicart = "Castle Center: Invention room giant Famicart"
+ccia_inventions_zeppelin = "Castle Center: Invention room zeppelin"
+ccia_maids_outer = "Castle Center: Maid sisters room outer table"
+ccia_maids_inner = "Castle Center: Maid sisters room inner table"
+ccia_maids_vase = "Castle Center: Maid sisters room vase"
+ccia_stairs_knight = "Castle Center: Hell Knight landing corner knight"
+
+# Castle Center sub-weapons
+ccb_skel_hallway_ba = "Castle Center: Behemoth arena hallway"
+ccelv_switch = "Castle Center: Near elevator switch"
+ccff_lizard_near_knight = "Castle Center: Near lizard locker knight"
+
+# Castle Center lizard lockers
+ccff_lizard_locker_nfr = "Castle Center: Far-right near-side lizard locker"
+ccff_lizard_locker_nmr = "Castle Center: Mid-right near-side lizard locker"
+ccff_lizard_locker_nml = "Castle Center: Mid-left near-side lizard locker"
+ccff_lizard_locker_nfl = "Castle Center: Far-left near-side lizard locker"
+ccff_lizard_locker_fl = "Castle Center: Left far-side lizard locker"
+ccff_lizard_locker_fr = "Castle Center: Right far-side lizard locker"
+
+# Castle Center 3-hit breakables
+ccb_behemoth_crate1 = "Castle Center: Behemoth arena crate - Item 1"
+ccb_behemoth_crate2 = "Castle Center: Behemoth arena crate - Item 2"
+ccb_behemoth_crate3 = "Castle Center: Behemoth arena crate - Item 3"
+ccb_behemoth_crate4 = "Castle Center: Behemoth arena crate - Item 4"
+ccb_behemoth_crate5 = "Castle Center: Behemoth arena crate - Item 5"
+ccelv_stand1 = "Castle Center: Elevator room unoccupied statue stand - Item 1"
+ccelv_stand2 = "Castle Center: Elevator room unoccupied statue stand - Item 2"
+ccelv_stand3 = "Castle Center: Elevator room unoccupied statue stand - Item 3"
+ccff_lizard_slab1 = "Castle Center: Lizard locker room slab - Item 1"
+ccff_lizard_slab2 = "Castle Center: Lizard locker room slab - Item 2"
+ccff_lizard_slab3 = "Castle Center: Lizard locker room slab - Item 3"
+ccff_lizard_slab4 = "Castle Center: Lizard locker room slab - Item 4"
+
+
+# Duel Tower main locations
+dt_stones_start = "Duel Tower: Stepping stone path start"
+dt_werebull_arena = "Duel Tower: Above Were-bull arena"
+dt_ibridge_l = "Duel Tower: Invisible bridge balcony - Left"
+dt_ibridge_r = "Duel Tower: Invisible bridge balcony - Right"
+
+# Duel Tower sub-weapons
+dt_stones_end = "Duel Tower: Stepping stone path end"
+
+
+# Tower of Execution main locations
+toe_midsavespikes_r = "Tower of Execution: Past mid-savepoint spikes - Right"
+toe_midsavespikes_l = "Tower of Execution: Past mid-savepoint spikes - Left"
+toe_elec_grate = "Tower of Execution: Electric grate ledge"
+toe_ibridge = "Tower of Execution: Invisible bridge ledge"
+toe_top = "Tower of Execution: Guillotine tower top level"
+toe_keygate_l = "Tower of Execution: Key gate alcove - Left"
+
+# Tower of Execution 3-hit breakables
+toe_ledge1 = "Tower of Execution: Pre-mid-savepoint platforms ledge - Item 1"
+toe_ledge2 = "Tower of Execution: Pre-mid-savepoint platforms ledge - Item 2"
+toe_ledge3 = "Tower of Execution: Pre-mid-savepoint platforms ledge - Item 3"
+toe_ledge4 = "Tower of Execution: Pre-mid-savepoint platforms ledge - Item 4"
+toe_ledge5 = "Tower of Execution: Pre-mid-savepoint platforms ledge - Item 5"
+
+# Tower of Execution sub-weapons
+toe_keygate_r = "Tower of Execution: Key gate alcove - Right"
+
+
+# Tower of Science main locations
+tosci_elevator = "Tower of Science: Elevator hallway"
+tosci_plain_sr = "Tower of Science: Plain sight side room"
+tosci_stairs_sr = "Tower of Science: Staircase side room"
+tosci_three_door_hall = "Tower of Science: Pick-a-door hallway locked middle room"
+tosci_ibridge_t = "Tower of Science: Invisible bridge platform torch"
+tosci_conveyor_sr = "Tower of Science: Spiky conveyor side room"
+tosci_exit = "Tower of Science: Exit hallway"
+tosci_key3_r = "Tower of Science: Locked Key3 room - Right"
+tosci_key3_l = "Tower of Science: Locked Key3 room - Left"
+
+# Tower of Science 3-hit breakables
+tosci_ibridge_b1 = "Tower of Science: Invisible bridge platform crate - Item 1"
+tosci_ibridge_b2 = "Tower of Science: Invisible bridge platform crate - Item 2"
+tosci_ibridge_b3 = "Tower of Science: Invisible bridge platform crate - Item 3"
+tosci_ibridge_b4 = "Tower of Science: Invisible bridge platform crate - Item 4"
+tosci_ibridge_b5 = "Tower of Science: Invisible bridge platform crate - Item 5"
+tosci_ibridge_b6 = "Tower of Science: Invisible bridge platform crate - Item 6"
+
+# Tower of Science sub-weapons
+tosci_key3_m = "Tower of Science: Locked Key3 room - Middle"
+
+
+# Tower of Sorcery main locations
+tosor_stained_tower = "Tower of Sorcery: Stained glass tower"
+tosor_savepoint = "Tower of Sorcery: Mid-savepoint platform"
+tosor_trickshot = "Tower of Sorcery: Trick shot from mid-savepoint platform"
+tosor_yellow_bubble = "Tower of Sorcery: Above yellow bubble"
+tosor_blue_platforms = "Tower of Sorcery: Above tiny blue platforms start"
+tosor_side_isle = "Tower of Sorcery: Lone red platform side island"
+tosor_ibridge = "Tower of Sorcery: Invisible bridge platform"
+
+# Room of Clocks main locations
+roc_ent_l = "Room of Clocks: Left from entrance hallway"
+roc_cont_r = "Room of Clocks: Right of Contract"
+roc_ent_r = "Room of Clocks: Right from entrance hallway"
+
+# Room of Clocks sub-weapons
+roc_elev_l = "Room of Clocks: Left of elevator hallway"
+roc_elev_r = "Room of Clocks: Right of elevator hallway"
+
+# Room of Clocks empty breakables
+roc_cont_l = "Room of Clocks: Left of Contract"
+roc_exit = "Room of Clocks: Left of exit"
+
+# Clock Tower main locations
+ct_gearclimb_corner = "Clock Tower: Gear climb room corner"
+ct_gearclimb_side = "Clock Tower: Gear climb room side"
+ct_bp_chasm_fl = "Clock Tower: Bone Pillar chasm room - Front-left"
+ct_bp_chasm_fr = "Clock Tower: Bone Pillar chasm room - Front-right"
+ct_bp_chasm_k = "Clock Tower: Bone Pillar chasm room key alcove"
+ct_finalroom_platform = "Clock Tower: Final room key ledge"
+
+# Clock Tower 3-hit breakables
+ct_gearclimb_battery_slab1 = "Clock Tower: Gear climb room beneath battery slab - Item 1"
+ct_gearclimb_battery_slab2 = "Clock Tower: Gear climb room beneath battery slab - Item 2"
+ct_gearclimb_battery_slab3 = "Clock Tower: Gear climb room beneath battery slab - Item 3"
+ct_gearclimb_door_slab1 = "Clock Tower: Gear climb room beneath door slab - Item 1"
+ct_gearclimb_door_slab2 = "Clock Tower: Gear climb room beneath door slab - Item 2"
+ct_gearclimb_door_slab3 = "Clock Tower: Gear climb room beneath door slab - Item 3"
+ct_finalroom_door_slab1 = "Clock Tower: Final room entrance slab - Item 1"
+ct_finalroom_door_slab2 = "Clock Tower: Final room entrance slab - Item 2"
+ct_finalroom_renon_slab1 = "Clock Tower: Renon's final offers slab - Item 1"
+ct_finalroom_renon_slab2 = "Clock Tower: Renon's final offers slab - Item 2"
+ct_finalroom_renon_slab3 = "Clock Tower: Renon's final offers slab - Item 3"
+ct_finalroom_renon_slab4 = "Clock Tower: Renon's final offers slab - Item 4"
+ct_finalroom_renon_slab5 = "Clock Tower: Renon's final offers slab - Item 5"
+ct_finalroom_renon_slab6 = "Clock Tower: Renon's final offers slab - Item 6"
+ct_finalroom_renon_slab7 = "Clock Tower: Renon's final offers slab - Item 7"
+ct_finalroom_renon_slab8 = "Clock Tower: Renon's final offers slab - Item 8"
+
+# Clock Tower sub-weapons
+ct_bp_chasm_rl = "Clock Tower: Bone Pillar chasm room - Rear-left"
+ct_finalroom_fr = "Clock Tower: Final room floor - front-right"
+ct_finalroom_fl = "Clock Tower: Final room floor - front-left"
+ct_finalroom_rr = "Clock Tower: Final room floor - rear-right"
+ct_finalroom_rl = "Clock Tower: Final room floor - rear-left"
+
+
+# Castle Keep main locations
+ck_flame_l = "Castle Keep: Left Dracula door flame"
+ck_flame_r = "Castle Keep: Right Dracula door flame"
+ck_behind_drac = "Castle Keep: Behind Dracula's chamber"
+ck_cube = "Castle Keep: Dracula's floating cube"
+
+
+# Renon's shop locations
+renon1 = "Renon's shop: Roast Chicken purchase"
+renon2 = "Renon's shop: Roast Beef purchase"
+renon3 = "Renon's shop: Healing Kit purchase"
+renon4 = "Renon's shop: Purifying purchase"
+renon5 = "Renon's shop: Cure Ampoule purchase"
+renon6 = "Renon's shop: Sun Card purchase"
+renon7 = "Renon's shop: Moon Card purchase"
+
+
+# Events
+forest_boss_one = "Forest of Silence: King Skeleton 1"
+forest_boss_two = "Forest of Silence: Were-tiger"
+forest_boss_three = "Forest of Silence: King Skeleton 2"
+cw_boss = "Castle Wall: Bone Dragons"
+villa_boss_one = "Villa: J. A. Oldrey"
+villa_boss_two = "Villa: Undead Maiden"
+uw_boss = "Underground Waterway: Lizard-man trio"
+cc_boss_one = "Castle Center: Behemoth"
+cc_boss_two = "Castle Center: Rosa/Camilla"
+dt_boss_one = "Duel Tower: Were-jaguar"
+dt_boss_two = "Duel Tower: Werewolf"
+dt_boss_three = "Duel Tower: Were-bull"
+dt_boss_four = "Duel Tower: Were-tiger"
+roc_boss = "Room of Clocks: Death/Actrise"
+ck_boss_one = "Castle Keep: Renon"
+ck_boss_two = "Castle Keep: Vincent"
+
+cc_behind_the_seal = "Castle Center: Behind the seal"
+
+the_end = "Dracula"
diff --git a/worlds/cv64/data/patches.py b/worlds/cv64/data/patches.py
new file mode 100644
index 00000000000..4c467036383
--- /dev/null
+++ b/worlds/cv64/data/patches.py
@@ -0,0 +1,2865 @@
+normal_door_hook = [
+ 0x00862021, # ADDU A0, A0, A2
+ 0x80849C60, # LB A0, 0x9C60 (A0)
+ 0x0C0FF174, # JAL 0x803FC5D0
+ 0x308900FF # ANDI T1, A0, 0x00FF
+]
+
+normal_door_code = [
+ 0x00024080, # SLL T0, V0, 2
+ 0x3C048039, # LUI A0, 0x8039
+ 0x00882021, # ADDU A0, A0, T0
+ 0x8C849BE4, # LW A0, 0x9BE4 (A0)
+ 0x8C6A0008, # LW T2, 0x0008 (V1)
+ 0x008A5824, # AND T3, A0, T2
+ 0x11600003, # BEQZ T3, [forward 0x03]
+ 0x00000000, # NOP
+ 0x24020003, # ADDIU V0, R0, 0x0003
+ 0x27FF006C, # ADDIU RA, RA, 0x006C
+ 0x03E00008 # JR RA
+]
+
+ct_door_hook = [
+ 0x0C0FF182, # JAL 0x803FC608
+ 0x00000000, # NOP
+ 0x315900FF # ANDI T9, T2, 0x00FF
+]
+
+ct_door_code = [
+ 0x3C0A8039, # LUI T2, 0x8039
+ 0x8D429BF8, # LW V0, 0x9BF8 (T2)
+ 0x01465021, # ADDU T2, T2, A2
+ 0x814A9C60, # LB T2, 0x9C60 (T2)
+ 0x00495824, # AND T3, V0, T1
+ 0x55600001, # BNEZL T3, [forward 0x01]
+ 0x27FF0010, # ADDIU RA, RA, 0x0010
+ 0x03E00008 # JR RA
+]
+
+stage_select_overwrite = [
+ # Replacement for the "wipe world state" function when using the warp menu. Now it's the "Special1 jewel checker"
+ # to see how many destinations can be selected on it with the current count.
+ 0x8FA60018, # LW A2, 0x0018 (SP)
+ 0xA0606437, # SB R0, 0x6437 (V1)
+ 0x10000029, # B [forward 0x29]
+ 0x00000000, # NOP
+ 0x3C0A8039, # LUI T2, 0x8039
+ 0x254A9C4B, # ADDIU T2, T2, 0x9C4B
+ 0x814B0000, # LB T3, 0x0000 (T2)
+ 0x240C000A, # ADDIU T4, R0, 0x000A
+ 0x016C001B, # DIVU T3, T4
+ 0x00003012, # MFLO A2
+ 0x24C60001, # ADDIU A2, A2, 0x0001
+ 0x28CA0009, # SLTI T2, A2, 0x0009
+ 0x51400001, # BEQZL T2, 0x8012AC7C
+ 0x24060008, # ADDIU A2, R0, 0x0008
+ 0x3C0A800D, # LUI T2, 0x800D
+ 0x914A5E20, # LBU T2, 0x5E20 (T2)
+ 0x314A0040, # ANDI T2, T2, 0x0040
+ 0x11400003, # BEQZ T2, [forward 0x03]
+ 0x240BFFFE, # ADDIU T3, R0, 0xFFFE
+ 0x3C0C8034, # LUI T4, 0x8034
+ 0xAD8B2084, # SW T3, 0x2084 (T4)
+ 0x03200008, # JR T9
+ 0x00000000, # NOP
+ 0x00000000, # NOP
+ 0x00000000, # NOP
+ 0x00000000, # NOP
+ 0x00000000, # NOP
+ 0x00000000, # NOP
+ 0x00000000, # NOP
+ 0x00000000, # NOP
+ 0x00000000, # NOP
+ 0x00000000, # NOP
+ 0x00000000, # NOP
+ 0x00000000, # NOP
+ 0x00000000, # NOP
+ 0x00000000, # NOP
+ 0x00000000, # NOP
+ 0x00000000, # NOP
+ 0x00000000, # NOP
+ 0x00000000, # NOP
+ 0x00000000, # NOP
+ 0x00000000, # NOP
+ 0x00000000, # NOP
+ 0x00000000, # NOP
+]
+
+custom_code_loader = [
+ # On boot, when the company logos show up, this will trigger and load most of the custom ASM data in this module
+ # off from ROM offsets 0xBFC000-0xBFFFFF and into the 803FC000-803FFFFF range in RAM.
+ 0x3C080C10, # LUI T0, 0x0C10
+ 0x2508F1C0, # ADDIU T0, T0, 0xF1C0
+ 0x3C098000, # LUI T1, 0x8000
+ 0xAD282438, # SW T0, 0x2438 (T1)
+ 0x3C088040, # LUI T0, 0x8040
+ 0x9108C000, # ADDIU T0, 0xC000 (T0)
+ 0x15000007, # BNEZ T0, [forward 0x07]
+ 0x3C0400C0, # LUI A0, 0x00C0
+ 0x2484C000, # ADDIU A0, A0, 0xC000
+ 0x3C058040, # LUI A1, 0x8040
+ 0x24A5C000, # ADDIU A1, A1, 0xC000
+ 0x24064000, # ADDIU A2, R0, 0x4000
+ 0x08005DFB, # J 0x800177EC
+ 0x00000000, # NOP
+ 0x03E00008 # JR RA
+]
+
+remote_item_giver = [
+ # The essential multiworld function. Every frame wherein the player is in control and not looking at a text box,
+ # this thing will check some bytes in RAM to see if an item or DeathLink has been received and trigger the right
+ # functions accordingly to either reward items or kill the player.
+
+ # Primary checks
+ 0x3C088034, # LUI T0, 0x8034
+ 0x9509244A, # LHU T1, 0x244A (T0)
+ 0x3C088039, # LUI T0, 0x8039
+ 0x910A9EFB, # LBU T2, 0x9EFF (T0)
+ 0x012A4821, # ADDU T1, T1, T2
+ 0x910A9EFF, # LBU T2, 0x9EFF (T0)
+ 0x012A4821, # ADDU T1, T1, T2
+ 0x910A9CCF, # LBU T2, 0x9CCF (T0)
+ 0x012A4821, # ADDU T1, T1, T2
+ 0x910A9EEF, # LBU T2, 0x9EEF (T0)
+ 0x012A4821, # ADDU T1, T1, T2
+ 0x910A9CD3, # LBU T2, 0x9CD3 (T0)
+ 0x012A4821, # ADDU T1, T1, T2
+ 0x3C088038, # LUI T0, 0x8038
+ 0x910A7ADD, # LBU T2, 0x7ADD (T0)
+ 0x012A4821, # ADDU T1, T1, T2
+ 0x3C0B8039, # LUI T3, 0x8039
+ 0x916A9BE0, # LBU T2, 0x9BE0 (T3)
+ 0x012A4821, # ADDU T1, T1, T2
+ 0x11200006, # BEQZ T1, [forward 0x06]
+ 0x00000000, # NOP
+ 0x11400002, # BEQZ T2, [forward 0x02]
+ 0x254AFFFF, # ADDIU T2, T2, 0xFFFF
+ 0xA16A9BE0, # SB T2, 0x9BE0 (T3)
+ 0x03E00008, # JR RA
+ 0x00000000, # NOP
+ # Item-specific checks
+ 0x3C088034, # LUI T0, 0x8034
+ 0x91082891, # LBU T0, 0x2891 (T0)
+ 0x24090002, # ADDIU T1, R0, 0x0002
+ 0x15090012, # BNE T0, T1, [forward 0x12]
+ 0x00000000, # NOP
+ 0x256B9BDF, # ADDIU T3, T3, 0x9BDF
+ 0x91640000, # LBU A0, 0x0000 (T3)
+ 0x14800003, # BNEZ A0, [forward 0x03]
+ 0x00000000, # NOP
+ 0x10000005, # B [forward 0x05]
+ 0x256B0002, # ADDIU T3, T3, 0x0002
+ 0x2409000F, # ADDIU T1, R0, 0x000F
+ 0xA1690001, # SB T1, 0x0001 (T3)
+ 0x080FF8DD, # J 0x803FE374
+ 0xA1600000, # SB R0, 0x0000 (T3)
+ 0x91640000, # LBU A0, 0x0000 (T3)
+ 0x14800002, # BNEZ A0, [forward 0x02]
+ 0x00000000, # NOP
+ 0x10000003, # B [forward 0x03]
+ 0x2409000F, # ADDIU T1, R0, 0x000F
+ 0x080FF864, # J 0x803FE190
+ 0xA169FFFF, # SB T1, 0xFFFF (T3)
+ # DeathLink-specific checks
+ 0x3C0B8039, # LUI T3, 0x8039
+ 0x256B9BE1, # ADDIU T3, T3, 0x9BE1
+ 0x91640002, # LBU A0, 0x0002 (T3)
+ 0x14800002, # BNEZ A0, [forward 0x02]
+ 0x916900A7, # LBU T1, 0x00A7 (T3)
+ 0x080FF9C0, # J 0x803FE700
+ 0x312A0080, # ANDI T2, T1, 0x0080
+ 0x11400002, # BEQZ T2, [forward 0x02]
+ 0x00000000, # NOP
+ 0x03E00008, # JR RA
+ 0x35290080, # ORI T1, T1, 0x0080
+ 0xA16900A7, # SB T1, 0x00A7 (T3)
+ 0x2484FFFF, # ADDIU A0, A0, 0xFFFF
+ 0x24080001, # ADDIU T0, R0, 0x0001
+ 0x03E00008, # JR RA
+ 0xA168FFFD, # SB T0, 0xFFFD (T3)
+]
+
+deathlink_nitro_edition = [
+ # Alternative to the end of the above DeathLink-specific checks that kills the player with the Nitro explosion
+ # instead of the normal death.
+ 0x91690043, # LBU T1, 0x0043 (T3)
+ 0x080FF9C0, # J 0x803FE700
+ 0x3C088034, # LUI T0, 0x8034
+ 0x91082BFE, # LBU T0, 0x2BFE (T0)
+ 0x11000002, # BEQZ T0, [forward 0x02]
+ 0x00000000, # NOP
+ 0x03E00008, # JR RA
+ 0x35290080, # ORI T1, T1, 0x0080
+ 0xA1690043, # SB T1, 0x0043 (T3)
+ 0x2484FFFF, # ADDIU A0, A0, 0xFFFF
+ 0x24080001, # ADDIU T0, R0, 0x0001
+ 0x03E00008, # JR RA
+ 0xA168FFFD, # SB T0, 0xFFFD (T3)
+]
+
+nitro_fall_killer = [
+ # Custom code to force the instant fall death if at a high enough falling speed after getting killed by the Nitro
+ # explosion, since the game doesn't run the checks for the fall death after getting hit by said explosion and could
+ # result in a softlock when getting blown into an abyss.
+ 0x3C0C8035, # LUI T4, 0x8035
+ 0x918807E2, # LBU T0, 0x07E2 (T4)
+ 0x2409000C, # ADDIU T1, R0, 0x000C
+ 0x15090006, # BNE T0, T1, [forward 0x06]
+ 0x3C098035, # LUI T1, 0x8035
+ 0x91290810, # LBU T1, 0x0810 (T1)
+ 0x240A00C1, # ADDIU T2, R0, 0x00C1
+ 0x152A0002, # BNE T1, T2, [forward 0x02]
+ 0x240B0001, # ADDIU T3, R0, 0x0001
+ 0xA18B07E2, # SB T3, 0x07E2 (T4)
+ 0x03E00008 # JR RA
+]
+
+deathlink_counter_decrementer = [
+ # Decrements the DeathLink counter if it's above zero upon loading a previous state. Checking this number will be
+ # how the client will tell if a player's cause of death was something in-game or a DeathLink (and send a DeathLink
+ # to the server if it was the former). Also resets the remote item values to 00 so the player's received items don't
+ # get mucked up in-game.
+ 0x3C088039, # LUI T0, 0x8039
+ 0x91099BE3, # LBU T1, 0x9BE3 (T0)
+ 0x11200002, # BEQZ T1, 0x803FC154
+ 0x2529FFFF, # ADDIU T1, T1, 0xFFFF
+ 0xA1099BE3, # SB T1, 0x9BE3
+ 0x240900FF, # ADDIU T1, R0, 0x00FF
+ 0xA1099BE0, # SB T1, 0x9BE0 (T0)
+ 0xA1009BDF, # SB R0, 0x9BDF (T0)
+ 0xA1009BE1, # SB R0, 0x9BE1 (T0)
+ 0x91099BDE, # LBU T1, 0x9BDE (T0)
+ 0x55200001, # BNEZL T1, [forward 0x01]
+ 0x24090000, # ADDIU T1, R0, 0x0000
+ 0xA1099BDE, # SB T1, 0x9BDE (T0)
+ 0x91099C24, # LBU T1, 0x9C24 (T0)
+ 0x312A0080, # ANDI T2, T1, 0x0080
+ 0x55400001, # BNEZL T2, [forward 0x01]
+ 0x3129007F, # ANDI T1, T1, 0x007F
+ 0x03E00008, # JR RA
+ 0xA1099C24 # SB T1, 0x9C24 (T0)
+]
+
+death_flag_unsetter = [
+ # Un-sets the Death status bitflag when overwriting the "Restart this stage" state and sets health to full if it's
+ # empty. This is to ensure DeathLinked players won't get trapped in a perpetual death loop for eternity should they
+ # receive one right before transitioning to a different stage.
+ 0x3C048039, # LUI A0, 0x8039
+ 0x90889C88, # LBU T0, 0x9C88 (A0)
+ 0x31090080, # ANDI T1, T0, 0x0080
+ 0x01094023, # SUBU T0, T0, T1
+ 0x908A9C3F, # LBU T2, 0x9C3F (A0)
+ 0x24090064, # ADDIU T1, R0, 0x0064
+ 0x51400001, # BEQZL T2, [forward 0x01]
+ 0xA0899C3F, # SB T1, 0x9C3F (A0)
+ 0x08006DAE, # J 0x8001B6B8
+ 0xA0889C88 # SB T0, 0x9C88 (A0)
+]
+
+warp_menu_opener = [
+ # Enables opening the Stage Select menu by pausing while holding Z + R when not in a boss fight, the castle
+ # crumbling sequence following Fake Dracula, or Renon's arena (in the few seconds after his health bar vanishes).
+ 0x3C08800D, # LUI T0, 0x800D
+ 0x85095E20, # LH T1, 0x5E20 (T0)
+ 0x24083010, # ADDIU T0, R0, 0x3010
+ 0x15090011, # BNE T0, T1, [forward 0x11]
+ 0x3C088035, # LUI T0, 0x8035
+ 0x9108F7D8, # LBU T0, 0xF7D8 (T0)
+ 0x24090020, # ADDIU T1, R0, 0x0020
+ 0x1109000D, # BEQ T0, T1, [forward 0x0D]
+ 0x3C088039, # LUI T0, 0x8039
+ 0x91099BFA, # LBU T1, 0x9BFA (T0)
+ 0x31290001, # ANDI T1, T1, 0x0001
+ 0x15200009, # BNEZ T1, [forward 0x09]
+ 0x8D099EE0, # LW T1, 0x9EE0
+ 0x3C0A001B, # LUI T2, 0x001B
+ 0x254A0003, # ADDIU T2, T2, 0x0003
+ 0x112A0005, # BEQ T1, T2, [forward 0x05]
+ 0x3C098034, # LUI T1, 0x8034
+ 0xA1009BE1, # SB R0, 0x9BE1 (T0)
+ 0x2408FFFC, # ADDIU T0, R0, 0xFFFC
+ 0x0804DA70, # J 0x80136960
+ 0xAD282084, # SW T0, 0x2084 (T1)
+ 0x0804DA70, # J 0x80136960
+ 0xA44E6436 # SH T6, 0x6436 (V0)
+]
+
+give_subweapon_stopper = [
+ # Extension to "give subweapon" function to not change the player's weapon if the received item is a Stake or Rose.
+ # Can also increment the Ice Trap counter if getting a Rose or jump to prev_subweapon_dropper if applicable.
+ 0x24090011, # ADDIU T1, R0, 0x0011
+ 0x11240009, # BEQ T1, A0, [forward 0x09]
+ 0x24090012, # ADDIU T1, R0, 0x0012
+ 0x11240003, # BEQ T1, A0, [forward 0x03]
+ 0x9465618A, # LHU A1, 0x618A (V1)
+ 0xA46D618A, # SH T5, 0x618A (V1)
+ 0x0804F0BF, # J 0x8013C2FC
+ 0x3C098039, # LUI T1, 0x8039
+ 0x912A9BE2, # LBU T2, 0x9BE2 (T1)
+ 0x254A0001, # ADDIU T2, T2, 0x0001
+ 0xA12A9BE2, # SB T2, 0x9BE2 (T1)
+ 0x0804F0BF, # J 0x8013C2FC
+]
+
+give_powerup_stopper = [
+ # Extension to "give PowerUp" function to not increase the player's PowerUp count beyond 2
+ 0x240D0002, # ADDIU T5, R0, 0x0002
+ 0x556D0001, # BNEL T3, T5, [forward 0x01]
+ 0xA46C6234, # SH T4, 0x6234 (V1)
+ 0x0804F0BF # J 0x8013C2FC
+]
+
+npc_item_hack = [
+ # Hack to make NPC items show item textboxes when received (and decrease the Countdown if applicable).
+ 0x3C098039, # LUI T1, 0x8039
+ 0x001F5602, # SRL T2, RA, 24
+ 0x240B0080, # ADDIU T3, R0, 0x0080
+ 0x114B001F, # BEQ T2, T3, [forward 0x1F]
+ 0x240A001A, # ADDIU T2, R0, 0x001A
+ 0x27BD0020, # ADDIU SP, SP, 0x20
+ 0x15440004, # BNE T2, A0, [forward 0x04]
+ 0x240B0029, # ADDIU T3, R0, 0x0029
+ 0x34199464, # ORI T9, R0, 0x9464
+ 0x10000004, # B [forward 0x04]
+ 0x240C0002, # ADDIU T4, R0, 0x0002
+ 0x3419DA64, # ORI T9, R0, 0xDA64
+ 0x240B0002, # ADDIU T3, R0, 0x0002
+ 0x240C000E, # ADDIU T4, R0, 0x000E
+ 0x012C7021, # ADDU T6, T1, T4
+ 0x316C00FF, # ANDI T4, T3, 0x00FF
+ 0x000B5A02, # SRL T3, T3, 8
+ 0x91CA9CA4, # LBU T2, 0x9CA4 (T6)
+ 0x3C0D8040, # LUI T5, 0x8040
+ 0x256FFFFF, # ADDIU T7, T3, 0xFFFF
+ 0x01AF6821, # ADDU T5, T5, T7
+ 0x91B8D71C, # LBU T8, 0xD71C (T5)
+ 0x29EF0019, # SLTI T7, T7, 0x0019
+ 0x51E00001, # BEQZL T7, [forward 0x01]
+ 0x91B8D71F, # LBU T8, 0xD71F (T5)
+ 0x13000002, # BEQZ T8, [forward 0x02]
+ 0x254AFFFF, # ADDIU T2, T2, 0xFFFF
+ 0xA1CA9CA4, # SB T2, 0x9CA4 (T6)
+ 0xA12C9BDF, # SB T4, 0x9BDF (T1)
+ 0x3C0400BB, # LUI A0, 0x00BB
+ 0x00992025, # OR A0, A0, T9
+ 0x3C058019, # LUI A1, 0x8019
+ 0x24A5BF98, # ADDIU A1, A1, 0xBF98
+ 0x08005DFB, # J 0x800177EC
+ 0x24060100, # ADDIU A2, R0, 0x0100
+ 0x0804EFFD, # J 0x8013BFF4
+ 0xAFBF0014 # SW RA, 0x0014 (SP)
+]
+
+overlay_modifiers = [
+ # Whenever a compressed overlay gets decompressed and mapped in the 0F or 0E domains, this thing will check the
+ # number ID in the T0 register to tell which one it is and overwrite some instructions in it on-the-fly accordingly
+ # to said number before it runs. Confirmed to NOT be a foolproof solution on console and Simple64; the instructions
+ # may not be properly overwritten on the first execution of the overlay.
+
+ # Prevent being able to throw Nitro into the Hazardous Waste Disposals
+ 0x3C0A2402, # LUI T2, 0x2402
+ 0x254A0001, # ADDIU T2, T2, 0x0001
+ 0x24090023, # ADDIU T1, R0, 0x0023
+ 0x15090003, # BNE T0, T1, [forward 0x03]
+ 0x00000000, # NOP
+ 0x03200008, # JR T9
+ 0xAF2A01D4, # SW T2, 0x01D4 (T9)
+ # Make it so nothing can be taken from the Nitro or Mandragora shelves through the textboxes
+ 0x24090022, # ADDIU T1, R0, 0x0022
+ 0x11090002, # BEQ T0, T1, [forward 0x02]
+ 0x24090021, # ADDIU T1, R0, 0x0021
+ 0x15090003, # BNE T0, T1, [forward 0x03]
+ 0x254AFFFF, # ADDIU T2, T2, 0xFFFF
+ 0x03200008, # JR T9
+ 0xAF2A0194, # SW T2, 0x0194 (T9)
+ # Fix to allow placing both bomb components at a cracked wall at once while having multiple copies of each, and
+ # prevent placing them at the downstairs crack altogether until the seal is removed. Also enables placing both in
+ # one interaction.
+ 0x24090024, # ADDIU T1, R0, 0x0024
+ 0x15090012, # BNE T0, T1, [forward 0x12]
+ 0x240A0040, # ADDIU T2, R0, 0x0040
+ 0x240BC338, # ADDIU T3, R0, 0xC338
+ 0x240CC3D4, # ADDIU T4, R0, 0xC3D4
+ 0x240DC38C, # ADDIU T5, R0, 0xC38C
+ 0xA32A030F, # SB T2, 0x030F (T9)
+ 0xA72B0312, # SH T3, 0x0312 (T9)
+ 0xA32A033F, # SB T2, 0x033F (T9)
+ 0xA72B0342, # SH T3, 0x0342 (T9)
+ 0xA32A03E3, # SB T2, 0x03E3 (T9)
+ 0xA72C03E6, # SH T4, 0x03E6 (T9)
+ 0xA32A039F, # SB T2, 0x039F (T9)
+ 0xA72D03A2, # SH T5, 0x03A2 (T9)
+ 0xA32A03CB, # SB T2, 0x03CB (T9)
+ 0xA72D03CE, # SH T5, 0x03CE (T9)
+ 0xA32A05CF, # SB T2, 0x05CF (T9)
+ 0x240EE074, # ADDIU T6, R0, 0xE074
+ 0xA72E05D2, # SH T6, 0x05D2 (T9)
+ 0x03200008, # JR T9
+ # Disable the costume and Hard Mode flag checks so that pressing Up on the Player Select screen will always allow
+ # the characters' alternate costumes to be used as well as Hard Mode being selectable without creating save data.
+ 0x2409012E, # ADDIU T1, R0, 0x012E
+ 0x1509000A, # BNE T0, T1, [forward 0x0A]
+ 0x3C0A3C0B, # LUI T2, 0x3C0B
+ 0x254A8000, # ADDIU T2, T2, 0x8000
+ 0x240B240E, # ADDIU T3, R0, 0x240E
+ 0x240C240F, # ADDIU T4, R0, 0x240F
+ 0x240D0024, # ADDIU T5, R0, 0x0024
+ 0xAF2A0C78, # SW T2, 0x0C78 (T9)
+ 0xA72B0CA0, # SH T3, 0x0CA0 (T9)
+ 0xA72C0CDC, # SH T4, 0x0CDC (T9)
+ 0xA32D0168, # SB T5, 0x0024 (T9)
+ 0x03200008, # JR T9
+ # Overwrite instructions in the Forest end cutscene script to store a spawn position ID instead of a cutscene ID.
+ 0x2409002E, # ADDIU T1, R0, 0x002E
+ 0x15090005, # BNE T0, T1, [forward 0x05]
+ 0x3C0AA058, # LUI T2, 0xA058
+ 0x254A642B, # ADDIU T2, T2, 0x642B
+ 0xAF2A0D88, # SW T2, 0x0D88 (T9)
+ 0xAF200D98, # SW R0, 0x0D98 (T9)
+ 0x03200008, # JR T9
+ # Disable the rapid flashing effect in the CC planetarium cutscene to ensure it won't trigger seizures.
+ 0x2409003E, # ADDIU T1, R0, 0x003E
+ 0x1509000C, # BNE T0, T1, [forward 0x0C]
+ 0x00000000, # NOP
+ 0xAF200C5C, # SW R0, 0x0C5C
+ 0xAF200CD0, # SW R0, 0x0CD0
+ 0xAF200C64, # SW R0, 0x0C64
+ 0xAF200C74, # SW R0, 0x0C74
+ 0xAF200C80, # SW R0, 0x0C80
+ 0xAF200C88, # SW R0, 0x0C88
+ 0xAF200C90, # SW R0, 0x0C90
+ 0xAF200C9C, # SW R0, 0x0C9C
+ 0xAF200CB4, # SW R0, 0x0CB4
+ 0xAF200CC8, # SW R0, 0x0CC8
+ 0x03200008, # JR T9
+ 0x24090134, # ADDIU T1, R0, 0x0134
+ 0x15090005, # BNE T0, T1, [forward 0x05]
+ 0x340B8040, # ORI T3, R0, 0x8040
+ 0x340CDD20, # ORI T4, R0, 0xDD20
+ 0xA72B1D1E, # SH T3, 0x1D1E (T9)
+ 0xA72C1D22, # SH T4, 0x1D22 (T9)
+ 0x03200008, # JR T9
+ # Make the Ice Trap model check branch properly
+ 0x24090125, # ADDIU T1, R0, 0x0125
+ 0x15090003, # BNE T0, T1, [forward 0x03]
+ 0x3C0B3C19, # LUI T3, 0x3C19
+ 0x356B803F, # ORI T3, T3, 0x803F
+ 0xAF2B04D0, # SW T3, 0x04D0 (T9)
+ 0x03200008 # JR T9
+]
+
+double_component_checker = [
+ # When checking to see if a bomb component can be placed at a cracked wall, this will run if the code lands at the
+ # "no need to set 2" outcome to see if the other can be set.
+
+ # Mandragora checker
+ 0x10400007, # BEQZ V0, [forward 0x07]
+ 0x3C0A8039, # LUI T2, 0x8039
+ 0x31098000, # ANDI T1, T0, 0x8000
+ 0x15200008, # BNEZ T1, [forward 0x08]
+ 0x91499C5D, # LBU T1, 0x9C5D (T2)
+ 0x11200006, # BEQZ T1, 0x80183938
+ 0x00000000, # NOP
+ 0x10000007, # B [forward 0x07]
+ 0x31E90100, # ANDI T1, T7, 0x0100
+ 0x15200002, # BNEZ T1, [forward 0x02]
+ 0x91499C5D, # LBU T1, 0x9C5D (T2)
+ 0x15200003, # BNEZ T1, [forward 0x03]
+ 0x3C198000, # LUI T9, 0x8000
+ 0x27391590, # ADDIU T9, T9, 0x1590
+ 0x03200008, # JR T9
+ 0x24090001, # ADDIU T1, R0, 0x0001
+ 0xA4E9004C, # SH T1, 0x004C (A3)
+ 0x3C190E00, # LUI T9, 0x0E00
+ 0x273903E0, # ADDIU T9, T9, 0x03E0
+ 0x03200008, # JR T9
+ 0x00000000, # NOP
+ # Nitro checker
+ 0x10400007, # BEQZ V0, [forward 0x07]
+ 0x3C0A8039, # LUI T2, 0x8039
+ 0x31694000, # ANDI T1, T3, 0x4000
+ 0x15200008, # BNEZ T1, [forward 0x08]
+ 0x91499C5C, # LBU T1, 0x9C5C
+ 0x11200006, # BEQZ T1, [forward 0x06]
+ 0x00000000, # NOP
+ 0x1000FFF4, # B [backward 0x0B]
+ 0x914F9C18, # LBU T7, 0x9C18 (T2)
+ 0x31E90002, # ANDI T1, T7, 0x0002
+ 0x1520FFEC, # BNEZ T1, [backward 0x13]
+ 0x91499C5C, # LBU T1, 0x9C5C (T2)
+ 0x1520FFEF, # BNEZ T1, [backward 0x15]
+ 0x00000000, # NOP
+ 0x1000FFE8, # B [backward 0x17]
+ 0x00000000, # NOP
+]
+
+downstairs_seal_checker = [
+ # This will run specifically for the downstairs crack to see if the seal has been removed before then deciding to
+ # let the player set the bomb components or not. An anti-dick measure, since there is a limited number of each
+ # component per world.
+ 0x14400004, # BNEZ V0, [forward 0x04]
+ 0x3C0A8039, # LUI T2, 0x8039
+ 0x914A9C18, # LBU T2, 0x9C18 (T2)
+ 0x314A0001, # ANDI T2, T2, 0x0001
+ 0x11400003, # BEQZ T2, [forward 0x03]
+ 0x3C198000, # LUI T9, 0x8000
+ 0x27391448, # ADDIU T9, T9, 0x1448
+ 0x03200008, # JR T9
+ 0x3C190E00, # LUI T9, 0x0E00
+ 0x273902B4, # ADDIU T9, T9, 0x02B4
+ 0x03200008, # JR T9
+ 0x00000000, # NOP
+]
+
+map_data_modifiers = [
+ # Overwrites the map data table on-the-fly after it loads and before the game reads it to load objects. Good for
+ # changing anything that is part of a compression chain in the ROM data, including some freestanding item IDs.
+ # Also jumps to the function that overwrites the "Restart this stage" data if entering through the back of a level.
+
+ 0x08006DAA, # J 0x8001B6A8
+ 0x00000000, # NOP
+ # Demo checker (if we're in a title demo, don't do any of this)
+ 0x3C028034, # LUI V0, 0x8034
+ 0x9449244A, # LHU T1, 0x244A (V0)
+ 0x11200002, # BEQZ T1, [forward 0x02]
+ # Zero checker (if there are zeroes in the word at 0x8034244A, where the entity list address is stored, don't do
+ # any of this either)
+ 0x8C422B00, # LW V0, 0x2B00 (V0)
+ 0x03E00008, # JR RA
+ 0x00000000, # NOP
+ 0x14400002, # BNEZ V0, [forward 0x02]
+ 0x00000000, # NOP
+ 0x03E00008, # JR RA
+ 0x3C088039, # LUI T0, 0x8039
+ 0x91199EE3, # LBU T9, 0x9EE3 (T0)
+ 0x91089EE1, # LBU T0, 0x9EE1 (T0)
+ # Forest of Silence (replaces 1 invisible chicken)
+ 0x15000006, # BNEZ T0, [forward 0x06]
+ 0x340A0001, # ORI T2, R0, 0x0001 <- Werewolf plaque
+ 0xA44A01C8, # SH T2, 0x01C8 (V0)
+ 0x24090001, # ADDIU T1, R0, 0x0001
+ 0x1139FFED, # BEQ T1, T9, [backward 0x12]
+ 0x00000000, # NOP
+ 0x03E00008, # JR RA
+ # Villa front yard (replaces 1 moneybag and 2 beefs)
+ 0x24090003, # ADDIU T1, R0, 0x0003
+ 0x15090008, # BNE T0, T1, [forward 0x08]
+ 0x340A0001, # ORI T2, R0, 0x0001 <- Fountain FL
+ 0x340B0001, # ORI T3, R0, 0x0001 <- Fountain RL
+ 0x340C001F, # ORI T4, R0, 0x0001 <- Dog food gate
+ 0xA44A0058, # SH T2, 0x0058 (V0)
+ 0xA44B0038, # SH T3, 0x0038 (V0)
+ 0xA44C0068, # SH T4, 0x0068 (V0)
+ 0x03E00008, # JR RA
+ 0x00000000, # NOP
+ # Villa living area (Replaces 1 chicken, 1 knife, and 3 invisible Purifyings and assigns flags to the sub-weapons)
+ 0x24090005, # ADDIU T1, R0, 0x0005
+ 0x15090025, # BNE T0, T1, [forward 0x25]
+ 0x340B0010, # ORI T3, R0, 0x0001 <- Hallway axe
+ 0xA44B00B8, # SH T3, 0x00B8 (V0)
+ 0x340A0001, # ORI T2, R0, 0x0001 <- Storeroom R
+ 0x340B0010, # ORI T3, R0, 0x0001 <- Hallway knife
+ 0x340C0001, # ORI T4, R0, 0x0001 <- Living Room painting
+ 0x340D0001, # ORI T5, R0, 0x0001 <- Dining Room vase
+ 0x340E0001, # ORI T6, R0, 0x0001 <- Archives table
+ 0xA44A0078, # SH T2, 0x0078 (V0)
+ 0xA44B00C8, # SH T3, 0x00C8 (V0)
+ 0xA44C0108, # SH T4, 0x0108 (V0)
+ 0xA44D0128, # SH T5, 0x0128 (V0)
+ 0xA44E0138, # SH T6, 0x0138 (V0)
+ 0x340A0000, # ORI T2, R0, 0x0000 <- Sub-weapons left flag half
+ 0xA44A009C, # SH T2, 0x009C (V0)
+ 0xA44A00AC, # SH T2, 0x00AC (V0)
+ 0xA44A00BC, # SH T2, 0x00BC (V0)
+ 0xA44A00CC, # SH T2, 0x00CC (V0)
+ 0x340A0000, # ORI T2, R0, 0x0000 <- Sub-weapons right flag halves
+ 0x240B0000, # ADDIU T3, R0, 0x0000
+ 0x240C0000, # ADDIU T4, R0, 0x0000
+ 0x240D0000, # ADDIU T5, R0, 0x0000
+ 0xA44A00CA, # SH T2, 0x00CA (V0)
+ 0xA44B00BA, # SH T3, 0x00BA (V0)
+ 0xA44C009A, # SH T4, 0x009A (V0)
+ 0xA44D00AA, # SH T5, 0x00AA (V0)
+ 0x340A0001, # ORI T2, R0, 0x0001 <- Near bed
+ 0x340B0010, # ORI T3, R0, 0x0001 <- Storeroom L
+ 0x340C0001, # ORI T4, R0, 0x0001 <- Storeroom statue
+ 0x340D0001, # ORI T5, R0, 0x0001 <- Exit knight
+ 0x340E0001, # ORI T6, R0, 0x0001 <- Sitting room table
+ 0xA44A0048, # SH T2, 0x0078 (V0)
+ 0xA44B0088, # SH T3, 0x00C8 (V0)
+ 0xA44C00D8, # SH T4, 0x0108 (V0)
+ 0xA44D00F8, # SH T5, 0x0128 (V0)
+ 0xA44E0118, # SH T6, 0x0138 (V0)
+ 0x03E00008, # JR RA
+ 0x00000000, # NOP
+ # Tunnel (replaces 1 invisible Cure Ampoule)
+ 0x24090007, # ADDIU T1, R0, 0x0007
+ 0x1509000A, # BNE T0, T1, [forward 0x0A]
+ 0x340A0001, # ORI T2, R0, 0x0001 <- Twin arrow signs
+ 0xA44A0268, # SH T2, 0x0268 (V0)
+ 0x340A0001, # ORI T2, R0, 0x0001 <- Bucket
+ 0xA44A0258, # SH T2, 0x0258 (V0)
+ 0x240B0005, # ADDIU T3, R0, 0x0005
+ 0xA04B0150, # SB T3, 0x0150 (V0)
+ 0x24090011, # ADDIU T1, R0, 0x0011
+ 0x1139FFB0, # BEQ T1, T9, [backward 0x50]
+ 0x00000000, # NOP
+ 0x03E00008, # JR RA
+ # Castle Center factory floor (replaces 1 moneybag, 1 jewel, and gives every lizard man coffin item a unique flag)
+ 0x2409000B, # ADDIU T1, R0, 0x000B
+ 0x15090016, # BNE T0, T1, [forward 0x16]
+ 0x340A001A, # ORI T2, R0, 0x001A <- Lizard coffin nearside mid-right
+ 0x340B0003, # ORI T3, R0, 0x0003 <- Lizard coffin nearside mid-left
+ 0xA44A00C8, # SH T2, 0x00C8 (V0)
+ 0xA44B00D8, # SH T3, 0x00D8 (V0)
+ 0x240A1000, # ADDIU T2, R0, 0x1000
+ 0x240B2000, # ADDIU T3, R0, 0x2000
+ 0x240C0400, # ADDIU T4, R0, 0x0400
+ 0x240D0800, # ADDIU T5, R0, 0x0800
+ 0x240E0200, # ADDIU T6, R0, 0x0200
+ 0x240F0100, # ADDIU T7, R0, 0x0100
+ 0xA44A009A, # SH T2, 0x009A (V0)
+ 0xA44B00AA, # SH T3, 0x00AA (V0)
+ 0xA44C00CA, # SH T4, 0x00CA (V0)
+ 0xA44D00BA, # SH T5, 0x00BA (V0)
+ 0xA44E00DA, # SH T6, 0x00DA (V0)
+ 0xA44F00EA, # SH T7, 0x00EA (V0)
+ 0x340A0017, # ORI T2, R0, 0x0017 <- Lizard coffin nearside mid-right
+ 0x340B000C, # ORI T3, R0, 0x000C <- Lizard coffin nearside mid-left
+ 0xA44A00A8, # SH T2, 0x00C8 (V0)
+ 0xA44B00E8, # SH T3, 0x00D8 (V0)
+ 0x03E00008, # JR RA
+ 0x00000000, # NOP
+ # Duel Tower (replaces a flame on top of a rotating lion pillar with a White Jewel on the invisible bridge ledge)
+ 0x24090013, # ADDIU T1, R0, 0x0013
+ 0x1509000F, # BNE T0, T1, [forward 0x0F]
+ 0x3C0A00B9, # LUI T2, 0x00BB
+ 0x254A012B, # ADDIU T2, T2, 0x012B
+ 0x3C0BFE2A, # LUI T3, 0xFE2A
+ 0x256B0027, # ADDIU T3, T3, 0x0027
+ 0x3C0C0001, # LUI T4, 0x0001
+ 0x3C0D0022, # LUI T5, 0x0022
+ 0x25AD0100, # ADDIU T5, T5, 0x0100
+ 0xAC4A0A80, # SW T2, 0x0AE0 (V0)
+ 0xAC4B0A84, # SW T3, 0x0AE4 (V0)
+ 0xAC4C0A88, # SW T4, 0x0AE8 (V0)
+ 0xAC4D0A8C, # SW T5, 0x0AEC (V0)
+ 0x24090001, # ADDIU T1, R0, 0x0001
+ 0x1139FF87, # BEQ T1, T9, [backward 0x77]
+ 0x00000000, # NOP
+ 0x03E00008, # JR RA
+ # Castle Keep outside (replaces 1 invisible Healing Kit and gives both invisible Healing Kits pickup flags)
+ 0x24090014, # ADDIU T1, R0, 0x0014
+ 0x1509000A, # BNE T0, T1, [forward 0x0A]
+ 0x340A0001, # ORI T2, R0, 0x0001 <- Right flame
+ 0xA44A0058, # SH T2, 0x0058 (V0)
+ 0x240A0001, # ADDIU T2, R0, 0x0001
+ 0x240B0002, # ADDIU T3, R0, 0x0002
+ 0xA44A004A, # SH T2, 0x004A (V0)
+ 0xA44B005A, # SH T3, 0x005A (V0)
+ 0x24090002, # ADDIU T1, R0, 0x0002
+ 0x1139FF7B, # BEQ T0, T1, [backward 0x74]
+ 0x00000000, # NOP
+ 0x03E00008, # JR RA
+ # Castle Wall main area (sets a flag for the freestanding Holy Water if applicable and the "beginning of stage"
+ # state if entered from the rear)
+ 0x24090002, # ADDIU T1, R0, 0x0002
+ 0x15090006, # BNE T0, T1, [forward 0x06]
+ 0x24090000, # ADDIU T1, R0, 0x0000
+ 0xA049009B, # SB T1, 0x009B (V0)
+ 0x24090010, # ADDIU T1, R0, 0x0010
+ 0x1139FF73, # BEQ T1, T9, [backward 0x8D]
+ 0x00000000, # NOP
+ 0x03E00008, # JR RA
+ # Villa vampire crypt (sets the "beginning of stage" state if entered from the rear, as well as the "can warp here"
+ # flag if arriving for the first time)
+ 0x2409001A, # ADDIU T1, R0, 0x001A
+ 0x15090008, # BNE T0, T1, [forward 0x08]
+ 0x3C0A8039, # LUI T2, 0x8039
+ 0x914B9C1C, # LBU T3, 0x9C1C (T2)
+ 0x356B0001, # ORI T3, T3, 0x0001
+ 0xA14B9C1C, # SB T3, 0x9C1C (T2)
+ 0x24090003, # ADDIU T1, R0, 0x0003
+ 0x1139FF69, # BEQ T1, T9, [backward 0x98]
+ 0x00000000, # NOP
+ 0x03E00008, # JR RA
+ # Underground Waterway (sets the "beginning of stage" state if entered from the rear)
+ 0x24090008, # ADDIU T1, R0, 0x0008
+ 0x15090004, # BNE T0, T1, [forward 0x04]
+ 0x24090001, # ADDIU T1, R0, 0x0001
+ 0x1139FF63, # BEQ T1, T9, [backward 0x9F]
+ 0x00000000, # NOP
+ 0x03E00008, # JR RA
+ # Castle Center elevator top (sets the "beginning of stage" state if entered from either rear, as well as the "can
+ # warp here" flag if arriving for the first time)
+ 0x2409000F, # ADDIU T1, R0, 0x000F
+ 0x1509000A, # BNE T0, T1, [forward 0x0A]
+ 0x3C0A8039, # LUI T2, 0x8039
+ 0x914B9C1C, # LBU T3, 0x9C1C (T2)
+ 0x356B0002, # ORI T3, T3, 0x0002
+ 0xA14B9C1C, # SB T3, 0x9C1C (T2)
+ 0x24090002, # ADDIU T1, R0, 0x0002
+ 0x1139FF59, # BEQ T1, T9, [backward 0xAA]
+ 0x24090003, # ADDIU T1, R0, 0x0003
+ 0x1139FF57, # BEQ T1, T9, [backward 0xAC]
+ 0x00000000, # NOP
+ 0x03E00008, # JR RA
+ # Tower of Execution (sets the "beginning of stage" state if entered from the rear)
+ 0x24090010, # ADDIU T1, R0, 0x0010
+ 0x15090004, # BNE T0, T1, [forward 0x10]
+ 0x24090012, # ADDIU T1, R0, 0x0012
+ 0x1139FF51, # BEQ T1, T9, [backward 0xAF]
+ 0x00000000, # NOP
+ 0x03E00008, # JR RA
+ # Tower of Sorcery (sets the "beginning of stage" state if entered from the rear)
+ 0x24090011, # ADDIU T1, R0, 0x0011
+ 0x15090004, # BNE T0, T1, [forward 0x04]
+ 0x24090013, # ADDIU T1, R0, 0x0013
+ 0x1139FF4B, # BEQ T1, T9, [backward 0xBA]
+ 0x00000000, # NOP
+ 0x03E00008, # JR RA
+ # Tower of Science (sets the "beginning of stage" state if entered from the rear)
+ 0x24090012, # ADDIU T1, R0, 0x0012
+ 0x15090004, # BNE T0, T1, [forward 0x04]
+ 0x24090004, # ADDIU T1, R0, 0x0004
+ 0x1139FF45, # BEQ T1, T9, [backward 0xC1]
+ 0x00000000, # NOP
+ 0x03E00008, # JR RA
+ # Room of Clocks (changes 2 candle settings if applicable and sets the "begging of stage" state if spawning at end)
+ 0x2409001B, # ADDIU T1, R0, 0x001B
+ 0x15090008, # BNE T0, T1, [forward 0x08]
+ 0x24090006, # ADDIU T1, R0, 0x0006
+ 0x240A0006, # ADDIU T2, R0, 0x0006
+ 0xA0490059, # SB T1, 0x0059 (V0)
+ 0xA04A0069, # SB T2, 0x0069 (V0)
+ 0x24090014, # ADDIU T1, R0, 0x0014
+ 0x1139FF3B, # BEQ T1, T9, [backward 0xCC]
+ 0x00000000, # NOP
+ 0x03E00008, # JR RA
+ # Castle Center basement (changes 2 non-pickup-able Mandragoras into 2 real items and moves the torture shelf item
+ # forward slightly if it's turned visible)
+ 0x24090009, # ADDIU T1, R0, 0x0009
+ 0x15090011, # BNE T0, T1, [forward 0x11]
+ 0x3409FFFC, # ORI T1, R0, 0xFFFC
+ 0xA44907C0, # SH T1, 0x07C0 (V0)
+ 0xA44907D0, # SH T1, 0x07D0 (V0)
+ 0x240A0027, # ADDIU T2, R0, 0x0027
+ 0xA44A07C6, # SH T2, 0x07C6 (V0)
+ 0xA44A07D6, # SH T2, 0x07D6 (V0)
+ 0x340B0001, # ORI T3, R0, 0x0001 <- Right Mandragora
+ 0x340C0001, # ORI T4, R0, 0x0001 <- Left Mandragora
+ 0xA44B07C8, # SH T3, 0x07C8 (V0)
+ 0xA44C07D8, # SH T4, 0x07D8 (V0)
+ 0x240D00F5, # ADDIU T5, R0, 0x00F5
+ 0xA04D06D1, # SB T5, 0x06D1 (V0)
+ 0x24090040, # ADDIU T1, R0, 0x0040
+ 0x240A0080, # ADDIU T2, R0, 0x0080
+ 0xA04907CA, # SB T1, 0x07CA (V0)
+ 0xA04A07DA, # SB T2, 0x07DA (V0)
+ 0x03E00008, # JR RA
+ # Castle Center nitro area (changes 2 non-pickup-able Nitros into 2 real items)
+ 0x2409000E, # ADDIU T1, R0, 0x000E
+ 0x15090015, # BNE T0, T1, [forward 0x15]
+ 0x240900C0, # ADDIU T1, R0, 0x00C0
+ 0x240A00CE, # ADDIU T2, R0, 0x00CE
+ 0xA0490471, # SB T1, 0x0471 (V0)
+ 0xA04A04A1, # SB T2, 0x04A1 (V0)
+ 0x24090027, # ADDIU T1, R0, 0x0027
+ 0xA4490476, # SH T1, 0x0476 (V0)
+ 0xA44904A6, # SH T1, 0x04A6 (V0)
+ 0x340A0001, # ORI T2, R0, 0x0001 <- Invention-side shelf
+ 0x340B0001, # ORI T3, R0, 0x0001 <- Heinrich-side shelf
+ 0xA44A0478, # SH T2, 0x0478 (V0)
+ 0xA44B04A8, # SH T3, 0x04A8 (V0)
+ 0x24090080, # ADDIU T1, R0, 0x0080
+ 0xA049047A, # SB T1, 0x047A (V0)
+ 0xA440047C, # SH R0, 0x047C (V0)
+ 0x240A0400, # ADDIU T2, R0, 0x0400
+ 0x340BFF05, # ORI T3, R0, 0xFF05
+ 0xA44A04AA, # SH T2, 0x04AA (V0)
+ 0xA44B04AC, # SH T3, 0x04AC (V0)
+ 0x24090046, # ADDIU T1, R0, 0x0046
+ 0xA04904A3, # SB T1, 0x04A3 (V0)
+ 0x03E00008, # JR RA
+ # Fan meeting room (sets "beginning of stage" flag)
+ 0x24090019, # ADDIU T1, R0, 0x0019
+ 0x1109FF0D, # BEQ T1, T9, [backward 0xFB]
+ 0x00000000, # NOP
+ 0x03E00008, # JR RA
+]
+
+renon_cutscene_checker = [
+ # Prevents Renon's departure/pre-fight cutscene from playing if the player is either in the escape sequence or both
+ # did not spend the required 30K to fight him and lacks the required Special2s to fight Dracula.
+ 0x15810002, # BNE T4, AT, [forward 0x02]
+ 0x00000000, # NOP
+ 0x08049EB3, # J 0x80127ACC
+ 0x24090016, # ADDIU T1, R0, 0x0016
+ 0x11C90002, # BEQ T6, T1, [forward 0x02]
+ 0x00000000, # NOP
+ 0x08049ECA, # J 0x80127B28
+ 0x24190000, # ADDIU T9, R0, 0x0000
+ 0x8C696208, # LW T1, 0x6208 (V1)
+ 0x292A7531, # SLTI T2, T1, 0x7531
+ 0x51400001, # BEQZL T2, [forward 0x01]
+ 0x24190001, # ADDIU T9, R0, 0x0001
+ 0x3C0B8013, # LUI T3, 0x8013
+ 0x916BAC9F, # LBU T3, 0xAC9F (T3)
+ 0x906C6194, # LBU T4, 0x6194 (V1)
+ 0x018B502A, # SLT T2, T4, T3
+ 0x51400001, # BEQZL T2, [forward 0x01]
+ 0x24190001, # ADDIU T9, R0, 0x0001
+ 0x90696142, # LBU T1, 0x6142 (V1)
+ 0x31290002, # ANDI T1, T1, 0x0002
+ 0x55200001, # BNEZL T1, [forward 0x01]
+ 0x24190000, # ADDIU T9, R0, 0x0000
+ 0x17200003, # BNEZ T9, [forward 0x03]
+ 0x00000000, # NOP
+ 0x08049ECC, # J 0x80127B30
+ 0x00000000, # NOP
+ 0x08049ECA # J 0x80127B28
+]
+
+renon_cutscene_checker_jr = [
+ # Like renon_cutscene_checker, but without the checks for the Special2 and spent money counters. Inserted instead if
+ # the player chooses to guarantee or disable the Renon fight on their YAML.
+ 0x15810002, # BNE T4, AT, [forward 0x02]
+ 0x00000000, # NOP
+ 0x08049EB3, # J 0x80127ACC
+ 0x24090016, # ADDIU T1, R0, 0x0016
+ 0x11C90002, # BEQ T6, T1, [forward 0x02]
+ 0x00000000, # NOP
+ 0x08049ECA, # J 0x80127B28
+ 0x24190001, # ADDIU T9, R0, 0x0001
+ 0x90696142, # LBU T1, 0x6142 (V1)
+ 0x31290002, # ANDI T1, T1, 0x0002
+ 0x55200001, # BNEZL T1, [forward 0x01]
+ 0x24190000, # ADDIU T9, R0, 0x0000
+ 0x17200003, # BNEZ T9, [forward 0x03]
+ 0x00000000, # NOP
+ 0x08049ECC, # J 0x80127B30
+ 0x00000000, # NOP
+ 0x08049ECA # J 0x80127B28
+]
+
+ck_door_music_player = [
+ # Plays Castle Keep's song if you spawn in front of Dracula's door (teleporting via the warp menu) and haven't
+ # started the escape sequence yet.
+ 0x17010002, # BNE T8, AT, [forward 0x02]
+ 0x00000000, # NOP
+ 0x08063DF9, # J 0x8018F7E4
+ 0x240A0000, # ADDIU T2, R0, 0x0000
+ 0x3C088039, # LUI T0, 0x8039
+ 0x91089BFA, # LBU T0, 0x9BFA (T0)
+ 0x31080002, # ANDI T0, T0, 0x0002
+ 0x51090001, # BEQL T0, T1, [forward 0x01]
+ 0x254A0001, # ADDIU T2, T2, 0x0001
+ 0x24080003, # ADDIU T0, R0, 0x0003
+ 0x51180001, # BEQL T0, T8, [forward 0x01]
+ 0x254A0001, # ADDIU T2, T2, 0x0001
+ 0x240B0002, # ADDIU T3, R0, 0x0002
+ 0x114B0002, # BEQ T2, T3, [forward 0x02]
+ 0x00000000, # NOP
+ 0x08063DFD, # J 0x8018F7F4
+ 0x00000000, # NOP
+ 0x08063DF9 # J 0x8018F7E4
+]
+
+dracula_door_text_redirector = [
+ # Switches the standard pointer to the map text with one to a custom message for Dracula's chamber door if the
+ # current scene is Castle Keep exterior (Scene 0x14).
+ 0x3C088039, # LUI T0, 0x8039
+ 0x91089EE1, # LBU T0, 0x9EE1 (T0)
+ 0x24090014, # ADDIU T1, R0, 0x0014
+ 0x15090006, # BNE T0, T1, [forward 0x06]
+ 0x3C088014, # LUI T0, 0x8014
+ 0x2508B9F4, # ADDIU T0, T0, 0xB9F4
+ 0x151F0003, # BNE T0, RA, [forward 0x03]
+ 0x00000000, # NOP
+ 0x3C028040, # LUI V0, 0x8040
+ 0x2442CC48, # ADDIU V0, V0, 0xCC48
+ 0x03E00008 # JR RA
+]
+
+coffin_time_checker = [
+ # When entering the Villa coffin, this will check to see whether it's day or night and send you to either the Tunnel
+ # or Underground Waterway level slot accordingly regardless of which character you are
+ 0x28490006, # SLTI T1, V0, 0x0006
+ 0x15200005, # BNEZ T1, [forward 0x05]
+ 0x28490012, # SLTI T1, V0, 0x0012
+ 0x11200003, # BEQZ T1, [forward 0x03]
+ 0x00000000, # NOP
+ 0x08055AEB, # J 0x80156BAC
+ 0x00000000, # NOP
+ 0x08055AED # J 0x80156BB4
+]
+
+werebull_flag_unsetter = [
+ # This will un-set Were-bull's defeat flag in Duel Tower after beating him so that the check above his arena can
+ # still be acquired later, if it hasn't been acquired already. This is the only check in the entire game that can be
+ # permanently missed even with the ability to return to levels.
+ 0x3C0E0400, # LUI T6, 0x0400
+ 0x15CF0006, # BNE T6, T7, [forward 0x06]
+ 0x00187402, # SRL T6, T8, 16
+ 0x31CE2000, # ANDI T6, T6, 0x2000
+ 0x15C00003, # BNEZ T6, [forward 0x03]
+ 0x3C0E0020, # LUI T6, 0x0020
+ 0x014E5025, # OR T2, T2, T6
+ 0xAC4A613C, # SW T2, 0x613C (V0)
+ 0x03200008 # JR T9
+]
+
+werebull_flag_unsetter_special2_electric_boogaloo = [
+ # Like werebull_flag_unsetter, but with the added feature of awarding a Special2 after determining the player isn't
+ # trying to beat Were-bull twice! This will be inserted over the former if the goal is set to boss hunt.
+ 0x3C0E0400, # LUI T6, 0x0400
+ 0x15CF0008, # BNE T6, T7, [forward 0x06]
+ 0x00187402, # SRL T6, T8, 16
+ 0x31CE2000, # ANDI T6, T6, 0x2000
+ 0x15C00005, # BNEZ T6, [forward 0x05]
+ 0x3C0E0020, # LUI T6, 0x0020
+ 0x014EC024, # AND T8, T2, T6
+ 0x014E5025, # OR T2, T2, T6
+ 0xAC4A613C, # SW T2, 0x613C (V0)
+ 0x17000003, # BNEZ T8, [forward 0x03]
+ 0x3C188039, # LUI T8, 0x8039
+ 0x240E0005, # ADDIU T6, R0, 0x0005
+ 0xA30E9BDF, # SB T6, 0x9BDF (T8)
+ 0x03200008 # JR T9
+]
+
+werebull_flag_pickup_setter = [
+ # Checks to see if an item being picked up is the one on top of Were-bull's arena. If it is, then it'll check to see
+ # if our makeshift "Were-bull defeated once" flag and, if it is, set Were-bull's arena flag proper, so it'll
+ # permanently stay down.
+ 0x3C088038, # LUI T0, 0x8038
+ 0x25083AC8, # ADDIU T0, T0, 0x3AC8
+ 0x15020007, # BNE T0, V0, [forward 0x07]
+ 0x3C082000, # LUI T0, 0x2000
+ 0x15040005, # BNE T0, A0, [forward 0x05]
+ 0x9449612C, # LHU T1, 0x612C (V0)
+ 0x31290020, # ANDI T1, T1, 0x0020
+ 0x11200002, # BEQZ T1, [forward 0x02]
+ 0x3C0A0400, # LUI T2, 0x0400
+ 0x014D6825, # OR T5, T2, T5
+ 0xAC4D612C, # SW T5, 0x612C (V0)
+ 0x03E00008 # JR RA
+]
+
+boss_special2_giver = [
+ # Enables the rewarding of Special2s upon the vanishing of a boss's health bar when defeating it.
+
+ # Also sets a flag in the case of the Castle Wall White Dragons' health bar going away. Their defeat flag in vanilla
+ # is tied to hitting the lever after killing them, so this alternate flag is used to track them for the "All Bosses"
+ # goal in the event someone kills them and then warps out opting to not be a Konami pachinko champ.
+ 0x3C118035, # LUI S1, 0x8035
+ 0x962DF834, # LHU T5, 0xF834 (S1)
+ 0x240E3F73, # ADDIU T6, R0, 0x3F73
+ 0x15AE0012, # BNE T5, T6, [forward 0x12]
+ 0x3C118039, # LUI S1, 0x8039
+ 0x922D9EE1, # LBU T5, 0x9EE1 (S1)
+ 0x240E0013, # ADDIU T6, R0, 0x0013
+ 0x11AE000E, # BEQ T5, T6, [forward 0x0E]
+ 0x922F9BFA, # LBU T7, 0x9BFA (S1)
+ 0x31EF0001, # ANDI T7, T7, 0x0001
+ 0x15E0000B, # BNEZ T7, [forward 0x0B]
+ 0x240E0002, # ADDIU T6, R0, 0x0002
+ 0x15AE0006, # BNE T5, T6, [forward 0x06]
+ 0x00000000, # NOP
+ 0x862F9BF4, # LH T7, 0x9BF4 (S1)
+ 0x31ED0080, # ANDI T5, T7, 0x0080
+ 0x15A00005, # BNEZ T5, [forward 0x05]
+ 0x35EF0080, # ORI T7, T7, 0x0080
+ 0xA62F9BF4, # SH T7, 0x9BF4 (S1)
+ 0x240D0005, # ADDIU T5, R0, 0x0005
+ 0xA22D9BDF, # SB T5, 0x9BDF (S1)
+ 0xA22D9BE0, # SB T5, 0x9BE0 (S1)
+ 0x03E00008 # JR RA
+]
+
+boss_goal_checker = [
+ # Checks each boss flag to see if every boss with a health meter has been defeated and puts 0x0004 in V0 to
+ # disallow opening Dracula's door if not all have been.
+ 0x3C0A8039, # LUI T2, 0x8039
+ 0x954B9BF4, # LHU T3, 0x9BF4 (T2)
+ 0x316D0BA0, # ANDI T5, T3, 0x0BA0
+ 0x914B9BFB, # LBU T3, 0x9BFB (T2)
+ 0x000B6182, # SRL T4, T3, 6
+ 0x11800010, # BEQZ T4, [forward 0x10]
+ 0x240C00C0, # ADDIU T4, R0, 0x00C0
+ 0x01AC6821, # ADDU T5, T5, T4
+ 0x914B9BFD, # LBU T3, 0x9BFD (T2)
+ 0x316C0020, # ANDI T4, T3, 0x0020
+ 0x01AC6821, # ADDU T5, T5, T4
+ 0x914B9BFE, # LBU T3, 0x9BFE (T2)
+ 0x316C0010, # ANDI T4, T3, 0x0010
+ 0x01AC6821, # ADDU T5, T5, T4
+ 0x914B9C18, # LBU T3, 0x9C18 (T2)
+ 0x316C0010, # ANDI T4, T3, 0x0010
+ 0x01AC6821, # ADDU T5, T5, T4
+ 0x914B9C1B, # LBU T3, 0x9C1B (T2)
+ 0x000B6102, # SRL T4, T3, 4
+ 0x11800005, # BEQZ T4, [forward 0x05]
+ 0x240C0050, # ADDIU T4, R0, 0x0050
+ 0x01AC6821, # ADDU T5, T5, T4
+ 0x240E0CF0, # ADDIU T6, R0, 0x0CF0
+ 0x55CD0001, # BNEL T6, T5, [forward 0x01]
+ 0x24020004, # ADDIU V0, R0, 0x0004
+ 0x03E00008 # JR RA
+]
+
+special_goal_checker = [
+ # Checks the Special2 counter to see if the specified threshold has been reached and puts 0x0001 in V0 to disallow
+ # opening Dracula's door if it hasn't been.
+ 0x3C0A8039, # LUI T2, 0x8039
+ 0x914B9C4C, # LBU T3, 0x9C4C (T2)
+ 0x296A001E, # SLTI T2, T3, 0x001E
+ 0x55400001, # BNEZL T2, 0x8012AC8C
+ 0x24020001, # ADDIU V0, R0, 0x0001
+ 0x03E00008 # JR RA
+]
+
+warp_menu_rewrite = [
+ # Rewrite to the warp menu code to ensure each option can have its own scene ID, spawn ID, and fade color.
+ # Start Warp
+ 0x3C0E0000, # LUI T6, 0x0000
+ 0x25CE0000, # ADDIU T6, T6, 0x0000
+ 0x1000001F, # B [forward 0x1F]
+ 0x3C0F8000, # LUI T7, 0x8000
+ # Warp 1
+ 0x3C0E0000, # LUI T6, 0x0000
+ 0x25CE0000, # ADDIU T6, T6, 0x0000
+ 0x1000001B, # B [forward 0x1B]
+ 0x3C0F8040, # LUI T7, 0x8040
+ # Warp 2
+ 0x3C0E0000, # LUI T6, 0x0000
+ 0x25CE0000, # ADDIU T6, T6, 0x0000
+ 0x10000017, # B [forward 0x17]
+ 0x3C0F8080, # LUI T7, 0x8080
+ # Warp 3
+ 0x3C0E0000, # LUI T6, 0x0000
+ 0x25CE0000, # ADDIU T6, T6, 0x0000
+ 0x10000013, # B [forward 0x13]
+ 0x3C0F0080, # LUI T7, 0x0080
+ # Warp 4
+ 0x3C0E0000, # LUI T6, 0x0000
+ 0x25CE0000, # ADDIU T6, T6, 0x0000
+ 0x3C0F0080, # LUI T7, 0x0080
+ 0x1000000E, # B [forward 0x0E]
+ 0x25EF8000, # ADDIU T7, T7, 0x8000
+ # Warp 5
+ 0x3C0E0000, # LUI T6, 0x0000
+ 0x25CE0000, # ADDIU T6, T6, 0x0000
+ 0x1000000A, # B [forward 0x0A]
+ 0x340F8000, # ORI T7, R0, 0x8000
+ # Warp 6
+ 0x3C0E0000, # LUI T6, 0x0000
+ 0x25CE0000, # ADDIU T6, T6, 0x0000
+ 0x3C0F8000, # LUI T7, 0x8000
+ 0x10000005, # B [forward 0x05]
+ 0x35EF8000, # ORI T7, T7, 0x8000
+ # Warp 7
+ 0x3C0E0000, # LUI T6, 0x0000
+ 0x25CE0000, # ADDIU T6, T6, 0x0000
+ 0x3C0F8040, # LUI T7, 0x8040
+ 0x35EF8000, # ORI T7, T7, 0x8000
+ # Warp Crypt
+ 0x3C18800D, # LUI T8, 0x800D
+ 0x97185E20, # LHU T8, 0x5E20 (T8)
+ 0x24192000, # ADDIU T9, R0, 0x2000
+ 0x17190009, # BNE T8, T9, [forward 0x09]
+ 0x3C088039, # LUI T0, 0x8039
+ 0x91089C1C, # LBU T0, 0x9C1C (T0)
+ 0x31080001, # ANDI T0, T0, 0x0001
+ 0x1100000F, # BEQZ T0, [forward 0x0F]
+ 0x00000000, # NOP
+ 0x3C0E001A, # LUI T6, 0x001A
+ 0x25CE0003, # ADDIU T6, T6, 0x0003
+ 0x1000000B, # B [forward 0x0B]
+ 0x240F0000, # ADDIU T7, R0, 0x0000
+ # Warp Elevator
+ 0x24190010, # ADDIU T9, R0, 0x0010
+ 0x17190008, # BNE T8, T9, [forward 0x08]
+ 0x91089C1C, # LBU T0, 0x9C1C (T0)
+ 0x31080002, # ANDI T0, T0, 0x0002
+ 0x11000005, # BEQZ T0, [forward 0x05]
+ 0x00000000, # NOP
+ 0x3C0E000F, # LUI T6, 0x000F
+ 0x25CE0001, # ADDIU T6, T6, 0x0001
+ 0x3C0F8080, # LUI T7, 0x8080
+ 0x35EF8000, # ORI T7, T7, 0x8000
+ # All
+ 0xAC6E6428, # SW T6, 0x6428 (V1)
+ 0xAC6F642C, # SW T7, 0x642C (V1)
+ 0x2402001E, # ADDIU V0, R0, 0x001E
+ 0xA4626430, # SH V0, 0x6430 (V1)
+ 0xA4626432, # SH V0, 0x6432 (V1)
+]
+
+warp_pointer_table = [
+ # Changed pointer table addresses to go with the warp menu rewrite
+ 0x8012AD74,
+ 0x8012AD84,
+ 0x8012AD94,
+ 0x8012ADA4,
+ 0x8012ADB4,
+ 0x8012ADC8,
+ 0x8012ADD8,
+ 0x8012ADEC,
+]
+
+spawn_coordinates_extension = [
+ # Checks if the 0x10 bit is set in the spawn ID and references the below list of custom spawn coordinates if it is.
+ 0x316A0010, # ANDI T2, T3, 0x0010
+ 0x11400003, # BEQZ T2, [forward 0x03]
+ 0x8CD90008, # LW T9, 0x0008 (A2)
+ 0x3C198040, # LUI T9, 0x8040
+ 0x2739C2CC, # ADDIU T9, T9, 0xC2CC
+ 0x08054A83, # J 0x80152A0C
+ 0x00000000, # NOP
+ 0x00000000, # NOP
+
+ # Castle Wall end: 10
+ # player camera focus point
+ # x = 0xFFFF 0xFFFF 0xFFFF
+ # y = 0x0003 0x0012 0x000D
+ # z = 0xFFF3 0xEDFF 0xFFF3
+ # r = 0xC000
+ 0x0000FFFF,
+ 0x0003FFF3,
+ 0xC000FFFF,
+ 0x0012FFED,
+ 0xFFFF000D,
+ 0xFFF30000,
+
+ # Tunnel end: 11
+ # player camera focus point
+ # x = 0x0088 0x0087 0x0088
+ # y = 0x01D6 0x01F1 0x01E5
+ # z = 0xF803 0xF7D2 0xF803
+ # r = 0xC000
+ 0x008801D6,
+ 0xF803C000,
+ 0x008701F1,
+ 0xF7D20088,
+ 0x01E5F803,
+
+ # Tower of Execution end: 12
+ # player camera focus point
+ # x = 0x00AC 0x00EC 0x00AC
+ # y = 0x0154 0x0183 0x0160
+ # z = 0xFE8F 0xFE8F 0xFE8F
+ # r = 0x8000
+ 0x000000AC,
+ 0x0154FE8F,
+ 0x800000EC,
+ 0x0183FE8F,
+ 0x00AC0160,
+ 0xFE8F0000,
+
+ # Tower of Sorcery end: 13
+ # player camera focus point
+ # x = 0xFEB0 0xFE60 0xFEB0
+ # y = 0x0348 0x036D 0x0358
+ # z = 0xFEFB 0xFEFB 0xFEFB
+ # r = 0x0000
+ 0xFEB00348,
+ 0xFEFB0000,
+ 0xFE60036D,
+ 0xFEFBFEB0,
+ 0x0358FEFB,
+
+ # Room of Clocks end: 14
+ # player camera focus point
+ # x = 0x01B1 0x01BE 0x01B1
+ # y = 0x0006 0x001B 0x0015
+ # z = 0xFFCD 0xFFCD 0xFFCD
+ # r = 0x8000
+ 0x000001B1,
+ 0x0006FFCD,
+ 0x800001BE,
+ 0x001BFFCD,
+ 0x01B10015,
+ 0xFFCD0000,
+
+ # Duel Tower savepoint: 15
+ # player camera focus point
+ # x = 0x00B9 0x00B9 0x00B9
+ # y = 0x012B 0x0150 0x0138
+ # z = 0xFE20 0xFE92 0xFE20
+ # r = 0xC000
+ 0x00B9012B,
+ 0xFE20C000,
+ 0x00B90150,
+ 0xFE9200B9,
+ 0x0138FE20
+]
+
+waterway_end_coordinates = [
+ # Underground Waterway end: 01
+ # player camera focus point
+ # x = 0x0397 0x03A1 0x0397
+ # y = 0xFFC4 0xFFDC 0xFFD3
+ # z = 0xFDB9 0xFDB8 0xFDB9
+ # r = 0x8000
+ 0x00000397,
+ 0xFFC4FDB9,
+ 0x800003A1,
+ 0xFFDCFDB8,
+ 0x0397FFD3,
+ 0xFDB90000
+]
+
+continue_cursor_start_checker = [
+ # This is used to improve the Game Over screen's "Continue" menu by starting the cursor on whichever checkpoint
+ # is most recent instead of always on "Previously saved". If a menu has a cursor start value of 0xFF in its text
+ # data, this will read the byte at 0x80389BC0 to determine which option to start the cursor on.
+ 0x8208001C, # LB T0, 0x001C(S0)
+ 0x05010003, # BGEZ T0, [forward 0x03]
+ 0x3C098039, # LUI T1, 0x8039
+ 0x81289BC0, # LB T0, 0x9BC0 (T1)
+ 0xA208001C, # SB T0, 0x001C (S0)
+ 0x03E00008 # JR RA
+]
+
+savepoint_cursor_updater = [
+ # Sets the value at 0x80389BC0 to 0x00 after saving to let the Game Over screen's "Continue" menu know to start the
+ # cursor on "Previously saved" as well as updates the entrance variable for B warping. It then jumps to
+ # deathlink_counter_decrementer in the event we're loading a save from the Game Over screen.
+ 0x3C088039, # LUI T0, 0x8039
+ 0x91099C95, # LBU T1, 0x9C95 (T0)
+ 0x000948C0, # SLL T1, T1, 3
+ 0x3C0A8018, # LUI T2, 0x8018
+ 0x01495021, # ADDU T2, T2, T1
+ 0x914B17CF, # LBU T3, 0x17CF (T2)
+ 0xA10B9EE3, # SB T3, 0x9EE3 (T0)
+ 0xA1009BC0, # SB R0, 0x9BC0 (T0)
+ 0x080FF8F0 # J 0x803FE3C0
+]
+
+stage_start_cursor_updater = [
+ # Sets the value at 0x80389BC0 to 0x01 after entering a stage to let the Game Over screen's "Continue" menu know to
+ # start the cursor on "Restart this stage".
+ 0x3C088039, # LUI T0, 0x8039
+ 0x24090001, # ADDIU T1, R0, 0x0001
+ 0xA1099BC0, # SB T1, 0x9BC0 (T0)
+ 0x03E00008 # JR RA
+]
+
+elevator_flag_checker = [
+ # Prevents the top elevator in Castle Center from activating if the bottom elevator switch is not turned on.
+ 0x3C088039, # LUI T0, 0x8039
+ 0x91089C07, # LBU T0, 0x9C07 (T0)
+ 0x31080002, # ANDI T0, T0, 0x0002
+ 0x15000002, # BNEZ T0, [forward 0x02]
+ 0x848E004C, # LH T6, 0x004C (A0)
+ 0x240E0000, # ADDIU T6, R0, 0x0000
+ 0x03E00008 # JR RA
+]
+
+crystal_special2_giver = [
+ # Gives a Special2 upon activating the big crystal in CC basement.
+ 0x3C098039, # LUI T1, 0x8039
+ 0x24190005, # ADDIU T9, R0, 0x0005
+ 0xA1399BDF, # SB T9, 0x9BDF (T1)
+ 0x03E00008, # JR RA
+ 0x3C198000 # LUI T9, 0x8000
+]
+
+boss_save_stopper = [
+ # Prevents usage of a White Jewel if in a boss fight. Important for the lizard-man trio in Waterway as escaping
+ # their fight by saving/reloading can render a Special2 permanently missable.
+ 0x24080001, # ADDIU T0, R0, 0x0001
+ 0x15030005, # BNE T0, V1, [forward 0x05]
+ 0x3C088035, # LUI T0, 0x8035
+ 0x9108F7D8, # LBU T0, 0xF7D8 (T0)
+ 0x24090020, # ADDIU T1, R0, 0x0020
+ 0x51090001, # BEQL T0, T1, [forward 0x01]
+ 0x24020000, # ADDIU V0, R0, 0x0000
+ 0x03E00008 # JR RA
+]
+
+music_modifier = [
+ # Uses the ID of a song about to be played to pull a switcheroo by grabbing a new ID from a custom table to play
+ # instead. A hacky way to circumvent song IDs in the compressed overlays' "play song" function calls, but it works!
+ 0xAFBF001C, # SW RA, 0x001C (SP)
+ 0x0C004A6B, # JAL 0x800129AC
+ 0x44800000, # MTC1 R0, F0
+ 0x10400003, # BEQZ V0, [forward 0x03]
+ 0x3C088040, # LUI T0, 0x8040
+ 0x01044821, # ADDU T1, T0, A0
+ 0x9124CD20, # LBU A0, 0xCD20 (T1)
+ 0x08004E64 # J 0x80013990
+]
+
+music_comparer_modifier = [
+ # The same as music_modifier, but for the function that compares the "song to play" ID with the one that's currently
+ # playing. This will ensure the randomized music doesn't reset when going through a loading zone in Villa or CC.
+ 0x3C088040, # LUI T0, 0x8040
+ 0x01044821, # ADDU T1, T0, A0
+ 0x9124CD20, # LBU A0, 0xCD20 (T1)
+ 0x08004A60, # J 0x80012980
+]
+
+item_customizer = [
+ # Allows changing an item's appearance settings and visibility independent of what it actually is as well as setting
+ # its bitflag literally anywhere in the save file by changing things in the item actor's data as it's being created
+ # for the below three functions to then utilize.
+ 0x03205825, # OR T3, T9, R0
+ 0x000B5A02, # SRL T3, T3, 8
+ 0x316C0080, # ANDI T4, T3, 0x0080
+ 0xA0CC0041, # SB T4, 0x0041 (A2)
+ 0x016C5823, # SUBU T3, T3, T4
+ 0xA0CB0040, # SB T3, 0x0040 (A2)
+ 0x333900FF, # ANDI T9, T9, 0x00FF
+ 0xA4D90038, # SH T9, 0x0038 (A2)
+ 0x8CCD0058, # LW T5, 0x0058 (A2)
+ 0x31ACFF00, # ANDI T4, T5, 0xFF00
+ 0x340EFF00, # ORI T6, R0, 0xFF00
+ 0x158E000A, # BNE T4, T6, [forward 0x0A]
+ 0x31AC00FF, # ANDI T4, T5, 0x00FF
+ 0x240E0002, # ADDIU T6, R0, 0x0002
+ 0x018E001B, # DIVU T4, T6
+ 0x00006010, # MFHI T4
+ 0x000D5C02, # SRL T3, T5, 16
+ 0x51800001, # BEQZL T4, [forward 0x01]
+ 0x000B5C00, # SLL T3, T3, 16
+ 0x00006012, # MFLO T4
+ 0xA0CC0055, # SB T4, 0x0055 (A2)
+ 0xACCB0058, # SW T3, 0x0058 (A2)
+ 0x080494E5, # J 0x80125394
+ 0x032A0019 # MULTU T9, T2
+]
+
+item_appearance_switcher = [
+ # Determines an item's model appearance by checking to see if a different item appearance ID was written in a
+ # specific spot in the actor's data; if one wasn't, then the appearance value will be grabbed from the item's entry
+ # in the item property table like normal instead.
+ 0x92080040, # LBU T0, 0x0040 (S0)
+ 0x55000001, # BNEZL T0, T1, [forward 0x01]
+ 0x01002025, # OR A0, T0, R0
+ 0x03E00008, # JR RA
+ 0xAFA70024 # SW A3, 0x0024 (SP)
+]
+
+item_model_visibility_switcher = [
+ # If 80 is written one byte ahead of the appearance switch value in the item's actor data, parse 0C00 to the
+ # function that checks if an item should be invisible or not. Otherwise, grab that setting from the item property
+ # table like normal.
+ 0x920B0041, # LBU T3, 0x0041 (S0)
+ 0x316E0080, # ANDI T6, T3, 0x0080
+ 0x11C00003, # BEQZ T6, [forward 0x03]
+ 0x240D0C00, # ADDIU T5, R0, 0x0C00
+ 0x03E00008, # JR RA
+ 0x00000000, # NOP
+ 0x03E00008, # JR RA
+ 0x958D0004 # LHU T5, 0x0004 (T4)
+]
+
+item_shine_visibility_switcher = [
+ # Same as the above, but for item shines instead of the model.
+ 0x920B0041, # LBU T3, 0x0041 (S0)
+ 0x31690080, # ANDI T1, T3, 0x0080
+ 0x11200003, # BEQZ T1, [forward 0x03]
+ 0x00000000, # NOP
+ 0x03E00008, # JR RA
+ 0x240C0C00, # ADDIU T4, R0, 0x0C00
+ 0x03E00008, # JR RA
+ 0x958CA908 # LHU T4, 0xA908 (T4)
+]
+
+three_hit_item_flags_setter = [
+ # As the function to create items from the 3HB item lists iterates through said item lists, this will pass unique
+ # flag values to each item when calling the "create item instance" function by right-shifting said flag by a number
+ # of bits depending on which item in the list it is. Unlike the vanilla game which always puts flags of 0x00000000
+ # on each of these.
+ 0x8DC80008, # LW T0, 0x0008 (T6)
+ 0x240A0000, # ADDIU T2, R0, 0x0000
+ 0x00084C02, # SRL T1, T0, 16
+ 0x3108FFFF, # ANDI T0, T0, 0xFFFF
+ 0x00094842, # SRL T1, T1, 1
+ 0x15200003, # BNEZ T1, [forward 0x03]
+ 0x00000000, # NOP
+ 0x34098000, # ORI T1, R0, 0x8000
+ 0x25080001, # ADDIU T0, T0, 0x0001
+ 0x0154582A, # SLT T3, T2, S4
+ 0x1560FFF9, # BNEZ T3, [backward 0x07]
+ 0x254A0001, # ADDIU T2, T2, 0x0001
+ 0x00094C00, # SLL T1, T1, 16
+ 0x01094025, # OR T0, T0, T1
+ 0x0805971E, # J 0x80165C78
+ 0xAFA80010 # SW T0, 0x0010 (SP)
+]
+
+chandelier_item_flags_setter = [
+ # Same as the above, but for the unique function made specifically and ONLY for the Villa foyer chandelier's item
+ # list. KCEK, why the heck did you have to do this!?
+ 0x8F280014, # LW T0, 0x0014 (T9)
+ 0x240A0000, # ADDIU T2, R0, 0x0000
+ 0x00084C02, # SRL T1, T0, 16
+ 0x3108FFFF, # ANDI T0, T0, 0xFFFF
+ 0x00094842, # SRL T1, T1, 1
+ 0x15200003, # BNEZ T1, [forward 0x03]
+ 0x00000000, # NOP
+ 0x34098000, # ORI T1, R0, 0x8000
+ 0x25080001, # ADDIU T0, T0, 0x0001
+ 0x0155582A, # SLT T3, T2, S5
+ 0x1560FFF9, # BNEZ T3, [backward 0x07]
+ 0x254A0001, # ADDIU T2, T2, 0x0001
+ 0x00094C00, # SLL T1, T1, 16
+ 0x01094025, # OR T0, T0, T1
+ 0x0805971E, # J 0x80165C78
+ 0xAFA80010 # SW T0, 0x0010 (SP)
+]
+
+prev_subweapon_spawn_checker = [
+ # When picking up a sub-weapon this will check to see if it's different from the one the player already had (if they
+ # did have one) and jump to prev_subweapon_dropper, which will spawn a subweapon actor of what they had before
+ # directly behind them.
+ 0x322F3031, # Previous sub-weapon bytes
+ 0x10A00009, # BEQZ A1, [forward 0x09]
+ 0x00000000, # NOP
+ 0x10AD0007, # BEQ A1, T5, [forward 0x07]
+ 0x3C088040, # LUI T0, 0x8040
+ 0x01054021, # ADDU T0, T0, A1
+ 0x0C0FF418, # JAL 0x803FD060
+ 0x9104CFC3, # LBU A0, 0xCFC3 (T0)
+ 0x2484FF9C, # ADDIU A0, A0, 0xFF9C
+ 0x3C088039, # LUI T0, 0x8039
+ 0xAD049BD4, # SW A0, 0x9BD4 (T0)
+ 0x0804F0BF, # J 0x8013C2FC
+ 0x24020001 # ADDIU V0, R0, 0x0001
+]
+
+prev_subweapon_fall_checker = [
+ # Checks to see if a pointer to a previous sub-weapon drop actor spawned by prev_subweapon_dropper is in 80389BD4
+ # and calls the function in prev_subweapon_dropper to lower the weapon closer to the ground on the next frame if a
+ # pointer exists and its actor ID is 0x0027. Once it hits the ground or despawns, the connection to the actor will
+ # be severed by 0-ing out the pointer.
+ 0x3C088039, # LUI T0, 0x8039
+ 0x8D049BD4, # LW A0, 0x9BD4 (T0)
+ 0x10800008, # BEQZ A0, [forward 0x08]
+ 0x00000000, # NOP
+ 0x84890000, # LH T1, 0x0000 (A0)
+ 0x240A0027, # ADDIU T2, R0, 0x0027
+ 0x152A0004, # BNE T1, T2, [forward 0x04]
+ 0x00000000, # NOP
+ 0x0C0FF452, # JAL 0x803FD148
+ 0x00000000, # NOP
+ 0x50400001, # BEQZL V0, [forward 0x01]
+ 0xAD009BD4, # SW R0, 0x9BD4 (T0)
+ 0x080FF40F # J 0x803FD03C
+]
+
+prev_subweapon_dropper = [
+ # Spawns a pickup actor of the sub-weapon the player had before picking up a new one behind them at their current
+ # position like in other CVs. This will enable them to pick it back up again if they still want it.
+ # Courtesy of Moisés; see derp.c in the src folder for the C source code.
+ 0x27BDFFC8,
+ 0xAFBF001C,
+ 0xAFA40038,
+ 0xAFB00018,
+ 0x0C0006B4,
+ 0x2404016C,
+ 0x00402025,
+ 0x0C000660,
+ 0x24050027,
+ 0x1040002B,
+ 0x00408025,
+ 0x3C048035,
+ 0x848409DE,
+ 0x00042023,
+ 0x0C0230D4,
+ 0x3084FFFF,
+ 0x44822000,
+ 0x3C018040,
+ 0xC428D370,
+ 0x468021A0,
+ 0x3C048035,
+ 0x848409DE,
+ 0x00042023,
+ 0x46083282,
+ 0x3084FFFF,
+ 0x0C01FFAC,
+ 0xE7AA0024,
+ 0x44828000,
+ 0x3C018040,
+ 0xC424D374,
+ 0x468084A0,
+ 0x27A40024,
+ 0x00802825,
+ 0x3C064100,
+ 0x46049182,
+ 0x0C004562,
+ 0xE7A6002C,
+ 0x3C058035,
+ 0x24A509D0,
+ 0x26040064,
+ 0x0C004530,
+ 0x27A60024,
+ 0x3C018035,
+ 0xC42809D4,
+ 0x3C0140A0,
+ 0x44815000,
+ 0x00000000,
+ 0x460A4400,
+ 0xE6100068,
+ 0xC6120068,
+ 0xE6120034,
+ 0x8FAE0038,
+ 0xA60E0038,
+ 0x8FBF001C,
+ 0x8FB00018,
+ 0x27BD0038,
+ 0x03E00008,
+ 0x00000000,
+ 0x3C068040,
+ 0x24C6D368,
+ 0x90CE0000,
+ 0x27BDFFE8,
+ 0xAFBF0014,
+ 0x15C00027,
+ 0x00802825,
+ 0x240400DB,
+ 0x0C0006B4,
+ 0xAFA50018,
+ 0x44802000,
+ 0x3C038040,
+ 0x2463D364,
+ 0x3C068040,
+ 0x24C6D368,
+ 0x8FA50018,
+ 0x1040000A,
+ 0xE4640000,
+ 0x8C4F0024,
+ 0x3C013F80,
+ 0x44814000,
+ 0xC5E60044,
+ 0xC4700000,
+ 0x3C018040,
+ 0x46083280,
+ 0x460A8480,
+ 0xE432D364,
+ 0x94A20038,
+ 0x2401000F,
+ 0x24180001,
+ 0x10410006,
+ 0x24010010,
+ 0x10410004,
+ 0x2401002F,
+ 0x10410002,
+ 0x24010030,
+ 0x14410005,
+ 0x3C014040,
+ 0x44813000,
+ 0xC4640000,
+ 0x46062200,
+ 0xE4680000,
+ 0xA0D80000,
+ 0x10000023,
+ 0x24020001,
+ 0x3C038040,
+ 0x2463D364,
+ 0xC4600000,
+ 0xC4A20068,
+ 0x3C038039,
+ 0x24639BD0,
+ 0x4600103E,
+ 0x00001025,
+ 0x45000006,
+ 0x00000000,
+ 0x44808000,
+ 0xE4A00068,
+ 0xA0C00000,
+ 0x10000014,
+ 0xE4700000,
+ 0x3C038039,
+ 0x24639BD0,
+ 0x3C018019,
+ 0xC42AC870,
+ 0xC4600000,
+ 0x460A003C,
+ 0x00000000,
+ 0x45000006,
+ 0x3C018019,
+ 0xC432C878,
+ 0x46120100,
+ 0xE4640000,
+ 0xC4600000,
+ 0xC4A20068,
+ 0x46001181,
+ 0x24020001,
+ 0xE4A60068,
+ 0xC4A80068,
+ 0xE4A80034,
+ 0x8FBF0014,
+ 0x27BD0018,
+ 0x03E00008,
+ 0x00000000,
+ 0x00000000,
+ 0x00000000,
+ 0x00000000,
+ 0x0000001B,
+ 0x060048E0,
+ 0x40000000,
+ 0x06AEFFD3,
+ 0x06004B30,
+ 0x40000000,
+ 0x00000000,
+ 0x06004CB8,
+ 0x0000031A,
+ 0x002C0000,
+ 0x060059B8,
+ 0x40000248,
+ 0xFFB50186,
+ 0x06005B68,
+ 0xC00001DF,
+ 0x00000000,
+ 0x06005C88,
+ 0x80000149,
+ 0x00000000,
+ 0x06005DC0,
+ 0xC0000248,
+ 0xFFB5FE7B,
+ 0x06005F70,
+ 0xC00001E0,
+ 0x00000000,
+ 0x06006090,
+ 0x8000014A,
+ 0x00000000,
+ 0x06007D28,
+ 0x4000010E,
+ 0xFFF100A5,
+ 0x06007F60,
+ 0xC0000275,
+ 0x00000000,
+ 0x06008208,
+ 0x800002B2,
+ 0x00000000,
+ 0x060083B0,
+ 0xC000010D,
+ 0xFFF2FF5C,
+ 0x060085E8,
+ 0xC0000275,
+ 0x00000000,
+ 0x06008890,
+ 0x800002B2,
+ 0x00000000,
+ 0x3D4CCCCD,
+ 0x3FC00000,
+ 0x00000000,
+ 0x00000000,
+ 0x00000000,
+ 0x00000000,
+ 0xB8000100,
+ 0xB8000100,
+]
+
+subweapon_surface_checker = [
+ # During the process of remotely giving an item received via multiworld, this will check to see if the item being
+ # received is a subweapon and, if it is, wait until the player is not above an abyss or instant kill surface before
+ # giving it. This is to ensure dropped previous subweapons won't land somewhere inaccessible.
+ 0x2408000D, # ADDIU T0, R0, 0x000D
+ 0x11040006, # BEQ T0, A0, [forward 0x06]
+ 0x2409000E, # ADDIU T1, R0, 0x000E
+ 0x11240004, # BEQ T1, A0, [forward 0x04]
+ 0x2408000F, # ADDIU T0, R0, 0x000F
+ 0x11040002, # BEQ T0, A0, [forward 0x02]
+ 0x24090010, # ADDIU T1, R0, 0x0010
+ 0x1524000B, # BNE T1, A0, [forward 0x0B]
+ 0x3C0A800D, # LUI T2, 0x800D
+ 0x8D4A7B5C, # LW T2, 0x7B5C (T2)
+ 0x1140000E, # BEQZ T2, [forward 0x0E]
+ 0x00000000, # NOP
+ 0x914A0001, # LBU T2, 0x0001 (T2)
+ 0x240800A2, # ADDIU T0, R0, 0x00A2
+ 0x110A000A, # BEQ T0, T2, [forward 0x0A]
+ 0x24090092, # ADDIU T1, R0, 0x0092
+ 0x112A0008, # BEQ T1, T2, [forward 0x08]
+ 0x24080080, # ADDIU T0, R0, 0x0080
+ 0x110A0006, # BEQ T0, T2, [forward 0x06]
+ 0x956C00DD, # LHU T4, 0x00DD (T3)
+ 0xA1600000, # SB R0, 0x0000 (T3)
+ 0x258C0001, # ADDIU T4, T4, 0x0001
+ 0x080FF8D0, # J 0x803FE340
+ 0xA56C00DD, # SH T4, 0x00DD (T3)
+ 0x00000000, # NOP
+ 0x03E00008 # JR RA
+]
+
+countdown_number_displayer = [
+ # Displays a number below the HUD clock of however many items are left to find in whichever stage the player is in.
+ # Which number in the save file to display depends on which map the player is currently on. It can track either
+ # items marked progression only or all locations in the stage.
+ # Courtesy of Moisés; see print_text_ovl.c in the src folder for the C source code.
+ 0x27BDFFD8,
+ 0xAFBF0024,
+ 0x00002025,
+ 0x0C000360,
+ 0x2405000C,
+ 0x3C038040,
+ 0x3C198034,
+ 0x2463D6D0,
+ 0x37392814,
+ 0x240E0002,
+ 0x3C0F0860,
+ 0x24180014,
+ 0xAC620000,
+ 0xAFB80018,
+ 0xAFAF0014,
+ 0xAFAE0010,
+ 0xAFB9001C,
+ 0x00002025,
+ 0x00402825,
+ 0x2406001E,
+ 0x0C0FF55D,
+ 0x24070028,
+ 0x8FBF0024,
+ 0x3C018040,
+ 0xAC22D6D4,
+ 0x03E00008,
+ 0x27BD0028,
+ 0x27BDFFE0,
+ 0xAFA40020,
+ 0x93AE0023,
+ 0x3C058039,
+ 0xAFBF001C,
+ 0x3C048040,
+ 0x3C068040,
+ 0x240F0014,
+ 0x00AE2821,
+ 0x90A59CA4,
+ 0xAFAF0010,
+ 0x8CC6D6D0,
+ 0x8C84D6D4,
+ 0x0C0FF58A,
+ 0x24070002,
+ 0x8FBF001C,
+ 0x27BD0020,
+ 0x03E00008,
+ 0x00000000,
+ 0x00000000,
+ 0x00000000,
+ 0x90820000,
+ 0x00001825,
+ 0x50400008,
+ 0xA4A00000,
+ 0xA4A20000,
+ 0x90820001,
+ 0x24840001,
+ 0x24A50002,
+ 0x1440FFFB,
+ 0x24630001,
+ 0xA4A00000,
+ 0x03E00008,
+ 0x00601025,
+ 0x27BDFFD8,
+ 0xAFBF0024,
+ 0xAFB0001C,
+ 0xAFA40028,
+ 0xAFA5002C,
+ 0xAFB10020,
+ 0xAFA60030,
+ 0xAFA70034,
+ 0x00008025,
+ 0x24050064,
+ 0x0C000360,
+ 0x00002025,
+ 0x8FA40040,
+ 0x00408825,
+ 0x3C05800A,
+ 0x10800004,
+ 0x8FA6003C,
+ 0x0C04B2E2,
+ 0x8CA5B450,
+ 0x00408025,
+ 0x5200001A,
+ 0x8FBF0024,
+ 0x12200017,
+ 0x8FAE0028,
+ 0x11C00015,
+ 0x02002025,
+ 0x97A5002E,
+ 0x97A60032,
+ 0x0C04B33F,
+ 0x24070001,
+ 0x02002025,
+ 0x83A50037,
+ 0x87A6003A,
+ 0x00003825,
+ 0x0C04B345,
+ 0xAFA00010,
+ 0x8FA40028,
+ 0x0C0FF51C,
+ 0x02202825,
+ 0x0C006CF0,
+ 0x02202025,
+ 0x02002025,
+ 0x02202825,
+ 0x00003025,
+ 0x0C04B34E,
+ 0x00003825,
+ 0x8FBF0024,
+ 0x02001025,
+ 0x8FB0001C,
+ 0x8FB10020,
+ 0x03E00008,
+ 0x27BD0028,
+ 0x27BDFFD8,
+ 0x8FAE0044,
+ 0xAFB00020,
+ 0xAFBF0024,
+ 0xAFA40028,
+ 0xAFA5002C,
+ 0xAFA60030,
+ 0xAFA70034,
+ 0x11C00007,
+ 0x00008025,
+ 0x3C05800A,
+ 0x8CA5B450,
+ 0x01C02025,
+ 0x0C04B2E2,
+ 0x8FA6003C,
+ 0x00408025,
+ 0x12000017,
+ 0x8FAF002C,
+ 0x11E00015,
+ 0x02002025,
+ 0x97A50032,
+ 0x97A60036,
+ 0x0C04B33F,
+ 0x24070001,
+ 0x02002025,
+ 0x24050001,
+ 0x24060064,
+ 0x00003825,
+ 0x0C04B345,
+ 0xAFA00010,
+ 0x8FA40028,
+ 0x8FA5002C,
+ 0x93A6003B,
+ 0x0C04B5BD,
+ 0x8FA70040,
+ 0x02002025,
+ 0x8FA5002C,
+ 0x00003025,
+ 0x0C04B34E,
+ 0x00003825,
+ 0x8FBF0024,
+ 0x02001025,
+ 0x8FB00020,
+ 0x03E00008,
+ 0x27BD0028,
+ 0x27BDFFE8,
+ 0xAFBF0014,
+ 0xAFA40018,
+ 0xAFA5001C,
+ 0xAFA60020,
+ 0x10C0000B,
+ 0xAFA70024,
+ 0x00A02025,
+ 0x00C02825,
+ 0x93A60027,
+ 0x0C04B5BD,
+ 0x8FA70028,
+ 0x8FA20018,
+ 0x3C010100,
+ 0x8C4F0000,
+ 0x01E1C025,
+ 0xAC580000,
+ 0x8FBF0014,
+ 0x27BD0018,
+ 0x03E00008,
+ 0x00000000,
+ 0xAFA50004,
+ 0x1080000E,
+ 0x30A500FF,
+ 0x24010001,
+ 0x54A10008,
+ 0x8C980000,
+ 0x8C8E0000,
+ 0x3C017FFF,
+ 0x3421FFFF,
+ 0x01C17824,
+ 0x03E00008,
+ 0xAC8F0000,
+ 0x8C980000,
+ 0x3C018000,
+ 0x0301C825,
+ 0xAC990000,
+ 0x03E00008,
+ 0x00000000,
+ 0x00000000,
+ 0x00000000,
+ 0x00000000,
+ 0x00000000,
+ 0x00000000
+]
+
+countdown_number_manager = [
+ # Tables and code for managing things about the Countdown number at the appropriate times.
+ 0x00010102, # Map ID offset table start
+ 0x02020D03,
+ 0x04050505,
+ 0x0E0E0E05,
+ 0x07090806,
+ 0x0C0C000B,
+ 0x0C050D0A,
+ 0x00000000, # Table end
+ 0x00000000,
+ 0x00000000,
+ 0x00000000,
+ 0x00000000,
+ 0x00000000,
+ 0x00000000,
+ 0x00000000,
+ 0x00000000,
+ 0x00000001, # Major identifiers table start
+ 0x01000000,
+ 0x00000000,
+ 0x00000000,
+ 0x01000000,
+ 0x01010000,
+ 0x00010101,
+ 0x01010101,
+ 0x01010101,
+ 0x01010000,
+ 0x00000000, # Table end
+ # Decrements the counter upon picking up an item if the counter should be decremented.
+ 0x90E80039, # LBU T0, 0x0039 (A3)
+ 0x240B0011, # ADDIU T3, R0, 0x0011
+ 0x110B0002, # BEQ T0, T3, [forward 0x02]
+ 0x90EA0040, # LBU T2, 0x0040 (A3)
+ 0x2548FFFF, # ADDIU T0, T2, 0xFFFF
+ 0x3C098040, # LUI T1, 0x8040
+ 0x01284821, # ADDIU T1, T1, T0
+ 0x9129D71C, # LBU T1, 0xD71C (T1)
+ 0x11200009, # BEQZ T1, [forward 0x09]
+ 0x3C088039, # LUI T0, 0x8039
+ 0x91099EE1, # LBU T1, 0x9EE1 (T0)
+ 0x3C0A8040, # LUI T2, 0x8040
+ 0x01495021, # ADDU T2, T2, T1
+ 0x914AD6DC, # LBU T2, 0xD6DC (T2)
+ 0x010A4021, # ADDU T0, T0, T2
+ 0x91099CA4, # LBU T1, 0x9CA4 (T0)
+ 0x2529FFFF, # ADDIU T1, T1, 0xFFFF
+ 0xA1099CA4, # SB T1, 0x9CA4 (T0)
+ 0x03E00008, # JR RA
+ 0x00000000, # NOP
+ # Moves the number to/from its pause menu position when pausing/un-pausing.
+ 0x3C088040, # LUI T0, 0x8040
+ 0x8D08D6D4, # LW T0, 0xD6D4
+ 0x11000009, # BEQZ T0, [forward 0x09]
+ 0x92090000, # LBU T1, 0x0000 (S0)
+ 0x14200004, # BNEZ AT, [forward 0x04]
+ 0x3C0A0033, # LUI T2, 0x0033
+ 0x254A001F, # ADDIU T2, T2, 0x001F
+ 0x03E00008, # JR RA
+ 0xAD0A0014, # SW T2, 0x0014 (T0)
+ 0x3C0A00D4, # LUI T2, 0x00D4
+ 0x254A003C, # ADDIU T2, T2, 0x003C
+ 0xAD0A0014, # SW T2, 0x0014 (T0)
+ 0x03E00008, # JR RA
+ 0x00000000, # NOP
+ # Hides the number when going into a cutscene or the Options menu.
+ 0x3C048040, # LUI A0, 0x8040
+ 0x8C84D6D4, # LW A0, 0xD6D4 (A0)
+ 0x0C0FF59F, # JAL 0x803FD67C
+ 0x24050000, # ADDIU A1, R0, 0x0000
+ 0x0804DFE0, # J 0x80137FB0
+ 0x3C048000, # LUI A0, 0x8000
+ 0x00000000, # NOP
+ # Un-hides the number when leaving a cutscene or the Options menu.
+ 0x3C048040, # LUI A0, 0x8040
+ 0x8C84D6D4, # LW A0, 0xD6D4 (A0)
+ 0x0C0FF59F, # JAL 0x803FD67C
+ 0x24050001, # ADDIU A1, R0, 0x0000
+ 0x0804DFFA, # J 0x8013
+ 0x3C047FFF, # LUI A0, 0x7FFFF
+ 0x00000000, # NOP
+ # Kills the last map's pointer to the Countdown stuff.
+ 0x3C088040, # LUI T0, 0x8040
+ 0xFD00D6D0, # SD R0, 0xD6D0 (T0)
+ 0x03E00008 # JR RA
+]
+
+new_game_extras = [
+ # Upon starting a new game, this will write anything extra to the save file data that the run should have at the
+ # start. The initial Countdown numbers begin here.
+ 0x24080000, # ADDIU T0, R0, 0x0000
+ 0x24090010, # ADDIU T1, R0, 0x0010
+ 0x11090008, # BEQ T0, T1, [forward 0x08]
+ 0x3C0A8040, # LUI T2, 0x8040
+ 0x01485021, # ADDU T2, T2, T0
+ 0x8D4AD818, # LW T2, 0xD818 (T2)
+ 0x3C0B8039, # LUI T3, 0x8039
+ 0x01685821, # ADDU T3, T3, T0
+ 0xAD6A9CA4, # SW T2, 0x9CA4 (T3)
+ 0x1000FFF8, # B [backward 0x08]
+ 0x25080004, # ADDIU T0, T0, 0x0004
+ # start_inventory begins here
+ 0x3C088039, # LUI T0, 0x8039
+ 0x91099C27, # LBU T1, 0x9C27 (T0)
+ 0x31290010, # ANDI T1, T1, 0x0010
+ 0x15200005, # BNEZ T1, [forward 0x05]
+ 0x24090000, # ADDIU T1, R0, 0x0000 <- Starting jewels
+ 0xA1099C49, # SB T1, 0x9C49
+ 0x3C0A8040, # LUI T2, 0x8040
+ 0x8D4BE514, # LW T3, 0xE514 (T2) <- Starting money
+ 0xAD0B9C44, # SW T3, 0x9C44 (T0)
+ 0x24090000, # ADDIU T1, R0, 0x0000 <- Starting PowerUps
+ 0xA1099CED, # SB T1, 0x9CED (T0)
+ 0x24090000, # ADDIU T1, R0, 0x0000 <- Starting sub-weapon
+ 0xA1099C43, # SB T1, 0x9C43 (T0)
+ 0x24090000, # ADDIU T1, R0, 0x0000 <- Starting Ice Traps
+ 0xA1099BE2, # SB T1, 0x9BE2 (T0)
+ 0x240C0000, # ADDIU T4, R0, 0x0000
+ 0x240D0022, # ADDIU T5, R0, 0x0022
+ 0x11AC0007, # BEQ T5, T4, [forward 0x07]
+ 0x3C0A8040, # LUI T2, 0x8040
+ 0x014C5021, # ADDU T2, T2, T4
+ 0x814AE518, # LB T2, 0xE518 <- Starting inventory items
+ 0x25080001, # ADDIU T0, T0, 0x0001
+ 0xA10A9C4A, # SB T2, 0x9C4A (T0)
+ 0x1000FFF9, # B [backward 0x07]
+ 0x258C0001, # ADDIU T4, T4, 0x0001
+ 0x03E00008 # JR RA
+]
+
+shopsanity_stuff = [
+ # Everything related to shopsanity.
+ # Flag table (in bytes) start
+ 0x80402010,
+ 0x08000000,
+ 0x00000000,
+ 0x00000000,
+ 0x00040200,
+ # Replacement item table (in halfwords) start
+ 0x00030003,
+ 0x00030003,
+ 0x00030000,
+ 0x00000000,
+ 0x00000000,
+ 0x00000000,
+ 0x00000000,
+ 0x00000000,
+ 0x00000003,
+ 0x00030000,
+ # Switches the vanilla item being bought with the randomized one, if its flag is un-set, and sets its flag.
+ 0x3C088040, # LUI T0, 0x8040
+ 0x01044021, # ADDU T0, T0, A0
+ 0x9109D8CA, # LBU T1, 0xD8CA (T0)
+ 0x3C0B8039, # LUI T3, 0x8039
+ 0x916A9C1D, # LBU T2, 0x9C1D (T3)
+ 0x01496024, # AND T4, T2, T1
+ 0x15800005, # BNEZ T4, [forward 0x05]
+ 0x01495025, # OR T2, T2, T1
+ 0xA16A9C1D, # SB T2, 0x9C1D (T3)
+ 0x01044021, # ADDU T0, T0, A0
+ 0x9504D8D8, # LHU A0, 0xD8D8 (T0)
+ 0x308400FF, # ANDI A0, A0, 0x00FF
+ 0x0804EFFB, # J 0x8013BFEC
+ 0x00000000, # NOP
+ # Switches the vanilla item model on the buy menu with the randomized item if the randomized item isn't purchased.
+ 0x3C088040, # LUI T0, 0x8040
+ 0x01044021, # ADDU T0, T0, A0
+ 0x9109D8CA, # LBU T1, 0xD8CA (T0)
+ 0x3C0B8039, # LUI T3, 0x8039
+ 0x916A9C1D, # LBU T2, 0x9C1D (T3)
+ 0x01495024, # AND T2, T2, T1
+ 0x15400005, # BNEZ T2, [forward 0x05]
+ 0x01044021, # ADDU T0, T0, A0
+ 0x9504D8D8, # LHU A0, 0xD8D8 (T0)
+ 0x00046202, # SRL T4, A0, 8
+ 0x55800001, # BNEZL T4, [forward 0x01]
+ 0x01802021, # ADDU A0, T4, R0
+ 0x0804F180, # J 0x8013C600
+ 0x00000000, # NOP
+ # Replacement item names table start.
+ 0x00010203,
+ 0x04000000,
+ 0x00000000,
+ 0x00000000,
+ 0x00050600,
+ 0x00000000,
+ # Switches the vanilla item name in the shop menu with the randomized item if the randomized item isn't purchased.
+ 0x3C088040, # LUI T0, 0x8040
+ 0x01064021, # ADDU T0, T0, A2
+ 0x9109D8CA, # LBU T1, 0xD8CA (T0)
+ 0x3C0B8039, # LUI T3, 0x8039
+ 0x916A9C1D, # LBU T2, 0x9C1D (T3)
+ 0x01495024, # AND T2, T2, T1
+ 0x15400004, # BNEZ T2, [forward 0x04]
+ 0x00000000, # NOP
+ 0x9105D976, # LBU A1, 0xD976 (T0)
+ 0x3C048001, # LUI A0, 8001
+ 0x3484A100, # ORI A0, A0, 0xA100
+ 0x0804B39F, # J 0x8012CE7C
+ 0x00000000, # NOP
+ 0x00000000,
+ 0x00000000,
+ 0x00000000,
+ 0x00000000,
+ 0x00000000,
+ # Displays "Not purchased." if the selected randomized item is nor purchased, or the current holding amount of that
+ # slot's vanilla item if it is.
+ 0x3C0C8040, # LUI T4, 0x8040
+ 0x018B6021, # ADDU T4, T4, T3
+ 0x918DD8CA, # LBU T5, 0xD8CA (T4)
+ 0x3C0E8039, # LUI T6, 0x8039
+ 0x91D89C1D, # LBU T8, 0x9C1D (T6)
+ 0x030DC024, # AND T8, T8, T5
+ 0x13000003, # BEQZ T8, [forward 0x03]
+ 0x00000000, # NOP
+ 0x0804E819, # J 0x8013A064
+ 0x00000000, # NOP
+ 0x0804E852, # J 0x8013A148
+ 0x820F0061, # LB T7, 0x0061 (S0)
+ 0x00000000, # NOP
+ # Displays a custom item description if the selected randomized item is not purchased.
+ 0x3C088040, # LUI T0, 0x8040
+ 0x01054021, # ADDU T0, T0, A1
+ 0x9109D8D0, # LBU T1, 0xD8D0 (T0)
+ 0x3C0A8039, # LUI T2, 0x8039
+ 0x914B9C1D, # LBU T3, 0x9C1D (T2)
+ 0x01695824, # AND T3, T3, T1
+ 0x15600003, # BNEZ T3, [forward 0x03]
+ 0x00000000, # NOP
+ 0x3C048002, # LUI A0, 0x8002
+ 0x24849C00, # ADDIU A0, A0, 0x9C00
+ 0x0804B39F # J 0x8012CE7C
+]
+
+special_sound_notifs = [
+ # Plays a distinct sound whenever you get enough Special1s to unlock a new location or enough Special2s to unlock
+ # Dracula's door.
+ 0x3C088013, # LUI A0, 0x8013
+ 0x9108AC9F, # LBU T0, 0xAC57 (T0)
+ 0x3C098039, # LUI T1, 0x8039
+ 0x91299C4C, # LBU T1, 0x9C4B (T1)
+ 0x15090003, # BNE T0, T1, [forward 0x03]
+ 0x00000000, # NOP
+ 0x0C004FAB, # JAL 0x80013EAC
+ 0x24040162, # ADDIU A0, R0, 0x0162
+ 0x0804F0BF, # J 0x8013C2FC
+ 0x00000000, # NOP
+ 0x3C088013, # LUI T0, 0x8013
+ 0x9108AC57, # LBU T0, 0xAC57 (T0)
+ 0x3C098039, # LUI T1, 0x8039
+ 0x91299C4B, # LBU T1, 0x9C4B (T1)
+ 0x0128001B, # DIVU T1, T0
+ 0x00005010, # MFHI
+ 0x15400006, # BNEZ T2, [forward 0x06]
+ 0x00005812, # MFLO T3
+ 0x296C0008, # SLTI T4, T3, 0x0008
+ 0x11800003, # BEQZ T4, [forward 0x03]
+ 0x00000000, # NOP
+ 0x0C004FAB, # JAL 0x80013EAC
+ 0x2404019E, # ADDIU A0, R0, 0x019E
+ 0x0804F0BF # J 0x8013C2FC
+]
+
+map_text_redirector = [
+ # Checks for Map Texts 06 or 08 if in the Forest or Castle Wall Main maps respectively and redirects the text
+ # pointer to a blank string, skipping all the yes/no prompt text for pulling levers.
+ 0x0002FFFF, # Dummy text string
+ 0x3C0B8039, # LUI T3, 0x8039
+ 0x91689EE1, # LBU T0, 0x9EE1 (T3)
+ 0x1100000F, # BEQZ T0, [forward 0x0F]
+ 0x24090006, # ADDIU T1, R0, 0x0006
+ 0x240A0002, # ADDIU T2, R0, 0x0002
+ 0x110A000C, # BEQ T0, T2, [forward 0x0C]
+ 0x24090008, # ADDIU T1, R0, 0x0008
+ 0x240A0009, # ADDIU T2, R0, 0x0009
+ 0x110A0009, # BEQ T0, T2, [forward 0x09]
+ 0x24090004, # ADDIU T1, R0, 0x0004
+ 0x240A000A, # ADDIU T2, R0, 0x000A
+ 0x110A0006, # BEQ T0, T2, [forward 0x06]
+ 0x24090001, # ADDIU T1, R0, 0x0001
+ 0x240A000C, # ADDIU T2, R0, 0x000C
+ 0x110A0003, # BEQ T0, T2, [forward 0x03]
+ 0x2409000C, # ADDIU T1, R0, 0x000C
+ 0x10000008, # B 0x803FDB34
+ 0x00000000, # NOP
+ 0x15250006, # BNE T1, A1, [forward 0x06]
+ 0x00000000, # NOP
+ 0x3C04803F, # LUI A0, 0x803F
+ 0x3484DACC, # ORI A0, A0, 0xDACC
+ 0x24050000, # ADDIU A1, R0, 0x0000
+ 0x0804B39F, # J 0x8012CE7C
+ 0x00000000, # NOP
+ # Redirects to a custom message if you try placing the bomb ingredients at the bottom CC crack before deactivating
+ # the seal.
+ 0x24090009, # ADDIU T1, R0, 0x0009
+ 0x15090009, # BNE T0, T1, [forward 0x09]
+ 0x240A0002, # ADDIU T2, R0, 0x0002
+ 0x15450007, # BNE T2, A1, [forward 0x07]
+ 0x916A9C18, # LBU T2, 0x9C18 (T3)
+ 0x314A0001, # ANDI T2, T2, 0x0001
+ 0x15400004, # BNEZ T2, [forward 0x04]
+ 0x00000000, # NOP
+ 0x3C04803F, # LUI A0, 0x803F
+ 0x3484DBAC, # ORI A0, A0, 0xDBAC
+ 0x24050000, # ADDIU A1, R0, 0x0000
+ 0x0804B39F, # J 0x8012CE7C
+ 0x00000000, # NOP
+ # Checks for Map Texts 02 or 00 if in the Villa hallway or CC lizard lab maps respectively and redirects the text
+ # pointer to a blank string, skipping all the NPC dialogue mandatory for checks.
+ 0x3C088039, # LUI T0, 0x8039
+ 0x91089EE1, # LBU T0, 0x9EE1 (T0)
+ 0x240A0005, # ADDIU T2, R0, 0x0005
+ 0x110A0006, # BEQ T0, T2, [forward 0x06]
+ 0x24090002, # ADDIU T1, R0, 0x0002
+ 0x240A000C, # ADDIU T2, R0, 0x000C
+ 0x110A0003, # BEQ T0, T2, [forward 0x03]
+ 0x24090000, # ADDIU T1, R0, 0x0000
+ 0x0804B39F, # J 0x8012CE7C
+ 0x00000000, # NOP
+ 0x15250004, # BNE T1, A1, [forward 0x04]
+ 0x00000000, # NOP
+ 0x3C04803F, # LUI A0, 0x803F
+ 0x3484DACC, # ORI A0, A0, 0xDACC
+ 0x24050000, # ADDIU A1, R0, 0x0000
+ 0x0804B39F # J 0x8012CE7C
+]
+
+special_descriptions_redirector = [
+ # Redirects the menu description when looking at the Special1 and 2 items to different, custom strings that tell
+ # how many are needed per warp and to fight Dracula respectively, and how many there are of both in the whole seed.
+ 0x240A0003, # ADDIU T2, R0, 0x0003
+ 0x10AA0005, # BEQ A1, T2, [forward 0x05]
+ 0x240A0004, # ADDIU T2, R0, 0x0004
+ 0x10AA0003, # BEQ A1, T2, [forward 0x03]
+ 0x00000000, # NOP
+ 0x0804B39F, # J 0x8012CE7C
+ 0x00000000, # NOP
+ 0x3C04803F, # LUI A0, 0x803F
+ 0x3484E53C, # ORI A0, A0, 0xE53C
+ 0x24A5FFFD, # ADDIU A1, A1, 0xFFFD
+ 0x0804B39F # J 0x8012CE7C
+]
+
+forest_cw_villa_intro_cs_player = [
+ # Plays the Forest, Castle Wall, or Villa intro cutscene after transitioning to a different map if the map being
+ # transitioned to is the start of their levels respectively. Gets around the fact that they have to be set on the
+ # previous loading zone for them to play normally.
+ 0x3C088039, # LUI T0, 0x8039
+ 0x8D099EE0, # LW T1, 0x9EE0 (T0)
+ 0x1120000B, # BEQZ T1 T1, [forward 0x0B]
+ 0x240B0000, # ADDIU T3, R0, 0x0000
+ 0x3C0A0002, # LUI T2, 0x0002
+ 0x112A0008, # BEQ T1, T2, [forward 0x08]
+ 0x240B0007, # ADDIU T3, R0, 0x0007
+ 0x254A0007, # ADDIU T2, T2, 0x0007
+ 0x112A0005, # BEQ T1, T2, [forward 0x05]
+ 0x3C0A0003, # LUI T2, 0x0003
+ 0x112A0003, # BEQ T1, T2, [forward 0x03]
+ 0x240B0003, # ADDIU T3, R0, 0x0003
+ 0x08005FAA, # J 0x80017EA8
+ 0x00000000, # NOP
+ 0x010B6021, # ADDU T4, T0, T3
+ 0x918D9C08, # LBU T5, 0x9C08 (T4)
+ 0x31AF0001, # ANDI T7, T5, 0x0001
+ 0x15E00009, # BNEZ T7, [forward 0x09]
+ 0x240E0009, # ADDIU T6, R0, 0x0009
+ 0x3C180003, # LUI T8, 0x0003
+ 0x57090001, # BNEL T8, T1, [forward 0x01]
+ 0x240E0004, # ADDIU T6, R0, 0x0004
+ 0x15200003, # BNEZ T1, [forward 0x03]
+ 0x240F0001, # ADDIU T7, R0, 0x0001
+ 0xA18F9C08, # SB T7, 0x9C08 (T4)
+ 0x240E003C, # ADDIU T6, R0, 0x003C
+ 0xA10E9EFF, # SB T6, 0x9EFF (T0)
+ 0x08005FAA # J 0x80017EA8
+]
+
+map_id_refresher = [
+ # After transitioning to a different map, if this detects the map ID being transitioned to as FF, it will write back
+ # the past map ID so that the map will reset. Useful for thngs like getting around a bug wherein the camera fixes in
+ # place if you enter a loading zone that doesn't actually change the map, which can happen in a seed that gives you
+ # any character tower stage at the very start.
+ 0x240800FF, # ADDIU T0, R0, 0x00FF
+ 0x110E0003, # BEQ T0, T6, [forward 0x03]
+ 0x00000000, # NOP
+ 0x03E00008, # JR RA
+ 0xA44E61D8, # SH T6, 0x61D8
+ 0x904961D9, # LBU T1, 0x61D9
+ 0xA0496429, # SB T1, 0x6429
+ 0x03E00008 # JR RA
+]
+
+character_changer = [
+ # Changes the character being controlled if the player is holding L while loading into a map by swapping the
+ # character ID.
+ 0x3C08800D, # LUI T0, 0x800D
+ 0x910B5E21, # LBU T3, 0x5E21 (T0)
+ 0x31680020, # ANDI T0, T3, 0x0020
+ 0x3C0A8039, # LUI T2, 0x8039
+ 0x1100000B, # BEQZ T0, [forward 0x0B]
+ 0x91499C3D, # LBU T1, 0x9C3D (T2)
+ 0x11200005, # BEQZ T1, [forward 0x05]
+ 0x24080000, # ADDIU T0, R0, 0x0000
+ 0xA1489C3D, # SB T0, 0x9C3D (T2)
+ 0x25080001, # ADDIU T0, T0, 0x0001
+ 0xA1489BC2, # SB T0, 0x9BC2 (T2)
+ 0x10000004, # B [forward 0x04]
+ 0x24080001, # ADDIU T0, R0, 0x0001
+ 0xA1489C3D, # SB T0, 0x9C3D (T2)
+ 0x25080001, # ADDIU T0, T0, 0x0001
+ 0xA1489BC2, # SB T0, 0x9BC2 (T2)
+ # Changes the alternate costume variables if the player is holding C-up.
+ 0x31680008, # ANDI T0, T3, 0x0008
+ 0x11000009, # BEQZ T0, [forward 0x09]
+ 0x91499C24, # LBU T1, 0x9C24 (T2)
+ 0x312B0040, # ANDI T3, T1, 0x0040
+ 0x2528FFC0, # ADDIU T0, T1, 0xFFC0
+ 0x15600003, # BNEZ T3, [forward 0x03]
+ 0x240C0000, # ADDIU T4, R0, 0x0000
+ 0x25280040, # ADDIU T0, T1, 0x0040
+ 0x240C0001, # ADDIU T4, R0, 0x0001
+ 0xA1489C24, # SB T0, 0x9C24 (T2)
+ 0xA14C9CEE, # SB T4, 0x9CEE (T2)
+ 0x080062AA, # J 0x80018AA8
+ 0x00000000, # NOP
+ # Plays the attack sound of the character being changed into to indicate the change was successful.
+ 0x3C088039, # LUI T0, 0x8039
+ 0x91099BC2, # LBU T1, 0x9BC2 (T0)
+ 0xA1009BC2, # SB R0, 0x9BC2 (T0)
+ 0xA1009BC1, # SB R0, 0x9BC1 (T0)
+ 0x11200006, # BEQZ T1, [forward 0x06]
+ 0x2529FFFF, # ADDIU T1, T1, 0xFFFF
+ 0x240402F6, # ADDIU A0, R0, 0x02F6
+ 0x55200001, # BNEZL T1, [forward 0x01]
+ 0x240402F8, # ADDIU A0, R0, 0x02F8
+ 0x08004FAB, # J 0x80013EAC
+ 0x00000000, # NOP
+ 0x03E00008 # JR RA
+]
+
+panther_dash = [
+ # Changes various movement parameters when holding C-right so the player will move way faster.
+ # Increases movement speed and speeds up the running animation.
+ 0x3C08800D, # LUI T0, 0x800D
+ 0x91085E21, # LBU T0, 0x5E21 (T0)
+ 0x31080001, # ANDI T0, T0, 0x0001
+ 0x24093FEA, # ADDIU T1, R0, 0x3FEA
+ 0x11000004, # BEQZ T0, [forward 0x04]
+ 0x240B0010, # ADDIU T3, R0, 0x0010
+ 0x3C073F20, # LUI A3, 0x3F20
+ 0x240940AA, # ADDIU T1, R0, 0x40AA
+ 0x240B000A, # ADDIU T3, R0, 0x000A
+ 0x3C0C8035, # LUI T4, 0x8035
+ 0xA18B07AE, # SB T3, 0x07AE (T4)
+ 0xA18B07C2, # SB T3, 0x07C2 (T4)
+ 0x3C0A8034, # LUI T2, 0x8034
+ 0x03200008, # JR T9
+ 0xA5492BD8, # SH T1, 0x2BD8 (T2)
+ 0x00000000, # NOP
+ # Increases the turning speed so that handling is better.
+ 0x3C08800D, # LUI T0, 0x800D
+ 0x91085E21, # LBU T0, 0x5E21 (T0)
+ 0x31080001, # ANDI T0, T0, 0x0001
+ 0x11000002, # BEQZ T0, [forward 0x02]
+ 0x240A00D9, # ADDIU T2, R0, 0x00D9
+ 0x240A00F0, # ADDIU T2, R0, 0x00F0
+ 0x3C0B8039, # LUI T3, 0x8039
+ 0x916B9C3D, # LBU T3, 0x9C3D (T3)
+ 0x11600003, # BEQZ T3, [forward 0x03]
+ 0xD428DD58, # LDC1 F8, 0xDD58 (AT)
+ 0x03E00008, # JR RA
+ 0xA02ADD59, # SB T2, 0xDD59 (AT)
+ 0xD428D798, # LDC1 F8, 0xD798 (AT)
+ 0x03E00008, # JR RA
+ 0xA02AD799, # SB T2, 0xD799 (AT)
+ 0x00000000, # NOP
+ # Increases crouch-walking x and z speed.
+ 0x3C08800D, # LUI T0, 0x800D
+ 0x91085E21, # LBU T0, 0x5E21 (T0)
+ 0x31080001, # ANDI T0, T0, 0x0001
+ 0x11000002, # BEQZ T0, [forward 0x02]
+ 0x240A00C5, # ADDIU T2, R0, 0x00C5
+ 0x240A00F8, # ADDIU T2, R0, 0x00F8
+ 0x3C0B8039, # LUI T3, 0x8039
+ 0x916B9C3D, # LBU T3, 0x9C3D (T3)
+ 0x15600005, # BNEZ T3, [forward 0x05]
+ 0x00000000, # NOP
+ 0xA02AD801, # SB T2, 0xD801 (AT)
+ 0xA02AD809, # SB T2, 0xD809 (AT)
+ 0x03E00008, # JR RA
+ 0xD430D800, # LDC1 F16, 0xD800 (AT)
+ 0xA02ADDC1, # SB T2, 0xDDC1 (AT)
+ 0xA02ADDC9, # SB T2, 0xDDC9 (AT)
+ 0x03E00008, # JR RA
+ 0xD430DDC0 # LDC1 F16, 0xDDC0 (AT)
+]
+
+panther_jump_preventer = [
+ # Optional hack to prevent jumping while moving at the increased panther dash speed as a way to prevent logic
+ # sequence breaks that would otherwise be impossible without it. Such sequence breaks are never considered in logic
+ # either way.
+
+ # Decreases a "can running jump" value by 1 per frame unless it's at 0, or while in the sliding state. When the
+ # player lets go of C-right, their running speed should have returned to a normal amount by the time it hits 0.
+ 0x9208007F, # LBU T0, 0x007F (S0)
+ 0x24090008, # ADDIU T1, R0, 0x0008
+ 0x11090005, # BEQ T0, T1, [forward 0x05]
+ 0x3C088039, # LUI T0, 0x8039
+ 0x91099BC1, # LBU T1, 0x9BC1 (T0)
+ 0x11200002, # BEQZ T1, [forward 0x02]
+ 0x2529FFFF, # ADDIU T1, T1, 0xFFFF
+ 0xA1099BC1, # SB T1, 0x9BC1 (T0)
+ 0x080FF413, # J 0x803FD04C
+ 0x00000000, # NOP
+ # Increases the "can running jump" value by 2 per frame while panther dashing unless it's at 8 or higher, at which
+ # point the player should be at the max panther dash speed.
+ 0x00074402, # SRL T0, A3, 16
+ 0x29083F7F, # SLTI T0, T0, 0x3F7F
+ 0x11000006, # BEQZ T0, [forward 0x06]
+ 0x3C098039, # LUI T1, 0x8039
+ 0x912A9BC1, # LBU T2, 0x9BC1 (T1)
+ 0x254A0002, # ADDIU T2, T2, 0x0002
+ 0x294B0008, # SLTI T3, T2, 0x0008
+ 0x55600001, # BNEZL T3, [forward 0x01]
+ 0xA12A9BC1, # SB T2, 0x9BC1 (T1)
+ 0x03200008, # JR T9
+ 0x00000000, # NOP
+ # Makes running jumps only work while the "can running jump" value is at 0. Otherwise, their state won't change.
+ 0x3C010001, # LUI AT, 0x0001
+ 0x3C088039, # LUI T0, 0x8039
+ 0x91089BC1, # LBU T0, 0x9BC1 (T0)
+ 0x55000001, # BNEZL T0, [forward 0x01]
+ 0x3C010000, # LUI AT, 0x0000
+ 0x03E00008 # JR RA
+]
+
+gondola_skipper = [
+ # Upon stepping on one of the gondolas in Tunnel to activate it, this will instantly teleport you to the other end
+ # of the gondola course depending on which one activated, skipping the entire 3-minute wait to get there.
+ 0x3C088039, # LUI T0, 0x8039
+ 0x240900FF, # ADDIU T1, R0, 0x00FF
+ 0xA1099EE1, # SB T1, 0x9EE1 (T0)
+ 0x31EA0020, # ANDI T2, T7, 0x0020
+ 0x3C0C3080, # LUI T4, 0x3080
+ 0x358C9700, # ORI T4, T4, 0x9700
+ 0x154B0003, # BNE T2, T3, [forward 0x03]
+ 0x24090002, # ADDIU T1, R0, 0x0002
+ 0x24090003, # ADDIU T1, R0, 0x0003
+ 0x3C0C7A00, # LUI T4, 0x7A00
+ 0xA1099EE3, # SB T1, 0x9EE3 (T0)
+ 0xAD0C9EE4, # SW T4, 0x9EE4 (T0)
+ 0x3C0D0010, # LUI T5, 0x0010
+ 0x25AD0010, # ADDIU T5, T5, 0x0010
+ 0xAD0D9EE8, # SW T5, 0x9EE8 (T0)
+ 0x08063E68 # J 0x8018F9A0
+]
+
+mandragora_with_nitro_setter = [
+ # When setting a Nitro, if Mandragora is in the inventory too and the wall's "Mandragora set" flag is not set, this
+ # will automatically subtract a Mandragora from the inventory and set its flag so the wall can be blown up in just
+ # one interaction instead of two.
+ 0x3C088039, # LUI T0, 0x8039
+ 0x81099EE1, # LB T1, 0x9EE1 (T0)
+ 0x240A000C, # ADDIU T2, R0, 0x000C
+ 0x112A000E, # BEQ T1, T2, [forward 0x0E]
+ 0x81099C18, # LB T1, 0x9C18 (T0)
+ 0x31290002, # ANDI T1, T1, 0x0002
+ 0x11200009, # BEQZ T1, [forward 0x09]
+ 0x91099C5D, # LBU T1, 0x9C5D (T0)
+ 0x11200007, # BEQZ T1, [forward 0x07]
+ 0x910B9C1A, # LBU T3, 0x9C1A (T0)
+ 0x316A0001, # ANDI T2, T3, 0x0001
+ 0x15400004, # BNEZ T2, [forward 0x04]
+ 0x2529FFFF, # ADDIU T1, T1, 0xFFFF
+ 0xA1099C5D, # SB T1, 0x9C5D (T0)
+ 0x356B0001, # ORI T3, T3, 0x0001
+ 0xA10B9C1A, # SB T3, 0x9C1A (T0)
+ 0x08000512, # J 0x80001448
+ 0x00000000, # NOP
+ 0x810B9BF2, # LB T3, 0x9BF2 (T0)
+ 0x31690040, # ANDI T1, T3, 0x0040
+ 0x11200008, # BEQZ T1, [forward 0x08]
+ 0x91099C5D, # LBU T1, 0x9C5D (T0)
+ 0x11200006, # BEQZ T1, [forward 0x06]
+ 0x316A0080, # ANDI T2, T3, 0x0080
+ 0x15400004, # BNEZ T2, 0x803FE0E8
+ 0x2529FFFF, # ADDIU T1, T1, 0xFFFF
+ 0xA1099C5D, # SB T1, 0x9C5D (T0)
+ 0x356B0080, # ORI T3, T3, 0x0080
+ 0xA10B9BF2, # SB T3, 0x9BF2 (T0)
+ 0x08000512 # J 0x80001448
+]
+
+ambience_silencer = [
+ # Silences all map-specific ambience when loading into a different map, so we don't have to live with, say, Tower of
+ # Science/Clock Tower machinery noises everywhere until either resetting, dying, or going into a map that is
+ # normally set up to disable said noises.
+ 0x3C088039, # LUI T0, 0x8039
+ 0x91089EE1, # LBU T0, 0x9EE1 (T0)
+ 0x24090012, # ADDIU T1, R0, 0x0012
+ 0x11090003, # BEQ T0, T1, [forward 0x03]
+ 0x00000000, # NOP
+ 0x0C004FAB, # JAL 0x80013EAC
+ 0x3404818C, # ORI A0, R0, 0x818C
+ 0x0C004FAB, # JAL 0x80013EAC
+ 0x34048134, # ORI A0, R0, 0x8134
+ 0x0C004FAB, # JAL 0x80013EAC
+ 0x34048135, # ORI A0, R0, 0x8135
+ 0x0C004FAB, # JAL 0x80013EAC
+ 0x34048136, # ORI A0, R0, 0x8136
+ 0x08054987, # J 0x8015261C
+ 0x00000000, # NOP
+ # Plays the fan ambience when loading into the fan meeting room if this detects the active character's cutscene flag
+ # here already being set.
+ 0x3C088039, # LUI T0, 0x8039
+ 0x91099EE1, # LBU T1, 0x9EE1 (T0)
+ 0x240A0019, # ADDIU T2, R0, 0x0019
+ 0x152A000A, # BNE T1, T2, [forward 0x0A]
+ 0x910B9BFE, # LBU T3, 0x9BFE (T0)
+ 0x910C9C3D, # LBU T4, 0x9C3D (T0)
+ 0x240D0001, # ADDIU T5, R0, 0x0001
+ 0x55800001, # BNEZL T4, [forward 0x01]
+ 0x240D0002, # ADDIU T5, R0, 0x0002
+ 0x016D7024, # AND T6, T3, T5
+ 0x11C00003, # BEQZ T6, [forward 0x03]
+ 0x00000000, # NOP
+ 0x0C0052B4, # JAL 0x80014AD0
+ 0x34040169, # ORI A0, R0, 0x0169
+ 0x0805581C # J 0x80156070
+]
+
+coffin_cutscene_skipper = [
+ # Kills the normally-unskippable "Found a hidden path" cutscene at the end of Villa if this detects, in the current
+ # module in the modules array, the cutscene's module number of 0x205C and the "skip" value 0f 0x01 normally set by
+ # all cutscenes upon pressing Start.
+ 0x10A0000B, # BEQZ A1, [forward 0x0B]
+ 0x00000000, # NOP
+ 0x94A80000, # LHU T0, 0x0000 (A1)
+ 0x2409205C, # ADDIU T1, R0, 0x205C
+ 0x15090007, # BNE T0, T1, [forward 0x07]
+ 0x90AA0070, # LBU T2, 0x0070 (A1)
+ 0x11400005, # BEQZ T2, [forward 0x05]
+ 0x90AB0009, # LBU T3, 0x0009 (A1)
+ 0x240C0003, # ADDIU T4, R0, 0x0003
+ 0x156C0002, # BNE T3, T4, [forward 0x02]
+ 0x240B0004, # ADDIU T3, R0, 0x0004
+ 0xA0AB0009, # SB T3, 0x0009 (A1)
+ 0x03E00008 # JR RA
+]
+
+multiworld_item_name_loader = [
+ # When picking up an item from another world, this will load from ROM the custom message for that item explaining
+ # in the item textbox what the item is and who it's for. The flag index it calculates determines from what part of
+ # the ROM to load the item name from. If the item being picked up is a white jewel or a contract, it will always
+ # load from a part of the ROM that has nothing in it to ensure their set "flag" values don't yield unintended names.
+ 0x3C088040, # LUI T0, 0x8040
+ 0xAD03E238, # SW V1, 0xE238 (T0)
+ 0x92080039, # LBU T0, 0x0039 (S0)
+ 0x11000003, # BEQZ T0, [forward 0x03]
+ 0x24090012, # ADDIU T1, R0, 0x0012
+ 0x15090003, # BNE T0, T1, [forward 0x03]
+ 0x24080000, # ADDIU T0, R0, 0x0000
+ 0x10000010, # B [forward 0x10]
+ 0x24080000, # ADDIU T0, R0, 0x0000
+ 0x920C0055, # LBU T4, 0x0055 (S0)
+ 0x8E090058, # LW T1, 0x0058 (S0)
+ 0x1120000C, # BEQZ T1, [forward 0x0C]
+ 0x298A0011, # SLTI T2, T4, 0x0011
+ 0x51400001, # BEQZL T2, [forward 0x01]
+ 0x258CFFED, # ADDIU T4, T4, 0xFFED
+ 0x240A0000, # ADDIU T2, R0, 0x0000
+ 0x00094840, # SLL T1, T1, 1
+ 0x5520FFFE, # BNEZL T1, [backward 0x02]
+ 0x254A0001, # ADDIU T2, T2, 0x0001
+ 0x240B0020, # ADDIU T3, R0, 0x0020
+ 0x018B0019, # MULTU T4, T3
+ 0x00004812, # MFLO T1
+ 0x012A4021, # ADDU T0, T1, T2
+ 0x00084200, # SLL T0, T0, 8
+ 0x3C0400BB, # LUI A0, 0x00BB
+ 0x24847164, # ADDIU A0, A0, 0x7164
+ 0x00882020, # ADD A0, A0, T0
+ 0x3C058018, # LUI A1, 0x8018
+ 0x34A5BF98, # ORI A1, A1, 0xBF98
+ 0x0C005DFB, # JAL 0x800177EC
+ 0x24060100, # ADDIU A2, R0, 0x0100
+ 0x3C088040, # LUI T0, 0x8040
+ 0x8D03E238, # LW V1, 0xE238 (T0)
+ 0x3C1F8012, # LUI RA, 0x8012
+ 0x27FF5BA4, # ADDIU RA, RA, 0x5BA4
+ 0x0804EF54, # J 0x8013BD50
+ 0x94640002, # LHU A0, 0x0002 (V1)
+ # Changes the Y screen position of the textbox depending on how many line breaks there are.
+ 0x3C088019, # LUI T0, 0x8019
+ 0x9108C097, # LBU T0, 0xC097 (T0)
+ 0x11000005, # BEQZ T0, [forward 0x05]
+ 0x2508FFFF, # ADDIU T0, T0, 0xFFFF
+ 0x11000003, # BEQZ T0, [forward 0x03]
+ 0x00000000, # NOP
+ 0x1000FFFC, # B [backward 0x04]
+ 0x24C6FFF1, # ADDIU A2, A2, 0xFFF1
+ 0x0804B33F, # J 0x8012CCFC
+ # Changes the length and number of lines on the textbox if there's a multiworld message in the buffer.
+ 0x3C088019, # LUI T0, 0x8019
+ 0x9108C097, # LBU T0, 0xC097 (T0)
+ 0x11000003, # BEQZ T0, [forward 0x03]
+ 0x00000000, # NOP
+ 0x00082821, # ADDU A1, R0, T0
+ 0x240600B6, # ADDIU A2, R0, 0x00B6
+ 0x0804B345, # J 0x8012CD14
+ 0x00000000, # NOP
+ # Redirects the text to the multiworld message buffer if a message exists in it.
+ 0x3C088019, # LUI T0, 0x8019
+ 0x9108C097, # LBU T0, 0xC097 (T0)
+ 0x11000004, # BEQZ T0, [forward 0x04]
+ 0x00000000, # NOP
+ 0x3C048018, # LUI A0, 0x8018
+ 0x3484BF98, # ORI A0, A0, 0xBF98
+ 0x24050000, # ADDIU A1, R0, 0x0000
+ 0x0804B39F, # J 0x8012CE7C
+ # Copy the "item from player" text when being given an item through the multiworld via the game's copy function.
+ 0x00000000, # NOP
+ 0x00000000, # NOP
+ 0x00000000, # NOP
+ 0x3C088040, # LUI T0, 0x8040
+ 0xAD1FE33C, # SW RA, 0xE33C (T0)
+ 0xA104E338, # SB A0, 0xE338 (T0)
+ 0x3C048019, # LUI A0, 0x8019
+ 0x2484C0A8, # ADDIU A0, A0, 0xC0A8
+ 0x3C058019, # LUI A1, 0x8019
+ 0x24A5BF98, # ADDIU A1, A1, 0xBF98
+ 0x0C000234, # JAL 0x800008D0
+ 0x24060100, # ADDIU A2, R0, 0x0100
+ 0x3C088040, # LUI T0, 0x8040
+ 0x8D1FE33C, # LW RA, 0xE33C (T0)
+ 0x0804EDCE, # J 0x8013B738
+ 0x9104E338, # LBU A0, 0xE338 (T0)
+ 0x00000000, # NOP
+ # Neuters the multiworld item text buffer if giving a non-multiworld item through the in-game remote item rewarder
+ # byte before then jumping to item_prepareTextbox.
+ 0x24080011, # ADDIU T0, R0, 0x0011
+ 0x10880004, # BEQ A0, T0, [forward 0x04]
+ 0x24080012, # ADDIU T0, R0, 0x0012
+ 0x10880002, # BEQ A0, T0, [forward 0x02]
+ 0x3C088019, # LUI T0, 0x8019
+ 0xA100C097, # SB R0, 0xC097 (T0)
+ 0x0804EDCE # J 0x8013B738
+]
+
+ice_trap_initializer = [
+ # During a map load, creates the module that allows the ice block model to appear while in the frozen state if not
+ # on the intro narration map (map 0x16).
+ 0x3C088039, # LUI T0, 0x8039
+ 0x91089EE1, # LBU T0, 0x9EE1 (T0)
+ 0x24090016, # ADDIU T1, R0, 0x0016
+ 0x11090004, # BEQ T0, T1, [forward 0x04]
+ 0x3C048034, # LUI A0, 0x8034
+ 0x24842ACC, # ADDIU A0, A0, 0x2ACC
+ 0x08000660, # J 0x80001980
+ 0x24052125, # ADDIU A1, R0, 0x2125
+ 0x03E00008 # JR RA
+]
+
+the_deep_freezer = [
+ # Writes 000C0000 into the player state to freeze the player on the spot if Ice Traps have been received, writes the
+ # Ice Trap code into the pointer value (0x20B8, which is also Camilla's boss code),and decrements the Ice Traps
+ # remaining counter. All after verifying the player is in a "safe" state to be frozen in.
+ 0x3C0B8039, # LUI T3, 0x8039
+ 0x91699BE2, # LBU T3, 0x9BE2 (T0)
+ 0x11200015, # BEQZ T1, [forward 0x15]
+ 0x3C088034, # LUI T0, 0x8034
+ 0x910827A9, # LBU T0, 0x27A9 (T0)
+ 0x240A0005, # ADDIU T2, R0, 0x0005
+ 0x110A0011, # BEQ T0, T2, [forward 0x11]
+ 0x240A000C, # ADDIU T2, R0, 0x000C
+ 0x110A000F, # BEQ T0, T2, [forward 0x0F]
+ 0x240A0002, # ADDIU T2, R0, 0x0002
+ 0x110A000D, # BEQ T0, T2, [forward 0x0D]
+ 0x240A0008, # ADDIU T2, R0, 0x0008
+ 0x110A000B, # BEQ T0, T2, [forward 0x0B]
+ 0x2529FFFF, # ADDIU T1, T1, 0xFFFF
+ 0xA1699BE2, # SB T1, 0x9BE2 (T3)
+ 0x3C088034, # LUI T0, 0x8034
+ 0x3C09000C, # LUI T1, 0x000C
+ 0xAD0927A8, # SW T1, 0x27A8 (T0)
+ 0x240C20B8, # ADDIU T4, R0, 0x20B8
+ 0xA56C9E6E, # SH T4, 0x9E6E (T3)
+ 0x8D0927C8, # LW T1, 0x27C8 (T0)
+ 0x912A0048, # LBU T2, 0x0068 (T1)
+ 0x314A007F, # ANDI T2, T2, 0x007F
+ 0xA12A0048, # SB T2, 0x0068 (T1)
+ 0x03E00008 # JR RA
+]
+
+freeze_verifier = [
+ # Verifies for the ice chunk module that a freeze should spawn the ice model. The player must be in the frozen state
+ # (0x000C) and 0x20B8 must be in either the freeze pointer value or the current boss ID (Camilla's); otherwise, we
+ # weill assume that the freeze happened due to a vampire grab or Actrise shard tornado and not spawn the ice chunk.
+ 0x8C4E000C, # LW T6, 0x000C (V0)
+ 0x00803025, # OR A2, A0, R0
+ 0x8DC30008, # LW V1, 0x0008 (T6)
+ 0x3C088039, # LUI T0, 0x8039
+ 0x240920B8, # ADDIU T1, R0, 0x20B8
+ 0x950A9E72, # LHU T2, 0x9E72 (T0)
+ 0x3C0C8034, # LUI T4, 0x8034
+ 0x918C27A9, # LBU T4, 0x27A9 (T4)
+ 0x240D000C, # ADDIU T5, R0, 0x000C
+ 0x158D0004, # BNE T4, T5, [forward 0x04]
+ 0x3C0B0F00, # LUI T3, 0x0F00
+ 0x112A0005, # BEQ T1, T2, [forward 0x05]
+ 0x950A9E78, # LHU T2, 0x9E78 (T0)
+ 0x112A0003, # BEQ T1, T2, [forward 0x03]
+ 0x357996A0, # ORI T9, T3, 0x96A0
+ 0x03200008, # JR T9
+ 0x00000000, # NOP
+ 0x35799640, # ORI T9, T3, 0x9640
+ 0x03200008, # JR T9
+]
+
+countdown_extra_safety_check = [
+ # Checks to see if the multiworld message is a red flashing trap before then truly deciding to decrement the
+ # Countdown number. This was a VERY last minute thing I caught, since Ice Traps for other CV64 players can take the
+ # appearance of majors with no other way of the game knowing.
+ 0x3C0B8019, # LUI T3, 0x8019
+ 0x956BBF98, # LHU T3, 0xBF98 (T3)
+ 0x240C0000, # ADDIU T4, R0, 0x0000
+ 0x358CA20B, # ORI T4, T4, 0xA20B
+ 0x556C0001, # BNEL T3, T4, [forward 0x01]
+ 0xA1099CA4, # SB T1, 0x9CA4 (T0)
+ 0x03E00008 # JR RA
+]
+
+countdown_demo_hider = [
+ # Hides the Countdown number if we are not in the Gameplay state (state 2), which would happen if we were in the
+ # Demo state (state 9). This is to ensure the demo maps' number is not peep-able before starting a run proper, for
+ # the sake of preventing a marginal unfair advantage. Otherwise, updates the number once per frame.
+ 0x3C088039, # LUI T0, 0x8039
+ 0x91089EE1, # LBU T0, 0x9EE1 (T0)
+ 0x3C098040, # LUI T1, 0x8040
+ 0x01284821, # ADDU T1, T1, T0
+ 0x0C0FF507, # JAL 0x803FD41C
+ 0x9124D6DC, # LBU A0, 0xD6DC (T1)
+ 0x3C088034, # LUI T0, 0x8034
+ 0x91092087, # LBU T0, 0x2087 (T0)
+ 0x240A0002, # ADDIU T2, R0, 0x0002
+ 0x112A0003, # BEQ T1, T2, [forward 0x03]
+ 0x3C048040, # LUI A0, 0x8040
+ 0x8C84D6D4, # LW A0, 0xD6D4 (A0)
+ 0x0C0FF59F, # JAL 0x803FD67C
+ 0x24050000, # ADDIU A1, R0, 0x0000
+ 0x080FF411, # J 0x803FD044
+]
+
+item_drop_spin_corrector = [
+ # Corrects how far AP-placed items drop and how fast they spin based on what appearance they take.
+
+ # Pickup actor ID table for the item appearance IDs to reference.
+ 0x01020304,
+ 0x05060708,
+ 0x090A0B0C,
+ 0x100D0E0F,
+ 0x11121314,
+ 0x15161718,
+ 0x191D1E1F,
+ 0x20212223,
+ 0x24252627,
+ 0x28291A1B,
+ 0x1C000000,
+ 0x00000000,
+ # Makes AP-placed items in 1-hit breakables drop to their correct, dev-intended height depending on what appearance
+ # we gave it. Primarily intended for the Axe and the Cross to ensure they don't land half buried in the ground.
+ 0x000C4202, # SRL T0, T4, 8
+ 0x318C00FF, # ANDI T4, T4, 0x00FF
+ 0x11000003, # BEQZ T0, [forward 0x03]
+ 0x3C098040, # LUI T1, 0x8040
+ 0x01284821, # ADDU T1, T1, T0
+ 0x912CE7DB, # LBU T4, 0xE7D8
+ 0x03E00008, # JR RA
+ 0xAC600000, # SW R0, 0x0000 (V1)
+ 0x00000000, # NOP
+ # Makes items with changed appearances spin at their correct speed. Unless it's a local Ice Trap, wherein it will
+ # instead spin at the speed it isn't supposed to.
+ 0x920B0040, # LBU T3, 0x0040 (S0)
+ 0x1160000D, # BEQZ T3, [forward 0x0D]
+ 0x3C0C8040, # LUI T4, 0x8040
+ 0x016C6021, # ADDU T4, T3, T4
+ 0x918CE7DB, # LBU T4, 0xE7DB (T4)
+ 0x258CFFFF, # ADDIU T4, T4, 0xFFFF
+ 0x240D0011, # ADDIU T5, R0, 0x0011
+ 0x154D0006, # BNE T2, T5, [forward 0x06]
+ 0x29AE0006, # SLTI T6, T5, 0x0006
+ 0x240A0001, # ADDIU T2, R0, 0x0001
+ 0x55C00001, # BNEZL T6, [forward 0x01]
+ 0x240A0007, # ADDIU T2, R0, 0x0007
+ 0x10000002, # B [forward 0x02]
+ 0x00000000, # NOP
+ 0x258A0000, # ADDIU T2, T4, 0x0000
+ 0x08049648, # J 0x80125920
+ 0x3C028017, # LUI V0, 0x8017
+ 0x00000000,
+ 0x00000000,
+ 0x00000000,
+ 0x00000000,
+ # Makes AP-placed items in 3-hit breakables drop to their correct, dev-intended height depending on what appearance
+ # we gave it.
+ 0x00184202, # SRL T0, T8, 8
+ 0x331800FF, # ANDI T8, T8, 0x00FF
+ 0x11000003, # BEQZ T0, [forward 0x03]
+ 0x3C098040, # LUI T1, 0x8040
+ 0x01284821, # ADDU T1, T1, T0
+ 0x9138E7DB, # LBU T8, 0xE7D8
+ 0x03E00008, # JR RA
+ 0xAC60FFD8, # SW R0, 0xFFD8 (V1)
+ 0x00000000,
+ # Makes AP-placed items in the Villa chandelier drop to their correct, dev-intended height depending on what
+ # appearance we gave it. (why must this singular breakable be such a problem child with its own code? :/)
+ 0x000D4202, # SRL T0, T5, 8
+ 0x31AD00FF, # ANDI T5, T5, 0x00FF
+ 0x11000003, # BEQZ T0, [forward 0x03]
+ 0x3C098040, # LUI T1, 0x8040
+ 0x01284821, # ADDU T1, T1, T0
+ 0x912DE7DB, # LBU T5, 0xE7D8
+ 0x03E00008, # JR RA
+ 0xAC60FFD8, # SW R0, 0xFFD8 (V1)
+]
+
+big_tosser = [
+ # Makes every hit the player takes that does not immobilize them send them flying backwards with the power of
+ # Behemoth's charge.
+ 0x3C0A8038, # LUI T2, 0x8038
+ 0x914A7D7E, # LBU T2, 0x7D7E (T2)
+ 0x314A0020, # ANDI T2, T2, 0x0020
+ 0x1540000D, # BEQZ T2, [forward 0x0D]
+ 0x3C0A800E, # LUI T2, 0x800E
+ 0x954B8290, # LHU T3, 0x8290 (T2)
+ 0x356B2000, # ORI T3, T3, 0x2000
+ 0xA54B8290, # SH T3, 0x8290 (T2)
+ 0x3C0C8035, # LUI T4, 0x8035
+ 0x958C09DE, # LHU T4, 0x09DE (T4)
+ 0x258C8000, # ADDIU T4, T4, 0x8000
+ 0x3C0D8039, # LUI T5, 0x8039
+ 0xA5AC9CF0, # SH T4, 0x9CF0 (T5)
+ 0x3C0C4160, # LUI T4, 0x4160
+ 0xADAC9CF4, # SW T4, 0x9CF4 (T5)
+ 0x3C0C4040, # LUI T4, 0x4040
+ 0xADAC9CF8, # SW T4, 0x9CF8 (T5)
+ 0x03E00008, # JR RA
+ 0x8C680048, # LW T0, 0x0048 (V1)
+ 0x00000000,
+ 0x00000000,
+ # Allows pressing A while getting launched to cancel all XZ momentum. Useful for saving oneself from getting
+ # launched into an instant death trap.
+ 0x3C088038, # LUI T0, 0x8038
+ 0x91087D80, # LBU T0, 0x7D80 (T0)
+ 0x31090080, # ANDI T1, T0, 0x0080
+ 0x11200009, # BEQZ T1, [forward 0x09]
+ 0x3C088035, # LUI T0, 0x8035
+ 0x8D0A079C, # LW T2, 0x079C (T0)
+ 0x3C0B000C, # LUI T3, 0x000C
+ 0x256B4000, # ADDIU T3, T3, 0x4000
+ 0x014B5024, # AND T2, T2, T3
+ 0x154B0003, # BNE T2, T3, [forward 0x03]
+ 0x00000000, # NOP
+ 0xAD00080C, # SW R0, 0x080C (T0)
+ 0xAD000814, # SW R0, 0x0814 (T0)
+ 0x03200008 # JR T9
+]
diff --git a/worlds/cv64/data/rname.py b/worlds/cv64/data/rname.py
new file mode 100644
index 00000000000..851ee618af0
--- /dev/null
+++ b/worlds/cv64/data/rname.py
@@ -0,0 +1,63 @@
+forest_of_silence = "Forest of Silence"
+forest_start = "Forest of Silence: first half"
+forest_mid = "Forest of Silence: second half"
+forest_end = "Forest of Silence: end area"
+
+castle_wall = "Castle Wall"
+cw_start = "Castle Wall: main area"
+cw_exit = "Castle Wall: exit room"
+cw_ltower = "Castle Wall: left tower"
+
+villa = "Villa"
+villa_start = "Villa: dog gates"
+villa_main = "Villa: main interior"
+villa_storeroom = "Villa: storeroom"
+villa_archives = "Villa: archives"
+villa_maze = "Villa: maze"
+villa_servants = "Villa: servants entrance"
+villa_crypt = "Villa: crypt"
+
+tunnel = "Tunnel"
+tunnel_start = "Tunnel: first half"
+tunnel_end = "Tunnel: second half"
+
+underground_waterway = "Underground Waterway"
+uw_main = "Underground Waterway: main area"
+uw_end = "Underground Waterway: end"
+
+castle_center = "Castle Center"
+cc_main = "Castle Center: main area"
+cc_crystal = "Castle Center: big crystal"
+cc_torture_chamber = "Castle Center: torture chamber"
+cc_library = "Castle Center: library"
+cc_elev_top = "Castle Center: elevator top"
+
+duel_tower = "Duel Tower"
+dt_main = "Duel Tower"
+
+tower_of_sorcery = "Tower of Sorcery"
+tosor_main = "Tower of Sorcery"
+
+tower_of_execution = "Tower of Execution"
+toe_main = "Tower of Execution: main area"
+toe_ledge = "Tower of Execution: gated ledge"
+
+tower_of_science = "Tower of Science"
+tosci_start = "Tower of Science: turret lab"
+tosci_three_doors = "Tower of Science: locked key1 room"
+tosci_conveyors = "Tower of Science: spiky conveyors"
+tosci_key3 = "Tower of Science: locked key3 room"
+
+room_of_clocks = "Room of Clocks"
+roc_main = "Room of Clocks"
+
+clock_tower = "Clock Tower"
+ct_start = "Clock Tower: start"
+ct_middle = "Clock Tower: middle"
+ct_end = "Clock Tower: end"
+
+castle_keep = "Castle Keep"
+ck_main = "Castle Keep: exterior"
+ck_drac_chamber = "Castle Keep: Dracula's chamber"
+
+renon = "Renon's shop"
diff --git a/worlds/cv64/docs/en_Castlevania 64.md b/worlds/cv64/docs/en_Castlevania 64.md
new file mode 100644
index 00000000000..5fe85555c40
--- /dev/null
+++ b/worlds/cv64/docs/en_Castlevania 64.md
@@ -0,0 +1,148 @@
+# Castlevania 64
+
+## Where is the settings page?
+
+The [player settings page for this game](../player-settings) contains all the options you need to configure and export a
+config file.
+
+## What does randomization do to this game?
+
+All items that you would normally pick up throughout the game, be it from candles, breakables, or sitting out, have been
+moved around. This includes the key items that the player would normally need to find to progress in some stages, which can
+now be found outside their own stages, so returning to previously-visited stages will very likely be necessary (see: [How do
+I jump to a different stage?](#how-do-i-jump-to-a-different-stage?)). The positions of the stages can optionally be randomized
+too, so you may start out in Duel Tower and get Forest of Silence as your penultimate stage before Castle Keep, amongst
+many other possibilities.
+
+## How do I jump to a different stage?
+
+Instant travel to an earlier or later stage is made possible through the Warp Menu, a major addition to the game that can
+be pulled up while not in a boss fight by pressing START while holding Z and R. By finding Special1 jewels (the item that
+unlocks Hard Mode in vanilla Castlevania 64), more destinations become available to be selected on this menu. The destinations
+on the list are randomized per seed and the first one, which requires no Special1s to select, will always be your starting
+area.
+
+NOTE: Regardless of which option on the menu you are currently highlighting, you can hold Z or R while making your selection
+to return to Villa's crypt or Castle Center's top elevator room respectively, provided you've already been to that place at
+least once. This can make checking out both character stages at the start of a route divergence far less of a hassle.
+
+## Can I do everything as one character?
+
+Yes! The Villa end-of-level coffin has had its behavior modified so that which character stage slot it sends you to
+depends on the time of day, and likewise both bridges at the top of Castle Center's elevator are intact so both exits are
+reachable regardless of who you are. With these changes in game behavior, every stage can be accessed by any character
+in a singular run.
+
+NOTE: By holding L while loading into a map (this can even be done while cancelling out of the Warp Menu), you can swap to
+the other character you are not playing as, and/or hold C-Up to swap to and from the characters' alternate costumes. Unless
+you have Carrie Logic enabled, and you are not playing as her, switching should never be necessary.
+
+## What is the goal of Castlevania 64 when randomized?
+
+Make it to Castle Keep, enter Dracula's chamber, and defeat him to trigger an ending and complete your goal. Whether you
+get your character's good or bad ending does **not** matter; the goal will send regardless. Options exist to force a specific
+ending for those who prefer a specific one.
+
+Dracula's chamber's entrance door is initially locked until whichever of the following objectives that was specified on your
+YAML under `draculas_condition` is completed:
+- `crystal`: Activate the big crystal in the basement of Castle Center. Doing this entails finding two Magical Nitros and
+two Mandragoras to blow up both cracked walls (see: [How does the Nitro transport work in this?](#how-does-the-nitro-transport-work-in-this?)).
+Behemoth and Rosa/Camilla do **NOT** have to be defeated.
+- `bosses`: Kill bosses with visible health meters to earn Trophies. The number of Trophies required can be specified under
+`bosses_required`.
+- `special2s`: Find enough Special2 jewels (the item that normally unlocks alternate costumes) that are shuffled in the
+regular item pool. The total amount and percent needed can be specified under `total_special2s` and `percent_special2s_required` respectively.
+
+If `none` was specified, then there is no objective. Dracula's chamber door is unlocked from the start, and you merely have to reach it.
+
+## What items and locations get shuffled?
+
+Inventory items, jewels, moneybags, and PowerUps are all placed in the item pool by default. Randomizing Sub-weapons is optional,
+and they can be shuffled in their own separate pool or in the main item pool. An optional hack can be enabled to make your
+old sub-weapon drop behind you when you receive a different one, so you can pick it up again if you still want it. Location
+checks by default include freestanding items, items from one-hit breakables, and the very few items given through NPC text. Additional
+locations that can be toggled are:
+- Objects that break in three hits.
+- Sub-weapon locations if they have been shuffled anywhere.
+- Seven items sold by the shopkeeper Renon.
+- The two items beyond the crawlspace in Waterway that normally require Carrie, if Carrie Logic is on.
+- The six items inside the Lizard-man generators in Castle Center that open randomly to spawn Lizard-men. These are particularly annoying!
+
+## How does the Nitro transport work in this?
+
+Two Magical Nitros and two Mandragoras are placed into the item pool for blowing up the cracked walls in Castle Center
+and two randomized items are placed on both of their shelves. The randomized Magical Nitro will **NOT** kill you upon landing
+or taking damage, so don't panic when you receive one! Hazardous Waste Dispoal bins are disabled and the basement crack with
+a seal will not let you set anything at it until said seal is removed so none of the limited ingredients can be wasted.
+
+In short, Nitro is still in, explode-y business is not! Unless you turn on explosive DeathLink, that is...
+
+## 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. The exception is if sub-weapons
+are shuffled in their own pool, in which case they will only appear in your world in sub-weapon spots.
+
+## What does another world's item look like in Castlevania 64?
+
+An item belonging to another world will show up as that item if it's from another Castlevania 64 world, or one of two
+Archipelago logo icons if it's from a different game entirely. If the icon is big and has an orange arrow in the top-right
+corner, it is a progression item for that world; definitely get these! Otherwise, if it's small and with no arrow, it is
+either filler, useful, or a trap.
+
+When you pick up someone else's item, you will not receive anything and the item textbox will show up to announce what you
+found and who it was for. The color of the text will tell you its classification:
+- Light brown-ish: Common
+- White/Yellow: Useful
+- Yellow/Green: Progression
+- Yellow/Red: Trap
+
+## When the player receives an item, what happens?
+
+A textbox containing the name of the item and the player who sent it will appear, and they will get it.
+Just like the textbox that appears when sending an item, the color of the text will tell you its classification.
+
+NOTE: You can press B to close the item textbox instantly and get through your item queue quicker.
+
+## What tricks and glitches should I know for Hard Logic?
+
+The following tricks always have a chance to be required:
+- Left Tower Skip in Castle Wall
+- Copper Door Skip in Villa (both characters have their own methods for this)
+- Waterfall Skip if you travel backwards into Underground Waterway
+- Slope Jump to Room of Clocks from Castle Keep
+- Jump to the gated ledge from the level above in Tower of Execution
+
+Enabling Carrie Logic will also expect the following:
+
+- Orb-sniping dogs through the front gates in Villa
+
+Library Skip is **NOT** logically expected on any setting. The basement hallway crack will always logically expect two Nitros
+and two Mandragoras even with Hard Logic on due to the possibility of wasting a pair on the upper wall, after managing
+to skip past it. And plus, the RNG manip may not even be possible after picking up all the items in the Nitro room.
+
+## What are the item name groups?
+The groups you can use for Castlevania 64 are `bomb` and `ingredient`, both of which will hint randomly for either a
+Magical Nitro or Mandragora.
+
+## What are the location name groups?
+In Castlevania 64, every location that is specific to a stage is part of a location group under that stage's name.
+So if you want to exclude all of, say, Duel Tower, you can do so by just excluding "Duel Tower" as a whole.
+
+## I'm stuck and/or I can't find this hinted location...is there a map tracker?
+At the moment, no map tracker exists. [Here](https://github.com/ArchipelagoMW/Archipelago/tree/main/worlds/cv64/docs/obscure_checks.md)
+is a list of many checks that someone could very likely miss, with instructions on how to find them. See if the check you
+are missing is on there and if it isn't, or you still can't find it, reach out in the [Archipelago Discord server](https://discord.gg/archipelago)
+to inquire about having the list updated if you think it should be.
+
+If you are new to this randomizer, it is strongly recommended to play with the Countdown option enabled to at least give you a general
+idea of where you should be looking if you get completely stuck. It can track the total number of unchecked locations in the
+area you are currently in, or the total remaining majors.
+
+## Why does the game stop working when I sit on the title screen for too long?
+This is an issue that existed with Castlevania 64 on mupen64plus way back in 2017, and BizHawk never updated their
+mupen64plus core since it was fixed way back then. This is a Castlevania 64 in general problem that happens even with the
+vanilla ROM, so there's not much that can done about it besides opening an issue to them (which [has been done](https://github.com/TASEmulators/BizHawk/issues/3670))
+and hoping they update their mupen64plus core one day...
+
+## How the f*** do I set Nitro/Mandragora?
+(>)
diff --git a/worlds/cv64/docs/obscure_checks.md b/worlds/cv64/docs/obscure_checks.md
new file mode 100644
index 00000000000..4aafc2db1c5
--- /dev/null
+++ b/worlds/cv64/docs/obscure_checks.md
@@ -0,0 +1,429 @@
+# Obscure locations in the AP Castlevania 64 randomizer
+
+
+
+## Forest of Silence
+
+#### Invisible bridge platform
+A square platform floating off to the side of the broken bridge between the three-crypt and Werewolf areas. There's an
+invisible bridge connecting it with the middle piece on the broken bridge that you can cross over to reach it. This is
+where you normally get the Special1 in vanilla that unlocks Hard Mode.
+
+### Invisible Items
+#### Dirge maiden pedestal plaque
+This plaque in question can be found on the statue pedestal with a torch on it in the area right after the first switch gate,
+near the cliff where the breakable rock can be found. The plaque reads "A maiden sings a dirge" if you check it after you
+pick up the item there, hence the name of all the locations in this area.
+
+#### Werewolf statue plaque
+The plaque on the statue pedestal in the area inhabited by the Werewolf. Reading this plaque after picking up the item
+says it's "the lady who blesses and restores."
+
+### 3-Hit Breakables
+#### Dirge maiden pedestal rock
+This rock can be found near the cliff behind the empty above-mentioned dirge maiden pedestal. Normally has a ton of money
+in vanilla, contains 5 checks in rando.
+
+#### Bat archway rock
+After the broken bridge containing the invisible pathway to the Special1 in vanilla, this rock is off to the side in front
+of the gate frame with a swarm of bats that come at you, before the Werewolf's territory. Contains 4 checks. If you are new
+to speedrunning the vanilla game and haven't yet learned the RNG manip strats, this is a guranteed spot to find a PowerUp at.
+
+
+
+## Castle Wall
+#### Above bottom right/left tower door
+These checks are located on top of the bottom doorways inside the both the Left and Right Tower. You have to drop from above
+to reach them. In the case of the left tower, it's probably easiest to wait on the green platform directly above it until it flips.
+
+#### Left tower child ledge
+When you reach the bridge of four rotating green platforms, look towards the pillar in the center of the room (hold C-up to
+enter first person view), and you'll see this. There's an invisible bridge between the rotating platforms and the tiny ledge
+that you can use to get to and from it. In Legacy of Darkness, it is on this ledge where one of Henry's children is found.
+
+### Invisible Items
+#### Sandbag shelf - Left
+If you thought the PowerUp on this shelf in vanilla CV64 was the only thing here, then you'd be wrong! Hidden inside the
+sandbags near the item is another item you can pick up before subsequent checks on this spot yield "only sand and gravel".
+Legacy took this item out entirely, interestingly enough.
+
+### 3-Hit Breakables
+#### Upper rampart savepoint slab
+After killing the two White Dragons and flipping their switch, drop down onto this platform from the top, and you'll find
+it near the White Jewel. Contains 5 checks that are all normally Red Jewels in vanilla, making it an excellent place to
+fuel up at if you're not doing Left Tower Skip. Just be careful of the infinitely spawning skeletons!
+
+#### Dracula switch slab
+Located behind the door that you come out of at the top of the left tower where you encounter Totally Real Dracula in a
+cutscene. Contains 5 checks that are all normally money; take note of all these money spots if you're plaing vanilla and
+plan on trying to trigger the Renon fight.
+
+
+
+## Villa
+#### Outer front gate platform
+From the start of the level, turn right, and you'll see a platform with a torch above a torch on the ground. This upper torch
+is reachable via an invisible platform that you can grab and pull yourself up onto. The PAL version and onwards removed
+this secret entirely, interestingly enough.
+
+#### Front yard cross grave near gates/porch
+In the Villa's front yard area are two cross-shaped grave markers that are actually 1-hit breakables just like torches.
+They contain a check each.
+
+#### Midnight fountain
+At exactly midnight (0:00 on the pause screen), a pillar in the fountain's base will rise to give you access to the six
+checks on the upper parts of the fountain. If you're playing with Disable Time Requirements enabled, this pillar will be
+raised regardless of the current time.
+
+#### Vincent
+Vincent has a check that he will give to you by speaking to him after triggering the Rosa cutscene at 3 AM in the rose
+garden. With Disable Time Requirements enabled, the Rosa cutscene will trigger at any time.
+
+#### Living room ceiling light
+In the rectangular living room with ghosts and flying skulls that come at you, there are two yellow lights on the ceiling
+and one red light between them. The red light can be broken for a check; just jump directly below it and use your c-left
+attack to hit it.
+
+#### Front maze garden - Frankie's right dead-end urn
+When you first enter the maze, before going to trigger the Malus cutscene, go forward, right at the one-way door, then right
+at the T-junction, and you'll reach a dead-end where the Gardner is just going about his business. The urn on the left
+at this dead-end can be broken for a check; it's the ONLY urn in the entire maze that can be broken like this.
+
+#### Crypt bridge upstream
+After unlocking the Copper Door, follow the stream all the way past the bridge to end up at this torch.
+I see many people miss this one.
+
+### Invisible Items
+#### Front yard visitor's tombstone
+The tombstone closest to the Villa building itself, in the top-right corner if approaching from the gates. If you are
+familiar with the puzzle here in Cornell's quest in Legacy, it's the tombstone prepared for "anybody else who drops by
+to visit".
+
+#### Foyer sofa
+The first sofa in the foyer, on the upper floor to the right.
+
+#### Mary's room table
+The table closer to the mirror on the right in the small room adjacent to the bedroom, where Mary would normally be found
+in Cornell's story in Legacy.
+
+#### Dining room rose vase
+The vase of roses in the dining room that a rose falls out of in the cutscene here to warn Reinhardt/Carrie of the vampire
+villager.
+
+#### Living room clawed painting
+The painting with claw marks on it above the fireplace in the middle of the living room.
+
+#### Living room lion head
+The lion head on the left wall of the living room (if you entered from one of the doors to the main hallway).
+
+#### Maze garden exit knight
+The suit of armor in the stairs room before the Maze Garden, where Renon normally introduces himself.
+
+#### Storeroom statue
+The weird statue in the back of the Storeroom. If you check it again after taking its item, the game questions why would
+someone make something like it.
+
+#### Archives table
+The table in the middle of the Archives. In Legacy, this is where Oldrey's diary normally sits if you are playing Cornell.
+
+#### Malus's hiding bush
+The bush that Reinhardt/Carrie find Malus hiding in at the start of the Maze Garden chase sequence.
+
+### 3-Hit Breakables
+#### Foyer chandelier
+The big chandelier above the foyer can be broken for 5 assorted items, all of which become checks in rando with the multi
+hits setting on. This is the only 3-hit breakable in the entire stage.
+
+Here's a fun fact about the chandelier: for some reason, KCEK made this thing a completely separate object from every other
+3-hit breakable in the game, complete with its own distinct code that I had to modify entirely separately as I was making
+this rando to make the 3-hit breakable setting feasible! What fun!
+
+
+
+## Tunnel
+#### Stepping stone alcove
+After the first save following the initial Spider Women encounter, take the first right you see, and you'll arrive back at
+the poison river where there's a second set of stepping stones similar to the one you just jumped across earlier. Jump on
+these to find the secret alcove containing one or two checks depending on whether sub-weapons are randomized anywhere or not.
+
+### Sun/Moon Doors
+
+In total, there are six of these throughout the entire stage. One of them you are required to open in order to leave the stage,
+while the other five lead to optional side rooms containing items. These are all very skippable in the vanilla game, but in a
+rando context, it is obviously *very* important you learn where all of them are for when the day comes that a Hookshot
+lands behind one! If it helps, two of them are before the gondolas, while all the rest are after them.
+
+#### Lonesome bucket moon door
+After you ride up on the second elevator, turn left at the first split path you see, and you will find, as I called it, the
+"Lonesome bucket". Keep moving forward past this, and you will arrive at the moon door. The only thing of value, beside a shop
+point, is a sub-weapon location. So if you don't have sub-weapons shuffled anywhere, you can very much skip this one.
+
+#### Gondola rock crusher sun door
+Once you get past the first poison pit that you are literally required to platform over, go forward at the next junction
+instead of left (going left is progress and will take you to the rock crusher right before the gondolas). This door notably
+hides two Roast Beefs normally, making it probably the most worthwhile one to visit in vanilla.
+
+#### Corpse bucket moon door
+After the poison pit immediately following the gondola ride, you will arrive at a bucket surrounded by corpses (hence the name).
+Go left here, and you will arrive at this door.
+
+#### Shovel zone moon door
+On the straight path to the end-of-level sun door are two separate poison pits on the right that you can platform over.
+Both of these lead to and from the same optional area, the "shovel zone" as I call it due to the random shovel you can find
+here. Follow the path near the shovel that leads away from both poison pits, and you'll arrive at a junction with a save jewel.
+Go straight on at this junction to arrive at this moon door. This particular one is more notable in Legacy of Darkness as it
+contains one of the locations of Henry's children.
+
+#### Shovel zone sun door
+Same as the above moon door, but go left at the save jewel junction instead of straight.
+
+### Invisible Items
+#### Twin arrow signs
+From the save point after the stepping stones following the initial Spider Women encounter, travel forward until you reach a
+T-junction with two arrow signs at it. The right-pointing sign here contains an item on its post.
+
+#### Near lonesome bucket
+After riding the first upwards elevator following turning left at the twin arrow signs, you'll arrive at the lonesome bucket
+area, with said bucket being found if you turn left at the first opportunity after said elevator. The item here is not
+found *in* the bucket, but rather on a completely unmarked spot some meters from it. This had to have been a mistake,
+seeing as Legacy moved it to actually be in the bucket.
+
+#### Shovel
+Can be found by taking either platforming course on the right side of the straightaway to the end after the gondolas.
+This entire zone is noteable for the fact that there's no reason for Reinhardt to come here in either game; it's only ever
+required for Henry to rescue one of his children.
+
+### 3-Hit Breakables
+#### Twin arrow signs rock
+Turn right at the twin arrow signs junction, and you'll find this rock at the dead-end by the river. It contains a bunch of
+healing and status items that translate into 5 rando checks.
+
+#### Lonesome bucket poison pit rock
+Near the lonesome bucket is the start of a platforming course over poison water that connects near Albert's campsite...which
+you could reach anyway just by traveling forward at the prior junction instead of left. So what's the point of this poison
+pit, then? Look out into the middle of it, and you'll see this rock on a tiny island out in the middle of it. If you choose
+to take the hard way here, your reward will be three meat checks.
+
+
+
+## Underground Waterway
+#### Carrie Crawlspace
+This is located shortly after the corridor following the ledges that let you reach the first waterfall's source alcove.
+Notably, only Carrie is able to crouch and go through this, making these the only checks in the *entire* game that are
+hard impossible without being a specific character. So if you have Carrie Logic on and your character is Reinhardt, you'll
+have to hold L while loading into a map to change to Carrie just for this one secret. If Carrie Logic is off, then these
+locations will not be added and you can just skip them entirely.
+
+### 3-Hit Breakables
+#### First poison parkour ledge
+Near the start of the level is a series of ledges you can climb onto and platform across to reach a corner with a lantern
+that you can normally get a Cure Ampoule from. The first of these ledges can be broken for an assortment of 6 things.
+
+#### Inside skeleton crusher ledge
+To the left of the hallway entrance leading to the third switch is a long shimmy-able ledge that you can grab onto and shimmy
+for a whole eternity (I implemented a setting JUST to make shimmying this ledge faster!) to get to a couple stand on-able ledges,
+one of which has a lantern above it containing a check. This ledge can be broken for 3 chickens. I'd highly suggest bringing
+Holy Water for this because otherwise you're forced to break it from the other, lower ledge that's here. And this ledge
+will drop endless crawling skeletons on you as long as you're on it.
+
+
+
+## Castle Center
+#### Atop elevator room machine
+In the elevator room, right from the entrance coming in from the vampire triplets' room, is a machine that you can press
+C-Right on to get dialog reading "An enormous machine." There's a torch on top of this machine that you can reach by
+climbing onto the slanted part of the walls in the room.
+
+#### Heinrich Meyer
+The friendly lizard-man who normally gives you the Chamber Key in vanilla has a check for you just like Vincent.
+Yes, he has a name! And you'd best not forget it!
+
+#### Torture chamber rafters
+A check can be found in the rafters in the room with the Mandragora shelf. Get onto and jump off the giant scythe or the
+torture instrument shelf to make it up there. It's less annoying to do without Mandragora since having it will cause ghosts to
+infinitely spawn in here.
+
+### Invisible Items
+#### Red carpet hall knight
+The suit of armor in the red carpet hallway after the bottom elevator room, directly next to the door leading into the
+Lizard Locker Room.
+
+#### Lizard locker knight
+The suit of armor in the Lizard Locker Room itself, directly across from the door connecting to the red carpet hallway.
+
+#### Broken staircase knight
+The suit of armor in the broken staircase room following the Lizard Locker Room.
+
+#### Inside cracked wall hallway flamethrower
+In the upper cracked wall hallway, it is in the lower flamethrower that is part of the pair between the Butler Bros. Room
+and the main part of the hallway.
+
+#### Nitro room crates
+The wall of crates in the Nitro room on Heinrich Meyer's side. This is notable for being one of the very rare Healing Kits
+that you can get for free in vanilla.
+
+#### Hell Knight landing corner knight
+The inactive suit of armor in the corner of the room before the Maid Sisters' Room, which also contains an active knight.
+
+#### Maid sisters room vase
+The lone vase in the vampire Maid Sisters' Room, directly across from the door leading to the Hell Knight Landing.
+Yes, you are actually supposed to *check* this with C-right to get its item; not break it like you did to the pots in
+the Villa earlier!
+
+#### Invention room giant Famicart
+The giant square-shaped thing in one corner of the invention room that looks vaguely like a massive video game cartridge.
+A Famicom cartridge, perhaps?
+
+#### Invention room round machine
+The brown circular machine in the invention room, close to the middle of the wall on the side of the Spike Crusher Room.
+
+#### Inside nitro hallway flamethrower
+The lower flamethrower in the hallway between the Nitro room from the Spike Crusher Room, near the two doors to said rooms.
+
+#### Torture chamber instrument rack
+The shelf full of torture instruments in the torture chamber, to the right of the Mandragora shelf.
+
+### 3-Hit Breakables
+#### Behemoth arena crate
+This large crate can be found in the back-right corner of Behemoth's arena and is pretty hard to miss. Break it to get 5
+moneybags-turned-checks.
+
+#### Elevator room unoccupied statue stand
+In the bottom elevator room is a statue on a stand that will cry literal Bloody Tears if you get near it. On the opposite
+side of the room from this, near the enormous machine, is a stand much like the one the aforementioned statue is on only
+this one is completely vacant. This stand can be broken for 3 roast beefs-turned checks.
+
+#### Lizard locker room slab
+In the Lizard Locker Room, on top of the second locker from the side of the room with the door to the red carpet hallway,
+is a metallic box-like slab thingy that can be broken normally for 4 status items. This 3HB is notable for being one of two
+funny ones in the game that does NOT set a flag when you break it in vanilla, meaning you can keep breaking it over and
+over again for infinite Purifyings and Cure Ampoules!
+
+### The Lizard Lockers
+If you turned on the Lizard Locker Items setting, then hoo boy, you are in for a FUN =) time! Inside each one of the six Lizard
+Lockers is a check, and the way you get these checks is by camping near each of the lockers as you defeat said Lizards,
+praying that one will emerge from it and cause it to open to give you a chance to grab it. It is *completely* luck-based,
+you have a 1-in-6 (around 16%) chance per Lizard-man that emerges, and you have to repeat this process six times for each
+check inside each locker. You can open and cancel the warp menu to make things easier, but other than that, enjoy playing
+with the Lizards!
+
+
+
+## Duel Tower
+#### Invisible bridge balcony
+Located between Werewolf and Were-bull's arenas. Use an invisible bridge to reach it; it starts at the highest platform
+on the same wall as it that appears after defeating Werewolf. The balcony contains two checks and a save point that I
+added specifically for this rando to make the level less frustrating.
+
+#### Above Were-bull arena
+The only check that can be permanently missed in the vanilla game depending on the order you do things, not counting any
+points of no return. Between Werewolf and Were-bull's arenas is an alternate path that you can take downward and around
+to avoid Were-bull completely and get on top of his still-raised arena, so you can reach this. In the rando, I set it up so
+that his arena will go back to being raised if you leave the area and come back, and if you get the item later his arena flag
+will be set then. If you're playing with Dracula's bosses condition, then you can only get one Special2 off of him the first
+time you beat him and then none more after that.
+
+
+
+## Tower of Execution
+#### Invisible bridge ledge
+There are two ways to reach this one; use the invisible bridge that starts at the walkway above the entrance, or jump to
+it from the Execution Key gate alcove. Collecting this Special2 in vanilla unlocks Reinhardt's alternate costume.
+
+#### Guillotine tower top level
+This iron maiden is strategically placed in such a way that you will very likely miss it if you aren't looking carefully for
+it, so I am including it here. When you make it to the top floor of the level, as you approach the tower for the final time,
+look on the opposite side of it from where you approach it, and you will find this. The laggiest check in the whole game!
+I'd dare someone to find some check in some other Archipelago game that lags harder than this.
+
+### 3-Hit Breakables
+#### Pre-mid-savepoint platforms ledge
+Here's a really weird one that even I never found about until well after I finished the 3HB setting and moved on to deving
+other things, and I'd honestly be shocked if ANYONE knew about outside the context of this rando! This square-shaped
+platform can be found right before the second set of expanding and retracting wall platforms, leading up to the mid-save
+point, after going towards the central tower structure for the second time on the second floor. Breaking it will drop an
+assortment of 5 items, one of which is notable for being the ONE sub-weapon that drops from a 3HB. This meant I had to really
+change how things work to account for sub-weapons being in 3HBs!
+
+
+
+## Tower of Science
+#### Invisible bridge platform
+Following the hallway with a save jewel beyond the Science Key2 door, look to your right, and you'll see this. Mind the
+gap separating the invisible bridge from the solid ground of the bottom part of this section!
+
+### 3-Hit Breakables
+#### Invisible bridge platform crate
+Near the candle on the above-mentioned invisible bridge platform is a small metallic crate. Break it for a total of 6
+checks, which in vanilla are 2 chickens, moneybags, and jewels.
+
+
+
+## Tower of Sorcery
+#### Trick shot from mid-savepoint platform
+From the platform with the save jewel, look back towards the vanishing red platforms that you had to conquer to get up
+there, and you'll see a breakable diamond floating high above a solid platform before it. An ultra-precise orb shot from
+Carrie can hit it to reveal the check, so you can then go down and get it. If you are playing as Reinhardt, you'll have
+to use a sub-weapon. Any sub-weapon that's not Holy Water will work. Sub-weapons and the necessary jewels can both be
+farmed off the local Icemen if it really comes down to it.
+
+#### Above yellow bubble
+Directly above the yellow bubble that you break to raise the middle yellow large platform. Jump off the final red platform
+in the series of red platforms right after the save point when said platform bobs all the way up, and you'll be able to
+hit this diamond with good timing.
+
+#### Above tiny blue platforms start
+Above the large platform after the yellow ones from whence you can reach the first of the series of tiny blue platforms.
+This diamond is low enough that you can reach it by simply jumping straight up to it.
+
+#### Invisible bridge platform
+Located at the very end of the stage, off to the side. Use the invisible bridge to reach it. The Special2 that unlocks
+Carrie's costume can be normally found here (or Special3, depending on if you are playing the PAL version or not).
+
+
+
+## Clock Tower
+All the normal items here and in Room of Clocks are self-explanatory, but the 3HBs in Clock Tower are a whoooooooole 'nother story. So buckle up...
+
+### 3-Hit Breakables
+#### Gear climb room battery underside slab
+In the first room, on the side you initially climb up, go up until you find a battery-like object that you can stand on
+as a platform. From the platform right after this, you can hit this 3HB that can be found on the underside of the structure
+in the corner, above the structure before the first set of gears that you initially start the climb on. 3 chickens/checks
+can be gotten out of this one.
+
+#### Gear climb room door underside slab
+Now THIS one can be very annoying to get, doubly so if you're playing as Reinhardt, triply so if you're playing Hard Mode
+on top of that because you will also have to deal with invincible, bone-throwing red skeletons here! This slab can be
+found on the underside of the platform in the first room with the door leading out into the second area and drops 3
+beefs/checks when you break it. Carrie is small enough to crouch under the platform and shoot the thing a few times
+without *too* much hassle, but the only way I know of for Reinhardt to get it is to hang him off the side of the gear
+and pull him up once he gets moved under the platform. Getting him to then face the breakable and whip it without falling
+off due to the gear's rotation is next-to-impossible, I find, so the safest method to breaking it as him is to just rush
+it with his sword, go back up, repeat 2 more times. Pray the aftermentioned Hard Mode skellies don't decide to cause *too*
+much trouble during all of this!
+
+#### Final room entrance slab
+Simply next to the entrance when you come into the third and final room from the intended way. Drops 2 moneybags-turned-checks,
+which funnily normally have their item IDs shared with the other 3HB in this room's items, so I had to separate those for
+the rando.
+
+#### Renon's final offers slab
+At the top of the final room, on a platform near the Renon contract that would normally be the very last in the game.
+This 3HB drops a whopping 8 items, more than any other 3HB in the entire game, and 6 of those are all moneybags. They
+*really* shower you in gold in preparation for the finale, huh?
+
+
+
+## Castle Keep
+#### Behind Dracula's chamber/Dracula's floating cube
+This game continues the CV tradition of having a hidden secret around Dracula's chamber that you can get helpful things
+from before the final battle begins. Jump onto the torch ledges and use the thin walkway to reach the backside of Dracula's
+chamber where these can both be found. The floating cube, in question, can be reached with an invisible bridge. The other
+candle here is noteworthy for being the sole jewel candle in vanilla that doesn't set a flag, meaning you can keep going
+back down and up the stairs to farm infinite sub-weapon ammo for Dracula like in old school Castlevania!
+
+### Invisible Items
+#### Left/Right Dracula door flame
+Inside the torches on either side of the door to Dracula's chamber. Similar to the above-mentioned jewel torch, these do
+not set flags. So you can get infinite healing kits for free by constantly going down and back up!
\ No newline at end of file
diff --git a/worlds/cv64/docs/setup_en.md b/worlds/cv64/docs/setup_en.md
new file mode 100644
index 00000000000..6065b142c82
--- /dev/null
+++ b/worlds/cv64/docs/setup_en.md
@@ -0,0 +1,63 @@
+# Castlevania 64 Setup Guide
+
+## Required Software
+
+- [Archipelago](https://github.com/ArchipelagoMW/Archipelago/releases)
+- A Castlevania 64 ROM of the US 1.0 version specifically. The Archipelago community cannot provide this.
+- [BizHawk](https://tasvideos.org/BizHawk/ReleaseHistory) 2.7 or later
+
+### Configuring BizHawk
+
+Once you have installed BizHawk, open `EmuHawk.exe` and change the following settings:
+
+- If you're using BizHawk 2.7 or 2.8, go to `Config > Customize`. On the Advanced tab, switch the Lua Core from
+`NLua+KopiLua` to `Lua+LuaInterface`, then restart EmuHawk. (If you're using BizHawk 2.9, you can skip this step.)
+- Under `Config > Customize`, check the "Run in background" option to prevent disconnecting from the client while you're
+tabbed out of EmuHawk.
+- Open a `.z64` file in EmuHawk and go to `Config > Controllers…` to configure your inputs. If you can't click
+`Controllers…`, load any `.z64` ROM first.
+- Consider clearing keybinds in `Config > Hotkeys…` if you don't intend to use them. Select the keybind and press Esc to
+clear it.
+- All non-Japanese versions of the N64 Castlevanias require a Controller Pak to save game data. To enable this, while
+you still have the `.z64` ROM loaded, go to `N64 > Controller Settings...`, click the dropdown by `Controller 1`, and
+click `Memory Card`. You must then restart EmuHawk for it to take effect.
+- After enabling the `Memory Card` setting, next time you boot up your Castlevania 64 ROM, you will see the
+No "CASTLEVANIA" Note Found screen. Pick `Create "CASTLEVANIA" Note Now > Yes` to create save data and enable saving at
+the White Jewels.
+
+
+## Generating and Patching a Game
+
+1. Create your settings file (YAML). You can make one on the
+[Castlevania 64 settings page](../../../games/Castlevania 64/player-settings).
+2. Follow the general Archipelago instructions for [generating a game](../../Archipelago/setup/en#generating-a-game).
+This will generate an output file for you. Your patch file will have the `.apcv64` file extension.
+3. Open `ArchipelagoLauncher.exe`
+4. Select "Open Patch" on the left side and select your patch file.
+5. If this is your first time patching, you will be prompted to locate your vanilla ROM.
+6. A patched `.z64` file will be created in the same place as the patch file.
+7. On your first time opening a patch with BizHawk Client, you will also be asked to locate `EmuHawk.exe` in your
+BizHawk install.
+
+If you're playing a single-player seed, and you don't care about hints, you can stop here, close the client, and load
+the patched ROM in any emulator or EverDrive of your choice. However, for multiworlds and other Archipelago features,
+continue below using BizHawk as your emulator.
+
+## Connecting to a Server
+
+By default, opening a patch file will do steps 1-5 below for you automatically. Even so, keep them in your memory just
+in case you have to close and reopen a window mid-game for some reason.
+
+1. Castlevania 64 uses Archipelago's BizHawk Client. If the client isn't still open from when you patched your game,
+you can re-open it from the launcher.
+2. Ensure EmuHawk is running the patched ROM.
+3. In EmuHawk, go to `Tools > Lua Console`. This window must stay open while playing.
+4. In the Lua Console window, go to `Script > Open Script…`.
+5. Navigate to your Archipelago install folder and open `data/lua/connector_bizhawk_generic.lua`.
+6. The emulator may freeze every few seconds until it manages to connect to the client. This is expected. The BizHawk
+Client window should indicate that it connected and recognized Castlevania 64.
+7. To connect the client to the server, enter your room's address and port (e.g. `archipelago.gg:38281`) into the
+top text field of the client and click Connect.
+
+You should now be able to receive and send items. You'll need to do these steps every time you want to reconnect. It is
+perfectly safe to make progress offline; everything will re-sync when you reconnect.
diff --git a/worlds/cv64/entrances.py b/worlds/cv64/entrances.py
new file mode 100644
index 00000000000..74537f92441
--- /dev/null
+++ b/worlds/cv64/entrances.py
@@ -0,0 +1,149 @@
+from .data import ename, iname, rname
+from .stages import get_stage_info
+from .options import CV64Options
+
+from typing import Dict, List, Tuple, Union
+
+# # # KEY # # #
+# "connection" = The name of the Region the Entrance connects into. If it's a Tuple[str, str], we take the stage in
+# active_stage_exits given in the second string and then the stage given in that stage's slot given in
+# the first string, and take the start or end Region of that stage.
+# "rule" = What rule should be applied to the Entrance during set_rules, as defined in self.rules in the CV64Rules class
+# definition in rules.py.
+# "add conds" = A list of player options conditions that must be satisfied for the Entrance to be added. Can be of
+# varying length depending on how many conditions need to be satisfied. In the add_conds dict's tuples,
+# the first element is the name of the option, the second is the option value to check for, and the third
+# is a boolean for whether we are evaluating for the option value or not.
+entrance_info = {
+ # Forest of Silence
+ ename.forest_dbridge_gate: {"connection": rname.forest_mid},
+ ename.forest_werewolf_gate: {"connection": rname.forest_end},
+ ename.forest_end: {"connection": ("next", rname.forest_of_silence)},
+ # Castle Wall
+ ename.cw_portcullis_c: {"connection": rname.cw_exit},
+ ename.cw_lt_skip: {"connection": ("next", rname.castle_wall), "add conds": ["hard"]},
+ ename.cw_lt_door: {"connection": rname.cw_ltower, "rule": iname.left_tower_key},
+ ename.cw_end: {"connection": ("next", rname.castle_wall)},
+ # Villa
+ ename.villa_dog_gates: {"connection": rname.villa_main},
+ ename.villa_snipe_dogs: {"connection": rname.villa_start, "add conds": ["carrie", "hard"]},
+ ename.villa_to_storeroom: {"connection": rname.villa_storeroom, "rule": iname.storeroom_key},
+ ename.villa_to_archives: {"connection": rname.villa_archives, "rule": iname.archives_key},
+ ename.villa_renon: {"connection": rname.renon, "add conds": ["shopsanity"]},
+ ename.villa_to_maze: {"connection": rname.villa_maze, "rule": iname.garden_key},
+ ename.villa_from_storeroom: {"connection": rname.villa_main, "rule": iname.storeroom_key},
+ ename.villa_from_maze: {"connection": rname.villa_servants, "rule": iname.garden_key},
+ ename.villa_servant_door: {"connection": rname.villa_main},
+ ename.villa_copper_door: {"connection": rname.villa_crypt, "rule": iname.copper_key,
+ "add conds": ["not hard"]},
+ ename.villa_copper_skip: {"connection": rname.villa_crypt, "add conds": ["hard"]},
+ ename.villa_bridge_door: {"connection": rname.villa_maze},
+ ename.villa_end_r: {"connection": ("next", rname.villa)},
+ ename.villa_end_c: {"connection": ("alt", rname.villa)},
+ # Tunnel
+ ename.tunnel_start_renon: {"connection": rname.renon, "add conds": ["shopsanity"]},
+ ename.tunnel_gondolas: {"connection": rname.tunnel_end},
+ ename.tunnel_end_renon: {"connection": rname.renon, "add conds": ["shopsanity"]},
+ ename.tunnel_end: {"connection": ("next", rname.tunnel)},
+ # Underground Waterway
+ ename.uw_renon: {"connection": rname.renon, "add conds": ["shopsanity"]},
+ ename.uw_final_waterfall: {"connection": rname.uw_end},
+ ename.uw_waterfall_skip: {"connection": rname.uw_main, "add conds": ["hard"]},
+ ename.uw_end: {"connection": ("next", rname.underground_waterway)},
+ # Castle Center
+ ename.cc_tc_door: {"connection": rname.cc_torture_chamber, "rule": iname.chamber_key},
+ ename.cc_renon: {"connection": rname.renon, "add conds": ["shopsanity"]},
+ ename.cc_lower_wall: {"connection": rname.cc_crystal, "rule": "Bomb 2"},
+ ename.cc_upper_wall: {"connection": rname.cc_library, "rule": "Bomb 1"},
+ ename.cc_elevator: {"connection": rname.cc_elev_top},
+ ename.cc_exit_r: {"connection": ("next", rname.castle_center)},
+ ename.cc_exit_c: {"connection": ("alt", rname.castle_center)},
+ # Duel Tower
+ ename.dt_start: {"connection": ("prev", rname.duel_tower)},
+ ename.dt_end: {"connection": ("next", rname.duel_tower)},
+ # Tower of Execution
+ ename.toe_start: {"connection": ("prev", rname.tower_of_execution)},
+ ename.toe_gate: {"connection": rname.toe_ledge, "rule": iname.execution_key,
+ "add conds": ["not hard"]},
+ ename.toe_gate_skip: {"connection": rname.toe_ledge, "add conds": ["hard"]},
+ ename.toe_end: {"connection": ("next", rname.tower_of_execution)},
+ # Tower of Science
+ ename.tosci_start: {"connection": ("prev", rname.tower_of_science)},
+ ename.tosci_key1_door: {"connection": rname.tosci_three_doors, "rule": iname.science_key1},
+ ename.tosci_to_key2_door: {"connection": rname.tosci_conveyors, "rule": iname.science_key2},
+ ename.tosci_from_key2_door: {"connection": rname.tosci_start, "rule": iname.science_key2},
+ ename.tosci_key3_door: {"connection": rname.tosci_key3, "rule": iname.science_key3},
+ ename.tosci_end: {"connection": ("next", rname.tower_of_science)},
+ # Tower of Sorcery
+ ename.tosor_start: {"connection": ("prev", rname.tower_of_sorcery)},
+ ename.tosor_end: {"connection": ("next", rname.tower_of_sorcery)},
+ # Room of Clocks
+ ename.roc_gate: {"connection": ("next", rname.room_of_clocks)},
+ # Clock Tower
+ ename.ct_to_door1: {"connection": rname.ct_middle, "rule": iname.clocktower_key1},
+ ename.ct_from_door1: {"connection": rname.ct_start, "rule": iname.clocktower_key1},
+ ename.ct_to_door2: {"connection": rname.ct_end, "rule": iname.clocktower_key2},
+ ename.ct_from_door2: {"connection": rname.ct_middle, "rule": iname.clocktower_key2},
+ ename.ct_renon: {"connection": rname.renon, "add conds": ["shopsanity"]},
+ ename.ct_door_3: {"connection": ("next", rname.clock_tower), "rule": iname.clocktower_key3},
+ # Castle Keep
+ ename.ck_slope_jump: {"connection": rname.roc_main, "add conds": ["hard"]},
+ ename.ck_drac_door: {"connection": rname.ck_drac_chamber, "rule": "Dracula"}
+}
+
+add_conds = {"carrie": ("carrie_logic", True, True),
+ "hard": ("hard_logic", True, True),
+ "not hard": ("hard_logic", False, True),
+ "shopsanity": ("shopsanity", True, True)}
+
+stage_connection_types = {"prev": "end region",
+ "next": "start region",
+ "alt": "start region"}
+
+
+def get_entrance_info(entrance: str, info: str) -> Union[str, Tuple[str, str], List[str], None]:
+ return entrance_info[entrance].get(info, None)
+
+
+def get_warp_entrances(active_warp_list: List[str]) -> Dict[str, str]:
+ # Create the starting stage Entrance.
+ warp_entrances = {get_stage_info(active_warp_list[0], "start region"): "Start stage"}
+
+ # Create the warp Entrances.
+ for i in range(1, len(active_warp_list)):
+ mid_stage_region = get_stage_info(active_warp_list[i], "mid region")
+ warp_entrances.update({mid_stage_region: f"Warp {i}"})
+
+ return warp_entrances
+
+
+def verify_entrances(options: CV64Options, entrances: List[str],
+ active_stage_exits: Dict[str, Dict[str, Union[str, int, None]]]) -> Dict[str, str]:
+ verified_entrances = {}
+
+ for ent_name in entrances:
+ ent_add_conds = get_entrance_info(ent_name, "add conds")
+
+ # Check any options that might be associated with the Entrance before adding it.
+ add_it = True
+ if ent_add_conds is not None:
+ for cond in ent_add_conds:
+ if not ((getattr(options, add_conds[cond][0]).value == add_conds[cond][1]) == add_conds[cond][2]):
+ add_it = False
+
+ if not add_it:
+ continue
+
+ # Add the Entrance to the verified Entrances if the above check passes.
+ connection = get_entrance_info(ent_name, "connection")
+
+ # If the Entrance is a connection to a different stage, get the corresponding other stage Region.
+ if isinstance(connection, tuple):
+ connecting_stage = active_stage_exits[connection[1]][connection[0]]
+ # Stages that lead backwards at the beginning of the line will appear leading to "Menu".
+ if connecting_stage in ["Menu", None]:
+ continue
+ connection = get_stage_info(connecting_stage, stage_connection_types[connection[0]])
+ verified_entrances.update({connection: ent_name})
+
+ return verified_entrances
diff --git a/worlds/cv64/items.py b/worlds/cv64/items.py
new file mode 100644
index 00000000000..d40f5d53cb4
--- /dev/null
+++ b/worlds/cv64/items.py
@@ -0,0 +1,214 @@
+from BaseClasses import Item
+from .data import iname
+from .locations import base_id, get_location_info
+from .options import DraculasCondition, SpareKeys
+
+from typing import TYPE_CHECKING, Dict, Union
+
+if TYPE_CHECKING:
+ from . import CV64World
+
+import math
+
+
+class CV64Item(Item):
+ game: str = "Castlevania 64"
+
+
+# # # KEY # # #
+# "code" = The unique part of the Item's AP code attribute, as well as the value to call the in-game "prepare item
+# textbox" function with to give the Item in-game. Add this + base_id to get the actual AP code.
+# "default classification" = The AP Item Classification that gets assigned to instances of that Item in create_item
+# by default, unless I deliberately override it (as is the case for some Special1s).
+# "inventory offset" = What offset from the start of the in-game inventory array (beginning at 0x80389C4B) stores the
+# current count for that Item. Used for start inventory purposes.
+# "pickup actor id" = The ID for the Item's in-game Item pickup actor. If it's not in the Item's data dict, it's the
+# same as the Item's code. This is what gets written in the ROM to replace non-NPC/shop items.
+# "sub equip id" = For sub-weapons specifically, this is the number to put in the game's "current sub-weapon" value to
+# indicate the player currently having that weapon. Used for start inventory purposes.
+item_info = {
+ # White jewel
+ iname.red_jewel_s: {"code": 0x02, "default classification": "filler"},
+ iname.red_jewel_l: {"code": 0x03, "default classification": "filler"},
+ iname.special_one: {"code": 0x04, "default classification": "progression_skip_balancing",
+ "inventory offset": 0},
+ iname.special_two: {"code": 0x05, "default classification": "progression_skip_balancing",
+ "inventory offset": 1},
+ iname.roast_chicken: {"code": 0x06, "default classification": "filler", "inventory offset": 2},
+ iname.roast_beef: {"code": 0x07, "default classification": "filler", "inventory offset": 3},
+ iname.healing_kit: {"code": 0x08, "default classification": "useful", "inventory offset": 4},
+ iname.purifying: {"code": 0x09, "default classification": "filler", "inventory offset": 5},
+ iname.cure_ampoule: {"code": 0x0A, "default classification": "filler", "inventory offset": 6},
+ # pot-pourri
+ iname.powerup: {"code": 0x0C, "default classification": "filler"},
+ iname.permaup: {"code": 0x10C, "default classification": "useful", "pickup actor id": 0x0C,
+ "inventory offset": 8},
+ iname.knife: {"code": 0x0D, "default classification": "filler", "pickup actor id": 0x10,
+ "sub equip id": 1},
+ iname.holy_water: {"code": 0x0E, "default classification": "filler", "pickup actor id": 0x0D,
+ "sub equip id": 2},
+ iname.cross: {"code": 0x0F, "default classification": "filler", "pickup actor id": 0x0E,
+ "sub equip id": 3},
+ iname.axe: {"code": 0x10, "default classification": "filler", "pickup actor id": 0x0F,
+ "sub equip id": 4},
+ # Wooden stake (AP item)
+ iname.ice_trap: {"code": 0x12, "default classification": "trap"},
+ # The contract
+ # engagement ring
+ iname.magical_nitro: {"code": 0x15, "default classification": "progression", "inventory offset": 17},
+ iname.mandragora: {"code": 0x16, "default classification": "progression", "inventory offset": 18},
+ iname.sun_card: {"code": 0x17, "default classification": "filler", "inventory offset": 19},
+ iname.moon_card: {"code": 0x18, "default classification": "filler", "inventory offset": 20},
+ # Incandescent gaze
+ iname.archives_key: {"code": 0x1A, "default classification": "progression", "pickup actor id": 0x1D,
+ "inventory offset": 22},
+ iname.left_tower_key: {"code": 0x1B, "default classification": "progression", "pickup actor id": 0x1E,
+ "inventory offset": 23},
+ iname.storeroom_key: {"code": 0x1C, "default classification": "progression", "pickup actor id": 0x1F,
+ "inventory offset": 24},
+ iname.garden_key: {"code": 0x1D, "default classification": "progression", "pickup actor id": 0x20,
+ "inventory offset": 25},
+ iname.copper_key: {"code": 0x1E, "default classification": "progression", "pickup actor id": 0x21,
+ "inventory offset": 26},
+ iname.chamber_key: {"code": 0x1F, "default classification": "progression", "pickup actor id": 0x22,
+ "inventory offset": 27},
+ iname.execution_key: {"code": 0x20, "default classification": "progression", "pickup actor id": 0x23,
+ "inventory offset": 28},
+ iname.science_key1: {"code": 0x21, "default classification": "progression", "pickup actor id": 0x24,
+ "inventory offset": 29},
+ iname.science_key2: {"code": 0x22, "default classification": "progression", "pickup actor id": 0x25,
+ "inventory offset": 30},
+ iname.science_key3: {"code": 0x23, "default classification": "progression", "pickup actor id": 0x26,
+ "inventory offset": 31},
+ iname.clocktower_key1: {"code": 0x24, "default classification": "progression", "pickup actor id": 0x27,
+ "inventory offset": 32},
+ iname.clocktower_key2: {"code": 0x25, "default classification": "progression", "pickup actor id": 0x28,
+ "inventory offset": 33},
+ iname.clocktower_key3: {"code": 0x26, "default classification": "progression", "pickup actor id": 0x29,
+ "inventory offset": 34},
+ iname.five_hundred_gold: {"code": 0x27, "default classification": "filler", "pickup actor id": 0x1A},
+ iname.three_hundred_gold: {"code": 0x28, "default classification": "filler", "pickup actor id": 0x1B},
+ iname.one_hundred_gold: {"code": 0x29, "default classification": "filler", "pickup actor id": 0x1C},
+ iname.crystal: {"default classification": "progression"},
+ iname.trophy: {"default classification": "progression"},
+ iname.victory: {"default classification": "progression"}
+}
+
+filler_item_names = [iname.red_jewel_s, iname.red_jewel_l, iname.five_hundred_gold, iname.three_hundred_gold,
+ iname.one_hundred_gold]
+
+
+def get_item_info(item: str, info: str) -> Union[str, int, None]:
+ return item_info[item].get(info, None)
+
+
+def get_item_names_to_ids() -> Dict[str, int]:
+ return {name: get_item_info(name, "code")+base_id for name in item_info if get_item_info(name, "code") is not None}
+
+
+def get_item_counts(world: "CV64World") -> Dict[str, Dict[str, int]]:
+
+ active_locations = world.multiworld.get_unfilled_locations(world.player)
+
+ item_counts = {
+ "progression": {},
+ "progression_skip_balancing": {},
+ "useful": {},
+ "filler": {},
+ "trap": {}
+ }
+ total_items = 0
+ extras_count = 0
+
+ # Get from each location its vanilla item and add it to the default item counts.
+ for loc in active_locations:
+ if loc.address is None:
+ continue
+
+ if world.options.hard_item_pool and get_location_info(loc.name, "hard item") is not None:
+ item_to_add = get_location_info(loc.name, "hard item")
+ else:
+ item_to_add = get_location_info(loc.name, "normal item")
+
+ classification = get_item_info(item_to_add, "default classification")
+
+ if item_to_add not in item_counts[classification]:
+ item_counts[classification][item_to_add] = 1
+ else:
+ item_counts[classification][item_to_add] += 1
+ total_items += 1
+
+ # Replace all but 2 PowerUps with junk if Permanent PowerUps is on and mark those two PowerUps as Useful.
+ if world.options.permanent_powerups:
+ for i in range(item_counts["filler"][iname.powerup] - 2):
+ item_counts["filler"][world.get_filler_item_name()] += 1
+ del(item_counts["filler"][iname.powerup])
+ item_counts["useful"][iname.permaup] = 2
+
+ # Add the total Special1s.
+ item_counts["progression_skip_balancing"][iname.special_one] = world.options.total_special1s.value
+ extras_count += world.options.total_special1s.value
+
+ # Add the total Special2s if Dracula's Condition is Special2s.
+ if world.options.draculas_condition == DraculasCondition.option_specials:
+ item_counts["progression_skip_balancing"][iname.special_two] = world.options.total_special2s.value
+ extras_count += world.options.total_special2s.value
+
+ # Determine the extra key counts if applicable. Doing this before moving Special1s will ensure only the keys and
+ # bomb components are affected by this.
+ for key in item_counts["progression"]:
+ spare_keys = 0
+ if world.options.spare_keys == SpareKeys.option_on:
+ spare_keys = item_counts["progression"][key]
+ elif world.options.spare_keys == SpareKeys.option_chance:
+ if item_counts["progression"][key] > 0:
+ for i in range(item_counts["progression"][key]):
+ spare_keys += world.random.randint(0, 1)
+ item_counts["progression"][key] += spare_keys
+ extras_count += spare_keys
+
+ # Move the total number of Special1s needed to warp everywhere to normal progression balancing if S1s per warp is
+ # 3 or lower.
+ if world.s1s_per_warp <= 3:
+ item_counts["progression_skip_balancing"][iname.special_one] -= world.s1s_per_warp * 7
+ item_counts["progression"][iname.special_one] = world.s1s_per_warp * 7
+
+ # Determine the total amounts of replaceable filler and non-filler junk.
+ total_filler_junk = 0
+ total_non_filler_junk = 0
+ for junk in item_counts["filler"]:
+ if junk in filler_item_names:
+ total_filler_junk += item_counts["filler"][junk]
+ else:
+ total_non_filler_junk += item_counts["filler"][junk]
+
+ # Subtract from the filler counts total number of "extra" items we've added. get_filler_item_name() filler will be
+ # subtracted from first until we run out of that, at which point we'll start subtracting from the rest. At this
+ # moment, non-filler item name filler cannot run out no matter the settings, so I haven't bothered adding handling
+ # for when it does yet.
+ available_filler_junk = filler_item_names.copy()
+ for i in range(extras_count):
+ if total_filler_junk > 0:
+ total_filler_junk -= 1
+ item_to_subtract = world.random.choice(available_filler_junk)
+ else:
+ total_non_filler_junk -= 1
+ item_to_subtract = world.random.choice(list(item_counts["filler"].keys()))
+
+ item_counts["filler"][item_to_subtract] -= 1
+ if item_counts["filler"][item_to_subtract] == 0:
+ del(item_counts["filler"][item_to_subtract])
+ if item_to_subtract in available_filler_junk:
+ available_filler_junk.remove(item_to_subtract)
+
+ # Determine the Ice Trap count by taking a certain % of the total filler remaining at this point.
+ item_counts["trap"][iname.ice_trap] = math.floor((total_filler_junk + total_non_filler_junk) *
+ (world.options.ice_trap_percentage.value / 100.0))
+ for i in range(item_counts["trap"][iname.ice_trap]):
+ # Subtract the remaining filler after determining the ice trap count.
+ item_to_subtract = world.random.choice(list(item_counts["filler"].keys()))
+ item_counts["filler"][item_to_subtract] -= 1
+ if item_counts["filler"][item_to_subtract] == 0:
+ del (item_counts["filler"][item_to_subtract])
+
+ return item_counts
diff --git a/worlds/cv64/locations.py b/worlds/cv64/locations.py
new file mode 100644
index 00000000000..264f2f7c0b9
--- /dev/null
+++ b/worlds/cv64/locations.py
@@ -0,0 +1,699 @@
+from BaseClasses import Location
+from .data import lname, iname
+from .options import CV64Options, SubWeaponShuffle, DraculasCondition, RenonFightCondition, VincentFightCondition
+
+from typing import Dict, Optional, Union, List, Tuple
+
+base_id = 0xC64000
+
+
+class CV64Location(Location):
+ game: str = "Castlevania 64"
+
+
+# # # KEY # # #
+# "code" = The unique part of the Location's AP code attribute, as well as the in-game bitflag index starting from
+# 0x80389BE4 that indicates the Location has been checked. Add this + base_id to get the actual AP code.
+# "offset" = The offset in the ROM to overwrite to change the Item on that Location.
+# "normal item" = The Item normally there in vanilla on most difficulties in most versions of the game. Used to
+# determine the World's Item counts by checking what Locations are active.
+# "hard item" = The Item normally there in Hard Mode in the PAL version of CV64 specifically. Used instead of the
+# normal Item when the hard Item pool is enabled if it's in the Location's data dict.
+# "add conds" = A list of player options conditions that must be satisfied for the Location to be added. Can be of
+# varying length depending on how many conditions need to be satisfied. In the add_conds dict's tuples,
+# the first element is the name of the option, the second is the option value to check for, and the third
+# is a boolean for whether we are evaluating for the option value or not.
+# "event" = What event Item to place on that Location, for Locations that are events specifically.
+# "countdown" = What Countdown number in the array of Countdown numbers that Location contributes to. For the most part,
+# this is figured out by taking that Location's corresponding stage's postion in the vanilla stage order,
+# but there are some exceptions made for Locations in parts of Villa and Castle Center that split off into
+# their own numbers.
+# "type" = Anything special about this Location in-game, whether it be NPC-given, invisible, etc.
+location_info = {
+ # Forest of Silence
+ lname.forest_pillars_right: {"code": 0x1C, "offset": 0x10C67B, "normal item": iname.red_jewel_l,
+ "hard item": iname.red_jewel_s},
+ lname.forest_pillars_left: {"code": 0x46, "offset": 0x10C6EB, "normal item": iname.knife,
+ "add conds": ["sub"]},
+ lname.forest_pillars_top: {"code": 0x13, "offset": 0x10C71B, "normal item": iname.roast_beef,
+ "hard item": iname.red_jewel_l},
+ lname.forest_boss_one: {"event": iname.trophy, "add conds": ["boss"]},
+ lname.forest_king_skeleton: {"code": 0xC, "offset": 0x10C6BB, "normal item": iname.five_hundred_gold},
+ lname.forest_lgaz_in: {"code": 0x1A, "offset": 0x10C68B, "normal item": iname.moon_card},
+ lname.forest_lgaz_top: {"code": 0x19, "offset": 0x10C693, "normal item": iname.red_jewel_l,
+ "hard item": iname.red_jewel_s},
+ lname.forest_hgaz_in: {"code": 0xB, "offset": 0x10C6C3, "normal item": iname.sun_card},
+ lname.forest_hgaz_top: {"code": 0x3, "offset": 0x10C6E3, "normal item": iname.roast_chicken,
+ "hard item": iname.five_hundred_gold},
+ lname.forest_weretiger_sw: {"code": 0xA, "offset": 0x10C6CB, "normal item": iname.five_hundred_gold},
+ lname.forest_boss_two: {"event": iname.trophy, "add conds": ["boss"]},
+ lname.forest_weretiger_gate: {"code": 0x7, "offset": 0x10C683, "normal item": iname.powerup},
+ lname.forest_dirge_tomb_l: {"code": 0x59, "offset": 0x10C74B, "normal item": iname.one_hundred_gold,
+ "add conds": ["empty"]},
+ lname.forest_dirge_tomb_u: {"code": 0x8, "offset": 0x10C743, "normal item": iname.one_hundred_gold},
+ lname.forest_dirge_plaque: {"code": 0x6, "offset": 0x7C7F9D, "normal item": iname.roast_chicken,
+ "hard item": iname.one_hundred_gold, "type": "inv"},
+ lname.forest_dirge_ped: {"code": 0x45, "offset": 0x10C6FB, "normal item": iname.cross,
+ "add conds": ["sub"]},
+ lname.forest_dirge_rock1: {"code": 0x221, "offset": 0x10C791, "normal item": iname.five_hundred_gold,
+ "add conds": ["3hb"]},
+ lname.forest_dirge_rock2: {"code": 0x222, "offset": 0x10C793, "normal item": iname.five_hundred_gold,
+ "add conds": ["3hb"]},
+ lname.forest_dirge_rock3: {"code": 0x223, "offset": 0x10C795, "normal item": iname.five_hundred_gold,
+ "add conds": ["3hb"]},
+ lname.forest_dirge_rock4: {"code": 0x224, "offset": 0x10C797, "normal item": iname.five_hundred_gold,
+ "add conds": ["3hb"]},
+ lname.forest_dirge_rock5: {"code": 0x225, "offset": 0x10C799, "normal item": iname.five_hundred_gold,
+ "add conds": ["3hb"]},
+ lname.forest_corpse_save: {"code": 0xF, "offset": 0x10C6A3, "normal item": iname.red_jewel_s},
+ lname.forest_dbridge_wall: {"code": 0x18, "offset": 0x10C69B, "normal item": iname.red_jewel_s},
+ lname.forest_dbridge_sw: {"code": 0x9, "offset": 0x10C6D3, "normal item": iname.roast_beef,
+ "hard item": iname.one_hundred_gold},
+ lname.forest_dbridge_gate_l: {"code": 0x44, "offset": 0x10C6F3, "normal item": iname.axe, "add conds": ["sub"]},
+ lname.forest_dbridge_gate_r: {"code": 0xE, "offset": 0x10C6AB, "normal item": iname.red_jewel_l,
+ "hard item": iname.red_jewel_s},
+ lname.forest_dbridge_tomb_l: {"code": 0xEA, "offset": 0x10C763, "normal item": iname.three_hundred_gold,
+ "add conds": ["empty"]},
+ lname.forest_dbridge_tomb_ur: {"code": 0xE4, "offset": 0x10C773, "normal item": iname.three_hundred_gold,
+ "add conds": ["empty"]},
+ lname.forest_dbridge_tomb_uf: {"code": 0x1B, "offset": 0x10C76B, "normal item": iname.red_jewel_s},
+ lname.forest_bface_tomb_lf: {"code": 0x10, "offset": 0x10C75B, "normal item": iname.roast_chicken},
+ lname.forest_bface_tomb_lr: {"code": 0x58, "offset": 0x10C753, "normal item": iname.three_hundred_gold,
+ "add conds": ["empty"]},
+ lname.forest_bface_tomb_u: {"code": 0x1E, "offset": 0x10C77B, "normal item": iname.one_hundred_gold},
+ lname.forest_ibridge: {"code": 0x2, "offset": 0x10C713, "normal item": iname.one_hundred_gold},
+ lname.forest_bridge_rock1: {"code": 0x227, "offset": 0x10C79D, "normal item": iname.red_jewel_l,
+ "add conds": ["3hb"]},
+ lname.forest_bridge_rock2: {"code": 0x228, "offset": 0x10C79F, "normal item": iname.five_hundred_gold,
+ "hard item": iname.three_hundred_gold, "add conds": ["3hb"]},
+ lname.forest_bridge_rock3: {"code": 0x229, "offset": 0x10C7A1, "normal item": iname.powerup,
+ "hard item": iname.three_hundred_gold, "add conds": ["3hb"]},
+ lname.forest_bridge_rock4: {"code": 0x22A, "offset": 0x10C7A3, "normal item": iname.roast_chicken,
+ "hard item": iname.one_hundred_gold, "add conds": ["3hb"]},
+ lname.forest_werewolf_tomb_lf: {"code": 0xE7, "offset": 0x10C783, "normal item": iname.one_hundred_gold,
+ "add conds": ["empty"]},
+ lname.forest_werewolf_tomb_lr: {"code": 0xE6, "offset": 0x10C73B, "normal item": iname.three_hundred_gold,
+ "add conds": ["empty"]},
+ lname.forest_werewolf_tomb_r: {"code": 0x4, "offset": 0x10C733, "normal item": iname.sun_card},
+ lname.forest_werewolf_plaque: {"code": 0x1, "offset": 0xBFC8AF, "normal item": iname.roast_chicken,
+ "type": "inv"},
+ lname.forest_werewolf_tree: {"code": 0xD, "offset": 0x10C6B3, "normal item": iname.red_jewel_s},
+ lname.forest_werewolf_island: {"code": 0x41, "offset": 0x10C703, "normal item": iname.holy_water,
+ "add conds": ["sub"]},
+ lname.forest_final_sw: {"code": 0x12, "offset": 0x10C72B, "normal item": iname.roast_beef},
+ lname.forest_boss_three: {"event": iname.trophy, "add conds": ["boss"]},
+
+ # Castle Wall
+ lname.cwr_bottom: {"code": 0x1DD, "offset": 0x10C7E7, "normal item": iname.sun_card,
+ "hard item": iname.one_hundred_gold},
+ lname.cw_dragon_sw: {"code": 0x153, "offset": 0x10C817, "normal item": iname.roast_chicken},
+ lname.cw_boss: {"event": iname.trophy, "add conds": ["boss"]},
+ lname.cw_save_slab1: {"code": 0x22C, "offset": 0x10C84D, "normal item": iname.red_jewel_l,
+ "add conds": ["3hb"]},
+ lname.cw_save_slab2: {"code": 0x22D, "offset": 0x10C84F, "normal item": iname.red_jewel_l,
+ "hard item": iname.red_jewel_s, "add conds": ["3hb"]},
+ lname.cw_save_slab3: {"code": 0x22E, "offset": 0x10C851, "normal item": iname.red_jewel_l,
+ "hard item": iname.red_jewel_s, "add conds": ["3hb"]},
+ lname.cw_save_slab4: {"code": 0x22F, "offset": 0x10C853, "normal item": iname.red_jewel_l,
+ "hard item": iname.red_jewel_s, "add conds": ["3hb"]},
+ lname.cw_save_slab5: {"code": 0x230, "offset": 0x10C855, "normal item": iname.red_jewel_l,
+ "hard item": iname.red_jewel_s, "add conds": ["3hb"]},
+ lname.cw_rrampart: {"code": 0x156, "offset": 0x10C7FF, "normal item": iname.five_hundred_gold},
+ lname.cw_lrampart: {"code": 0x155, "offset": 0x10C807, "normal item": iname.moon_card,
+ "hard item": iname.one_hundred_gold},
+ lname.cw_pillar: {"code": 0x14D, "offset": 0x7F9A0F, "normal item": iname.holy_water, "add conds": ["sub"]},
+ lname.cw_shelf_visible: {"code": 0x158, "offset": 0x7F99A9, "normal item": iname.powerup},
+ lname.cw_shelf_sandbags: {"code": 0x14E, "offset": 0x7F9A3E, "normal item": iname.five_hundred_gold, "type": "inv"},
+ lname.cw_shelf_torch: {"code": 0x14C, "offset": 0x10C82F, "normal item": iname.cross, "add conds": ["sub"]},
+ lname.cw_ground_left: {"code": 0x14B, "offset": 0x10C827, "normal item": iname.knife, "add conds": ["sub"]},
+ lname.cw_ground_middle: {"code": 0x159, "offset": 0x10C7F7, "normal item": iname.left_tower_key},
+ lname.cw_ground_right: {"code": 0x14A, "offset": 0x10C81F, "normal item": iname.axe, "add conds": ["sub"]},
+ lname.cwl_bottom: {"code": 0x1DE, "offset": 0x10C7DF, "normal item": iname.moon_card},
+ lname.cwl_bridge: {"code": 0x1DC, "offset": 0x10C7EF, "normal item": iname.roast_beef},
+ lname.cw_drac_sw: {"code": 0x154, "offset": 0x10C80F, "normal item": iname.roast_chicken,
+ "hard item": iname.one_hundred_gold},
+ lname.cw_drac_slab1: {"code": 0x232, "offset": 0x10C859, "normal item": iname.five_hundred_gold,
+ "add conds": ["3hb"]},
+ lname.cw_drac_slab2: {"code": 0x233, "offset": 0x10C85B, "normal item": iname.five_hundred_gold,
+ "add conds": ["3hb"]},
+ lname.cw_drac_slab3: {"code": 0x234, "offset": 0x10C85D, "normal item": iname.five_hundred_gold,
+ "add conds": ["3hb"]},
+ lname.cw_drac_slab4: {"code": 0x235, "offset": 0x10C85F, "normal item": iname.five_hundred_gold,
+ "add conds": ["3hb"]},
+ lname.cw_drac_slab5: {"code": 0x236, "offset": 0x10C861, "normal item": iname.five_hundred_gold,
+ "add conds": ["3hb"]},
+ # Villa
+ lname.villafy_outer_gate_l: {"code": 0x133, "offset": 0x10C87F, "normal item": iname.red_jewel_l},
+ lname.villafy_outer_gate_r: {"code": 0x132, "offset": 0x10C887, "normal item": iname.red_jewel_l},
+ lname.villafy_dog_platform: {"code": 0x134, "offset": 0x10C89F, "normal item": iname.red_jewel_l},
+ lname.villafy_inner_gate: {"code": 0x138, "offset": 0xBFC8D7, "normal item": iname.roast_beef},
+ lname.villafy_gate_marker: {"code": 0x131, "offset": 0x10C8A7, "normal item": iname.powerup,
+ "hard item": iname.one_hundred_gold},
+ lname.villafy_villa_marker: {"code": 0x13E, "offset": 0x10C897, "normal item": iname.roast_beef,
+ "hard item": iname.one_hundred_gold},
+ lname.villafy_tombstone: {"code": 0x12F, "offset": 0x8099CC, "normal item": iname.moon_card,
+ "type": "inv"},
+ lname.villafy_fountain_fl: {"code": 0x139, "offset": 0xBFC8CF, "normal item": iname.five_hundred_gold},
+ lname.villafy_fountain_fr: {"code": 0x130, "offset": 0x80997D, "normal item": iname.purifying},
+ lname.villafy_fountain_ml: {"code": 0x13A, "offset": 0x809956, "normal item": iname.sun_card},
+ lname.villafy_fountain_mr: {"code": 0x13D, "offset": 0x80992D, "normal item": iname.moon_card},
+ lname.villafy_fountain_rl: {"code": 0x13B, "offset": 0xBFC8D3, "normal item": iname.roast_beef,
+ "hard item": iname.five_hundred_gold},
+ lname.villafy_fountain_rr: {"code": 0x13C, "offset": 0x80993C, "normal item": iname.five_hundred_gold},
+ lname.villafo_front_r: {"code": 0x3D, "offset": 0x10C8E7, "normal item": iname.red_jewel_l,
+ "hard item": iname.five_hundred_gold},
+ lname.villafo_front_l: {"code": 0x3B, "offset": 0x10C8DF, "normal item": iname.red_jewel_s},
+ lname.villafo_mid_l: {"code": 0x3C, "offset": 0x10C8D7, "normal item": iname.red_jewel_s},
+ lname.villafo_mid_r: {"code": 0xE5, "offset": 0x10C8CF, "normal item": iname.three_hundred_gold,
+ "add conds": ["empty"]},
+ lname.villafo_rear_r: {"code": 0x38, "offset": 0x10C8C7, "normal item": iname.red_jewel_s},
+ lname.villafo_rear_l: {"code": 0x39, "offset": 0x10C8BF, "normal item": iname.red_jewel_l,
+ "hard item": iname.red_jewel_s},
+ lname.villafo_pot_r: {"code": 0x2E, "offset": 0x10C8AF, "normal item": iname.red_jewel_l,
+ "hard item": iname.red_jewel_s},
+ lname.villafo_pot_l: {"code": 0x2F, "offset": 0x10C8B7, "normal item": iname.red_jewel_s},
+ lname.villafo_sofa: {"code": 0x2D, "offset": 0x81F07C, "normal item": iname.purifying,
+ "type": "inv"},
+ lname.villafo_chandelier1: {"code": 0x27D, "offset": 0x10C8F5, "normal item": iname.red_jewel_l,
+ "add conds": ["3hb"]},
+ lname.villafo_chandelier2: {"code": 0x27E, "offset": 0x10C8F7, "normal item": iname.purifying,
+ "add conds": ["3hb"]},
+ lname.villafo_chandelier3: {"code": 0x27F, "offset": 0x10C8F9, "normal item": iname.five_hundred_gold,
+ "add conds": ["3hb"]},
+ lname.villafo_chandelier4: {"code": 0x280, "offset": 0x10C8FB, "normal item": iname.cure_ampoule,
+ "add conds": ["3hb"]},
+ lname.villafo_chandelier5: {"code": 0x281, "offset": 0x10C8FD, "normal item": iname.roast_chicken,
+ "add conds": ["3hb"]},
+ lname.villala_hallway_stairs: {"code": 0x34, "offset": 0x10C927, "normal item": iname.red_jewel_l},
+ lname.villala_hallway_l: {"code": 0x40, "offset": 0xBFC903, "normal item": iname.knife,
+ "add conds": ["sub"]},
+ lname.villala_hallway_r: {"code": 0x4F, "offset": 0xBFC8F7, "normal item": iname.axe,
+ "add conds": ["sub"]},
+ lname.villala_bedroom_chairs: {"code": 0x33, "offset": 0x83A588, "normal item": iname.purifying,
+ "hard item": iname.three_hundred_gold},
+ lname.villala_bedroom_bed: {"code": 0x32, "offset": 0xBFC95B, "normal item": iname.red_jewel_l,
+ "hard item": iname.three_hundred_gold},
+ lname.villala_vincent: {"code": 0x23, "offset": 0xBFE42F, "normal item": iname.archives_key,
+ "type": "npc"},
+ lname.villala_slivingroom_table: {"code": 0x2B, "offset": 0xBFC96B, "normal item": iname.five_hundred_gold,
+ "type": "inv"},
+ lname.villala_slivingroom_mirror: {"code": 0x49, "offset": 0x83A5D9, "normal item": iname.cross,
+ "add conds": ["sub"]},
+ lname.villala_diningroom_roses: {"code": 0x2A, "offset": 0xBFC90B, "normal item": iname.purifying,
+ "hard item": iname.three_hundred_gold, "type": "inv"},
+ lname.villala_llivingroom_pot_r: {"code": 0x26, "offset": 0x10C90F, "normal item": iname.storeroom_key},
+ lname.villala_llivingroom_pot_l: {"code": 0x25, "offset": 0x10C917, "normal item": iname.roast_chicken},
+ lname.villala_llivingroom_painting: {"code": 0x2C, "offset": 0xBFC907, "normal item": iname.purifying,
+ "hard item": iname.one_hundred_gold, "type": "inv"},
+ lname.villala_llivingroom_light: {"code": 0x28, "offset": 0x10C91F, "normal item": iname.purifying},
+ lname.villala_llivingroom_lion: {"code": 0x30, "offset": 0x83A610, "normal item": iname.roast_chicken,
+ "hard item": iname.five_hundred_gold, "type": "inv"},
+ lname.villala_exit_knight: {"code": 0x27, "offset": 0xBFC967, "normal item": iname.purifying,
+ "type": "inv"},
+ lname.villala_storeroom_l: {"code": 0x36, "offset": 0xBFC95F, "normal item": iname.roast_beef},
+ lname.villala_storeroom_r: {"code": 0x37, "offset": 0xBFC8FF, "normal item": iname.roast_chicken,
+ "hard item": iname.five_hundred_gold},
+ lname.villala_storeroom_s: {"code": 0x31, "offset": 0xBFC963, "normal item": iname.purifying,
+ "hard item": iname.one_hundred_gold, "type": "inv"},
+ lname.villala_archives_entrance: {"code": 0x48, "offset": 0x83A5E5, "normal item": iname.holy_water,
+ "add conds": ["sub"]},
+ lname.villala_archives_table: {"code": 0x29, "offset": 0xBFC90F, "normal item": iname.purifying,
+ "type": "inv"},
+ lname.villala_archives_rear: {"code": 0x24, "offset": 0x83A5B1, "normal item": iname.garden_key},
+ lname.villam_malus_torch: {"code": 0x173, "offset": 0x10C967, "normal item": iname.red_jewel_s,
+ "countdown": 13},
+ lname.villam_malus_bush: {"code": 0x16C, "offset": 0x850FEC, "normal item": iname.roast_chicken,
+ "type": "inv", "countdown": 13},
+ lname.villam_fplatform: {"code": 0x16B, "offset": 0x10C987, "normal item": iname.knife,
+ "add conds": ["sub"], "countdown": 13},
+ lname.villam_frankieturf_l: {"code": 0x177, "offset": 0x10C947, "normal item": iname.three_hundred_gold,
+ "countdown": 13},
+ lname.villam_frankieturf_r: {"code": 0x16A, "offset": 0x10C98F, "normal item": iname.holy_water,
+ "add conds": ["sub"], "countdown": 13},
+ lname.villam_frankieturf_ru: {"code": 0x16E, "offset": 0x10C9A7, "normal item": iname.red_jewel_s,
+ "countdown": 13},
+ lname.villam_fgarden_f: {"code": 0x172, "offset": 0x10C96F, "normal item": iname.red_jewel_s,
+ "countdown": 13},
+ lname.villam_fgarden_mf: {"code": 0x171, "offset": 0x10C977, "normal item": iname.red_jewel_s,
+ "countdown": 13},
+ lname.villam_fgarden_mr: {"code": 0x174, "offset": 0x10C95F, "normal item": iname.roast_chicken,
+ "countdown": 13},
+ lname.villam_fgarden_r: {"code": 0x170, "offset": 0x10C97F, "normal item": iname.red_jewel_l,
+ "countdown": 13},
+ lname.villam_rplatform: {"code": 0x169, "offset": 0x10C997, "normal item": iname.axe,
+ "add conds": ["sub"], "countdown": 13},
+ lname.villam_rplatform_de: {"code": 0x176, "offset": 0x10C94F, "normal item": iname.five_hundred_gold,
+ "countdown": 13},
+ lname.villam_exit_de: {"code": 0x175, "offset": 0x10C957, "normal item": iname.three_hundred_gold,
+ "countdown": 13},
+ lname.villam_serv_path: {"code": 0x17A, "offset": 0x10C92F, "normal item": iname.copper_key,
+ "countdown": 13},
+ lname.villafo_serv_ent: {"code": 0x3E, "offset": 0x10C8EF, "normal item": iname.roast_chicken},
+ lname.villam_crypt_ent: {"code": 0x178, "offset": 0x10C93F, "normal item": iname.purifying,
+ "countdown": 13},
+ lname.villam_crypt_upstream: {"code": 0x179, "offset": 0x10C937, "normal item": iname.roast_beef,
+ "countdown": 13},
+ lname.villac_ent_l: {"code": 0xC9, "offset": 0x10CF4B, "normal item": iname.red_jewel_s,
+ "countdown": 13},
+ lname.villac_ent_r: {"code": 0xC0, "offset": 0x10CF63, "normal item": iname.five_hundred_gold,
+ "countdown": 13},
+ lname.villac_wall_l: {"code": 0xC2, "offset": 0x10CF6B, "normal item": iname.roast_chicken,
+ "countdown": 13},
+ lname.villac_wall_r: {"code": 0xC1, "offset": 0x10CF5B, "normal item": iname.red_jewel_l,
+ "countdown": 13},
+ lname.villac_coffin_l: {"code": 0xD8, "offset": 0x10CF73, "normal item": iname.knife,
+ "add conds": ["sub"], "countdown": 13},
+ lname.villac_coffin_r: {"code": 0xC8, "offset": 0x10CF53, "normal item": iname.red_jewel_s,
+ "countdown": 13},
+ lname.villa_boss_one: {"event": iname.trophy, "add conds": ["boss"]},
+ lname.villa_boss_two: {"event": iname.trophy, "add conds": ["boss"]},
+ # Tunnel
+ lname.tunnel_landing: {"code": 0x197, "offset": 0x10C9AF, "normal item": iname.red_jewel_l,
+ "hard item": iname.one_hundred_gold},
+ lname.tunnel_landing_rc: {"code": 0x196, "offset": 0x10C9B7, "normal item": iname.red_jewel_s,
+ "hard item": iname.one_hundred_gold},
+ lname.tunnel_stone_alcove_r: {"code": 0xE1, "offset": 0x10CA57, "normal item": iname.holy_water,
+ "add conds": ["sub"]},
+ lname.tunnel_stone_alcove_l: {"code": 0x187, "offset": 0x10CA9F, "normal item": iname.roast_beef,
+ "hard item": iname.roast_chicken},
+ lname.tunnel_twin_arrows: {"code": 0x195, "offset": 0xBFC993, "normal item": iname.cure_ampoule,
+ "type": "inv"},
+ lname.tunnel_arrows_rock1: {"code": 0x238, "offset": 0x10CABD, "normal item": iname.purifying,
+ "add conds": ["3hb"]},
+ lname.tunnel_arrows_rock2: {"code": 0x239, "offset": 0x10CABF, "normal item": iname.purifying,
+ "hard item": iname.one_hundred_gold, "add conds": ["3hb"]},
+ lname.tunnel_arrows_rock3: {"code": 0x23A, "offset": 0x10CAC1, "normal item": iname.cure_ampoule,
+ "add conds": ["3hb"]},
+ lname.tunnel_arrows_rock4: {"code": 0x23B, "offset": 0x10CAC3, "normal item": iname.cure_ampoule,
+ "hard item": iname.one_hundred_gold, "add conds": ["3hb"]},
+ lname.tunnel_arrows_rock5: {"code": 0x23C, "offset": 0x10CAC5, "normal item": iname.roast_chicken,
+ "hard item": iname.one_hundred_gold, "add conds": ["3hb"]},
+ lname.tunnel_lonesome_bucket: {"code": 0x189, "offset": 0xBFC99B, "normal item": iname.cure_ampoule,
+ "type": "inv"},
+ lname.tunnel_lbucket_mdoor_l: {"code": 0x198, "offset": 0x10CA67, "normal item": iname.knife,
+ "add conds": ["sub"]},
+ lname.tunnel_lbucket_quag: {"code": 0x191, "offset": 0x10C9DF, "normal item": iname.red_jewel_l},
+ lname.tunnel_bucket_quag_rock1: {"code": 0x23E, "offset": 0x10CAC9, "normal item": iname.roast_beef,
+ "hard item": iname.roast_chicken, "add conds": ["3hb"]},
+ lname.tunnel_bucket_quag_rock2: {"code": 0x23F, "offset": 0x10CACB, "normal item": iname.roast_beef,
+ "hard item": iname.roast_chicken, "add conds": ["3hb"]},
+ lname.tunnel_bucket_quag_rock3: {"code": 0x240, "offset": 0x10CACD, "normal item": iname.roast_beef,
+ "hard item": iname.roast_chicken, "add conds": ["3hb"]},
+ lname.tunnel_lbucket_albert: {"code": 0x190, "offset": 0x10C9E7, "normal item": iname.red_jewel_s},
+ lname.tunnel_albert_camp: {"code": 0x192, "offset": 0x10C9D7, "normal item": iname.red_jewel_s},
+ lname.tunnel_albert_quag: {"code": 0x193, "offset": 0x10C9CF, "normal item": iname.red_jewel_l},
+ lname.tunnel_gondola_rc_sdoor_l: {"code": 0x53, "offset": 0x10CA5F, "normal item": iname.cross,
+ "add conds": ["sub"]},
+ lname.tunnel_gondola_rc_sdoor_m: {"code": 0x19E, "offset": 0x10CAA7, "normal item": iname.roast_beef,
+ "hard item": iname.one_hundred_gold},
+ lname.tunnel_gondola_rc_sdoor_r: {"code": 0x188, "offset": 0x10CA27, "normal item": iname.roast_beef,
+ "hard item": iname.one_hundred_gold},
+ lname.tunnel_gondola_rc: {"code": 0x19C, "offset": 0x10CAB7, "normal item": iname.powerup},
+ lname.tunnel_rgondola_station: {"code": 0x194, "offset": 0x10C9C7, "normal item": iname.red_jewel_s},
+ lname.tunnel_gondola_transfer: {"code": 0x186, "offset": 0x10CA2F, "normal item": iname.five_hundred_gold},
+ lname.tunnel_corpse_bucket_quag: {"code": 0x18E, "offset": 0x10C9F7, "normal item": iname.red_jewel_s},
+ lname.tunnel_corpse_bucket_mdoor_l: {"code": 0x52, "offset": 0x10CA6F, "normal item": iname.holy_water,
+ "add conds": ["sub"]},
+ lname.tunnel_corpse_bucket_mdoor_r: {"code": 0x185, "offset": 0x10CA37, "normal item": iname.sun_card,
+ "hard item": iname.one_hundred_gold},
+ lname.tunnel_shovel_quag_start: {"code": 0x18D, "offset": 0x10C9FF, "normal item": iname.red_jewel_l},
+ lname.tunnel_exit_quag_start: {"code": 0x18C, "offset": 0x10CA07, "normal item": iname.red_jewel_l},
+ lname.tunnel_shovel_quag_end: {"code": 0x18B, "offset": 0x10CA0F, "normal item": iname.red_jewel_l},
+ lname.tunnel_exit_quag_end: {"code": 0x184, "offset": 0x10CA3F, "normal item": iname.five_hundred_gold},
+ lname.tunnel_shovel: {"code": 0x18F, "offset": 0x86D8FC, "normal item": iname.roast_beef,
+ "type": "inv"},
+ lname.tunnel_shovel_save: {"code": 0x18A, "offset": 0x10CA17, "normal item": iname.red_jewel_l},
+ lname.tunnel_shovel_mdoor_l: {"code": 0x183, "offset": 0x10CA47, "normal item": iname.sun_card,
+ "hard item": iname.one_hundred_gold},
+ lname.tunnel_shovel_mdoor_r: {"code": 0x51, "offset": 0x10CA77, "normal item": iname.axe,
+ "add conds": ["sub"]},
+ lname.tunnel_shovel_sdoor_l: {"code": 0x182, "offset": 0x10CA4F, "normal item": iname.moon_card},
+ lname.tunnel_shovel_sdoor_m: {"code": 0x19D, "offset": 0x10CAAF, "normal item": iname.roast_chicken},
+ lname.tunnel_shovel_sdoor_r: {"code": 0x50, "offset": 0x10CA7F, "normal item": iname.cross,
+ "add conds": ["sub"]},
+ # Underground Waterway
+ lname.uw_near_ent: {"code": 0x4C, "offset": 0x10CB03, "normal item": iname.three_hundred_gold},
+ lname.uw_across_ent: {"code": 0x4E, "offset": 0x10CAF3, "normal item": iname.five_hundred_gold},
+ lname.uw_first_ledge1: {"code": 0x242, "offset": 0x10CB39, "normal item": iname.five_hundred_gold,
+ "add conds": ["3hb"]},
+ lname.uw_first_ledge2: {"code": 0x243, "offset": 0x10CB3B, "normal item": iname.five_hundred_gold,
+ "add conds": ["3hb"]},
+ lname.uw_first_ledge3: {"code": 0x244, "offset": 0x10CB3D, "normal item": iname.purifying,
+ "hard item": iname.five_hundred_gold, "add conds": ["3hb"]},
+ lname.uw_first_ledge4: {"code": 0x245, "offset": 0x10CB3F, "normal item": iname.cure_ampoule,
+ "hard item": iname.five_hundred_gold, "add conds": ["3hb"]},
+ lname.uw_first_ledge5: {"code": 0x246, "offset": 0x10CB41, "normal item": iname.purifying,
+ "hard item": iname.three_hundred_gold, "add conds": ["3hb"]},
+ lname.uw_first_ledge6: {"code": 0x247, "offset": 0x10CB43, "normal item": iname.cure_ampoule,
+ "hard item": iname.one_hundred_gold, "add conds": ["3hb"]},
+ lname.uw_poison_parkour: {"code": 0x4D, "offset": 0x10CAFB, "normal item": iname.cure_ampoule},
+ lname.uw_boss: {"event": iname.trophy, "add conds": ["boss"]},
+ lname.uw_waterfall_alcove: {"code": 0x57, "offset": 0x10CB23, "normal item": iname.five_hundred_gold},
+ lname.uw_carrie1: {"code": 0x4B, "offset": 0x10CB0B, "normal item": iname.moon_card,
+ "hard item": iname.five_hundred_gold, "add conds": ["carrie"]},
+ lname.uw_carrie2: {"code": 0x4A, "offset": 0x10CB13, "normal item": iname.roast_beef,
+ "hard item": iname.five_hundred_gold, "add conds": ["carrie"]},
+ lname.uw_bricks_save: {"code": 0x5A, "offset": 0x10CB33, "normal item": iname.powerup,
+ "hard item": iname.one_hundred_gold},
+ lname.uw_above_skel_ledge: {"code": 0x56, "offset": 0x10CB2B, "normal item": iname.roast_chicken},
+ lname.uw_in_skel_ledge1: {"code": 0x249, "offset": 0x10CB45, "normal item": iname.roast_chicken,
+ "add conds": ["3hb"]},
+ lname.uw_in_skel_ledge2: {"code": 0x24A, "offset": 0x10CB47, "normal item": iname.roast_chicken,
+ "add conds": ["3hb"]},
+ lname.uw_in_skel_ledge3: {"code": 0x24B, "offset": 0x10CB49, "normal item": iname.roast_chicken,
+ "add conds": ["3hb"]},
+ # Castle Center
+ lname.ccb_skel_hallway_ent: {"code": 0x1AF, "offset": 0x10CB67, "normal item": iname.red_jewel_s},
+ lname.ccb_skel_hallway_jun: {"code": 0x1A8, "offset": 0x10CBD7, "normal item": iname.powerup},
+ lname.ccb_skel_hallway_tc: {"code": 0x1AE, "offset": 0x10CB6F, "normal item": iname.red_jewel_l},
+ lname.ccb_skel_hallway_ba: {"code": 0x1B6, "offset": 0x10CBC7, "normal item": iname.cross,
+ "add conds": ["sub"]},
+ lname.ccb_behemoth_l_ff: {"code": 0x1AD, "offset": 0x10CB77, "normal item": iname.red_jewel_s},
+ lname.ccb_behemoth_l_mf: {"code": 0x1B3, "offset": 0x10CBA7, "normal item": iname.three_hundred_gold,
+ "hard item": iname.one_hundred_gold},
+ lname.ccb_behemoth_l_mr: {"code": 0x1AC, "offset": 0x10CB7F, "normal item": iname.red_jewel_l},
+ lname.ccb_behemoth_l_fr: {"code": 0x1B2, "offset": 0x10CBAF, "normal item": iname.three_hundred_gold,
+ "hard item": iname.one_hundred_gold},
+ lname.ccb_behemoth_r_ff: {"code": 0x1B1, "offset": 0x10CBB7, "normal item": iname.three_hundred_gold,
+ "hard item": iname.one_hundred_gold},
+ lname.ccb_behemoth_r_mf: {"code": 0x1AB, "offset": 0x10CB87, "normal item": iname.red_jewel_s},
+ lname.ccb_behemoth_r_mr: {"code": 0x1B0, "offset": 0x10CBBF, "normal item": iname.three_hundred_gold,
+ "hard item": iname.one_hundred_gold},
+ lname.ccb_behemoth_r_fr: {"code": 0x1AA, "offset": 0x10CB8F, "normal item": iname.red_jewel_l},
+ lname.ccb_behemoth_crate1: {"code": 0x24D, "offset": 0x10CBDD, "normal item": iname.five_hundred_gold,
+ "add conds": ["3hb"]},
+ lname.ccb_behemoth_crate2: {"code": 0x24E, "offset": 0x10CBDF, "normal item": iname.five_hundred_gold,
+ "add conds": ["3hb"]},
+ lname.ccb_behemoth_crate3: {"code": 0x24F, "offset": 0x10CBE1, "normal item": iname.five_hundred_gold,
+ "add conds": ["3hb"]},
+ lname.ccb_behemoth_crate4: {"code": 0x250, "offset": 0x10CBE3, "normal item": iname.five_hundred_gold,
+ "add conds": ["3hb"]},
+ lname.ccb_behemoth_crate5: {"code": 0x251, "offset": 0x10CBE5, "normal item": iname.five_hundred_gold,
+ "add conds": ["3hb"]},
+ lname.ccelv_near_machine: {"code": 0x11A, "offset": 0x10CBF7, "normal item": iname.red_jewel_s},
+ lname.ccelv_atop_machine: {"code": 0x118, "offset": 0x10CC17, "normal item": iname.powerup,
+ "hard item": iname.three_hundred_gold},
+ lname.ccelv_stand1: {"code": 0x253, "offset": 0x10CC1D, "normal item": iname.roast_beef,
+ "add conds": ["3hb"]},
+ lname.ccelv_stand2: {"code": 0x254, "offset": 0x10CC1F, "normal item": iname.roast_beef,
+ "hard item": iname.three_hundred_gold, "add conds": ["3hb"]},
+ lname.ccelv_stand3: {"code": 0x255, "offset": 0x10CC21, "normal item": iname.roast_beef,
+ "hard item": iname.one_hundred_gold, "add conds": ["3hb"]},
+ lname.ccelv_pipes: {"code": 0x11B, "offset": 0x10CC07, "normal item": iname.one_hundred_gold},
+ lname.ccelv_switch: {"code": 0x100, "offset": 0x10CC0F, "normal item": iname.holy_water,
+ "add conds": ["sub"]},
+ lname.ccelv_staircase: {"code": 0x119, "offset": 0x10CBFF, "normal item": iname.red_jewel_l,
+ "hard item": iname.five_hundred_gold},
+ lname.ccff_redcarpet_knight: {"code": 0x10A, "offset": 0x8C44D9, "normal item": iname.red_jewel_l,
+ "hard item": iname.red_jewel_s, "type": "inv"},
+ lname.ccff_gears_side: {"code": 0x10F, "offset": 0x10CC33, "normal item": iname.red_jewel_s},
+ lname.ccff_gears_mid: {"code": 0x10E, "offset": 0x10CC3B, "normal item": iname.purifying,
+ "hard item": iname.one_hundred_gold},
+ lname.ccff_gears_corner: {"code": 0x10D, "offset": 0x10CC43, "normal item": iname.roast_chicken,
+ "hard item": iname.one_hundred_gold},
+ lname.ccff_lizard_knight: {"code": 0x109, "offset": 0x8C44E7, "normal item": iname.roast_chicken,
+ "hard item": iname.three_hundred_gold, "type": "inv"},
+ lname.ccff_lizard_near_knight: {"code": 0x101, "offset": 0x10CC5B, "normal item": iname.axe,
+ "add conds": ["sub"]},
+ lname.ccff_lizard_pit: {"code": 0x10C, "offset": 0x10CC4B, "normal item": iname.sun_card,
+ "hard item": iname.five_hundred_gold},
+ lname.ccff_lizard_corner: {"code": 0x10B, "offset": 0x10CC53, "normal item": iname.moon_card,
+ "hard item": iname.five_hundred_gold},
+ lname.ccff_lizard_locker_nfr: {"code": 0x104, "offset": 0x8C450A, "normal item": iname.red_jewel_l,
+ "add conds": ["liz"]},
+ lname.ccff_lizard_locker_nmr: {"code": 0x105, "offset": 0xBFC9C3, "normal item": iname.five_hundred_gold,
+ "add conds": ["liz"]},
+ lname.ccff_lizard_locker_nml: {"code": 0x106, "offset": 0xBFC9C7, "normal item": iname.red_jewel_l,
+ "hard item": iname.cure_ampoule, "add conds": ["liz"]},
+ lname.ccff_lizard_locker_nfl: {"code": 0x107, "offset": 0xBFCA07, "normal item": iname.powerup,
+ "add conds": ["liz"]},
+ lname.ccff_lizard_locker_fl: {"code": 0x102, "offset": 0xBFCA03, "normal item": iname.five_hundred_gold,
+ "add conds": ["liz"]},
+ lname.ccff_lizard_locker_fr: {"code": 0x103, "offset": 0x8C44F5, "normal item": iname.sun_card,
+ "hard item": iname.three_hundred_gold, "add conds": ["liz"]},
+ lname.ccff_lizard_slab1: {"code": 0x257, "offset": 0x10CC61, "normal item": iname.purifying,
+ "hard item": iname.roast_chicken, "add conds": ["3hb"]},
+ lname.ccff_lizard_slab2: {"code": 0x258, "offset": 0x10CC63, "normal item": iname.purifying,
+ "hard item": iname.powerup, "add conds": ["3hb"]},
+ lname.ccff_lizard_slab3: {"code": 0x259, "offset": 0x10CC65, "normal item": iname.cure_ampoule,
+ "hard item": iname.one_hundred_gold, "add conds": ["3hb"]},
+ lname.ccff_lizard_slab4: {"code": 0x25A, "offset": 0x10CC67, "normal item": iname.cure_ampoule,
+ "hard item": iname.one_hundred_gold, "add conds": ["3hb"]},
+ lname.ccb_mandrag_shelf_l: {"code": 0x1A0, "offset": 0xBFCBB3, "normal item": iname.mandragora},
+ lname.ccb_mandrag_shelf_r: {"code": 0x1A1, "offset": 0xBFCBAF, "normal item": iname.mandragora},
+ lname.ccb_torture_rack: {"code": 0x1A9, "offset": 0x8985E5, "normal item": iname.purifying,
+ "type": "inv"},
+ lname.ccb_torture_rafters: {"code": 0x1A2, "offset": 0x8985D6, "normal item": iname.roast_beef},
+ lname.cc_behind_the_seal: {"event": iname.crystal, "add conds": ["crystal"]},
+ lname.cc_boss_one: {"event": iname.trophy, "add conds": ["boss"]},
+ lname.cc_boss_two: {"event": iname.trophy, "add conds": ["boss"]},
+ lname.ccll_brokenstairs_floor: {"code": 0x7B, "offset": 0x10CC8F, "normal item": iname.red_jewel_l,
+ "countdown": 14},
+ lname.ccll_brokenstairs_knight: {"code": 0x74, "offset": 0x8DF782, "normal item": iname.roast_beef,
+ "hard item": iname.one_hundred_gold, "type": "inv", "countdown": 14},
+ lname.ccll_brokenstairs_save: {"code": 0x7C, "offset": 0x10CC87, "normal item": iname.red_jewel_l,
+ "countdown": 14},
+ lname.ccll_glassknight_l: {"code": 0x7A, "offset": 0x10CC97, "normal item": iname.red_jewel_s,
+ "hard item": iname.five_hundred_gold, "countdown": 14},
+ lname.ccll_glassknight_r: {"code": 0x7E, "offset": 0x10CC77, "normal item": iname.red_jewel_s,
+ "hard item": iname.five_hundred_gold, "countdown": 14},
+ lname.ccll_butlers_door: {"code": 0x7D, "offset": 0x10CC7F, "normal item": iname.red_jewel_s,
+ "countdown": 14},
+ lname.ccll_butlers_side: {"code": 0x79, "offset": 0x10CC9F, "normal item": iname.purifying,
+ "hard item": iname.one_hundred_gold, "countdown": 14},
+ lname.ccll_cwhall_butlerflames_past: {"code": 0x78, "offset": 0x10CCA7, "normal item": iname.cure_ampoule,
+ "hard item": iname.red_jewel_l, "countdown": 14},
+ lname.ccll_cwhall_flamethrower: {"code": 0x73, "offset": 0x8DF580, "normal item": iname.five_hundred_gold,
+ "type": "inv", "countdown": 14},
+ lname.ccll_cwhall_cwflames: {"code": 0x77, "offset": 0x10CCAF, "normal item": iname.roast_chicken,
+ "hard item": iname.red_jewel_l, "countdown": 14},
+ lname.ccll_heinrich: {"code": 0x69, "offset": 0xBFE443, "normal item": iname.chamber_key,
+ "type": "npc", "countdown": 14},
+ lname.ccia_nitro_crates: {"code": 0x66, "offset": 0x90FCE9, "normal item": iname.healing_kit,
+ "hard item": iname.one_hundred_gold, "type": "inv", "countdown": 14},
+ lname.ccia_nitro_shelf_h: {"code": 0x55, "offset": 0xBFCC03, "normal item": iname.magical_nitro,
+ "countdown": 14},
+ lname.ccia_stairs_knight: {"code": 0x61, "offset": 0x90FE5C, "normal item": iname.five_hundred_gold,
+ "type": "inv", "countdown": 14},
+ lname.ccia_maids_vase: {"code": 0x63, "offset": 0x90FF1D, "normal item": iname.red_jewel_l,
+ "type": "inv", "countdown": 14},
+ lname.ccia_maids_outer: {"code": 0x6B, "offset": 0x10CCFF, "normal item": iname.purifying,
+ "hard item": iname.three_hundred_gold, "countdown": 14},
+ lname.ccia_maids_inner: {"code": 0x6A, "offset": 0x10CD07, "normal item": iname.cure_ampoule,
+ "hard item": iname.three_hundred_gold, "countdown": 14},
+ lname.ccia_inventions_maids: {"code": 0x6C, "offset": 0x10CCE7, "normal item": iname.moon_card,
+ "hard item": iname.one_hundred_gold, "countdown": 14},
+ lname.ccia_inventions_crusher: {"code": 0x6E, "offset": 0x10CCDF, "normal item": iname.sun_card,
+ "hard item": iname.one_hundred_gold, "countdown": 14},
+ lname.ccia_inventions_famicart: {"code": 0x64, "offset": 0x90FBB3, "normal item": iname.five_hundred_gold,
+ "type": "inv", "countdown": 14},
+ lname.ccia_inventions_zeppelin: {"code": 0x6D, "offset": 0x90FBC0, "normal item": iname.roast_beef,
+ "countdown": 14},
+ lname.ccia_inventions_round: {"code": 0x65, "offset": 0x90FBA7, "normal item": iname.roast_beef,
+ "hard item": iname.five_hundred_gold, "type": "inv", "countdown": 14},
+ lname.ccia_nitrohall_flamethrower: {"code": 0x62, "offset": 0x90FCDA, "normal item": iname.red_jewel_l,
+ "type": "inv", "countdown": 14},
+ lname.ccia_nitrohall_torch: {"code": 0x6F, "offset": 0x10CCD7, "normal item": iname.roast_chicken,
+ "hard item": iname.red_jewel_s, "countdown": 14},
+ lname.ccia_nitro_shelf_i: {"code": 0x60, "offset": 0xBFCBFF, "normal item": iname.magical_nitro,
+ "countdown": 14},
+ lname.ccll_cwhall_wall: {"code": 0x76, "offset": 0x10CCB7, "normal item": iname.roast_beef,
+ "hard item": iname.one_hundred_gold, "countdown": 14},
+ lname.ccl_bookcase: {"code": 0x166, "offset": 0x8F1197, "normal item": iname.sun_card,
+ "countdown": 14},
+ # Duel Tower
+ lname.dt_boss_one: {"event": iname.trophy, "add conds": ["boss"]},
+ lname.dt_boss_two: {"event": iname.trophy, "add conds": ["boss"]},
+ lname.dt_ibridge_l: {"code": 0x81, "offset": 0x10CE8B, "normal item": iname.roast_beef,
+ "hard item": iname.five_hundred_gold},
+ lname.dt_ibridge_r: {"code": 0x80, "offset": 0x10CE93, "normal item": iname.powerup},
+ lname.dt_stones_start: {"code": 0x83, "offset": 0x10CE73, "normal item": iname.roast_chicken,
+ "hard item": iname.five_hundred_gold},
+ lname.dt_stones_end: {"code": 0x97, "offset": 0x10CE83, "normal item": iname.knife, "add conds": ["sub"]},
+ lname.dt_werebull_arena: {"code": 0x82, "offset": 0x10CE7B, "normal item": iname.roast_beef},
+ lname.dt_boss_three: {"event": iname.trophy, "add conds": ["boss"]},
+ lname.dt_boss_four: {"event": iname.trophy, "add conds": ["boss"]},
+ # Tower of Execution
+ lname.toe_ledge1: {"code": 0x25C, "offset": 0x10CD5D, "normal item": iname.red_jewel_l,
+ "add conds": ["3hb"]},
+ lname.toe_ledge2: {"code": 0x25D, "offset": 0x10CD5F, "normal item": iname.purifying,
+ "hard item": iname.red_jewel_s, "add conds": ["3hb"]},
+ lname.toe_ledge3: {"code": 0x25E, "offset": 0x10CD61, "normal item": iname.five_hundred_gold,
+ "hard item": iname.red_jewel_s, "add conds": ["3hb"]},
+ lname.toe_ledge4: {"code": 0x25F, "offset": 0x10CD63, "normal item": iname.cure_ampoule,
+ "hard item": iname.five_hundred_gold, "add conds": ["3hb"]},
+ lname.toe_ledge5: {"code": 0x260, "offset": 0x10CD65, "normal item": iname.holy_water,
+ "add conds": ["3hb", "sub"]},
+ lname.toe_midsavespikes_r: {"code": 0x9C, "offset": 0x10CD1F, "normal item": iname.five_hundred_gold},
+ lname.toe_midsavespikes_l: {"code": 0x9B, "offset": 0x10CD27, "normal item": iname.roast_chicken,
+ "hard item": iname.five_hundred_gold},
+ lname.toe_elec_grate: {"code": 0x99, "offset": 0x10CD17, "normal item": iname.execution_key},
+ lname.toe_ibridge: {"code": 0x98, "offset": 0x10CD47, "normal item": iname.one_hundred_gold},
+ lname.toe_top: {"code": 0x9D, "offset": 0x10CD4F, "normal item": iname.red_jewel_l},
+ lname.toe_keygate_l: {"code": 0x9A, "offset": 0x10CD37, "normal item": iname.roast_beef,
+ "hard item": iname.one_hundred_gold},
+ lname.toe_keygate_r: {"code": 0x9E, "offset": 0x10CD3F, "normal item": iname.cross, "add conds": ["sub"]},
+ # Tower of Science
+ lname.tosci_elevator: {"code": 0x1FC, "offset": 0x10CE0B, "normal item": iname.three_hundred_gold},
+ lname.tosci_plain_sr: {"code": 0x1FF, "offset": 0x10CDF3, "normal item": iname.science_key1},
+ lname.tosci_stairs_sr: {"code": 0x1FB, "offset": 0x10CE13, "normal item": iname.three_hundred_gold},
+ lname.tosci_three_door_hall: {"code": 0x1FE, "offset": 0x10CDFB, "normal item": iname.science_key2},
+ lname.tosci_ibridge_t: {"code": 0x1F3, "offset": 0x10CE3B, "normal item": iname.roast_beef,
+ "hard item": iname.red_jewel_l},
+ lname.tosci_ibridge_b1: {"code": 0x262, "offset": 0x10CE59, "normal item": iname.red_jewel_l,
+ "add conds": ["3hb"]},
+ lname.tosci_ibridge_b2: {"code": 0x263, "offset": 0x10CE5B, "normal item": iname.red_jewel_l,
+ "add conds": ["3hb"]},
+ lname.tosci_ibridge_b3: {"code": 0x264, "offset": 0x10CE5D, "normal item": iname.five_hundred_gold,
+ "add conds": ["3hb"]},
+ lname.tosci_ibridge_b4: {"code": 0x265, "offset": 0x10CE5F, "normal item": iname.five_hundred_gold,
+ "add conds": ["3hb"]},
+ lname.tosci_ibridge_b5: {"code": 0x266, "offset": 0x10CE61, "normal item": iname.roast_chicken,
+ "add conds": ["3hb"]},
+ lname.tosci_ibridge_b6: {"code": 0x267, "offset": 0x10CE63, "normal item": iname.roast_chicken,
+ "hard item": iname.one_hundred_gold, "add conds": ["3hb"]},
+ lname.tosci_conveyor_sr: {"code": 0x1F7, "offset": 0x10CE33, "normal item": iname.red_jewel_l,
+ "hard item": iname.red_jewel_s},
+ lname.tosci_exit: {"code": 0x1FD, "offset": 0x10CE03, "normal item": iname.science_key3},
+ lname.tosci_key3_r: {"code": 0x1FA, "offset": 0x10CE1B, "normal item": iname.five_hundred_gold},
+ lname.tosci_key3_m: {"code": 0x1F2, "offset": 0x10CE2B, "normal item": iname.cross, "add conds": ["sub"]},
+ lname.tosci_key3_l: {"code": 0x1F9, "offset": 0x10CE23, "normal item": iname.five_hundred_gold},
+ # Tower of Sorcery
+ lname.tosor_stained_tower: {"code": 0x96, "offset": 0x10CDB3, "normal item": iname.red_jewel_l},
+ lname.tosor_savepoint: {"code": 0x95, "offset": 0x10CDBB, "normal item": iname.red_jewel_l},
+ lname.tosor_trickshot: {"code": 0x92, "offset": 0x10CDD3, "normal item": iname.roast_beef},
+ lname.tosor_yellow_bubble: {"code": 0x91, "offset": 0x10CDDB, "normal item": iname.five_hundred_gold},
+ lname.tosor_blue_platforms: {"code": 0x94, "offset": 0x10CDC3, "normal item": iname.red_jewel_s},
+ lname.tosor_side_isle: {"code": 0x93, "offset": 0x10CDCB, "normal item": iname.red_jewel_s},
+ lname.tosor_ibridge: {"code": 0x90, "offset": 0x10CDE3, "normal item": iname.three_hundred_gold},
+ # Room of Clocks
+ lname.roc_ent_l: {"code": 0xC6, "offset": 0x10CF7B, "normal item": iname.roast_beef,
+ "hard item": iname.red_jewel_l},
+ lname.roc_ent_r: {"code": 0xC3, "offset": 0x10CFBB, "normal item": iname.powerup,
+ "hard item": iname.five_hundred_gold},
+ lname.roc_elev_r: {"code": 0xD4, "offset": 0x10CF93, "normal item": iname.holy_water, "add conds": ["sub"]},
+ lname.roc_elev_l: {"code": 0xD5, "offset": 0x10CF8B, "normal item": iname.axe, "add conds": ["sub"]},
+ lname.roc_cont_r: {"code": 0xC5, "offset": 0x10CFB3, "normal item": iname.powerup,
+ "hard item": iname.one_hundred_gold},
+ lname.roc_cont_l: {"code": 0xDF, "offset": 0x10CFA3, "normal item": iname.three_hundred_gold,
+ "add conds": ["empty"]},
+ lname.roc_exit: {"code": 0xDC, "offset": 0x10CF9B, "normal item": iname.three_hundred_gold,
+ "add conds": ["empty"]},
+ lname.roc_boss: {"event": iname.trophy, "add conds": ["boss"]},
+ # Clock Tower
+ lname.ct_gearclimb_battery_slab1: {"code": 0x269, "offset": 0x10CEF9, "normal item": iname.roast_chicken,
+ "add conds": ["3hb"]},
+ lname.ct_gearclimb_battery_slab2: {"code": 0x26A, "offset": 0x10CEFB, "normal item": iname.roast_chicken,
+ "hard item": iname.red_jewel_s, "add conds": ["3hb"]},
+ lname.ct_gearclimb_battery_slab3: {"code": 0x26B, "offset": 0x10CEFD, "normal item": iname.roast_chicken,
+ "hard item": iname.red_jewel_s, "add conds": ["3hb"]},
+ lname.ct_gearclimb_corner: {"code": 0xA7, "offset": 0x10CEB3, "normal item": iname.red_jewel_s},
+ lname.ct_gearclimb_side: {"code": 0xAD, "offset": 0x10CEC3, "normal item": iname.clocktower_key1},
+ lname.ct_gearclimb_door_slab1: {"code": 0x26D, "offset": 0x10CF01, "normal item": iname.roast_beef,
+ "add conds": ["3hb"]},
+ lname.ct_gearclimb_door_slab2: {"code": 0x26E, "offset": 0x10CF03, "normal item": iname.roast_beef,
+ "hard item": iname.one_hundred_gold, "add conds": ["3hb"]},
+ lname.ct_gearclimb_door_slab3: {"code": 0x26F, "offset": 0x10CF05, "normal item": iname.roast_beef,
+ "hard item": iname.one_hundred_gold, "add conds": ["3hb"]},
+ lname.ct_bp_chasm_fl: {"code": 0xA5, "offset": 0x99BC4D, "normal item": iname.five_hundred_gold},
+ lname.ct_bp_chasm_fr: {"code": 0xA6, "offset": 0x99BC3E, "normal item": iname.red_jewel_l},
+ lname.ct_bp_chasm_rl: {"code": 0xA4, "offset": 0x99BC5A, "normal item": iname.holy_water,
+ "add conds": ["sub"]},
+ lname.ct_bp_chasm_k: {"code": 0xAC, "offset": 0x99BC30, "normal item": iname.clocktower_key2},
+ lname.ct_finalroom_door_slab1: {"code": 0x271, "offset": 0x10CEF5, "normal item": iname.five_hundred_gold,
+ "add conds": ["3hb"]},
+ lname.ct_finalroom_door_slab2: {"code": 0x272, "offset": 0x10CEF7, "normal item": iname.five_hundred_gold,
+ "add conds": ["3hb"]},
+ lname.ct_finalroom_fl: {"code": 0xB3, "offset": 0x10CED3, "normal item": iname.axe,
+ "add conds": ["sub"]},
+ lname.ct_finalroom_fr: {"code": 0xB4, "offset": 0x10CECB, "normal item": iname.knife,
+ "add conds": ["sub"]},
+ lname.ct_finalroom_rl: {"code": 0xB2, "offset": 0x10CEE3, "normal item": iname.holy_water,
+ "add conds": ["sub"]},
+ lname.ct_finalroom_rr: {"code": 0xB0, "offset": 0x10CEDB, "normal item": iname.cross,
+ "add conds": ["sub"]},
+ lname.ct_finalroom_platform: {"code": 0xAB, "offset": 0x10CEBB, "normal item": iname.clocktower_key3},
+ lname.ct_finalroom_renon_slab1: {"code": 0x274, "offset": 0x10CF09, "normal item": iname.five_hundred_gold,
+ "add conds": ["3hb"]},
+ lname.ct_finalroom_renon_slab2: {"code": 0x275, "offset": 0x10CF0B, "normal item": iname.five_hundred_gold,
+ "add conds": ["3hb"]},
+ lname.ct_finalroom_renon_slab3: {"code": 0x276, "offset": 0x10CF0D, "normal item": iname.five_hundred_gold,
+ "add conds": ["3hb"]},
+ lname.ct_finalroom_renon_slab4: {"code": 0x277, "offset": 0x10CF0F, "normal item": iname.five_hundred_gold,
+ "add conds": ["3hb"]},
+ lname.ct_finalroom_renon_slab5: {"code": 0x278, "offset": 0x10CF11, "normal item": iname.five_hundred_gold,
+ "add conds": ["3hb"]},
+ lname.ct_finalroom_renon_slab6: {"code": 0x279, "offset": 0x10CF13, "normal item": iname.five_hundred_gold,
+ "add conds": ["3hb"]},
+ lname.ct_finalroom_renon_slab7: {"code": 0x27A, "offset": 0x10CF15, "normal item": iname.red_jewel_l,
+ "add conds": ["3hb"]},
+ lname.ct_finalroom_renon_slab8: {"code": 0x27B, "offset": 0x10CF17, "normal item": iname.red_jewel_l,
+ "add conds": ["3hb"]},
+ # Castle Keep
+ lname.ck_boss_one: {"event": iname.trophy, "add conds": ["boss", "renon"]},
+ lname.ck_boss_two: {"event": iname.trophy, "add conds": ["boss", "vincent"]},
+ lname.ck_flame_l: {"code": 0xAF, "offset": 0x9778C8, "normal item": iname.healing_kit, "type": "inv"},
+ lname.ck_flame_r: {"code": 0xAE, "offset": 0xBFCA67, "normal item": iname.healing_kit, "type": "inv"},
+ lname.ck_behind_drac: {"code": 0xBF, "offset": 0x10CE9B, "normal item": iname.red_jewel_l},
+ lname.ck_cube: {"code": 0xB5, "offset": 0x10CEA3, "normal item": iname.healing_kit},
+ lname.renon1: {"code": 0x1C8, "offset": 0xBFD8E5, "normal item": iname.roast_chicken, "type": "shop"},
+ lname.renon2: {"code": 0x1C9, "offset": 0xBFD8E7, "normal item": iname.roast_beef, "type": "shop"},
+ lname.renon3: {"code": 0x1CA, "offset": 0xBFD8E9, "normal item": iname.healing_kit, "type": "shop"},
+ lname.renon4: {"code": 0x1CB, "offset": 0xBFD8EB, "normal item": iname.purifying, "type": "shop"},
+ lname.renon5: {"code": 0x1CC, "offset": 0xBFD8ED, "normal item": iname.cure_ampoule, "type": "shop"},
+ lname.renon6: {"code": 0x1CD, "offset": 0xBFD907, "normal item": iname.sun_card, "type": "shop"},
+ lname.renon7: {"code": 0x1CE, "offset": 0xBFD909, "normal item": iname.moon_card, "type": "shop"},
+ lname.the_end: {"event": iname.victory},
+}
+
+
+add_conds = {"carrie": ("carrie_logic", True, True),
+ "liz": ("lizard_locker_items", True, True),
+ "sub": ("sub_weapon_shuffle", SubWeaponShuffle.option_anywhere, True),
+ "3hb": ("multi_hit_breakables", True, True),
+ "empty": ("empty_breakables", True, True),
+ "shop": ("shopsanity", True, True),
+ "crystal": ("draculas_condition", DraculasCondition.option_crystal, True),
+ "boss": ("draculas_condition", DraculasCondition.option_bosses, True),
+ "renon": ("renon_fight_condition", RenonFightCondition.option_never, False),
+ "vincent": ("vincent_fight_condition", VincentFightCondition.option_never, False)}
+
+
+def get_location_info(location: str, info: str) -> Union[int, str, List[str], None]:
+ return location_info[location].get(info, None)
+
+
+def get_location_names_to_ids() -> Dict[str, int]:
+ return {name: get_location_info(name, "code")+base_id for name in location_info if get_location_info(name, "code")
+ is not None}
+
+
+def verify_locations(options: CV64Options, locations: List[str]) -> Tuple[Dict[str, Optional[int]], Dict[str, str]]:
+
+ verified_locations = {}
+ events = {}
+
+ for loc in locations:
+ loc_add_conds = get_location_info(loc, "add conds")
+ loc_code = get_location_info(loc, "code")
+
+ # Check any options that might be associated with the Location before adding it.
+ add_it = True
+ if isinstance(loc_add_conds, list):
+ for cond in loc_add_conds:
+ if not ((getattr(options, add_conds[cond][0]).value == add_conds[cond][1]) == add_conds[cond][2]):
+ add_it = False
+
+ if not add_it:
+ continue
+
+ # Add the location to the verified Locations if the above check passes.
+ # If we are looking at an event Location, add its associated event Item to the events' dict.
+ # Otherwise, add the base_id to the Location's code.
+ if loc_code is None:
+ events[loc] = get_location_info(loc, "event")
+ else:
+ loc_code += base_id
+ verified_locations.update({loc: loc_code})
+
+ return verified_locations, events
diff --git a/worlds/cv64/lzkn64.py b/worlds/cv64/lzkn64.py
new file mode 100644
index 00000000000..9a94cebbb4e
--- /dev/null
+++ b/worlds/cv64/lzkn64.py
@@ -0,0 +1,266 @@
+# **************************************************************
+# * LZKN64 Compression and Decompression Utility *
+# * Original repo at https://github.com/Fluvian/lzkn64, *
+# * converted from C to Python with permission from Fluvian. *
+# **************************************************************
+
+TYPE_COMPRESS = 1
+TYPE_DECOMPRESS = 2
+
+MODE_NONE = 0x7F
+MODE_WINDOW_COPY = 0x00
+MODE_RAW_COPY = 0x80
+MODE_RLE_WRITE_A = 0xC0
+MODE_RLE_WRITE_B = 0xE0
+MODE_RLE_WRITE_C = 0xFF
+
+WINDOW_SIZE = 0x3FF
+COPY_SIZE = 0x21
+RLE_SIZE = 0x101
+
+
+# Compresses the data in the buffer specified in the arguments.
+def compress_buffer(file_buffer: bytearray) -> bytearray:
+ # Size of the buffer to compress
+ buffer_size = len(file_buffer) - 1
+
+ # Position of the current read location in the buffer.
+ buffer_position = 0
+
+ # Position of the current write location in the written buffer.
+ write_position = 4
+
+ # Allocate write_buffer with size of 0xFFFFFF (24-bit).
+ write_buffer = bytearray(0xFFFFFF)
+
+ # Position in the input buffer of the last time one of the copy modes was used.
+ buffer_last_copy_position = 0
+
+ while buffer_position < buffer_size:
+ # Calculate maximum length we are able to copy without going out of bounds.
+ if COPY_SIZE < (buffer_size - 1) - buffer_position:
+ sliding_window_maximum_length = COPY_SIZE
+ else:
+ sliding_window_maximum_length = (buffer_size - 1) - buffer_position
+
+ # Calculate how far we are able to look back without going behind the start of the uncompressed buffer.
+ if buffer_position - WINDOW_SIZE > 0:
+ sliding_window_maximum_offset = buffer_position - WINDOW_SIZE
+ else:
+ sliding_window_maximum_offset = 0
+
+ # Calculate maximum length the forwarding looking window is able to search.
+ if RLE_SIZE < (buffer_size - 1) - buffer_position:
+ forward_window_maximum_length = RLE_SIZE
+ else:
+ forward_window_maximum_length = (buffer_size - 1) - buffer_position
+
+ sliding_window_match_position = -1
+ sliding_window_match_size = 0
+
+ forward_window_match_value = 0
+ forward_window_match_size = 0
+
+ # The current mode the compression algorithm prefers. (0x7F == None)
+ current_mode = MODE_NONE
+
+ # The current submode the compression algorithm prefers.
+ current_submode = MODE_NONE
+
+ # How many bytes will have to be copied in the raw copy command.
+ raw_copy_size = buffer_position - buffer_last_copy_position
+
+ # How many bytes we still have to copy in RLE matches with more than 0x21 bytes.
+ rle_bytes_left = 0
+
+ """Go backwards in the buffer, is there a matching value?
+ If yes, search forward and check for more matching values in a loop.
+ If no, go further back and repeat."""
+ for search_position in range(buffer_position - 1, sliding_window_maximum_offset - 1, -1):
+ matching_sequence_size = 0
+
+ while file_buffer[search_position + matching_sequence_size] == file_buffer[buffer_position +
+ matching_sequence_size]:
+ matching_sequence_size += 1
+
+ if matching_sequence_size >= sliding_window_maximum_length:
+ break
+
+ # Once we find a match or a match that is bigger than the match before it, we save its position and length.
+ if matching_sequence_size > sliding_window_match_size:
+ sliding_window_match_position = search_position
+ sliding_window_match_size = matching_sequence_size
+
+ """Look one step forward in the buffer, is there a matching value?
+ If yes, search further and check for a repeating value in a loop.
+ If no, continue to the rest of the function."""
+ matching_sequence_value = file_buffer[buffer_position]
+ matching_sequence_size = 0
+
+ while file_buffer[buffer_position + matching_sequence_size] == matching_sequence_value:
+ matching_sequence_size += 1
+
+ if matching_sequence_size >= forward_window_maximum_length:
+ break
+
+ # If we find a sequence of matching values, save them.
+ if matching_sequence_size >= 1:
+ forward_window_match_value = matching_sequence_value
+ forward_window_match_size = matching_sequence_size
+
+ # Try to pick which mode works best with the current values.
+ if sliding_window_match_size >= 3:
+ current_mode = MODE_WINDOW_COPY
+ elif forward_window_match_size >= 3:
+ current_mode = MODE_RLE_WRITE_A
+
+ if forward_window_match_value != 0x00 and forward_window_match_size <= COPY_SIZE:
+ current_submode = MODE_RLE_WRITE_A
+ elif forward_window_match_value != 0x00 and forward_window_match_size > COPY_SIZE:
+ current_submode = MODE_RLE_WRITE_A
+ rle_bytes_left = forward_window_match_size
+ elif forward_window_match_value == 0x00 and forward_window_match_size <= COPY_SIZE:
+ current_submode = MODE_RLE_WRITE_B
+ elif forward_window_match_value == 0x00 and forward_window_match_size > COPY_SIZE:
+ current_submode = MODE_RLE_WRITE_C
+ elif forward_window_match_size >= 2 and forward_window_match_value == 0x00:
+ current_mode = MODE_RLE_WRITE_A
+ current_submode = MODE_RLE_WRITE_B
+
+ """Write a raw copy command when these following conditions are met:
+ The current mode is set and there are raw bytes available to be copied.
+ The raw byte length exceeds the maximum length that can be stored.
+ Raw bytes need to be written due to the proximity to the end of the buffer."""
+ if (current_mode != MODE_NONE and raw_copy_size >= 1) or raw_copy_size >= 0x1F or \
+ (buffer_position + 1) == buffer_size:
+ if buffer_position + 1 == buffer_size:
+ raw_copy_size = buffer_size - buffer_last_copy_position
+
+ write_buffer[write_position] = MODE_RAW_COPY | raw_copy_size & 0x1F
+ write_position += 1
+
+ for written_bytes in range(raw_copy_size):
+ write_buffer[write_position] = file_buffer[buffer_last_copy_position]
+ write_position += 1
+ buffer_last_copy_position += 1
+
+ if current_mode == MODE_WINDOW_COPY:
+ write_buffer[write_position] = MODE_WINDOW_COPY | ((sliding_window_match_size - 2) & 0x1F) << 2 | \
+ (((buffer_position - sliding_window_match_position) & 0x300) >> 8)
+ write_position += 1
+ write_buffer[write_position] = (buffer_position - sliding_window_match_position) & 0xFF
+ write_position += 1
+
+ buffer_position += sliding_window_match_size
+ buffer_last_copy_position = buffer_position
+ elif current_mode == MODE_RLE_WRITE_A:
+ if current_submode == MODE_RLE_WRITE_A:
+ if rle_bytes_left > 0:
+ while rle_bytes_left > 0:
+ # Dump raw bytes if we have less than two bytes left, not doing so would cause an underflow
+ # error.
+ if rle_bytes_left < 2:
+ write_buffer[write_position] = MODE_RAW_COPY | rle_bytes_left & 0x1F
+ write_position += 1
+
+ for writtenBytes in range(rle_bytes_left):
+ write_buffer[write_position] = forward_window_match_value & 0xFF
+ write_position += 1
+
+ rle_bytes_left = 0
+ break
+
+ if rle_bytes_left < COPY_SIZE:
+ write_buffer[write_position] = MODE_RLE_WRITE_A | (rle_bytes_left - 2) & 0x1F
+ write_position += 1
+ else:
+ write_buffer[write_position] = MODE_RLE_WRITE_A | (COPY_SIZE - 2) & 0x1F
+ write_position += 1
+ write_buffer[write_position] = forward_window_match_value & 0xFF
+ write_position += 1
+ rle_bytes_left -= COPY_SIZE
+ else:
+ write_buffer[write_position] = MODE_RLE_WRITE_A | (forward_window_match_size - 2) & 0x1F
+ write_position += 1
+ write_buffer[write_position] = forward_window_match_value & 0xFF
+ write_position += 1
+
+ elif current_submode == MODE_RLE_WRITE_B:
+ write_buffer[write_position] = MODE_RLE_WRITE_B | (forward_window_match_size - 2) & 0x1F
+ write_position += 1
+ elif current_submode == MODE_RLE_WRITE_C:
+ write_buffer[write_position] = MODE_RLE_WRITE_C
+ write_position += 1
+ write_buffer[write_position] = (forward_window_match_size - 2) & 0xFF
+ write_position += 1
+
+ buffer_position += forward_window_match_size
+ buffer_last_copy_position = buffer_position
+ else:
+ buffer_position += 1
+
+ # Write the compressed size.
+ write_buffer[1] = 0x00
+ write_buffer[1] = write_position >> 16 & 0xFF
+ write_buffer[2] = write_position >> 8 & 0xFF
+ write_buffer[3] = write_position & 0xFF
+
+ # Return the compressed write buffer.
+ return write_buffer[0:write_position]
+
+
+# Decompresses the data in the buffer specified in the arguments.
+def decompress_buffer(file_buffer: bytearray) -> bytearray:
+ # Position of the current read location in the buffer.
+ buffer_position = 4
+
+ # Position of the current write location in the written buffer.
+ write_position = 0
+
+ # Get compressed size.
+ compressed_size = (file_buffer[1] << 16) + (file_buffer[2] << 8) + file_buffer[3] - 1
+
+ # Allocate writeBuffer with size of 0xFFFFFF (24-bit).
+ write_buffer = bytearray(0xFFFFFF)
+
+ while buffer_position < compressed_size:
+ mode_command = file_buffer[buffer_position]
+ buffer_position += 1
+
+ if MODE_WINDOW_COPY <= mode_command < MODE_RAW_COPY:
+ copy_length = (mode_command >> 2) + 2
+ copy_offset = file_buffer[buffer_position] + (mode_command << 8) & 0x3FF
+ buffer_position += 1
+
+ for current_length in range(copy_length, 0, -1):
+ write_buffer[write_position] = write_buffer[write_position - copy_offset]
+ write_position += 1
+ elif MODE_RAW_COPY <= mode_command < MODE_RLE_WRITE_A:
+ copy_length = mode_command & 0x1F
+
+ for current_length in range(copy_length, 0, -1):
+ write_buffer[write_position] = file_buffer[buffer_position]
+ write_position += 1
+ buffer_position += 1
+ elif MODE_RLE_WRITE_A <= mode_command <= MODE_RLE_WRITE_C:
+ write_length = 0
+ write_value = 0x00
+
+ if MODE_RLE_WRITE_A <= mode_command < MODE_RLE_WRITE_B:
+ write_length = (mode_command & 0x1F) + 2
+ write_value = file_buffer[buffer_position]
+ buffer_position += 1
+ elif MODE_RLE_WRITE_B <= mode_command < MODE_RLE_WRITE_C:
+ write_length = (mode_command & 0x1F) + 2
+ elif mode_command == MODE_RLE_WRITE_C:
+ write_length = file_buffer[buffer_position] + 2
+ buffer_position += 1
+
+ for current_length in range(write_length, 0, -1):
+ write_buffer[write_position] = write_value
+ write_position += 1
+
+ # Return the current position of the write buffer, essentially giving us the size of the write buffer.
+ while write_position % 16 != 0:
+ write_position += 1
+ return write_buffer[0:write_position]
diff --git a/worlds/cv64/options.py b/worlds/cv64/options.py
new file mode 100644
index 00000000000..4545cd0b5c2
--- /dev/null
+++ b/worlds/cv64/options.py
@@ -0,0 +1,490 @@
+from dataclasses import dataclass
+from Options import Choice, DefaultOnToggle, Range, Toggle, PerGameCommonOptions, StartInventoryPool
+
+
+class CharacterStages(Choice):
+ """Whether to include Reinhardt-only stages, Carrie-only stages, or both with or without branching paths at the end
+ of Villa and Castle Center."""
+ display_name = "Character Stages"
+ option_both = 0
+ option_branchless_both = 1
+ option_reinhardt_only = 2
+ option_carrie_only = 3
+ default = 0
+
+
+class StageShuffle(Toggle):
+ """Shuffles which stages appear in which stage slots. Villa and Castle Center will never appear in any character
+ stage slots if Character Stages is set to Both; they can only be somewhere on the main path.
+ Castle Keep will always be at the end of the line."""
+ display_name = "Stage Shuffle"
+
+
+class StartingStage(Choice):
+ """Which stage to start at if Stage Shuffle is turned on."""
+ display_name = "Starting Stage"
+ option_forest_of_silence = 0
+ option_castle_wall = 1
+ option_villa = 2
+ option_tunnel = 3
+ option_underground_waterway = 4
+ option_castle_center = 5
+ option_duel_tower = 6
+ option_tower_of_execution = 7
+ option_tower_of_science = 8
+ option_tower_of_sorcery = 9
+ option_room_of_clocks = 10
+ option_clock_tower = 11
+ default = "random"
+
+
+class WarpOrder(Choice):
+ """Arranges the warps in the warp menu in whichever stage order chosen,
+ thereby changing the order they are unlocked in."""
+ display_name = "Warp Order"
+ option_seed_stage_order = 0
+ option_vanilla_stage_order = 1
+ option_randomized_order = 2
+ default = 0
+
+
+class SubWeaponShuffle(Choice):
+ """Shuffles all sub-weapons in the game within each other in their own pool or in the main item pool."""
+ display_name = "Sub-weapon Shuffle"
+ option_off = 0
+ option_own_pool = 1
+ option_anywhere = 2
+ default = 0
+
+
+class SpareKeys(Choice):
+ """Puts an additional copy of every non-Special key item in the pool for every key item that there is.
+ Chance gives each key item a 50% chance of having a duplicate instead of guaranteeing one for all of them."""
+ display_name = "Spare Keys"
+ option_off = 0
+ option_on = 1
+ option_chance = 2
+ default = 0
+
+
+class HardItemPool(Toggle):
+ """Replaces some items in the item pool with less valuable ones, to make the item pool sort of resemble Hard Mode
+ in the PAL version."""
+ display_name = "Hard Item Pool"
+
+
+class Special1sPerWarp(Range):
+ """Sets how many Special1 jewels are needed per warp menu option unlock."""
+ range_start = 1
+ range_end = 10
+ default = 1
+ display_name = "Special1s Per Warp"
+
+
+class TotalSpecial1s(Range):
+ """Sets how many Speical1 jewels are in the pool in total.
+ If this is set to be less than Special1s Per Warp x 7, it will decrease by 1 until it isn't."""
+ range_start = 7
+ range_end = 70
+ default = 7
+ display_name = "Total Special1s"
+
+
+class DraculasCondition(Choice):
+ """Sets the requirement for unlocking and opening the door to Dracula's chamber.
+ None: No requirement. Door is unlocked from the start.
+ Crystal: Activate the big crystal in Castle Center's basement. Neither boss afterwards has to be defeated.
+ Bosses: Kill a specified number of bosses with health bars and claim their Trophies.
+ Specials: Find a specified number of Special2 jewels shuffled in the main item pool."""
+ display_name = "Dracula's Condition"
+ option_none = 0
+ option_crystal = 1
+ option_bosses = 2
+ option_specials = 3
+ default = 1
+
+
+class PercentSpecial2sRequired(Range):
+ """Percentage of Special2s required to enter Dracula's chamber when Dracula's Condition is Special2s."""
+ range_start = 1
+ range_end = 100
+ default = 80
+ display_name = "Percent Special2s Required"
+
+
+class TotalSpecial2s(Range):
+ """How many Speical2 jewels are in the pool in total when Dracula's Condition is Special2s."""
+ range_start = 1
+ range_end = 70
+ default = 25
+ display_name = "Total Special2s"
+
+
+class BossesRequired(Range):
+ """How many bosses need to be defeated to enter Dracula's chamber when Dracula's Condition is set to Bosses.
+ This will automatically adjust if there are fewer available bosses than the chosen number."""
+ range_start = 1
+ range_end = 16
+ default = 14
+ display_name = "Bosses Required"
+
+
+class CarrieLogic(Toggle):
+ """Adds the 2 checks inside Underground Waterway's crawlspace to the pool.
+ If you (and everyone else if racing the same seed) are planning to only ever play Reinhardt, don't enable this.
+ Can be combined with Hard Logic to include Carrie-only tricks."""
+ display_name = "Carrie Logic"
+
+
+class HardLogic(Toggle):
+ """Properly considers sequence break tricks in logic (i.e. maze skip). Can be combined with Carrie Logic to include
+ Carrie-only tricks.
+ See the Game Page for a full list of tricks and glitches that may be logically required."""
+ display_name = "Hard Logic"
+
+
+class MultiHitBreakables(Toggle):
+ """Adds the items that drop from the objects that break in three hits to the pool. There are 17 of these throughout
+ the game, adding up to 74 checks in total with all stages.
+ The game will be modified to
+ remember exactly which of their items you've picked up instead of simply whether they were broken or not."""
+ display_name = "Multi-hit Breakables"
+
+
+class EmptyBreakables(Toggle):
+ """Adds 9 check locations in the form of breakables that normally have nothing (all empty Forest coffins, etc.)
+ and some additional Red Jewels and/or moneybags into the item pool to compensate."""
+ display_name = "Empty Breakables"
+
+
+class LizardLockerItems(Toggle):
+ """Adds the 6 items inside Castle Center 2F's Lizard-man generators to the pool.
+ Picking up all of these can be a very tedious luck-based process, so they are off by default."""
+ display_name = "Lizard Locker Items"
+
+
+class Shopsanity(Toggle):
+ """Adds 7 one-time purchases from Renon's shop into the location pool. After buying an item from a slot, it will
+ revert to whatever it is in the vanilla game."""
+ display_name = "Shopsanity"
+
+
+class ShopPrices(Choice):
+ """Randomizes the amount of gold each item costs in Renon's shop.
+ Use the below options to control how much or little an item can cost."""
+ display_name = "Shop Prices"
+ option_vanilla = 0
+ option_randomized = 1
+ default = 0
+
+
+class MinimumGoldPrice(Range):
+ """The lowest amount of gold an item can cost in Renon's shop, divided by 100."""
+ display_name = "Minimum Gold Price"
+ range_start = 1
+ range_end = 50
+ default = 2
+
+
+class MaximumGoldPrice(Range):
+ """The highest amount of gold an item can cost in Renon's shop, divided by 100."""
+ display_name = "Maximum Gold Price"
+ range_start = 1
+ range_end = 50
+ default = 30
+
+
+class PostBehemothBoss(Choice):
+ """Sets which boss is fought in the vampire triplets' room in Castle Center by which characters after defeating
+ Behemoth."""
+ display_name = "Post-Behemoth Boss"
+ option_vanilla = 0
+ option_inverted = 1
+ option_always_rosa = 2
+ option_always_camilla = 3
+ default = 0
+
+
+class RoomOfClocksBoss(Choice):
+ """Sets which boss is fought at Room of Clocks by which characters."""
+ display_name = "Room of Clocks Boss"
+ option_vanilla = 0
+ option_inverted = 1
+ option_always_death = 2
+ option_always_actrise = 3
+ default = 0
+
+
+class RenonFightCondition(Choice):
+ """Sets the condition on which the Renon fight will trigger."""
+ display_name = "Renon Fight Condition"
+ option_never = 0
+ option_spend_30k = 1
+ option_always = 2
+ default = 1
+
+
+class VincentFightCondition(Choice):
+ """Sets the condition on which the vampire Vincent fight will trigger."""
+ display_name = "Vincent Fight Condition"
+ option_never = 0
+ option_wait_16_days = 1
+ option_always = 2
+ default = 1
+
+
+class BadEndingCondition(Choice):
+ """Sets the condition on which the currently-controlled character's Bad Ending will trigger."""
+ display_name = "Bad Ending Condition"
+ option_never = 0
+ option_kill_vincent = 1
+ option_always = 2
+ default = 1
+
+
+class IncreaseItemLimit(DefaultOnToggle):
+ """Increases the holding limit of usable items from 10 to 99 of each item."""
+ display_name = "Increase Item Limit"
+
+
+class NerfHealingItems(Toggle):
+ """Decreases the amount of health healed by Roast Chickens to 25%, Roast Beefs to 50%, and Healing Kits to 80%."""
+ display_name = "Nerf Healing Items"
+
+
+class LoadingZoneHeals(DefaultOnToggle):
+ """Whether end-of-level loading zones restore health and cure status aliments or not.
+ Recommended off for those looking for more of a survival horror experience!"""
+ display_name = "Loading Zone Heals"
+
+
+class InvisibleItems(Choice):
+ """Sets which items are visible in their locations and which are invisible until picked up.
+ 'Chance' gives each item a 50/50 chance of being visible or invisible."""
+ display_name = "Invisible Items"
+ option_vanilla = 0
+ option_reveal_all = 1
+ option_hide_all = 2
+ option_chance = 3
+ default = 0
+
+
+class DropPreviousSubWeapon(Toggle):
+ """When receiving a sub-weapon, the one you had before will drop behind you, so it can be taken back if desired."""
+ display_name = "Drop Previous Sub-weapon"
+
+
+class PermanentPowerUps(Toggle):
+ """Replaces PowerUps with PermaUps, which upgrade your B weapon level permanently and will stay even after
+ dying and/or continuing.
+ To compensate, only two will be in the pool overall, and they will not drop from any enemy or projectile."""
+ display_name = "Permanent PowerUps"
+
+
+class IceTrapPercentage(Range):
+ """Replaces a percentage of junk items with Ice Traps.
+ These will be visibly disguised as other items, and receiving one will freeze you
+ as if you were hit by Camilla's ice cloud attack."""
+ display_name = "Ice Trap Percentage"
+ range_start = 0
+ range_end = 100
+ default = 0
+
+
+class IceTrapAppearance(Choice):
+ """What items Ice Traps can possibly be disguised as."""
+ display_name = "Ice Trap Appearance"
+ option_major_only = 0
+ option_junk_only = 1
+ option_anything = 2
+ default = 0
+
+
+class DisableTimeRestrictions(Toggle):
+ """Disables the restriction on every event and door that requires the current time
+ to be within a specific range, so they can be triggered at any time.
+ This includes all sun/moon doors and, in the Villa, the meeting with Rosa and the fountain pillar.
+ The Villa coffin is not affected by this."""
+ display_name = "Disable Time Requirements"
+
+
+class SkipGondolas(Toggle):
+ """Makes jumping on and activating a gondola in Tunnel instantly teleport you
+ to the other station, thereby skipping the entire three-minute ride.
+ The item normally at the gondola transfer point is moved to instead be
+ near the red gondola at its station."""
+ display_name = "Skip Gondolas"
+
+
+class SkipWaterwayBlocks(Toggle):
+ """Opens the door to the third switch in Underground Waterway from the start so that the jumping across floating
+ brick platforms won't have to be done. Shopping at the Contract on the other side of them may still be logically
+ required if Shopsanity is on."""
+ display_name = "Skip Waterway Blocks"
+
+
+class Countdown(Choice):
+ """Displays, near the HUD clock and below the health bar, the number of unobtained progression-marked items
+ or the total check locations remaining in the stage you are currently in."""
+ display_name = "Countdown"
+ option_none = 0
+ option_majors = 1
+ option_all_locations = 2
+ default = 0
+
+
+class BigToss(Toggle):
+ """Makes every non-immobilizing damage source launch you as if you got hit by Behemoth's charge.
+ Press A while tossed to cancel the launch momentum and avoid being thrown off ledges.
+ Hold Z to have all incoming damage be treated as it normally would.
+ Any tricks that might be possible with it are NOT considered in logic on any setting."""
+ display_name = "Big Toss"
+
+
+class PantherDash(Choice):
+ """Hold C-right at any time to sprint way faster. Any tricks that might be
+ possible with it are NOT considered in logic on any setting and any boss
+ fights with boss health meters, if started, are expected to be finished
+ before leaving their arenas if Dracula's Condition is bosses. Jumpless will
+ prevent jumping while moving at the increased speed to ensure logic cannot be broken with it."""
+ display_name = "Panther Dash"
+ option_off = 0
+ option_on = 1
+ option_jumpless = 2
+ default = 0
+
+
+class IncreaseShimmySpeed(Toggle):
+ """Increases the speed at which characters shimmy left and right while hanging on ledges."""
+ display_name = "Increase Shimmy Speed"
+
+
+class FallGuard(Toggle):
+ """Removes fall damage from landing too hard. Note that falling for too long will still result in instant death."""
+ display_name = "Fall Guard"
+
+
+class BackgroundMusic(Choice):
+ """Randomizes or disables the music heard throughout the game.
+ Randomized music is split into two pools: songs that loop and songs that don't.
+ The "lead-in" versions of some songs will be paired accordingly."""
+ display_name = "Background Music"
+ option_normal = 0
+ option_disabled = 1
+ option_randomized = 2
+ default = 0
+
+
+class MapLighting(Choice):
+ """Randomizes the lighting color RGB values on every map during every time of day to be literally anything.
+ The colors and/or shading of the following things are affected: fog, maps, player, enemies, and some objects."""
+ display_name = "Map Lighting"
+ option_normal = 0
+ option_randomized = 1
+ default = 0
+
+
+class CinematicExperience(Toggle):
+ """Enables an unused film reel effect on every cutscene in the game. Purely cosmetic."""
+ display_name = "Cinematic Experience"
+
+
+class WindowColorR(Range):
+ """The red value for the background color of the text windows during gameplay."""
+ display_name = "Window Color R"
+ range_start = 0
+ range_end = 15
+ default = 1
+
+
+class WindowColorG(Range):
+ """The green value for the background color of the text windows during gameplay."""
+ display_name = "Window Color G"
+ range_start = 0
+ range_end = 15
+ default = 5
+
+
+class WindowColorB(Range):
+ """The blue value for the background color of the text windows during gameplay."""
+ display_name = "Window Color B"
+ range_start = 0
+ range_end = 15
+ default = 15
+
+
+class WindowColorA(Range):
+ """The alpha value for the background color of the text windows during gameplay."""
+ display_name = "Window Color A"
+ range_start = 0
+ range_end = 15
+ default = 8
+
+
+class DeathLink(Choice):
+ """When you die, everyone dies. Of course the reverse is true too.
+ Explosive: Makes received DeathLinks kill you via the Magical Nitro explosion
+ instead of the normal death animation."""
+ display_name = "DeathLink"
+ option_off = 0
+ alias_no = 0
+ alias_true = 1
+ alias_yes = 1
+ option_on = 1
+ option_explosive = 2
+
+
+@dataclass
+class CV64Options(PerGameCommonOptions):
+ character_stages: CharacterStages
+ stage_shuffle: StageShuffle
+ starting_stage: StartingStage
+ warp_order: WarpOrder
+ sub_weapon_shuffle: SubWeaponShuffle
+ spare_keys: SpareKeys
+ hard_item_pool: HardItemPool
+ special1s_per_warp: Special1sPerWarp
+ total_special1s: TotalSpecial1s
+ draculas_condition: DraculasCondition
+ percent_special2s_required: PercentSpecial2sRequired
+ total_special2s: TotalSpecial2s
+ bosses_required: BossesRequired
+ carrie_logic: CarrieLogic
+ hard_logic: HardLogic
+ multi_hit_breakables: MultiHitBreakables
+ empty_breakables: EmptyBreakables
+ lizard_locker_items: LizardLockerItems
+ shopsanity: Shopsanity
+ shop_prices: ShopPrices
+ minimum_gold_price: MinimumGoldPrice
+ maximum_gold_price: MaximumGoldPrice
+ post_behemoth_boss: PostBehemothBoss
+ room_of_clocks_boss: RoomOfClocksBoss
+ renon_fight_condition: RenonFightCondition
+ vincent_fight_condition: VincentFightCondition
+ bad_ending_condition: BadEndingCondition
+ increase_item_limit: IncreaseItemLimit
+ nerf_healing_items: NerfHealingItems
+ loading_zone_heals: LoadingZoneHeals
+ invisible_items: InvisibleItems
+ drop_previous_sub_weapon: DropPreviousSubWeapon
+ permanent_powerups: PermanentPowerUps
+ ice_trap_percentage: IceTrapPercentage
+ ice_trap_appearance: IceTrapAppearance
+ disable_time_restrictions: DisableTimeRestrictions
+ skip_gondolas: SkipGondolas
+ skip_waterway_blocks: SkipWaterwayBlocks
+ countdown: Countdown
+ big_toss: BigToss
+ panther_dash: PantherDash
+ increase_shimmy_speed: IncreaseShimmySpeed
+ background_music: BackgroundMusic
+ map_lighting: MapLighting
+ fall_guard: FallGuard
+ cinematic_experience: CinematicExperience
+ window_color_r: WindowColorR
+ window_color_g: WindowColorG
+ window_color_b: WindowColorB
+ window_color_a: WindowColorA
+ death_link: DeathLink
+ start_inventory_from_pool: StartInventoryPool
diff --git a/worlds/cv64/regions.py b/worlds/cv64/regions.py
new file mode 100644
index 00000000000..2194828a19a
--- /dev/null
+++ b/worlds/cv64/regions.py
@@ -0,0 +1,517 @@
+from .data import lname, rname, ename
+from typing import List, Union
+
+
+# # # KEY # # #
+# "stage" = What stage the Region is a part of. The Region and its corresponding Locations and Entrances will only be
+# put in if its stage is active.
+# "locations" = The Locations to add to that Region when putting in said Region (provided their add conditions pass).
+# "entrances" = The Entrances to add to that Region when putting in said Region (provided their add conditions pass).
+region_info = {
+ "Menu": {},
+
+ rname.forest_start: {"stage": rname.forest_of_silence,
+ "locations": [lname.forest_pillars_right,
+ lname.forest_pillars_left,
+ lname.forest_pillars_top,
+ lname.forest_king_skeleton,
+ lname.forest_boss_one,
+ lname.forest_lgaz_in,
+ lname.forest_lgaz_top,
+ lname.forest_hgaz_in,
+ lname.forest_hgaz_top,
+ lname.forest_weretiger_sw,
+ lname.forest_boss_two,
+ lname.forest_weretiger_gate,
+ lname.forest_dirge_tomb_l,
+ lname.forest_dirge_tomb_u,
+ lname.forest_dirge_plaque,
+ lname.forest_dirge_ped,
+ lname.forest_dirge_rock1,
+ lname.forest_dirge_rock2,
+ lname.forest_dirge_rock3,
+ lname.forest_dirge_rock4,
+ lname.forest_dirge_rock5,
+ lname.forest_corpse_save,
+ lname.forest_dbridge_wall,
+ lname.forest_dbridge_sw],
+ "entrances": [ename.forest_dbridge_gate]},
+
+ rname.forest_mid: {"stage": rname.forest_of_silence,
+ "locations": [lname.forest_dbridge_gate_l,
+ lname.forest_dbridge_gate_r,
+ lname.forest_dbridge_tomb_l,
+ lname.forest_dbridge_tomb_ur,
+ lname.forest_dbridge_tomb_uf,
+ lname.forest_bface_tomb_lf,
+ lname.forest_bface_tomb_lr,
+ lname.forest_bface_tomb_u,
+ lname.forest_ibridge,
+ lname.forest_bridge_rock1,
+ lname.forest_bridge_rock2,
+ lname.forest_bridge_rock3,
+ lname.forest_bridge_rock4,
+ lname.forest_werewolf_tomb_lf,
+ lname.forest_werewolf_tomb_lr,
+ lname.forest_werewolf_tomb_r,
+ lname.forest_werewolf_plaque,
+ lname.forest_werewolf_tree,
+ lname.forest_werewolf_island,
+ lname.forest_final_sw],
+ "entrances": [ename.forest_werewolf_gate]},
+
+ rname.forest_end: {"stage": rname.forest_of_silence,
+ "locations": [lname.forest_boss_three],
+ "entrances": [ename.forest_end]},
+
+ rname.cw_start: {"stage": rname.castle_wall,
+ "locations": [lname.cwr_bottom,
+ lname.cw_dragon_sw,
+ lname.cw_boss,
+ lname.cw_save_slab1,
+ lname.cw_save_slab2,
+ lname.cw_save_slab3,
+ lname.cw_save_slab4,
+ lname.cw_save_slab5,
+ lname.cw_rrampart,
+ lname.cw_lrampart,
+ lname.cw_pillar,
+ lname.cw_shelf_visible,
+ lname.cw_shelf_sandbags,
+ lname.cw_shelf_torch],
+ "entrances": [ename.cw_portcullis_c,
+ ename.cw_lt_skip,
+ ename.cw_lt_door]},
+
+ rname.cw_exit: {"stage": rname.castle_wall,
+ "locations": [lname.cw_ground_left,
+ lname.cw_ground_middle,
+ lname.cw_ground_right]},
+
+ rname.cw_ltower: {"stage": rname.castle_wall,
+ "locations": [lname.cwl_bottom,
+ lname.cwl_bridge,
+ lname.cw_drac_sw,
+ lname.cw_drac_slab1,
+ lname.cw_drac_slab2,
+ lname.cw_drac_slab3,
+ lname.cw_drac_slab4,
+ lname.cw_drac_slab5],
+ "entrances": [ename.cw_end]},
+
+ rname.villa_start: {"stage": rname.villa,
+ "locations": [lname.villafy_outer_gate_l,
+ lname.villafy_outer_gate_r,
+ lname.villafy_dog_platform,
+ lname.villafy_inner_gate],
+ "entrances": [ename.villa_dog_gates]},
+
+ rname.villa_main: {"stage": rname.villa,
+ "locations": [lname.villafy_gate_marker,
+ lname.villafy_villa_marker,
+ lname.villafy_tombstone,
+ lname.villafy_fountain_fl,
+ lname.villafy_fountain_fr,
+ lname.villafy_fountain_ml,
+ lname.villafy_fountain_mr,
+ lname.villafy_fountain_rl,
+ lname.villafy_fountain_rr,
+ lname.villafo_front_r,
+ lname.villafo_front_l,
+ lname.villafo_mid_l,
+ lname.villafo_mid_r,
+ lname.villafo_rear_r,
+ lname.villafo_rear_l,
+ lname.villafo_pot_r,
+ lname.villafo_pot_l,
+ lname.villafo_sofa,
+ lname.villafo_chandelier1,
+ lname.villafo_chandelier2,
+ lname.villafo_chandelier3,
+ lname.villafo_chandelier4,
+ lname.villafo_chandelier5,
+ lname.villala_hallway_stairs,
+ lname.villala_hallway_l,
+ lname.villala_hallway_r,
+ lname.villala_bedroom_chairs,
+ lname.villala_bedroom_bed,
+ lname.villala_vincent,
+ lname.villala_slivingroom_table,
+ lname.villala_slivingroom_mirror,
+ lname.villala_diningroom_roses,
+ lname.villala_llivingroom_pot_r,
+ lname.villala_llivingroom_pot_l,
+ lname.villala_llivingroom_painting,
+ lname.villala_llivingroom_light,
+ lname.villala_llivingroom_lion,
+ lname.villala_exit_knight],
+ "entrances": [ename.villa_snipe_dogs,
+ ename.villa_renon,
+ ename.villa_to_storeroom,
+ ename.villa_to_archives,
+ ename.villa_to_maze]},
+
+ rname.villa_storeroom: {"stage": rname.villa,
+ "locations": [lname.villala_storeroom_l,
+ lname.villala_storeroom_r,
+ lname.villala_storeroom_s],
+ "entrances": [ename.villa_from_storeroom]},
+
+ rname.villa_archives: {"stage": rname.villa,
+ "locations": [lname.villala_archives_entrance,
+ lname.villala_archives_table,
+ lname.villala_archives_rear]},
+
+ rname.villa_maze: {"stage": rname.villa,
+ "locations": [lname.villam_malus_torch,
+ lname.villam_malus_bush,
+ lname.villam_fplatform,
+ lname.villam_frankieturf_l,
+ lname.villam_frankieturf_r,
+ lname.villam_frankieturf_ru,
+ lname.villam_fgarden_f,
+ lname.villam_fgarden_mf,
+ lname.villam_fgarden_mr,
+ lname.villam_fgarden_r,
+ lname.villam_rplatform,
+ lname.villam_rplatform_de,
+ lname.villam_exit_de,
+ lname.villam_serv_path],
+ "entrances": [ename.villa_from_maze,
+ ename.villa_copper_door,
+ ename.villa_copper_skip]},
+
+ rname.villa_servants: {"stage": rname.villa,
+ "locations": [lname.villafo_serv_ent],
+ "entrances": [ename.villa_servant_door]},
+
+ rname.villa_crypt: {"stage": rname.villa,
+ "locations": [lname.villam_crypt_ent,
+ lname.villam_crypt_upstream,
+ lname.villac_ent_l,
+ lname.villac_ent_r,
+ lname.villac_wall_l,
+ lname.villac_wall_r,
+ lname.villac_coffin_l,
+ lname.villac_coffin_r,
+ lname.villa_boss_one,
+ lname.villa_boss_two],
+ "entrances": [ename.villa_bridge_door,
+ ename.villa_end_r,
+ ename.villa_end_c]},
+
+ rname.tunnel_start: {"stage": rname.tunnel,
+ "locations": [lname.tunnel_landing,
+ lname.tunnel_landing_rc,
+ lname.tunnel_stone_alcove_r,
+ lname.tunnel_stone_alcove_l,
+ lname.tunnel_twin_arrows,
+ lname.tunnel_arrows_rock1,
+ lname.tunnel_arrows_rock2,
+ lname.tunnel_arrows_rock3,
+ lname.tunnel_arrows_rock4,
+ lname.tunnel_arrows_rock5,
+ lname.tunnel_lonesome_bucket,
+ lname.tunnel_lbucket_mdoor_l,
+ lname.tunnel_lbucket_quag,
+ lname.tunnel_bucket_quag_rock1,
+ lname.tunnel_bucket_quag_rock2,
+ lname.tunnel_bucket_quag_rock3,
+ lname.tunnel_lbucket_albert,
+ lname.tunnel_albert_camp,
+ lname.tunnel_albert_quag,
+ lname.tunnel_gondola_rc_sdoor_l,
+ lname.tunnel_gondola_rc_sdoor_m,
+ lname.tunnel_gondola_rc_sdoor_r,
+ lname.tunnel_gondola_rc,
+ lname.tunnel_rgondola_station,
+ lname.tunnel_gondola_transfer],
+ "entrances": [ename.tunnel_start_renon,
+ ename.tunnel_gondolas]},
+
+ rname.tunnel_end: {"stage": rname.tunnel,
+ "locations": [lname.tunnel_corpse_bucket_quag,
+ lname.tunnel_corpse_bucket_mdoor_l,
+ lname.tunnel_corpse_bucket_mdoor_r,
+ lname.tunnel_shovel_quag_start,
+ lname.tunnel_exit_quag_start,
+ lname.tunnel_shovel_quag_end,
+ lname.tunnel_exit_quag_end,
+ lname.tunnel_shovel,
+ lname.tunnel_shovel_save,
+ lname.tunnel_shovel_mdoor_l,
+ lname.tunnel_shovel_mdoor_r,
+ lname.tunnel_shovel_sdoor_l,
+ lname.tunnel_shovel_sdoor_m,
+ lname.tunnel_shovel_sdoor_r],
+ "entrances": [ename.tunnel_end_renon,
+ ename.tunnel_end]},
+
+ rname.uw_main: {"stage": rname.underground_waterway,
+ "locations": [lname.uw_near_ent,
+ lname.uw_across_ent,
+ lname.uw_first_ledge1,
+ lname.uw_first_ledge2,
+ lname.uw_first_ledge3,
+ lname.uw_first_ledge4,
+ lname.uw_first_ledge5,
+ lname.uw_first_ledge6,
+ lname.uw_poison_parkour,
+ lname.uw_boss,
+ lname.uw_waterfall_alcove,
+ lname.uw_carrie1,
+ lname.uw_carrie2,
+ lname.uw_bricks_save,
+ lname.uw_above_skel_ledge,
+ lname.uw_in_skel_ledge1,
+ lname.uw_in_skel_ledge2,
+ lname.uw_in_skel_ledge3],
+ "entrances": [ename.uw_final_waterfall,
+ ename.uw_renon]},
+
+ rname.uw_end: {"stage": rname.underground_waterway,
+ "entrances": [ename.uw_waterfall_skip,
+ ename.uw_end]},
+
+ rname.cc_main: {"stage": rname.castle_center,
+ "locations": [lname.ccb_skel_hallway_ent,
+ lname.ccb_skel_hallway_jun,
+ lname.ccb_skel_hallway_tc,
+ lname.ccb_skel_hallway_ba,
+ lname.ccb_behemoth_l_ff,
+ lname.ccb_behemoth_l_mf,
+ lname.ccb_behemoth_l_mr,
+ lname.ccb_behemoth_l_fr,
+ lname.ccb_behemoth_r_ff,
+ lname.ccb_behemoth_r_mf,
+ lname.ccb_behemoth_r_mr,
+ lname.ccb_behemoth_r_fr,
+ lname.ccb_behemoth_crate1,
+ lname.ccb_behemoth_crate2,
+ lname.ccb_behemoth_crate3,
+ lname.ccb_behemoth_crate4,
+ lname.ccb_behemoth_crate5,
+ lname.ccelv_near_machine,
+ lname.ccelv_atop_machine,
+ lname.ccelv_stand1,
+ lname.ccelv_stand2,
+ lname.ccelv_stand3,
+ lname.ccelv_pipes,
+ lname.ccelv_switch,
+ lname.ccelv_staircase,
+ lname.ccff_redcarpet_knight,
+ lname.ccff_gears_side,
+ lname.ccff_gears_mid,
+ lname.ccff_gears_corner,
+ lname.ccff_lizard_knight,
+ lname.ccff_lizard_near_knight,
+ lname.ccff_lizard_pit,
+ lname.ccff_lizard_corner,
+ lname.ccff_lizard_locker_nfr,
+ lname.ccff_lizard_locker_nmr,
+ lname.ccff_lizard_locker_nml,
+ lname.ccff_lizard_locker_nfl,
+ lname.ccff_lizard_locker_fl,
+ lname.ccff_lizard_locker_fr,
+ lname.ccff_lizard_slab1,
+ lname.ccff_lizard_slab2,
+ lname.ccff_lizard_slab3,
+ lname.ccff_lizard_slab4,
+ lname.ccll_brokenstairs_floor,
+ lname.ccll_brokenstairs_knight,
+ lname.ccll_brokenstairs_save,
+ lname.ccll_glassknight_l,
+ lname.ccll_glassknight_r,
+ lname.ccll_butlers_door,
+ lname.ccll_butlers_side,
+ lname.ccll_cwhall_butlerflames_past,
+ lname.ccll_cwhall_flamethrower,
+ lname.ccll_cwhall_cwflames,
+ lname.ccll_heinrich,
+ lname.ccia_nitro_crates,
+ lname.ccia_nitro_shelf_h,
+ lname.ccia_stairs_knight,
+ lname.ccia_maids_vase,
+ lname.ccia_maids_outer,
+ lname.ccia_maids_inner,
+ lname.ccia_inventions_maids,
+ lname.ccia_inventions_crusher,
+ lname.ccia_inventions_famicart,
+ lname.ccia_inventions_zeppelin,
+ lname.ccia_inventions_round,
+ lname.ccia_nitrohall_flamethrower,
+ lname.ccia_nitrohall_torch,
+ lname.ccia_nitro_shelf_i],
+ "entrances": [ename.cc_tc_door,
+ ename.cc_lower_wall,
+ ename.cc_renon,
+ ename.cc_upper_wall]},
+
+ rname.cc_torture_chamber: {"stage": rname.castle_center,
+ "locations": [lname.ccb_mandrag_shelf_l,
+ lname.ccb_mandrag_shelf_r,
+ lname.ccb_torture_rack,
+ lname.ccb_torture_rafters]},
+
+ rname.cc_library: {"stage": rname.castle_center,
+ "locations": [lname.ccll_cwhall_wall,
+ lname.ccl_bookcase]},
+
+ rname.cc_crystal: {"stage": rname.castle_center,
+ "locations": [lname.cc_behind_the_seal,
+ lname.cc_boss_one,
+ lname.cc_boss_two],
+ "entrances": [ename.cc_elevator]},
+
+ rname.cc_elev_top: {"stage": rname.castle_center,
+ "entrances": [ename.cc_exit_r,
+ ename.cc_exit_c]},
+
+ rname.dt_main: {"stage": rname.duel_tower,
+ "locations": [lname.dt_boss_one,
+ lname.dt_boss_two,
+ lname.dt_ibridge_l,
+ lname.dt_ibridge_r,
+ lname.dt_stones_start,
+ lname.dt_stones_end,
+ lname.dt_werebull_arena,
+ lname.dt_boss_three,
+ lname.dt_boss_four],
+ "entrances": [ename.dt_start,
+ ename.dt_end]},
+
+ rname.toe_main: {"stage": rname.tower_of_execution,
+ "locations": [lname.toe_ledge1,
+ lname.toe_ledge2,
+ lname.toe_ledge3,
+ lname.toe_ledge4,
+ lname.toe_ledge5,
+ lname.toe_midsavespikes_r,
+ lname.toe_midsavespikes_l,
+ lname.toe_elec_grate,
+ lname.toe_ibridge,
+ lname.toe_top],
+ "entrances": [ename.toe_start,
+ ename.toe_gate,
+ ename.toe_gate_skip,
+ ename.toe_end]},
+
+ rname.toe_ledge: {"stage": rname.tower_of_execution,
+ "locations": [lname.toe_keygate_l,
+ lname.toe_keygate_r]},
+
+ rname.tosci_start: {"stage": rname.tower_of_science,
+ "locations": [lname.tosci_elevator,
+ lname.tosci_plain_sr,
+ lname.tosci_stairs_sr],
+ "entrances": [ename.tosci_start,
+ ename.tosci_key1_door,
+ ename.tosci_to_key2_door]},
+
+ rname.tosci_three_doors: {"stage": rname.tower_of_science,
+ "locations": [lname.tosci_three_door_hall]},
+
+ rname.tosci_conveyors: {"stage": rname.tower_of_science,
+ "locations": [lname.tosci_ibridge_t,
+ lname.tosci_ibridge_b1,
+ lname.tosci_ibridge_b2,
+ lname.tosci_ibridge_b3,
+ lname.tosci_ibridge_b4,
+ lname.tosci_ibridge_b5,
+ lname.tosci_ibridge_b6,
+ lname.tosci_conveyor_sr,
+ lname.tosci_exit],
+ "entrances": [ename.tosci_from_key2_door,
+ ename.tosci_key3_door,
+ ename.tosci_end]},
+
+ rname.tosci_key3: {"stage": rname.tower_of_science,
+ "locations": [lname.tosci_key3_r,
+ lname.tosci_key3_m,
+ lname.tosci_key3_l]},
+
+ rname.tosor_main: {"stage": rname.tower_of_sorcery,
+ "locations": [lname.tosor_stained_tower,
+ lname.tosor_savepoint,
+ lname.tosor_trickshot,
+ lname.tosor_yellow_bubble,
+ lname.tosor_blue_platforms,
+ lname.tosor_side_isle,
+ lname.tosor_ibridge],
+ "entrances": [ename.tosor_start,
+ ename.tosor_end]},
+
+ rname.roc_main: {"stage": rname.room_of_clocks,
+ "locations": [lname.roc_ent_l,
+ lname.roc_ent_r,
+ lname.roc_elev_r,
+ lname.roc_elev_l,
+ lname.roc_cont_r,
+ lname.roc_cont_l,
+ lname.roc_exit,
+ lname.roc_boss],
+ "entrances": [ename.roc_gate]},
+
+ rname.ct_start: {"stage": rname.clock_tower,
+ "locations": [lname.ct_gearclimb_battery_slab1,
+ lname.ct_gearclimb_battery_slab2,
+ lname.ct_gearclimb_battery_slab3,
+ lname.ct_gearclimb_side,
+ lname.ct_gearclimb_corner,
+ lname.ct_gearclimb_door_slab1,
+ lname.ct_gearclimb_door_slab2,
+ lname.ct_gearclimb_door_slab3],
+ "entrances": [ename.ct_to_door1]},
+
+ rname.ct_middle: {"stage": rname.clock_tower,
+ "locations": [lname.ct_bp_chasm_fl,
+ lname.ct_bp_chasm_fr,
+ lname.ct_bp_chasm_rl,
+ lname.ct_bp_chasm_k],
+ "entrances": [ename.ct_from_door1,
+ ename.ct_to_door2]},
+
+ rname.ct_end: {"stage": rname.clock_tower,
+ "locations": [lname.ct_finalroom_door_slab1,
+ lname.ct_finalroom_door_slab2,
+ lname.ct_finalroom_fl,
+ lname.ct_finalroom_fr,
+ lname.ct_finalroom_rl,
+ lname.ct_finalroom_rr,
+ lname.ct_finalroom_platform,
+ lname.ct_finalroom_renon_slab1,
+ lname.ct_finalroom_renon_slab2,
+ lname.ct_finalroom_renon_slab3,
+ lname.ct_finalroom_renon_slab4,
+ lname.ct_finalroom_renon_slab5,
+ lname.ct_finalroom_renon_slab6,
+ lname.ct_finalroom_renon_slab7,
+ lname.ct_finalroom_renon_slab8],
+ "entrances": [ename.ct_from_door2,
+ ename.ct_renon,
+ ename.ct_door_3]},
+
+ rname.ck_main: {"stage": rname.castle_keep,
+ "locations": [lname.ck_boss_one,
+ lname.ck_boss_two,
+ lname.ck_flame_l,
+ lname.ck_flame_r,
+ lname.ck_behind_drac,
+ lname.ck_cube],
+ "entrances": [ename.ck_slope_jump,
+ ename.ck_drac_door]},
+
+ rname.renon: {"locations": [lname.renon1,
+ lname.renon2,
+ lname.renon3,
+ lname.renon4,
+ lname.renon5,
+ lname.renon6,
+ lname.renon7]},
+
+ rname.ck_drac_chamber: {"locations": [lname.the_end]}
+}
+
+
+def get_region_info(region: str, info: str) -> Union[str, List[str], None]:
+ return region_info[region].get(info, None)
diff --git a/worlds/cv64/rom.py b/worlds/cv64/rom.py
new file mode 100644
index 00000000000..ab8c7030aa4
--- /dev/null
+++ b/worlds/cv64/rom.py
@@ -0,0 +1,959 @@
+
+import Utils
+
+from BaseClasses import Location
+from worlds.Files import APDeltaPatch
+from typing import List, Dict, Union, Iterable, Collection, TYPE_CHECKING
+
+import hashlib
+import os
+import pkgutil
+
+from . import lzkn64
+from .data import patches
+from .stages import get_stage_info
+from .text import cv64_string_to_bytearray, cv64_text_truncate, cv64_text_wrap
+from .aesthetics import renon_item_dialogue, get_item_text_color
+from .locations import get_location_info
+from .options import CharacterStages, VincentFightCondition, RenonFightCondition, PostBehemothBoss, RoomOfClocksBoss, \
+ BadEndingCondition, DeathLink, DraculasCondition, InvisibleItems, Countdown, PantherDash
+from settings import get_settings
+
+if TYPE_CHECKING:
+ from . import CV64World
+
+CV64US10HASH = "1cc5cf3b4d29d8c3ade957648b529dc1"
+ROM_PLAYER_LIMIT = 65535
+
+warp_map_offsets = [0xADF67, 0xADF77, 0xADF87, 0xADF97, 0xADFA7, 0xADFBB, 0xADFCB, 0xADFDF]
+
+
+class LocalRom:
+ orig_buffer: None
+ buffer: bytearray
+
+ def __init__(self, file: str) -> None:
+ self.orig_buffer = None
+
+ with open(file, "rb") as stream:
+ self.buffer = bytearray(stream.read())
+
+ def read_bit(self, address: int, bit_number: int) -> bool:
+ bitflag = (1 << bit_number)
+ return (self.buffer[address] & bitflag) != 0
+
+ def read_byte(self, address: int) -> int:
+ return self.buffer[address]
+
+ def read_bytes(self, start_address: int, length: int) -> bytearray:
+ return self.buffer[start_address:start_address + length]
+
+ def write_byte(self, address: int, value: int) -> None:
+ self.buffer[address] = value
+
+ def write_bytes(self, start_address: int, values: Collection[int]) -> None:
+ self.buffer[start_address:start_address + len(values)] = values
+
+ def write_int16(self, address: int, value: int) -> None:
+ value = value & 0xFFFF
+ self.write_bytes(address, [(value >> 8) & 0xFF, value & 0xFF])
+
+ def write_int16s(self, start_address: int, values: List[int]) -> None:
+ for i, value in enumerate(values):
+ self.write_int16(start_address + (i * 2), value)
+
+ def write_int24(self, address: int, value: int) -> None:
+ value = value & 0xFFFFFF
+ self.write_bytes(address, [(value >> 16) & 0xFF, (value >> 8) & 0xFF, value & 0xFF])
+
+ def write_int24s(self, start_address: int, values: List[int]) -> None:
+ for i, value in enumerate(values):
+ self.write_int24(start_address + (i * 3), value)
+
+ def write_int32(self, address, value: int) -> None:
+ value = value & 0xFFFFFFFF
+ self.write_bytes(address, [(value >> 24) & 0xFF, (value >> 16) & 0xFF, (value >> 8) & 0xFF, value & 0xFF])
+
+ def write_int32s(self, start_address: int, values: list) -> None:
+ for i, value in enumerate(values):
+ self.write_int32(start_address + (i * 4), value)
+
+ def write_to_file(self, filepath: str) -> None:
+ with open(filepath, "wb") as outfile:
+ outfile.write(self.buffer)
+
+
+def patch_rom(world: "CV64World", rom: LocalRom, offset_data: Dict[int, int], shop_name_list: List[str],
+ shop_desc_list: List[List[Union[int, str, None]]], shop_colors_list: List[bytearray],
+ active_locations: Iterable[Location]) -> None:
+
+ multiworld = world.multiworld
+ options = world.options
+ player = world.player
+ active_stage_exits = world.active_stage_exits
+ s1s_per_warp = world.s1s_per_warp
+ active_warp_list = world.active_warp_list
+ required_s2s = world.required_s2s
+ total_s2s = world.total_s2s
+
+ # NOP out the CRC BNEs
+ rom.write_int32(0x66C, 0x00000000)
+ rom.write_int32(0x678, 0x00000000)
+
+ # Always offer Hard Mode on file creation
+ rom.write_int32(0xC8810, 0x240A0100) # ADDIU T2, R0, 0x0100
+
+ # Disable Easy Mode cutoff point at Castle Center elevator
+ rom.write_int32(0xD9E18, 0x240D0000) # ADDIU T5, R0, 0x0000
+
+ # Disable the Forest, Castle Wall, and Villa intro cutscenes and make it possible to change the starting level
+ rom.write_byte(0xB73308, 0x00)
+ rom.write_byte(0xB7331A, 0x40)
+ rom.write_byte(0xB7332B, 0x4C)
+ rom.write_byte(0xB6302B, 0x00)
+ rom.write_byte(0x109F8F, 0x00)
+
+ # Prevent Forest end cutscene flag from setting so it can be triggered infinitely
+ rom.write_byte(0xEEA51, 0x01)
+
+ # Hack to make the Forest, CW and Villa intro cutscenes play at the start of their levels no matter what map came
+ # before them
+ rom.write_int32(0x97244, 0x803FDD60)
+ rom.write_int32s(0xBFDD60, patches.forest_cw_villa_intro_cs_player)
+
+ # Make changing the map ID to 0xFF reset the map. Helpful to work around a bug wherein the camera gets stuck when
+ # entering a loading zone that doesn't change the map.
+ rom.write_int32s(0x197B0, [0x0C0FF7E6, # JAL 0x803FDF98
+ 0x24840008]) # ADDIU A0, A0, 0x0008
+ rom.write_int32s(0xBFDF98, patches.map_id_refresher)
+
+ # Enable swapping characters when loading into a map by holding L.
+ rom.write_int32(0x97294, 0x803FDFC4)
+ rom.write_int32(0x19710, 0x080FF80E) # J 0x803FE038
+ rom.write_int32s(0xBFDFC4, patches.character_changer)
+
+ # Villa coffin time-of-day hack
+ rom.write_byte(0xD9D83, 0x74)
+ rom.write_int32(0xD9D84, 0x080FF14D) # J 0x803FC534
+ rom.write_int32s(0xBFC534, patches.coffin_time_checker)
+
+ # Fix both Castle Center elevator bridges for both characters unless enabling only one character's stages. At which
+ # point one bridge will be always broken and one always repaired instead.
+ if options.character_stages == CharacterStages.option_reinhardt_only:
+ rom.write_int32(0x6CEAA0, 0x240B0000) # ADDIU T3, R0, 0x0000
+ elif options.character_stages == CharacterStages.option_carrie_only:
+ rom.write_int32(0x6CEAA0, 0x240B0001) # ADDIU T3, R0, 0x0001
+ else:
+ rom.write_int32(0x6CEAA0, 0x240B0001) # ADDIU T3, R0, 0x0001
+ rom.write_int32(0x6CEAA4, 0x240D0001) # ADDIU T5, R0, 0x0001
+
+ # Were-bull arena flag hack
+ rom.write_int32(0x6E38F0, 0x0C0FF157) # JAL 0x803FC55C
+ rom.write_int32s(0xBFC55C, patches.werebull_flag_unsetter)
+ rom.write_int32(0xA949C, 0x0C0FF380) # JAL 0x803FCE00
+ rom.write_int32s(0xBFCE00, patches.werebull_flag_pickup_setter)
+
+ # Enable being able to carry multiple Special jewels, Nitros, and Mandragoras simultaneously
+ rom.write_int32(0xBF1F4, 0x3C038039) # LUI V1, 0x8039
+ # Special1
+ rom.write_int32(0xBF210, 0x80659C4B) # LB A1, 0x9C4B (V1)
+ rom.write_int32(0xBF214, 0x24A50001) # ADDIU A1, A1, 0x0001
+ rom.write_int32(0xBF21C, 0xA0659C4B) # SB A1, 0x9C4B (V1)
+ # Special2
+ rom.write_int32(0xBF230, 0x80659C4C) # LB A1, 0x9C4C (V1)
+ rom.write_int32(0xBF234, 0x24A50001) # ADDIU A1, A1, 0x0001
+ rom.write_int32(0xbf23C, 0xA0659C4C) # SB A1, 0x9C4C (V1)
+ # Magical Nitro
+ rom.write_int32(0xBF360, 0x10000004) # B 0x8013C184
+ rom.write_int32(0xBF378, 0x25E50001) # ADDIU A1, T7, 0x0001
+ rom.write_int32(0xBF37C, 0x10000003) # B 0x8013C19C
+ # Mandragora
+ rom.write_int32(0xBF3A8, 0x10000004) # B 0x8013C1CC
+ rom.write_int32(0xBF3C0, 0x25050001) # ADDIU A1, T0, 0x0001
+ rom.write_int32(0xBF3C4, 0x10000003) # B 0x8013C1E4
+
+ # Give PowerUps their Legacy of Darkness behavior when attempting to pick up more than two
+ rom.write_int16(0xA9624, 0x1000)
+ rom.write_int32(0xA9730, 0x24090000) # ADDIU T1, R0, 0x0000
+ rom.write_int32(0xBF2FC, 0x080FF16D) # J 0x803FC5B4
+ rom.write_int32(0xBF300, 0x00000000) # NOP
+ rom.write_int32s(0xBFC5B4, patches.give_powerup_stopper)
+
+ # Rename the Wooden Stake and Rose to "You are a FOOL!"
+ rom.write_bytes(0xEFE34,
+ bytearray([0xFF, 0xFF, 0xA2, 0x0B]) + cv64_string_to_bytearray("You are a FOOL!", append_end=False))
+ # Capitalize the "k" in "Archives key" to be consistent with...literally every other key name!
+ rom.write_byte(0xEFF21, 0x2D)
+
+ # Skip the "There is a white jewel" text so checking one saves the game instantly.
+ rom.write_int32s(0xEFC72, [0x00020002 for _ in range(37)])
+ rom.write_int32(0xA8FC0, 0x24020001) # ADDIU V0, R0, 0x0001
+ # Skip the yes/no prompts when activating things.
+ rom.write_int32s(0xBFDACC, patches.map_text_redirector)
+ rom.write_int32(0xA9084, 0x24020001) # ADDIU V0, R0, 0x0001
+ rom.write_int32(0xBEBE8, 0x0C0FF6B4) # JAL 0x803FDAD0
+ # Skip Vincent and Heinrich's mandatory-for-a-check dialogue
+ rom.write_int32(0xBED9C, 0x0C0FF6DA) # JAL 0x803FDB68
+ # Skip the long yes/no prompt in the CC planetarium to set the pieces.
+ rom.write_int32(0xB5C5DF, 0x24030001) # ADDIU V1, R0, 0x0001
+ # Skip the yes/no prompt to activate the CC elevator.
+ rom.write_int32(0xB5E3FB, 0x24020001) # ADDIU V0, R0, 0x0001
+ # Skip the yes/no prompts to set Nitro/Mandragora at both walls.
+ rom.write_int32(0xB5DF3E, 0x24030001) # ADDIU V1, R0, 0x0001
+
+ # Custom message if you try checking the downstairs CC crack before removing the seal.
+ rom.write_bytes(0xBFDBAC, cv64_string_to_bytearray("The Furious Nerd Curse\n"
+ "prevents you from setting\n"
+ "anything until the seal\n"
+ "is removed!", True))
+
+ rom.write_int32s(0xBFDD20, patches.special_descriptions_redirector)
+
+ # Change the Stage Select menu options
+ rom.write_int32s(0xADF64, patches.warp_menu_rewrite)
+ rom.write_int32s(0x10E0C8, patches.warp_pointer_table)
+ for i in range(len(active_warp_list)):
+ if i == 0:
+ rom.write_byte(warp_map_offsets[i], get_stage_info(active_warp_list[i], "start map id"))
+ rom.write_byte(warp_map_offsets[i] + 4, get_stage_info(active_warp_list[i], "start spawn id"))
+ else:
+ rom.write_byte(warp_map_offsets[i], get_stage_info(active_warp_list[i], "mid map id"))
+ rom.write_byte(warp_map_offsets[i] + 4, get_stage_info(active_warp_list[i], "mid spawn id"))
+
+ # Play the "teleportation" sound effect when teleporting
+ rom.write_int32s(0xAE088, [0x08004FAB, # J 0x80013EAC
+ 0x2404019E]) # ADDIU A0, R0, 0x019E
+
+ # Change the Stage Select menu's text to reflect its new purpose
+ rom.write_bytes(0xEFAD0, cv64_string_to_bytearray(f"Where to...?\t{active_warp_list[0]}\t"
+ f"`{str(s1s_per_warp).zfill(2)} {active_warp_list[1]}\t"
+ f"`{str(s1s_per_warp * 2).zfill(2)} {active_warp_list[2]}\t"
+ f"`{str(s1s_per_warp * 3).zfill(2)} {active_warp_list[3]}\t"
+ f"`{str(s1s_per_warp * 4).zfill(2)} {active_warp_list[4]}\t"
+ f"`{str(s1s_per_warp * 5).zfill(2)} {active_warp_list[5]}\t"
+ f"`{str(s1s_per_warp * 6).zfill(2)} {active_warp_list[6]}\t"
+ f"`{str(s1s_per_warp * 7).zfill(2)} {active_warp_list[7]}"))
+
+ # Lizard-man save proofing
+ rom.write_int32(0xA99AC, 0x080FF0B8) # J 0x803FC2E0
+ rom.write_int32s(0xBFC2E0, patches.boss_save_stopper)
+
+ # Disable or guarantee vampire Vincent's fight
+ if options.vincent_fight_condition == VincentFightCondition.option_never:
+ rom.write_int32(0xAACC0, 0x24010001) # ADDIU AT, R0, 0x0001
+ rom.write_int32(0xAACE0, 0x24180000) # ADDIU T8, R0, 0x0000
+ elif options.vincent_fight_condition == VincentFightCondition.option_always:
+ rom.write_int32(0xAACE0, 0x24180010) # ADDIU T8, R0, 0x0010
+ else:
+ rom.write_int32(0xAACE0, 0x24180000) # ADDIU T8, R0, 0x0000
+
+ # Disable or guarantee Renon's fight
+ rom.write_int32(0xAACB4, 0x080FF1A4) # J 0x803FC690
+ if options.renon_fight_condition == RenonFightCondition.option_never:
+ rom.write_byte(0xB804F0, 0x00)
+ rom.write_byte(0xB80632, 0x00)
+ rom.write_byte(0xB807E3, 0x00)
+ rom.write_byte(0xB80988, 0xB8)
+ rom.write_byte(0xB816BD, 0xB8)
+ rom.write_byte(0xB817CF, 0x00)
+ rom.write_int32s(0xBFC690, patches.renon_cutscene_checker_jr)
+ elif options.renon_fight_condition == RenonFightCondition.option_always:
+ rom.write_byte(0xB804F0, 0x0C)
+ rom.write_byte(0xB80632, 0x0C)
+ rom.write_byte(0xB807E3, 0x0C)
+ rom.write_byte(0xB80988, 0xC4)
+ rom.write_byte(0xB816BD, 0xC4)
+ rom.write_byte(0xB817CF, 0x0C)
+ rom.write_int32s(0xBFC690, patches.renon_cutscene_checker_jr)
+ else:
+ rom.write_int32s(0xBFC690, patches.renon_cutscene_checker)
+
+ # NOP the Easy Mode check when buying a thing from Renon, so he can be triggered even on this mode.
+ rom.write_int32(0xBD8B4, 0x00000000)
+
+ # Disable or guarantee the Bad Ending
+ if options.bad_ending_condition == BadEndingCondition.option_never:
+ rom.write_int32(0xAEE5C6, 0x3C0A0000) # LUI T2, 0x0000
+ elif options.bad_ending_condition == BadEndingCondition.option_always:
+ rom.write_int32(0xAEE5C6, 0x3C0A0040) # LUI T2, 0x0040
+
+ # Play Castle Keep's song if teleporting in front of Dracula's door outside the escape sequence
+ rom.write_int32(0x6E937C, 0x080FF12E) # J 0x803FC4B8
+ rom.write_int32s(0xBFC4B8, patches.ck_door_music_player)
+
+ # Increase item capacity to 100 if "Increase Item Limit" is turned on
+ if options.increase_item_limit:
+ rom.write_byte(0xBF30B, 0x63) # Most items
+ rom.write_byte(0xBF3F7, 0x63) # Sun/Moon cards
+ rom.write_byte(0xBF353, 0x64) # Keys (increase regardless)
+
+ # Change the item healing values if "Nerf Healing" is turned on
+ if options.nerf_healing_items:
+ rom.write_byte(0xB56371, 0x50) # Healing kit (100 -> 80)
+ rom.write_byte(0xB56374, 0x32) # Roast beef ( 80 -> 50)
+ rom.write_byte(0xB56377, 0x19) # Roast chicken ( 50 -> 25)
+
+ # Disable loading zone healing if turned off
+ if not options.loading_zone_heals:
+ rom.write_byte(0xD99A5, 0x00) # Skip all loading zone checks
+ rom.write_byte(0xA9DFFB, 0x40) # Disable free heal from King Skeleton by reading the unused magic meter value
+
+ # Disable spinning on the Special1 and 2 pickup models so colorblind people can more easily identify them
+ rom.write_byte(0xEE4F5, 0x00) # Special1
+ rom.write_byte(0xEE505, 0x00) # Special2
+ # Make the Special2 the same size as a Red jewel(L) to further distinguish them
+ rom.write_int32(0xEE4FC, 0x3FA66666)
+
+ # Prevent the vanilla Magical Nitro transport's "can explode" flag from setting
+ rom.write_int32(0xB5D7AA, 0x00000000) # NOP
+
+ # Ensure the vampire Nitro check will always pass, so they'll never not spawn and crash the Villa cutscenes
+ rom.write_byte(0xA6253D, 0x03)
+
+ # Enable the Game Over's "Continue" menu starting the cursor on whichever checkpoint is most recent
+ rom.write_int32(0xB4DDC, 0x0C060D58) # JAL 0x80183560
+ rom.write_int32s(0x106750, patches.continue_cursor_start_checker)
+ rom.write_int32(0x1C444, 0x080FF08A) # J 0x803FC228
+ rom.write_int32(0x1C2A0, 0x080FF08A) # J 0x803FC228
+ rom.write_int32s(0xBFC228, patches.savepoint_cursor_updater)
+ rom.write_int32(0x1C2D0, 0x080FF094) # J 0x803FC250
+ rom.write_int32s(0xBFC250, patches.stage_start_cursor_updater)
+ rom.write_byte(0xB585C8, 0xFF)
+
+ # Make the Special1 and 2 play sounds when you reach milestones with them.
+ rom.write_int32s(0xBFDA50, patches.special_sound_notifs)
+ rom.write_int32(0xBF240, 0x080FF694) # J 0x803FDA50
+ rom.write_int32(0xBF220, 0x080FF69E) # J 0x803FDA78
+
+ # Add data for White Jewel #22 (the new Duel Tower savepoint) at the end of the White Jewel ID data list
+ rom.write_int16s(0x104AC8, [0x0000, 0x0006,
+ 0x0013, 0x0015])
+
+ # Take the contract in Waterway off of its 00400000 bitflag.
+ rom.write_byte(0x87E3DA, 0x00)
+
+ # Spawn coordinates list extension
+ rom.write_int32(0xD5BF4, 0x080FF103) # J 0x803FC40C
+ rom.write_int32s(0xBFC40C, patches.spawn_coordinates_extension)
+ rom.write_int32s(0x108A5E, patches.waterway_end_coordinates)
+
+ # Change the File Select stage numbers to match the new stage order. Also fix a vanilla issue wherein saving in a
+ # character-exclusive stage as the other character would incorrectly display the name of that character's equivalent
+ # stage on the save file instead of the one they're actually in.
+ rom.write_byte(0xC9FE3, 0xD4)
+ rom.write_byte(0xCA055, 0x08)
+ rom.write_byte(0xCA066, 0x40)
+ rom.write_int32(0xCA068, 0x860C17D0) # LH T4, 0x17D0 (S0)
+ rom.write_byte(0xCA06D, 0x08)
+ rom.write_byte(0x104A31, 0x01)
+ rom.write_byte(0x104A39, 0x01)
+ rom.write_byte(0x104A89, 0x01)
+ rom.write_byte(0x104A91, 0x01)
+ rom.write_byte(0x104A99, 0x01)
+ rom.write_byte(0x104AA1, 0x01)
+
+ for stage in active_stage_exits:
+ for offset in get_stage_info(stage, "save number offsets"):
+ rom.write_byte(offset, active_stage_exits[stage]["position"])
+
+ # CC top elevator switch check
+ rom.write_int32(0x6CF0A0, 0x0C0FF0B0) # JAL 0x803FC2C0
+ rom.write_int32s(0xBFC2C0, patches.elevator_flag_checker)
+
+ # Disable time restrictions
+ if options.disable_time_restrictions:
+ # Fountain
+ rom.write_int32(0x6C2340, 0x00000000) # NOP
+ rom.write_int32(0x6C257C, 0x10000023) # B [forward 0x23]
+ # Rosa
+ rom.write_byte(0xEEAAB, 0x00)
+ rom.write_byte(0xEEAAD, 0x18)
+ # Moon doors
+ rom.write_int32(0xDC3E0, 0x00000000) # NOP
+ rom.write_int32(0xDC3E8, 0x00000000) # NOP
+ # Sun doors
+ rom.write_int32(0xDC410, 0x00000000) # NOP
+ rom.write_int32(0xDC418, 0x00000000) # NOP
+
+ # Custom data-loading code
+ rom.write_int32(0x6B5028, 0x08060D70) # J 0x801835D0
+ rom.write_int32s(0x1067B0, patches.custom_code_loader)
+
+ # Custom remote item rewarding and DeathLink receiving code
+ rom.write_int32(0x19B98, 0x080FF000) # J 0x803FC000
+ rom.write_int32s(0xBFC000, patches.remote_item_giver)
+ rom.write_int32s(0xBFE190, patches.subweapon_surface_checker)
+
+ # Make received DeathLinks blow you to smithereens instead of kill you normally.
+ if options.death_link == DeathLink.option_explosive:
+ rom.write_int32(0x27A70, 0x10000008) # B [forward 0x08]
+ rom.write_int32s(0xBFC0D0, patches.deathlink_nitro_edition)
+
+ # Set the DeathLink ROM flag if it's on at all.
+ if options.death_link != DeathLink.option_off:
+ rom.write_byte(0xBFBFDE, 0x01)
+
+ # DeathLink counter decrementer code
+ rom.write_int32(0x1C340, 0x080FF8F0) # J 0x803FE3C0
+ rom.write_int32s(0xBFE3C0, patches.deathlink_counter_decrementer)
+ rom.write_int32(0x25B6C, 0x0080FF052) # J 0x803FC148
+ rom.write_int32s(0xBFC148, patches.nitro_fall_killer)
+
+ # Death flag un-setter on "Beginning of stage" state overwrite code
+ rom.write_int32(0x1C2B0, 0x080FF047) # J 0x803FC11C
+ rom.write_int32s(0xBFC11C, patches.death_flag_unsetter)
+
+ # Warp menu-opening code
+ rom.write_int32(0xB9BA8, 0x080FF099) # J 0x803FC264
+ rom.write_int32s(0xBFC264, patches.warp_menu_opener)
+
+ # NPC item textbox hack
+ rom.write_int32(0xBF1DC, 0x080FF904) # J 0x803FE410
+ rom.write_int32(0xBF1E0, 0x27BDFFE0) # ADDIU SP, SP, -0x20
+ rom.write_int32s(0xBFE410, patches.npc_item_hack)
+
+ # Sub-weapon check function hook
+ rom.write_int32(0xBF32C, 0x00000000) # NOP
+ rom.write_int32(0xBF330, 0x080FF05E) # J 0x803FC178
+ rom.write_int32s(0xBFC178, patches.give_subweapon_stopper)
+
+ # Warp menu Special1 restriction
+ rom.write_int32(0xADD68, 0x0C04AB12) # JAL 0x8012AC48
+ rom.write_int32s(0xADE28, patches.stage_select_overwrite)
+ rom.write_byte(0xADE47, s1s_per_warp)
+
+ # Dracula's door text pointer hijack
+ rom.write_int32(0xD69F0, 0x080FF141) # J 0x803FC504
+ rom.write_int32s(0xBFC504, patches.dracula_door_text_redirector)
+
+ # Dracula's chamber condition
+ rom.write_int32(0xE2FDC, 0x0804AB25) # J 0x8012AC78
+ rom.write_int32s(0xADE84, patches.special_goal_checker)
+ rom.write_bytes(0xBFCC48, [0xA0, 0x00, 0xFF, 0xFF, 0xA0, 0x01, 0xFF, 0xFF, 0xA0, 0x02, 0xFF, 0xFF, 0xA0, 0x03, 0xFF,
+ 0xFF, 0xA0, 0x04, 0xFF, 0xFF, 0xA0, 0x05, 0xFF, 0xFF, 0xA0, 0x06, 0xFF, 0xFF, 0xA0, 0x07,
+ 0xFF, 0xFF, 0xA0, 0x08, 0xFF, 0xFF, 0xA0, 0x09])
+ if options.draculas_condition == DraculasCondition.option_crystal:
+ rom.write_int32(0x6C8A54, 0x0C0FF0C1) # JAL 0x803FC304
+ rom.write_int32s(0xBFC304, patches.crystal_special2_giver)
+ rom.write_bytes(0xBFCC6E, cv64_string_to_bytearray(f"It won't budge!\n"
+ f"You'll need the power\n"
+ f"of the basement crystal\n"
+ f"to undo the seal.", True))
+ special2_name = "Crystal "
+ special2_text = "The crystal is on!\n" \
+ "Time to teach the old man\n" \
+ "a lesson!"
+ elif options.draculas_condition == DraculasCondition.option_bosses:
+ rom.write_int32(0xBBD50, 0x080FF18C) # J 0x803FC630
+ rom.write_int32s(0xBFC630, patches.boss_special2_giver)
+ rom.write_int32s(0xBFC55C, patches.werebull_flag_unsetter_special2_electric_boogaloo)
+ rom.write_bytes(0xBFCC6E, cv64_string_to_bytearray(f"It won't budge!\n"
+ f"You'll need to defeat\n"
+ f"{required_s2s} powerful monsters\n"
+ f"to undo the seal.", True))
+ special2_name = "Trophy "
+ special2_text = f"Proof you killed a powerful\n" \
+ f"Night Creature. Earn {required_s2s}/{total_s2s}\n" \
+ f"to battle Dracula."
+ elif options.draculas_condition == DraculasCondition.option_specials:
+ special2_name = "Special2"
+ rom.write_bytes(0xBFCC6E, cv64_string_to_bytearray(f"It won't budge!\n"
+ f"You'll need to find\n"
+ f"{required_s2s} Special2 jewels\n"
+ f"to undo the seal.", True))
+ special2_text = f"Need {required_s2s}/{total_s2s} to kill Dracula.\n" \
+ f"Looking closely, you see...\n" \
+ f"a piece of him within?"
+ else:
+ rom.write_byte(0xADE8F, 0x00)
+ special2_name = "Special2"
+ special2_text = "If you're reading this,\n" \
+ "how did you get a Special2!?"
+ rom.write_byte(0xADE8F, required_s2s)
+ # Change the Special2 name depending on the setting.
+ rom.write_bytes(0xEFD4E, cv64_string_to_bytearray(special2_name))
+ # Change the Special1 and 2 menu descriptions to tell you how many you need to unlock a warp and fight Dracula
+ # respectively.
+ special_text_bytes = cv64_string_to_bytearray(f"{s1s_per_warp} per warp unlock.\n"
+ f"{options.total_special1s.value} exist in total.\n"
+ f"Z + R + START to warp.") + cv64_string_to_bytearray(special2_text)
+ rom.write_bytes(0xBFE53C, special_text_bytes)
+
+ # On-the-fly TLB script modifier
+ rom.write_int32s(0xBFC338, patches.double_component_checker)
+ rom.write_int32s(0xBFC3D4, patches.downstairs_seal_checker)
+ rom.write_int32s(0xBFE074, patches.mandragora_with_nitro_setter)
+ rom.write_int32s(0xBFC700, patches.overlay_modifiers)
+
+ # On-the-fly actor data modifier hook
+ rom.write_int32(0xEAB04, 0x080FF21E) # J 0x803FC878
+ rom.write_int32s(0xBFC870, patches.map_data_modifiers)
+
+ # Fix to make flags apply to freestanding invisible items properly
+ rom.write_int32(0xA84F8, 0x90CC0039) # LBU T4, 0x0039 (A2)
+
+ # Fix locked doors to check the key counters instead of their vanilla key locations' bitflags
+ # Pickup flag check modifications:
+ rom.write_int32(0x10B2D8, 0x00000002) # Left Tower Door
+ rom.write_int32(0x10B2F0, 0x00000003) # Storeroom Door
+ rom.write_int32(0x10B2FC, 0x00000001) # Archives Door
+ rom.write_int32(0x10B314, 0x00000004) # Maze Gate
+ rom.write_int32(0x10B350, 0x00000005) # Copper Door
+ rom.write_int32(0x10B3A4, 0x00000006) # Torture Chamber Door
+ rom.write_int32(0x10B3B0, 0x00000007) # ToE Gate
+ rom.write_int32(0x10B3BC, 0x00000008) # Science Door1
+ rom.write_int32(0x10B3C8, 0x00000009) # Science Door2
+ rom.write_int32(0x10B3D4, 0x0000000A) # Science Door3
+ rom.write_int32(0x6F0094, 0x0000000B) # CT Door 1
+ rom.write_int32(0x6F00A4, 0x0000000C) # CT Door 2
+ rom.write_int32(0x6F00B4, 0x0000000D) # CT Door 3
+ # Item counter decrement check modifications:
+ rom.write_int32(0xEDA84, 0x00000001) # Archives Door
+ rom.write_int32(0xEDA8C, 0x00000002) # Left Tower Door
+ rom.write_int32(0xEDA94, 0x00000003) # Storeroom Door
+ rom.write_int32(0xEDA9C, 0x00000004) # Maze Gate
+ rom.write_int32(0xEDAA4, 0x00000005) # Copper Door
+ rom.write_int32(0xEDAAC, 0x00000006) # Torture Chamber Door
+ rom.write_int32(0xEDAB4, 0x00000007) # ToE Gate
+ rom.write_int32(0xEDABC, 0x00000008) # Science Door1
+ rom.write_int32(0xEDAC4, 0x00000009) # Science Door2
+ rom.write_int32(0xEDACC, 0x0000000A) # Science Door3
+ rom.write_int32(0xEDAD4, 0x0000000B) # CT Door 1
+ rom.write_int32(0xEDADC, 0x0000000C) # CT Door 2
+ rom.write_int32(0xEDAE4, 0x0000000D) # CT Door 3
+
+ # Fix ToE gate's "unlocked" flag in the locked door flags table
+ rom.write_int16(0x10B3B6, 0x0001)
+
+ rom.write_int32(0x10AB2C, 0x8015FBD4) # Maze Gates' check code pointer adjustments
+ rom.write_int32(0x10AB40, 0x8015FBD4)
+ rom.write_int32s(0x10AB50, [0x0D0C0000,
+ 0x8015FBD4])
+ rom.write_int32s(0x10AB64, [0x0D0C0000,
+ 0x8015FBD4])
+ rom.write_int32s(0xE2E14, patches.normal_door_hook)
+ rom.write_int32s(0xBFC5D0, patches.normal_door_code)
+ rom.write_int32s(0x6EF298, patches.ct_door_hook)
+ rom.write_int32s(0xBFC608, patches.ct_door_code)
+ # Fix key counter not decrementing if 2 or above
+ rom.write_int32(0xAA0E0, 0x24020000) # ADDIU V0, R0, 0x0000
+
+ # Make the Easy-only candle drops in Room of Clocks appear on any difficulty
+ rom.write_byte(0x9B518F, 0x01)
+
+ # Slightly move some once-invisible freestanding items to be more visible
+ if options.invisible_items == InvisibleItems.option_reveal_all:
+ rom.write_byte(0x7C7F95, 0xEF) # Forest dirge maiden statue
+ rom.write_byte(0x7C7FA8, 0xAB) # Forest werewolf statue
+ rom.write_byte(0x8099C4, 0x8C) # Villa courtyard tombstone
+ rom.write_byte(0x83A626, 0xC2) # Villa living room painting
+ # rom.write_byte(0x83A62F, 0x64) # Villa Mary's room table
+ rom.write_byte(0xBFCB97, 0xF5) # CC torture instrument rack
+ rom.write_byte(0x8C44D5, 0x22) # CC red carpet hallway knight
+ rom.write_byte(0x8DF57C, 0xF1) # CC cracked wall hallway flamethrower
+ rom.write_byte(0x90FCD6, 0xA5) # CC nitro hallway flamethrower
+ rom.write_byte(0x90FB9F, 0x9A) # CC invention room round machine
+ rom.write_byte(0x90FBAF, 0x03) # CC invention room giant famicart
+ rom.write_byte(0x90FE54, 0x97) # CC staircase knight (x)
+ rom.write_byte(0x90FE58, 0xFB) # CC staircase knight (z)
+
+ # Change bitflag on item in upper coffin in Forest final switch gate tomb to one that's not used by something else
+ rom.write_int32(0x10C77C, 0x00000002)
+
+ # Make the torch directly behind Dracula's chamber that normally doesn't set a flag set bitflag 0x08 in 0x80389BFA
+ rom.write_byte(0x10CE9F, 0x01)
+
+ # Change the CC post-Behemoth boss depending on the option for Post-Behemoth Boss
+ if options.post_behemoth_boss == PostBehemothBoss.option_inverted:
+ rom.write_byte(0xEEDAD, 0x02)
+ rom.write_byte(0xEEDD9, 0x01)
+ elif options.post_behemoth_boss == PostBehemothBoss.option_always_rosa:
+ rom.write_byte(0xEEDAD, 0x00)
+ rom.write_byte(0xEEDD9, 0x03)
+ # Put both on the same flag so changing character won't trigger a rematch with the same boss.
+ rom.write_byte(0xEED8B, 0x40)
+ elif options.post_behemoth_boss == PostBehemothBoss.option_always_camilla:
+ rom.write_byte(0xEEDAD, 0x03)
+ rom.write_byte(0xEEDD9, 0x00)
+ rom.write_byte(0xEED8B, 0x40)
+
+ # Change the RoC boss depending on the option for Room of Clocks Boss
+ if options.room_of_clocks_boss == RoomOfClocksBoss.option_inverted:
+ rom.write_byte(0x109FB3, 0x56)
+ rom.write_byte(0x109FBF, 0x44)
+ rom.write_byte(0xD9D44, 0x14)
+ rom.write_byte(0xD9D4C, 0x14)
+ elif options.room_of_clocks_boss == RoomOfClocksBoss.option_always_death:
+ rom.write_byte(0x109FBF, 0x44)
+ rom.write_byte(0xD9D45, 0x00)
+ # Put both on the same flag so changing character won't trigger a rematch with the same boss.
+ rom.write_byte(0x109FB7, 0x90)
+ rom.write_byte(0x109FC3, 0x90)
+ elif options.room_of_clocks_boss == RoomOfClocksBoss.option_always_actrise:
+ rom.write_byte(0x109FB3, 0x56)
+ rom.write_int32(0xD9D44, 0x00000000)
+ rom.write_byte(0xD9D4D, 0x00)
+ rom.write_byte(0x109FB7, 0x90)
+ rom.write_byte(0x109FC3, 0x90)
+
+ # Un-nerf Actrise when playing as Reinhardt.
+ # This is likely a leftover TGS demo feature in which players could battle Actrise as Reinhardt.
+ rom.write_int32(0xB318B4, 0x240E0001) # ADDIU T6, R0, 0x0001
+
+ # Tunnel gondola skip
+ if options.skip_gondolas:
+ rom.write_int32(0x6C5F58, 0x080FF7D0) # J 0x803FDF40
+ rom.write_int32s(0xBFDF40, patches.gondola_skipper)
+ # New gondola transfer point candle coordinates
+ rom.write_byte(0xBFC9A3, 0x04)
+ rom.write_bytes(0x86D824, [0x27, 0x01, 0x10, 0xF7, 0xA0])
+
+ # Waterway brick platforms skip
+ if options.skip_waterway_blocks:
+ rom.write_int32(0x6C7E2C, 0x00000000) # NOP
+
+ # Ambience silencing fix
+ rom.write_int32(0xD9270, 0x080FF840) # J 0x803FE100
+ rom.write_int32s(0xBFE100, patches.ambience_silencer)
+ # Fix for the door sliding sound playing infinitely if leaving the fan meeting room before the door closes entirely.
+ # Hooking this in the ambience silencer code does nothing for some reason.
+ rom.write_int32s(0xAE10C, [0x08004FAB, # J 0x80013EAC
+ 0x3404829B]) # ORI A0, R0, 0x829B
+ rom.write_int32s(0xD9E8C, [0x08004FAB, # J 0x80013EAC
+ 0x3404829B]) # ORI A0, R0, 0x829B
+ # Fan meeting room ambience fix
+ rom.write_int32(0x109964, 0x803FE13C)
+
+ # Make the Villa coffin cutscene skippable
+ rom.write_int32(0xAA530, 0x080FF880) # J 0x803FE200
+ rom.write_int32s(0xBFE200, patches.coffin_cutscene_skipper)
+
+ # Increase shimmy speed
+ if options.increase_shimmy_speed:
+ rom.write_byte(0xA4241, 0x5A)
+
+ # Disable landing fall damage
+ if options.fall_guard:
+ rom.write_byte(0x27B23, 0x00)
+
+ # Enable the unused film reel effect on all cutscenes
+ if options.cinematic_experience:
+ rom.write_int32(0xAA33C, 0x240A0001) # ADDIU T2, R0, 0x0001
+ rom.write_byte(0xAA34B, 0x0C)
+ rom.write_int32(0xAA4C4, 0x24090001) # ADDIU T1, R0, 0x0001
+
+ # Permanent PowerUp stuff
+ if options.permanent_powerups:
+ # Make receiving PowerUps increase the unused menu PowerUp counter instead of the one outside the save struct
+ rom.write_int32(0xBF2EC, 0x806B619B) # LB T3, 0x619B (V1)
+ rom.write_int32(0xBFC5BC, 0xA06C619B) # SB T4, 0x619B (V1)
+ # Make Reinhardt's whip check the menu PowerUp counter
+ rom.write_int32(0x69FA08, 0x80CC619B) # LB T4, 0x619B (A2)
+ rom.write_int32(0x69FBFC, 0x80C3619B) # LB V1, 0x619B (A2)
+ rom.write_int32(0x69FFE0, 0x818C9C53) # LB T4, 0x9C53 (T4)
+ # Make Carrie's orb check the menu PowerUp counter
+ rom.write_int32(0x6AC86C, 0x8105619B) # LB A1, 0x619B (T0)
+ rom.write_int32(0x6AC950, 0x8105619B) # LB A1, 0x619B (T0)
+ rom.write_int32(0x6AC99C, 0x810E619B) # LB T6, 0x619B (T0)
+ rom.write_int32(0x5AFA0, 0x80639C53) # LB V1, 0x9C53 (V1)
+ rom.write_int32(0x5B0A0, 0x81089C53) # LB T0, 0x9C53 (T0)
+ rom.write_byte(0x391C7, 0x00) # Prevent PowerUps from dropping from regular enemies
+ rom.write_byte(0xEDEDF, 0x03) # Make any vanishing PowerUps that do show up L jewels instead
+ # Rename the PowerUp to "PermaUp"
+ rom.write_bytes(0xEFDEE, cv64_string_to_bytearray("PermaUp"))
+ # Replace the PowerUp in the Forest Special1 Bridge 3HB rock with an L jewel if 3HBs aren't randomized
+ if not options.multi_hit_breakables:
+ rom.write_byte(0x10C7A1, 0x03)
+ # Change the appearance of the Pot-Pourri to that of a larger PowerUp regardless of the above setting, so other
+ # game PermaUps are distinguishable.
+ rom.write_int32s(0xEE558, [0x06005F08, 0x3FB00000, 0xFFFFFF00])
+
+ # Write the randomized (or disabled) music ID list and its associated code
+ if options.background_music:
+ rom.write_int32(0x14588, 0x08060D60) # J 0x80183580
+ rom.write_int32(0x14590, 0x00000000) # NOP
+ rom.write_int32s(0x106770, patches.music_modifier)
+ rom.write_int32(0x15780, 0x0C0FF36E) # JAL 0x803FCDB8
+ rom.write_int32s(0xBFCDB8, patches.music_comparer_modifier)
+
+ # Enable storing item flags anywhere and changing the item model/visibility on any item instance.
+ rom.write_int32s(0xA857C, [0x080FF38F, # J 0x803FCE3C
+ 0x94D90038]) # LHU T9, 0x0038 (A2)
+ rom.write_int32s(0xBFCE3C, patches.item_customizer)
+ rom.write_int32s(0xA86A0, [0x0C0FF3AF, # JAL 0x803FCEBC
+ 0x95C40002]) # LHU A0, 0x0002 (T6)
+ rom.write_int32s(0xBFCEBC, patches.item_appearance_switcher)
+ rom.write_int32s(0xA8728, [0x0C0FF3B8, # JAL 0x803FCEE4
+ 0x01396021]) # ADDU T4, T1, T9
+ rom.write_int32s(0xBFCEE4, patches.item_model_visibility_switcher)
+ rom.write_int32s(0xA8A04, [0x0C0FF3C2, # JAL 0x803FCF08
+ 0x018B6021]) # ADDU T4, T4, T3
+ rom.write_int32s(0xBFCF08, patches.item_shine_visibility_switcher)
+
+ # Make Axes and Crosses in AP Locations drop to their correct height, and make items with changed appearances spin
+ # their correct speed.
+ rom.write_int32s(0xE649C, [0x0C0FFA03, # JAL 0x803FE80C
+ 0x956C0002]) # LHU T4, 0x0002 (T3)
+ rom.write_int32s(0xA8B08, [0x080FFA0C, # J 0x803FE830
+ 0x960A0038]) # LHU T2, 0x0038 (S0)
+ rom.write_int32s(0xE8584, [0x0C0FFA21, # JAL 0x803FE884
+ 0x95D80000]) # LHU T8, 0x0000 (T6)
+ rom.write_int32s(0xE7AF0, [0x0C0FFA2A, # JAL 0x803FE8A8
+ 0x958D0000]) # LHU T5, 0x0000 (T4)
+ rom.write_int32s(0xBFE7DC, patches.item_drop_spin_corrector)
+
+ # Disable the 3HBs checking and setting flags when breaking them and enable their individual items checking and
+ # setting flags instead.
+ if options.multi_hit_breakables:
+ rom.write_int32(0xE87F8, 0x00000000) # NOP
+ rom.write_int16(0xE836C, 0x1000)
+ rom.write_int32(0xE8B40, 0x0C0FF3CD) # JAL 0x803FCF34
+ rom.write_int32s(0xBFCF34, patches.three_hit_item_flags_setter)
+ # Villa foyer chandelier-specific functions (yeah, IDK why KCEK made different functions for this one)
+ rom.write_int32(0xE7D54, 0x00000000) # NOP
+ rom.write_int16(0xE7908, 0x1000)
+ rom.write_byte(0xE7A5C, 0x10)
+ rom.write_int32(0xE7F08, 0x0C0FF3DF) # JAL 0x803FCF7C
+ rom.write_int32s(0xBFCF7C, patches.chandelier_item_flags_setter)
+
+ # New flag values to put in each 3HB vanilla flag's spot
+ rom.write_int32(0x10C7C8, 0x8000FF48) # FoS dirge maiden rock
+ rom.write_int32(0x10C7B0, 0x0200FF48) # FoS S1 bridge rock
+ rom.write_int32(0x10C86C, 0x0010FF48) # CW upper rampart save nub
+ rom.write_int32(0x10C878, 0x4000FF49) # CW Dracula switch slab
+ rom.write_int32(0x10CAD8, 0x0100FF49) # Tunnel twin arrows slab
+ rom.write_int32(0x10CAE4, 0x0004FF49) # Tunnel lonesome bucket pit rock
+ rom.write_int32(0x10CB54, 0x4000FF4A) # UW poison parkour ledge
+ rom.write_int32(0x10CB60, 0x0080FF4A) # UW skeleton crusher ledge
+ rom.write_int32(0x10CBF0, 0x0008FF4A) # CC Behemoth crate
+ rom.write_int32(0x10CC2C, 0x2000FF4B) # CC elevator pedestal
+ rom.write_int32(0x10CC70, 0x0200FF4B) # CC lizard locker slab
+ rom.write_int32(0x10CD88, 0x0010FF4B) # ToE pre-midsavepoint platforms ledge
+ rom.write_int32(0x10CE6C, 0x4000FF4C) # ToSci invisible bridge crate
+ rom.write_int32(0x10CF20, 0x0080FF4C) # CT inverted battery slab
+ rom.write_int32(0x10CF2C, 0x0008FF4C) # CT inverted door slab
+ rom.write_int32(0x10CF38, 0x8000FF4D) # CT final room door slab
+ rom.write_int32(0x10CF44, 0x1000FF4D) # CT Renon slab
+ rom.write_int32(0x10C908, 0x0008FF4D) # Villa foyer chandelier
+ rom.write_byte(0x10CF37, 0x04) # pointer for CT final room door slab item data
+
+ # Once-per-frame gameplay checks
+ rom.write_int32(0x6C848, 0x080FF40D) # J 0x803FD034
+ rom.write_int32(0xBFD058, 0x0801AEB5) # J 0x8006BAD4
+
+ # Everything related to dropping the previous sub-weapon
+ if options.drop_previous_sub_weapon:
+ rom.write_int32(0xBFD034, 0x080FF3FF) # J 0x803FCFFC
+ rom.write_int32(0xBFC190, 0x080FF3F2) # J 0x803FCFC8
+ rom.write_int32s(0xBFCFC4, patches.prev_subweapon_spawn_checker)
+ rom.write_int32s(0xBFCFFC, patches.prev_subweapon_fall_checker)
+ rom.write_int32s(0xBFD060, patches.prev_subweapon_dropper)
+
+ # Everything related to the Countdown counter
+ if options.countdown:
+ rom.write_int32(0xBFD03C, 0x080FF9DC) # J 0x803FE770
+ rom.write_int32(0xD5D48, 0x080FF4EC) # J 0x803FD3B0
+ rom.write_int32s(0xBFD3B0, patches.countdown_number_displayer)
+ rom.write_int32s(0xBFD6DC, patches.countdown_number_manager)
+ rom.write_int32s(0xBFE770, patches.countdown_demo_hider)
+ rom.write_int32(0xBFCE2C, 0x080FF5D2) # J 0x803FD748
+ rom.write_int32s(0xBB168, [0x080FF5F4, # J 0x803FD7D0
+ 0x8E020028]) # LW V0, 0x0028 (S0)
+ rom.write_int32s(0xBB1D0, [0x080FF5FB, # J 0x803FD7EC
+ 0x8E020028]) # LW V0, 0x0028 (S0)
+ rom.write_int32(0xBC4A0, 0x080FF5E6) # J 0x803FD798
+ rom.write_int32(0xBC4C4, 0x080FF5E6) # J 0x803FD798
+ rom.write_int32(0x19844, 0x080FF602) # J 0x803FD808
+ # If the option is set to "all locations", count it down no matter what the item is.
+ if options.countdown == Countdown.option_all_locations:
+ rom.write_int32s(0xBFD71C, [0x01010101, 0x01010101, 0x01010101, 0x01010101, 0x01010101, 0x01010101,
+ 0x01010101, 0x01010101, 0x01010101, 0x01010101, 0x01010101])
+ else:
+ # If it's majors, then insert this last minute check I threw together for the weird edge case of a CV64 ice
+ # trap for another CV64 player taking the form of a major.
+ rom.write_int32s(0xBFD788, [0x080FF717, # J 0x803FDC5C
+ 0x2529FFFF]) # ADDIU T1, T1, 0xFFFF
+ rom.write_int32s(0xBFDC5C, patches.countdown_extra_safety_check)
+ rom.write_int32(0xA9ECC, 0x00000000) # NOP the pointless overwrite of the item actor appearance custom value.
+
+ # Ice Trap stuff
+ rom.write_int32(0x697C60, 0x080FF06B) # J 0x803FC18C
+ rom.write_int32(0x6A5160, 0x080FF06B) # J 0x803FC18C
+ rom.write_int32s(0xBFC1AC, patches.ice_trap_initializer)
+ rom.write_int32s(0xBFE700, patches.the_deep_freezer)
+ rom.write_int32s(0xB2F354, [0x3739E4C0, # ORI T9, T9, 0xE4C0
+ 0x03200008, # JR T9
+ 0x00000000]) # NOP
+ rom.write_int32s(0xBFE4C0, patches.freeze_verifier)
+
+ # Initial Countdown numbers
+ rom.write_int32(0xAD6A8, 0x080FF60A) # J 0x803FD828
+ rom.write_int32s(0xBFD828, patches.new_game_extras)
+
+ # Everything related to shopsanity
+ if options.shopsanity:
+ rom.write_byte(0xBFBFDF, 0x01)
+ rom.write_bytes(0x103868, cv64_string_to_bytearray("Not obtained. "))
+ rom.write_int32s(0xBFD8D0, patches.shopsanity_stuff)
+ rom.write_int32(0xBD828, 0x0C0FF643) # JAL 0x803FD90C
+ rom.write_int32(0xBD5B8, 0x0C0FF651) # JAL 0x803FD944
+ rom.write_int32(0xB0610, 0x0C0FF665) # JAL 0x803FD994
+ rom.write_int32s(0xBD24C, [0x0C0FF677, # J 0x803FD9DC
+ 0x00000000]) # NOP
+ rom.write_int32(0xBD618, 0x0C0FF684) # JAL 0x803FDA10
+
+ shopsanity_name_text = []
+ shopsanity_desc_text = []
+ for i in range(len(shop_name_list)):
+ shopsanity_name_text += bytearray([0xA0, i]) + shop_colors_list[i] + \
+ cv64_string_to_bytearray(cv64_text_truncate(shop_name_list[i], 74))
+
+ shopsanity_desc_text += [0xA0, i]
+ if shop_desc_list[i][1] is not None:
+ shopsanity_desc_text += cv64_string_to_bytearray("For " + shop_desc_list[i][1] + ".\n",
+ append_end=False)
+ shopsanity_desc_text += cv64_string_to_bytearray(renon_item_dialogue[shop_desc_list[i][0]])
+ rom.write_bytes(0x1AD00, shopsanity_name_text)
+ rom.write_bytes(0x1A800, shopsanity_desc_text)
+
+ # Panther Dash running
+ if options.panther_dash:
+ rom.write_int32(0x69C8C4, 0x0C0FF77E) # JAL 0x803FDDF8
+ rom.write_int32(0x6AA228, 0x0C0FF77E) # JAL 0x803FDDF8
+ rom.write_int32s(0x69C86C, [0x0C0FF78E, # JAL 0x803FDE38
+ 0x3C01803E]) # LUI AT, 0x803E
+ rom.write_int32s(0x6AA1D0, [0x0C0FF78E, # JAL 0x803FDE38
+ 0x3C01803E]) # LUI AT, 0x803E
+ rom.write_int32(0x69D37C, 0x0C0FF79E) # JAL 0x803FDE78
+ rom.write_int32(0x6AACE0, 0x0C0FF79E) # JAL 0x803FDE78
+ rom.write_int32s(0xBFDDF8, patches.panther_dash)
+ # Jump prevention
+ if options.panther_dash == PantherDash.option_jumpless:
+ rom.write_int32(0xBFDE2C, 0x080FF7BB) # J 0x803FDEEC
+ rom.write_int32(0xBFD044, 0x080FF7B1) # J 0x803FDEC4
+ rom.write_int32s(0x69B630, [0x0C0FF7C6, # JAL 0x803FDF18
+ 0x8CCD0000]) # LW T5, 0x0000 (A2)
+ rom.write_int32s(0x6A8EC0, [0x0C0FF7C6, # JAL 0x803FDF18
+ 0x8CCC0000]) # LW T4, 0x0000 (A2)
+ # Fun fact: KCEK put separate code to handle coyote time jumping
+ rom.write_int32s(0x69910C, [0x0C0FF7C6, # JAL 0x803FDF18
+ 0x8C4E0000]) # LW T6, 0x0000 (V0)
+ rom.write_int32s(0x6A6718, [0x0C0FF7C6, # JAL 0x803FDF18
+ 0x8C4E0000]) # LW T6, 0x0000 (V0)
+ rom.write_int32s(0xBFDEC4, patches.panther_jump_preventer)
+
+ # Everything related to Big Toss.
+ if options.big_toss:
+ rom.write_int32s(0x27E90, [0x0C0FFA38, # JAL 0x803FE8E0
+ 0xAFB80074]) # SW T8, 0x0074 (SP)
+ rom.write_int32(0x26F54, 0x0C0FFA4D) # JAL 0x803FE934
+ rom.write_int32s(0xBFE8E0, patches.big_tosser)
+
+ # Write all the new randomized bytes.
+ for offset, item_id in offset_data.items():
+ if item_id <= 0xFF:
+ rom.write_byte(offset, item_id)
+ elif item_id <= 0xFFFF:
+ rom.write_int16(offset, item_id)
+ elif item_id <= 0xFFFFFF:
+ rom.write_int24(offset, item_id)
+ else:
+ rom.write_int32(offset, item_id)
+
+ # Write the secondary name the client will use to distinguish a vanilla ROM from an AP one.
+ rom.write_bytes(0xBFBFD0, "ARCHIPELAGO1".encode("utf-8"))
+ # Write the slot authentication
+ rom.write_bytes(0xBFBFE0, world.auth)
+
+ # Write the specified window colors
+ rom.write_byte(0xAEC23, options.window_color_r.value << 4)
+ rom.write_byte(0xAEC33, options.window_color_g.value << 4)
+ rom.write_byte(0xAEC47, options.window_color_b.value << 4)
+ rom.write_byte(0xAEC43, options.window_color_a.value << 4)
+
+ # Write the item/player names for other game items
+ for loc in active_locations:
+ if loc.address is None or get_location_info(loc.name, "type") == "shop" or loc.item.player == player:
+ continue
+ if len(loc.item.name) > 67:
+ item_name = loc.item.name[0x00:0x68]
+ else:
+ item_name = loc.item.name
+ inject_address = 0xBB7164 + (256 * (loc.address & 0xFFF))
+ wrapped_name, num_lines = cv64_text_wrap(item_name + "\nfor " + multiworld.get_player_name(loc.item.player), 96)
+ rom.write_bytes(inject_address, get_item_text_color(loc) + cv64_string_to_bytearray(wrapped_name))
+ rom.write_byte(inject_address + 255, num_lines)
+
+ # Everything relating to loading the other game items text
+ rom.write_int32(0xA8D8C, 0x080FF88F) # J 0x803FE23C
+ rom.write_int32(0xBEA98, 0x0C0FF8B4) # JAL 0x803FE2D0
+ rom.write_int32(0xBEAB0, 0x0C0FF8BD) # JAL 0x803FE2F8
+ rom.write_int32(0xBEACC, 0x0C0FF8C5) # JAL 0x803FE314
+ rom.write_int32s(0xBFE23C, patches.multiworld_item_name_loader)
+ rom.write_bytes(0x10F188, [0x00 for _ in range(264)])
+ rom.write_bytes(0x10F298, [0x00 for _ in range(264)])
+
+ # When the game normally JALs to the item prepare textbox function after the player picks up an item, set the
+ # "no receiving" timer to ensure the item textbox doesn't freak out if you pick something up while there's a queue
+ # of unreceived items.
+ rom.write_int32(0xA8D94, 0x0C0FF9F0) # JAL 0x803FE7C0
+ rom.write_int32s(0xBFE7C0, [0x3C088039, # LUI T0, 0x8039
+ 0x24090020, # ADDIU T1, R0, 0x0020
+ 0x0804EDCE, # J 0x8013B738
+ 0xA1099BE0]) # SB T1, 0x9BE0 (T0)
+
+
+class CV64DeltaPatch(APDeltaPatch):
+ hash = CV64US10HASH
+ patch_file_ending: str = ".apcv64"
+ result_file_ending: str = ".z64"
+
+ game = "Castlevania 64"
+
+ @classmethod
+ def get_source_data(cls) -> bytes:
+ return get_base_rom_bytes()
+
+ def patch(self, target: str):
+ super().patch(target)
+ rom = LocalRom(target)
+
+ # Extract the item models file, decompress it, append the AP icons, compress it back, re-insert it.
+ items_file = lzkn64.decompress_buffer(rom.read_bytes(0x9C5310, 0x3D28))
+ compressed_file = lzkn64.compress_buffer(items_file[0:0x69B6] + pkgutil.get_data(__name__, "data/ap_icons.bin"))
+ rom.write_bytes(0xBB2D88, compressed_file)
+ # Update the items' Nisitenma-Ichigo table entry to point to the new file's start and end addresses in the ROM.
+ rom.write_int32s(0x95F04, [0x80BB2D88, 0x00BB2D88 + len(compressed_file)])
+ # Update the items' decompressed file size tables with the new file's decompressed file size.
+ rom.write_int16(0x95706, 0x7BF0)
+ rom.write_int16(0x104CCE, 0x7BF0)
+ # Update the Wooden Stake and Roses' item appearance settings table to point to the Archipelago item graphics.
+ rom.write_int16(0xEE5BA, 0x7B38)
+ rom.write_int16(0xEE5CA, 0x7280)
+ # Change the items' sizes. The progression one will be larger than the non-progression one.
+ rom.write_int32(0xEE5BC, 0x3FF00000)
+ rom.write_int32(0xEE5CC, 0x3FA00000)
+ rom.write_to_file(target)
+
+
+def get_base_rom_bytes(file_name: str = "") -> bytes:
+ base_rom_bytes = getattr(get_base_rom_bytes, "base_rom_bytes", None)
+ if not base_rom_bytes:
+ file_name = get_base_rom_path(file_name)
+ base_rom_bytes = bytes(open(file_name, "rb").read())
+
+ basemd5 = hashlib.md5()
+ basemd5.update(base_rom_bytes)
+ if CV64US10HASH != basemd5.hexdigest():
+ raise Exception("Supplied Base Rom does not match known MD5 for Castlevania 64 US 1.0."
+ "Get the correct game and version, then dump it.")
+ setattr(get_base_rom_bytes, "base_rom_bytes", base_rom_bytes)
+ return base_rom_bytes
+
+
+def get_base_rom_path(file_name: str = "") -> str:
+ if not file_name:
+ file_name = get_settings()["cv64_options"]["rom_file"]
+ if not os.path.exists(file_name):
+ file_name = Utils.user_path(file_name)
+ return file_name
diff --git a/worlds/cv64/rules.py b/worlds/cv64/rules.py
new file mode 100644
index 00000000000..9642073341e
--- /dev/null
+++ b/worlds/cv64/rules.py
@@ -0,0 +1,103 @@
+from typing import Dict, TYPE_CHECKING
+
+from BaseClasses import CollectionState
+from worlds.generic.Rules import allow_self_locking_items, CollectionRule
+from .options import DraculasCondition
+from .entrances import get_entrance_info
+from .data import iname, rname
+
+if TYPE_CHECKING:
+ from . import CV64World
+
+
+class CV64Rules:
+ player: int
+ world: "CV64World"
+ rules: Dict[str, CollectionRule]
+ s1s_per_warp: int
+ required_s2s: int
+ drac_condition: int
+
+ def __init__(self, world: "CV64World") -> None:
+ self.player = world.player
+ self.world = world
+ self.s1s_per_warp = world.s1s_per_warp
+ self.required_s2s = world.required_s2s
+ self.drac_condition = world.drac_condition
+
+ self.rules = {
+ iname.left_tower_key: lambda state: state.has(iname.left_tower_key, self.player),
+ iname.storeroom_key: lambda state: state.has(iname.storeroom_key, self.player),
+ iname.archives_key: lambda state: state.has(iname.archives_key, self.player),
+ iname.garden_key: lambda state: state.has(iname.garden_key, self.player),
+ iname.copper_key: lambda state: state.has(iname.copper_key, self.player),
+ iname.chamber_key: lambda state: state.has(iname.chamber_key, self.player),
+ "Bomb 1": lambda state: state.has_all({iname.magical_nitro, iname.mandragora}, self.player),
+ "Bomb 2": lambda state: state.has(iname.magical_nitro, self.player, 2)
+ and state.has(iname.mandragora, self.player, 2),
+ iname.execution_key: lambda state: state.has(iname.execution_key, self.player),
+ iname.science_key1: lambda state: state.has(iname.science_key1, self.player),
+ iname.science_key2: lambda state: state.has(iname.science_key2, self.player),
+ iname.science_key3: lambda state: state.has(iname.science_key3, self.player),
+ iname.clocktower_key1: lambda state: state.has(iname.clocktower_key1, self.player),
+ iname.clocktower_key2: lambda state: state.has(iname.clocktower_key2, self.player),
+ iname.clocktower_key3: lambda state: state.has(iname.clocktower_key3, self.player),
+ "Dracula": self.can_enter_dracs_chamber
+ }
+
+ def can_enter_dracs_chamber(self, state: CollectionState) -> bool:
+ drac_object_name = None
+ if self.drac_condition == DraculasCondition.option_crystal:
+ drac_object_name = "Crystal"
+ elif self.drac_condition == DraculasCondition.option_bosses:
+ drac_object_name = "Trophy"
+ elif self.drac_condition == DraculasCondition.option_specials:
+ drac_object_name = "Special2"
+
+ if drac_object_name is not None:
+ return state.has(drac_object_name, self.player, self.required_s2s)
+ return True
+
+ def set_cv64_rules(self) -> None:
+ multiworld = self.world.multiworld
+
+ for region in multiworld.get_regions(self.player):
+ # Set each entrance's rule if it should have one.
+ # Warp entrances have their own special handling.
+ for entrance in region.entrances:
+ if entrance.parent_region.name == "Menu":
+ if entrance.name.startswith("Warp "):
+ entrance.access_rule = lambda state, warp_num=int(entrance.name[5]): \
+ state.has(iname.special_one, self.player, self.s1s_per_warp * warp_num)
+ else:
+ ent_rule = get_entrance_info(entrance.name, "rule")
+ if ent_rule in self.rules:
+ entrance.access_rule = self.rules[ent_rule]
+
+ multiworld.completion_condition[self.player] = lambda state: state.has(iname.victory, self.player)
+ if self.world.options.accessibility: # not locations accessibility
+ self.set_self_locking_items()
+
+ def set_self_locking_items(self) -> None:
+ multiworld = self.world.multiworld
+
+ # Do the regions that we know for a fact always exist, and we always do no matter what.
+ allow_self_locking_items(multiworld.get_region(rname.villa_archives, self.player), iname.archives_key)
+ allow_self_locking_items(multiworld.get_region(rname.cc_torture_chamber, self.player), iname.chamber_key)
+
+ # Add this region if the world doesn't have the Villa Storeroom warp entrance.
+ if "Villa" not in self.world.active_warp_list[1:]:
+ allow_self_locking_items(multiworld.get_region(rname.villa_storeroom, self.player), iname.storeroom_key)
+
+ # Add this region if Hard Logic is on and Multi Hit Breakables are off.
+ if self.world.options.hard_logic and not self.world.options.multi_hit_breakables:
+ allow_self_locking_items(multiworld.get_region(rname.cw_ltower, self.player), iname.left_tower_key)
+
+ # Add these regions if Tower of Science is in the world.
+ if "Tower of Science" in self.world.active_stage_exits:
+ allow_self_locking_items(multiworld.get_region(rname.tosci_three_doors, self.player), iname.science_key1)
+ allow_self_locking_items(multiworld.get_region(rname.tosci_key3, self.player), iname.science_key3)
+
+ # Add this region if Tower of Execution is in the world and Hard Logic is not on.
+ if "Tower of Execution" in self.world.active_stage_exits and self.world.options.hard_logic:
+ allow_self_locking_items(multiworld.get_region(rname.toe_ledge, self.player), iname.execution_key)
diff --git a/worlds/cv64/src/drop_sub_weapon.c b/worlds/cv64/src/drop_sub_weapon.c
new file mode 100644
index 00000000000..d1a4b52269c
--- /dev/null
+++ b/worlds/cv64/src/drop_sub_weapon.c
@@ -0,0 +1,69 @@
+// Written by Moisés
+#include "include/game/module.h"
+#include "include/game/math.h"
+#include "cv64.h"
+
+extern vec3f player_pos;
+extern vec3s player_angle; // player_angle.y = Player's facing angle (yaw)
+extern f32 player_height_with_respect_of_floor; // Stored negative in-game
+
+#define SHT_MAX 32767.0f
+#define SHT_MINV (1.0f / SHT_MAX)
+
+void spawn_item_behind_player(s32 item) {
+ interactuablesModule* pickable_item = NULL;
+ const f32 spawnDistance = 8.0f;
+ vec3f player_backwards_dir;
+
+ pickable_item = (interactuablesModule*)module_createAndSetChild(moduleList_findFirstModuleByID(ACTOR_CREATOR), ACTOR_ITEM);
+ if (pickable_item != NULL) {
+ // Convert facing angle to a vec3f
+ // SHT_MINV needs to be negative here for the item to be spawned properly on the character's back
+ player_backwards_dir.x = coss(-player_angle.y) * -SHT_MINV;
+ player_backwards_dir.z = sins(-player_angle.y) * -SHT_MINV;
+ // Multiply facing vector with distance away from the player
+ vec3f_multiplyScalar(&player_backwards_dir, &player_backwards_dir, spawnDistance);
+ // Assign the position of the item relative to the player's current position.
+ vec3f_add(&pickable_item->position, &player_pos, &player_backwards_dir);
+ // The Y position of the item will be the same as the floor right under the player
+ // The player's height with respect of the flower under them is already stored negative in-game,
+ // so no need to substract
+ pickable_item->position.y = player_pos.y + 5.0f;
+ pickable_item->height = pickable_item->position.y;
+
+ // Assign item ID
+ pickable_item->item_ID = item;
+ }
+}
+
+
+const f32 droppingAccel = 0.05f;
+const f32 maxDroppingSpeed = 1.5f;
+f32 droppingSpeed = 0.0f;
+f32 droppingTargetYPos = 0.0f;
+u8 dropItemCalcFuncCalled = FALSE;
+
+s32 drop_item_calc(interactuablesModule* pickable_item) {
+ if (dropItemCalcFuncCalled == FALSE) {
+ droppingTargetYPos = player_pos.y + player_height_with_respect_of_floor + 1.0f;
+ if (pickable_item->item_ID == CROSS || pickable_item->item_ID == AXE ||
+ pickable_item->item_ID == CROSS__VANISH || pickable_item->item_ID == AXE__VANISH) {
+ droppingTargetYPos += 3.0f;
+ }
+ dropItemCalcFuncCalled = TRUE;
+ return TRUE;
+ }
+ if (pickable_item->position.y <= droppingTargetYPos) {
+ droppingSpeed = 0.0f;
+ dropItemCalcFuncCalled = FALSE;
+ return FALSE;
+ }
+ else {
+ if (droppingSpeed < maxDroppingSpeed) {
+ droppingSpeed += droppingAccel;
+ }
+ pickable_item->position.y -= droppingSpeed;
+ pickable_item->height = pickable_item->position.y;
+ return TRUE;
+ }
+}
\ No newline at end of file
diff --git a/worlds/cv64/src/print.c b/worlds/cv64/src/print.c
new file mode 100644
index 00000000000..7f77afb00f0
--- /dev/null
+++ b/worlds/cv64/src/print.c
@@ -0,0 +1,116 @@
+// Written by Moisés.
+// NOTE: This is an earlier version to-be-replaced.
+#include
+#include
+
+// Helper function
+// https://decomp.me/scratch/9H1Uy
+u32 convertUTF8StringToUTF16(char* src, u16* buffer) {
+ u32 string_length = 0;
+
+ // If the source string starts with a null char (0), we assume the string empty.
+ if (*src != 0) {
+ // Copy the char from the source string into the bufferination.
+ // Then advance to the next char until we find the null char (0).
+ do {
+ *buffer = *src;
+ src++;
+ buffer++;
+ string_length++;
+ } while (*src != 0);
+ }
+ // Make sure to add the null char at the end of the bufferination string,
+ // and then return the length of the string.
+ *buffer = 0;
+ return string_length;
+}
+
+// Begin printing ASCII text stored in a char*
+textbox* print_text(const char* message, const s16 X_pos, const s16 Y_pos, const u8 number_of_lines, const s16 textbox_width, const u32 txtbox_flags, const void* module) {
+ textbox* (*ptr_textbox_create)(void*, void*, u32) = textbox_create;
+ void (*ptr_textbox_setPos)(textbox*, u16, u16, s32) = textbox_setPos;
+ void (*ptr_textbox_setDimensions)(textbox*, s8, s16, u8, u8) = textbox_setDimensions;
+ void (*ptr_textbox_setMessagePtr)(textbox*, u16*, s32, s16) = textbox_setMessagePtr;
+ u16* (*ptr_convertUTF16ToCustomTextFormat)(u16*) = convertUTF16ToCustomTextFormat;
+ void* (*ptr_malloc)(s32, u32) = malloc;
+
+ textbox* txtbox = NULL;
+
+ // Allocate memory for the text buffer
+ u16* text_buffer = (u16*) ptr_malloc(0, 100);
+
+ // Create the textbox data structure
+ if (module != NULL) {
+ txtbox = ptr_textbox_create(module, HUD_camera, txtbox_flags);
+ }
+
+ if (txtbox != NULL && text_buffer != NULL && message != NULL) {
+ // Set text position and dimensions
+ ptr_textbox_setPos(txtbox, X_pos, Y_pos, 1);
+ ptr_textbox_setDimensions(txtbox, number_of_lines, textbox_width, 0, 0);
+
+ // Convert the ASCII message to the CV64 custom format
+ convertUTF8StringToUTF16(message, text_buffer);
+ ptr_convertUTF16ToCustomTextFormat(text_buffer);
+
+ // Set the text buffer pointer to the textbox data structure
+ ptr_textbox_setMessagePtr(txtbox, text_buffer, 0, 0);
+ }
+ // We return the textbox so that we can modify its properties once it begins printing
+ // (say to show, hide the text)
+ return txtbox;
+}
+
+// Begin printing signed integer
+textbox* print_number(const s32 number, u16* text_buffer, const s16 X_pos, const s16 Y_pos, const u8 number_of_digits, const u32 txtbox_flags, const u32 additional_text_flag, const void* module) {
+ textbox* (*ptr_textbox_create)(void*, void*, u32) = textbox_create;
+ void (*ptr_textbox_setPos)(textbox*, u16, u16, s32) = textbox_setPos;
+ void (*ptr_textbox_setDimensions)(textbox*, s8, s16, u8, u8) = textbox_setDimensions;
+ void (*ptr_textbox_setMessagePtr)(textbox*, u16*, s32, s16) = textbox_setMessagePtr;
+ void (*ptr_text_convertIntNumberToText)(u32, u16*, u8, u32) = text_convertIntNumberToText;
+
+ textbox* txtbox = NULL;
+
+ // Create the textbox data structure
+ if (module != NULL) {
+ txtbox = ptr_textbox_create(module, HUD_camera, txtbox_flags);
+ }
+
+ if (txtbox != NULL && text_buffer != NULL) {
+ // Set text position and dimensions
+ ptr_textbox_setPos(txtbox, X_pos, Y_pos, 1);
+ ptr_textbox_setDimensions(txtbox, 1, 100, 0, 0);
+
+ // Convert the number to the CV64 custom format
+ ptr_text_convertIntNumberToText(number, text_buffer, number_of_digits, additional_text_flag);
+
+ // Set the text buffer pointer to the textbox data structure
+ ptr_textbox_setMessagePtr(txtbox, text_buffer, 0, 0);
+ }
+ // We return the textbox so that we can modify its properties once it begins printing
+ // (say to show, hide the text)
+ return txtbox;
+}
+
+// Update the value of a number that began printing after calling "print_number()"
+void update_printed_number(textbox* txtbox, const s32 number, u16* text_buffer, const u8 number_of_digits, const u32 additional_text_flag) {
+ void (*ptr_text_convertIntNumberToText)(u32, u16*, u8, u32) = text_convertIntNumberToText;
+
+ if (text_buffer != NULL) {
+ ptr_text_convertIntNumberToText(number, text_buffer, number_of_digits, additional_text_flag);
+ txtbox->flags |= 0x1000000; // Needed to make sure the number updates properly
+ }
+}
+
+void display_text(textbox* txtbox, const u8 display_textbox) {
+ if (txtbox != NULL) {
+ if (display_textbox == TRUE) {
+ // Show text
+ txtbox->flags &= ~HIDE_TEXTBOX;
+ }
+ else {
+ // Hide text
+ txtbox->flags |= HIDE_TEXTBOX;
+ }
+ }
+}
diff --git a/worlds/cv64/src/print_text_ovl.c b/worlds/cv64/src/print_text_ovl.c
new file mode 100644
index 00000000000..7ca8e6f35e2
--- /dev/null
+++ b/worlds/cv64/src/print_text_ovl.c
@@ -0,0 +1,26 @@
+// Written by Moisés
+#include "print.h"
+#include
+#include
+
+#define counter_X_pos 30
+#define counter_Y_pos 40
+#define counter_number_of_digits 2
+#define GOLD_JEWEL_FONT 0x14
+
+extern u8 bytes[13];
+
+u16* number_text_buffer = NULL;
+textbox* txtbox = NULL;
+
+void begin_print() {
+ // Allocate memory for the number text
+ number_text_buffer = (u16*) malloc(0, 12);
+
+ // Assuming that 0x80342814 = HUD Module
+ txtbox = print_number(0, number_text_buffer, counter_X_pos, counter_Y_pos, counter_number_of_digits, 0x08600000, GOLD_JEWEL_FONT, (void*) 0x80342814);
+}
+
+void update_print(u8 i) {
+ update_printed_number(txtbox, (s32) bytes[i], number_text_buffer, counter_number_of_digits, GOLD_JEWEL_FONT);
+}
diff --git a/worlds/cv64/stages.py b/worlds/cv64/stages.py
new file mode 100644
index 00000000000..a6fa6679214
--- /dev/null
+++ b/worlds/cv64/stages.py
@@ -0,0 +1,490 @@
+import logging
+
+from .data import rname
+from .regions import get_region_info
+from .locations import get_location_info
+from .options import WarpOrder
+
+from typing import TYPE_CHECKING, Dict, List, Tuple, Union
+
+if TYPE_CHECKING:
+ from . import CV64World
+
+
+# # # KEY # # #
+# "start region" = The Region that the start of the stage is in. Used for connecting the previous stage's end and
+# alternate end (if it exists) Entrances to the start of the next one.
+# "start map id" = The map ID that the start of the stage is in.
+# "start spawn id" = The player spawn location ID for the start of the stage. This and "start map id" are both written
+# to the previous stage's end loading zone to make it send the player to the next stage in the
+# world's determined stage order.
+# "mid region" = The Region that the stage's middle warp point is in. Used for connecting the warp Entrances after the
+# starting stage to where they should be connecting to.
+# "mid map id" = The map ID that the stage's middle warp point is in.
+# "mid spawn id" = The player spawn location ID for the stage's middle warp point. This and "mid map id" are both
+# written to the warp menu code to make it send the player to where it should be sending them.
+# "end region" = The Region that the end of the stage is in. Used for connecting the next stage's beginning Entrance
+# (if it exists) to the end of the previous one.
+# "end map id" = The map ID that the end of the stage is in.
+# "end spawn id" = The player spawn location ID for the end of the stage. This and "end map id" are both written to the
+# next stage's beginning loading zone (if it exists) to make it send the player to the previous stage
+# in the world's determined stage order.
+# startzone map offset = The offset in the ROM to overwrite to change where the start of the stage leads.
+# startzone spawn offset = The offset in the ROM to overwrite to change what spawn location in the previous map the
+# start of the stage puts the player at.
+# endzone map offset = The offset in the ROM to overwrite to change where the end of the stage leads.
+# endzone spawn offset = The offset in the ROM to overwrite to change what spawn location in the next map the end of
+# the stage puts the player at.
+# altzone map offset = The offset in the ROM to overwrite to change where the alternate end of the stage leads
+# (if it exists).
+# altzone spawn offset = The offset in the ROM to overwrite to change what spawn location in the next map the alternate
+# end of the stage puts the player at.
+# character = What character that stage is exclusively meant for normally. Used in determining what stages to leave out
+# depending on what character stage setting was chosen in the player options.
+# save number offsets = The offsets to overwrite to change what stage number is displayed on the save file when saving
+# at the stage's White Jewels.
+# regions = All Regions that make up the stage. If the stage is in the world's active stages, its Regions and their
+# corresponding Locations and Entrances will all be created.
+stage_info = {
+ "Forest of Silence": {
+ "start region": rname.forest_start, "start map id": 0x00, "start spawn id": 0x00,
+ "mid region": rname.forest_mid, "mid map id": 0x00, "mid spawn id": 0x04,
+ "end region": rname.forest_end, "end map id": 0x00, "end spawn id": 0x01,
+ "endzone map offset": 0xB6302F, "endzone spawn offset": 0xB6302B,
+ "save number offsets": [0x1049C5, 0x1049CD, 0x1049D5],
+ "regions": [rname.forest_start,
+ rname.forest_mid,
+ rname.forest_end]
+ },
+
+ "Castle Wall": {
+ "start region": rname.cw_start, "start map id": 0x02, "start spawn id": 0x00,
+ "mid region": rname.cw_start, "mid map id": 0x02, "mid spawn id": 0x07,
+ "end region": rname.cw_exit, "end map id": 0x02, "end spawn id": 0x10,
+ "endzone map offset": 0x109A5F, "endzone spawn offset": 0x109A61,
+ "save number offsets": [0x1049DD, 0x1049E5, 0x1049ED],
+ "regions": [rname.cw_start,
+ rname.cw_exit,
+ rname.cw_ltower]
+ },
+
+ "Villa": {
+ "start region": rname.villa_start, "start map id": 0x03, "start spawn id": 0x00,
+ "mid region": rname.villa_storeroom, "mid map id": 0x05, "mid spawn id": 0x04,
+ "end region": rname.villa_crypt, "end map id": 0x1A, "end spawn id": 0x03,
+ "endzone map offset": 0xD9DA3, "endzone spawn offset": 0x109E81,
+ "altzone map offset": 0xD9DAB, "altzone spawn offset": 0x109E81,
+ "save number offsets": [0x1049F5, 0x1049FD, 0x104A05, 0x104A0D],
+ "regions": [rname.villa_start,
+ rname.villa_main,
+ rname.villa_storeroom,
+ rname.villa_archives,
+ rname.villa_maze,
+ rname.villa_servants,
+ rname.villa_crypt]
+ },
+
+ "Tunnel": {
+ "start region": rname.tunnel_start, "start map id": 0x07, "start spawn id": 0x00,
+ "mid region": rname.tunnel_end, "mid map id": 0x07, "mid spawn id": 0x03,
+ "end region": rname.tunnel_end, "end map id": 0x07, "end spawn id": 0x11,
+ "endzone map offset": 0x109B4F, "endzone spawn offset": 0x109B51, "character": "Reinhardt",
+ "save number offsets": [0x104A15, 0x104A1D, 0x104A25, 0x104A2D],
+ "regions": [rname.tunnel_start,
+ rname.tunnel_end]
+ },
+
+ "Underground Waterway": {
+ "start region": rname.uw_main, "start map id": 0x08, "start spawn id": 0x00,
+ "mid region": rname.uw_main, "mid map id": 0x08, "mid spawn id": 0x03,
+ "end region": rname.uw_end, "end map id": 0x08, "end spawn id": 0x01,
+ "endzone map offset": 0x109B67, "endzone spawn offset": 0x109B69, "character": "Carrie",
+ "save number offsets": [0x104A35, 0x104A3D],
+ "regions": [rname.uw_main,
+ rname.uw_end]
+ },
+
+ "Castle Center": {
+ "start region": rname.cc_main, "start map id": 0x19, "start spawn id": 0x00,
+ "mid region": rname.cc_main, "mid map id": 0x0E, "mid spawn id": 0x03,
+ "end region": rname.cc_elev_top, "end map id": 0x0F, "end spawn id": 0x02,
+ "endzone map offset": 0x109CB7, "endzone spawn offset": 0x109CB9,
+ "altzone map offset": 0x109CCF, "altzone spawn offset": 0x109CD1,
+ "save number offsets": [0x104A45, 0x104A4D, 0x104A55, 0x104A5D, 0x104A65, 0x104A6D, 0x104A75],
+ "regions": [rname.cc_main,
+ rname.cc_torture_chamber,
+ rname.cc_library,
+ rname.cc_crystal,
+ rname.cc_elev_top]
+ },
+
+ "Duel Tower": {
+ "start region": rname.dt_main, "start map id": 0x13, "start spawn id": 0x00,
+ "startzone map offset": 0x109DA7, "startzone spawn offset": 0x109DA9,
+ "mid region": rname.dt_main, "mid map id": 0x13, "mid spawn id": 0x15,
+ "end region": rname.dt_main, "end map id": 0x13, "end spawn id": 0x01,
+ "endzone map offset": 0x109D8F, "endzone spawn offset": 0x109D91, "character": "Reinhardt",
+ "save number offsets": [0x104ACD],
+ "regions": [rname.dt_main]
+ },
+
+ "Tower of Execution": {
+ "start region": rname.toe_main, "start map id": 0x10, "start spawn id": 0x00,
+ "startzone map offset": 0x109D17, "startzone spawn offset": 0x109D19,
+ "mid region": rname.toe_main, "mid map id": 0x10, "mid spawn id": 0x02,
+ "end region": rname.toe_main, "end map id": 0x10, "end spawn id": 0x12,
+ "endzone map offset": 0x109CFF, "endzone spawn offset": 0x109D01, "character": "Reinhardt",
+ "save number offsets": [0x104A7D, 0x104A85],
+ "regions": [rname.toe_main,
+ rname.toe_ledge]
+ },
+
+ "Tower of Science": {
+ "start region": rname.tosci_start, "start map id": 0x12, "start spawn id": 0x00,
+ "startzone map offset": 0x109D77, "startzone spawn offset": 0x109D79,
+ "mid region": rname.tosci_conveyors, "mid map id": 0x12, "mid spawn id": 0x03,
+ "end region": rname.tosci_conveyors, "end map id": 0x12, "end spawn id": 0x04,
+ "endzone map offset": 0x109D5F, "endzone spawn offset": 0x109D61, "character": "Carrie",
+ "save number offsets": [0x104A95, 0x104A9D, 0x104AA5],
+ "regions": [rname.tosci_start,
+ rname.tosci_three_doors,
+ rname.tosci_conveyors,
+ rname.tosci_key3]
+ },
+
+ "Tower of Sorcery": {
+ "start region": rname.tosor_main, "start map id": 0x11, "start spawn id": 0x00,
+ "startzone map offset": 0x109D47, "startzone spawn offset": 0x109D49,
+ "mid region": rname.tosor_main, "mid map id": 0x11, "mid spawn id": 0x01,
+ "end region": rname.tosor_main, "end map id": 0x11, "end spawn id": 0x13,
+ "endzone map offset": 0x109D2F, "endzone spawn offset": 0x109D31, "character": "Carrie",
+ "save number offsets": [0x104A8D],
+ "regions": [rname.tosor_main]
+ },
+
+ "Room of Clocks": {
+ "start region": rname.roc_main, "start map id": 0x1B, "start spawn id": 0x00,
+ "mid region": rname.roc_main, "mid map id": 0x1B, "mid spawn id": 0x02,
+ "end region": rname.roc_main, "end map id": 0x1B, "end spawn id": 0x14,
+ "endzone map offset": 0x109EAF, "endzone spawn offset": 0x109EB1,
+ "save number offsets": [0x104AC5],
+ "regions": [rname.roc_main]
+ },
+
+ "Clock Tower": {
+ "start region": rname.ct_start, "start map id": 0x17, "start spawn id": 0x00,
+ "mid region": rname.ct_middle, "mid map id": 0x17, "mid spawn id": 0x02,
+ "end region": rname.ct_end, "end map id": 0x17, "end spawn id": 0x03,
+ "endzone map offset": 0x109E37, "endzone spawn offset": 0x109E39,
+ "save number offsets": [0x104AB5, 0x104ABD],
+ "regions": [rname.ct_start,
+ rname.ct_middle,
+ rname.ct_end]
+ },
+
+ "Castle Keep": {
+ "start region": rname.ck_main, "start map id": 0x14, "start spawn id": 0x02,
+ "mid region": rname.ck_main, "mid map id": 0x14, "mid spawn id": 0x03,
+ "end region": rname.ck_drac_chamber,
+ "save number offsets": [0x104AAD],
+ "regions": [rname.ck_main]
+ },
+}
+
+vanilla_stage_order = ("Forest of Silence", "Castle Wall", "Villa", "Tunnel", "Underground Waterway", "Castle Center",
+ "Duel Tower", "Tower of Execution", "Tower of Science", "Tower of Sorcery", "Room of Clocks",
+ "Clock Tower", "Castle Keep")
+
+# # # KEY # # #
+# "prev" = The previous stage in the line.
+# "next" = The next stage in the line.
+# "alt" = The alternate next stage in the line (if one exists).
+# "position" = The stage's number in the order of stages.
+# "path" = Character indicating whether the stage is on the main path or an alternate path, similar to Rondo of Blood.
+# Used in writing the randomized stage order to the spoiler.
+vanilla_stage_exits = {rname.forest_of_silence: {"prev": None, "next": rname.castle_wall,
+ "alt": None, "position": 1, "path": " "},
+ rname.castle_wall: {"prev": None, "next": rname.villa,
+ "alt": None, "position": 2, "path": " "},
+ rname.villa: {"prev": None, "next": rname.tunnel,
+ "alt": rname.underground_waterway, "position": 3, "path": " "},
+ rname.tunnel: {"prev": None, "next": rname.castle_center,
+ "alt": None, "position": 4, "path": " "},
+ rname.underground_waterway: {"prev": None, "next": rname.castle_center,
+ "alt": None, "position": 4, "path": "'"},
+ rname.castle_center: {"prev": None, "next": rname.duel_tower,
+ "alt": rname.tower_of_science, "position": 5, "path": " "},
+ rname.duel_tower: {"prev": rname.castle_center, "next": rname.tower_of_execution,
+ "alt": None, "position": 6, "path": " "},
+ rname.tower_of_execution: {"prev": rname.duel_tower, "next": rname.room_of_clocks,
+ "alt": None, "position": 7, "path": " "},
+ rname.tower_of_science: {"prev": rname.castle_center, "next": rname.tower_of_sorcery,
+ "alt": None, "position": 6, "path": "'"},
+ rname.tower_of_sorcery: {"prev": rname.tower_of_science, "next": rname.room_of_clocks,
+ "alt": None, "position": 7, "path": "'"},
+ rname.room_of_clocks: {"prev": None, "next": rname.clock_tower,
+ "alt": None, "position": 8, "path": " "},
+ rname.clock_tower: {"prev": None, "next": rname.castle_keep,
+ "alt": None, "position": 9, "path": " "},
+ rname.castle_keep: {"prev": None, "next": None,
+ "alt": None, "position": 10, "path": " "}}
+
+
+def get_stage_info(stage: str, info: str) -> Union[str, int, Union[List[int], List[str]], None]:
+ return stage_info[stage].get(info, None)
+
+
+def get_locations_from_stage(stage: str) -> List[str]:
+ overall_locations = []
+ for region in get_stage_info(stage, "regions"):
+ stage_locations = get_region_info(region, "locations")
+ if stage_locations is not None:
+ overall_locations += stage_locations
+
+ final_locations = []
+ for loc in overall_locations:
+ if get_location_info(loc, "code") is not None:
+ final_locations.append(loc)
+ return final_locations
+
+
+def verify_character_stage(world: "CV64World", stage: str) -> bool:
+ # Verify a character stage is in the world if the given stage is a character stage.
+ stage_char = get_stage_info(stage, "character")
+ return stage_char is None or (world.reinhardt_stages and stage_char == "Reinhardt") or \
+ (world.carrie_stages and stage_char == "Carrie")
+
+
+def get_normal_stage_exits(world: "CV64World") -> Dict[str, dict]:
+ exits = {name: vanilla_stage_exits[name].copy() for name in vanilla_stage_exits}
+ non_branching_pos = 1
+
+ for stage in stage_info:
+ # Remove character stages that are not enabled.
+ if not verify_character_stage(world, stage):
+ del exits[stage]
+ continue
+
+ # If branching pathways are not enabled, update the exit info to converge said stages on a single path.
+ if world.branching_stages:
+ continue
+ if world.carrie_stages and not world.reinhardt_stages and exits[stage]["alt"] is not None:
+ exits[stage]["next"] = exits[stage]["alt"]
+ elif world.carrie_stages and world.reinhardt_stages and stage != rname.castle_keep:
+ exits[stage]["next"] = vanilla_stage_order[vanilla_stage_order.index(stage) + 1]
+ exits[stage]["alt"] = None
+ exits[stage]["position"] = non_branching_pos
+ exits[stage]["path"] = " "
+ non_branching_pos += 1
+
+ return exits
+
+
+def shuffle_stages(world: "CV64World", stage_1_blacklist: List[str]) \
+ -> Tuple[Dict[str, Dict[str, Union[str, int, None]]], str, List[str]]:
+ """Woah, this is a lot! I should probably summarize what's happening in here, huh?
+
+ So, in the vanilla game, all the stages are basically laid out on a linear "timeline" with some stages being
+ different depending on who you are playing as. The different character stages, in question, are the one following
+ Villa and the two following Castle Center. The ends of these two stages are considered the route divergences and, in
+ this rando, the game's behavior has been changed in such that both characters can access each other's exclusive
+ stages (thereby making the entire game playable in just one character run). With this in mind, when shuffling the
+ stages around, there is one particularly big rule that must be kept in mind to ensure things don't get too wacky.
+ That being:
+
+ Villa and Castle Center cannot appear in branching path stage slots; they can only be on "main" path slots.
+
+ So for this reason, generating a new stage layout is not as simple as just scrambling a list of stages around. It
+ must be done in such a way that whatever stages directly follow Villa or CC is not the other stage. The exception is
+ if branching stages are not a thing at all due to the player settings, in which case everything I said above does
+ not matter. Consider the following representation of a stage "timeline", wherein each "-" represents a main stage
+ and a "=" represents a pair of branching stages:
+
+ -==---=---
+
+ In the above example, CC is the first "-" and Villa is the fourth. CC and Villa can only be "-"s whereas every other
+ stage can be literally anywhere, including on one of the "=" dashes. Villa will always be followed by one pair of
+ branching stages and CC will be followed by two pairs.
+
+ This code starts by first generating a singular list of stages that fit the criteria of Castle Center not being in
+ the next two entries following Villa and Villa not being in the next four entries after Castle Center. Once that has
+ been figured out, it will then generate a dictionary of stages with the appropriate information regarding what
+ stages come before and after them to then be used for Entrance creation as well as what position in the list they
+ are in for the purposes of the spoiler log and extended hint information.
+
+ I opted to use the Rondo of Blood "'" stage notation to represent Carrie stage slots specifically. If a main stage
+ with a backwards connection connects backwards into a pair of branching stages, it will be the non-"'" stage
+ (Reinhardt's) that it connects to. The Carrie stage slot cannot be accessed this way.
+
+ If anyone has any ideas or suggestions on how to improve this, I'd love to hear them! Because it's only going to get
+ uglier come Legacy of Darkness and Cornell's funny side route later on.
+ """
+
+ starting_stage_value = world.options.starting_stage.value
+
+ # Verify the starting stage is valid. If it isn't, pick a stage at random.
+ if vanilla_stage_order[starting_stage_value] not in stage_1_blacklist and \
+ verify_character_stage(world, vanilla_stage_order[starting_stage_value]):
+ starting_stage = vanilla_stage_order[starting_stage_value]
+ else:
+ logging.warning(f"[{world.multiworld.player_name[world.player]}] {vanilla_stage_order[starting_stage_value]} "
+ f"cannot be the starting stage with the chosen settings. Picking a different stage instead...")
+ possible_stages = []
+ for stage in vanilla_stage_order:
+ if stage in world.active_stage_exits and stage != rname.castle_keep:
+ possible_stages.append(stage)
+ starting_stage = world.random.choice(possible_stages)
+ world.options.starting_stage.value = vanilla_stage_order.index(starting_stage)
+
+ remaining_stage_pool = [stage for stage in world.active_stage_exits]
+ remaining_stage_pool.remove(rname.castle_keep)
+
+ total_stages = len(remaining_stage_pool)
+
+ new_stage_order = []
+ villa_cc_ids = [2, 3]
+ alt_villa_stage = []
+ alt_cc_stages = []
+
+ # If there are branching stages, remove Villa and CC from the list and determine their placements first.
+ if world.branching_stages:
+ villa_cc_ids = world.random.sample(range(1, 5), 2)
+ remaining_stage_pool.remove(rname.villa)
+ remaining_stage_pool.remove(rname.castle_center)
+
+ # Remove the starting stage from the remaining pool if it's in there at this point.
+ if starting_stage in remaining_stage_pool:
+ remaining_stage_pool.remove(starting_stage)
+
+ # If Villa or CC is our starting stage, force its respective ID to be 0 and re-randomize the other.
+ if starting_stage == rname.villa:
+ villa_cc_ids[0] = 0
+ villa_cc_ids[1] = world.random.randint(1, 5)
+ elif starting_stage == rname.castle_center:
+ villa_cc_ids[1] = 0
+ villa_cc_ids[0] = world.random.randint(1, 5)
+
+ for i in range(total_stages):
+ # If we're on Villa or CC's ID while in branching stage mode, put the respective stage in the slot.
+ if world.branching_stages and i == villa_cc_ids[0] and rname.villa not in new_stage_order:
+ new_stage_order.append(rname.villa)
+ villa_cc_ids[1] += 2
+ elif world.branching_stages and i == villa_cc_ids[1] and rname.castle_center not in new_stage_order:
+ new_stage_order.append(rname.castle_center)
+ villa_cc_ids[0] += 4
+ else:
+ # If neither of the above are true, if we're looking at Stage 1, append the starting stage.
+ # Otherwise, draw a random stage from the active list and delete it from there.
+ if i == 0:
+ new_stage_order.append(starting_stage)
+ else:
+ new_stage_order.append(world.random.choice(remaining_stage_pool))
+ remaining_stage_pool.remove(new_stage_order[i])
+
+ # If we're looking at an alternate stage slot, put the stage in one of these lists to indicate it as such
+ if not world.branching_stages:
+ continue
+ if i - 2 >= 0:
+ if new_stage_order[i - 2] == rname.villa:
+ alt_villa_stage.append(new_stage_order[i])
+ if i - 3 >= 0:
+ if new_stage_order[i - 3] == rname.castle_center:
+ alt_cc_stages.append(new_stage_order[i])
+ if i - 4 >= 0:
+ if new_stage_order[i - 4] == rname.castle_center:
+ alt_cc_stages.append(new_stage_order[i])
+
+ new_stage_order.append(rname.castle_keep)
+
+ # Update the dictionary of stage exits
+ current_stage_number = 1
+ for i in range(len(new_stage_order)):
+ # Stage position number and alternate path indicator
+ world.active_stage_exits[new_stage_order[i]]["position"] = current_stage_number
+ if new_stage_order[i] in alt_villa_stage + alt_cc_stages:
+ world.active_stage_exits[new_stage_order[i]]["path"] = "'"
+ else:
+ world.active_stage_exits[new_stage_order[i]]["path"] = " "
+
+ # Previous stage
+ if world.active_stage_exits[new_stage_order[i]]["prev"]:
+ if i - 1 < 0:
+ world.active_stage_exits[new_stage_order[i]]["prev"] = "Menu"
+ elif world.branching_stages:
+ if new_stage_order[i - 1] == alt_villa_stage[0] or new_stage_order[i] == alt_villa_stage[0]:
+ world.active_stage_exits[new_stage_order[i]]["prev"] = new_stage_order[i - 2]
+ elif new_stage_order[i - 1] == alt_cc_stages[1] or new_stage_order[i] == alt_cc_stages[0]:
+ world.active_stage_exits[new_stage_order[i]]["prev"] = new_stage_order[i - 3]
+ else:
+ world.active_stage_exits[new_stage_order[i]]["prev"] = new_stage_order[i - 1]
+ else:
+ world.active_stage_exits[new_stage_order[i]]["prev"] = new_stage_order[i - 1]
+
+ # Next stage
+ if world.active_stage_exits[new_stage_order[i]]["next"]:
+ if world.branching_stages:
+ if new_stage_order[i + 1] == alt_villa_stage[0]:
+ world.active_stage_exits[new_stage_order[i]]["next"] = new_stage_order[i + 2]
+ current_stage_number -= 1
+ elif new_stage_order[i + 1] == alt_cc_stages[0]:
+ world.active_stage_exits[new_stage_order[i]]["next"] = new_stage_order[i + 3]
+ current_stage_number -= 2
+ else:
+ world.active_stage_exits[new_stage_order[i]]["next"] = new_stage_order[i + 1]
+ else:
+ world.active_stage_exits[new_stage_order[i]]["next"] = new_stage_order[i + 1]
+
+ # Alternate next stage
+ if world.active_stage_exits[new_stage_order[i]]["alt"]:
+ if world.branching_stages:
+ if new_stage_order[i] == rname.villa:
+ world.active_stage_exits[new_stage_order[i]]["alt"] = alt_villa_stage[0]
+ else:
+ world.active_stage_exits[new_stage_order[i]]["alt"] = alt_cc_stages[0]
+ else:
+ world.active_stage_exits[new_stage_order[i]]["alt"] = None
+
+ current_stage_number += 1
+
+ return world.active_stage_exits, starting_stage, new_stage_order
+
+
+def generate_warps(world: "CV64World") -> List[str]:
+ # Create a list of warps from the active stage list. They are in a random order by default and will never
+ # include the starting stage.
+ possible_warps = [stage for stage in world.active_stage_list]
+
+ # Remove the starting stage from the possible warps.
+ del (possible_warps[0])
+
+ active_warp_list = world.random.sample(possible_warps, 7)
+
+ if world.options.warp_order == WarpOrder.option_seed_stage_order:
+ # Arrange the warps to be in the seed's stage order
+ new_list = world.active_stage_list.copy()
+ for warp in world.active_stage_list:
+ if warp not in active_warp_list:
+ new_list.remove(warp)
+ active_warp_list = new_list
+ elif world.options.warp_order == WarpOrder.option_vanilla_stage_order:
+ # Arrange the warps to be in the vanilla game's stage order
+ new_list = list(vanilla_stage_order)
+ for warp in vanilla_stage_order:
+ if warp not in active_warp_list:
+ new_list.remove(warp)
+ active_warp_list = new_list
+
+ # Insert the starting stage at the start of the warp list
+ active_warp_list.insert(0, world.active_stage_list[0])
+
+ return active_warp_list
+
+
+def get_region_names(active_stage_exits: Dict[str, Dict[str, Union[str, int, None]]]) -> List[str]:
+ region_names = []
+ for stage in active_stage_exits:
+ stage_regions = get_stage_info(stage, "regions")
+ for region in stage_regions:
+ region_names.append(region)
+
+ return region_names
diff --git a/worlds/cv64/test/__init__.py b/worlds/cv64/test/__init__.py
new file mode 100644
index 00000000000..2d09e27cb31
--- /dev/null
+++ b/worlds/cv64/test/__init__.py
@@ -0,0 +1,6 @@
+from test.bases import WorldTestBase
+
+
+class CV64TestBase(WorldTestBase):
+ game = "Castlevania 64"
+ player: int = 1
diff --git a/worlds/cv64/test/test_access.py b/worlds/cv64/test/test_access.py
new file mode 100644
index 00000000000..79b1e14e11e
--- /dev/null
+++ b/worlds/cv64/test/test_access.py
@@ -0,0 +1,250 @@
+from . import CV64TestBase
+
+
+class WarpTest(CV64TestBase):
+ options = {
+ "special1s_per_warp": 3,
+ "total_special1s": 21
+ }
+
+ def test_warps(self) -> None:
+ for i in range(1, 8):
+ self.assertFalse(self.can_reach_entrance(f"Warp {i}"))
+ self.collect([self.get_item_by_name("Special1")] * 2)
+ self.assertFalse(self.can_reach_entrance(f"Warp {i}"))
+ self.collect([self.get_item_by_name("Special1")] * 1)
+ self.assertTrue(self.can_reach_entrance(f"Warp {i}"))
+
+
+class CastleWallTest(CV64TestBase):
+ options = {
+ "stage_shuffle": True,
+ "starting_stage": 1
+ }
+
+ def test_doors(self) -> None:
+ self.assertFalse(self.can_reach_entrance(f"Left Tower door"))
+ self.collect([self.get_item_by_name("Left Tower Key")] * 1)
+ self.assertTrue(self.can_reach_entrance(f"Left Tower door"))
+
+
+class VillaTest(CV64TestBase):
+ options = {
+ "stage_shuffle": True,
+ "starting_stage": 2
+ }
+
+ def test_doors(self) -> None:
+ self.assertFalse(self.can_reach_entrance("To Storeroom door"))
+ self.collect([self.get_item_by_name("Storeroom Key")] * 1)
+ self.assertTrue(self.can_reach_entrance("To Storeroom door"))
+ self.assertFalse(self.can_reach_entrance("To Archives door"))
+ self.collect([self.get_item_by_name("Archives Key")] * 1)
+ self.assertTrue(self.can_reach_entrance("To Archives door"))
+ self.assertFalse(self.can_reach_entrance("To maze gate"))
+ self.assertFalse(self.can_reach_entrance("Copper door"))
+ self.collect([self.get_item_by_name("Garden Key")] * 1)
+ self.assertTrue(self.can_reach_entrance("To maze gate"))
+ self.assertFalse(self.can_reach_entrance("Copper door"))
+ self.collect([self.get_item_by_name("Copper Key")] * 1)
+ self.assertTrue(self.can_reach_entrance("Copper door"))
+
+
+class CastleCenterTest(CV64TestBase):
+ options = {
+ "stage_shuffle": True,
+ "starting_stage": 5
+ }
+
+ def test_doors(self) -> None:
+ self.assertFalse(self.can_reach_entrance("Torture Chamber door"))
+ self.collect([self.get_item_by_name("Chamber Key")] * 1)
+ self.assertTrue(self.can_reach_entrance("Torture Chamber door"))
+ self.assertFalse(self.can_reach_entrance("Lower sealed cracked wall"))
+ self.assertFalse(self.can_reach_entrance("Upper cracked wall"))
+ self.collect([self.get_item_by_name("Magical Nitro")] * 1)
+ self.assertFalse(self.can_reach_entrance("Upper cracked wall"))
+ self.assertFalse(self.can_reach_entrance("Lower sealed cracked wall"))
+ self.collect([self.get_item_by_name("Mandragora")] * 1)
+ self.assertTrue(self.can_reach_entrance("Upper cracked wall"))
+ self.assertFalse(self.can_reach_entrance("Lower sealed cracked wall"))
+ self.collect([self.get_item_by_name("Magical Nitro")] * 1)
+ self.assertFalse(self.can_reach_entrance("Lower sealed cracked wall"))
+ self.collect([self.get_item_by_name("Mandragora")] * 1)
+ self.assertTrue(self.can_reach_entrance("Upper cracked wall"))
+
+
+class ExecutionTest(CV64TestBase):
+ options = {
+ "stage_shuffle": True,
+ "starting_stage": 7
+ }
+
+ def test_doors(self) -> None:
+ self.assertFalse(self.can_reach_entrance("Execution gate"))
+ self.collect([self.get_item_by_name("Execution Key")] * 1)
+ self.assertTrue(self.can_reach_entrance("Execution gate"))
+
+
+class ScienceTest(CV64TestBase):
+ options = {
+ "stage_shuffle": True,
+ "starting_stage": 8
+ }
+
+ def test_doors(self) -> None:
+ self.assertFalse(self.can_reach_entrance("Science Door 1"))
+ self.collect([self.get_item_by_name("Science Key1")] * 1)
+ self.assertTrue(self.can_reach_entrance("Science Door 1"))
+ self.assertFalse(self.can_reach_entrance("To Science Door 2"))
+ self.assertFalse(self.can_reach_entrance("Science Door 3"))
+ self.collect([self.get_item_by_name("Science Key2")] * 1)
+ self.assertTrue(self.can_reach_entrance("To Science Door 2"))
+ self.assertFalse(self.can_reach_entrance("Science Door 3"))
+ self.collect([self.get_item_by_name("Science Key3")] * 1)
+ self.assertTrue(self.can_reach_entrance("Science Door 3"))
+
+
+class ClocktowerTest(CV64TestBase):
+ options = {
+ "stage_shuffle": True,
+ "starting_stage": 11
+ }
+
+ def test_doors(self) -> None:
+ self.assertFalse(self.can_reach_entrance("To Clocktower Door 1"))
+ self.assertFalse(self.can_reach_entrance("To Clocktower Door 2"))
+ self.assertFalse(self.can_reach_entrance("Clocktower Door 3"))
+ self.collect([self.get_item_by_name("Clocktower Key1")] * 1)
+ self.assertTrue(self.can_reach_entrance("To Clocktower Door 1"))
+ self.assertFalse(self.can_reach_entrance("To Clocktower Door 2"))
+ self.assertFalse(self.can_reach_entrance("Clocktower Door 3"))
+ self.collect([self.get_item_by_name("Clocktower Key2")] * 1)
+ self.assertTrue(self.can_reach_entrance("To Clocktower Door 2"))
+ self.assertFalse(self.can_reach_entrance("Clocktower Door 3"))
+ self.collect([self.get_item_by_name("Clocktower Key3")] * 1)
+ self.assertTrue(self.can_reach_entrance("Clocktower Door 3"))
+
+
+class DraculaNoneTest(CV64TestBase):
+ options = {
+ "draculas_condition": 0,
+ "stage_shuffle": True,
+ "starting_stage": 5,
+ }
+
+ def test_dracula_none_condition(self) -> None:
+ self.assertFalse(self.can_reach_entrance("Dracula's door"))
+ self.collect([self.get_item_by_name("Left Tower Key"),
+ self.get_item_by_name("Garden Key"),
+ self.get_item_by_name("Copper Key"),
+ self.get_item_by_name("Science Key1"),
+ self.get_item_by_name("Science Key2"),
+ self.get_item_by_name("Science Key3"),
+ self.get_item_by_name("Clocktower Key1"),
+ self.get_item_by_name("Clocktower Key2"),
+ self.get_item_by_name("Clocktower Key3")] * 1)
+ self.assertFalse(self.can_reach_entrance("Dracula's door"))
+ self.collect([self.get_item_by_name("Special1")] * 7)
+ self.assertTrue(self.can_reach_entrance("Dracula's door"))
+
+
+class DraculaSpecialTest(CV64TestBase):
+ options = {
+ "draculas_condition": 3
+ }
+
+ def test_dracula_special_condition(self) -> None:
+ self.assertFalse(self.can_reach_entrance("Clocktower Door 3"))
+ self.collect([self.get_item_by_name("Left Tower Key"),
+ self.get_item_by_name("Garden Key"),
+ self.get_item_by_name("Copper Key"),
+ self.get_item_by_name("Magical Nitro"),
+ self.get_item_by_name("Mandragora"),
+ self.get_item_by_name("Clocktower Key1"),
+ self.get_item_by_name("Clocktower Key2"),
+ self.get_item_by_name("Clocktower Key3")] * 2)
+ self.assertTrue(self.can_reach_entrance("Clocktower Door 3"))
+ self.assertFalse(self.can_reach_entrance("Dracula's door"))
+ self.collect([self.get_item_by_name("Special2")] * 19)
+ self.assertFalse(self.can_reach_entrance("Dracula's door"))
+ self.collect([self.get_item_by_name("Special2")] * 1)
+ self.assertTrue(self.can_reach_entrance("Dracula's door"))
+
+
+class DraculaCrystalTest(CV64TestBase):
+ options = {
+ "draculas_condition": 1,
+ "stage_shuffle": True,
+ "starting_stage": 5,
+ "hard_logic": True
+ }
+
+ def test_dracula_crystal_condition(self) -> None:
+ self.assertFalse(self.can_reach_entrance("Slope Jump to boss tower"))
+ self.collect([self.get_item_by_name("Left Tower Key"),
+ self.get_item_by_name("Garden Key"),
+ self.get_item_by_name("Copper Key"),
+ self.get_item_by_name("Science Key1"),
+ self.get_item_by_name("Science Key2"),
+ self.get_item_by_name("Science Key3"),
+ self.get_item_by_name("Clocktower Key1"),
+ self.get_item_by_name("Clocktower Key2"),
+ self.get_item_by_name("Clocktower Key3")] * 1)
+ self.assertFalse(self.can_reach_entrance("Slope Jump to boss tower"))
+ self.collect([self.get_item_by_name("Special1")] * 7)
+ self.assertTrue(self.can_reach_entrance("Slope Jump to boss tower"))
+ self.assertFalse(self.can_reach_entrance("Dracula's door"))
+ self.collect([self.get_item_by_name("Magical Nitro"),
+ self.get_item_by_name("Mandragora")] * 1)
+ self.assertFalse(self.can_reach_entrance("Dracula's door"))
+ self.assertFalse(self.can_reach_entrance("Lower sealed cracked wall"))
+ self.collect([self.get_item_by_name("Magical Nitro"),
+ self.get_item_by_name("Mandragora")] * 1)
+ self.assertTrue(self.can_reach_entrance("Lower sealed cracked wall"))
+ self.assertTrue(self.can_reach_entrance("Dracula's door"))
+
+
+class DraculaBossTest(CV64TestBase):
+ options = {
+ "draculas_condition": 2,
+ "stage_shuffle": True,
+ "starting_stage": 5,
+ "hard_logic": True,
+ "bosses_required": 16
+ }
+
+ def test_dracula_boss_condition(self) -> None:
+ self.assertFalse(self.can_reach_entrance("Slope Jump to boss tower"))
+ self.collect([self.get_item_by_name("Left Tower Key"),
+ self.get_item_by_name("Garden Key"),
+ self.get_item_by_name("Copper Key"),
+ self.get_item_by_name("Science Key1"),
+ self.get_item_by_name("Science Key2"),
+ self.get_item_by_name("Science Key3"),
+ self.get_item_by_name("Clocktower Key1"),
+ self.get_item_by_name("Clocktower Key2"),
+ self.get_item_by_name("Clocktower Key3")] * 1)
+ self.assertFalse(self.can_reach_entrance("Slope Jump to boss tower"))
+ self.collect([self.get_item_by_name("Special1")] * 7)
+ self.assertTrue(self.can_reach_entrance("Slope Jump to boss tower"))
+ self.assertFalse(self.can_reach_entrance("Dracula's door"))
+ self.collect([self.get_item_by_name("Magical Nitro"),
+ self.get_item_by_name("Mandragora")] * 1)
+ self.assertFalse(self.can_reach_entrance("Dracula's door"))
+ self.assertFalse(self.can_reach_entrance("Lower sealed cracked wall"))
+ self.collect([self.get_item_by_name("Magical Nitro"),
+ self.get_item_by_name("Mandragora")] * 1)
+ self.assertTrue(self.can_reach_entrance("Lower sealed cracked wall"))
+ self.assertTrue(self.can_reach_entrance("Dracula's door"))
+
+
+class LizardTest(CV64TestBase):
+ options = {
+ "stage_shuffle": True,
+ "draculas_condition": 2,
+ "starting_stage": 4
+ }
+
+ def test_lizard_man_trio(self) -> None:
+ self.assertTrue(self.can_reach_location("Underground Waterway: Lizard-man trio"))
diff --git a/worlds/cv64/text.py b/worlds/cv64/text.py
new file mode 100644
index 00000000000..76ffaf1f7d3
--- /dev/null
+++ b/worlds/cv64/text.py
@@ -0,0 +1,95 @@
+from typing import Tuple
+
+cv64_char_dict = {"\n": (0x01, 0), " ": (0x02, 4), "!": (0x03, 2), '"': (0x04, 5), "#": (0x05, 6), "$": (0x06, 5),
+ "%": (0x07, 8), "&": (0x08, 7), "'": (0x09, 4), "(": (0x0A, 3), ")": (0x0B, 3), "*": (0x0C, 4),
+ "+": (0x0D, 5), ",": (0x0E, 3), "-": (0x0F, 4), ".": (0x10, 3), "/": (0x11, 6), "0": (0x12, 5),
+ "1": (0x13, 3), "2": (0x14, 5), "3": (0x15, 4), "4": (0x16, 5), "5": (0x17, 5), "6": (0x18, 5),
+ "7": (0x19, 5), "8": (0x1A, 5), "9": (0x1B, 5), ":": (0x1C, 3), ";": (0x1D, 3), "<": (0x1E, 3),
+ "=": (0x1F, 4), ">": (0x20, 3), "?": (0x21, 5), "@": (0x22, 8), "A": (0x23, 7), "B": (0x24, 6),
+ "C": (0x25, 5), "D": (0x26, 7), "E": (0x27, 5), "F": (0x28, 6), "G": (0x29, 6), "H": (0x2A, 7),
+ "I": (0x2B, 3), "J": (0x2C, 3), "K": (0x2D, 6), "L": (0x2E, 6), "M": (0x2F, 8), "N": (0x30, 7),
+ "O": (0x31, 6), "P": (0x32, 6), "Q": (0x33, 8), "R": (0x34, 6), "S": (0x35, 5), "T": (0x36, 6),
+ "U": (0x37, 6), "V": (0x38, 7), "W": (0x39, 8), "X": (0x3A, 6), "Y": (0x3B, 7), "Z": (0x3C, 6),
+ "[": (0x3D, 3), "\\": (0x3E, 6), "]": (0x3F, 3), "^": (0x40, 6), "_": (0x41, 5), "a": (0x43, 5),
+ "b": (0x44, 6), "c": (0x45, 4), "d": (0x46, 6), "e": (0x47, 5), "f": (0x48, 5), "g": (0x49, 5),
+ "h": (0x4A, 6), "i": (0x4B, 3), "j": (0x4C, 3), "k": (0x4D, 6), "l": (0x4E, 3), "m": (0x4F, 8),
+ "n": (0x50, 6), "o": (0x51, 5), "p": (0x52, 5), "q": (0x53, 5), "r": (0x54, 4), "s": (0x55, 4),
+ "t": (0x56, 4), "u": (0x57, 5), "v": (0x58, 6), "w": (0x59, 8), "x": (0x5A, 5), "y": (0x5B, 5),
+ "z": (0x5C, 4), "{": (0x5D, 4), "|": (0x5E, 2), "}": (0x5F, 3), "`": (0x61, 4), "「": (0x62, 3),
+ "」": (0x63, 3), "~": (0x65, 3), "″": (0x72, 3), "°": (0x73, 3), "∞": (0x74, 8)}
+# [0] = CV64's in-game ID for that text character.
+# [1] = How much space towards the in-game line length limit it contributes.
+
+
+def cv64_string_to_bytearray(cv64text: str, a_advance: bool = False, append_end: bool = True) -> bytearray:
+ """Converts a string into a bytearray following CV64's string format."""
+ text_bytes = bytearray(0)
+ for i, char in enumerate(cv64text):
+ if char == "\t":
+ text_bytes.extend([0xFF, 0xFF])
+ else:
+ if char in cv64_char_dict:
+ text_bytes.extend([0x00, cv64_char_dict[char][0]])
+ else:
+ text_bytes.extend([0x00, 0x41])
+
+ if a_advance:
+ text_bytes.extend([0xA3, 0x00])
+ if append_end:
+ text_bytes.extend([0xFF, 0xFF])
+ return text_bytes
+
+
+def cv64_text_truncate(cv64text: str, textbox_len_limit: int) -> str:
+ """Truncates a string at a given in-game text line length."""
+ line_len = 0
+
+ for i in range(len(cv64text)):
+ line_len += cv64_char_dict[cv64text[i]][1]
+
+ if line_len > textbox_len_limit:
+ return cv64text[0x00:i]
+
+ return cv64text
+
+
+def cv64_text_wrap(cv64text: str, textbox_len_limit: int) -> Tuple[str, int]:
+ """Rebuilds a string with some of its spaces replaced with newlines to ensure the text wraps properly in an in-game
+ textbox of a given length."""
+ words = cv64text.split(" ")
+ new_text = ""
+ line_len = 0
+ num_lines = 1
+
+ for i in range(len(words)):
+ word_len = 0
+ word_divider = " "
+
+ if line_len != 0:
+ line_len += 4
+ else:
+ word_divider = ""
+
+ for char in words[i]:
+ if char in cv64_char_dict:
+ line_len += cv64_char_dict[char][1]
+ word_len += cv64_char_dict[char][1]
+ else:
+ line_len += 5
+ word_len += 5
+
+ if word_len > textbox_len_limit or char in ["\n", "\t"]:
+ word_len = 0
+ line_len = 0
+ if num_lines < 4:
+ num_lines += 1
+
+ if line_len > textbox_len_limit:
+ word_divider = "\n"
+ line_len = word_len
+ if num_lines < 4:
+ num_lines += 1
+
+ new_text += word_divider + words[i]
+
+ return new_text, num_lines