## What is this fixing or adding? - Adds the majority of OoTR 7.0 features: - Pot shuffle, Freestanding item shuffle, Crate shuffle, Beehive shuffle - Key rings mode - Dungeon shortcuts to speed up dungeons - "Regional" shuffle for dungeon items - New options for shop pricing in shopsanity - Expanded Ganon's Boss Key shuffle options - Pre-planted beans - Improved Chest Appearance Matches Contents mode - Blue Fire Arrows - Bonk self-damage - Finer control over MQ dungeons and spawn position randomization - Several bugfixes as a result of the update: - Items recognized by the server and valid starting items are now in a 1-to-1 correspondence. In particular, starting with keys is now supported. - Entrance randomization success rate improved. Hopefully it is now at 100%. Co-authored-by: Zach Parks <zach@alliware.com>
		
			
				
	
	
		
			1204 lines
		
	
	
		
			53 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			1204 lines
		
	
	
		
			53 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
from itertools import chain
 | 
						|
from enum import IntEnum
 | 
						|
from .ItemPool import IGNORE_LOCATION
 | 
						|
 | 
						|
class Scenes(IntEnum):
 | 
						|
    # Dungeons
 | 
						|
    DEKU_TREE = 0x00
 | 
						|
    DODONGOS_CAVERN = 0x01
 | 
						|
    KING_DODONGO_LOBBY = 0x12
 | 
						|
    JABU_JABU = 0x02
 | 
						|
    FOREST_TEMPLE = 0x03
 | 
						|
    FIRE_TEMPLE = 0x04
 | 
						|
    WATER_TEMPLE = 0x05
 | 
						|
    SPIRIT_TEMPLE = 0x06
 | 
						|
    SHADOW_TEMPLE = 0x07
 | 
						|
    # Bean patch scenes
 | 
						|
    GRAVEYARD = 0x53
 | 
						|
    ZORAS_RIVER = 0x54
 | 
						|
    KOKIRI_FOREST = 0x55
 | 
						|
    LAKE_HYLIA = 0x57
 | 
						|
    GERUDO_VALLEY = 0x5A
 | 
						|
    LOST_WOODS = 0x5B
 | 
						|
    DESERT_COLOSSUS = 0x5C
 | 
						|
    DEATH_MOUNTAIN_TRAIL = 0x60
 | 
						|
    DEATH_MOUNTAIN_CRATER = 0x61
 | 
						|
 | 
						|
class FlagType(IntEnum):
 | 
						|
    CHEST = 0x00
 | 
						|
    SWITCH = 0x01
 | 
						|
    CLEAR = 0x02
 | 
						|
    COLLECT = 0x03
 | 
						|
    # 0x04 unused
 | 
						|
    VISITED_ROOM = 0x05
 | 
						|
    VISITED_FLOOR = 0x06
 | 
						|
 | 
						|
class Address():
 | 
						|
    prev_address = None
 | 
						|
 | 
						|
    def __init__(self, address=None, size=4, mask=0xFFFFFFFF, max=None, choices=None, value=None):
 | 
						|
        if address is None:
 | 
						|
            self.address = Address.prev_address
 | 
						|
        else:
 | 
						|
            self.address = address           
 | 
						|
        self.value = value
 | 
						|
        self.size = size
 | 
						|
        self.choices = choices
 | 
						|
        self.mask = mask
 | 
						|
 | 
						|
        Address.prev_address = self.address + self.size
 | 
						|
 | 
						|
        self.bit_offset = 0
 | 
						|
        while mask & 1 == 0:
 | 
						|
            mask = mask >> 1
 | 
						|
            self.bit_offset += 1
 | 
						|
 | 
						|
        if max is None:
 | 
						|
            self.max = mask
 | 
						|
        else:
 | 
						|
            self.max = max
 | 
						|
 | 
						|
 | 
						|
    def get_value(self, default=0):
 | 
						|
        if self.value is None:
 | 
						|
            return default
 | 
						|
        return self.value
 | 
						|
 | 
						|
 | 
						|
    def get_value_raw(self):
 | 
						|
        if self.value is None:
 | 
						|
            return None
 | 
						|
 | 
						|
        value = self.value
 | 
						|
        if self.choices is not None:
 | 
						|
            value = self.choices[value]
 | 
						|
        if not isinstance(value, int):
 | 
						|
            raise ValueError("Invalid value type '%s'" % str(value))
 | 
						|
 | 
						|
        if isinstance(value, bool):
 | 
						|
            value = 1 if value else 0
 | 
						|
        if value > self.max:
 | 
						|
            value = self.max
 | 
						|
 | 
						|
        value = (value << self.bit_offset) & self.mask
 | 
						|
        return value
 | 
						|
 | 
						|
 | 
						|
    def set_value_raw(self, value):
 | 
						|
        if value is None:
 | 
						|
            self.value = None
 | 
						|
            return
 | 
						|
 | 
						|
        if not isinstance(value, int):
 | 
						|
            raise ValueError("Invalid value type '%s'" % str(value))
 | 
						|
 | 
						|
        value = (value & self.mask) >> self.bit_offset
 | 
						|
        if value > self.max:
 | 
						|
            value = self.max
 | 
						|
        
 | 
						|
        if self.choices is not None:
 | 
						|
            for choice_name, choice_value in self.choices.items():
 | 
						|
                if choice_value == value:
 | 
						|
                    value = choice_name
 | 
						|
                    break
 | 
						|
 | 
						|
        self.value = value
 | 
						|
 | 
						|
 | 
						|
    def get_writes(self, save_context):
 | 
						|
        if self.value is None:
 | 
						|
            return
 | 
						|
 | 
						|
        value = self.get_value_raw()
 | 
						|
        if value is None:
 | 
						|
            return
 | 
						|
 | 
						|
        values = zip(Address.to_bytes(value, self.size), 
 | 
						|
                     Address.to_bytes(self.mask, self.size))
 | 
						|
 | 
						|
        for i, (byte, mask) in enumerate(values):
 | 
						|
            if mask == 0:
 | 
						|
                continue
 | 
						|
            if mask == 0xFF:
 | 
						|
                save_context.write_byte(self.address + i, byte)
 | 
						|
            else:
 | 
						|
                save_context.write_bits(self.address + i, byte, mask=mask)
 | 
						|
 | 
						|
 | 
						|
    def to_bytes(value, size):
 | 
						|
        ret = []
 | 
						|
        for _ in range(size):
 | 
						|
            ret.insert(0, value & 0xFF)
 | 
						|
            value = value >> 8
 | 
						|
        return ret
 | 
						|
 | 
						|
 | 
						|
class SaveContext():
 | 
						|
    def __init__(self):
 | 
						|
        self.save_bits = {}
 | 
						|
        self.save_bytes = {}
 | 
						|
        self.addresses = self.get_save_context_addresses()
 | 
						|
 | 
						|
 | 
						|
    # will set the bits of value to the offset in the save (or'ing them with what is already there)
 | 
						|
    def write_bits(self, address, value, mask=None, predicate=None):
 | 
						|
        if predicate and not predicate(value):
 | 
						|
            return
 | 
						|
 | 
						|
        if mask is not None:
 | 
						|
            value = value & mask
 | 
						|
 | 
						|
        if address in self.save_bytes:
 | 
						|
            old_val = self.save_bytes[address]
 | 
						|
            if mask is not None:
 | 
						|
                old_val &= ~mask
 | 
						|
            value = old_val | value
 | 
						|
            self.write_byte(address, value, predicate)
 | 
						|
        elif address in self.save_bits:
 | 
						|
            if mask is not None:
 | 
						|
                self.save_bits[address] &= ~mask
 | 
						|
            self.save_bits[address] |= value
 | 
						|
        else:
 | 
						|
            self.save_bits[address] = value
 | 
						|
 | 
						|
 | 
						|
    # will overwrite the byte at offset with the given value
 | 
						|
    def write_byte(self, address, value, predicate=None):
 | 
						|
        if predicate and not predicate(value):
 | 
						|
            return
 | 
						|
 | 
						|
        if address in self.save_bits:
 | 
						|
            del self.save_bits[address]
 | 
						|
 | 
						|
        self.save_bytes[address] = value
 | 
						|
 | 
						|
 | 
						|
    # will overwrite the byte at offset with the given value
 | 
						|
    def write_bytes(self, address, bytes, predicate=None):
 | 
						|
        for i, value in enumerate(bytes):
 | 
						|
            self.write_byte(address + i, value, predicate)
 | 
						|
 | 
						|
 | 
						|
    def write_save_entry(self, address):
 | 
						|
        if isinstance(address, dict):
 | 
						|
            for name, sub_address in address.items():
 | 
						|
                self.write_save_entry(sub_address)
 | 
						|
        elif isinstance(address, list):
 | 
						|
            for sub_address in address:
 | 
						|
                self.write_save_entry(sub_address)
 | 
						|
        else:
 | 
						|
            address.get_writes(self)
 | 
						|
 | 
						|
    def write_permanent_flag(self, scene, type, byte_offset, bit_values):
 | 
						|
        # Save format is described here: https://wiki.cloudmodding.com/oot/Save_Format
 | 
						|
        # Permanent flags start at offset 0x00D4. Each scene has 7 types of flags, one
 | 
						|
        # of which is unused. Each flag type is 4 bytes wide per-scene, thus each scene
 | 
						|
        # takes 28 (0x1C) bytes.
 | 
						|
        # Scenes and FlagType enums are defined for increased readability when using
 | 
						|
        # this function.
 | 
						|
        self.write_bits(0x00D4 + scene * 0x1C + type * 0x04 + byte_offset, bit_values)
 | 
						|
 | 
						|
    def set_ammo_max(self):
 | 
						|
        ammo_maxes = {
 | 
						|
            'stick'     : ('stick_upgrade', [10,  10,  20,  30]),
 | 
						|
            'nut'       : ('nut_upgrade',   [20,  20,  30,  40]),
 | 
						|
            'bomb'      : ('bomb_bag',      [00,  20,  30,  40]),
 | 
						|
            'bow'       : ('quiver',        [00,  30,  40,  50]),
 | 
						|
            'slingshot' : ('bullet_bag',    [00,  30,  40,  50]),
 | 
						|
            'rupees'    : ('wallet',        [99, 200, 500, 999]),
 | 
						|
        }
 | 
						|
 | 
						|
        for ammo, (upgrade, maxes) in ammo_maxes.items():
 | 
						|
            upgrade_count = self.addresses['upgrades'][upgrade].get_value()
 | 
						|
            try:
 | 
						|
                ammo_max = maxes[upgrade_count]
 | 
						|
            except IndexError:
 | 
						|
                ammo_max = maxes[-1]
 | 
						|
            if ammo == 'rupees':
 | 
						|
                self.addresses[ammo].max = ammo_max
 | 
						|
            else:
 | 
						|
                self.addresses['ammo'][ammo].max = ammo_max
 | 
						|
 | 
						|
 | 
						|
    # will overwrite the byte at offset with the given value
 | 
						|
    def write_save_table(self, rom):
 | 
						|
        self.set_ammo_max()
 | 
						|
        for name, address in self.addresses.items():
 | 
						|
            self.write_save_entry(address)
 | 
						|
 | 
						|
        save_table = []
 | 
						|
        for address, value in self.save_bits.items():
 | 
						|
            if value != 0:
 | 
						|
                save_table += [(address & 0xFF00) >> 8, address & 0xFF, 0x00, value]
 | 
						|
        for address, value in self.save_bytes.items():
 | 
						|
            save_table += [(address & 0xFF00) >> 8, address & 0xFF, 0x01, value]
 | 
						|
        save_table += [0x00,0x00,0x00,0x00]
 | 
						|
 | 
						|
        table_len = len(save_table)
 | 
						|
        if table_len > 0x400:
 | 
						|
            raise Exception("The Initial Save Table has exceeded its maximum capacity: 0x%03X/0x400" % table_len)
 | 
						|
        rom.write_bytes(rom.sym('INITIAL_SAVE_DATA'), save_table)
 | 
						|
 | 
						|
 | 
						|
    def give_bottle(self, item, count):
 | 
						|
        for bottle_id in range(4):
 | 
						|
            item_slot = 'bottle_%d' % (bottle_id + 1)
 | 
						|
            if self.addresses['item_slot'][item_slot].get_value(0xFF) != 0xFF:
 | 
						|
                continue
 | 
						|
 | 
						|
            self.addresses['item_slot'][item_slot].value = SaveContext.bottle_types[item]
 | 
						|
            count -= 1
 | 
						|
 | 
						|
            if count == 0:
 | 
						|
                return
 | 
						|
 | 
						|
 | 
						|
    def give_health(self, health):
 | 
						|
        health += self.addresses['health_capacity'].get_value(0x30) / 0x10
 | 
						|
        health += self.addresses['quest']['heart_pieces'].get_value() / 4
 | 
						|
 | 
						|
        self.addresses['health_capacity'].value       = int(health) * 0x10
 | 
						|
        self.addresses['health'].value                = int(health) * 0x10
 | 
						|
        self.addresses['quest']['heart_pieces'].value = int((health % 1) * 4) * 0x10
 | 
						|
 | 
						|
 | 
						|
    def give_item(self, world, item, count=1):
 | 
						|
        if item.endswith(')'):
 | 
						|
            item_base, implicit_count = item[:-1].split(' (', 1)
 | 
						|
            if implicit_count.isdigit():
 | 
						|
                item = item_base
 | 
						|
                count *= int(implicit_count)
 | 
						|
 | 
						|
        if item in SaveContext.bottle_types:
 | 
						|
            self.give_bottle(item, count)
 | 
						|
        elif item in ["Piece of Heart", "Piece of Heart (Treasure Chest Game)"]:
 | 
						|
            self.give_health(count / 4)
 | 
						|
        elif item == "Heart Container":
 | 
						|
            self.give_health(count)
 | 
						|
        elif item == "Bombchu Item":
 | 
						|
            self.give_bombchu_item(world)
 | 
						|
        elif item == IGNORE_LOCATION:
 | 
						|
            pass # used to disable some skipped and inaccessible locations
 | 
						|
        elif item in SaveContext.save_writes_table:
 | 
						|
            if item.startswith('Small Key Ring ('):
 | 
						|
                dungeon = item[:-1].split(' (', 1)[1]
 | 
						|
                save_writes = {
 | 
						|
                    "Forest Temple"          : {
 | 
						|
                        'keys.forest': 6 if world.dungeon_mq[dungeon] else 5,
 | 
						|
                        'total_keys.forest': 6 if world.dungeon_mq[dungeon] else 5,
 | 
						|
                    },
 | 
						|
                    "Fire Temple"            : {
 | 
						|
                        'keys.fire': 5 if world.dungeon_mq[dungeon] else 8,
 | 
						|
                        'total_keys.fire': 5 if world.dungeon_mq[dungeon] else 8,
 | 
						|
                    },
 | 
						|
                    "Water Temple"           : {
 | 
						|
                        'keys.water': 2 if world.dungeon_mq[dungeon] else 6,
 | 
						|
                        'total_keys.water': 2 if world.dungeon_mq[dungeon] else 6,
 | 
						|
                    },
 | 
						|
                    "Spirit Temple"          : {
 | 
						|
                        'keys.spirit': 7 if world.dungeon_mq[dungeon] else 5,
 | 
						|
                        'total_keys.spirit': 7 if world.dungeon_mq[dungeon] else 5,
 | 
						|
                    },
 | 
						|
                    "Shadow Temple"          : {
 | 
						|
                        'keys.shadow': 6 if world.dungeon_mq[dungeon] else 5,
 | 
						|
                        'total_keys.shadow': 6 if world.dungeon_mq[dungeon] else 5,
 | 
						|
                    },
 | 
						|
                    "Bottom of the Well"     : {
 | 
						|
                        'keys.botw': 2 if world.dungeon_mq[dungeon] else 3,
 | 
						|
                        'total_keys.botw': 2 if world.dungeon_mq[dungeon] else 3,
 | 
						|
                    },
 | 
						|
                    "Gerudo Training Ground" : {
 | 
						|
                        'keys.gtg': 3 if world.dungeon_mq[dungeon] else 9,
 | 
						|
                        'total_keys.gtg': 3 if world.dungeon_mq[dungeon] else 9,
 | 
						|
                    },
 | 
						|
                    "Thieves Hideout"        : {
 | 
						|
                        'keys.fortress': 4,
 | 
						|
                        'total_keys.fortress': 4,
 | 
						|
                    },
 | 
						|
                    "Ganons Castle"          : {
 | 
						|
                        'keys.gc': 3 if world.dungeon_mq[dungeon] else 2,
 | 
						|
                        'total_keys.gc': 3 if world.dungeon_mq[dungeon] else 2,
 | 
						|
                    },
 | 
						|
                }[dungeon]
 | 
						|
            else:
 | 
						|
                save_writes = SaveContext.save_writes_table[item]
 | 
						|
            for address, value in save_writes.items():
 | 
						|
                if value is None:
 | 
						|
                    value = count
 | 
						|
                elif isinstance(value, list):
 | 
						|
                    value = value[min(len(value), count) - 1]
 | 
						|
                elif isinstance(value, bool):
 | 
						|
                    value = 1 if value else 0
 | 
						|
 | 
						|
                address_value = self.addresses
 | 
						|
                prev_sub_address = 'Save Context'
 | 
						|
                for sub_address in address.split('.'):
 | 
						|
                    if sub_address not in address_value:
 | 
						|
                        raise ValueError('Unknown key %s in %s of SaveContext' % (sub_address, prev_sub_address))
 | 
						|
 | 
						|
                    if isinstance(address_value, list):
 | 
						|
                        sub_address =  int(sub_address)
 | 
						|
 | 
						|
                    address_value = address_value[sub_address]
 | 
						|
                    prev_sub_address = sub_address
 | 
						|
                if not isinstance(address_value, Address):
 | 
						|
                    raise ValueError('%s does not resolve to an Address in SaveContext' % (sub_address))
 | 
						|
 | 
						|
                if isinstance(value, int) and value < address_value.get_value():
 | 
						|
                    continue
 | 
						|
 | 
						|
                address_value.value = value
 | 
						|
        else:
 | 
						|
            raise ValueError("Cannot give unknown starting item %s" % item)
 | 
						|
 | 
						|
 | 
						|
    def give_bombchu_item(self, world):
 | 
						|
        self.give_item(world, "Bombchus", 0)
 | 
						|
 | 
						|
 | 
						|
    def equip_default_items(self, age):
 | 
						|
        self.equip_items(age, 'equips_' + age)
 | 
						|
 | 
						|
 | 
						|
    def equip_current_items(self, age):
 | 
						|
        self.equip_items(age, 'equips')
 | 
						|
 | 
						|
 | 
						|
    def equip_items(self, age, equip_type):
 | 
						|
        if age not in ['child', 'adult']:
 | 
						|
            raise ValueError("Age must be 'adult' or 'child', not %s" % age)
 | 
						|
 | 
						|
        if equip_type not in ['equips', 'equips_child', 'equips_adult']:
 | 
						|
            raise ValueError("Equip type must be 'equips', 'equips_child' or 'equips_adult', not %s" % equip_type)
 | 
						|
 | 
						|
        age = 'equips_' + age
 | 
						|
        c_buttons = list(self.addresses[age]['button_slots'].keys())
 | 
						|
        for item_slot in SaveContext.equipable_items[age]['items']:
 | 
						|
            item = self.addresses['item_slot'][item_slot].get_value('none')
 | 
						|
            if item != 'none':
 | 
						|
                c_button = c_buttons.pop()
 | 
						|
                self.addresses[equip_type]['button_slots'][c_button].value = item_slot
 | 
						|
                self.addresses[equip_type]['button_items'][c_button].value = item
 | 
						|
                if not c_buttons:
 | 
						|
                    break
 | 
						|
 | 
						|
        for equip_item, equip_addresses in self.addresses[age]['equips'].items():
 | 
						|
            for item in SaveContext.equipable_items[age][equip_item]:
 | 
						|
                if self.addresses['equip_items'][item].get_value():
 | 
						|
                    item_value = self.addresses['equip_items'][item].get_value_raw()
 | 
						|
                    self.addresses[equip_type]['equips'][equip_item].set_value_raw(item_value)
 | 
						|
                    if equip_item == 'tunic':
 | 
						|
                        self.addresses[equip_type]['equips'][equip_item].value = 1
 | 
						|
                    if equip_item == 'sword':
 | 
						|
                        self.addresses[equip_type]['button_items']['b'].value = item
 | 
						|
                    break
 | 
						|
 | 
						|
 | 
						|
    def get_save_context_addresses(self):
 | 
						|
        return {
 | 
						|
            'entrance_index'             : Address(0x0000, size=4),
 | 
						|
            'link_age'                   : Address(size=4, max=1),
 | 
						|
            'unk_00'                     : Address(size=2),
 | 
						|
            'cutscene_index'             : Address(size=2),
 | 
						|
            'time_of_day'                : Address(size=2),
 | 
						|
            'unk_01'                     : Address(size=2),
 | 
						|
            'night_flag'                 : Address(size=4, max=1),
 | 
						|
            'unk_02'                     : Address(size=8),
 | 
						|
            'id'                         : Address(size=6),
 | 
						|
            'deaths'                     : Address(size=2),
 | 
						|
            'file_name'                  : Address(size=8),
 | 
						|
            'n64dd_flag'                 : Address(size=2),
 | 
						|
            'health_capacity'            : Address(size=2, max=0x140),
 | 
						|
            'health'                     : Address(size=2, max=0x140),
 | 
						|
            'magic_level'                : Address(size=1, max=2),
 | 
						|
            'magic'                      : Address(size=1, max=0x60),
 | 
						|
            'rupees'                     : Address(size=2),
 | 
						|
            'bgs_hits_left'              : Address(size=2),
 | 
						|
            'navi_timer'                 : Address(size=2),
 | 
						|
            'magic_acquired'             : Address(size=1, max=1),
 | 
						|
            'unk_03'                     : Address(size=1),
 | 
						|
            'double_magic'               : Address(size=1, max=1),
 | 
						|
            'double_defense'             : Address(size=1, max=1),
 | 
						|
            'bgs_flag'                   : Address(size=1, max=1),
 | 
						|
            'unk_05'                     : Address(size=1),
 | 
						|
 | 
						|
            # Equiped Items
 | 
						|
            'equips_child' : {
 | 
						|
                'button_items' : {
 | 
						|
                    'b'                  : Address(size=1, choices=SaveContext.item_id_map),
 | 
						|
                    'left'               : Address(size=1, choices=SaveContext.item_id_map),
 | 
						|
                    'down'               : Address(size=1, choices=SaveContext.item_id_map),
 | 
						|
                    'right'              : Address(size=1, choices=SaveContext.item_id_map),
 | 
						|
                },
 | 
						|
                'button_slots' : {
 | 
						|
                    'left'               : Address(size=1, choices=SaveContext.slot_id_map),
 | 
						|
                    'down'               : Address(size=1, choices=SaveContext.slot_id_map),
 | 
						|
                    'right'              : Address(size=1, choices=SaveContext.slot_id_map),
 | 
						|
                },
 | 
						|
                'equips' : {
 | 
						|
                    'sword'              : Address(0x0048, size=2, mask=0x000F),
 | 
						|
                    'shield'             : Address(0x0048, size=2, mask=0x00F0),
 | 
						|
                    'tunic'              : Address(0x0048, size=2, mask=0x0F00),
 | 
						|
                    'boots'              : Address(0x0048, size=2, mask=0xF000),                
 | 
						|
                },
 | 
						|
            },
 | 
						|
            'equips_adult' : {
 | 
						|
                'button_items' : {
 | 
						|
                    'b'                  : Address(size=1, choices=SaveContext.item_id_map),
 | 
						|
                    'left'               : Address(size=1, choices=SaveContext.item_id_map),
 | 
						|
                    'down'               : Address(size=1, choices=SaveContext.item_id_map),
 | 
						|
                    'right'              : Address(size=1, choices=SaveContext.item_id_map),
 | 
						|
                },
 | 
						|
                'button_slots' : {
 | 
						|
                    'left'               : Address(size=1, choices=SaveContext.slot_id_map),
 | 
						|
                    'down'               : Address(size=1, choices=SaveContext.slot_id_map),
 | 
						|
                    'right'              : Address(size=1, choices=SaveContext.slot_id_map),
 | 
						|
                },
 | 
						|
                'equips' : {
 | 
						|
                    'sword'              : Address(0x0052, size=2, mask=0x000F),
 | 
						|
                    'shield'             : Address(0x0052, size=2, mask=0x00F0),
 | 
						|
                    'tunic'              : Address(0x0052, size=2, mask=0x0F00),
 | 
						|
                    'boots'              : Address(0x0052, size=2, mask=0xF000),                
 | 
						|
                },
 | 
						|
            },
 | 
						|
            'unk_06'                     : Address(size=0x12),
 | 
						|
            'scene_index'                : Address(size=2),
 | 
						|
 | 
						|
            'equips' : {
 | 
						|
                'button_items' : {
 | 
						|
                    'b'                  : Address(size=1, choices=SaveContext.item_id_map),
 | 
						|
                    'left'               : Address(size=1, choices=SaveContext.item_id_map),
 | 
						|
                    'down'               : Address(size=1, choices=SaveContext.item_id_map),
 | 
						|
                    'right'              : Address(size=1, choices=SaveContext.item_id_map),
 | 
						|
                },
 | 
						|
                'button_slots' : {
 | 
						|
                    'left'               : Address(size=1, choices=SaveContext.slot_id_map),
 | 
						|
                    'down'               : Address(size=1, choices=SaveContext.slot_id_map),
 | 
						|
                    'right'              : Address(size=1, choices=SaveContext.slot_id_map),
 | 
						|
                },
 | 
						|
                'equips' : {
 | 
						|
                    'sword'              : Address(0x0070, size=2, mask=0x000F, max=3),
 | 
						|
                    'shield'             : Address(0x0070, size=2, mask=0x00F0, max=3),
 | 
						|
                    'tunic'              : Address(0x0070, size=2, mask=0x0F00, max=3),
 | 
						|
                    'boots'              : Address(0x0070, size=2, mask=0xF000, max=3),                
 | 
						|
                },
 | 
						|
            },
 | 
						|
            'unk_07'                     : Address(size=2),
 | 
						|
 | 
						|
            # Item Slots
 | 
						|
            'item_slot'                  : {
 | 
						|
                'stick'                  : Address(size=1, choices=SaveContext.item_id_map),
 | 
						|
                'nut'                    : Address(size=1, choices=SaveContext.item_id_map),
 | 
						|
                'bomb'                   : Address(size=1, choices=SaveContext.item_id_map),
 | 
						|
                'bow'                    : Address(size=1, choices=SaveContext.item_id_map),
 | 
						|
                'fire_arrow'             : Address(size=1, choices=SaveContext.item_id_map),
 | 
						|
                'dins_fire'              : Address(size=1, choices=SaveContext.item_id_map),
 | 
						|
                'slingshot'              : Address(size=1, choices=SaveContext.item_id_map),
 | 
						|
                'ocarina'                : Address(size=1, choices=SaveContext.item_id_map),
 | 
						|
                'bombchu'                : Address(size=1, choices=SaveContext.item_id_map),
 | 
						|
                'hookshot'               : Address(size=1, choices=SaveContext.item_id_map),
 | 
						|
                'ice_arrow'              : Address(size=1, choices=SaveContext.item_id_map),
 | 
						|
                'farores_wind'           : Address(size=1, choices=SaveContext.item_id_map),
 | 
						|
                'boomerang'              : Address(size=1, choices=SaveContext.item_id_map),
 | 
						|
                'lens'                   : Address(size=1, choices=SaveContext.item_id_map),
 | 
						|
                'beans'                  : Address(size=1, choices=SaveContext.item_id_map),
 | 
						|
                'hammer'                 : Address(size=1, choices=SaveContext.item_id_map),
 | 
						|
                'light_arrow'            : Address(size=1, choices=SaveContext.item_id_map),
 | 
						|
                'nayrus_love'            : Address(size=1, choices=SaveContext.item_id_map),
 | 
						|
                'bottle_1'               : Address(size=1, choices=SaveContext.item_id_map),
 | 
						|
                'bottle_2'               : Address(size=1, choices=SaveContext.item_id_map),
 | 
						|
                'bottle_3'               : Address(size=1, choices=SaveContext.item_id_map),
 | 
						|
                'bottle_4'               : Address(size=1, choices=SaveContext.item_id_map),
 | 
						|
                'adult_trade'            : Address(size=1, choices=SaveContext.item_id_map),
 | 
						|
                'child_trade'            : Address(size=1, choices=SaveContext.item_id_map),
 | 
						|
            },
 | 
						|
 | 
						|
            # Item Ammo
 | 
						|
            'ammo' : {
 | 
						|
                'stick'                  : Address(size=1),
 | 
						|
                'nut'                    : Address(size=1),
 | 
						|
                'bomb'                   : Address(size=1),
 | 
						|
                'bow'                    : Address(size=1),
 | 
						|
                'fire_arrow'             : Address(size=1, max=0),
 | 
						|
                'dins_fire'              : Address(size=1, max=0),
 | 
						|
                'slingshot'              : Address(size=1),
 | 
						|
                'ocarina'                : Address(size=1, max=0),
 | 
						|
                'bombchu'                : Address(size=1, max=50),
 | 
						|
                'hookshot'               : Address(size=1, max=0),
 | 
						|
                'ice_arrow'              : Address(size=1, max=0),
 | 
						|
                'farores_wind'           : Address(size=1, max=0),
 | 
						|
                'boomerang'              : Address(size=1, max=0),
 | 
						|
                'lens'                   : Address(size=1, max=0),
 | 
						|
                'beans'                  : Address(size=1, max=10),
 | 
						|
            },
 | 
						|
            'magic_beans_sold'           : Address(size=1, max=10),
 | 
						|
 | 
						|
            # Equipment
 | 
						|
            'equip_items' : {
 | 
						|
                'kokiri_sword'           : Address(0x009C, size=2, mask=0x0001),
 | 
						|
                'master_sword'           : Address(0x009C, size=2, mask=0x0002),
 | 
						|
                'biggoron_sword'         : Address(0x009C, size=2, mask=0x0004),
 | 
						|
                'broken_giants_knife'    : Address(0x009C, size=2, mask=0x0008),
 | 
						|
                'deku_shield'            : Address(0x009C, size=2, mask=0x0010),
 | 
						|
                'hylian_shield'          : Address(0x009C, size=2, mask=0x0020),
 | 
						|
                'mirror_shield'          : Address(0x009C, size=2, mask=0x0040),
 | 
						|
                'kokiri_tunic'           : Address(0x009C, size=2, mask=0x0100),
 | 
						|
                'goron_tunic'            : Address(0x009C, size=2, mask=0x0200),
 | 
						|
                'zora_tunic'             : Address(0x009C, size=2, mask=0x0400),
 | 
						|
                'kokiri_boots'           : Address(0x009C, size=2, mask=0x1000),
 | 
						|
                'iron_boots'             : Address(0x009C, size=2, mask=0x2000),
 | 
						|
                'hover_boots'            : Address(0x009C, size=2, mask=0x4000),
 | 
						|
            },
 | 
						|
 | 
						|
            'unk_08'                     : Address(size=2),
 | 
						|
 | 
						|
            # Upgrades
 | 
						|
            'upgrades' : {
 | 
						|
                'quiver'                 : Address(0x00A0, mask=0x00000007, max=3),
 | 
						|
                'bomb_bag'               : Address(0x00A0, mask=0x00000038, max=3),
 | 
						|
                'strength_upgrade'       : Address(0x00A0, mask=0x000001C0, max=3),
 | 
						|
                'diving_upgrade'         : Address(0x00A0, mask=0x00000E00, max=2),
 | 
						|
                'wallet'                 : Address(0x00A0, mask=0x00003000, max=3),
 | 
						|
                'bullet_bag'             : Address(0x00A0, mask=0x0001C000, max=3),
 | 
						|
                'stick_upgrade'          : Address(0x00A0, mask=0x000E0000, max=3),
 | 
						|
                'nut_upgrade'            : Address(0x00A0, mask=0x00700000, max=3),
 | 
						|
            },
 | 
						|
 | 
						|
            # Medallions
 | 
						|
            'quest' : {
 | 
						|
                'medallions' : {
 | 
						|
                    'forest'             : Address(0x00A4, mask=0x00000001),
 | 
						|
                    'fire'               : Address(0x00A4, mask=0x00000002),
 | 
						|
                    'water'              : Address(0x00A4, mask=0x00000004),
 | 
						|
                    'spirit'             : Address(0x00A4, mask=0x00000008),
 | 
						|
                    'shadow'             : Address(0x00A4, mask=0x00000010),
 | 
						|
                    'light'              : Address(0x00A4, mask=0x00000020),
 | 
						|
 | 
						|
                },
 | 
						|
                'songs' : {
 | 
						|
                    'minuet_of_forest'   : Address(0x00A4, mask=0x00000040),
 | 
						|
                    'bolero_of_fire'     : Address(0x00A4, mask=0x00000080),
 | 
						|
                    'serenade_of_water'  : Address(0x00A4, mask=0x00000100),
 | 
						|
                    'requiem_of_spirit'  : Address(0x00A4, mask=0x00000200),
 | 
						|
                    'nocturne_of_shadow' : Address(0x00A4, mask=0x00000400),
 | 
						|
                    'prelude_of_light'   : Address(0x00A4, mask=0x00000800),
 | 
						|
                    'zeldas_lullaby'     : Address(0x00A4, mask=0x00001000),
 | 
						|
                    'eponas_song'        : Address(0x00A4, mask=0x00002000),
 | 
						|
                    'sarias_song'        : Address(0x00A4, mask=0x00004000),
 | 
						|
                    'suns_song'          : Address(0x00A4, mask=0x00008000),
 | 
						|
                    'song_of_time'       : Address(0x00A4, mask=0x00010000),
 | 
						|
                    'song_of_storms'     : Address(0x00A4, mask=0x00020000),
 | 
						|
                },
 | 
						|
                'stones' : {
 | 
						|
                    'kokiris_emerald'    : Address(0x00A4, mask=0x00040000),
 | 
						|
                    'gorons_ruby'        : Address(0x00A4, mask=0x00080000),
 | 
						|
                    'zoras_sapphire'     : Address(0x00A4, mask=0x00100000),
 | 
						|
                },
 | 
						|
                'stone_of_agony'         : Address(0x00A4, mask=0x00200000),
 | 
						|
                'gerudos_card'           : Address(0x00A4, mask=0x00400000),
 | 
						|
                'gold_skulltula'         : Address(0x00A4, mask=0x00800000),
 | 
						|
                'heart_pieces'           : Address(0x00A4, mask=0xFF000000),
 | 
						|
            },
 | 
						|
 | 
						|
            # Dungeon Items
 | 
						|
            'dungeon_items' : {
 | 
						|
                'deku' : {
 | 
						|
                     'boss_key'          : Address(0x00A8, size=1, mask=0x01),
 | 
						|
                     'compass'           : Address(0x00A8, size=1, mask=0x02),
 | 
						|
                     'map'               : Address(0x00A8, size=1, mask=0x04),
 | 
						|
                },
 | 
						|
                'dodongo' : {
 | 
						|
                     'boss_key'          : Address(0x00A9, size=1, mask=0x01),
 | 
						|
                     'compass'           : Address(0x00A9, size=1, mask=0x02),
 | 
						|
                     'map'               : Address(0x00A9, size=1, mask=0x04),
 | 
						|
                },
 | 
						|
                'jabu' : {
 | 
						|
                     'boss_key'          : Address(0x00AA, size=1, mask=0x01),
 | 
						|
                     'compass'           : Address(0x00AA, size=1, mask=0x02),
 | 
						|
                     'map'               : Address(0x00AA, size=1, mask=0x04),
 | 
						|
                },
 | 
						|
                'forest' : {
 | 
						|
                     'boss_key'          : Address(0x00AB, size=1, mask=0x01),
 | 
						|
                     'compass'           : Address(0x00AB, size=1, mask=0x02),
 | 
						|
                     'map'               : Address(0x00AB, size=1, mask=0x04),
 | 
						|
                },
 | 
						|
                'fire' : {
 | 
						|
                     'boss_key'          : Address(0x00AC, size=1, mask=0x01),
 | 
						|
                     'compass'           : Address(0x00AC, size=1, mask=0x02),
 | 
						|
                     'map'               : Address(0x00AC, size=1, mask=0x04),
 | 
						|
                },
 | 
						|
                'water' : {
 | 
						|
                     'boss_key'          : Address(0x00AD, size=1, mask=0x01),
 | 
						|
                     'compass'           : Address(0x00AD, size=1, mask=0x02),
 | 
						|
                     'map'               : Address(0x00AD, size=1, mask=0x04),
 | 
						|
                },
 | 
						|
                'spirit' : {
 | 
						|
                     'boss_key'          : Address(0x00AE, size=1, mask=0x01),
 | 
						|
                     'compass'           : Address(0x00AE, size=1, mask=0x02),
 | 
						|
                     'map'               : Address(0x00AE, size=1, mask=0x04),
 | 
						|
                },
 | 
						|
                'shadow' : {
 | 
						|
                     'boss_key'          : Address(0x00AF, size=1, mask=0x01),
 | 
						|
                     'compass'           : Address(0x00AF, size=1, mask=0x02),
 | 
						|
                     'map'               : Address(0x00AF, size=1, mask=0x04),
 | 
						|
                },
 | 
						|
                'botw' : {
 | 
						|
                     'boss_key'          : Address(0x00B0, size=1, mask=0x01),
 | 
						|
                     'compass'           : Address(0x00B0, size=1, mask=0x02),
 | 
						|
                     'map'               : Address(0x00B0, size=1, mask=0x04),
 | 
						|
                },
 | 
						|
                'ice' : {
 | 
						|
                     'boss_key'          : Address(0x00B1, size=1, mask=0x01),
 | 
						|
                     'compass'           : Address(0x00B1, size=1, mask=0x02),
 | 
						|
                     'map'               : Address(0x00B1, size=1, mask=0x04),
 | 
						|
                },
 | 
						|
                'gt' : {
 | 
						|
                     'boss_key'          : Address(0x00B2, size=1, mask=0x01),
 | 
						|
                     'compass'           : Address(0x00B2, size=1, mask=0x02),
 | 
						|
                     'map'               : Address(0x00B2, size=1, mask=0x04),
 | 
						|
                },
 | 
						|
                'gtg' : {
 | 
						|
                     'boss_key'          : Address(0x00B3, size=1, mask=0x01),
 | 
						|
                     'compass'           : Address(0x00B3, size=1, mask=0x02),
 | 
						|
                     'map'               : Address(0x00B3, size=1, mask=0x04),
 | 
						|
                },
 | 
						|
                'fortress' : {
 | 
						|
                     'boss_key'          : Address(0x00B4, size=1, mask=0x01),
 | 
						|
                     'compass'           : Address(0x00B4, size=1, mask=0x02),
 | 
						|
                     'map'               : Address(0x00B4, size=1, mask=0x04),
 | 
						|
                },
 | 
						|
                'gc' : {
 | 
						|
                     'boss_key'          : Address(0x00B5, size=1, mask=0x01),
 | 
						|
                     'compass'           : Address(0x00B5, size=1, mask=0x02),
 | 
						|
                     'map'               : Address(0x00B5, size=1, mask=0x04),
 | 
						|
                },
 | 
						|
                'unused'                 : Address(size=6),
 | 
						|
            },
 | 
						|
            'keys' : {
 | 
						|
                'deku'                   : Address(size=1),
 | 
						|
                'dodongo'                : Address(size=1),
 | 
						|
                'jabu'                   : Address(size=1),
 | 
						|
                'forest'                 : Address(size=1),
 | 
						|
                'fire'                   : Address(size=1),
 | 
						|
                'water'                  : Address(size=1),
 | 
						|
                'spirit'                 : Address(size=1),
 | 
						|
                'shadow'                 : Address(size=1),
 | 
						|
                'botw'                   : Address(size=1),
 | 
						|
                'ice'                    : Address(size=1),
 | 
						|
                'gt'                     : Address(size=1),
 | 
						|
                'gtg'                    : Address(size=1),
 | 
						|
                'fortress'               : Address(size=1),
 | 
						|
                'gc'                     : Address(size=1),
 | 
						|
                'unused'                 : Address(size=5),
 | 
						|
            },
 | 
						|
            'defense_hearts'             : Address(size=1, max=20),
 | 
						|
            'gs_tokens'                  : Address(size=2, max=100),
 | 
						|
            'total_keys' : { # Upper half of unused word in own scene
 | 
						|
                'deku'                   : Address(0xD4 + 0x1C * 0x00 + 0x10, size=2),
 | 
						|
                'dodongo'                : Address(0xD4 + 0x1C * 0x01 + 0x10, size=2),
 | 
						|
                'jabu'                   : Address(0xD4 + 0x1C * 0x02 + 0x10, size=2),
 | 
						|
                'forest'                 : Address(0xD4 + 0x1C * 0x03 + 0x10, size=2),
 | 
						|
                'fire'                   : Address(0xD4 + 0x1C * 0x04 + 0x10, size=2),
 | 
						|
                'water'                  : Address(0xD4 + 0x1C * 0x05 + 0x10, size=2),
 | 
						|
                'spirit'                 : Address(0xD4 + 0x1C * 0x06 + 0x10, size=2),
 | 
						|
                'shadow'                 : Address(0xD4 + 0x1C * 0x07 + 0x10, size=2),
 | 
						|
                'botw'                   : Address(0xD4 + 0x1C * 0x08 + 0x10, size=2),
 | 
						|
                'ice'                    : Address(0xD4 + 0x1C * 0x09 + 0x10, size=2),
 | 
						|
                'gt'                     : Address(0xD4 + 0x1C * 0x0A + 0x10, size=2),
 | 
						|
                'gtg'                    : Address(0xD4 + 0x1C * 0x0B + 0x10, size=2),
 | 
						|
                'fortress'               : Address(0xD4 + 0x1C * 0x0C + 0x10, size=2),
 | 
						|
                'gc'                     : Address(0xD4 + 0x1C * 0x0D + 0x10, size=2),
 | 
						|
            },
 | 
						|
            'triforce_pieces'            : Address(0xD4 + 0x1C * 0x48 + 0x10, size=4), # Unused word in scene x48
 | 
						|
            'pending_freezes'            : Address(0xD4 + 0x1C * 0x49 + 0x10, size=4), # Unused word in scene x49
 | 
						|
        }
 | 
						|
 | 
						|
 | 
						|
    item_id_map = {
 | 
						|
        'none'                : 0xFF,
 | 
						|
        'stick'               : 0x00,
 | 
						|
        'nut'                 : 0x01,
 | 
						|
        'bomb'                : 0x02,
 | 
						|
        'bow'                 : 0x03,
 | 
						|
        'fire_arrow'          : 0x04,
 | 
						|
        'dins_fire'           : 0x05,
 | 
						|
        'slingshot'           : 0x06,
 | 
						|
        'fairy_ocarina'       : 0x07,
 | 
						|
        'ocarina_of_time'     : 0x08,
 | 
						|
        'bombchu'             : 0x09,
 | 
						|
        'hookshot'            : 0x0A,
 | 
						|
        'longshot'            : 0x0B,
 | 
						|
        'ice_arrow'           : 0x0C,
 | 
						|
        'farores_wind'        : 0x0D,
 | 
						|
        'boomerang'           : 0x0E,
 | 
						|
        'lens'                : 0x0F,
 | 
						|
        'beans'               : 0x10,
 | 
						|
        'hammer'              : 0x11,
 | 
						|
        'light_arrow'         : 0x12,
 | 
						|
        'nayrus_love'         : 0x13,
 | 
						|
        'bottle'              : 0x14,
 | 
						|
        'red_potion'          : 0x15,
 | 
						|
        'green_potion'        : 0x16,
 | 
						|
        'blue_potion'         : 0x17,
 | 
						|
        'fairy'               : 0x18,
 | 
						|
        'fish'                : 0x19,
 | 
						|
        'milk'                : 0x1A,
 | 
						|
        'letter'              : 0x1B,
 | 
						|
        'blue_fire'           : 0x1C,
 | 
						|
        'bug'                 : 0x1D,
 | 
						|
        'big_poe'             : 0x1E,
 | 
						|
        'half_milk'           : 0x1F,
 | 
						|
        'poe'                 : 0x20,
 | 
						|
        'weird_egg'           : 0x21,
 | 
						|
        'chicken'             : 0x22,
 | 
						|
        'zeldas_letter'       : 0x23,
 | 
						|
        'keaton_mask'         : 0x24,
 | 
						|
        'skull_mask'          : 0x25,
 | 
						|
        'spooky_mask'         : 0x26,
 | 
						|
        'bunny_hood'          : 0x27,
 | 
						|
        'goron_mask'          : 0x28,
 | 
						|
        'zora_mask'           : 0x29,
 | 
						|
        'gerudo_mask'         : 0x2A,
 | 
						|
        'mask_of_truth'       : 0x2B,
 | 
						|
        'sold_out'            : 0x2C,
 | 
						|
        'pocket_egg'          : 0x2D,
 | 
						|
        'pocket_cucco'        : 0x2E,
 | 
						|
        'cojiro'              : 0x2F,
 | 
						|
        'odd_mushroom'        : 0x30,
 | 
						|
        'odd_potion'          : 0x31,
 | 
						|
        'poachers_saw'        : 0x32,
 | 
						|
        'broken_sword'        : 0x33,
 | 
						|
        'prescription'        : 0x34,
 | 
						|
        'eyeball_frog'        : 0x35,
 | 
						|
        'eye_drops'           : 0x36,
 | 
						|
        'claim_check'         : 0x37,
 | 
						|
        'bow_fire_arrow'      : 0x38,
 | 
						|
        'bow_ice_arrow'       : 0x39,
 | 
						|
        'bow_light_arrow'     : 0x3A,
 | 
						|
        'kokiri_sword'        : 0x3B,
 | 
						|
        'master_sword'        : 0x3C,
 | 
						|
        'biggoron_sword'      : 0x3D,
 | 
						|
        'deku_shield'         : 0x3E,
 | 
						|
        'hylian_shield'       : 0x3F,
 | 
						|
        'mirror_shield'       : 0x40,
 | 
						|
        'kokiri_tunic'        : 0x41,
 | 
						|
        'goron_tunic'         : 0x42,
 | 
						|
        'zora_tunic'          : 0x43,
 | 
						|
        'kokiri_boots'        : 0x44,
 | 
						|
        'iron_boots'          : 0x45,
 | 
						|
        'hover_boots'         : 0x46,
 | 
						|
        'bullet_bag_30'       : 0x47,
 | 
						|
        'bullet_bag_40'       : 0x48,
 | 
						|
        'bullet_bag_50'       : 0x49,
 | 
						|
        'quiver_30'           : 0x4A,
 | 
						|
        'quiver_40'           : 0x4B,
 | 
						|
        'quiver_50'           : 0x4C,
 | 
						|
        'bomb_bag_20'         : 0x4D,
 | 
						|
        'bomb_bag_30'         : 0x4E,
 | 
						|
        'bomb_bag_40'         : 0x4F,
 | 
						|
        'gorons_bracelet'     : 0x40,
 | 
						|
        'silver_gauntlets'    : 0x41,
 | 
						|
        'golden_gauntlets'    : 0x42,
 | 
						|
        'silver_scale'        : 0x43,
 | 
						|
        'golden_scale'        : 0x44,
 | 
						|
        'broken_giants_knife' : 0x45,
 | 
						|
        'adults_wallet'       : 0x46,
 | 
						|
        'giants_wallet'       : 0x47,
 | 
						|
        'deku_seeds'          : 0x48,
 | 
						|
        'fishing_pole'        : 0x49,
 | 
						|
        'minuet'              : 0x4A,
 | 
						|
        'bolero'              : 0x4B,
 | 
						|
        'serenade'            : 0x4C,
 | 
						|
        'requiem'             : 0x4D,
 | 
						|
        'nocturne'            : 0x4E,
 | 
						|
        'prelude'             : 0x4F,
 | 
						|
        'zeldas_lullaby'      : 0x50,
 | 
						|
        'eponas_song'         : 0x51,
 | 
						|
        'sarias_song'         : 0x52,
 | 
						|
        'suns_song'           : 0x53,
 | 
						|
        'song_of_time'        : 0x54,
 | 
						|
        'song_of_storms'      : 0x55,
 | 
						|
        'forest_medallion'    : 0x56,
 | 
						|
        'fire_medallion'      : 0x57,
 | 
						|
        'water_medallion'     : 0x58,
 | 
						|
        'spirit_medallion'    : 0x59,
 | 
						|
        'shadow_medallion'    : 0x5A,
 | 
						|
        'light_medallion'     : 0x5B,
 | 
						|
        'kokiris_emerald'     : 0x5C,
 | 
						|
        'gorons_ruby'         : 0x5D,
 | 
						|
        'zoras_sapphire'      : 0x5E,
 | 
						|
        'stone_of_agony'      : 0x5F,
 | 
						|
        'gerudos_card'        : 0x60,
 | 
						|
        'gold_skulltula'      : 0x61,
 | 
						|
        'heart_container'     : 0x62,
 | 
						|
        'piece_of_heart'      : 0x63,
 | 
						|
        'boss_key'            : 0x64,
 | 
						|
        'compass'             : 0x65,
 | 
						|
        'dungeon_map'         : 0x66,
 | 
						|
        'small_key'           : 0x67,
 | 
						|
    }
 | 
						|
 | 
						|
 | 
						|
    slot_id_map = {
 | 
						|
        'stick'               : 0x00,
 | 
						|
        'nut'                 : 0x01,
 | 
						|
        'bomb'                : 0x02,
 | 
						|
        'bow'                 : 0x03,
 | 
						|
        'fire_arrow'          : 0x04,
 | 
						|
        'dins_fire'           : 0x05,
 | 
						|
        'slingshot'           : 0x06,
 | 
						|
        'ocarina'             : 0x07,
 | 
						|
        'bombchu'             : 0x08,
 | 
						|
        'hookshot'            : 0x09,
 | 
						|
        'ice_arrow'           : 0x0A,
 | 
						|
        'farores_wind'        : 0x0B,
 | 
						|
        'boomerang'           : 0x0C,
 | 
						|
        'lens'                : 0x0D,
 | 
						|
        'beans'               : 0x0E,
 | 
						|
        'hammer'              : 0x0F,
 | 
						|
        'light_arrow'         : 0x10,
 | 
						|
        'nayrus_love'         : 0x11,
 | 
						|
        'bottle_1'            : 0x12,
 | 
						|
        'bottle_2'            : 0x13,
 | 
						|
        'bottle_3'            : 0x14,
 | 
						|
        'bottle_4'            : 0x15,
 | 
						|
        'adult_trade'         : 0x16,
 | 
						|
        'child_trade'         : 0x17,
 | 
						|
    }
 | 
						|
 | 
						|
 | 
						|
    bottle_types = {
 | 
						|
        "Bottle"                   : 'bottle',
 | 
						|
        "Bottle with Red Potion"   : 'red_potion',
 | 
						|
        "Bottle with Green Potion" : 'green_potion',
 | 
						|
        "Bottle with Blue Potion"  : 'blue_potion',
 | 
						|
        "Bottle with Fairy"        : 'fairy',
 | 
						|
        "Bottle with Fish"         : 'fish',
 | 
						|
        "Bottle with Milk"         : 'milk',
 | 
						|
        "Rutos Letter"             : 'letter',
 | 
						|
        "Bottle with Blue Fire"    : 'blue_fire',
 | 
						|
        "Bottle with Bugs"         : 'bug',
 | 
						|
        "Bottle with Big Poe"      : 'big_poe',
 | 
						|
        "Bottle with Milk (Half)"  : 'half_milk',
 | 
						|
        "Bottle with Poe"          : 'poe',    
 | 
						|
    }
 | 
						|
 | 
						|
 | 
						|
    save_writes_table = {
 | 
						|
        "Deku Stick Capacity": {
 | 
						|
            'item_slot.stick'            : 'stick',
 | 
						|
            'upgrades.stick_upgrade'     : [2,3],
 | 
						|
        },
 | 
						|
        "Deku Stick": {
 | 
						|
            'item_slot.stick'            : 'stick',
 | 
						|
            'upgrades.stick_upgrade'     : 1,
 | 
						|
            'ammo.stick'                 : None,
 | 
						|
        },
 | 
						|
        "Deku Sticks": {
 | 
						|
            'item_slot.stick'            : 'stick',
 | 
						|
            'upgrades.stick_upgrade'     : 1,
 | 
						|
            'ammo.stick'                 : None,
 | 
						|
        },
 | 
						|
        "Deku Nut Capacity": {
 | 
						|
            'item_slot.nut'              : 'nut',
 | 
						|
            'upgrades.nut_upgrade'       : [2,3],
 | 
						|
        },
 | 
						|
        "Deku Nuts": {
 | 
						|
            'item_slot.nut'              : 'nut',
 | 
						|
            'upgrades.nut_upgrade'       : 1,
 | 
						|
            'ammo.nut'                   : None,
 | 
						|
        },
 | 
						|
        "Bomb Bag": {
 | 
						|
            'item_slot.bomb'             : 'bomb',
 | 
						|
            'upgrades.bomb_bag'          : None,
 | 
						|
        },
 | 
						|
        "Bombs" : {
 | 
						|
            'ammo.bomb'                  : None,
 | 
						|
        },
 | 
						|
        "Bombchus" : {
 | 
						|
            'item_slot.bombchu'          : 'bombchu',
 | 
						|
            'ammo.bombchu'               : None,
 | 
						|
        },
 | 
						|
        "Bow" : {
 | 
						|
            'item_slot.bow'              : 'bow',
 | 
						|
            'upgrades.quiver'            : None,
 | 
						|
        },
 | 
						|
        "Arrows" : {
 | 
						|
            'ammo.bow'                   : None,
 | 
						|
        },
 | 
						|
        "Slingshot"    : {
 | 
						|
            'item_slot.slingshot'        : 'slingshot',
 | 
						|
            'upgrades.bullet_bag'        : None,
 | 
						|
        },
 | 
						|
        "Deku Seeds" : {
 | 
						|
            'ammo.slingshot'             : None,
 | 
						|
        },
 | 
						|
        "Magic Bean" : {
 | 
						|
            'item_slot.beans'            : 'beans',
 | 
						|
            'ammo.beans'                 : None,
 | 
						|
            'magic_beans_sold'           : None,
 | 
						|
        },
 | 
						|
        "Fire Arrows"    : {'item_slot.fire_arrow'      : 'fire_arrow'},
 | 
						|
        "Ice Arrows"     : {'item_slot.ice_arrow'       : 'ice_arrow'},
 | 
						|
        "Light Arrows"   : {'item_slot.light_arrow'     : 'light_arrow'},
 | 
						|
        "Dins Fire"      : {'item_slot.dins_fire'       : 'dins_fire'},
 | 
						|
        "Farores Wind"   : {'item_slot.farores_wind'    : 'farores_wind'},
 | 
						|
        "Nayrus Love"    : {'item_slot.nayrus_love'     : 'nayrus_love'},
 | 
						|
        "Ocarina"        : {'item_slot.ocarina'         : ['fairy_ocarina', 'ocarina_of_time']},
 | 
						|
        "Progressive Hookshot" : {'item_slot.hookshot'  : ['hookshot', 'longshot']},
 | 
						|
        "Boomerang"      : {'item_slot.boomerang'       : 'boomerang'},
 | 
						|
        "Lens of Truth"  : {'item_slot.lens'            : 'lens'},
 | 
						|
        "Megaton Hammer"         : {'item_slot.hammer'          : 'hammer'},
 | 
						|
        "Pocket Egg"     : {'item_slot.adult_trade'     : 'pocket_egg'},
 | 
						|
        "Pocket Cucco"   : {'item_slot.adult_trade'     : 'pocket_cucco'},
 | 
						|
        "Cojiro"         : {'item_slot.adult_trade'     : 'cojiro'},
 | 
						|
        "Odd Mushroom"   : {'item_slot.adult_trade'     : 'odd_mushroom'},
 | 
						|
        "Poachers Saw"   : {'item_slot.adult_trade'     : 'poachers_saw'},
 | 
						|
        "Broken Sword"   : {'item_slot.adult_trade'     : 'broken_sword'},
 | 
						|
        "Prescription"   : {'item_slot.adult_trade'     : 'prescription'},
 | 
						|
        "Eyeball Frog"   : {'item_slot.adult_trade'     : 'eyeball_frog'},
 | 
						|
        "Eyedrops"       : {'item_slot.adult_trade'     : 'eye_drops'},
 | 
						|
        "Claim Check"    : {'item_slot.adult_trade'     : 'claim_check'},
 | 
						|
        "Weird Egg"      : {'item_slot.child_trade'     : 'weird_egg'},
 | 
						|
        "Chicken"        : {'item_slot.child_trade'     : 'chicken'},
 | 
						|
        "Zeldas Letter"  : {'item_slot.child_trade'     : 'zeldas_letter'},
 | 
						|
        "Goron Tunic"    : {'equip_items.goron_tunic'   : True},
 | 
						|
        "Zora Tunic"     : {'equip_items.zora_tunic'    : True},
 | 
						|
        "Iron Boots"     : {'equip_items.iron_boots'    : True},
 | 
						|
        "Hover Boots"    : {'equip_items.hover_boots'   : True},
 | 
						|
        "Deku Shield"    : {'equip_items.deku_shield'   : True},
 | 
						|
        "Hylian Shield"  : {'equip_items.hylian_shield' : True},
 | 
						|
        "Mirror Shield"  : {'equip_items.mirror_shield' : True},
 | 
						|
        "Kokiri Sword"   : {'equip_items.kokiri_sword'  : True},
 | 
						|
        "Master Sword"   : {'equip_items.master_sword'  : True},
 | 
						|
        "Giants Knife" : {
 | 
						|
            'equip_items.biggoron_sword' : True,
 | 
						|
            'bgs_hits_left'              : 8,
 | 
						|
        },
 | 
						|
        "Biggoron Sword" : {
 | 
						|
            'equip_items.biggoron_sword' : True,
 | 
						|
            'bgs_flag'                   : True,
 | 
						|
            'bgs_hits_left'              : 1,
 | 
						|
        },
 | 
						|
        "Gerudo Membership Card" : {'quest.gerudos_card'             : True},
 | 
						|
        "Stone of Agony"         : {'quest.stone_of_agony'           : True},
 | 
						|
        "Zeldas Lullaby"         : {'quest.songs.zeldas_lullaby'     : True},
 | 
						|
        "Eponas Song"            : {'quest.songs.eponas_song'        : True},
 | 
						|
        "Sarias Song"            : {'quest.songs.sarias_song'        : True},
 | 
						|
        "Suns Song"              : {'quest.songs.suns_song'          : True},
 | 
						|
        "Song of Time"           : {'quest.songs.song_of_time'       : True},
 | 
						|
        "Song of Storms"         : {'quest.songs.song_of_storms'     : True},
 | 
						|
        "Minuet of Forest"       : {'quest.songs.minuet_of_forest'   : True},
 | 
						|
        "Bolero of Fire"         : {'quest.songs.bolero_of_fire'     : True},
 | 
						|
        "Serenade of Water"      : {'quest.songs.serenade_of_water'  : True},
 | 
						|
        "Requiem of Spirit"      : {'quest.songs.requiem_of_spirit'  : True},
 | 
						|
        "Nocturne of Shadow"     : {'quest.songs.nocturne_of_shadow' : True},
 | 
						|
        "Prelude of Light"       : {'quest.songs.prelude_of_light'   : True},
 | 
						|
        "Kokiri Emerald"         : {'quest.stones.kokiris_emerald'   : True},
 | 
						|
        "Goron Ruby"             : {'quest.stones.gorons_ruby'       : True},
 | 
						|
        "Zora Sapphire"          : {'quest.stones.zoras_sapphire'    : True},
 | 
						|
        "Light Medallion"        : {'quest.medallions.light'         : True},
 | 
						|
        "Forest Medallion"       : {'quest.medallions.forest'        : True},
 | 
						|
        "Fire Medallion"         : {'quest.medallions.fire'          : True},
 | 
						|
        "Water Medallion"        : {'quest.medallions.water'         : True},
 | 
						|
        "Spirit Medallion"       : {'quest.medallions.spirit'        : True},
 | 
						|
        "Shadow Medallion"       : {'quest.medallions.shadow'        : True},
 | 
						|
        "Progressive Strength Upgrade" : {'upgrades.strength_upgrade' : None},
 | 
						|
        "Progressive Scale"            : {'upgrades.diving_upgrade'   : None},
 | 
						|
        "Progressive Wallet"           : {'upgrades.wallet'           : None},
 | 
						|
        "Gold Skulltula Token" : {
 | 
						|
            'quest.gold_skulltula'  : True,
 | 
						|
            'gs_tokens'             : None,
 | 
						|
        },
 | 
						|
        "Double Defense" : {
 | 
						|
            'double_defense'        : True,
 | 
						|
            'defense_hearts'        : 20,
 | 
						|
        },
 | 
						|
        "Magic Meter" : {
 | 
						|
            'magic_acquired'        : True,
 | 
						|
            'magic'                 : [0x30, 0x60],
 | 
						|
            'magic_level'           : None,
 | 
						|
            'double_magic'          : [False, True],
 | 
						|
        },
 | 
						|
        "Rupee"                     : {'rupees' : None},
 | 
						|
        "Rupee (Treasure Chest Game)" : {'rupees' : None},
 | 
						|
        "Rupees"                    : {'rupees' : None},
 | 
						|
        "Magic Bean Pack" : {
 | 
						|
            'item_slot.beans'       : 'beans',
 | 
						|
            'ammo.beans'            : 10
 | 
						|
        },
 | 
						|
        "Ice Trap"                  : {'pending_freezes': None},
 | 
						|
        "Triforce Piece"            : {'triforce_pieces': None},
 | 
						|
        "Boss Key (Forest Temple)"                : {'dungeon_items.forest.boss_key': True},
 | 
						|
        "Boss Key (Fire Temple)"                  : {'dungeon_items.fire.boss_key': True},
 | 
						|
        "Boss Key (Water Temple)"                 : {'dungeon_items.water.boss_key': True},
 | 
						|
        "Boss Key (Spirit Temple)"                : {'dungeon_items.spirit.boss_key': True},
 | 
						|
        "Boss Key (Shadow Temple)"                : {'dungeon_items.shadow.boss_key': True},
 | 
						|
        "Boss Key (Ganons Castle)"                : {'dungeon_items.gt.boss_key': True},
 | 
						|
        "Compass (Deku Tree)"                     : {'dungeon_items.deku.compass': True},
 | 
						|
        "Compass (Dodongos Cavern)"               : {'dungeon_items.dodongo.compass': True},
 | 
						|
        "Compass (Jabu Jabus Belly)"              : {'dungeon_items.jabu.compass': True},
 | 
						|
        "Compass (Forest Temple)"                 : {'dungeon_items.forest.compass': True},
 | 
						|
        "Compass (Fire Temple)"                   : {'dungeon_items.fire.compass': True},
 | 
						|
        "Compass (Water Temple)"                  : {'dungeon_items.water.compass': True},
 | 
						|
        "Compass (Spirit Temple)"                 : {'dungeon_items.spirit.compass': True},
 | 
						|
        "Compass (Shadow Temple)"                 : {'dungeon_items.shadow.compass': True},
 | 
						|
        "Compass (Bottom of the Well)"            : {'dungeon_items.botw.compass': True},
 | 
						|
        "Compass (Ice Cavern)"                    : {'dungeon_items.ice.compass': True},
 | 
						|
        "Map (Deku Tree)"                         : {'dungeon_items.deku.map': True},
 | 
						|
        "Map (Dodongos Cavern)"                   : {'dungeon_items.dodongo.map': True},
 | 
						|
        "Map (Jabu Jabus Belly)"                  : {'dungeon_items.jabu.map': True},
 | 
						|
        "Map (Forest Temple)"                     : {'dungeon_items.forest.map': True},
 | 
						|
        "Map (Fire Temple)"                       : {'dungeon_items.fire.map': True},
 | 
						|
        "Map (Water Temple)"                      : {'dungeon_items.water.map': True},
 | 
						|
        "Map (Spirit Temple)"                     : {'dungeon_items.spirit.map': True},
 | 
						|
        "Map (Shadow Temple)"                     : {'dungeon_items.shadow.map': True},
 | 
						|
        "Map (Bottom of the Well)"                : {'dungeon_items.botw.map': True},
 | 
						|
        "Map (Ice Cavern)"                        : {'dungeon_items.ice.map': True},
 | 
						|
        "Small Key (Forest Temple)"               : {
 | 
						|
            'keys.forest': None,
 | 
						|
            'total_keys.forest': None,
 | 
						|
        },
 | 
						|
        "Small Key (Fire Temple)"                 : {
 | 
						|
            'keys.fire': None,
 | 
						|
            'total_keys.fire': None,
 | 
						|
        },
 | 
						|
        "Small Key (Water Temple)"                : {
 | 
						|
            'keys.water': None,
 | 
						|
            'total_keys.water': None,
 | 
						|
        },
 | 
						|
        "Small Key (Spirit Temple)"               : {
 | 
						|
            'keys.spirit': None,
 | 
						|
            'total_keys.spirit': None,
 | 
						|
        },
 | 
						|
        "Small Key (Shadow Temple)"               : {
 | 
						|
            'keys.shadow': None,
 | 
						|
            'total_keys.shadow': None,
 | 
						|
        },
 | 
						|
        "Small Key (Bottom of the Well)"          : {
 | 
						|
            'keys.botw': None,
 | 
						|
            'total_keys.botw': None,
 | 
						|
        },
 | 
						|
        "Small Key (Gerudo Training Ground)"      : {
 | 
						|
            'keys.gtg': None,
 | 
						|
            'total_keys.gtg': None,
 | 
						|
        },
 | 
						|
        "Small Key (Thieves Hideout)"             : {
 | 
						|
            'keys.fortress': None,
 | 
						|
            'total_keys.fortress': None,
 | 
						|
        },
 | 
						|
        "Small Key (Ganons Castle)"               : {
 | 
						|
            'keys.gc': None,
 | 
						|
            'total_keys.gc': None,
 | 
						|
        },
 | 
						|
        #HACK: these counts aren't used since exact counts based on whether the dungeon is MQ are defined above,
 | 
						|
        # but the entries need to be there for key rings to be valid starting items
 | 
						|
        "Small Key Ring (Forest Temple)"          : {
 | 
						|
            'keys.forest': 6,
 | 
						|
            'total_keys.forest': 6,
 | 
						|
        },
 | 
						|
        "Small Key Ring (Fire Temple)"            : {
 | 
						|
            'keys.fire': 8,
 | 
						|
            'total_keys.fire': 8,
 | 
						|
        },
 | 
						|
        "Small Key Ring (Water Temple)"           : {
 | 
						|
            'keys.water': 6,
 | 
						|
            'total_keys.water': 6,
 | 
						|
        },
 | 
						|
        "Small Key Ring (Spirit Temple)"          : {
 | 
						|
            'keys.spirit': 7,
 | 
						|
            'total_keys.spirit': 7,
 | 
						|
        },
 | 
						|
        "Small Key Ring (Shadow Temple)"          : {
 | 
						|
            'keys.shadow': 6,
 | 
						|
            'total_keys.shadow': 6,
 | 
						|
        },
 | 
						|
        "Small Key Ring (Bottom of the Well)"     : {
 | 
						|
            'keys.botw': 3,
 | 
						|
            'total_keys.botw': 3,
 | 
						|
        },
 | 
						|
        "Small Key Ring (Gerudo Training Ground)" : {
 | 
						|
            'keys.gtg': 9,
 | 
						|
            'total_keys.gtg': 9,
 | 
						|
        },
 | 
						|
        "Small Key Ring (Thieves Hideout)"        : {
 | 
						|
            'keys.fortress': 4,
 | 
						|
            'total_keys.fortress': 4,
 | 
						|
        },
 | 
						|
        "Small Key Ring (Ganons Castle)"          : {
 | 
						|
            'keys.gc': 3,
 | 
						|
            'total_keys.gc': 3,
 | 
						|
        },
 | 
						|
    }
 | 
						|
 | 
						|
 | 
						|
    equipable_items = {
 | 
						|
        'equips_adult' : {
 | 
						|
            'items': [
 | 
						|
                'hookshot',
 | 
						|
                'hammer',
 | 
						|
                'bomb',
 | 
						|
                'bow',
 | 
						|
                'nut',
 | 
						|
                'lens',
 | 
						|
                'farores_wind',
 | 
						|
                'dins_fire',
 | 
						|
                'bombchu',
 | 
						|
                'nayrus_love',
 | 
						|
                'adult_trade',
 | 
						|
                'bottle_1',
 | 
						|
                'bottle_2',
 | 
						|
                'bottle_3',
 | 
						|
                'bottle_4',
 | 
						|
            ],
 | 
						|
            'sword' : [
 | 
						|
                'biggoron_sword',
 | 
						|
                'master_sword',
 | 
						|
            ],
 | 
						|
            'shield' : [
 | 
						|
                'mirror_shield',
 | 
						|
                'hylian_shield',
 | 
						|
            ],
 | 
						|
            'tunic' : [
 | 
						|
                'goron_tunic',
 | 
						|
                'zora_tunic',
 | 
						|
                'kokiri_tunic',
 | 
						|
            ],
 | 
						|
            'boots' : [
 | 
						|
                'kokiri_boots'
 | 
						|
            ],
 | 
						|
        },
 | 
						|
        'equips_child' : {
 | 
						|
            'items': [
 | 
						|
                'bomb',
 | 
						|
                'boomerang',
 | 
						|
                'slingshot',
 | 
						|
                'stick',
 | 
						|
                'nut',
 | 
						|
                'lens',
 | 
						|
                'farores_wind',
 | 
						|
                'dins_fire',
 | 
						|
                'bombchu',
 | 
						|
                'nayrus_love',
 | 
						|
                'beans',
 | 
						|
                'bottle_1',
 | 
						|
                'bottle_2',
 | 
						|
                'bottle_3',
 | 
						|
                'bottle_4',
 | 
						|
            ],
 | 
						|
            'sword' : [
 | 
						|
                'kokiri_sword',
 | 
						|
            ],
 | 
						|
            'shield' : [
 | 
						|
                'deku_shield',
 | 
						|
                'hylian_shield',
 | 
						|
            ],
 | 
						|
            'tunic' : [
 | 
						|
                'kokiri_tunic',
 | 
						|
            ],
 | 
						|
            'boots' : [
 | 
						|
                'kokiri_boots',
 | 
						|
            ],
 | 
						|
        }
 | 
						|
    }
 |