Archipelago/worlds/oot/Music.py

485 lines
19 KiB
Python
Raw Normal View History

Ocarina of Time (#64) * first commit (not including OoT data files yet) * added some basic options * rule parser works now at least * make sure to commit everything this time * temporary change to BaseClasses for oot * overworld location graph builds mostly correctly * adding oot data files * commenting out world options until later since they only existed to make the RuleParser work * conversion functions between AP ids and OOT ids * world graph outputs * set scrub prices * itempool generates, entrances connected, way too many options added * fixed set_rules and set_shop_rules * temp baseclasses changes * Reaches the fill step now, old event-based system retained in case the new way breaks * Song placements and misc fixes everywhere * temporary changes to make oot work * changed root exits for AP fill framework * prevent infinite recursion due to OoT sharing usage of the address field * age reachability works hopefully, songs are broken again * working spoiler log generation on beatable-only * Logic tricks implemented * need this for logic tricks * fixed map/compass being placed on Serenade location * kill unreachable events before filling the world * add a bunch of utility functions to prepare for rom patching * move OptionList into generic options * fixed some silly bugs with OptionList * properly seed all random behavior (so far) * ROM generation working * fix hints trying to get alttp dungeon hint texts * continue fixing hints * add oot to network data package * change item and location IDs to 66000 and 67000 range respectively * push removed items to precollected items * fixed various issues with cross-contamination with multiple world generation * reenable glitched logic (hopefully) * glitched world files age-check fix * cleaned up some get_locations calls * added token shuffle and scrub shuffle, modified some options slightly to make the parsing work * reenable MQ dungeons * fix forest mq exception * made targeting style an option for now, will be cosmetic later * reminder to move targeting to cosmetics * some oot option maintenance * enabled starting time of day * fixed issue breaking shop slots in multiworld generation * added "off" option for text shuffle and hints * shopsanity functionality restored * change patch file extension * remove unnecessary utility functions + imports * update MIT license * change option to "patch_uncompressed_rom" instead of "compress_rom" * compliance with new AutoWorld systems * Kill only internal events, remove non-internal big poe event in code * re-add the big poe event and handle it correctly * remove extra method in Range option * fix typo * Starting items, starting with consumables option * do not remove nonexistent item * move set_shop_rules to after shop items are placed * some cleanup * add retries for song placement * flagged Skull Mask and Mask of Truth as advancement items * update OoT to use LogicMixin * Fixed trying to assign starting items from the wrong players * fixed song retry step * improved option handling, comments, and starting item replacements * DefaultOnToggle writes Yes or No to spoiler * enable compression of output if Compress executable is present * clean up compression * check whether (de)compressor exists before running the process * allow specification of rom path in host.yaml * check if decompressed file already exists before decompressing again * fix triforce hunt generation * rename all the oot state functions with prefix * OoT: mark triforce pieces as completion goal for triforce hunt * added overworld and any-dungeon shuffle for dungeon items * Hide most unshuffled locations and events from the list of locations in spoiler * build oot option ranges with a generic function instead of defining each separately * move oot output-type control to host.yaml instead of individual yamls * implement dungeon song shuffle * minor improvements to overworld dungeon item shuffle * remove random ice trap names in shops, mostly to avoid maintaining a massive censor list * always output patch file to folder, remove option to generate ROM in preparation for removal * re-add the fix for infinite recursion due to not being light or dark world * change AP-sendable to Ocarina of Time model, since the triforce piece has some extra code apparently * oot: remove item_names and location_names * oot: minor fixes * oot: comment out ROM patching * oot: only add CollectionState objects on creation if actually needed * main entrance shuffle method and entrances-based rules * fix entrances based rules * disable master quest and big poe count options for client compatibility * use get_player_name instead of get_player_names * fix OptionList * fix oot options for new option system * new coop section in oot rom: expand player names to 16 bytes, write AP_PLAYER_NAME at end of PLAYER_NAMES * fill AP player name in oot rom with 0 instead of 0xDF * encode player name with ASCII for fixed-width * revert oot player name array to 8 bytes per name * remove Pierre location if fast scarecrow is on * check player name length * "free_scarecrow" not "fast_scarecrow" * OoT locations now properly store the AP ID instead of the oot internal ID * oot __version__ updates in lockstep with AP version * pull in unmodified oot cosmetic files * also grab JSONDump since it's needed apparently * gather extra needed methods, modify imports * delete cosmetics log, replace all instances of SettingsList with OOTWorld * cosmetic options working, except for sound effects (due to ear-safe issues) * SFX, Music, and Fanfare randomization reenabled * move OoT data files into the worlds folder * move Compress and Decompress into oot data folder * Replace get_all_state with custom method to avoid the cache * OoT ROM: increment item counter before setting incoming item/player values to 0, preventing desync issues * set data_version to 0 * make Kokiri Sword shuffle off by default * reenable "Random Choice" for various cosmetic options * kill Ruto's Letter turnin if open fountain also fix for shopsanity * place Buy Goron/Zora Tunic first in shop shuffle * make ice traps appear as other items instead of breaking generation * managed to break ice traps on non-major-only * only handle ice traps if they are on * fix shopsanity for non-oot games, and write player name instead of player number * light arrows hint uses player name instead of player number * Reenable "skip child zelda" option * fix entrances_based_rules * fix ganondorf hint if starting with light arrows * fix dungeonitem shuffle and shopsanity interaction * remove has_all_of, has_any_of, count_of in BaseClasses, replace usage with has_all, has_any, has_group * force local giveable item on ZL if skip_child_zelda and shuffle_song_items is any * keep bosses and bombchu bowling chus out of data package * revert workaround for infinite recursion and fix it properly * fix shared shop id caches during patching process * fix shop text box overflows, as much as possible * add default oot host.yaml option * add .apz5, .n64, .z64 to gitignore * Properly document and name all (functioning) OOT options * clean up some imports * remove unnecessary files from oot's data * fix typo in gitignore * readd the Compress and Decompress utilities, since they are needed for generation * cleanup of imports and some minor optimizations * increase shop offset for item IDs to 0xCB * remove shop item AP ids entirely * prevent triforce pieces for other players from being received by yourself * add "excluded" property to Location * Hint system adapted and reenabled; hints still unseeded * make hints deterministic with lists instead of sets * do not allow hints to point to Light Arrows on non-vanilla bridge * foreign locations hint as their full name in OoT rather than their region * checkedLocations now stores hint names by player ID, so that the same location in different worlds can have hints associated * consolidate versioning in Utils * ice traps appear as major items rather than any progression item * set prescription and claim check as defaults for adult trade item settings * add oot options to playerSettings * allow case-insensitive logic tricks in yaml * fix oot shopsanity option formatting * Write OoT override info even if local item, enabling local checks to show up immediately in the client * implement CollectionState.can_live_dmg for oot glitched logic * filter item names for invalid characters when patching shops * make ice traps appear according to the settings of the world they are shuffled into, rather than the original world * set hidden-spoiler items and locations with Shop items to events * make GF carpenters, Gerudo Card, Malon, ZL, and Impa events if the relevant settings are enabled, preventing them from appearing in the client on game start * Fix oot Glitched and No Logic generation * fix indenting * Greatly reduce displayed cosmetic options * Change oot data version to 1 * add apz5 distribution to webhost * print player name if an ALttP dungeon contains a good item for OoT world * delete unneeded commented code * remove OcarinaSongs import to satisfy lint
2021-09-02 12:35:05 +00:00
#Much of this is heavily inspired from and/or based on az64's / Deathbasket's MM randomizer
import random
import os
from .Utils import compare_version, data_path
# Format: (Title, Sequence ID)
bgm_sequence_ids = [
("Hyrule Field", 0x02),
("Dodongos Cavern", 0x18),
("Kakariko Adult", 0x19),
("Battle", 0x1A),
("Boss Battle", 0x1B),
("Inside Deku Tree", 0x1C),
("Market", 0x1D),
("Title Theme", 0x1E),
("House", 0x1F),
("Jabu Jabu", 0x26),
("Kakariko Child", 0x27),
("Fairy Fountain", 0x28),
("Zelda Theme", 0x29),
("Fire Temple", 0x2A),
("Forest Temple", 0x2C),
("Castle Courtyard", 0x2D),
("Ganondorf Theme", 0x2E),
("Lon Lon Ranch", 0x2F),
("Goron City", 0x30),
("Miniboss Battle", 0x38),
("Temple of Time", 0x3A),
("Kokiri Forest", 0x3C),
("Lost Woods", 0x3E),
("Spirit Temple", 0x3F),
("Horse Race", 0x40),
("Ingo Theme", 0x42),
("Fairy Flying", 0x4A),
("Deku Tree", 0x4B),
("Windmill Hut", 0x4C),
("Shooting Gallery", 0x4E),
("Sheik Theme", 0x4F),
("Zoras Domain", 0x50),
("Shop", 0x55),
("Chamber of the Sages", 0x56),
("Ice Cavern", 0x58),
("Kaepora Gaebora", 0x5A),
("Shadow Temple", 0x5B),
("Water Temple", 0x5C),
("Gerudo Valley", 0x5F),
("Potion Shop", 0x60),
("Kotake and Koume", 0x61),
("Castle Escape", 0x62),
("Castle Underground", 0x63),
("Ganondorf Battle", 0x64),
("Ganon Battle", 0x65),
("Fire Boss", 0x6B),
("Mini-game", 0x6C)
]
fanfare_sequence_ids = [
("Game Over", 0x20),
("Boss Defeated", 0x21),
("Item Get", 0x22),
("Ganondorf Appears", 0x23),
("Heart Container Get", 0x24),
("Treasure Chest", 0x2B),
("Spirit Stone Get", 0x32),
("Heart Piece Get", 0x39),
("Escape from Ranch", 0x3B),
("Learn Song", 0x3D),
("Epona Race Goal", 0x41),
("Medallion Get", 0x43),
("Zelda Turns Around", 0x51),
("Master Sword", 0x53),
("Door of Time", 0x59)
]
ocarina_sequence_ids = [
("Prelude of Light", 0x25),
("Bolero of Fire", 0x33),
("Minuet of Forest", 0x34),
("Serenade of Water", 0x35),
("Requiem of Spirit", 0x36),
("Nocturne of Shadow", 0x37),
("Saria's Song", 0x44),
("Epona's Song", 0x45),
("Zelda's Lullaby", 0x46),
("Sun's Song", 0x47),
("Song of Time", 0x48),
("Song of Storms", 0x49)
]
# Represents the information associated with a sequence, aside from the sequence data itself
class TableEntry(object):
def __init__(self, name, cosmetic_name, type = 0x0202, instrument_set = 0x03, replaces = -1, vanilla_id = -1):
self.name = name
self.cosmetic_name = cosmetic_name
self.replaces = replaces
self.vanilla_id = vanilla_id
self.type = type
self.instrument_set = instrument_set
def copy(self):
copy = TableEntry(self.name, self.cosmetic_name, self.type, self.instrument_set, self.replaces, self.vanilla_id)
return copy
# Represents actual sequence data, along with metadata for the sequence data block
class Sequence(object):
def __init__(self):
self.address = -1
self.size = -1
self.data = []
def process_sequences(rom, sequences, target_sequences, disabled_source_sequences, disabled_target_sequences, ids, seq_type = 'bgm'):
# Process vanilla music data
for bgm in ids:
# Get sequence metadata
name = bgm[0]
cosmetic_name = name
type = rom.read_int16(0xB89AE8 + (bgm[1] * 0x10))
instrument_set = rom.read_byte(0xB89911 + 0xDD + (bgm[1] * 2))
id = bgm[1]
# Create new sequences
seq = TableEntry(name, cosmetic_name, type, instrument_set, vanilla_id = id)
target = TableEntry(name, cosmetic_name, type, instrument_set, replaces = id)
# Special handling for file select/fairy fountain
if seq.vanilla_id != 0x57 and cosmetic_name not in disabled_source_sequences:
sequences.append(seq)
if cosmetic_name not in disabled_target_sequences:
target_sequences.append(target)
# If present, load the file containing custom music to exclude
try:
with open(os.path.join(data_path(), u'custom_music_exclusion.txt')) as excl_in:
seq_exclusion_list = excl_in.readlines()
seq_exclusion_list = [seq.rstrip() for seq in seq_exclusion_list if seq[0] != '#']
seq_exclusion_list = [seq for seq in seq_exclusion_list if seq.endswith('.meta')]
except FileNotFoundError:
seq_exclusion_list = []
# Process music data in data/Music/
# Each sequence requires a valid .seq sequence file and a .meta metadata file
# Current .meta format: Cosmetic Name\nInstrument Set\nPool
for dirpath, _, filenames in os.walk(u'./data/Music', followlinks=True):
for fname in filenames:
# Skip if included in exclusion file
if fname in seq_exclusion_list:
continue
# Find meta file and check if corresponding seq file exists
if fname.endswith('.meta') and os.path.isfile(os.path.join(dirpath, fname.split('.')[0] + '.seq')):
# Read meta info
try:
with open(os.path.join(dirpath, fname), 'r') as stream:
lines = stream.readlines()
# Strip newline(s)
lines = [line.rstrip() for line in lines]
except FileNotFoundError as ex:
raise FileNotFoundError('No meta file for: "' + fname + '". This should never happen')
# Create new sequence, checking third line for correct type
if (len(lines) > 2 and (lines[2].lower() == seq_type.lower() or lines[2] == '')) or (len(lines) <= 2 and seq_type == 'bgm'):
seq = TableEntry(os.path.join(dirpath, fname.split('.')[0]), lines[0], instrument_set = int(lines[1], 16))
if seq.instrument_set < 0x00 or seq.instrument_set > 0x25:
raise Exception('Sequence instrument must be in range [0x00, 0x25]')
if seq.cosmetic_name not in disabled_source_sequences:
sequences.append(seq)
return sequences, target_sequences
def shuffle_music(sequences, target_sequences, music_mapping, log):
sequence_dict = {}
sequence_ids = []
for sequence in sequences:
if sequence.cosmetic_name == "None":
raise Exception('Sequences should not be named "None" as that is used for disabled music. Sequence with improper name: %s' % sequence.name)
if sequence.cosmetic_name in sequence_dict:
raise Exception('Sequence names should be unique. Duplicate sequence name: %s' % sequence.cosmetic_name)
sequence_dict[sequence.cosmetic_name] = sequence
if sequence.cosmetic_name not in music_mapping.values():
sequence_ids.append(sequence.cosmetic_name)
# Shuffle the sequences
if len(sequences) < len(target_sequences):
raise Exception(f"Not enough custom music/fanfares ({len(sequences)}) to omit base Ocarina of Time sequences ({len(target_sequences)}).")
random.shuffle(sequence_ids)
sequences = []
for target_sequence in target_sequences:
sequence = sequence_dict[sequence_ids.pop()].copy() if target_sequence.cosmetic_name not in music_mapping \
else ("None", 0x0) if music_mapping[target_sequence.cosmetic_name] == "None" \
else sequence_dict[music_mapping[target_sequence.cosmetic_name]].copy()
sequences.append(sequence)
sequence.replaces = target_sequence.replaces
log[target_sequence.cosmetic_name] = sequence.cosmetic_name
return sequences, log
def rebuild_sequences(rom, sequences):
# List of sequences (actual sequence data objects) containing the vanilla sequence data
old_sequences = []
for i in range(0x6E):
# Create new sequence object, an entry for the audio sequence
entry = Sequence()
# Get the address for the entry's pointer table entry
entry_address = 0xB89AE0 + (i * 0x10)
# Extract the info from the pointer table entry
entry.address = rom.read_int32(entry_address)
entry.size = rom.read_int32(entry_address + 0x04)
# If size > 0, read the sequence data from the rom into the sequence object
if entry.size > 0:
entry.data = rom.read_bytes(entry.address + 0x029DE0, entry.size)
else:
s = [seq for seq in sequences if seq.replaces == i]
if s != [] and entry.address > 0 and entry.address < 128:
s = s.pop()
if s.replaces != 0x28:
s.replaces = entry.address
else:
# Special handling for file select/fairy fountain
entry.data = old_sequences[0x57].data
entry.size = old_sequences[0x57].size
old_sequences.append(entry)
# List of sequences containing the new sequence data
new_sequences = []
address = 0
# Byte array to hold the data for the whole audio sequence
new_audio_sequence = []
for i in range(0x6E):
new_entry = Sequence()
# If sequence size is 0, the address doesn't matter and it doesn't effect the current address
if old_sequences[i].size == 0:
new_entry.address = old_sequences[i].address
# Continue from the end of the new sequence table
else:
new_entry.address = address
s = [seq for seq in sequences if seq.replaces == i]
if s != []:
assert len(s) == 1
s = s.pop()
# If we are using a vanilla sequence, get its data from old_sequences
if s.vanilla_id != -1:
new_entry.size = old_sequences[s.vanilla_id].size
new_entry.data = old_sequences[s.vanilla_id].data
else:
# Read sequence info
try:
with open(s.name + '.seq', 'rb') as stream:
new_entry.data = bytearray(stream.read())
new_entry.size = len(new_entry.data)
if new_entry.size <= 0x10:
raise Exception('Invalid sequence file "' + s.name + '.seq"')
new_entry.data[1] = 0x20
except FileNotFoundError as ex:
raise FileNotFoundError('No sequence file for: "' + s.name + '"')
else:
new_entry.size = old_sequences[i].size
new_entry.data = old_sequences[i].data
new_sequences.append(new_entry)
# Concatenate the full audio sequence and the new sequence data
if new_entry.data != [] and new_entry.size > 0:
# Align sequences to 0x10
if new_entry.size % 0x10 != 0:
new_entry.data.extend(bytearray(0x10 - (new_entry.size % 0x10)))
new_entry.size += 0x10 - (new_entry.size % 0x10)
new_audio_sequence.extend(new_entry.data)
# Increment the current address by the size of the new sequence
address += new_entry.size
# Check if the new audio sequence is larger than the vanilla one
if address > 0x04F690:
# Zero out the old audio sequence
rom.buffer[0x029DE0 : 0x029DE0 + 0x04F690] = [0] * 0x04F690
# Append new audio sequence
new_address = rom.free_space()
rom.write_bytes(new_address, new_audio_sequence)
#Update dmatable
rom.update_dmadata_record(0x029DE0, new_address, new_address + address)
else:
# Write new audio sequence file
rom.write_bytes(0x029DE0, new_audio_sequence)
# Update pointer table
for i in range(0x6E):
rom.write_int32(0xB89AE0 + (i * 0x10), new_sequences[i].address)
rom.write_int32(0xB89AE0 + (i * 0x10) + 0x04, new_sequences[i].size)
s = [seq for seq in sequences if seq.replaces == i]
if s != []:
assert len(s) == 1
s = s.pop()
rom.write_int16(0xB89AE0 + (i * 0x10) + 0x08, s.type)
# Update instrument sets
for i in range(0x6E):
base = 0xB89911 + 0xDD + (i * 2)
j = -1
if new_sequences[i].size == 0:
try:
j = [seq for seq in sequences if seq.replaces == new_sequences[i].address].pop()
except:
j = -1
else:
try:
j = [seq for seq in sequences if seq.replaces == i].pop()
except:
j = -1
if j != -1:
rom.write_byte(base, j.instrument_set)
def shuffle_pointers_table(rom, ids, music_mapping, log):
# Read in all the Music data
bgm_data = {}
bgm_ids = []
for bgm in ids:
bgm_sequence = rom.read_bytes(0xB89AE0 + (bgm[1] * 0x10), 0x10)
bgm_instrument = rom.read_int16(0xB89910 + 0xDD + (bgm[1] * 2))
bgm_data[bgm[0]] = (bgm[0], bgm_sequence, bgm_instrument)
if bgm[0] not in music_mapping.values():
bgm_ids.append(bgm[0])
# shuffle data
random.shuffle(bgm_ids)
# Write Music data back in random ordering
for bgm in ids:
if bgm[0] in music_mapping and music_mapping[bgm[0]] in bgm_data:
bgm_name = music_mapping[bgm[0]]
else:
bgm_name = bgm_ids.pop()
bgm_name, bgm_sequence, bgm_instrument = bgm_data[bgm_name]
rom.write_bytes(0xB89AE0 + (bgm[1] * 0x10), bgm_sequence)
rom.write_int16(0xB89910 + 0xDD + (bgm[1] * 2), bgm_instrument)
log[bgm[0]] = bgm_name
# Write Fairy Fountain instrument to File Select (uses same track but different instrument set pointer for some reason)
rom.write_int16(0xB89910 + 0xDD + (0x57 * 2), rom.read_int16(0xB89910 + 0xDD + (0x28 * 2)))
return log
def randomize_music(rom, ootworld, music_mapping):
log = {}
errors = []
sequences = []
target_sequences = []
fanfare_sequences = []
fanfare_target_sequences = []
disabled_source_sequences = {}
disabled_target_sequences = {}
# Make sure we aren't operating directly on these.
music_mapping = music_mapping.copy()
bgm_ids = bgm_sequence_ids.copy()
ff_ids = fanfare_sequence_ids.copy()
# Check if we have mapped music for BGM, Fanfares, or Ocarina Fanfares
bgm_mapped = any(bgm[0] in music_mapping for bgm in bgm_ids)
ff_mapped = any(ff[0] in music_mapping for ff in ff_ids)
ocarina_mapped = any(ocarina[0] in music_mapping for ocarina in ocarina_sequence_ids)
# Include ocarina songs in fanfare pool if checked
if ootworld.ocarina_fanfares or ocarina_mapped:
ff_ids.extend(ocarina_sequence_ids)
# Flag sequence locations that are set to off for disabling.
disabled_ids = []
if ootworld.background_music == 'off':
disabled_ids += [music_id for music_id in bgm_ids]
if ootworld.fanfares == 'off':
disabled_ids += [music_id for music_id in ff_ids]
disabled_ids += [music_id for music_id in ocarina_sequence_ids]
for bgm in [music_id for music_id in bgm_ids + ff_ids + ocarina_sequence_ids]:
if music_mapping.get(bgm[0], '') == "None":
disabled_target_sequences[bgm[0]] = bgm
for bgm in disabled_ids:
if bgm[0] not in music_mapping:
music_mapping[bgm[0]] = "None"
disabled_target_sequences[bgm[0]] = bgm
# Map music to itself if music is set to normal.
normal_ids = []
if ootworld.background_music == 'normal' and bgm_mapped:
normal_ids += [music_id for music_id in bgm_ids]
if ootworld.fanfares == 'normal' and (ff_mapped or ocarina_mapped):
normal_ids += [music_id for music_id in ff_ids]
if not ootworld.ocarina_fanfares and ootworld.fanfares == 'normal' and ocarina_mapped:
normal_ids += [music_id for music_id in ocarina_sequence_ids]
for bgm in normal_ids:
if bgm[0] not in music_mapping:
music_mapping[bgm[0]] = bgm[0]
# If not creating patch file, shuffle audio sequences. Otherwise, shuffle pointer table
# If generating from patch, also do a version check to make sure custom sequences are supported.
# custom_sequences_enabled = ootworld.compress_rom != 'Patch'
# if ootworld.patch_file != '':
# rom_version_bytes = rom.read_bytes(0x35, 3)
# rom_version = f'{rom_version_bytes[0]}.{rom_version_bytes[1]}.{rom_version_bytes[2]}'
# if compare_version(rom_version, '4.11.13') < 0:
# errors.append("Custom music is not supported by this patch version. Only randomizing vanilla music.")
# custom_sequences_enabled = False
# if custom_sequences_enabled:
# if ootworld.background_music in ['random', 'random_custom_only'] or bgm_mapped:
# process_sequences(rom, sequences, target_sequences, disabled_source_sequences, disabled_target_sequences, bgm_ids)
# if ootworld.background_music == 'random_custom_only':
# sequences = [seq for seq in sequences if seq.cosmetic_name not in [x[0] for x in bgm_ids] or seq.cosmetic_name in music_mapping.values()]
# sequences, log = shuffle_music(sequences, target_sequences, music_mapping, log)
# if ootworld.fanfares in ['random', 'random_custom_only'] or ff_mapped or ocarina_mapped:
# process_sequences(rom, fanfare_sequences, fanfare_target_sequences, disabled_source_sequences, disabled_target_sequences, ff_ids, 'fanfare')
# if ootworld.fanfares == 'random_custom_only':
# fanfare_sequences = [seq for seq in fanfare_sequences if seq.cosmetic_name not in [x[0] for x in fanfare_sequence_ids] or seq.cosmetic_name in music_mapping.values()]
# fanfare_sequences, log = shuffle_music(fanfare_sequences, fanfare_target_sequences, music_mapping, log)
# if disabled_source_sequences:
# log = disable_music(rom, disabled_source_sequences.values(), log)
# rebuild_sequences(rom, sequences + fanfare_sequences)
# else:
if ootworld.background_music == 'randomized' or bgm_mapped:
log = shuffle_pointers_table(rom, bgm_ids, music_mapping, log)
if ootworld.fanfares == 'randomized' or ff_mapped or ocarina_mapped:
log = shuffle_pointers_table(rom, ff_ids, music_mapping, log)
# end_else
if disabled_target_sequences:
log = disable_music(rom, disabled_target_sequences.values(), log)
return log, errors
def disable_music(rom, ids, log):
# First track is no music
blank_track = rom.read_bytes(0xB89AE0 + (0 * 0x10), 0x10)
for bgm in ids:
rom.write_bytes(0xB89AE0 + (bgm[1] * 0x10), blank_track)
log[bgm[0]] = "None"
return log
def restore_music(rom):
# Restore all music from original
for bgm in bgm_sequence_ids + fanfare_sequence_ids + ocarina_sequence_ids:
bgm_sequence = rom.original.read_bytes(0xB89AE0 + (bgm[1] * 0x10), 0x10)
rom.write_bytes(0xB89AE0 + (bgm[1] * 0x10), bgm_sequence)
bgm_instrument = rom.original.read_int16(0xB89910 + 0xDD + (bgm[1] * 2))
rom.write_int16(0xB89910 + 0xDD + (bgm[1] * 2), bgm_instrument)
# restore file select instrument
bgm_instrument = rom.original.read_int16(0xB89910 + 0xDD + (0x57 * 2))
rom.write_int16(0xB89910 + 0xDD + (0x57 * 2), bgm_instrument)
# Rebuild audioseq
orig_start, orig_end, orig_size = rom.original._get_dmadata_record(0x7470)
rom.write_bytes(orig_start, rom.original.read_bytes(orig_start, orig_size))
# If Audioseq was relocated
start, end, size = rom._get_dmadata_record(0x7470)
if start != 0x029DE0:
# Zero out old audioseq
rom.write_bytes(start, [0] * size)
rom.update_dmadata_record(start, orig_start, orig_end)