diff --git a/tools/build/audio/sbn.py b/tools/build/audio/sbn.py index 628cfe7017..14e4fb0b4d 100644 --- a/tools/build/audio/sbn.py +++ b/tools/build/audio/sbn.py @@ -1,25 +1,183 @@ #! /usr/bin/python3 -from sys import argv, path +import argparse +from sys import path from pathlib import Path +import sys +from typing import Tuple + +import yaml + +sys.path.append(str(Path(__file__).parent.parent)) +sys.path.append(str(Path(__file__).parent.parent.parent)) +sys.path.append(str(Path(__file__).parent.parent.parent)) +sys.path.append(str(Path(__file__).parent.parent.parent / "splat")) +from common import get_asset_path # allow importing splat_ext path.append(str(Path(__file__).parent.parent.parent)) -from splat_ext.pm_sbn import SBN +from splat_ext.pm_sbn import SBN, BufferEntry, InitSongEntry, SBNFile -if __name__ == "__main__": - out = argv[1] - inputs = [Path(p) for p in argv[2:]] +# Sorts a list of dicts by their "id" key. If "id" is "auto" or missing, it is sorted to the end. +def sort_by_id_or_auto(list): + def get_id(item): + id = item.get("id") + return 0xFFFF if id == "auto" else id + + list.sort(key=get_id) + + +def from_yaml(yaml_path: Path, asset_stack: Tuple[Path, ...]) -> SBN: sbn = SBN() - for input in inputs: - # if input is a sbn.yaml, read its directory - if input.suffix == ".yaml": - sbn.read(input.parent) + with yaml_path.open("r") as f: + config = yaml.safe_load(f) + + unknown_bin_path = get_asset_path("audio/unknown.bin", asset_stack) + with open(unknown_bin_path, "rb") as f: + sbn.unknown_data = f.read() + + files = config.get("files", {}) + songs = config.get("songs", []) + mseqs = config.get("mseqs", []) + banks = config.get("banks", []) + + sort_by_id_or_auto(files) + sort_by_id_or_auto(songs) + sort_by_id_or_auto(mseqs) + sort_by_id_or_auto(banks) + + # Read files + for file_dict in files: + id = file_dict.get("id", "auto") + if id == "auto": + id = len(sbn.files) + assert type(id) == int + + filename = file_dict.get("file") + assert type(filename) == str + + fakesize = file_dict.get("fakesize") + assert type(fakesize) == int or fakesize is None + + sbn_file = SBNFile() + sbn_file_path = get_asset_path(f"audio/{filename}", asset_stack) + sbn_file.read(sbn_file_path) + + if fakesize is not None: + sbn_file.fakesize = fakesize + else: + sbn_file.fakesize = sbn_file.size + + # Replace sbn.files[id] + if id < len(sbn.files): + print("Overwriting file ID {:02X}", id) + sbn.files[id] = sbn_file + elif id == len(sbn.files): + sbn.files.append(sbn_file) + else: + raise Exception(f"Invalid file ID: 0x{id:02X} - cannot have gaps") + + # Read INIT songs + for song in songs: + id = song.get("id", "auto") + if id == "auto": + id = len(sbn.init.song_entries) + assert type(id) == int + + # Lookup file ID + file = song.get("file") + assert type(file) == str + file_id = sbn.lookup_file_id(file) + + # Lookup BK file IDs + bk_files = song.get("bk_files", []) + assert type(bk_files) == list + bk_file_ids = [] + for bk_file in bk_files: + if bk_file is None: + bk_file_ids.append(0) + else: + assert type(bk_file) == str + bk_file_ids.append(sbn.lookup_file_id(bk_file)) + + init_song_entry = InitSongEntry(file_id, bk_file_ids[0], bk_file_ids[1], bk_file_ids[2]) + + # Replace sbn.init.song_entries[id] + if id < len(sbn.init.song_entries): + print(f"Overwriting song ID {id:02X}") + sbn.init.song_entries[id] = init_song_entry + elif id == len(sbn.init.song_entries): + sbn.init.song_entries.append(init_song_entry) + else: + raise Exception(f"Invalid song ID: 0x{id:02X} - cannot have gaps") + + # Read INIT mseqs + for mseq in mseqs: + id = mseq.get("id", "auto") + if id == "auto": + id = len(sbn.init.mseq_entries) + assert type(id) == int + + # Lookup file ID + file = mseq.get("file") + assert type(file) == str + file_id = sbn.lookup_file_id(file) + + # Replace sbn.init.mseq_entries[id] + if id < len(sbn.init.mseq_entries): + print(f"Overwriting MSEQ ID {id:02X}") + sbn.init.mseq_entries[id] = file_id + elif id == len(sbn.init.mseq_entries): + sbn.init.mseq_entries.append(file_id) + else: + raise Exception(f"Invalid MSEQ ID: 0x{id:02X} - cannot have gaps") + + # Read INIT banks + for bank in banks: + id = bank.get("id", "auto") + if id == "auto": + id = len(sbn.init.bk_entries) + assert type(id) == int + + file = bank.get("file") + assert type(file) == str + file_id = sbn.lookup_file_id(file) + + bank_index = bank.get("bank_index") + assert type(bank_index) == int + + bank_group = bank.get("bank_group") + assert type(bank_group) == int + + entry = BufferEntry(file_id, bank_index, bank_group) + + # Replace sbn.init.bk_entries[id] + if id < len(sbn.init.bk_entries): + print(f"Overwriting bank ID {id:02X}") + sbn.init.bk_entries[id] = entry + elif id == len(sbn.init.bk_entries): + sbn.init.bk_entries.append(entry) + else: + raise Exception(f"Invalid bank ID: {id:02X} - cannot have gaps") + return sbn + + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description="Builds SBN") + parser.add_argument("out") + parser.add_argument("asset_stack") + args = parser.parse_args() + + out_path = Path(args.out) + asset_stack = tuple(Path(d) for d in args.asset_stack.split(",")) + + yaml_path = get_asset_path(Path("audio/sbn.yaml"), asset_stack) + sbn = from_yaml(yaml_path, asset_stack) data = sbn.encode() - with open(out, "wb") as f: + with open(out_path, "wb") as f: f.write(data) diff --git a/tools/build/configure.py b/tools/build/configure.py index ab0674b3f9..6b84353d11 100755 --- a/tools/build/configure.py +++ b/tools/build/configure.py @@ -301,7 +301,7 @@ def write_ninja_rules( ninja.rule("effect_data", command=f"$python {BUILD_TOOLS}/effects.py $in_yaml $out_dir") - ninja.rule("pm_sbn", command=f"$python {BUILD_TOOLS}/audio/sbn.py $out $in") + ninja.rule("pm_sbn", command=f"$python {BUILD_TOOLS}/audio/sbn.py $out $asset_stack") with Path("tools/permuter_settings.toml").open("w") as f: f.write(f"compiler_command = \"{cc} {CPPFLAGS.replace('$version', 'pal')} {cflags} -DPERMUTER -fforce-addr\"\n") @@ -1060,7 +1060,15 @@ class Configure: build(entry.object_path, [entry.object_path.with_suffix("")], "bin") elif seg.type == "pm_sbn": sbn_path = entry.object_path.with_suffix("") - build(sbn_path, entry.src_paths, "pm_sbn") # could have non-yaml inputs be implicit + build( + sbn_path, + entry.src_paths, + "pm_sbn", + variables={ + "asset_stack": ",".join(self.asset_stack), + }, + asset_deps=entry.src_paths, + ) build(entry.object_path, [sbn_path], "bin") elif seg.type == "linker" or seg.type == "linker_offset": pass diff --git a/tools/splat_ext/pm_sbn.py b/tools/splat_ext/pm_sbn.py index c86c5ec80f..385b4e8d8f 100644 --- a/tools/splat_ext/pm_sbn.py +++ b/tools/splat_ext/pm_sbn.py @@ -274,139 +274,6 @@ class SBN: f.write(f" bank_group: 0x{entry.bankGroup:02X}\n") f.write("\n") - def read(self, dir: Path) -> List[Path]: - config_file_path = dir / "sbn.yaml" - with config_file_path.open("r") as f: - config = yaml.safe_load(f) - - with open(dir / "unknown.bin", "rb") as f: - self.unknown_data = f.read() - - files = config.get("files", {}) - songs = config.get("songs", []) - mseqs = config.get("mseqs", []) - banks = config.get("banks", []) - - sort_by_id_or_auto(files) - sort_by_id_or_auto(songs) - sort_by_id_or_auto(mseqs) - sort_by_id_or_auto(banks) - - # Read files - for file_dict in files: - id = file_dict.get("id", "auto") - if id == "auto": - id = len(self.files) - assert type(id) == int - - filename = file_dict.get("file") - assert type(filename) == str - - fakesize = file_dict.get("fakesize") - assert type(fakesize) == int or fakesize is None - - sbn_file = SBNFile() - sbn_file.read(dir / filename) - - if fakesize is not None: - sbn_file.fakesize = fakesize - else: - sbn_file.fakesize = sbn_file.size - - # Replace self.files[id] - if id < len(self.files): - print("Overwriting file ID {:02X}", id) - self.files[id] = sbn_file - elif id == len(self.files): - self.files.append(sbn_file) - else: - raise Exception(f"Invalid file ID: 0x{id:02X} - cannot have gaps") - - # Read INIT songs - for song in songs: - id = song.get("id", "auto") - if id == "auto": - id = len(self.init.song_entries) - assert type(id) == int - - # Lookup file ID - file = song.get("file") - assert type(file) == str - file_id = self.lookup_file_id(file) - - # Lookup BK file IDs - bk_files = song.get("bk_files", []) - assert type(bk_files) == list - bk_file_ids = [] - for bk_file in bk_files: - if bk_file is None: - bk_file_ids.append(0) - else: - assert type(bk_file) == str - bk_file_ids.append(self.lookup_file_id(bk_file)) - - init_song_entry = InitSongEntry(file_id, bk_file_ids[0], bk_file_ids[1], bk_file_ids[2]) - - # Replace self.init.song_entries[id] - if id < len(self.init.song_entries): - print(f"Overwriting song ID {id:02X}") - self.init.song_entries[id] = init_song_entry - elif id == len(self.init.song_entries): - self.init.song_entries.append(init_song_entry) - else: - raise Exception(f"Invalid song ID: 0x{id:02X} - cannot have gaps") - - # Read INIT mseqs - for mseq in mseqs: - id = mseq.get("id", "auto") - if id == "auto": - id = len(self.init.mseq_entries) - assert type(id) == int - - # Lookup file ID - file = mseq.get("file") - assert type(file) == str - file_id = self.lookup_file_id(file) - - # Replace self.init.mseq_entries[id] - if id < len(self.init.mseq_entries): - print(f"Overwriting MSEQ ID {id:02X}") - self.init.mseq_entries[id] = file_id - elif id == len(self.init.mseq_entries): - self.init.mseq_entries.append(file_id) - else: - raise Exception(f"Invalid MSEQ ID: 0x{id:02X} - cannot have gaps") - - # Read INIT banks - for bank in banks: - id = bank.get("id", "auto") - if id == "auto": - id = len(self.init.bk_entries) - assert type(id) == int - - file = bank.get("file") - assert type(file) == str - file_id = self.lookup_file_id(file) - - bank_index = bank.get("bank_index") - assert type(bank_index) == int - - bank_group = bank.get("bank_group") - assert type(bank_group) == int - - entry = BufferEntry(file_id, bank_index, bank_group) - - # Replace self.init.bk_entries[id] - if id < len(self.init.bk_entries): - print(f"Overwriting bank ID {id:02X}") - self.init.bk_entries[id] = entry - elif id == len(self.init.bk_entries): - self.init.bk_entries.append(entry) - else: - raise Exception(f"Invalid bank ID: {id:02X} - cannot have gaps") - - return [config_file_path] - def lookup_file_id(self, filename: str) -> int: for id, sbn_file in enumerate(self.files): if sbn_file.file_name() == filename: @@ -689,13 +556,10 @@ if splat_loaded: dir = options.opts.asset_path / self.dir / self.name out = options.opts.asset_path / self.dir / (self.name + ".sbn") - sbn = SBN() - config_files = sbn.read(dir) # TODO: LayeredFS/AssetsFS read, supporting merges - inputs = config_files + [dir / f.file_name() for f in sbn.files] return [ LinkerEntry( self, - inputs, + [dir], out, ".data", ), @@ -849,12 +713,3 @@ def get_song_symbol_name(song_id: int) -> str: 0x00000096: "SONG_NEW_PARTNER_JP", } return song_names.get(song_id, f"null") - - -# Sorts a list of dicts by their "id" key. If "id" is "auto" or missing, it is sorted to the end. -def sort_by_id_or_auto(list): - def get_id(item): - id = item.get("id") - return 0xFFFF if id == "auto" else id - - list.sort(key=get_id)