485 lines
		
	
	
		
			19 KiB
		
	
	
	
		
			Python
		
	
	
	
			
		
		
	
	
			485 lines
		
	
	
		
			19 KiB
		
	
	
	
		
			Python
		
	
	
	
| #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)
 | |
| 
 |