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) | ||
|  | 
 |