diff --git a/archipelago/DK64Client.py b/archipelago/DK64Client.py index 3ddae0cba..054291742 100644 --- a/archipelago/DK64Client.py +++ b/archipelago/DK64Client.py @@ -19,6 +19,7 @@ from archipelago.client.common import DK64MemoryMap, create_task_log_exception, check_version, get_ap_version from archipelago.client.emu_loader import EmuLoaderClient +from archipelago.client.everdrive_loader import EverdriveClient, SC64Client from archipelago.client.items import item_ids, item_names_to_id, trap_name_to_index, trap_index_to_name from archipelago.client.ap_item_packets import APItemPacket, build_packet, ICE_TRAP_TYPES, REQITEM_ICETRAP, REQITEM_GOLDENBANANA, REQITEM_MOVE, CONFIG_APPLY_ICE_TRAP from archipelago.client.check_flag_locations import location_flag_to_name, location_name_to_flag @@ -36,7 +37,8 @@ KONG_COUNT = 5 LEVEL_COUNT = 7 HINT_BITFIELD_SIZE = 5 - +FLAG_CACHE_SIZE = 0x200 +USB_BACKENDS = (EverdriveClient, SC64Client) class CreateHintsParams: """Parameters for creating hints.""" @@ -153,6 +155,7 @@ class DK64Client: auth = None memory_pointer = None stop_bizhawk_spam = False + use_everdrive = False seed_started = False locations_scouted = {} recvd_checks = {} @@ -173,6 +176,7 @@ class DK64Client: send_mode = 1 current_speed = NORMAL_TEXT_SPEED current_map = 0 + flag_cache = None # Hint system last_hint_bitfield = [0] * HINT_BITFIELD_SIZE @@ -181,47 +185,103 @@ class DK64Client: # ==================== CONNECTION METHODS ==================== + def _backend_name(self) -> str: + """Human-readable name of the currently selected backend.""" + if isinstance(self.n64_client, USB_BACKENDS): + return self.n64_client.DISPLAY_NAME + return "Emulator" + + def _open_emulator(self): + """Attach to a running emulator that already has a valid DK64 AP ROM.""" + client = EmuLoaderClient() + try: + if client.connect() and client.validate_rom(): + return client + except Exception: + pass + try: + client.disconnect() + except Exception: + pass + return None + + def _open_backend(self): + """Open a memory-access backend.""" + if self.use_everdrive: + usb_classes = list(USB_BACKENDS) + else: + usb_classes = [cls for cls in USB_BACKENDS if cls.cart_present()] + + for cls in usb_classes: + client = cls() + try: + if client.connect(): + return client + except Exception: + pass + try: + client.disconnect() + except Exception: + pass + + if self.use_everdrive: + return None + return self._open_emulator() + async def wait_for_pj64(self): - """Wait for emulator to connect to the game.""" + """Wait until an emulator or USB flashcart with a valid DK64 ROM is ready.""" clear_waiting_message = True if not self.stop_bizhawk_spam: - logger.info("Waiting on connection to emulator...") - self.n64_client = EmuLoaderClient() + logger.info("Waiting on a connection to an emulator or EverDrive...") self.stop_bizhawk_spam = True while True: try: - emulator_connected = False + # Open a backend if we don't have one. A USB cart's device is kept + # open across retries — re-opening it interrupts the cart while it + # boots — so only the ROM-readiness check below is retried. + if self.n64_client is None or not self.n64_client.is_connected(): + if self.n64_client is not None: + self.n64_client.disconnect() + self.n64_client = None + self.n64_client = self._open_backend() + + if self.n64_client is not None and self.n64_client.is_connected(): + if self.n64_client.validate_rom(): + if isinstance(self.n64_client, USB_BACKENDS): + # Clear any probe backlog so gameplay reads stay synced. + self.n64_client.resync() + self.stop_bizhawk_spam = False + logger.info(f"{self._backend_name()} connected to ROM!") + return + + if isinstance(self.n64_client, USB_BACKENDS): + # Cart is connected but the AP ROM hasn't booted yet. Hold + # the USB device open and wait; only drop it if the cart is + # physically unplugged (so we re-detect on reconnect). + if type(self.n64_client).cart_present(): + if clear_waiting_message: + logger.info(f"{self._backend_name()} connected over USB; waiting for the ROM to boot...") + clear_waiting_message = False + await asyncio.sleep(1.0) + continue + self.n64_client.disconnect() + self.n64_client = None + else: + # Emulator attached without a valid ROM; drop and re-detect. + self.n64_client.disconnect() + self.n64_client = None - # Try to connect to any available emulator - if not self.n64_client.is_connected(): - emulator_connected = self.n64_client.connect() - else: - emulator_connected = True - valid_rom = False - if emulator_connected: - valid_rom = self.n64_client.validate_rom() - logger.info("Emulator connected, validating ROM...") - - while not valid_rom: - if not self.n64_client.is_connected(): - emulator_connected = self.n64_client.connect() - if clear_waiting_message: - logger.info("Waiting on valid ROM...") - clear_waiting_message = False - await asyncio.sleep(1.0) - if self.n64_client.is_connected(): - valid_rom = self.n64_client.validate_rom() - - self.stop_bizhawk_spam = False - logger.info("Emulator Connected to ROM!") - return + if clear_waiting_message: + logger.info("Waiting on an emulator or EverDrive with a valid ROM...") + clear_waiting_message = False + await asyncio.sleep(1.0) except Exception as e: await asyncio.sleep(1.0) - logger.error(f"Error connecting to emulator, retrying... {str(e)}") + logger.error(f"Error connecting, retrying... {str(e)}") # Reset connection on error if self.n64_client: self.n64_client.disconnect() - pass + self.n64_client = None # ==================== GAME STATE METHODS ==================== @@ -262,7 +322,7 @@ async def validate_client_connection(self): """Validate the client connection.""" self.memory_pointer = self.n64_client.read_u32(DK64MemoryMap.memory_pointer) self.n64_client.write_u8(self.memory_pointer + DK64MemoryMap.connection, 0xFF) - if self.n64_client.read_u8(DK64MemoryMap.eeprom_determined) == 1: + if isinstance(self.n64_client, EmuLoaderClient) and self.n64_client.read_u8(DK64MemoryMap.eeprom_determined) == 1: if self.n64_client.read_u32(DK64MemoryMap.save_type) != 2: # Map emulator IDs to their setup guides emulator_setup_guides = { @@ -772,6 +832,15 @@ async def readChecks(self, cb): new_checks = [] checks_to_read = self.remaining_checks.copy() + # Snapshot the whole EEPROM flag block once + self.flag_cache = self.n64_client.read_block(DK64MemoryMap.EEPROM, FLAG_CACHE_SIZE) + try: + return self._scan_checks(checks_to_read, new_checks, cb) + finally: + self.flag_cache = None + + def _scan_checks(self, checks_to_read, new_checks, cb): + """Scan the requested location ids against the cached flag block.""" for id in checks_to_read: name = check_id_to_name.get(id) check = location_name_to_flag.get(name) @@ -873,8 +942,12 @@ def readFlag(self, index: int) -> int: """Read a flag in the game.""" byte_index = index >> 3 shift = index & 7 - offset = DK64MemoryMap.EEPROM + byte_index - val = self.n64_client.read_u8(offset) + cache = self.flag_cache + if cache is not None and byte_index < len(cache): + val = cache[byte_index] + else: + offset = DK64MemoryMap.EEPROM + byte_index + val = self.n64_client.read_u8(offset) return (val >> shift) & 1 def hasKong(self, kong_index: int) -> bool: @@ -1802,6 +1875,8 @@ async def disconnect_check(): logger.info("No checks received yet, requesting...") await self.sync() + tick_delay = 0.066 if isinstance(self.client.n64_client, USB_BACKENDS) else 0.033 + await asyncio.sleep(1.0) while True: logger.debug("Game loop tick") @@ -1812,10 +1887,10 @@ async def disconnect_check(): await victory() status = self.client.check_safe_gameplay() if status is False: - await asyncio.sleep(0.033) + await asyncio.sleep(tick_delay) continue await self.client.main_tick(on_item_get, deathlink, map_change, ring_link, tag_link, trap_link, hint_accessed) - await asyncio.sleep(0.033) + await asyncio.sleep(tick_delay) now = time.time() if self.last_resend + 0.5 < now: self.last_resend = now @@ -1838,11 +1913,13 @@ async def main(): """Entrypoint of codebase.""" parser = get_base_parser(description="Donkey Kong 64 Client.") parser.add_argument("--url", help="Archipelago connection url") + parser.add_argument("--everdrive", action="store_true", help="Force USB flashcart mode (EverDrive/SC64) and skip emulator detection. A connected cart is auto-detected without this flag.") args = parser.parse_args() check_version() ctx = DK64Context(args.connect, args.password) + ctx.client.use_everdrive = args.everdrive ctx.items_handling = 0b001 ctx.server_task = asyncio.create_task(server_loop(ctx), name="server loop") ctx.la_task = create_task_log_exception(ctx.run_game_loop()) diff --git a/archipelago/FillSettings.py b/archipelago/FillSettings.py index e084b68ed..ec58508c9 100644 --- a/archipelago/FillSettings.py +++ b/archipelago/FillSettings.py @@ -485,25 +485,17 @@ def fillsettings(options: DK64Options, multiworld: MultiWorld, random_obj: Rando slam_name = options.alter_switch_allocation.value[level_key] settings_dict[f"prog_slam_level_{i + 1}"] = slam_map.get(slam_name, SlamRequirement.green) - -def generate_blocker(option_value: str, blocker_max: int, random: Random): - """Randomize a B. Locker value, either within a range or up to the maximum.""" - upper_bound = blocker_max if option_value == "random" else int(option_value.split("-")[1]) + 1 - lower_bound = 0 if option_value == "random" else int(option_value.split("-")[0]) - return random.randrange(lower_bound, upper_bound) - - -def apply_blocker_settings(settings_dict: dict, options, random_obj) -> None: - """Apply level blocker settings.""" - blocker_options = [0, 0, 0, 0, 0, 0, 0, 0] - for blocker, amount in options.level_blockers.value.items(): - blocker_number = int(blocker.removeprefix("level_")) - 1 - try: - blocker_options[blocker_number] = int(amount) - except (TypeError, ValueError): - blocker_options[blocker_number] = generate_blocker(amount, options.blocker_max.value, random_obj) - - # Blocker settings - prioritize chaos blockers, then randomization setting + # Apply blocker settings + blocker_options: list[int] = [ + options.level_blockers.value.get("level_1", 0), + options.level_blockers.value.get("level_2", 0), + options.level_blockers.value.get("level_3", 0), + options.level_blockers.value.get("level_4", 0), + options.level_blockers.value.get("level_5", 0), + options.level_blockers.value.get("level_6", 0), + options.level_blockers.value.get("level_7", 0), + options.level_blockers.value.get("level_8", 64), + ] settings_dict["maximize_helm_blocker"] = options.maximize_level8_blocker.value if options.enable_chaos_blockers.value: settings_dict["blocker_text"] = options.chaos_ratio.value @@ -965,95 +957,10 @@ def apply_blocker_settings(settings_dict: dict, options, random_obj) -> None: settings_dict["coin_door_item"] = HelmDoorItem(options.coin_door_item.value) coin_item_key = door_item_to_key.get(settings_dict["coin_door_item"]) settings_dict["coin_door_item_count"] = options.helm_door_item_count.value.get(coin_item_key, 1) if coin_item_key else 1 - - if hasattr(multiworld, "generation_is_fake"): - if hasattr(multiworld, "re_gen_passthrough"): - if "Donkey Kong 64" in multiworld.re_gen_passthrough: - passthrough = multiworld.re_gen_passthrough["Donkey Kong 64"] - settings_dict["bonus_barrel_auto_complete"] = passthrough["Autocomplete"] - settings_dict["helm_room_bonus_count"] = HelmBonuses(passthrough["HelmBarrelCount"]) - - -def handle_fake_generation_settings(settings: Settings, multiworld) -> None: - """Handle settings for fake generation (UT mode).""" - if hasattr(multiworld, "generation_is_fake"): - settings.is_ut_generation = True - if hasattr(multiworld, "re_gen_passthrough"): - if "Donkey Kong 64" in multiworld.re_gen_passthrough: - passthrough = multiworld.re_gen_passthrough["Donkey Kong 64"] - settings.level_order = passthrough["LevelOrder"] - - # Switch logic lifted out of level shuffle due to static levels for UT - if settings.alter_switch_allocation: - for x in range(8): - settings.switch_allocation[x] = passthrough["SlamLevels"][x] - - settings.starting_kong_list = passthrough["StartingKongs"] - settings.starting_kong = settings.starting_kong_list[0] # fake a starting kong so that we don't force a different kong - settings.medal_requirement = passthrough["JetpacReq"] - settings.rareware_gb_fairies = passthrough["FairyRequirement"] - settings.BLockerEntryItems = passthrough["BLockerEntryItems"] - settings.BLockerEntryCount = passthrough["BLockerEntryCount"] - settings.medal_cb_req = passthrough["MedalCBRequirement"] - settings.medal_cb_req_level = [settings.medal_cb_req] * 8 - - for level, value in enumerate(passthrough["MedalCBRequirementLevel"]): - settings.medal_cb_req_level[Levels(level)] = int(value) - - settings.mermaid_gb_pearls = passthrough["MermaidPearls"] - settings.BossBananas = passthrough["BossBananas"] - settings.boss_maps = passthrough["BossMaps"] - settings.boss_kongs = passthrough["BossKongs"] - settings.lanky_freeing_kong = passthrough["LankyFreeingKong"] - settings.helm_order = passthrough["HelmOrder"] - settings.logic_type = LogicType[passthrough["LogicType"]] - settings.tricks_selected = passthrough["TricksSelected"] - settings.glitches_selected = passthrough["GlitchesSelected"] - settings.open_lobbies = passthrough["OpenLobbies"] - settings.starting_key_list = passthrough["StartingKeyList"] - settings.galleon_water = GalleonWaterSetting[passthrough["GalleonWater"]] - settings.galleon_water_internal = GalleonWaterSetting[passthrough["GalleonWater"]] - - # There's multiple sources of truth for helm order. - settings.helm_donkey = 0 in settings.helm_order - settings.helm_diddy = 4 in settings.helm_order - settings.helm_lanky = 3 in settings.helm_order - settings.helm_tiny = 2 in settings.helm_order - settings.helm_chunky = 1 in settings.helm_order - - # Switchsanity - for switch, data in passthrough["SwitchSanity"].items(): - needed_kong = Kongs[data["kong"]] - switch_type = SwitchType[data["type"]] - settings.switchsanity_data[Switches[switch]] = SwitchInfo(switch, needed_kong, switch_type, 0, 0, []) - - if passthrough["Shopkeepers"]: - settings.shuffled_location_types.append(Types.Cranky) - settings.shuffled_location_types.append(Types.Funky) - settings.shuffled_location_types.append(Types.Candy) - settings.shuffled_location_types.append(Types.Snide) - - -def fillsettings(options, multiworld, random_obj): - """Fill and configure all DK64 settings.""" - # Start with default settings - settings_dict = get_default_settings() - - # Apply all setting categories - apply_archipelago_settings(settings_dict, options, multiworld) - apply_blocker_settings(settings_dict, options, random_obj) - apply_item_randomization_settings(settings_dict, options) - apply_hard_mode_settings(settings_dict, options) - apply_kong_settings(settings_dict, options) - apply_switchsanity_settings(settings_dict, options) - apply_logic_and_barriers_settings(settings_dict, options) - apply_glitches_and_tricks_settings(settings_dict, options) - apply_boss_and_key_settings(settings_dict, options) - apply_goal_settings(settings_dict, options, random_obj) - apply_starting_moves_settings(settings_dict, options) - apply_hint_settings(settings_dict, options) - apply_minigame_settings(settings_dict, options, multiworld) - apply_enemies(settings_dict, options) + if hasattr(multiworld, "generation_is_fake") and hasattr(multiworld, "re_gen_passthrough") and "Donkey Kong 64" in multiworld.re_gen_passthrough: + passthrough = multiworld.re_gen_passthrough["Donkey Kong 64"] + settings_dict["bonus_barrel_auto_complete"] = passthrough["Autocomplete"] + settings_dict["helm_room_bonus_count"] = HelmBonuses(passthrough["HelmBarrelCount"]) # Handle fake generation keys if needed if hasattr(multiworld, "generation_is_fake"): diff --git a/archipelago/client/d2xx.py b/archipelago/client/d2xx.py new file mode 100644 index 000000000..d891ed9bb --- /dev/null +++ b/archipelago/client/d2xx.py @@ -0,0 +1,218 @@ +"""Minimal pure-ctypes wrapper around the FTDI D2XX driver.""" + +from __future__ import annotations + +import ctypes +import platform +from typing import List, Optional + +# FT_STATUS return codes +FT_OK = 0 + +# FT_Purge masks. +FT_PURGE_RX = 1 +FT_PURGE_TX = 2 + +# FTDI USB vendor id +FTDI_VID = 0x0403 + + +class FtdiError(Exception): + """Raised when the D2XX driver is missing or a D2XX call fails.""" + + +class _DeviceListInfoNode(ctypes.Structure): + """Mirror of FTDI's FT_DEVICE_LIST_INFO_NODE structure.""" + + _fields_ = [ + ("Flags", ctypes.c_uint32), + ("Type", ctypes.c_uint32), + ("ID", ctypes.c_uint32), + ("LocId", ctypes.c_uint32), + ("SerialNumber", ctypes.c_char * 16), + ("Description", ctypes.c_char * 64), + ("ftHandle", ctypes.c_void_p), + ] + + +class DeviceInfo: + """Lightweight description of an enumerated FTDI device.""" + + def __init__(self, index: int, node: _DeviceListInfoNode): + """Capture the fields we use to identify an EverDrive.""" + self.index = index + self.id = node.ID + self.vid = (node.ID >> 16) & 0xFFFF + self.pid = node.ID & 0xFFFF + self.serial = node.SerialNumber.decode("ascii", "ignore") + self.description = node.Description.decode("ascii", "ignore") + + def is_ftdi(self) -> bool: + """Whether this is an FTDI device (the EverDrive's USB chip is FTDI).""" + return self.vid == FTDI_VID + + def looks_like_everdrive(self) -> bool: + """Heuristic for an EverDrive 64's USB chip, for safe auto-detection.""" + + desc = self.description.upper() + return self.is_ftdi() and ("245" in desc or "FIFO" in desc or "EVERDRIVE" in desc) + + def looks_like_sc64(self) -> bool: + """Heuristic for a SummerCart64's USB chip, for safe auto-detection.""" + + desc = self.description.upper() + return self.is_ftdi() and (self.pid == 0x6014 or "SC64" in desc or "SUMMERCART" in desc) + + +_lib = None + + +def _candidate_library_names() -> List[str]: + """Return platform-appropriate D2XX library names to try, in order.""" + system = platform.system() + if system == "Windows": + return ["ftd2xx64.dll", "ftd2xx.dll"] + if system == "Darwin": + return ["libftd2xx.dylib", "/usr/local/lib/libftd2xx.dylib"] + return ["libftd2xx.so", "libftd2xx.so.1"] + + +def _load_library(): + """Load and configure the D2XX shared library, caching the handle.""" + global _lib + if _lib is not None: + return _lib + + names = _candidate_library_names() + loader = ctypes.WinDLL if platform.system() == "Windows" else ctypes.CDLL + last_error: Optional[Exception] = None + lib = None + for name in names: + try: + lib = loader(name) + break + except OSError as exc: + last_error = exc + if lib is None: + raise FtdiError( + "Could not load the FTDI D2XX driver (tried: " + ", ".join(names) + "). " + "Install the FTDI driver for your platform to use EverDrive mode. " + (f"({last_error})" if last_error else "") + ) + + # FT_HANDLE is a void*; FT_STATUS is a uint32. Declare signatures so the + # calls are correct on every architecture (notably 32-bit). + handle = ctypes.c_void_p + status = ctypes.c_uint32 + dword = ctypes.c_uint32 + + lib.FT_CreateDeviceInfoList.argtypes = [ctypes.POINTER(dword)] + lib.FT_CreateDeviceInfoList.restype = status + lib.FT_GetDeviceInfoList.argtypes = [ctypes.POINTER(_DeviceListInfoNode), ctypes.POINTER(dword)] + lib.FT_GetDeviceInfoList.restype = status + lib.FT_Open.argtypes = [ctypes.c_int, ctypes.POINTER(handle)] + lib.FT_Open.restype = status + lib.FT_Close.argtypes = [handle] + lib.FT_Close.restype = status + lib.FT_Read.argtypes = [handle, ctypes.c_void_p, dword, ctypes.POINTER(dword)] + lib.FT_Read.restype = status + lib.FT_Write.argtypes = [handle, ctypes.c_void_p, dword, ctypes.POINTER(dword)] + lib.FT_Write.restype = status + lib.FT_SetTimeouts.argtypes = [handle, dword, dword] + lib.FT_SetTimeouts.restype = status + lib.FT_SetLatencyTimer.argtypes = [handle, ctypes.c_ubyte] + lib.FT_SetLatencyTimer.restype = status + lib.FT_GetQueueStatus.argtypes = [handle, ctypes.POINTER(dword)] + lib.FT_GetQueueStatus.restype = status + lib.FT_Purge.argtypes = [handle, dword] + lib.FT_Purge.restype = status + lib.FT_ResetDevice.argtypes = [handle] + lib.FT_ResetDevice.restype = status + + _lib = lib + return _lib + + +def driver_available() -> bool: + """Return True if the D2XX driver can be loaded.""" + try: + _load_library() + return True + except FtdiError: + return False + + +def list_devices() -> List[DeviceInfo]: + """Enumerate connected FTDI/D2XX devices. Empty if the driver is missing.""" + try: + lib = _load_library() + except FtdiError: + return [] + + count = ctypes.c_uint32(0) + if lib.FT_CreateDeviceInfoList(ctypes.byref(count)) != FT_OK or count.value == 0: + return [] + + array = (_DeviceListInfoNode * count.value)() + if lib.FT_GetDeviceInfoList(array, ctypes.byref(count)) != FT_OK: + return [] + + return [DeviceInfo(i, array[i]) for i in range(count.value)] + + +class D2xxDevice: + """An open D2XX device handle with the few operations the bridge needs.""" + + def __init__(self, index: int): + """Open the FTDI device at the given enumeration index.""" + self._lib = _load_library() + self._handle = ctypes.c_void_p() + if self._lib.FT_Open(index, ctypes.byref(self._handle)) != FT_OK: + self._handle = None + raise FtdiError(f"Failed to open FTDI device {index}. Is it already in use by another program?") + self._lib.FT_SetTimeouts(self._handle, 1000, 1000) + self._lib.FT_SetLatencyTimer(self._handle, 1) + self.purge() + + def purge(self): + """Discard any buffered receive/transmit data.""" + if self._handle is not None: + self._lib.FT_Purge(self._handle, FT_PURGE_RX | FT_PURGE_TX) + + def queue_status(self) -> int: + """Return the number of bytes waiting in the receive queue.""" + pending = ctypes.c_uint32(0) + if self._lib.FT_GetQueueStatus(self._handle, ctypes.byref(pending)) != FT_OK: + raise FtdiError("FT_GetQueueStatus failed") + return pending.value + + def write(self, data: bytes) -> int: + """Write all bytes to the device; raise on a short/failed write.""" + buf = ctypes.create_string_buffer(bytes(data), len(data)) + written = ctypes.c_uint32(0) + if self._lib.FT_Write(self._handle, buf, len(data), ctypes.byref(written)) != FT_OK: + raise FtdiError("FT_Write failed") + if written.value != len(data): + raise FtdiError(f"FT_Write short write ({written.value}/{len(data)})") + return written.value + + def read(self, nbytes: int) -> bytes: + """Read exactly nbytes and raise on timeout/short read.""" + out = bytearray() + while len(out) < nbytes: + remaining = nbytes - len(out) + buf = ctypes.create_string_buffer(remaining) + got = ctypes.c_uint32(0) + if self._lib.FT_Read(self._handle, buf, remaining, ctypes.byref(got)) != FT_OK: + raise FtdiError("FT_Read failed") + if got.value == 0: + raise FtdiError(f"FT_Read timed out ({len(out)}/{nbytes} bytes)") + out += buf.raw[: got.value] + return bytes(out) + + def close(self): + """Close the device handle.""" + if self._handle is not None: + try: + self._lib.FT_Close(self._handle) + finally: + self._handle = None diff --git a/archipelago/client/emu_loader.py b/archipelago/client/emu_loader.py index c78b9e50d..223ef1197 100644 --- a/archipelago/client/emu_loader.py +++ b/archipelago/client/emu_loader.py @@ -738,6 +738,25 @@ def writeBytes(self, address: int, size: int, value: int): data = value.to_bytes(size, byteorder="little") # or "big" self.connected_process.write_bytes(mem_address, data, size) + def read_block(self, address: int, length: int) -> bytes: + """Read a contiguous block of bytes in logical (big-endian RDRAM) order.""" + + if self.connected_process is None or self.connected_offset is None: + self.runtime_error = "Not connected to a process, exiting" + raise Exception(self.runtime_error) + if length <= 0: + return b"" + if address & 0x80000000: + address &= 0x7FFFFFFF + + start_word = address & ~3 + end_word = (address + length + 3) & ~3 + raw = self.connected_process.read_bytes(self.connected_offset + start_word, end_word - start_word) + out = bytearray(length) + for i in range(length): + out[i] = raw[((address + i) ^ 3) - start_word] + return bytes(out) + def read_u8(self, address: int) -> int: """Read an 8-bit unsigned integer from memory.""" return self.readBytes(address, 1) @@ -905,6 +924,12 @@ def write_u32(self, address: int, value: int): raise Exception("Not connected to emulator") self.emulator_info.write_u32(address, value) # pyright: ignore[reportOptionalMemberAccess] + def read_block(self, address: int, length: int) -> bytes: + """Read a contiguous block of bytes in logical (big-endian RDRAM) order.""" + if not self.is_connected(): + raise Exception("Not connected to emulator") + return self.emulator_info.read_block(address, length) # pyright: ignore[reportOptionalMemberAccess] + def read_bytestring(self, address: int, length: int) -> str: """Read a bytestring from memory.""" if not self.is_connected(): diff --git a/archipelago/client/everdrive_loader.py b/archipelago/client/everdrive_loader.py new file mode 100644 index 000000000..2915d3019 --- /dev/null +++ b/archipelago/client/everdrive_loader.py @@ -0,0 +1,339 @@ +"""USB flashcart transports for the DK64 Archipelago client.""" + +from __future__ import annotations + +import struct +from typing import List, Tuple + +from archipelago.client import d2xx +from archipelago.client.common import DK64MemoryMap +from archipelago.client.emu_loader import sanitize_and_trim + +try: + from CommonClient import logger +except ImportError: + import logging + + logger = logging.getLogger(__name__) + +DATATYPE_TEXT = 0x01 +DATATYPE_HEARTBEAT = 0x05 +DATATYPE_RDRAM_READ = 0x30 +DATATYPE_RDRAM_WRITE = 0x31 +DATATYPE_RDRAM_DATA = 0x32 +DATATYPE_RDRAM_ACK = 0x33 +RDRAM_SIZE = 0x800000 + + +class EverdriveError(Exception): + """Raised on a USB framing/transport error talking to a flashcart.""" + + +def _align2(value: int) -> int: + """Round up to a multiple of 2 (flashcart FIFOs transfer 16-bit units).""" + return (value + 1) & ~1 + + +class _UsbInfoStub: + """Stand-in for ``EmulatorInfo`` so ``n64_client.emulator_info.id.name`` works.""" + + def __init__(self, name: str): + """Capture the backend's display name.""" + self.id = type("_Id", (), {"name": name})() + + +class _N64UsbBridgeClient: + """Shared logic for the USB flashcart backends.""" + + DISPLAY_NAME = "USB" + + def __init__(self): + """Initialise an unconnected client.""" + self._device = None + self.connected = False + self.emulator_info = _UsbInfoStub(self.DISPLAY_NAME) + + # framing primitives + + def _send(self, datatype: int, payload: bytes): + raise NotImplementedError + + def _recv(self, expected_type: int) -> bytes: + raise NotImplementedError + + @staticmethod + def _device_matches(dev) -> bool: + raise NotImplementedError + + # device discovery / lifecycle + + @classmethod + def cart_present(cls) -> bool: + """Cheaply probe whether this cart looks connected, for auto-detection.""" + try: + return any(cls._device_matches(dev) for dev in d2xx.list_devices()) + except Exception: + return False + + def connect(self) -> bool: + """Open the first matching cart. Returns True on success.""" + if self.is_connected(): + return True + candidates = [dev for dev in d2xx.list_devices() if self._device_matches(dev)] + if not candidates: + logger.info(f"No {self.DISPLAY_NAME} USB device was found. Is the cart connected and the FTDI driver installed?") + return False + + try: + self._device = d2xx.D2xxDevice(candidates[0].index) + except d2xx.FtdiError as exc: + logger.error(f"{self.DISPLAY_NAME}: {exc}") + self._device = None + return False + + self.connected = True + logger.info(f"Connected to {self.DISPLAY_NAME} over USB") + print(f"Connected to {self.DISPLAY_NAME} over USB") + return True + + def disconnect(self): + """Close the USB device.""" + if self._device is not None: + try: + self._device.close() + except Exception: + pass + self._device = None + self.connected = False + + def is_connected(self) -> bool: + """Whether a USB device handle is currently open.""" + return self.connected and self._device is not None + + def validate_rom(self) -> bool: + """Confirm the loaded ROM is a DK64 AP seed and the bridge answers.""" + if not self.is_connected(): + return False + try: + return (self.read_u8(DK64MemoryMap.rom_flags) & DK64MemoryMap.rom_flag_ap_status) != 0 + except Exception: + return False + + def resync(self): + """Drain any buffered/queued USB data so a fresh exchange starts clean.""" + if self._device is None: + return + try: + self._device.purge() + for _ in range(64): + pending = self._device.queue_status() + if not pending: + break + self._device.read(min(pending, 512)) + self._device.purge() + except Exception: + pass + + # ---- memory access (shared) ---- + + def read_block(self, address: int, length: int) -> bytes: + """Read ``length`` bytes of RDRAM starting at ``address`` (logical order).""" + if length <= 0: + return b"" + phys = address & 0x7FFFFFFF + if phys + length > RDRAM_SIZE: + raise EverdriveError(f"read out of range: 0x{phys:06x}+{length}") + self._send(DATATYPE_RDRAM_READ, struct.pack(">II", phys, length)) + payload = self._recv(DATATYPE_RDRAM_DATA) + if len(payload) < length: + raise EverdriveError(f"short RDRAM read ({len(payload)}/{length})") + return payload[:length] + + def write_block(self, address: int, data: bytes): + """Write ``data`` to RDRAM starting at ``address``.""" + data = bytes(data) + phys = address & 0x7FFFFFFF + if phys + len(data) > RDRAM_SIZE: + raise EverdriveError(f"write out of range: 0x{phys:06x}+{len(data)}") + self._send(DATATYPE_RDRAM_WRITE, struct.pack(">II", phys, len(data)) + data) + payload = self._recv(DATATYPE_RDRAM_ACK) + if not payload or payload[0] != 1: + raise EverdriveError("RDRAM write was not acknowledged") + + def read_u8(self, address: int) -> int: + """Read an 8-bit unsigned integer from memory.""" + return self.read_block(address, 1)[0] + + def read_u16(self, address: int) -> int: + """Read a 16-bit unsigned integer from memory (big-endian).""" + return int.from_bytes(self.read_block(address, 2), "big") + + def read_u32(self, address: int) -> int: + """Read a 32-bit unsigned integer from memory (big-endian).""" + return int.from_bytes(self.read_block(address, 4), "big") + + def write_u8(self, address: int, value: int): + """Write an 8-bit unsigned integer to memory.""" + self.write_block(address, bytes([value & 0xFF])) + + def write_u16(self, address: int, value: int): + """Write a 16-bit unsigned integer to memory (big-endian).""" + self.write_block(address, (value & 0xFFFF).to_bytes(2, "big")) + + def write_u32(self, address: int, value: int): + """Write a 32-bit unsigned integer to memory (big-endian).""" + self.write_block(address, (value & 0xFFFFFFFF).to_bytes(4, "big")) + + def read_bytestring(self, address: int, length: int) -> str: + """Read a null-terminated bytestring from memory as a Python str.""" + data = self.read_block(address, length) + result = "" + for byte_val in data: + if byte_val == 0: + break + result += chr(byte_val) + return result + + def write_bytestring(self, address: int, data: str): + """Write a sanitized, null-terminated bytestring to memory.""" + sanitized = sanitize_and_trim(data) + payload = bytes(ord(ch) & 0xFF for ch in sanitized) + b"\x00" + self.write_block(address, payload) + + +_ED_HEADER_MAGIC = b"DMA@" +_ED_TRAILER_MAGIC = b"CMPH" + + +class EverdriveClient(_N64UsbBridgeClient): + """EverDrive 64 (V3/X7): UNFLoader ``DMA@`` … ``CMPH`` framing over an FT245 FIFO.""" + + DISPLAY_NAME = "EverDrive" + + @staticmethod + def _device_matches(dev) -> bool: + return dev.looks_like_everdrive() + + def _send(self, datatype: int, payload: bytes): + size = len(payload) + frame = bytearray(_ED_HEADER_MAGIC) + frame.append(datatype & 0xFF) + frame += size.to_bytes(3, "big") + frame += payload + # The N64 reads ALIGN(size, 2) payload bytes, then the 4 trailer bytes. + if size & 1: + frame.append(0) + frame += _ED_TRAILER_MAGIC + self._device.write(bytes(frame)) + + def _recv_frame(self) -> Tuple[int, bytes]: + header = self._device.read(8) + if header[0:4] != _ED_HEADER_MAGIC: + self._device.purge() + raise EverdriveError(f"bad USB frame header: {header[0:4]!r}") + datatype = header[4] + size = int.from_bytes(header[5:8], "big") + # N64 -> PC frames align (payload + trailer) as a unit to 2 bytes. + body = self._device.read(_align2(size + 4)) + payload = body[:size] + if body[size : size + 4] != _ED_TRAILER_MAGIC: + self._device.purge() + raise EverdriveError("bad USB frame trailer") + return datatype, bytes(payload) + + def _recv(self, expected_type: int) -> bytes: + for _ in range(8): + datatype, payload = self._recv_frame() + if datatype == expected_type: + return payload + if datatype in (DATATYPE_HEARTBEAT, DATATYPE_TEXT): + continue + self._device.purge() + raise EverdriveError(f"unexpected USB datatype {datatype:#04x}") + raise EverdriveError("no matching USB response after several frames") + + +# SC64 USB packet identifiers and the debug command/packet id ('U'). +_SC64_CMD = b"CMD" +_SC64_CMP = b"CMP" +_SC64_ERR = b"ERR" +_SC64_PKT = b"PKT" +_SC64_DEBUG_ID = ord("U") # CMD_DEBUG_WRITE and USB_PACKET_DEBUG + + +class SC64Client(_N64UsbBridgeClient): + """SummerCart64: ``CMD``/``CMP``/``PKT`` command protocol over an FT232H. + + Datatype + size are carried in a 4-byte header inside the debug payload, the + way UNFLoader and libdragon's SC64 driver do it. + """ + + DISPLAY_NAME = "SC64" + + def __init__(self): + """Initialise, with a queue for async PKTs seen while awaiting a CMP.""" + super().__init__() + self._pending: List[Tuple[int, bytes]] = [] + + @staticmethod + def _device_matches(dev) -> bool: + return dev.looks_like_sc64() + + def disconnect(self): + """Close the device and drop any queued packets.""" + self._pending = [] + super().disconnect() + + def resync(self): + """Drop queued packets, then drain the USB buffers like the base class.""" + self._pending = [] + super().resync() + + def _read_packet(self) -> Tuple[bytes, int, bytes]: + """Read one SC64 USB packet: identifier(3) + id(1) + len(4 BE) + data.""" + header = self._device.read(8) + ident = bytes(header[0:3]) + pkt_id = header[3] + length = int.from_bytes(header[4:8], "big") + data = self._device.read(length) if length else b"" + return ident, pkt_id, bytes(data) + + def _send(self, datatype: int, payload: bytes): + size = len(payload) + self._device.write(_SC64_CMD + bytes([_SC64_DEBUG_ID]) + struct.pack(">II", datatype, size) + bytes(payload)) + # Consume the command completion, stashing any async PKT that races ahead. + for _ in range(16): + ident, pkt_id, data = self._read_packet() + if ident == _SC64_CMP: + return + if ident == _SC64_ERR: + self._device.purge() + raise EverdriveError("SC64 command returned an error") + if ident == _SC64_PKT: + self._pending.append((pkt_id, data)) + continue + self._device.purge() + raise EverdriveError(f"unexpected SC64 packet {ident!r}") + raise EverdriveError("SC64: no command completion received") + + def _recv(self, expected_type: int) -> bytes: + for _ in range(16): + if self._pending: + pkt_id, data = self._pending.pop(0) + else: + ident, pkt_id, data = self._read_packet() + if ident in (_SC64_CMP, _SC64_ERR): + continue # stray completion; ignore + if ident != _SC64_PKT: + self._device.purge() + raise EverdriveError(f"unexpected SC64 packet {ident!r}") + if pkt_id != _SC64_DEBUG_ID or len(data) < 4: + continue + header = int.from_bytes(data[0:4], "big") + datatype = (header >> 24) & 0xFF + size = header & 0xFFFFFF + if datatype == expected_type: + return data[4 : 4 + size] + if datatype in (DATATYPE_HEARTBEAT, DATATYPE_TEXT): + continue + raise EverdriveError("SC64: no matching response after several packets") diff --git a/archipelago/docs/setup_en.md b/archipelago/docs/setup_en.md index 1a22378bb..8356d0321 100644 --- a/archipelago/docs/setup_en.md +++ b/archipelago/docs/setup_en.md @@ -1,3 +1,64 @@ # Donkey Kong 64 Randomizer Setup Guide -Please check the [Archipelago page on the DK64 Randomizer wiki.](https://dk64randomizer.com/wiki/index.html?title=Archipelago#setup-guide). \ No newline at end of file +Please check the [Archipelago page on the DK64 Randomizer wiki.](https://dk64randomizer.com/wiki/index.html?title=Archipelago#setup-guide). + +## Real N64 Hardware (USB flashcart) + +You can play on a real N64 with a USB flashcart instead of an emulator. The same DK64 +Client connects to the console over the cart's USB port and reads/writes the game's +memory exactly as it does for an emulator. The client auto-detects a connected cart — no +extra flag is needed. + +**Supported carts:** +- **EverDrive 64** V3 (OS 3.06 or newer) and X7. The X5 has no USB port and the V2.5 is + not supported. +- **SummerCart64 (SC64)**. *Implemented but not yet tested* + +### 1. Install the FTDI D2XX driver + +Both carts talk over FTDI USB (the EverDrive uses an FT245 chip, the SC64 an FT232H), so +the PC needs FTDI's **D2XX** driver. This is the same driver UNFLoader and the cart's own +USB tools use.` + +**Windows** — install FTDI's [CDM driver package](https://ftdichip.com/drivers/d2xx-drivers/). +Do **not** run Zadig or replace the driver with WinUSB; that breaks D2XX. + +**Linux** — install `libftd2xx` and allow your user to access the device: + +```bash +# Driver (Debian/Ubuntu/Mint example; or grab the tarball from ftdichip.com): +sudo apt install libftd2xx2 # provides libftd2xx.so + +# Let your user open the cart without root, for any FTDI device (VID 0403): +echo 'SUBSYSTEM=="usb", ATTR{idVendor}=="0403", MODE="0666", GROUP="plugdev"' \ + | sudo tee /etc/udev/rules.d/99-n64-flashcart.rules +sudo udevadm control --reload-rules && sudo udevadm trigger +``` + +If the kernel's `ftdi_sio` virtual-COM-port driver grabs the cart (a `/dev/ttyUSB*` appears +and the client can't see the device), it is holding the FTDI interface D2XX needs. Unplug +and replug the cable, or `sudo modprobe -r ftdi_sio`. + +**macOS** — install FTDI's `libftd2xx` dylib from [ftdichip.com](https://ftdichip.com/drivers/d2xx-drivers/). + +### 2. Load the ROM and play + +1. Patch your `.chunky` and place the `.z64` in your SD card. +2. Boot the patched ROM on the console from the cart, with the USB cable connected to your + PC, and play to the **title screen**. +3. Launch the **DK64 Client** and connect to your Archipelago server as normal — a connected + cart is auto-detected. + +You should see `Connected to over USB` followed by ` connected to ROM!`. + +### Troubleshooting + +- *"No … USB device was found"* — check the USB cable, that the FTDI driver is installed, + and that the cart is powered on. +- *"Could not load the FTDI D2XX driver"* — the D2XX library isn't installed (see step 1). +- *"Failed to open … already in use"* — another program (UNFLoader, the cart's USB tools, a + serial monitor) is holding the device; close it. +- The cart disappears from USB / a `/dev/ttyUSB*` shows up (Linux) — `ftdi_sio` claimed it; + replug the cable or `modprobe -r ftdi_sio` (see step 1). +- The client keeps *"waiting … for a valid ROM"* — make sure the booted ROM is this + multiworld's Archipelago seed and the game has reached the title screen. diff --git a/base-hack/asm/objects.asm b/base-hack/asm/objects.asm index c1a704ed4..efa02f81c 100644 --- a/base-hack/asm/objects.asm +++ b/base-hack/asm/objects.asm @@ -1,82 +1,83 @@ -.importobj "obj/src_a.o" .importobj "obj/src_f3dex2.o" -.importobj "obj/src_lib.o" -.importobj "obj/src_lib_dropsanity.o" .importobj "obj/src_lib_items.o" +.importobj "obj/src_lib.o" .importobj "obj/src_main.o" +.importobj "obj/src_lib_dropsanity.o" +.importobj "obj/src_a.o" +.importobj "obj/src_instances_instance_lib.o" +.importobj "obj/src_instances_misc_pads.o" +.importobj "obj/src_instances_boss_portal.o" +.importobj "obj/src_instances_bananaport.o" +.importobj "obj/src_instances_instances.o" +.importobj "obj/src_overlays_race.o" +.importobj "obj/src_overlays_jetpac.o" +.importobj "obj/src_overlays_bonus.o" +.importobj "obj/src_overlays_changes.o" +.importobj "obj/src_overlays_arcade.o" +.importobj "obj/src_overlays_boss.o" +.importobj "obj/src_overlays_menu.o" +.importobj "obj/src_fixes_helm.o" .importobj "obj/src_fixes_cannon_game.o" +.importobj "obj/src_fixes_spiders.o" +.importobj "obj/src_fixes_tag_anywhere.o" +.importobj "obj/src_fixes_quality_fixes.o" +.importobj "obj/src_fixes_parent.o" .importobj "obj/src_fixes_dk_free_softlock.o" .importobj "obj/src_fixes_guardCatch.o" -.importobj "obj/src_fixes_helm.o" .importobj "obj/src_fixes_level_modifiers.o" -.importobj "obj/src_fixes_parent.o" -.importobj "obj/src_fixes_quality_fixes.o" -.importobj "obj/src_fixes_spiders.o" -.importobj "obj/src_fixes_tag_anywhere.o" -.importobj "obj/src_initialization_cosmetic.o" .importobj "obj/src_initialization_init.o" -.importobj "obj/src_initialization_item_rando.o" .importobj "obj/src_initialization_qol.o" .importobj "obj/src_initialization_stack_trace.o" +.importobj "obj/src_initialization_item_rando.o" +.importobj "obj/src_initialization_cosmetic.o" .importobj "obj/src_initialization_text.o" -.importobj "obj/src_instances_bananaport.o" -.importobj "obj/src_instances_boss_portal.o" -.importobj "obj/src_instances_instances.o" -.importobj "obj/src_instances_instance_lib.o" -.importobj "obj/src_instances_misc_pads.o" -.importobj "obj/src_item rando_actors.o" -.importobj "obj/src_item rando_archipelago.o" -.importobj "obj/src_item rando_collision.o" -.importobj "obj/src_item rando_defs.o" .importobj "obj/src_item rando_enemy_items.o" +.importobj "obj/src_item rando_spawning.o" +.importobj "obj/src_item rando_defs.o" .importobj "obj/src_item rando_ice_trap.o" -.importobj "obj/src_item rando_item_fixes.o" -.importobj "obj/src_item rando_item_grab.o" +.importobj "obj/src_item rando_actors.o" +.importobj "obj/src_item rando_archipelago.o" .importobj "obj/src_item rando_item_handler.o" -.importobj "obj/src_item rando_spawning.o" +.importobj "obj/src_item rando_item_fixes.o" .importobj "obj/src_item rando_text.o" -.importobj "obj/src_misc_bonus.o" -.importobj "obj/src_misc_cutscene_remove.o" -.importobj "obj/src_misc_enemies.o" -.importobj "obj/src_misc_enemy_drop_table.o" -.importobj "obj/src_misc_exit_parser.o" -.importobj "obj/src_misc_file_screen.o" -.importobj "obj/src_misc_hard_mode.o" -.importobj "obj/src_misc_helm_hurry.o" -.importobj "obj/src_misc_hud.o" -.importobj "obj/src_misc_item_db.o" -.importobj "obj/src_misc_krusha.o" -.importobj "obj/src_misc_move_hints.o" -.importobj "obj/src_misc_music_text.o" -.importobj "obj/src_misc_shop_indicator.o" -.importobj "obj/src_misc_short_tiny.o" -.importobj "obj/src_misc_stats.o" -.importobj "obj/src_misc_tns_number.o" -.importobj "obj/src_misc_turf.o" -.importobj "obj/src_misc_warp_to_isles.o" -.importobj "obj/src_misc_win_condition.o" -.importobj "obj/src_overlays_arcade.o" -.importobj "obj/src_overlays_bonus.o" -.importobj "obj/src_overlays_boss.o" -.importobj "obj/src_overlays_changes.o" -.importobj "obj/src_overlays_jetpac.o" -.importobj "obj/src_overlays_menu.o" -.importobj "obj/src_overlays_race.o" +.importobj "obj/src_item rando_collision.o" +.importobj "obj/src_item rando_item_grab.o" +.importobj "obj/src_shorteners_helm.o" +.importobj "obj/src_shorteners_quality_shorteners.o" +.importobj "obj/src_shorteners_moves.o" +.importobj "obj/src_shorteners_bonus.o" +.importobj "obj/src_shorteners_pre_turns.o" +.importobj "obj/src_shorteners_vulture.o" .importobj "obj/src_pause_carousel.o" .importobj "obj/src_pause_hints.o" .importobj "obj/src_pause_hint_region_list.o" -.importobj "obj/src_pause_lib.o" .importobj "obj/src_pause_regions.o" -.importobj "obj/src_randomizers_b_locker_requirement.o" -.importobj "obj/src_randomizers_crowd_control.o" +.importobj "obj/src_pause_lib.o" .importobj "obj/src_randomizers_every_lz_rando.o" .importobj "obj/src_randomizers_k_rool_order.o" +.importobj "obj/src_randomizers_b_locker_requirement.o" +.importobj "obj/src_randomizers_crowd_control.o" +.importobj "obj/src_randomizers_move_text.o" .importobj "obj/src_randomizers_misc_requirements.o" .importobj "obj/src_randomizers_move_rando.o" -.importobj "obj/src_randomizers_move_text.o" -.importobj "obj/src_shorteners_bonus.o" -.importobj "obj/src_shorteners_helm.o" -.importobj "obj/src_shorteners_moves.o" -.importobj "obj/src_shorteners_pre_turns.o" -.importobj "obj/src_shorteners_quality_shorteners.o" -.importobj "obj/src_shorteners_vulture.o" +.importobj "obj/src_everdrive_everdrive.o" +.importobj "obj/src_misc_move_hints.o" +.importobj "obj/src_misc_short_tiny.o" +.importobj "obj/src_misc_stats.o" +.importobj "obj/src_misc_music_text.o" +.importobj "obj/src_misc_file_screen.o" +.importobj "obj/src_misc_krusha.o" +.importobj "obj/src_misc_turf.o" +.importobj "obj/src_misc_item_db.o" +.importobj "obj/src_misc_enemies.o" +.importobj "obj/src_misc_win_condition.o" +.importobj "obj/src_misc_bonus.o" +.importobj "obj/src_misc_helm_hurry.o" +.importobj "obj/src_misc_hard_mode.o" +.importobj "obj/src_misc_cutscene_remove.o" +.importobj "obj/src_misc_shop_indicator.o" +.importobj "obj/src_misc_tns_number.o" +.importobj "obj/src_misc_hud.o" +.importobj "obj/src_misc_enemy_drop_table.o" +.importobj "obj/src_misc_exit_parser.o" +.importobj "obj/src_misc_warp_to_isles.o" diff --git a/base-hack/include/common.h b/base-hack/include/common.h index a1bdfdaf2..258569e4b 100644 --- a/base-hack/include/common.h +++ b/base-hack/include/common.h @@ -31,6 +31,7 @@ #include "global.h" #include "vars.h" #include "archipelago.h" +#include "everdrive.h" #include "music.h" #include "macros.h" #include "hint_regions.h" diff --git a/base-hack/include/everdrive.h b/base-hack/include/everdrive.h new file mode 100644 index 000000000..6bc1985fe --- /dev/null +++ b/base-hack/include/everdrive.h @@ -0,0 +1,18 @@ +#ifndef _EVERDRIVE_H_ +#define _EVERDRIVE_H_ + +/** + * @file everdrive.h + * @brief Real-hardware Archipelago bridge for the EverDrive 64. + */ + +#define ED_DATATYPE_RDRAM_READ 0x30 // PC -> N64: addr(4 BE) + len(4 BE) +#define ED_DATATYPE_RDRAM_WRITE 0x31 // PC -> N64: addr(4 BE) + len(4 BE) + bytes +#define ED_DATATYPE_RDRAM_DATA 0x32 // N64 -> PC: the requested bytes +#define ED_DATATYPE_RDRAM_ACK 0x33 // N64 -> PC: 1 status byte + +// Called once when an AP seed initializes +extern void everdrive_init(void); +extern void everdrive_service(void); + +#endif // _EVERDRIVE_H_ diff --git a/base-hack/src/everdrive/everdrive.c b/base-hack/src/everdrive/everdrive.c new file mode 100644 index 000000000..5d2aeca43 --- /dev/null +++ b/base-hack/src/everdrive/everdrive.c @@ -0,0 +1,548 @@ +/** + * @file everdrive.c + * @brief USB flashcart bridge for Archipelago on real N64 hardware. + */ + +#include "../../include/common.h" + +#ifndef ALIGN2 +#define ALIGN2(v) (((v) + 1) & ~1u) +#endif +#define ED_CART_NONE 0 +#define ED_CART_EVERDRIVE 2 +#define ED_CART_SC64 3 +#define ED_RDRAM_SIZE 0x800000 +#define ED_MAX_XFER 0x200 +#define ED_BUF_SIZE 0x400 +#define ED_MAX_CMDS_PER_FRAME 8 +#define ED_TIMEOUT_MS 100 + +typedef struct PI_regs_s { + volatile void *ram_address; + u32 pi_address; + u32 read_length; + u32 write_length; + u32 status; +} PI_regs_t; +#define PI_regs ((volatile PI_regs_t *)0xA4600000) + +#define PI_STATUS_DMA_BUSY (1 << 0) +#define PI_STATUS_IO_BUSY (1 << 1) +#define PI_STATUS_CLR_INTR (1 << 1) + +#define C0_STATUS_IE 0x00000001u + +// ROM_DATA forces these into .data +ROM_DATA static u32 ed_int_sr = 0; +ROM_DATA static u32 ed_int_depth = 0; + +static void ed_disable_interrupts(void) { + if (!ed_int_depth) { + u32 sr; + __asm__ volatile("mfc0 %0,$12" : "=r"(sr)); + ed_int_sr = sr; + __asm__ volatile("mtc0 %0,$12" ::"r"(sr & ~C0_STATUS_IE)); + } + ed_int_depth++; +} + +static void ed_enable_interrupts(void) { + if (ed_int_depth == 0) { + return; + } + ed_int_depth--; + if (!ed_int_depth) { + __asm__ volatile("mtc0 %0,$12" ::"r"(ed_int_sr)); + } +} + +static int ed_dma_busy(void) { + return PI_regs->status & (PI_STATUS_DMA_BUSY | PI_STATUS_IO_BUSY); +} + +static void ed_dma_wait(void) { + while (ed_dma_busy()) { + } +} + +// VR4300 dcache line is 16 bytes; ops 0x15 = hit writeback invalidate, 0x19 = hit writeback. +#define ed_cache_op(op, linesize) \ + ({ \ + if (length) { \ + void *cur = (void *)((unsigned long)addr & ~(linesize - 1)); \ + int count = (int)length + (addr - cur); \ + for (int i = 0; i < count; i += linesize) \ + __asm__("\tcache %0,(%1)\n" ::"i"(op), "r"(cur + i)); \ + } \ + }) + +static void ed_dcache_wb_inval(volatile void *addr, unsigned long length) { + ed_cache_op(0x15, 16); +} + +static void ed_dcache_wb(volatile void *addr, unsigned long length) { + ed_cache_op(0x19, 16); +} + +static u32 ed_io_read(u32 pi_address) { + u32 value; + ed_disable_interrupts(); + ed_dma_wait(); + value = *(volatile u32 *)(pi_address | 0xA0000000); + ed_enable_interrupts(); + return value; +} + +static void ed_io_write(u32 pi_address, u32 data) { + ed_disable_interrupts(); + ed_dma_wait(); + *(volatile u32 *)(pi_address | 0xA0000000) = data; + ed_enable_interrupts(); +} + +// DMA from the cartridge (PI bus) into RDRAM. +static void ed_dma_read(void *ram_address, u32 pi_address, u32 len) { + ed_dcache_wb_inval(ram_address, len); + ram_address = (void *)((u32)ram_address & 0x1FFFFFFF); + ed_disable_interrupts(); + ed_dma_wait(); + PI_regs->ram_address = ram_address; + PI_regs->pi_address = pi_address; + PI_regs->write_length = len - 1; + ed_dma_wait(); + PI_regs->status = PI_STATUS_CLR_INTR; + ed_enable_interrupts(); +} + +// DMA from RDRAM out to the cartridge (PI bus). +static void ed_dma_write(void *ram_address, u32 pi_address, u32 len) { + ed_dcache_wb(ram_address, len); + ram_address = (void *)((u32)ram_address & 0x1FFFFFFF); + ed_disable_interrupts(); + ed_dma_wait(); + PI_regs->ram_address = ram_address; + PI_regs->pi_address = pi_address; + PI_regs->read_length = len - 1; + ed_dma_wait(); + PI_regs->status = PI_STATUS_CLR_INTR; + ed_enable_interrupts(); +} + +// C0_COUNT increments every other CPU cycle (~46.875 MHz on a 93.75 MHz VR4300). +#define ED_COUNT_PER_MS 46875u + +static u32 ed_c0_count(void) { + u32 x; + __asm__ volatile("mfc0 %0,$9\n\tnop" : "=r"(x)); + return x; +} + +static u8 ed_count_elapsed(u32 start, u32 ms) { + return (ed_c0_count() - start) >= (ms * ED_COUNT_PER_MS); +} + +/********************************* + Shared bridge state +*********************************/ + +ROM_DATA static s8 ed_cart = ED_CART_NONE; +ROM_DATA static u8 ed_buffer_raw[ED_BUF_SIZE + 16]; +ROM_DATA static u8 *ed_buffer = 0; + +static u32 ed_be32(const u8 *p) { + return ((u32)p[0] << 24) | ((u32)p[1] << 16) | ((u32)p[2] << 8) | (u32)p[3]; +} + +/********************************* + EverDrive transport +*********************************/ + +#define ED_REG_USBCFG 0x1F800004 +#define ED_REG_VERSION 0x1F800014 +#define ED_REG_USBDAT 0x1F800400 +#define ED_REG_SYSCFG 0x1F808000 +#define ED_REG_KEY 0x1F808004 + +#define ED_USBMODE_RDNOP 0xC400 +#define ED_USBMODE_RD 0xC600 +#define ED_USBMODE_WRNOP 0xC000 +#define ED_USBMODE_WR 0xC200 + +#define ED_USBSTAT_ACT 0x0200 +#define ED_USBSTAT_RXF 0x0400 +#define ED_USBSTAT_TXE 0x0800 +#define ED_USBSTAT_POWER 0x1000 + +#define ED_REGKEY 0xAA55 +#define ED25_VERSION 0xED640007 // V2.5 (no USB support) +#define ED3_VERSION 0xED640008 // V3 +#define EDX_VERSION 0xED640013 // X7 / X5 + +#define ED_FIFO_SIZE 512 + +// Spin until the USB unit finishes the current transfer, or bail on timeout. +static u8 ed_usbbusy(void) { + u32 start = ed_c0_count(); + while (ed_io_read(ED_REG_USBCFG) & ED_USBSTAT_ACT) { + if (ed_count_elapsed(start, ED_TIMEOUT_MS)) { + ed_io_write(ED_REG_USBCFG, ED_USBMODE_RDNOP); + return 1; + } + } + return 0; +} + +// True when there is data to read (USB powered and the RX FIFO has bytes). +static u8 ed_can_read(void) { + return (ed_io_read(ED_REG_USBCFG) & (ED_USBSTAT_POWER | ED_USBSTAT_RXF)) == ED_USBSTAT_POWER; +} + +// Read exactly len bytes from the USB FIFO into ram. Returns 1 on timeout. +static u8 ed_fifo_read(void *ram, u32 len) { + ed_io_write(ED_REG_USBCFG, ED_USBMODE_RDNOP); + while (len) { + u32 block = (len > ED_FIFO_SIZE) ? ED_FIFO_SIZE : len; + u32 addr = ED_FIFO_SIZE - block; + ed_io_write(ED_REG_USBCFG, ED_USBMODE_RD | addr); + if (ed_usbbusy()) { + return 1; + } + ed_dma_read(ram, ED_REG_USBDAT + addr, block); + ram = (u8 *)ram + block; + len -= block; + } + return 0; +} + +// Write exactly len bytes from ram to the USB FIFO. Returns 1 on timeout. +static u8 ed_fifo_write(void *ram, u32 len) { + ed_io_write(ED_REG_USBCFG, ED_USBMODE_WRNOP); + while (len) { + u32 block = (len > ED_FIFO_SIZE) ? ED_FIFO_SIZE : len; + u32 addr = ED_FIFO_SIZE - block; + ed_dma_write(ram, ED_REG_USBDAT + addr, block); + ed_io_write(ED_REG_USBCFG, ED_USBMODE_WR | addr); + if (ed_usbbusy()) { + return 1; + } + ram = (u8 *)ram + block; + len -= block; + } + return 0; +} + +// Build and send one UNFLoader frame: DMA@ + datatype + size + payload + CMPH, +// padded to an even length (matching everdrive_loader.py's _recv). +static void ed_everdrive_send(u8 datatype, const void *src, u32 size) { + u32 total; + ed_buffer[0] = 'D'; + ed_buffer[1] = 'M'; + ed_buffer[2] = 'A'; + ed_buffer[3] = '@'; + ed_buffer[4] = datatype; + ed_buffer[5] = (size >> 16) & 0xFF; + ed_buffer[6] = (size >> 8) & 0xFF; + ed_buffer[7] = size & 0xFF; + if (size) { + dk_memcpy(ed_buffer + 8, (void *)src, size); + } + total = 8 + size; + ed_buffer[total + 0] = 'C'; + ed_buffer[total + 1] = 'M'; + ed_buffer[total + 2] = 'P'; + ed_buffer[total + 3] = 'H'; + total += 4; + if (total & 1) { + ed_buffer[total] = 0; + total++; + } + ed_fifo_write(ed_buffer, total); +} + +// Read one command frame. Returns 1 and fills datatype/size (payload in ed_buffer) +// if a valid command is pending, else 0. +static u8 ed_everdrive_recv(u8 *datatype, u32 *size) { + u32 sz, bodylen; + const u8 *cmp; + + if (!ed_can_read()) { + return 0; // nothing pending + } + if (ed_fifo_read(ed_buffer, 8)) { + return 0; // timed out reading the header + } + if (ed_buffer[0] != 'D' || ed_buffer[1] != 'M' || ed_buffer[2] != 'A' || ed_buffer[3] != '@') { + return 0; // desynced; PC will purge and retry + } + *datatype = ed_buffer[4]; + sz = ((u32)ed_buffer[5] << 16) | ((u32)ed_buffer[6] << 8) | (u32)ed_buffer[7]; + bodylen = ALIGN2(sz) + 4; // PC -> N64: payload padded to even, then CMPH + if (bodylen > ED_BUF_SIZE) { + return 0; // oversized; drop + } + if (ed_fifo_read(ed_buffer, bodylen)) { + return 0; + } + cmp = ed_buffer + ALIGN2(sz); + if (cmp[0] != 'C' || cmp[1] != 'M' || cmp[2] != 'P' || cmp[3] != 'H') { + return 0; // bad trailer + } + *size = sz; + return 1; +} + +static u8 ed_everdrive_detect(void) { + u32 version; + ed_io_write(ED_REG_KEY, ED_REGKEY); + version = ed_io_read(ED_REG_VERSION); + if (version != ED3_VERSION && version != EDX_VERSION) { + return 0; // V2.5, an emulator's open-bus, or a non-EverDrive cart + } + ed_io_write(ED_REG_SYSCFG, 0); + ed_io_write(ED_REG_USBCFG, ED_USBMODE_RDNOP); + if ((ed_io_read(ED_REG_USBCFG) & ED_USBSTAT_POWER) == 0) { + return 0; // USB unit unpowered (X5 variant) + } + return 1; +} + +/********************************* + SC64 transport +*********************************/ + +#define SC64_REG_SR_CMD 0x1FFF0000 +#define SC64_REG_DATA_0 0x1FFF0004 +#define SC64_REG_DATA_1 0x1FFF0008 +#define SC64_REG_IDENTIFIER 0x1FFF000C +#define SC64_REG_KEY 0x1FFF0010 + +#define SC64_SR_CMD_ERROR (1u << 30) +#define SC64_SR_CMD_BUSY (1u << 31) + +#define SC64_V2_IDENTIFIER 0x53437632 +#define SC64_KEY_RESET 0x00000000 +#define SC64_KEY_UNLOCK_1 0x5F554E4C +#define SC64_KEY_UNLOCK_2 0x4F434B5F + +#define SC64_CMD_CONFIG_SET 'C' +#define SC64_CMD_USB_WRITE_STATUS 'U' +#define SC64_CMD_USB_WRITE 'M' +#define SC64_CMD_USB_READ_STATUS 'u' +#define SC64_CMD_USB_READ 'm' + +#define SC64_CFG_ROM_WRITE_ENABLE 1 +#define SC64_USB_WRITE_STATUS_BUSY (1u << 31) +#define SC64_USB_READ_STATUS_BUSY (1u << 31) + +// Scratch region in cart SDRAM used to stage USB data, reserving the top 8 MB +// (libdragon's DEBUG_ADDRESS_SIZE) of the 64 MB cart space: 0x10000000 + +// (0x04000000 - 0x800000). Safe as long as the DK64 ROM image is < 56 MB. +#define SC64_STAGING 0x13800000 + +// Execute one SC64 controller command. Returns 1 on error/timeout, 0 on success. +static u8 sc64_exec(u8 cmd, const u32 *args, u32 *result) { + u32 sr; + u32 start; + if (args) { + ed_io_write(SC64_REG_DATA_0, args[0]); + ed_io_write(SC64_REG_DATA_1, args[1]); + } + ed_io_write(SC64_REG_SR_CMD, cmd); + start = ed_c0_count(); + do { + sr = ed_io_read(SC64_REG_SR_CMD); + if (ed_count_elapsed(start, ED_TIMEOUT_MS)) { + return 1; + } + } while (sr & SC64_SR_CMD_BUSY); + if (result) { + result[0] = ed_io_read(SC64_REG_DATA_0); + result[1] = ed_io_read(SC64_REG_DATA_1); + } + return (sr & SC64_SR_CMD_ERROR) ? 1 : 0; +} + +static void sc64_send(u8 datatype, const void *src, u32 size) { + u32 args[2]; + u32 result[2]; + u32 start; + u32 prev_writable; + + // Wait until any previous USB write has finished. + start = ed_c0_count(); + do { + if (sc64_exec(SC64_CMD_USB_WRITE_STATUS, 0, result)) { + return; + } + if (ed_count_elapsed(start, ED_TIMEOUT_MS)) { + return; + } + } while (result[0] & SC64_USB_WRITE_STATUS_BUSY); + + if (size) { + dk_memcpy(ed_buffer, (void *)src, size); + } + + // Enable SDRAM writes, stage the data, restore the previous setting. + args[0] = SC64_CFG_ROM_WRITE_ENABLE; + args[1] = 1; + sc64_exec(SC64_CMD_CONFIG_SET, args, result); + prev_writable = result[1]; + ed_dma_write(ed_buffer, SC64_STAGING, ALIGN2(size)); + args[0] = SC64_CFG_ROM_WRITE_ENABLE; + args[1] = prev_writable; + sc64_exec(SC64_CMD_CONFIG_SET, args, result); + + // Trigger the USB send: arg0 = staging address, arg1 = (datatype<<24)|size. + args[0] = SC64_STAGING; + args[1] = ((u32)datatype << 24) | (size & 0xFFFFFF); + if (sc64_exec(SC64_CMD_USB_WRITE, args, 0)) { + return; + } + + start = ed_c0_count(); + do { + if (sc64_exec(SC64_CMD_USB_WRITE_STATUS, 0, result)) { + return; + } + if (ed_count_elapsed(start, ED_TIMEOUT_MS)) { + return; + } + } while (result[0] & SC64_USB_WRITE_STATUS_BUSY); +} + +// Returns 1 and fills datatype/size (payload in ed_buffer) if a command is +// pending, else 0. +static u8 sc64_recv(u8 *datatype, u32 *size) { + u32 args[2]; + u32 result[2]; + u32 sz; + u32 start; + + if (sc64_exec(SC64_CMD_USB_READ_STATUS, 0, result)) { + return 0; + } + sz = result[1] & 0xFFFFFF; + if (sz == 0) { + return 0; // nothing pending + } + if (sz > ED_BUF_SIZE) { + sz = ED_BUF_SIZE; + } + + // Ask the SC64 to receive the pending data into the staging region. + args[0] = SC64_STAGING; + args[1] = sz; + if (sc64_exec(SC64_CMD_USB_READ, args, 0)) { + return 0; + } + start = ed_c0_count(); + do { + if (sc64_exec(SC64_CMD_USB_READ_STATUS, 0, result)) { + return 0; + } + if (ed_count_elapsed(start, ED_TIMEOUT_MS)) { + return 0; + } + } while (result[0] & SC64_USB_READ_STATUS_BUSY); + + ed_dma_read(ed_buffer, SC64_STAGING, ALIGN2(sz)); + *datatype = result[0] & 0xFF; + *size = sz; + return 1; +} + +static u8 sc64_detect(void) { + ed_io_write(SC64_REG_KEY, SC64_KEY_RESET); + ed_io_write(SC64_REG_KEY, SC64_KEY_UNLOCK_1); + ed_io_write(SC64_REG_KEY, SC64_KEY_UNLOCK_2); + return ed_io_read(SC64_REG_IDENTIFIER) == SC64_V2_IDENTIFIER; +} + +/********************************* + Command handler +*********************************/ + +static void cart_send(u8 datatype, const void *src, u32 size) { + if (ed_cart == ED_CART_EVERDRIVE) { + ed_everdrive_send(datatype, src, size); + } else if (ed_cart == ED_CART_SC64) { + sc64_send(datatype, src, size); + } +} + +static u8 cart_recv(u8 *datatype, u32 *size) { + if (ed_cart == ED_CART_EVERDRIVE) { + return ed_everdrive_recv(datatype, size); + } else if (ed_cart == ED_CART_SC64) { + return sc64_recv(datatype, size); + } + return 0; +} + +// payload/size are the validated command body; payload lives in ed_buffer. +static void ed_handle_command(u8 datatype, const u8 *payload, u32 size) { + if (datatype == ED_DATATYPE_RDRAM_READ && size >= 8) { + u32 addr = ed_be32(payload) & 0x7FFFFFFF; + u32 len = ed_be32(payload + 4); + if (len > ED_MAX_XFER) { + len = ED_MAX_XFER; + } + if (addr >= ED_RDRAM_SIZE) { + len = 0; + } else if (addr + len > ED_RDRAM_SIZE) { + len = ED_RDRAM_SIZE - addr; + } + cart_send(ED_DATATYPE_RDRAM_DATA, (void *)(addr | 0x80000000), len); + } else if (datatype == ED_DATATYPE_RDRAM_WRITE && size >= 8) { + u32 addr = ed_be32(payload) & 0x7FFFFFFF; + u32 len = ed_be32(payload + 4); + u8 ack = 1; + if (len > ED_MAX_XFER || (8 + len) > size) { + ack = 0; + } else if (addr + len > ED_RDRAM_SIZE) { + ack = 0; + } else if (len) { + dk_memcpy((void *)(addr | 0x80000000), (void *)(payload + 8), len); + } + cart_send(ED_DATATYPE_RDRAM_ACK, &ack, 1); + } +} + +/********************************* + Entry points +*********************************/ + +void everdrive_init(void) { + ed_cart = ED_CART_NONE; + + // The RDP clock register reads 0 on emulators; bail so this stays a no-op + // and the emulator memory-attach path is completely unaffected. + if (*(volatile u32 *)0xA4100010 == 0) { + return; + } + + if (ed_everdrive_detect()) { + ed_cart = ED_CART_EVERDRIVE; + } else if (sc64_detect()) { + ed_cart = ED_CART_SC64; + } else { + return; // unsupported cart / emulator open-bus + } + + ed_buffer = (u8 *)(((u32)ed_buffer_raw + 0xF) & ~0xF); +} + +void everdrive_service(void) { + if (ed_cart == ED_CART_NONE) { + return; + } + for (int i = 0; i < ED_MAX_CMDS_PER_FRAME; i++) { + u8 datatype; + u32 size; + if (!cart_recv(&datatype, &size)) { + return; + } + ed_handle_command(datatype, ed_buffer, size); + } +} diff --git a/base-hack/src/item rando/archipelago.c b/base-hack/src/item rando/archipelago.c index 9b346bb98..13e9c6a15 100644 --- a/base-hack/src/item rando/archipelago.c +++ b/base-hack/src/item rando/archipelago.c @@ -24,6 +24,7 @@ void initAP(void) { APData = &ap_info; ap_info.text_timer = 0x82; ap_info.tag_kong = -1; + everdrive_init(); } } diff --git a/base-hack/src/lib_items.c b/base-hack/src/lib_items.c index 3498e13ad..da9a8b783 100644 --- a/base-hack/src/lib_items.c +++ b/base-hack/src/lib_items.c @@ -1238,7 +1238,7 @@ health_damage_struct actor_health_damage[] = { {.init_health = 0, .damage_applied = 0}, {.init_health = 0, .damage_applied = 0}, {.init_health = 0, .damage_applied = 0}, - {.init_health = 0, .damage_applied = 0}, + {.init_health = 0, .damage_applied = 1}, {.init_health = 0, .damage_applied = 0}, {.init_health = 0, .damage_applied = 0}, {.init_health = 0, .damage_applied = 0}, diff --git a/base-hack/src/main.c b/base-hack/src/main.c index 73774198a..e09b14380 100644 --- a/base-hack/src/main.c +++ b/base-hack/src/main.c @@ -317,6 +317,7 @@ void earlyFrame(void) { } if (isAPEnabled()) { handleArchipelagoFeed(); + everdrive_service(); } if (CurrentMap == MAP_FUNGI) { if (Rando.fungi_time_of_day_setting == TIME_PROGRESSIVE) { diff --git a/static/patches/shrink-dk64.bps b/static/patches/shrink-dk64.bps index 472add663..e694d3536 100644 Binary files a/static/patches/shrink-dk64.bps and b/static/patches/shrink-dk64.bps differ diff --git a/static/patches/symbols.json b/static/patches/symbols.json index 6bc3a1901..f12733aec 100644 --- a/static/patches/symbols.json +++ b/static/patches/symbols.json @@ -268,800 +268,802 @@ "crankycoconutdonation_funky": 2153523420, "handlesnideendreward": 2153523428, "handlesnideendreward_finish": 2153523488, + "drawimage": 2153523504, "end_hook": 2153523504, - "spoiler_items": 2153523504, - "display_billboard_fix": 2153523784, - "hints_initialized": 2153523785, - "gamestats": 2153523788, - "enable_skip_check": 2153523800, - "ice_trap_queued": 2153523804, - "enemy_rewards_spawned": 2153523808, - "bonus_shown": 2153523824, - "base_text_color": 2153523828, - "crate_flags": 2153523832, - "patch_flags": 2153523896, - "balloon_path_pointers": 2153523960, - "headsize": 2153524216, - "force_enable_diving_timer": 2153524456, - "guard_tag_timer": 2153524460, - "tag_locked": 2153524462, - "itemloc_pointers": 2153524464, - "hint_pointers": 2153524948, - "cs_skip_db": 2153525368, - "text_overlay_data": 2153525376, - "static_mtx": 2153525440, - "drawimage": 2153526848, - "drawpixeltext": 2153527068, - "drawpixeltextcontainer": 2153527268, - "displaycenteredtext": 2153527452, - "drawscreenrect": 2153527580, - "drawstring": 2153527816, - "drawtext": 2153527984, - "drawtextcontainer": 2153528356, - "setcharacterrecoloring": 2153528576, - "setcharactercolor": 2153528596, - "applyhintrecoloring": 2153528632, - "getoverlayfrommap": 2153528988, - "getbitarrayvalue": 2153529008, - "inminigame": 2153529040, - "playsfx": 2153529056, - "setpermflag": 2153529108, - "convertidtoindex": 2153529120, - "convertsubidtoindex": 2153529196, - "isflaginrange": 2153529272, - "findactorwithtype": 2153529304, - "createcollisionobjinstance": 2153529376, - "resetmapcontainer": 2153529468, - "getcenter": 2153529524, - "cancelcutscene": 2153529600, - "modifycutscenepoint": 2153529736, - "modifycutsceneitem": 2153529808, - "modifycutscenepanpoint": 2153529888, - "modifycutscenepointtime": 2153530028, - "getwrinklylevelindex": 2153530104, - "getmingb": 2153530120, - "givegb": 2153530212, - "gettotalcbcount": 2153530316, - "inshortlist": 2153530416, - "inu8list": 2153530468, - "inshop": 2153530524, - "correctdkportal": 2153530540, - "spawnitemoverlay": 2153530816, - "giveslamlevel": 2153531068, - "inbattlecrown": 2153531136, - "intraining": 2153531176, - "inbossmap": 2153531208, - "isgamemode": 2153531308, - "filtersong": 2153531368, - "death": 2153531600, - "readfilesimple": 2153531648, - "savefilesimple": 2153531668, - "applydamagemask": 2153531712, - "replacewatertexture": 2153532040, - "replacewatertexture_spooky": 2153532084, - "isbounceobject": 2153532128, - "getfile": 2153532144, - "getpercentageofitem": 2153532232, - "getgamepercentage": 2153532288, - "gettotalmovecount": 2153532420, - "getitemcountreq": 2153532772, - "isitemrequirementsatisfied": 2153533260, - "getshopdata": 2153533324, - "exitboss": 2153533488, - "iskrushaadjacentmodel": 2153533576, - "isglobalcutsceneplaying": 2153533608, - "determinelevel_newlevel": 2153533668, - "refillifrefillable": 2153533904, - "refillhealthoninit": 2153534032, - "refillplayerhealthkko": 2153534092, - "isdynflag": 2153534124, - "getprojectilecount_modified": 2153534240, - "applybuttonbansinternals": 2153534664, - "guard_enabled_buttons": 2153534844, - "trap_enabled_buttons": 2153534846, - "cc_enabled_buttons": 2153534848, - "enabled_buttons": 2153534850, - "button_swaps": 2153534852, - "big_head_actors": 2153534900, - "ap_overlay_sprite": 2153535340, - "night_overlay_sprite": 2153535364, - "day_overlay_sprite": 2153535388, - "halfmedal_sprite": 2153535412, - "boulder_sprite": 2153535448, - "potion_sprite": 2153535484, - "company_coin_sprite": 2153535524, - "fool_overlay_sprite": 2153535576, - "feather_gun_sprite": 2153535600, - "krool_sprite": 2153535636, - "pearl_sprite": 2153535660, - "bean_sprite": 2153535684, - "regular_boss_maps": 2153535988, - "crown_maps": 2153535996, - "colorblind_colors": 2153536008, - "kong_pellets": 2153536056, - "tnsportal_flags": 2153536064, - "bfi_move_flags": 2153536080, - "tbarrel_flags": 2153536084, - "normal_key_flags": 2153536092, - "kong_flags": 2153536108, - "levels": 2153536260, - "dropsanity_levels": 2153536316, - "new_flag_mapping": 2153536532, - "actor_extra_data_sizes": 2153537508, - "actor_functions": 2153540464, - "actor_collisions": 2153543420, - "actor_health_damage": 2153549332, - "actor_interactions": 2153552288, - "actor_master_types": 2153553768, - "actor_defs": 2153554508, - "drops": 2153562476, - "object_collisions": 2153562716, - "item_scales": 2153564588, - "actor_drops": 2153564988, - "bounce_objects": 2153565088, - "acceptable_items": 2153565168, - "resetpicturestatus": 2153565260, - "cfuncloop": 2153565328, - "earlyframe": 2153566908, - "insertrommessages": 2153568724, - "drawinfotext": 2153569004, - "displaymenuwarnings": 2153569168, - "displaylistmodifiers": 2153569392, - "togglestandardammo": 2153571056, - "grab_lock_timer": 2153571527, - "handlecannongamereticle": 2153571940, - "resetcannongamestate": 2153572096, - "freedk": 2153572144, - "cutscenedkcode": 2153572188, - "charspawneritemcode": 2153572732, - "isbadmovementstate": 2153573068, - "guardcatchinternal": 2153573108, - "guardcatch": 2153573544, - "warphandle": 2153573704, - "catchwarphandle": 2153573828, - "inrabbitrace": 2153574104, - "renderkoplighthandler": 2153574152, - "newguardcode": 2153574268, - "setkopidleguarantee": 2153575064, - "givekopidleguarantee": 2153575088, - "fixhelmtimercorrection": 2153575188, - "helmtime_restart": 2153575316, - "helmtime_exitbonus": 2153575352, - "helmtime_exitrace": 2153575424, - "helmtime_exitlevel": 2153575452, - "helmtime_exitboss": 2153575480, - "helmtime_exitkrool": 2153575512, - "inithelmsetup": 2153575544, - "gethelmexit": 2153575620, - "warptohelm": 2153575672, - "helm_entry_points": 2153575708, - "load_object_script": 2153575712, - "adjust_level_modifiers": 2153575872, - "handletimeofday": 2153576108, - "istimeofday": 2153576796, - "ispreventcutsceneplaying": 2153576952, - "callparentmapfilter": 2153577124, - "qualityoflife_fixes": 2153577420, - "dropwrapper": 2153577732, - "candive_withcheck": 2153577776, - "playtransformationsong": 2153577880, - "hasenoughcbs": 2153577952, - "shouldding": 2153578060, - "renderindicatorsprite": 2153578256, - "renderdingsprite": 2153578760, - "initdingsprite": 2153578860, - "gethomingcountwithabilitycheck": 2153578876, - "playcbding": 2153578912, - "cbding": 2153578940, - "fixrbslowturn": 2153579088, - "postkroolsavecheck": 2153579136, - "tagbarrelbackgroundkong": 2153579200, - "updatemultibunchcount": 2153579248, - "rabbitraceinfinitecode": 2153579300, - "fixdillotntpads": 2153579444, - "canplayjetpac": 2153579488, - "fixcrownentryskong": 2153579548, - "reducetrapbubblelife": 2153579568, - "exittrapbubblecontroller": 2153579608, - "fixchimpycambug": 2153579864, - "movepelletwrapper": 2153579988, - "fixhelmtimerdisable": 2153580340, - "handlespidersilkspawn": 2153580444, - "spiderbossextracode": 2153580512, - "intransform": 2153580708, - "cantaganywhere": 2153580796, - "gettastate": 2153581232, - "hasaccesstokong": 2153581244, - "gettaganywherekong": 2153581260, - "changekong": 2153581432, - "taganywhere": 2153582044, - "taganywhereinit": 2153582716, - "populatesfxcache": 2153582800, - "handlesfxcache": 2153583020, - "taganywhereammo": 2153583348, - "handlegrabbinglock": 2153583524, - "canplayerclimb": 2153583560, - "handleledgelock": 2153583676, - "handlepushlock": 2153583776, - "handleactionset": 2153583812, - "updatekongtb": 2153584168, - "updateactorhandstates": 2153584288, - "cleargunhandler": 2153584492, - "updateactorhandstates_gun": 2153584632, - "pulloutgunhandler": 2153584972, - "determineshockwavecolor": 2153585388, - "initcosmetic": 2153585584, - "setfog": 2153585756, - "getrainbowammocolor": 2153585884, - "updatespritecolor": 2153586088, - "colorrainbowammohud": 2153586140, - "colorrainbowammohud_0": 2153586204, - "colorrainbowammo": 2153586268, - "sethudupdatefunction": 2153586328, - "sethudupdatefunction_0": 2153586360, - "pickrandomfrompool": 2153586432, - "playbonussong": 2153586520, - "playbosssong": 2153586608, - "playsongwcheck": 2153586760, - "stopbosssong": 2153587116, - "resetbosssong": 2153587312, - "playnotbosssong": 2153587332, - "getoscillationdelta": 2153587584, - "fixmusicrando": 2153587624, - "writeendsequence": 2153587676, - "loadhooks": 2153587744, - "inithack": 2153587860, - "quickinit": 2153588592, - "music_types": 2153612680, - "getcrownindex": 2153612864, - "getkeyindex": 2153612904, - "getpatchflag": 2153612944, - "getpatchworld": 2153613020, - "getcrateflag": 2153613044, - "getcrateworld": 2153613120, - "getbonusflag": 2153613144, - "updateboulderid": 2153613180, - "getboulderindex": 2153613204, - "getbarrelskinindex": 2153613320, - "getshopskinindex": 2153613400, - "alterbonusvisuals": 2153613536, - "getdirtpatchskin": 2153613756, - "inititemrando": 2153614004, - "m2_cb_coin_counts": 2153614072, - "actor_cb_counts": 2153614088, - "snide_rewards": 2153614532, - "company_coin_table": 2153614852, - "kong_check_data": 2153614860, - "bonus_data": 2153614892, - "balloon_item_table": 2153615684, - "box_item_table": 2153616100, - "boulder_item_table": 2153616196, - "extra_actor_spawns": 2153616324, - "crate_item_table": 2153616332, - "rcoin_item_table": 2153616396, - "fairy_item_table": 2153616460, - "key_item_table": 2153616620, - "crown_item_table": 2153616684, - "wrinkly_item_table": 2153616724, - "medal_item_table": 2153616864, - "bp_item_table": 2153617204, - "disableantialiasing": 2153617636, - "initqol_cutscenes": 2153617816, - "fixracehoopcode": 2153617892, - "renderhoop": 2153617908, - "fixupdraftbug": 2153617972, - "quickwrinklytextboxes": 2153618044, - "bootspeedup": 2153618096, - "initqol_fastwarp": 2153618128, - "writespawn": 2153618204, - "initspawn": 2153618228, - "qol_displayinstrument": 2153618312, - "headphonescodecontainer": 2153618328, - "newinstrumentrefill": 2153618376, - "getinstrumentrefillcount": 2153618420, - "correctrefillcap": 2153618496, - "initqol": 2153618524, - "crashhandler": 2153618620, - "getfaultedthread": 2153619732, - "version_string": 2153619816, - "displaymodifiedtext": 2153621348, - "inittextchanges": 2153621456, - "dark_mode_colors": 2153621620, - "emph_text_colors": 2153621660, - "bananaportgenericcode": 2153621700, - "tnsindicatorgenericcode": 2153623692, - "spawnwrinklywrapper": 2153623988, - "loadwrinklytextwrapper": 2153624384, - "portalwarpfix": 2153624428, - "change_object_scripts": 2153624484, - "setobjectopacity": 2153629404, - "hideobject": 2153629428, - "standingonm2object": 2153629468, - "canopenspecificblocker": 2153629516, - "canopenxblockers": 2153629612, - "activategonepad": 2153629812, - "spritecode": 2153629872, - "nothingcode": 2153630068, - "kongdropcode": 2153630080, - "potioncode": 2153630208, - "fairyduplicatecode": 2153630272, - "shopowneritemcode": 2153630340, - "missingshopownercode": 2153630432, - "crankycodehandler": 2153630636, - "funkycodehandler": 2153630684, - "candycodehandler": 2153630732, - "snidecodehandler": 2153630780, - "fakekeycode": 2153630828, - "fakegbcode": 2153630876, - "fakefairycode": 2153630924, - "mermaidcheck": 2153630972, - "fairyqueencutsceneinit": 2153631080, - "fairyqueencutscenecheck": 2153631152, - "fairyqueencheckspeedup": 2153631376, - "cannoncodewrapper": 2153631416, - "setuphook": 2153631572, - "checkkasplatspawnbitfield": 2153632560, - "isflaggedwatermelon": 2153633020, - "canitempersist": 2153633116, - "isapenabled": 2153633624, - "initap": 2153633644, - "initapcounter": 2153633712, - "saveapcounter": 2153633772, - "handlesentitem": 2153633824, - "sendtraplink": 2153633948, - "canreceiveitem": 2153633992, - "canreceiveshopkeeperitem": 2153634108, - "candie": 2153634160, - "handlearchipelagofeed": 2153634304, - "senddeath": 2153634876, - "displayapconnection": 2153634944, - "getcollisionsquare_new": 2153635288, - "writeitemscale": 2153635408, - "writeitemactorscale": 2153635600, - "getitemrequiredkong": 2153635640, - "iscollidingwithsphere": 2153635680, - "iscollidingwithcylinder": 2153635836, - "isvalidkongcollision": 2153636320, - "checkmodeltwoitemcollision": 2153636388, - "checkforvalidcollision": 2153637012, - "initactordefs": 2153637612, - "setactordamage": 2153637660, - "swapkremlingmodel": 2153637684, - "getbpcountstats": 2153637792, - "getturnedcount": 2153637888, - "turnedallin": 2153637988, - "hasturnedinatleast": 2153638024, - "getfirstemptysnidereward": 2153638096, - "fixed_bug_collision": 2153638220, - "fixed_klap_collision": 2153638244, - "fixed_dice_collision": 2153638340, - "fixed_scarab_collision": 2153638484, - "fixed_shockwave_collision": 2153638532, - "setenemydbpopulation": 2153638568, - "populateenemymapdata": 2153638580, - "getenemyitem": 2153638780, - "getenemyflag": 2153638816, - "wipeenemyspawnbitfield": 2153638864, - "setspawnbitfield": 2153638900, - "setspawnbitfieldfromflag": 2153638976, - "canspawnenemyreward": 2153639048, - "getrngwithinrange": 2153639168, - "rendersparkles": 2153639252, - "indicatecollectionstatus": 2153639516, - "fireballenemydeath": 2153639712, - "rulerenemydeath": 2153639788, - "tomatodeath": 2153639872, - "resetscreenflip": 2153641012, - "resettaganywhere": 2153641032, - "customdamagecode": 2153641056, - "trapplayer_new": 2153641196, - "renderspritesonplayer": 2153641472, - "wipereplenishibles": 2153641700, - "swapbuttons": 2153641840, - "initicetrap": 2153642092, - "slippeelcode": 2153642872, - "reseticetrapbuttons": 2153643036, - "queueicetrap": 2153643100, - "isbannedtrapmap": 2153643128, - "canloadicetrap": 2153643200, - "handleicetrapbuttons": 2153643464, - "istrapmodel": 2153643864, - "cancelicetrapsong": 2153643892, - "playicetrapsong": 2153643940, - "callicetrap": 2153643996, - "refreshpads": 2153644600, - "ismodeltwotiedflag_new": 2153644924, - "displaymedaloverlay": 2153645552, - "banana_medal_check": 2153646140, - "banana_medal_acquisition": 2153646208, - "getflagindex_corrected": 2153646424, - "giveitemfromsend": 2153646480, - "giveitemfromkongdata": 2153646576, - "givefairyitem": 2153646624, - "iscavesbeetlereward": 2153646688, - "candanceskip": 2153646756, - "forcedance": 2153647048, - "getitem": 2153647144, - "getobjectcollectability": 2153648604, - "iscollectable": 2153649044, - "handlemodeltwoopacity": 2153649328, - "getflagmappingdata": 2153649412, - "givecb": 2153649508, - "updateitemtotalshandler": 2153649752, - "giveitem": 2153653704, - "giveitemfrompacket": 2153655692, - "getitemcount_new": 2153655728, - "getkongownershipfromflag": 2153656788, - "givekongfromflag": 2153656848, - "hasflagmove": 2153656968, - "setflagmove": 2153657060, - "getshopflag": 2153657160, - "grabfileparameters_fileinfo": 2153657304, - "inititemrandopointer": 2153657408, - "readitemsfromfile": 2153657428, - "saveitemstofile": 2153657748, - "file_info_expansion": 2153658064, - "spawnactorwithflaghandler": 2153658516, - "spawnweirdreward": 2153658664, - "spawnweirdreward0": 2153658768, - "spawnbonusreward": 2153658860, - "spawnrewardatactor": 2153659016, - "spawnminecartreward": 2153659248, - "spawncrownreward": 2153659404, - "spawnbossreward": 2153659572, - "spawndirtpatchreward": 2153659900, - "spawncharspawneractor": 2153660124, - "meloncrateitemhandler": 2153660368, - "updatekegids": 2153660868, - "isvalidboulderobject": 2153661084, - "isvalidbreakableobject": 2153661216, - "spawnboulderobject": 2153661352, - "spawnbreakableobject": 2153661540, - "renderbouldersparkles": 2153661732, - "shoulddeleteballoon": 2153661932, - "isvalidballoonobject": 2153662008, - "balloonvishandler": 2153662152, - "balloonvishandler2": 2153662296, - "balloonitemhandler": 2153662472, - "handledynamicitemtext": 2153662876, - "gettextdata": 2153662960, - "getcharwidthmask": 2153663036, - "caves_beetle": 2153663084, - "aztec_beetle": 2153663116, - "renderget": 2153663152, - "wonarena": 2153663416, - "resolvebonuscontainer": 2153663440, - "warpoutofarenas": 2153663508, - "failtraining": 2153663632, - "warpoutoftraining": 2153663732, - "arenatagkongcode": 2153663900, - "arenaearlycompletioncheck": 2153664200, - "getminecartmayhemcoinreq": 2153664344, - "rendergetwrapper": 2153664412, - "wonminecartmayhem": 2153664516, - "initmmayhem": 2153664564, - "mayhem_minecart_size": 2153664720, - "updateskipcheck": 2153664764, - "iscutsceneskipped": 2153664820, - "cancelcutsceneinternals": 2153664864, - "clearqueuedcutscenefunctions": 2153664944, - "presstoskip": 2153664996, - "clearskipcache": 2153665124, - "pressskiphandler": 2153665156, - "updateskippablecutscenes": 2153665200, - "renderscreentransitioncheck": 2153665304, - "beaverextrahithandle": 2153665444, - "handlecrowntimerinternal": 2153665532, - "handlecrowntimer": 2153665792, - "klumpcrownhandler": 2153665868, - "handlebugenemy": 2153665932, - "kioskbugend": 2153666296, - "stomphandler": 2153666420, - "kioskbugcode": 2153666544, - "spawnenemydrops": 2153667468, - "loadexits": 2153668464, - "wipetrackercache": 2153668608, - "getenabledstate": 2153668708, - "updateenabledstates": 2153669000, - "inittracker": 2153669352, - "resettracker": 2153669364, - "modifytrackerimage": 2153669376, - "display_file_images": 2153670160, - "display_text": 2153670436, - "displayhash": 2153670932, - "correctkongfaces": 2153671284, - "wipefilemod": 2153671464, - "enterfileprogress": 2153671520, - "givecollectables": 2153671564, - "setalldefaultflags": 2153671756, - "startfile": 2153671856, - "testpasswordsequence": 2153672408, - "wipepassword": 2153672464, - "handlepassword": 2153672508, - "password_screen_code": 2153672696, - "password_screen_init": 2153672940, - "password_screen_gfx": 2153673084, - "file_progress_screen_code": 2153673508, - "displayinverted": 2153673736, - "setprevsavemap": 2153673808, - "quitgame": 2153673828, - "updateleveligt": 2153673856, - "changefileselectaction": 2153674108, - "changefileselectaction_0": 2153674388, - "pregiven_status": 2153674580, - "k_rool_text": 2153675264, - "getmemorychallengetype": 2153675564, - "isdarkworld": 2153675648, - "alterchunklighting": 2153675804, - "alterchunkdata": 2153676084, - "shinelight": 2153676220, - "isskyworld": 2153676428, - "displaynogeochunk": 2153676508, - "setfalldamageimmunity": 2153676628, - "handlefalldamageimmunity": 2153676640, - "transformbarrelimmunity": 2153676708, - "factoryshedfallimmunity": 2153676736, - "falldamagewrapper": 2153676772, - "spawnstalactite": 2153676820, - "spawnkroollankyballoon": 2153677056, - "popexistingballoon": 2153677152, - "handlekrooldirecting": 2153677240, - "inchitcounter": 2153677528, - "parsecontrollerinput": 2153677584, - "hitlessdeath": 2153677720, - "hitlessdeathwrapper0": 2153677884, - "hitlessdeathwrapper1": 2153677940, - "livesdisplay": 2153678016, - "sethardpathspeed": 2153678296, - "blast_maps": 2153678340, - "cansavehelmhurry": 2153678440, - "addhelmtime": 2153678564, - "checktotalcache": 2153678644, - "finishhelmhurry": 2153678704, - "inithelmhurry": 2153678756, - "savehelmhurrytime": 2153678864, - "inithuddirection": 2153678988, - "allocatehud": 2153679120, - "sethudkong": 2153680104, - "setallhudkongs": 2153680136, - "gethudsprite_complex": 2153680188, - "updatebarriercounts": 2153680528, - "displaybarrierhud": 2153680676, - "canusedpad": 2153680736, - "drawdpad": 2153680988, - "handledpadfunctionality": 2153681940, - "item_db": 2153683384, - "adjustgunbone": 2153684572, - "getcutscenemodeltableindex": 2153684720, - "fixcutscenemodels": 2153684772, - "adjustanimationtables": 2153684996, - "krushaslide": 2153685628, - "adaptkrushazbanimation_punchostand": 2153685712, - "adaptkrushazbanimation_charge": 2153685992, - "diddyswimfix": 2153686220, - "minecartjumpfix": 2153686380, - "minecartjumpfix_0": 2153686400, - "setkrushaammocolor": 2153686472, - "orangeguncode": 2153686596, - "gethinttextindex": 2153687912, - "isgoodtextbox": 2153688132, - "getmovehint": 2153688244, - "purchase_hint_text_items": 2153688428, - "resetdisplayedmusic": 2153688668, - "speedupmusic": 2153688680, - "initsongdisplay": 2153689100, - "detectsongchange": 2153689420, - "displaysongnamehandler": 2153689760, - "playmusicdontstop": 2153690292, - "postsynupdate": 2153690364, - "fixbrokenvoices": 2153690504, - "issharedmove": 2153690844, - "getcounteritem": 2153691084, - "getmovecountinshop": 2153691132, - "wipecounterimagecache": 2153691388, - "loadinternaltexture": 2153691448, - "loadfonttexture_counter": 2153691612, - "updatecounterdisplay": 2153691776, - "getactormodeltwodist": 2153691932, - "getclosestshop": 2153692060, - "getshopscale": 2153692396, - "newcountercode": 2153692528, - "handlefootprogress": 2153694024, - "setkongigt": 2153694476, - "updatepercentagekongstat": 2153694528, - "updatetagstat": 2153694740, - "updatefairystat": 2153694840, - "updateenemykillstat": 2153694876, - "createendseqcreditsfile": 2153695080, - "displaynumberonobject": 2153695876, - "getholdableheightlocal": 2153696096, - "getholdableheight": 2153696180, - "enterts": 2153696220, - "boulderdestroy": 2153696424, - "bouldertscode": 2153696464, - "tshandler": 2153696840, - "tsjump": 2153697024, - "tsspeed": 2153697104, - "tsdrop": 2153697228, - "writewti": 2153697420, - "handle_wti": 2153697460, - "warptoisles": 2153697632, - "beatgame": 2153697736, - "finalizebeatgame": 2153697812, - "hasbeatendkrapwincon": 2153697912, - "canaccesswincondition": 2153698148, - "checkseedvictory": 2153698568, - "winrabbitseed": 2153698644, - "safeguardrabbitreward": 2153698676, - "checkvictory_flaghook": 2153698736, - "issnapenemyinrange": 2153698764, - "getpkmnsnapdata": 2153699248, - "pokemonsnapmode": 2153699368, - "arcadeexit": 2153699760, - "determinearcadelevel": 2153699972, - "handlearcadevictory": 2153700164, - "spawnoverlaytext": 2153700340, - "overlay_mod_bonus": 2153700700, - "haschunkyphaseslam": 2153700956, - "overlay_mod_boss": 2153700984, - "overlay_changes": 2153701456, - "parsecutscenedata": 2153701680, - "loadjetpacsprites_handler": 2153702124, - "exitjetpac": 2153702192, - "completejetpac": 2153702228, - "patchcrankycode": 2153702284, - "overlay_mod_menu": 2153702432, - "overlay_mod_race": 2153702676, - "updatepausescreenwheel": 2153702732, - "newpausespritecode": 2153702812, - "totalssprite": 2153703512, - "checkssprite": 2153703520, - "initcarousel_onpause": 2153703528, - "pause_items": 2153703748, - "initprogressivetimer": 2153703960, - "renderprogressivesprite": 2153703976, - "playprogressiveding": 2153704052, - "inithints": 2153704080, - "wipehintcache": 2153704420, - "drawhinttext": 2153704496, - "drawsplitstring": 2153705024, - "gethintrequirement": 2153705340, - "handleprogressiveindicator": 2153705380, - "resetprogressive": 2153705564, - "displaycbcount": 2153705584, - "gethintitemregion": 2153705780, - "showhint": 2153705804, - "displaybubble": 2153705924, - "inithintflags": 2153706084, - "initdimtimer": 2153706256, - "renderdimsprite": 2153706272, - "checkdimcache": 2153706348, - "getitemname": 2153706612, - "drawhintscreen": 2153706752, - "drawitemlocationscreen": 2153707572, - "initlockout": 2153708352, - "handlehintscreenlockout": 2153708508, - "item_name_plural": 2153708972, - "item_names": 2153708988, - "itemloc_flags": 2153709072, - "unknown_hints": 2153710156, - "hint_region_names": 2153710216, - "getlevelpoints": 2153711760, - "printleveligt": 2153711936, - "getlevelofdropsanity": 2153712436, - "inititemcheckdenominators": 2153712480, - "checkitemdb": 2153712828, - "handlecshifting": 2153713888, - "pausescreen3and4header": 2153713996, - "drawtextpointers": 2153714568, - "pausescreen3and4itemname": 2153714720, - "pausescreen3and4counter": 2153714868, - "changepausescreen": 2153715120, - "changeselectedlevel": 2153715264, - "updatefilevariables": 2153715348, - "handleoutofcounters": 2153715400, - "exitmap": 2153715580, - "sethintregion": 2153717008, - "storehintregion": 2153717884, - "gethintregiontext": 2153717992, - "displayhintregion": 2153718124, - "getworldoffset": 2153718864, - "setblockerhead": 2153718912, - "displayblockeritemonhud": 2153719036, - "getcountofblockerrequireditem": 2153719108, - "displaycountonblockerteeth": 2153719152, - "cc_enable_drunky": 2153719244, - "cc_disable_drunky": 2153719308, - "cc_allower_rockfall": 2153719352, - "cc_allower_balloon": 2153719368, - "cc_allower_backflip": 2153719420, - "cc_enabler_ice": 2153719468, - "cc_disabler_ice": 2153719492, - "cc_allower_mini": 2153719536, - "cc_allower_boulder": 2153719580, - "cc_allower_crate": 2153719632, - "cc_disabler_paper": 2153719680, - "cc_allower_time": 2153719772, - "cc_allower_helmtimer": 2153719820, - "cc_allower_water": 2153719836, - "cc_enabler_icetrap": 2153719892, - "cc_allower_icetrap": 2153719928, - "cc_enabler_warptorap": 2153719980, - "cc_disabler_warptorap": 2153720064, - "displaygetoutreticle": 2153720192, - "cc_enable_getout": 2153720516, - "cc_enabler_doabackflip": 2153720704, - "cc_enabler_rockfall": 2153720788, - "cc_enabler_boulder": 2153721044, - "cc_enabler_crate": 2153721120, - "cc_enabler_balloon": 2153721196, - "cc_allower_spawnkop": 2153721332, - "cc_enabler_spawnkop": 2153721420, - "cc_enabler_slip": 2153721588, - "cc_allower_animals": 2153721660, - "cc_allower_tag": 2153721720, - "cc_enabler_tag": 2153721828, - "cc_enabler_animals": 2153721972, - "cc_disabler_animals": 2153722220, - "cc_enabler_paper": 2153722292, - "cc_enabler_time": 2153722468, - "cc_enabler_helmtimer": 2153722704, - "cc_enabler_water": 2153722748, - "cc_allower_generic": 2153723008, - "handlegamemodewrapper": 2153723164, - "skipdktv": 2153723236, - "fakegetout": 2153723320, - "dummyguardcode": 2153723820, - "cc_setscale": 2153724064, - "cc_enabler_mini": 2153724112, - "cc_disabler_mini": 2153724184, - "cc_effect_handler": 2153724196, - "blastwarpgetter": 2153725328, - "blastwarphandler": 2153725416, - "blast_entrances": 2153725544, - "replacement_lobby_exits_array": 2153725560, - "replacement_lobbies_array": 2153725580, - "swap_ending_cutscene_model": 2153725600, - "completeboss": 2153725832, - "fixkroolkong": 2153726544, - "handlekroolsaveprogress": 2153726644, - "writehudamount": 2153727136, - "coinhudelements": 2153727304, - "displaymovetext": 2153727348, - "movetransplant": 2153727660, - "isshopempty": 2153727728, - "getinstrumentlevel": 2153727836, - "getprice": 2153727892, - "getnextmovepurchase": 2153728060, - "purchasemove": 2153728400, - "checkfirstmovepurchase": 2153729016, - "purchasefirstmovehandler": 2153729172, - "setlocation": 2153729240, - "setlocationstatus": 2153729268, - "getlocationstatus": 2153729504, - "getnextmovetext": 2153729532, - "displaybfimovetext": 2153731636, - "showpostmovetext": 2153731764, - "instrumentupgnames": 2153733056, - "instrumentnames": 2153733064, - "ammobeltnames": 2153733072, - "gunupgnames": 2153733076, - "gunnames": 2153733080, - "specialmovesnames": 2153733088, - "simianslamnames": 2153733128, - "destroybonus": 2153733136, - "completebonus": 2153733168, - "helminit": 2153733272, - "helmbarrelcode": 2153734084, - "crowndoorcheck": 2153734452, - "unlockmoves": 2153734568, - "starting_item_data": 2153735080, - "auto_turn_keys": 2153735148, - "qualityoflife_shorteners": 2153735280, - "fastwarp": 2153735372, - "fastwarp_playmusic": 2153735404, - "fastwarpshockwavefix": 2153735432, - "clearvulturecutscene": 2153735536, + "drawpixeltext": 2153523724, + "drawpixeltextcontainer": 2153523924, + "displaycenteredtext": 2153524108, + "drawscreenrect": 2153524236, + "drawstring": 2153524472, + "drawtext": 2153524640, + "drawtextcontainer": 2153525012, + "setcharacterrecoloring": 2153525232, + "setcharactercolor": 2153525252, + "applyhintrecoloring": 2153525288, + "new_flag_mapping": 2153525644, + "actor_extra_data_sizes": 2153526620, + "actor_functions": 2153529576, + "actor_collisions": 2153532532, + "actor_health_damage": 2153538444, + "actor_interactions": 2153541400, + "actor_master_types": 2153542880, + "actor_defs": 2153543620, + "drops": 2153551588, + "object_collisions": 2153551828, + "item_scales": 2153553700, + "actor_drops": 2153554100, + "bounce_objects": 2153554200, + "acceptable_items": 2153554280, + "getoverlayfrommap": 2153554372, + "getbitarrayvalue": 2153554392, + "inminigame": 2153554424, + "playsfx": 2153554440, + "setpermflag": 2153554492, + "convertidtoindex": 2153554504, + "convertsubidtoindex": 2153554580, + "isflaginrange": 2153554656, + "findactorwithtype": 2153554688, + "createcollisionobjinstance": 2153554760, + "resetmapcontainer": 2153554852, + "getcenter": 2153554908, + "cancelcutscene": 2153554984, + "modifycutscenepoint": 2153555120, + "modifycutsceneitem": 2153555192, + "modifycutscenepanpoint": 2153555272, + "modifycutscenepointtime": 2153555412, + "getwrinklylevelindex": 2153555488, + "getmingb": 2153555504, + "givegb": 2153555596, + "gettotalcbcount": 2153555700, + "inshortlist": 2153555800, + "inu8list": 2153555852, + "inshop": 2153555908, + "correctdkportal": 2153555924, + "spawnitemoverlay": 2153556200, + "giveslamlevel": 2153556452, + "inbattlecrown": 2153556520, + "intraining": 2153556560, + "inbossmap": 2153556592, + "isgamemode": 2153556692, + "filtersong": 2153556752, + "death": 2153556984, + "readfilesimple": 2153557032, + "savefilesimple": 2153557052, + "applydamagemask": 2153557096, + "replacewatertexture": 2153557424, + "replacewatertexture_spooky": 2153557468, + "isbounceobject": 2153557512, + "getfile": 2153557528, + "getpercentageofitem": 2153557616, + "getgamepercentage": 2153557672, + "gettotalmovecount": 2153557804, + "getitemcountreq": 2153558156, + "isitemrequirementsatisfied": 2153558644, + "getshopdata": 2153558708, + "exitboss": 2153558872, + "iskrushaadjacentmodel": 2153558960, + "isglobalcutsceneplaying": 2153558992, + "determinelevel_newlevel": 2153559052, + "refillifrefillable": 2153559288, + "refillhealthoninit": 2153559416, + "refillplayerhealthkko": 2153559476, + "isdynflag": 2153559508, + "getprojectilecount_modified": 2153559624, + "applybuttonbansinternals": 2153560048, + "guard_enabled_buttons": 2153560228, + "trap_enabled_buttons": 2153560230, + "cc_enabled_buttons": 2153560232, + "enabled_buttons": 2153560234, + "button_swaps": 2153560236, + "big_head_actors": 2153560284, + "ap_overlay_sprite": 2153560724, + "night_overlay_sprite": 2153560748, + "day_overlay_sprite": 2153560772, + "halfmedal_sprite": 2153560796, + "boulder_sprite": 2153560832, + "potion_sprite": 2153560868, + "company_coin_sprite": 2153560908, + "fool_overlay_sprite": 2153560960, + "feather_gun_sprite": 2153560984, + "krool_sprite": 2153561020, + "pearl_sprite": 2153561044, + "bean_sprite": 2153561068, + "regular_boss_maps": 2153561372, + "crown_maps": 2153561380, + "colorblind_colors": 2153561392, + "kong_pellets": 2153561440, + "tnsportal_flags": 2153561448, + "bfi_move_flags": 2153561464, + "tbarrel_flags": 2153561468, + "normal_key_flags": 2153561476, + "kong_flags": 2153561492, + "levels": 2153561644, + "resetpicturestatus": 2153561700, + "cfuncloop": 2153561768, + "earlyframe": 2153563348, + "insertrommessages": 2153565172, + "drawinfotext": 2153565452, + "displaymenuwarnings": 2153565616, + "displaylistmodifiers": 2153565840, + "togglestandardammo": 2153567504, + "grab_lock_timer": 2153567975, + "dropsanity_levels": 2153568388, + "spoiler_items": 2153568604, + "display_billboard_fix": 2153568884, + "hints_initialized": 2153568885, + "gamestats": 2153568888, + "enable_skip_check": 2153568900, + "ice_trap_queued": 2153568904, + "enemy_rewards_spawned": 2153568908, + "bonus_shown": 2153568924, + "base_text_color": 2153568928, + "crate_flags": 2153568932, + "patch_flags": 2153568996, + "balloon_path_pointers": 2153569060, + "headsize": 2153569316, + "force_enable_diving_timer": 2153569556, + "guard_tag_timer": 2153569560, + "tag_locked": 2153569562, + "itemloc_pointers": 2153569564, + "hint_pointers": 2153570048, + "cs_skip_db": 2153570468, + "text_overlay_data": 2153570476, + "static_mtx": 2153570540, + "setobjectopacity": 2153571948, + "hideobject": 2153571972, + "standingonm2object": 2153572012, + "canopenspecificblocker": 2153572060, + "canopenxblockers": 2153572156, + "activategonepad": 2153572356, + "tnsindicatorgenericcode": 2153572416, + "bananaportgenericcode": 2153572712, + "spawnwrinklywrapper": 2153574704, + "loadwrinklytextwrapper": 2153575100, + "portalwarpfix": 2153575144, + "change_object_scripts": 2153575200, + "overlay_mod_race": 2153580120, + "loadjetpacsprites_handler": 2153580176, + "exitjetpac": 2153580244, + "completejetpac": 2153580280, + "spawnoverlaytext": 2153580336, + "overlay_mod_bonus": 2153580696, + "overlay_changes": 2153580952, + "parsecutscenedata": 2153581176, + "arcadeexit": 2153581620, + "determinearcadelevel": 2153581832, + "handlearcadevictory": 2153582024, + "haschunkyphaseslam": 2153582200, + "overlay_mod_boss": 2153582228, + "patchcrankycode": 2153582700, + "overlay_mod_menu": 2153582848, + "fixhelmtimercorrection": 2153583092, + "helmtime_restart": 2153583220, + "helmtime_exitbonus": 2153583256, + "helmtime_exitrace": 2153583328, + "helmtime_exitlevel": 2153583356, + "helmtime_exitboss": 2153583384, + "helmtime_exitkrool": 2153583416, + "inithelmsetup": 2153583448, + "gethelmexit": 2153583524, + "warptohelm": 2153583576, + "helm_entry_points": 2153583612, + "handlecannongamereticle": 2153583616, + "resetcannongamestate": 2153583772, + "handlespidersilkspawn": 2153583820, + "spiderbossextracode": 2153583888, + "intransform": 2153584084, + "cantaganywhere": 2153584172, + "gettastate": 2153584608, + "hasaccesstokong": 2153584620, + "gettaganywherekong": 2153584636, + "changekong": 2153584808, + "taganywhere": 2153585420, + "taganywhereinit": 2153586092, + "populatesfxcache": 2153586176, + "handlesfxcache": 2153586396, + "taganywhereammo": 2153586724, + "handlegrabbinglock": 2153586900, + "canplayerclimb": 2153586936, + "handleledgelock": 2153587052, + "handlepushlock": 2153587152, + "handleactionset": 2153587188, + "qualityoflife_fixes": 2153587544, + "dropwrapper": 2153587856, + "candive_withcheck": 2153587900, + "playtransformationsong": 2153588004, + "hasenoughcbs": 2153588076, + "shouldding": 2153588184, + "renderindicatorsprite": 2153588380, + "renderdingsprite": 2153588884, + "initdingsprite": 2153588984, + "gethomingcountwithabilitycheck": 2153589000, + "playcbding": 2153589036, + "cbding": 2153589064, + "fixrbslowturn": 2153589212, + "postkroolsavecheck": 2153589260, + "tagbarrelbackgroundkong": 2153589324, + "updatemultibunchcount": 2153589372, + "rabbitraceinfinitecode": 2153589424, + "fixdillotntpads": 2153589568, + "canplayjetpac": 2153589612, + "fixcrownentryskong": 2153589672, + "reducetrapbubblelife": 2153589692, + "exittrapbubblecontroller": 2153589732, + "fixchimpycambug": 2153589988, + "movepelletwrapper": 2153590112, + "fixhelmtimerdisable": 2153590464, + "ispreventcutsceneplaying": 2153590568, + "callparentmapfilter": 2153590740, + "freedk": 2153591036, + "cutscenedkcode": 2153591080, + "charspawneritemcode": 2153591624, + "isbadmovementstate": 2153591960, + "guardcatchinternal": 2153592000, + "guardcatch": 2153592436, + "warphandle": 2153592596, + "catchwarphandle": 2153592720, + "inrabbitrace": 2153592996, + "renderkoplighthandler": 2153593044, + "newguardcode": 2153593160, + "setkopidleguarantee": 2153593956, + "givekopidleguarantee": 2153593980, + "load_object_script": 2153594080, + "adjust_level_modifiers": 2153594240, + "handletimeofday": 2153594476, + "istimeofday": 2153595164, + "getoscillationdelta": 2153595320, + "fixmusicrando": 2153595360, + "writeendsequence": 2153595412, + "loadhooks": 2153595480, + "inithack": 2153595596, + "quickinit": 2153596328, + "music_types": 2153620416, + "disableantialiasing": 2153620600, + "initqol_cutscenes": 2153620780, + "fixracehoopcode": 2153620856, + "renderhoop": 2153620872, + "fixupdraftbug": 2153620936, + "quickwrinklytextboxes": 2153621008, + "bootspeedup": 2153621060, + "initqol_fastwarp": 2153621092, + "writespawn": 2153621168, + "initspawn": 2153621192, + "qol_displayinstrument": 2153621276, + "headphonescodecontainer": 2153621292, + "newinstrumentrefill": 2153621340, + "getinstrumentrefillcount": 2153621384, + "correctrefillcap": 2153621460, + "initqol": 2153621488, + "crashhandler": 2153621584, + "getfaultedthread": 2153622696, + "version_string": 2153622780, + "getcrownindex": 2153624312, + "getkeyindex": 2153624352, + "getpatchflag": 2153624392, + "getpatchworld": 2153624468, + "getcrateflag": 2153624492, + "getcrateworld": 2153624568, + "getbonusflag": 2153624592, + "updateboulderid": 2153624628, + "getboulderindex": 2153624652, + "getbarrelskinindex": 2153624768, + "getshopskinindex": 2153624848, + "alterbonusvisuals": 2153624984, + "getdirtpatchskin": 2153625204, + "inititemrando": 2153625452, + "m2_cb_coin_counts": 2153625520, + "actor_cb_counts": 2153625536, + "snide_rewards": 2153625980, + "company_coin_table": 2153626300, + "kong_check_data": 2153626308, + "bonus_data": 2153626340, + "balloon_item_table": 2153627132, + "box_item_table": 2153627548, + "boulder_item_table": 2153627644, + "extra_actor_spawns": 2153627772, + "crate_item_table": 2153627780, + "rcoin_item_table": 2153627844, + "fairy_item_table": 2153627908, + "key_item_table": 2153628068, + "crown_item_table": 2153628132, + "wrinkly_item_table": 2153628172, + "medal_item_table": 2153628312, + "bp_item_table": 2153628652, + "updatekongtb": 2153629084, + "updateactorhandstates": 2153629204, + "cleargunhandler": 2153629408, + "updateactorhandstates_gun": 2153629548, + "pulloutgunhandler": 2153629888, + "determineshockwavecolor": 2153630304, + "initcosmetic": 2153630500, + "setfog": 2153630672, + "getrainbowammocolor": 2153630800, + "updatespritecolor": 2153631004, + "colorrainbowammohud": 2153631056, + "colorrainbowammohud_0": 2153631120, + "colorrainbowammo": 2153631184, + "sethudupdatefunction": 2153631244, + "sethudupdatefunction_0": 2153631276, + "pickrandomfrompool": 2153631348, + "playbonussong": 2153631436, + "playbosssong": 2153631524, + "playsongwcheck": 2153631676, + "stopbosssong": 2153632032, + "resetbosssong": 2153632228, + "playnotbosssong": 2153632248, + "displaymodifiedtext": 2153632500, + "inittextchanges": 2153632608, + "dark_mode_colors": 2153632772, + "emph_text_colors": 2153632812, + "setenemydbpopulation": 2153632852, + "populateenemymapdata": 2153632864, + "getenemyitem": 2153633064, + "getenemyflag": 2153633100, + "wipeenemyspawnbitfield": 2153633148, + "setspawnbitfield": 2153633184, + "setspawnbitfieldfromflag": 2153633260, + "canspawnenemyreward": 2153633332, + "getrngwithinrange": 2153633452, + "rendersparkles": 2153633536, + "indicatecollectionstatus": 2153633800, + "fireballenemydeath": 2153633996, + "rulerenemydeath": 2153634072, + "tomatodeath": 2153634156, + "spawnactorwithflaghandler": 2153635296, + "spawnweirdreward": 2153635444, + "spawnweirdreward0": 2153635548, + "spawnbonusreward": 2153635640, + "spawnrewardatactor": 2153635796, + "spawnminecartreward": 2153636028, + "spawncrownreward": 2153636184, + "spawnbossreward": 2153636352, + "spawndirtpatchreward": 2153636680, + "spawncharspawneractor": 2153636904, + "meloncrateitemhandler": 2153637148, + "updatekegids": 2153637648, + "isvalidboulderobject": 2153637864, + "isvalidbreakableobject": 2153637996, + "spawnboulderobject": 2153638132, + "spawnbreakableobject": 2153638320, + "renderbouldersparkles": 2153638512, + "shoulddeleteballoon": 2153638712, + "isvalidballoonobject": 2153638788, + "balloonvishandler": 2153638932, + "balloonvishandler2": 2153639076, + "balloonitemhandler": 2153639252, + "initactordefs": 2153639656, + "setactordamage": 2153639704, + "swapkremlingmodel": 2153639728, + "getbpcountstats": 2153639836, + "getturnedcount": 2153639932, + "turnedallin": 2153640032, + "hasturnedinatleast": 2153640068, + "getfirstemptysnidereward": 2153640140, + "fixed_bug_collision": 2153640264, + "fixed_klap_collision": 2153640288, + "fixed_dice_collision": 2153640384, + "fixed_scarab_collision": 2153640528, + "fixed_shockwave_collision": 2153640576, + "resetscreenflip": 2153640612, + "resettaganywhere": 2153640632, + "customdamagecode": 2153640656, + "trapplayer_new": 2153640796, + "renderspritesonplayer": 2153641072, + "wipereplenishibles": 2153641300, + "swapbuttons": 2153641440, + "initicetrap": 2153641692, + "slippeelcode": 2153642472, + "reseticetrapbuttons": 2153642636, + "queueicetrap": 2153642700, + "isbannedtrapmap": 2153642728, + "canloadicetrap": 2153642800, + "handleicetrapbuttons": 2153643064, + "istrapmodel": 2153643464, + "cancelicetrapsong": 2153643492, + "playicetrapsong": 2153643540, + "callicetrap": 2153643596, + "spritecode": 2153644200, + "nothingcode": 2153644396, + "kongdropcode": 2153644408, + "potioncode": 2153644536, + "fairyduplicatecode": 2153644600, + "shopowneritemcode": 2153644668, + "missingshopownercode": 2153644760, + "crankycodehandler": 2153644964, + "funkycodehandler": 2153645012, + "candycodehandler": 2153645060, + "snidecodehandler": 2153645108, + "fakekeycode": 2153645156, + "fakegbcode": 2153645204, + "fakefairycode": 2153645252, + "mermaidcheck": 2153645300, + "fairyqueencutsceneinit": 2153645408, + "fairyqueencutscenecheck": 2153645480, + "fairyqueencheckspeedup": 2153645704, + "cannoncodewrapper": 2153645744, + "setuphook": 2153645900, + "checkkasplatspawnbitfield": 2153646888, + "isflaggedwatermelon": 2153647348, + "canitempersist": 2153647444, + "isapenabled": 2153647952, + "initap": 2153647972, + "initapcounter": 2153648048, + "saveapcounter": 2153648108, + "handlesentitem": 2153648160, + "sendtraplink": 2153648284, + "canreceiveitem": 2153648328, + "canreceiveshopkeeperitem": 2153648444, + "candie": 2153648496, + "handlearchipelagofeed": 2153648640, + "senddeath": 2153649212, + "displayapconnection": 2153649280, + "giveitem": 2153649624, + "giveitemfrompacket": 2153651612, + "getitemcount_new": 2153651648, + "getkongownershipfromflag": 2153652708, + "givekongfromflag": 2153652768, + "hasflagmove": 2153652888, + "setflagmove": 2153652980, + "getshopflag": 2153653080, + "grabfileparameters_fileinfo": 2153653224, + "inititemrandopointer": 2153653328, + "readitemsfromfile": 2153653348, + "saveitemstofile": 2153653668, + "file_info_expansion": 2153653984, + "refreshpads": 2153654436, + "ismodeltwotiedflag_new": 2153654760, + "handledynamicitemtext": 2153655388, + "gettextdata": 2153655472, + "getcharwidthmask": 2153655548, + "caves_beetle": 2153655596, + "aztec_beetle": 2153655628, + "getcollisionsquare_new": 2153655664, + "writeitemscale": 2153655784, + "writeitemactorscale": 2153655976, + "getitemrequiredkong": 2153656016, + "iscollidingwithsphere": 2153656056, + "iscollidingwithcylinder": 2153656212, + "isvalidkongcollision": 2153656696, + "checkmodeltwoitemcollision": 2153656764, + "checkforvalidcollision": 2153657388, + "displaymedaloverlay": 2153657988, + "banana_medal_check": 2153658576, + "banana_medal_acquisition": 2153658644, + "getflagindex_corrected": 2153658860, + "giveitemfromsend": 2153658916, + "giveitemfromkongdata": 2153659012, + "givefairyitem": 2153659060, + "iscavesbeetlereward": 2153659124, + "candanceskip": 2153659192, + "forcedance": 2153659484, + "getitem": 2153659580, + "getobjectcollectability": 2153661040, + "iscollectable": 2153661480, + "handlemodeltwoopacity": 2153661764, + "getflagmappingdata": 2153661848, + "givecb": 2153661944, + "updateitemtotalshandler": 2153662188, + "helminit": 2153666140, + "helmbarrelcode": 2153666952, + "crowndoorcheck": 2153667320, + "qualityoflife_shorteners": 2153667436, + "fastwarp": 2153667528, + "fastwarp_playmusic": 2153667560, + "fastwarpshockwavefix": 2153667588, + "unlockmoves": 2153667692, + "starting_item_data": 2153668204, + "destroybonus": 2153668272, + "completebonus": 2153668304, + "auto_turn_keys": 2153668408, + "clearvulturecutscene": 2153668540, + "updatepausescreenwheel": 2153668656, + "newpausespritecode": 2153668736, + "totalssprite": 2153669436, + "checkssprite": 2153669444, + "initcarousel_onpause": 2153669452, + "pause_items": 2153669672, + "initprogressivetimer": 2153669884, + "renderprogressivesprite": 2153669900, + "playprogressiveding": 2153669976, + "inithints": 2153670004, + "wipehintcache": 2153670344, + "drawhinttext": 2153670420, + "drawsplitstring": 2153670948, + "gethintrequirement": 2153671264, + "handleprogressiveindicator": 2153671304, + "resetprogressive": 2153671488, + "displaycbcount": 2153671508, + "gethintitemregion": 2153671704, + "showhint": 2153671728, + "displaybubble": 2153671848, + "inithintflags": 2153672008, + "initdimtimer": 2153672180, + "renderdimsprite": 2153672196, + "checkdimcache": 2153672272, + "getitemname": 2153672536, + "drawhintscreen": 2153672676, + "drawitemlocationscreen": 2153673496, + "initlockout": 2153674276, + "handlehintscreenlockout": 2153674432, + "item_name_plural": 2153674896, + "item_names": 2153674912, + "itemloc_flags": 2153674996, + "unknown_hints": 2153676080, + "hint_region_names": 2153676140, + "sethintregion": 2153677684, + "storehintregion": 2153678560, + "gethintregiontext": 2153678668, + "displayhintregion": 2153678800, + "getlevelpoints": 2153679540, + "printleveligt": 2153679716, + "getlevelofdropsanity": 2153680216, + "inititemcheckdenominators": 2153680260, + "checkitemdb": 2153680608, + "handlecshifting": 2153681668, + "pausescreen3and4header": 2153681776, + "drawtextpointers": 2153682348, + "pausescreen3and4itemname": 2153682500, + "pausescreen3and4counter": 2153682648, + "changepausescreen": 2153682900, + "changeselectedlevel": 2153683044, + "updatefilevariables": 2153683128, + "handleoutofcounters": 2153683180, + "exitmap": 2153683360, + "blastwarpgetter": 2153684788, + "blastwarphandler": 2153684876, + "blast_entrances": 2153685004, + "replacement_lobby_exits_array": 2153685020, + "replacement_lobbies_array": 2153685040, + "swap_ending_cutscene_model": 2153685060, + "completeboss": 2153685292, + "fixkroolkong": 2153686004, + "handlekroolsaveprogress": 2153686104, + "getworldoffset": 2153686596, + "setblockerhead": 2153686644, + "displayblockeritemonhud": 2153686768, + "getcountofblockerrequireditem": 2153686840, + "displaycountonblockerteeth": 2153686884, + "cc_enable_drunky": 2153686976, + "cc_disable_drunky": 2153687040, + "cc_allower_rockfall": 2153687084, + "cc_allower_balloon": 2153687100, + "cc_allower_backflip": 2153687152, + "cc_enabler_ice": 2153687200, + "cc_disabler_ice": 2153687224, + "cc_allower_mini": 2153687268, + "cc_allower_boulder": 2153687312, + "cc_allower_crate": 2153687364, + "cc_disabler_paper": 2153687412, + "cc_allower_time": 2153687504, + "cc_allower_helmtimer": 2153687552, + "cc_allower_water": 2153687568, + "cc_enabler_icetrap": 2153687624, + "cc_allower_icetrap": 2153687660, + "cc_enabler_warptorap": 2153687712, + "cc_disabler_warptorap": 2153687796, + "displaygetoutreticle": 2153687924, + "cc_enable_getout": 2153688248, + "cc_enabler_doabackflip": 2153688436, + "cc_enabler_rockfall": 2153688520, + "cc_enabler_boulder": 2153688776, + "cc_enabler_crate": 2153688852, + "cc_enabler_balloon": 2153688928, + "cc_allower_spawnkop": 2153689064, + "cc_enabler_spawnkop": 2153689152, + "cc_enabler_slip": 2153689320, + "cc_allower_animals": 2153689392, + "cc_allower_tag": 2153689452, + "cc_enabler_tag": 2153689560, + "cc_enabler_animals": 2153689704, + "cc_disabler_animals": 2153689952, + "cc_enabler_paper": 2153690024, + "cc_enabler_time": 2153690200, + "cc_enabler_helmtimer": 2153690436, + "cc_enabler_water": 2153690480, + "cc_allower_generic": 2153690740, + "handlegamemodewrapper": 2153690896, + "skipdktv": 2153690968, + "fakegetout": 2153691052, + "dummyguardcode": 2153691552, + "cc_setscale": 2153691796, + "cc_enabler_mini": 2153691844, + "cc_disabler_mini": 2153691916, + "cc_effect_handler": 2153691928, + "instrumentupgnames": 2153693056, + "instrumentnames": 2153693064, + "ammobeltnames": 2153693072, + "gunupgnames": 2153693076, + "gunnames": 2153693080, + "specialmovesnames": 2153693088, + "simianslamnames": 2153693128, + "writehudamount": 2153693136, + "coinhudelements": 2153693304, + "displaymovetext": 2153693348, + "movetransplant": 2153693660, + "isshopempty": 2153693728, + "getinstrumentlevel": 2153693836, + "getprice": 2153693892, + "getnextmovepurchase": 2153694060, + "purchasemove": 2153694400, + "checkfirstmovepurchase": 2153695016, + "purchasefirstmovehandler": 2153695172, + "setlocation": 2153695240, + "setlocationstatus": 2153695268, + "getlocationstatus": 2153695504, + "getnextmovetext": 2153695532, + "displaybfimovetext": 2153697636, + "showpostmovetext": 2153697764, + "everdrive_init": 2153702212, + "everdrive_service": 2153702332, + "gethinttextindex": 2153703500, + "isgoodtextbox": 2153703720, + "getmovehint": 2153703832, + "purchase_hint_text_items": 2153704016, + "handlefootprogress": 2153704256, + "setkongigt": 2153704708, + "updatepercentagekongstat": 2153704760, + "updatetagstat": 2153704972, + "updatefairystat": 2153705072, + "updateenemykillstat": 2153705108, + "createendseqcreditsfile": 2153705312, + "resetdisplayedmusic": 2153706108, + "speedupmusic": 2153706120, + "initsongdisplay": 2153706540, + "detectsongchange": 2153706860, + "displaysongnamehandler": 2153707200, + "playmusicdontstop": 2153707732, + "postsynupdate": 2153707804, + "fixbrokenvoices": 2153707944, + "wipetrackercache": 2153708284, + "getenabledstate": 2153708384, + "updateenabledstates": 2153708676, + "inittracker": 2153709028, + "resettracker": 2153709040, + "modifytrackerimage": 2153709052, + "display_file_images": 2153709836, + "display_text": 2153710112, + "displayhash": 2153710608, + "correctkongfaces": 2153710960, + "wipefilemod": 2153711140, + "enterfileprogress": 2153711196, + "givecollectables": 2153711240, + "setalldefaultflags": 2153711432, + "startfile": 2153711532, + "testpasswordsequence": 2153712084, + "wipepassword": 2153712140, + "handlepassword": 2153712184, + "password_screen_code": 2153712372, + "password_screen_init": 2153712616, + "password_screen_gfx": 2153712760, + "file_progress_screen_code": 2153713184, + "displayinverted": 2153713412, + "setprevsavemap": 2153713484, + "quitgame": 2153713504, + "updateleveligt": 2153713532, + "changefileselectaction": 2153713784, + "changefileselectaction_0": 2153714064, + "pregiven_status": 2153714256, + "k_rool_text": 2153714940, + "adjustgunbone": 2153715240, + "getcutscenemodeltableindex": 2153715388, + "fixcutscenemodels": 2153715440, + "adjustanimationtables": 2153715664, + "krushaslide": 2153716296, + "adaptkrushazbanimation_punchostand": 2153716380, + "adaptkrushazbanimation_charge": 2153716660, + "diddyswimfix": 2153716888, + "minecartjumpfix": 2153717048, + "minecartjumpfix_0": 2153717068, + "setkrushaammocolor": 2153717140, + "orangeguncode": 2153717264, + "getholdableheightlocal": 2153718580, + "getholdableheight": 2153718664, + "enterts": 2153718704, + "boulderdestroy": 2153718908, + "bouldertscode": 2153718948, + "tshandler": 2153719324, + "tsjump": 2153719508, + "tsspeed": 2153719588, + "tsdrop": 2153719712, + "item_db": 2153719904, + "beaverextrahithandle": 2153721092, + "handlecrowntimerinternal": 2153721180, + "handlecrowntimer": 2153721440, + "klumpcrownhandler": 2153721516, + "handlebugenemy": 2153721580, + "kioskbugend": 2153721944, + "stomphandler": 2153722068, + "kioskbugcode": 2153722192, + "beatgame": 2153723116, + "finalizebeatgame": 2153723192, + "hasbeatendkrapwincon": 2153723292, + "canaccesswincondition": 2153723528, + "checkseedvictory": 2153723948, + "winrabbitseed": 2153724024, + "safeguardrabbitreward": 2153724056, + "checkvictory_flaghook": 2153724116, + "issnapenemyinrange": 2153724144, + "getpkmnsnapdata": 2153724628, + "pokemonsnapmode": 2153724748, + "renderget": 2153725140, + "wonarena": 2153725404, + "resolvebonuscontainer": 2153725428, + "warpoutofarenas": 2153725496, + "failtraining": 2153725620, + "warpoutoftraining": 2153725720, + "arenatagkongcode": 2153725888, + "arenaearlycompletioncheck": 2153726188, + "getminecartmayhemcoinreq": 2153726332, + "rendergetwrapper": 2153726400, + "wonminecartmayhem": 2153726504, + "initmmayhem": 2153726552, + "mayhem_minecart_size": 2153726708, + "cansavehelmhurry": 2153726752, + "addhelmtime": 2153726876, + "checktotalcache": 2153726956, + "finishhelmhurry": 2153727016, + "inithelmhurry": 2153727068, + "savehelmhurrytime": 2153727176, + "getmemorychallengetype": 2153727300, + "isdarkworld": 2153727384, + "alterchunklighting": 2153727540, + "alterchunkdata": 2153727820, + "shinelight": 2153727956, + "isskyworld": 2153728164, + "displaynogeochunk": 2153728244, + "setfalldamageimmunity": 2153728364, + "handlefalldamageimmunity": 2153728376, + "transformbarrelimmunity": 2153728444, + "factoryshedfallimmunity": 2153728472, + "falldamagewrapper": 2153728508, + "spawnstalactite": 2153728556, + "spawnkroollankyballoon": 2153728792, + "popexistingballoon": 2153728888, + "handlekrooldirecting": 2153728976, + "inchitcounter": 2153729264, + "parsecontrollerinput": 2153729320, + "hitlessdeath": 2153729456, + "hitlessdeathwrapper0": 2153729620, + "hitlessdeathwrapper1": 2153729676, + "livesdisplay": 2153729752, + "sethardpathspeed": 2153730032, + "blast_maps": 2153730076, + "updateskipcheck": 2153730176, + "iscutsceneskipped": 2153730232, + "cancelcutsceneinternals": 2153730276, + "clearqueuedcutscenefunctions": 2153730356, + "presstoskip": 2153730408, + "clearskipcache": 2153730536, + "pressskiphandler": 2153730568, + "updateskippablecutscenes": 2153730612, + "renderscreentransitioncheck": 2153730716, + "issharedmove": 2153730856, + "getcounteritem": 2153731096, + "getmovecountinshop": 2153731144, + "wipecounterimagecache": 2153731400, + "loadinternaltexture": 2153731460, + "loadfonttexture_counter": 2153731624, + "updatecounterdisplay": 2153731788, + "getactormodeltwodist": 2153731944, + "getclosestshop": 2153732072, + "getshopscale": 2153732408, + "newcountercode": 2153732540, + "displaynumberonobject": 2153734036, + "inithuddirection": 2153734256, + "allocatehud": 2153734388, + "sethudkong": 2153735372, + "setallhudkongs": 2153735404, + "gethudsprite_complex": 2153735456, + "updatebarriercounts": 2153735796, + "displaybarrierhud": 2153735944, + "canusedpad": 2153736004, + "drawdpad": 2153736256, + "handledpadfunctionality": 2153737208, + "spawnenemydrops": 2153738652, + "loadexits": 2153739648, + "writewti": 2153739792, + "handle_wti": 2153739832, + "warptoisles": 2153740004, "codeend": 2153754112, "copyfunc": 2153756496, "regularframeloop": 2153759408, @@ -3062,15 +3064,15 @@ "check_terminator": 19 }, "minigames": { - "2048.loop": 2147637796, - "arkanoid.loop": 2147637476, - "connect4.loop": 2147636128, "flappy_bird.loop": 2147634264, - "hexagon.loop": 2147639072, - "minesweeper.loop": 2147637148, "picross.loop": 2147636404, "snake.loop": 2147633800, + "arkanoid.loop": 2147637476, "wordle.loop": 2147635320, - "wordle.word_to_guess": 2147635640 + "wordle.word_to_guess": 2147635640, + "hexagon.loop": 2147639072, + "minesweeper.loop": 2147637148, + "2048.loop": 2147637796, + "connect4.loop": 2147636128 } } \ No newline at end of file