Archipelago/worlds/oot/SaveContext.py

1001 lines
43 KiB
Python

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',
],
}
}