#!/usr/bin/env python3 r"""Generate `crates/benilla-ui/src/messages/catalog.rs` from `reference/1.12-message-catalog.tsv`. The TSV is wow-5875-re's byte-exact sweep of build 5875's `0xb4b498` registry -- the table `CGGameUI::DisplayError 0x496720` indexes by message id -- vendored under `reference/` like the other tables (it is regenerated there with `re/ui/extract-message-catalog.py`). This script only re-shapes it into a Rust const, so the table ships with benilla and is never read out of a sibling checkout. python3 scripts/gen-message-catalog.py # the vendored table -> the .rs, in place python3 scripts/gen-message-catalog.py # another table Refuses rather than emits on anything it does not understand: a non-dense id range, a duplicate key, an unknown kind. A generated table that quietly dropped a row would be worse than no table. """ import os import sys REPO = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) TABLE = os.path.join(REPO, "reference", "1.12-message-catalog.tsv") OUT = os.path.join(REPO, "crates", "benilla-ui", "src", "messages", "catalog.rs") KINDS = {0: "Chat", 1: "Info", 2: "Error"} HEADER = """\ //! The 5875 message catalog, row for row. //! //! @generated by `scripts/gen-message-catalog.py` from `reference/1.12-message-catalog.tsv` //! (wow-5875-re's `re/ui/message-catalog.tsv`, extracted out of `WoW.exe` there). //! Do not edit by hand — regenerate. use super::{MessageRecord, MsgKind}; /// Every row of `0xb4b498`, indexed by message id (the ids are dense `0..465`, so /// `CATALOG[id]` *is* the reference's `0xb4b498 + 20*id`). /// /// `rustfmt` is held off so that a regeneration is a no-op diff: one row per line is the shape /// this script emits, and a formatter that rewrapped it would make the file drift from its own /// generator. #[rustfmt::skip] pub static CATALOG: [MessageRecord; {count}] = [ """ def main(path: str) -> int: if not os.path.exists(path): raise SystemExit(f"no table at {path}") rows = [] for line in open(path): if line.startswith("#") or line.startswith("id_hex"): continue f = line.rstrip("\n").split("\t") if len(f) != 8: raise SystemExit(f"malformed row (expected 8 fields, got {len(f)}): {line!r}") rows.append( { "id": int(f[1]), "key": f[3], "kind": int(f[4]), "sound": f[5], "type_tag": int(f[6], 16), "chat_type": int(f[7]), } ) for want, r in enumerate(rows): if r["id"] != want: raise SystemExit(f"ids are not dense: expected {want}, got {r['id']}") keys = [r["key"] for r in rows] if len(set(keys)) != len(keys): raise SystemExit("duplicate key in the catalog — by_key would be ambiguous") for r in rows: if r["kind"] not in KINDS: raise SystemExit( f"row {r['id']} ({r['key']}) has kind {r['kind']}, which MsgKind does not model" ) out = [HEADER.replace("{count}", str(len(rows)))] for r in rows: sound = "None" if r["sound"] == "NONE" else f'Some("{r["sound"]}")' out.append( f' MessageRecord {{ id: {r["id"]}, key: "{r["key"]}", ' f'kind: MsgKind::{KINDS[r["kind"]]}, sound: {sound}, ' f'type_tag: {r["type_tag"]:#04x}, chat_type: {r["chat_type"]} }},\n' ) out.append("];\n") with open(OUT, "w") as f: f.write("".join(out)) print(f"wrote {os.path.relpath(OUT, REPO)} ({len(rows)} rows)") return 0 if __name__ == "__main__": if len(sys.argv) > 2: raise SystemExit(__doc__) sys.exit(main(sys.argv[1] if len(sys.argv) == 2 else TABLE))