from itertools import chain 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 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) def give_raw_item(self, item): if item.endswith(')'): item_base, count = item[:-1].split(' (', 1) if count.isdigit(): return self.give_item(item_base, count=int(count)) return self.give_item(item) def give_item(self, item, count=1): 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() elif item in SaveContext.save_writes_table: for address, value in SaveContext.save_writes_table[item].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): self.give_item("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_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), 'triforce_pieces' : Address(0xD4 + 0x1C * 0x48 + 0x10, size=4), # Unused word in scene x48 } 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_gorons_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 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_knife'}, "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}, "Rupees" : {'rupees' : None}, "Magic Bean Pack" : { 'item_slot.beans' : 'beans', 'ammo.beans' : 10 }, "Triforce Piece" : {'triforce_pieces': None}, } giveable_items = set(chain(save_writes_table.keys(), bottle_types.keys(), ["Piece of Heart", "Piece of Heart (Treasure Chest Game)", "Heart Container", "Rupee (1)"])) 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', 'child_trade', 'bottle_1', 'bottle_2', 'bottle_3', 'bottle_4', ], 'sword' : [ 'kokiri_sword', ], 'shield' : [ 'deku_shield', 'hylian_shield', ], 'tunic' : [ 'kokiri_tunic', ], 'boots' : [ 'kokiri_boots', ], } }