papermario/tools/build/audio/sbn.py

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

184 lines
5.5 KiB
Python
Raw Normal View History

#! /usr/bin/python3
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, BufferEntry, InitSongEntry, SBNFile
# 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()
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_path, "wb") as f:
f.write(data)