diff --git a/CHANGELOG.md b/CHANGELOG.md index dcc1abdc12..e7e4b5973c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,6 +29,10 @@ First versioned release. The following changes are relative to [vanilla Paper Ma - Modern compiler toolchain (GCC). - Enemy max HP increased to 32767. - Badges can cost negative BP. +- Message file improvements: + - Messages are automatically discovered at `assets/*/msg/*.msg`. + - You can now override a message using its name, e.g. `#message:0C:(CH1_0000)`. + - It is now an error to have duplicate message names or IDs (within the same layer of the asset stack). ### Removed diff --git a/tools/build/configure.py b/tools/build/configure.py index ecd969a001..5456262d19 100755 --- a/tools/build/configure.py +++ b/tools/build/configure.py @@ -302,7 +302,7 @@ def write_ninja_rules( ninja.rule( "msg_combine", description="Combining messages", - command=f"$python {BUILD_TOOLS}/msg/combine.py $out $in", + command=f"$python {BUILD_TOOLS}/msg/combine.py $out --layer-sizes $layer_sizes $in", ) ninja.rule( @@ -1137,13 +1137,27 @@ def build( elif seg.type == "pm_msg": msg_bins = [] - - for section_idx, msg_path in enumerate(entry.src_paths): - bin_path = ( - entry.object_path.with_suffix("") / f"{section_idx:02X}.bin" - ) - msg_bins.append(bin_path) - build(bin_path, [msg_path], "msg") + layer_sizes = [] + bin_idx = 0 + + # Process each asset stack layer, lowest priority first + for stack_dir in reversed(self.asset_stack): + msg_dir = Path(f"assets/{stack_dir}/msg") + layer_count = 0 + if msg_dir.exists(): + for msg_file in sorted(msg_dir.glob("*.msg")): + bin_path = entry.object_path.with_suffix("") / f"{bin_idx:02X}.bin" + msg_bins.append(bin_path) + skip_outputs.add(posix(bin_path)) + ninja.build( + outputs=[posix(bin_path)], + rule="msg", + inputs=[posix(msg_file)], + variables={"version": self.version}, + ) + bin_idx += 1 + layer_count += 1 + layer_sizes.append(str(layer_count)) build( [ @@ -1152,6 +1166,7 @@ def build( ], msg_bins, "msg_combine", + variables={"layer_sizes": ",".join(layer_sizes)}, ) build(entry.object_path, [entry.object_path.with_suffix(".bin")], "bin") diff --git a/tools/build/msg/combine.py b/tools/build/msg/combine.py index 3724c1366e..64f3e2e460 100755 --- a/tools/build/msg/combine.py +++ b/tools/build/msg/combine.py @@ -1,10 +1,8 @@ #!/usr/bin/env python3 from sys import argv -from collections import OrderedDict -import re + import msgpack -import os class Message: @@ -16,42 +14,39 @@ def __init__(self, d: dict, header_file_index: int): self.header_file_index = header_file_index -def write_if_unique(f, name, value, seen, warned): - if name in seen: - if name not in warned: - print(f"warning: duplicate: {name}") - warned.add(name) - return - seen.add(name) - f.write(f"#define {name} {value}\n") +def file_to_layer(file_index, layer_sizes): + cumulative = 0 + for layer, size in enumerate(layer_sizes): + cumulative += size + if file_index < cumulative: + return layer + return len(layer_sizes) - 1 if __name__ == "__main__": if len(argv) < 3: - print("usage: combine.py [out.bin] [out.h] [compiled...]") + print("usage: combine.py [out.bin] [out.h] [--layer-sizes SIZES] [compiled...]") exit(1) - _, outfile, header_file, *infiles = argv + _, outfile, header_file, *rest = argv + + # Parse --layer-sizes if present + layer_sizes = None + if "--layer-sizes" in rest: + idx = rest.index("--layer-sizes") + layer_sizes = [int(x) for x in rest[idx + 1].split(",")] + rest = rest[:idx] + rest[idx + 2 :] + + infiles = rest messages = [] - # header_files = [] for i, infile in enumerate(infiles): - # if infile == "--headers": - # header_files = infiles[i+1:] - # break - with open(infile, "rb") as f: messages.extend(Message(msg, i) for msg in msgpack.unpack(f)) with open(outfile, "wb") as f: - # sectioned+indexed, followed by just sectioned, followed by just indexed, followed by named (unsectioned & unindexed) - # messages.sort(key=lambda msg: bool(msg.section)<<2 + bool(msg.index)) - - names = set() - sections = [] - # messages_by_file = {} for message in messages: if message.section is None: @@ -67,25 +62,40 @@ def write_if_unique(f, name, value, seen, warned): section = sections[section_idx] if message.index is None: - message.index = len(section) - - # if message.name: - # if message.name in names: - # print(f"error: multiple messages with name '{message.name}'") - # exit(1) - # else: - # names.add(message.name) - - # if message.header_file_index in messages_by_file: - # messages_by_file[message.header_file_index].add(message) - # else: - # messages_by_file[message.header_file_index] = set([message]) - + # Look up existing message with this name to override it + if message.name: + for idx, existing in section.items(): + if existing.name == message.name: + message.index = idx + break + if message.index is None: + message.index = len(section) + + # Only allow overrides between asset stack layers if message.index in section: - print(f"warning: multiple messages allocated to id {section_idx:02X}:{message.index:03X}") - - if section[message.index].name and message.name: - print(f"warning: message '{section[message.index].name}' and '{message.name}' conflict") + existing = section[message.index] + + if layer_sizes is not None: + msg_layer = file_to_layer(message.header_file_index, layer_sizes) + existing_layer = file_to_layer( + existing.header_file_index, layer_sizes + ) + + if msg_layer == existing_layer: + if ( + existing.name + and message.name + and existing.name == message.name + ): + print( + f"error: duplicate message '{message.name}' in same layer" + ) + exit(1) + else: + print( + f"error: multiple messages allocated to id {section_idx:02X}:{message.index:03X} in same layer" + ) + exit(1) section[message.index] = message @@ -94,7 +104,9 @@ def write_if_unique(f, name, value, seen, warned): section_offsets = [] for section in sections: # convert dict into sorted list - section = [msg for idx, msg in sorted(section.items(), key=lambda ele: ele[0])] + section = [ + msg for idx, msg in sorted(section.items(), key=lambda ele: ele[0]) + ] message_offsets = [] for message in section: @@ -117,12 +129,20 @@ def write_if_unique(f, name, value, seen, warned): f.write(b"\0\0\0\0") with open(header_file, "w") as f: - f.write(f"#pragma once\n" "\n" '#include "messages.h"\n' "\n") + f.write(f'#pragma once\n\n#include "messages.h"\n\n') - seen = set() - warned = set() + seen = {} for message in messages: if message.name: - write_if_unique(f, f"MSG_{message.name}", f"MESSAGE_ID(0x{message.section:02X}, 0x{message.index:03X})", seen, warned) + define_name = f"MSG_{message.name}" + value = f"MESSAGE_ID(0x{message.section:02X}, 0x{message.index:03X})" + if define_name in seen: + if seen[define_name] != value: + print( + f"warning: conflicting values for {define_name}: {seen[define_name]} vs {value}" + ) + else: + seen[define_name] = value + f.write(f"#define {define_name} {value}\n") f.write("\n")