SM: Fix unobtainable items in remote items+item links combo (#1151)

* SM: fix using item links together with remote items

* SM: write 0 index for excess player ids

* some style and minor fixes (strotlog/Archipelago#1)

* more typing in SM patching

Co-authored-by: Doug Hoskisson <beauxq@users.noreply.github.com>
This commit is contained in:
strotlog 2022-10-31 22:42:11 -07:00 committed by GitHub
parent 802119502d
commit 655f287d42
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 663 additions and 503 deletions

View File

@ -4,7 +4,7 @@ import time
from NetUtils import ClientStatus, color from NetUtils import ClientStatus, color
from worlds.AutoSNIClient import SNIClient from worlds.AutoSNIClient import SNIClient
from .Rom import ROM_PLAYER_LIMIT as SM_ROM_PLAYER_LIMIT from .Rom import SM_ROM_MAX_PLAYERID
snes_logger = logging.getLogger("SNES") snes_logger = logging.getLogger("SNES")
@ -143,7 +143,7 @@ class SMSNIClient(SNIClient):
else: else:
location_id = 0x00 #backward compat location_id = 0x00 #backward compat
player_id = item.player if item.player <= SM_ROM_PLAYER_LIMIT else 0 player_id = item.player if item.player <= SM_ROM_MAX_PLAYERID else 0
snes_buffered_write(ctx, SM_RECV_QUEUE_START + item_out_ptr * 4, bytes( snes_buffered_write(ctx, SM_RECV_QUEUE_START + item_out_ptr * 4, bytes(
[player_id & 0xFF, (player_id >> 8) & 0xFF, item_id & 0xFF, location_id & 0xFF])) [player_id & 0xFF, (player_id >> 8) & 0xFF, item_id & 0xFF, location_id & 0xFF]))
item_out_ptr += 1 item_out_ptr += 1

View File

@ -7,8 +7,8 @@ from Utils import read_snes_rom
from worlds.Files import APDeltaPatch from worlds.Files import APDeltaPatch
SMJUHASH = '21f3e98df4780ee1c667b84e57d88675' SMJUHASH = '21f3e98df4780ee1c667b84e57d88675'
ROM_PLAYER_LIMIT = 65535 # max archipelago player ID. note, SM ROM itself will only store 201 names+ids max SM_ROM_MAX_PLAYERID = 65535
SM_ROM_PLAYERDATA_COUNT = 202
class SMDeltaPatch(APDeltaPatch): class SMDeltaPatch(APDeltaPatch):
hash = SMJUHASH hash = SMJUHASH

View File

@ -5,7 +5,7 @@ import copy
import os import os
import threading import threading
import base64 import base64
from typing import Set, TextIO from typing import Any, Dict, Iterable, List, Set, TextIO, TypedDict
from worlds.sm.variaRandomizer.graph.graph_utils import GraphUtils from worlds.sm.variaRandomizer.graph.graph_utils import GraphUtils
@ -15,7 +15,7 @@ from .Regions import create_regions
from .Rules import set_rules, add_entrance_rule from .Rules import set_rules, add_entrance_rule
from .Options import sm_options from .Options import sm_options
from .Client import SMSNIClient from .Client import SMSNIClient
from .Rom import get_base_rom_path, ROM_PLAYER_LIMIT, SMDeltaPatch, get_sm_symbols from .Rom import get_base_rom_path, SM_ROM_MAX_PLAYERID, SM_ROM_PLAYERDATA_COUNT, SMDeltaPatch, get_sm_symbols
import Utils import Utils
from BaseClasses import Region, Entrance, Location, MultiWorld, Item, ItemClassification, RegionType, CollectionState, Tutorial from BaseClasses import Region, Entrance, Location, MultiWorld, Item, ItemClassification, RegionType, CollectionState, Tutorial
@ -67,6 +67,13 @@ class SMWeb(WebWorld):
["Farrak Kilhn"] ["Farrak Kilhn"]
)] )]
class ByteEdit(TypedDict):
sym: Dict[str, Any]
offset: int
values: Iterable[int]
locations_start_id = 82000 locations_start_id = 82000
items_start_id = 83000 items_start_id = 83000
@ -201,7 +208,8 @@ class SMWorld(World):
create_locations(self, self.player) create_locations(self, self.player)
create_regions(self, self.multiworld, self.player) create_regions(self, self.multiworld, self.player)
def getWordArray(self, w): # little-endian convert a 16-bit number to an array of numbers <= 255 each def getWordArray(self, w: int) -> List[int]:
""" little-endian convert a 16-bit number to an array of numbers <= 255 each """
return [w & 0x00FF, (w & 0xFF00) >> 8] return [w & 0x00FF, (w & 0xFF00) >> 8]
# used for remote location Credits Spoiler of local items # used for remote location Credits Spoiler of local items
@ -281,48 +289,87 @@ class SMWorld(World):
"data", "SMBasepatch_prebuilt", "variapatches.ips")) "data", "SMBasepatch_prebuilt", "variapatches.ips"))
def APPostPatchRom(self, romPatcher): def APPostPatchRom(self, romPatcher):
symbols = get_sm_symbols(os.path.join(os.path.dirname(__file__), symbols = get_sm_symbols(os.path.join(os.path.dirname(__file__),
"data", "SMBasepatch_prebuilt", "sm-basepatch-symbols.json")) "data", "SMBasepatch_prebuilt", "sm-basepatch-symbols.json"))
multiWorldLocations = []
multiWorldItems = [] # gather all player ids and names relevant to this rom, then write player name and player id data tables
playerIdSet: Set[int] = {0} # 0 is for "Archipelago" server
for itemLoc in self.multiworld.get_locations():
assert itemLoc.item, f"World of player '{self.multiworld.player_name[itemLoc.player]}' has a loc.item " + \
f"that is {itemLoc.item} during generate_output"
# add each playerid who has a location containing an item to send to us *or* to an item_link we're part of
if itemLoc.item.player == self.player or \
(itemLoc.item.player in self.multiworld.groups and
self.player in self.multiworld.groups[itemLoc.item.player]['players']):
playerIdSet |= {itemLoc.player}
# add each playerid, including item link ids, that we'll be sending items to
if itemLoc.player == self.player:
playerIdSet |= {itemLoc.item.player}
if len(playerIdSet) > SM_ROM_PLAYERDATA_COUNT:
# max 202 entries, but it's possible for item links to add enough replacement items for us, that are placed
# in worlds that otherwise have no relation to us, that the 2*location count limit is exceeded
logger.warning("SM is interacting with too many players to fit in ROM. "
f"Removing the highest {len(playerIdSet) - SM_ROM_PLAYERDATA_COUNT} ids to fit")
playerIdSet = set(sorted(playerIdSet)[:SM_ROM_PLAYERDATA_COUNT])
otherPlayerIndex: Dict[int, int] = {} # ap player id -> rom-local player index
playerNameData: List[ByteEdit] = []
playerIdData: List[ByteEdit] = []
# sort all player data by player id so that the game can look up a player's data reasonably quickly when
# the client sends an ap playerid to the game
for i, playerid in enumerate(sorted(playerIdSet)):
playername = self.multiworld.player_name[playerid] if playerid != 0 else "Archipelago"
playerIdForRom = playerid
if playerid > SM_ROM_MAX_PLAYERID:
# note, playerIdForRom = 0 is not unique so the game cannot look it up.
# instead it will display the player received-from as "Archipelago"
playerIdForRom = 0
if playerid == self.player:
raise Exception(f"SM rom cannot fit enough bits to represent self player id {playerid}")
else:
logger.warning(f"SM rom cannot fit enough bits to represent player id {playerid}, setting to 0 in rom")
otherPlayerIndex[playerid] = i
playerNameData.append({"sym": symbols["rando_player_name_table"],
"offset": i * 16,
"values": playername[:16].upper().center(16).encode()})
playerIdData.append({"sym": symbols["rando_player_id_table"],
"offset": i * 2,
"values": self.getWordArray(playerIdForRom)})
multiWorldLocations: List[ByteEdit] = []
multiWorldItems: List[ByteEdit] = []
idx = 0 idx = 0
self.playerIDMap = {}
playerIDCount = 0 # 0 is for "Archipelago" server; highest possible = 200 (201 entries)
vanillaItemTypesCount = 21 vanillaItemTypesCount = 21
for itemLoc in self.multiworld.get_locations(): for itemLoc in self.multiworld.get_locations():
if itemLoc.player == self.player and locationsDict[itemLoc.name].Id != None: if itemLoc.player == self.player and locationsDict[itemLoc.name].Id != None:
# this SM world can find this item: write full item data to tables and assign player data for writing # item to place in this SM world: write full item data to tables
romPlayerID = itemLoc.item.player if itemLoc.item.player <= ROM_PLAYER_LIMIT else 0
if isinstance(itemLoc.item, SMItem) and itemLoc.item.type in ItemManager.Items: if isinstance(itemLoc.item, SMItem) and itemLoc.item.type in ItemManager.Items:
itemId = ItemManager.Items[itemLoc.item.type].Id itemId = ItemManager.Items[itemLoc.item.type].Id
else: else:
itemId = ItemManager.Items['ArchipelagoItem'].Id + idx itemId = ItemManager.Items["ArchipelagoItem"].Id + idx
multiWorldItems.append({"sym": symbols["message_item_names"], multiWorldItems.append({"sym": symbols["message_item_names"],
"offset": (vanillaItemTypesCount + idx)*64, "offset": (vanillaItemTypesCount + idx)*64,
"values": self.convertToROMItemName(itemLoc.item.name)}) "values": self.convertToROMItemName(itemLoc.item.name)})
idx += 1 idx += 1
if (romPlayerID > 0 and romPlayerID not in self.playerIDMap.keys()): if itemLoc.item.player == self.player:
playerIDCount += 1 itemDestinationType = 0 # dest type 0 means 'regular old SM item' per itemtable.asm
self.playerIDMap[romPlayerID] = playerIDCount elif itemLoc.item.player in self.multiworld.groups and \
self.player in self.multiworld.groups[itemLoc.item.player]['players']:
# dest type 2 means 'SM item link item that sends to the current player and others'
# per itemtable.asm (groups are synonymous with item_links, currently)
itemDestinationType = 2
else:
itemDestinationType = 1 # dest type 1 means 'item for entirely someone else' per itemtable.asm
[w0, w1] = self.getWordArray(0 if itemLoc.item.player == self.player else 1) [w0, w1] = self.getWordArray(itemDestinationType)
[w2, w3] = self.getWordArray(itemId) [w2, w3] = self.getWordArray(itemId)
[w4, w5] = self.getWordArray(romPlayerID) [w4, w5] = self.getWordArray(otherPlayerIndex[itemLoc.item.player] if itemLoc.item.player in
otherPlayerIndex else 0)
[w6, w7] = self.getWordArray(0 if itemLoc.item.advancement else 1) [w6, w7] = self.getWordArray(0 if itemLoc.item.advancement else 1)
multiWorldLocations.append({"sym": symbols["rando_item_table"], multiWorldLocations.append({"sym": symbols["rando_item_table"],
"offset": locationsDict[itemLoc.name].Id*8, "offset": locationsDict[itemLoc.name].Id*8,
"values": [w0, w1, w2, w3, w4, w5, w6, w7]}) "values": [w0, w1, w2, w3, w4, w5, w6, w7]})
elif itemLoc.item.player == self.player:
# this SM world owns the item: so in case the sending player might not have anything placed in this
# world to receive from it, assign them space in playerIDMap so that the ROM can display their name
# (SM item name not needed, as SM item type id will be in the message they send to this world live)
romPlayerID = itemLoc.player if itemLoc.player <= ROM_PLAYER_LIMIT else 0
if (romPlayerID > 0 and romPlayerID not in self.playerIDMap.keys()):
playerIDCount += 1
self.playerIDMap[romPlayerID] = playerIDCount
itemSprites = [{"fileName": "off_world_prog_item.bin", itemSprites = [{"fileName": "off_world_prog_item.bin",
"paletteSymbolName": "prog_item_eight_palette_indices", "paletteSymbolName": "prog_item_eight_palette_indices",
"dataSymbolName": "offworld_graphics_data_progression_item"}, "dataSymbolName": "offworld_graphics_data_progression_item"},
@ -331,7 +378,7 @@ class SMWorld(World):
"paletteSymbolName": "nonprog_item_eight_palette_indices", "paletteSymbolName": "nonprog_item_eight_palette_indices",
"dataSymbolName": "offworld_graphics_data_item"}] "dataSymbolName": "offworld_graphics_data_item"}]
idx = 0 idx = 0
offworldSprites = [] offworldSprites: List[ByteEdit] = []
for itemSprite in itemSprites: for itemSprite in itemSprites:
with open(os.path.join(os.path.dirname(__file__), "data", "custom_sprite", itemSprite["fileName"]), 'rb') as stream: with open(os.path.join(os.path.dirname(__file__), "data", "custom_sprite", itemSprite["fileName"]), 'rb') as stream:
buffer = bytearray(stream.read()) buffer = bytearray(stream.read())
@ -343,31 +390,21 @@ class SMWorld(World):
"values": buffer[8:264]}) "values": buffer[8:264]})
idx += 1 idx += 1
deathLink = [{"sym": symbols["config_deathlink"], deathLink: List[ByteEdit] = [{
"offset": 0, "sym": symbols["config_deathlink"],
"values": [self.multiworld.death_link[self.player].value]}] "offset": 0,
remoteItem = [{"sym": symbols["config_remote_items"], "values": [self.multiworld.death_link[self.player].value]
"offset": 0, }]
"values": self.getWordArray(0b001 + (0b010 if self.remote_items else 0b000))}] remoteItem: List[ByteEdit] = [{
ownPlayerId = [{"sym": symbols["config_player_id"], "sym": symbols["config_remote_items"],
"offset": 0, "offset": 0,
"values": self.getWordArray(self.player)}] "values": self.getWordArray(0b001 + (0b010 if self.remote_items else 0b000))
}]
playerNames = [] ownPlayerId: List[ByteEdit] = [{
playerNameIDMap = [] "sym": symbols["config_player_id"],
playerNames.append({"sym": symbols["rando_player_table"], "offset": 0,
"offset": 0, "values": self.getWordArray(self.player)
"values": "Archipelago".upper().center(16).encode()}) }]
playerNameIDMap.append({"sym": symbols["rando_player_id_table"],
"offset": 0,
"values": self.getWordArray(0)})
for key,value in self.playerIDMap.items():
playerNames.append({"sym": symbols["rando_player_table"],
"offset": value * 16,
"values": self.multiworld.player_name[key][:16].upper().center(16).encode()})
playerNameIDMap.append({"sym": symbols["rando_player_id_table"],
"offset": value * 2,
"values": self.getWordArray(key)})
patchDict = { 'MultiWorldLocations': multiWorldLocations, patchDict = { 'MultiWorldLocations': multiWorldLocations,
'MultiWorldItems': multiWorldItems, 'MultiWorldItems': multiWorldItems,
@ -375,15 +412,15 @@ class SMWorld(World):
'deathLink': deathLink, 'deathLink': deathLink,
'remoteItem': remoteItem, 'remoteItem': remoteItem,
'ownPlayerId': ownPlayerId, 'ownPlayerId': ownPlayerId,
'PlayerName': playerNames, 'playerNameData': playerNameData,
'PlayerNameIDMap': playerNameIDMap} 'playerIdData': playerIdData}
# convert an array of symbolic byte_edit dicts like {"sym": symobj, "offset": 0, "values": [1, 0]} # convert an array of symbolic byte_edit dicts like {"sym": symobj, "offset": 0, "values": [1, 0]}
# to a single rom patch dict like {0x438c: [1, 0], 0xa4a5: [0, 0, 0]} which varia will understand and apply # to a single rom patch dict like {0x438c: [1, 0], 0xa4a5: [0, 0, 0]} which varia will understand and apply
def resolve_symbols_to_file_offset_based_dict(byte_edits_arr) -> dict: def resolve_symbols_to_file_offset_based_dict(byte_edits_arr: List[ByteEdit]) -> Dict[int, Iterable[int]]:
this_patch_as_dict = {} this_patch_as_dict: Dict[int, Iterable[int]] = {}
for byte_edit in byte_edits_arr: for byte_edit in byte_edits_arr:
offset_within_rom_file = byte_edit["sym"]["offset_within_rom_file"] + byte_edit["offset"] offset_within_rom_file: int = byte_edit["sym"]["offset_within_rom_file"] + byte_edit["offset"]
this_patch_as_dict[offset_within_rom_file] = byte_edit["values"] this_patch_as_dict[offset_within_rom_file] = byte_edit["values"]
return this_patch_as_dict return this_patch_as_dict
@ -499,7 +536,7 @@ class SMWorld(World):
itemLocs = [ItemLocation(ItemManager.Items[itemLoc.item.type], locationsDict[itemLoc.name] if itemLoc.name in locationsDict and itemLoc.player == self.player else self.DummyLocation(self.multiworld.get_player_name(itemLoc.player) + " " + itemLoc.name), True) for itemLoc in self.multiworld.get_locations() if itemLoc.item.player == self.player] itemLocs = [ItemLocation(ItemManager.Items[itemLoc.item.type], locationsDict[itemLoc.name] if itemLoc.name in locationsDict and itemLoc.player == self.player else self.DummyLocation(self.multiworld.get_player_name(itemLoc.player) + " " + itemLoc.name), True) for itemLoc in self.multiworld.get_locations() if itemLoc.item.player == self.player]
progItemLocs = [ItemLocation(ItemManager.Items[itemLoc.item.type], locationsDict[itemLoc.name] if itemLoc.name in locationsDict and itemLoc.player == self.player else self.DummyLocation(self.multiworld.get_player_name(itemLoc.player) + " " + itemLoc.name), True) for itemLoc in self.multiworld.get_locations() if itemLoc.item.player == self.player and itemLoc.item.advancement == True] progItemLocs = [ItemLocation(ItemManager.Items[itemLoc.item.type], locationsDict[itemLoc.name] if itemLoc.name in locationsDict and itemLoc.player == self.player else self.DummyLocation(self.multiworld.get_player_name(itemLoc.player) + " " + itemLoc.name), True) for itemLoc in self.multiworld.get_locations() if itemLoc.item.player == self.player and itemLoc.item.advancement == True]
# progItemLocs = [ItemLocation(ItemManager.Items[itemLoc.item.type if itemLoc.item.type in ItemManager.Items else 'ArchipelagoItem'], locationsDict[itemLoc.name], True) for itemLoc in self.world.get_locations() if itemLoc.player == self.player and itemLoc.item.player == self.player and itemLoc.item.advancement == True] # progItemLocs = [ItemLocation(ItemManager.Items[itemLoc.item.type if itemLoc.item.type in ItemManager.Items else 'ArchipelagoItem'], locationsDict[itemLoc.name], True) for itemLoc in self.multiworld.get_locations() if itemLoc.player == self.player and itemLoc.item.player == self.player and itemLoc.item.advancement == True]
# romPatcher.writeSplitLocs(self.variaRando.args.majorsSplit, itemLocs, progItemLocs) # romPatcher.writeSplitLocs(self.variaRando.args.majorsSplit, itemLocs, progItemLocs)
romPatcher.writeSpoiler(itemLocs, progItemLocs) romPatcher.writeSpoiler(itemLocs, progItemLocs)

File diff suppressed because it is too large Load Diff

View File

@ -4,7 +4,7 @@
"CLIPLEN_end": "85:990F", "CLIPLEN_end": "85:990F",
"CLIPLEN_no_multi": "85:990C", "CLIPLEN_no_multi": "85:990C",
"CLIPSET": "85:FF1D", "CLIPSET": "85:FF1D",
"COLLECTTANK": "B8:84E8", "COLLECTTANK": "B8:8503",
"MISCFX": "85:FF45", "MISCFX": "85:FF45",
"NORMAL": "84:8BF2", "NORMAL": "84:8BF2",
"SETFX": "85:FF4E", "SETFX": "85:FF4E",
@ -12,6 +12,11 @@
"SOUNDFX_84": "84:F9E0", "SOUNDFX_84": "84:F9E0",
"SPECIALFX": "85:FF3C", "SPECIALFX": "85:FF3C",
"ammo_loop_table": "84:F896", "ammo_loop_table": "84:F896",
"ap_playerid_to_rom_other_player_index": "B8:85BA",
"ap_playerid_to_rom_other_player_index_checklastrow": "B8:85DD",
"ap_playerid_to_rom_other_player_index_correctindex": "B8:85F8",
"ap_playerid_to_rom_other_player_index_do_search_stage_1": "B8:85C0",
"ap_playerid_to_rom_other_player_index_notfound": "B8:85F5",
"archipelago_chozo_item_plm": "84:F874", "archipelago_chozo_item_plm": "84:F874",
"archipelago_hidden_item_plm": "84:F878", "archipelago_hidden_item_plm": "84:F878",
"archipelago_visible_item_plm": "84:F870", "archipelago_visible_item_plm": "84:F870",
@ -35,11 +40,13 @@
"i_item_setup_shared_all_items": "B8:8878", "i_item_setup_shared_all_items": "B8:8878",
"i_item_setup_shared_alwaysloaded": "B8:8883", "i_item_setup_shared_alwaysloaded": "B8:8883",
"i_live_pickup": "84:FA79", "i_live_pickup": "84:FA79",
"i_live_pickup_multiworld": "B8:8578", "i_live_pickup_multiworld": "B8:85FD",
"i_live_pickup_multiworld_end": "B8:85BD", "i_live_pickup_multiworld_end": "B8:8679",
"i_live_pickup_multiworld_local_item_or_offworld": "B8:8594", "i_live_pickup_multiworld_item_link_item": "B8:8659",
"i_live_pickup_multiworld_own_item": "B8:85A9", "i_live_pickup_multiworld_otherplayers_item": "B8:8649",
"i_live_pickup_multiworld_own_item1": "B8:85B5", "i_live_pickup_multiworld_own_item": "B8:8635",
"i_live_pickup_multiworld_own_item1": "B8:8641",
"i_live_pickup_multiworld_send_network": "B8:8620",
"i_load_custom_graphics": "84:FA1E", "i_load_custom_graphics": "84:FA1E",
"i_load_custom_graphics_all_items": "84:FA39", "i_load_custom_graphics_all_items": "84:FA39",
"i_load_custom_graphics_alwaysloaded": "84:FA49", "i_load_custom_graphics_alwaysloaded": "84:FA49",
@ -52,36 +59,41 @@
"i_start_draw_loop_visible_or_chozo": "84:F9E5", "i_start_draw_loop_visible_or_chozo": "84:F9E5",
"i_visible_item": "84:F8A6", "i_visible_item": "84:F8A6",
"i_visible_item_setup": "84:FA53", "i_visible_item_setup": "84:FA53",
"message_PlaceholderBig": "85:BA8A", "message_PlaceholderBig": "85:BB73",
"message_char_table": "85:BA0A", "message_char_table": "85:BAF3",
"message_hook_tilemap_calc": "85:BABC", "message_hook_tilemap_calc": "85:BBAA",
"message_hook_tilemap_calc_msgbox_mwrecv": "85:BADC", "message_hook_tilemap_calc_msgbox_mw_item_link": "85:BBDD",
"message_hook_tilemap_calc_msgbox_mwsend": "85:BACE", "message_hook_tilemap_calc_msgbox_mwrecv": "85:BBCF",
"message_hook_tilemap_calc_msgbox_mwsend": "85:BBC1",
"message_hook_tilemap_calc_normal": "85:824C", "message_hook_tilemap_calc_normal": "85:824C",
"message_hook_tilemap_calc_vanilla": "85:BAC9", "message_hook_tilemap_calc_vanilla": "85:BBBC",
"message_item_link_distributed": "85:B9A3",
"message_item_link_distributed_end": "85:BAA3",
"message_item_names": "85:9963", "message_item_names": "85:9963",
"message_item_received": "85:B8A3", "message_item_received": "85:B8A3",
"message_item_received_end": "85:B9A3", "message_item_received_end": "85:B9A3",
"message_item_sent": "85:B7A3", "message_item_sent": "85:B7A3",
"message_item_sent_end": "85:B8A3", "message_item_sent_end": "85:B8A3",
"message_multiworld_init_new_messagebox_if_needed": "85:BA95", "message_multiworld_init_new_messagebox_if_needed": "85:BB7E",
"message_multiworld_init_new_messagebox_if_needed_msgbox_mwrecv": "85:BAB1", "message_multiworld_init_new_messagebox_if_needed_msgbox_mw_item_link": "85:BB9F",
"message_multiworld_init_new_messagebox_if_needed_msgbox_mwsend": "85:BAB1", "message_multiworld_init_new_messagebox_if_needed_msgbox_mwrecv": "85:BB9F",
"message_multiworld_init_new_messagebox_if_needed_vanilla": "85:BAA9", "message_multiworld_init_new_messagebox_if_needed_msgbox_mwsend": "85:BB9F",
"message_write_placeholders": "85:B9A3", "message_multiworld_init_new_messagebox_if_needed_vanilla": "85:BB97",
"message_write_placeholders_adjust": "85:B9A5", "message_write_placeholders": "85:BAA3",
"message_write_placeholders_end": "85:BA04", "message_write_placeholders_adjust": "85:BAA5",
"message_write_placeholders_loop": "85:B9CA", "message_write_placeholders_end": "85:BAED",
"message_write_placeholders_notfound": "85:B9DC", "mw_cleanup_item_link_messagebox": "B8:84BB",
"message_write_placeholders_value_ok": "85:B9DF",
"mw_display_item_sent": "B8:848B", "mw_display_item_sent": "B8:848B",
"mw_handle_queue": "B8:84F8", "mw_handle_queue": "B8:8513",
"mw_handle_queue_end": "B8:8571", "mw_handle_queue_collect_item_if_present": "B8:8562",
"mw_handle_queue_loop": "B8:84FA", "mw_handle_queue_end": "B8:85B3",
"mw_handle_queue_new_remote_item": "B8:854A", "mw_handle_queue_found": "B8:859B",
"mw_handle_queue_next": "B8:8566", "mw_handle_queue_lookup_player": "B8:8522",
"mw_handle_queue_perform_receive": "B8:855C", "mw_handle_queue_loop": "B8:8515",
"mw_hook_main_game": "B8:85C1", "mw_handle_queue_new_remote_item": "B8:857C",
"mw_handle_queue_next": "B8:85A7",
"mw_handle_queue_perform_receive": "B8:858E",
"mw_hook_main_game": "B8:867D",
"mw_init": "B8:8311", "mw_init": "B8:8311",
"mw_init_continuereset": "B8:8366", "mw_init_continuereset": "B8:8366",
"mw_init_end": "B8:83EA", "mw_init_end": "B8:83EA",
@ -91,8 +103,9 @@
"mw_load_sram": "B8:8474", "mw_load_sram": "B8:8474",
"mw_load_sram_done": "B8:8482", "mw_load_sram_done": "B8:8482",
"mw_load_sram_setnewgame": "B8:8485", "mw_load_sram_setnewgame": "B8:8485",
"mw_receive_item": "B8:84A9", "mw_prep_item_link_messagebox": "B8:84A9",
"mw_receive_item_end": "B8:84E1", "mw_receive_item": "B8:84C4",
"mw_receive_item_end": "B8:84FC",
"mw_save_sram": "B8:8469", "mw_save_sram": "B8:8469",
"mw_write_message": "B8:8442", "mw_write_message": "B8:8442",
"nonprog_item_eight_palette_indices": "84:F888", "nonprog_item_eight_palette_indices": "84:F888",
@ -119,16 +132,16 @@
"p_visible_item_end": "84:F96E", "p_visible_item_end": "84:F96E",
"p_visible_item_loop": "84:F95B", "p_visible_item_loop": "84:F95B",
"p_visible_item_trigger": "84:F967", "p_visible_item_trigger": "84:F967",
"patch_load_multiworld": "B8:85D8", "patch_load_multiworld": "B8:8694",
"perform_item_pickup": "84:FA7E", "perform_item_pickup": "84:FA7E",
"plm_graphics_entry_offworld_item": "84:F886", "plm_graphics_entry_offworld_item": "84:F886",
"plm_graphics_entry_offworld_progression_item": "84:F87C", "plm_graphics_entry_offworld_progression_item": "84:F87C",
"plm_sequence_generic_item_0_bitmask": "84:FA90", "plm_sequence_generic_item_0_bitmask": "84:FA90",
"prog_item_eight_palette_indices": "84:F87E", "prog_item_eight_palette_indices": "84:F87E",
"rando_item_table": "B8:E000", "rando_item_table": "B8:E000",
"rando_player_id_table": "B8:DC90", "rando_player_id_table": "B8:DCA0",
"rando_player_id_table_end": "B8:DE22", "rando_player_id_table_end": "B8:DE34",
"rando_player_table": "B8:D000", "rando_player_name_table": "B8:D000",
"rando_seed_data": "B8:CF00", "rando_seed_data": "B8:CF00",
"sm_item_graphics": "B8:8800", "sm_item_graphics": "B8:8800",
"sm_item_plm_pickup_sequence_pointers": "B8:882E", "sm_item_plm_pickup_sequence_pointers": "B8:882E",