Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
31 changes: 23 additions & 8 deletions tools/build/configure.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -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(
[
Expand All @@ -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")

Expand Down
114 changes: 67 additions & 47 deletions tools/build/msg/combine.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
#!/usr/bin/env python3

from sys import argv
from collections import OrderedDict
import re

import msgpack
import os


class Message:
Expand All @@ -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:
Expand All @@ -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

Expand All @@ -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:
Expand All @@ -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")
Loading