Generate asset enum programmatically

This commit is contained in:
Dominik Peters 2024-10-14 21:22:33 +00:00 committed by Banjo Kazooie
parent 6fb4f2b3f1
commit 31c28a7009
1 changed files with 210 additions and 0 deletions

View File

@ -0,0 +1,210 @@
#!/usr/bin/env python3
import argparse
import yaml
import re
import os
from typing import Dict, List
from pathlib import Path
def build_asset_map(assets_file: str) -> Dict[str, str]:
file_types_to_skip = (
'LevelSetup', # 0x071D - 0x07B5
'DemoInput', # 0x09A2 - 0x0A0A
'QuizQuestion', # 0x1213 - 0x13D5
'GruntyQuestion', # 0x1407 - 0x1424
'Midi' # 0x1516 - 0x15C2
)
file_type_to_asset_type = {
'Animation': 'ANIM',
'Model': 'MODEL',
'Sprite_I4': 'SPRITE',
'Sprite_I8': 'SPRITE',
'Sprite_CI4': 'SPRITE',
'Sprite_CI8': 'SPRITE',
'Sprite_RGBA16': 'SPRITE',
'Sprite_RGBA32': 'SPRITE',
'Sprite_UNKNOWN(256)': 'SPRITE',
'Dialog': 'DIALOG',
}
asset_map = {}
with open(assets_file, 'r') as stream:
try:
data = yaml.safe_load(stream)
if "files" not in data:
raise Exception("Expected to find 'files' key in assets file")
files = data["files"]
for file in files:
file_type = file["type"]
if file_type in file_types_to_skip:
continue
if file_type not in file_type_to_asset_type:
continue
asset_type = file_type_to_asset_type[file_type]
address = file["uid"]
asset_map[f"{address:X}"] = f"ASSET_{address:X}_{asset_type}_UNKNOWN"
except yaml.YAMLError as exc:
print(exc)
return asset_map
def get_existing_asset_enums(assets_enum_file: str) -> Dict[str, str]:
with open(assets_enum_file, "r") as enums_stream:
existing_asset_match = re.compile(r"^\s*(ASSET_([A-F0-9]+)_\w+)", re.IGNORECASE)
existing_asset_mappings = {}
for line in enums_stream:
asset_line = existing_asset_match.match(line)
if asset_line is not None:
asset_address = asset_line.group(2).upper()
existing_asset_mappings[asset_address] = asset_line.group(1)
return existing_asset_mappings
def build_asset_enum(asset_map: Dict[str, str], existing_assets: Dict[str, str]) -> List[str]:
asset_enum_lines = []
address_already_defined = False
for i in range(1, int(0x1516)):
address = f"{i:X}"
if address in existing_assets:
if address_already_defined is False:
asset_enum_lines.append(
f"{existing_assets[address]} = 0x{address},")
address_already_defined = True
else:
asset_enum_lines.append(f"{existing_assets[address]},")
elif address in asset_map:
if address_already_defined is False:
asset_enum_lines.append(f"{asset_map[address]} = 0x{address},")
address_already_defined = True
else:
asset_enum_lines.append(f"{asset_map[address]},")
else:
asset_enum_lines.append(f"// {address} unused")
address_already_defined = False
asset_enum_lines = remove_unnecessary_unused_asset_lines(asset_enum_lines)
# Remove trailing comma in last asset line
last_line: str = asset_enum_lines[-1]
if last_line.endswith(","):
last_line = last_line.replace(",", "")
asset_enum_lines[-1] = last_line
return map(lambda x: f" {x}", asset_enum_lines)
def remove_unnecessary_unused_asset_lines(lines: List[str]) -> List[str]:
prev_unused = False
unused_asset_line_indexes = []
# Create list of lines that contain "// xx is unused"
for idx, line in enumerate(lines):
if line.endswith('unused') is False:
continue
if idx + 1 >= len(lines):
continue
prev: str | None = lines[idx - 1]
prev_unused = prev is not None and prev.endswith('unused')
next: str | None = lines[idx + 1]
next_unused = next is not None and next.endswith('unused')
if prev_unused and next_unused:
unused_asset_line_indexes.append(idx)
# "Group" lines where multiple "// xx is unused" lines occur in a row
groups = []
group = []
target = None
for idx, line_index in enumerate(unused_asset_line_indexes):
if target is not None and target > idx:
continue
else:
target = None
for next in range(0, len(unused_asset_line_indexes) - idx):
if unused_asset_line_indexes[next + idx] == line_index + next:
group.append(next + idx)
else:
break
if len(group) == 0:
continue
combine_from_index = group[0]
combine_from = unused_asset_line_indexes[combine_from_index]
combine_to_index = group[len(group)-1]
combine_to = unused_asset_line_indexes[combine_to_index]
groups.append((combine_from - 1, combine_to + 1))
group = []
target = next + idx
# Replace the "unnecessary" lines with a single line that lists the range that is unused
offset = 0
address_regex = re.compile(r"^// (\w+) unused$")
for group in groups:
(start, end) = group
start -= offset
start_line = lines[start]
start_match = address_regex.match(start_line)
end -= offset
end_line = lines[end]
end_match = address_regex.match(end_line)
if start_match is not None and end_match is not None:
lines[start:end+1] = [
f"// 0x{start_match.group(1)} - 0x{end_match.group(1)} unused"
]
offset += end - start
return lines
def write_enum_file(asset_lines: List[str], out_file: str):
with open(out_file, "w") as enums_out:
lines = [
"enum asset_e",
"{",
*asset_lines,
"};"
]
enums_out.write("\n".join(lines))
def main():
parser = argparse.ArgumentParser(
description='Generate asset_e enum from assets/assets.yaml and include/enums.h files',
formatter_class=argparse.RawDescriptionHelpFormatter
)
parser.add_argument(
'basedir',
type=str,
help="base directory (containing src/)"
)
args = parser.parse_args()
asset_map = build_asset_map(
assets_file=f"{args.basedir}{os.sep}assets{os.sep}assets.yaml"
)
existing_assets = get_existing_asset_enums(
assets_enum_file=f"{args.basedir}{os.sep}include{os.sep}enums.h"
)
asset_enum_lines = build_asset_enum(asset_map, existing_assets)
write_enum_file(
asset_enum_lines,
out_file=f"{args.basedir}{os.sep}assets.h"
)
if __name__ == '__main__':
main()