Files
moonwell-core/tools/build-solo-lfg-dbc.py

113 lines
4.0 KiB
Python

#!/usr/bin/env python3
"""Add native solo RDF categories to a WotLK 3.3.5a LFGDungeons.dbc."""
from __future__ import annotations
import argparse
import struct
from pathlib import Path
MAGIC = b"WDBC"
FIELD_COUNT = 49
RECORD_SIZE = FIELD_COUNT * 4
SOLO_ID_OFFSET = 1000
SOLO_NAMES = {
258: "Случайное одиночное подземелье: Классика",
259: "Случайное одиночное подземелье: Burning Crusade",
260: "Случайное одиночное героическое: Burning Crusade",
261: "Случайное одиночное подземелье Lich King",
262: "Случайное одиночное героическое подземелье Lich King",
}
SOLO_DESCRIPTION = (
"Случайное подземелье для одного игрока. "
"Существа и боссы масштабируются системой AutoBalance."
)
def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(
description="Duplicate the five stock random dungeon rows as solo RDF categories."
)
parser.add_argument("source", type=Path, help="Source LFGDungeons.dbc")
parser.add_argument("output", type=Path, help="Patched LFGDungeons.dbc")
return parser.parse_args()
def add_string(string_block: bytearray, value: str) -> int:
encoded = value.encode("utf-8") + b"\0"
offset = len(string_block)
string_block.extend(encoded)
return offset
def main() -> None:
args = parse_args()
raw = args.source.read_bytes()
if len(raw) < 20 or raw[:4] != MAGIC:
raise SystemExit(f"{args.source}: not a WDBC file")
record_count, field_count, record_size, string_size = struct.unpack_from("<4I", raw, 4)
if field_count != FIELD_COUNT or record_size != RECORD_SIZE:
raise SystemExit(
f"{args.source}: unexpected layout "
f"(fields={field_count}, record_size={record_size})"
)
records_start = 20
strings_start = records_start + record_count * record_size
strings_end = strings_start + string_size
if strings_end != len(raw):
raise SystemExit(f"{args.source}: inconsistent WDBC size")
records: list[list[int]] = []
source_records: dict[int, list[int]] = {}
solo_ids = {base_id + SOLO_ID_OFFSET for base_id in SOLO_NAMES}
for index in range(record_count):
offset = records_start + index * record_size
record = list(struct.unpack_from(f"<{FIELD_COUNT}I", raw, offset))
if record[0] in SOLO_NAMES:
source_records[record[0]] = record
if record[0] not in solo_ids:
records.append(record)
missing = sorted(set(SOLO_NAMES) - set(source_records))
if missing:
raise SystemExit(f"{args.source}: missing stock random dungeon rows: {missing}")
string_block = bytearray(raw[strings_start:strings_end])
for base_id, name in SOLO_NAMES.items():
record = source_records[base_id].copy()
record[0] = base_id + SOLO_ID_OFFSET
name_offset = add_string(string_block, name)
description_offset = add_string(string_block, SOLO_DESCRIPTION)
# Fill every locale slot. This keeps the custom row readable regardless
# of which locale column the client selects.
record[1:17] = [name_offset] * 16
record[32:48] = [description_offset] * 16
records.append(record)
records.sort(key=lambda record: record[0])
output = bytearray()
output.extend(MAGIC)
output.extend(struct.pack("<4I", len(records), FIELD_COUNT, RECORD_SIZE, len(string_block)))
for record in records:
output.extend(struct.pack(f"<{FIELD_COUNT}I", *record))
output.extend(string_block)
args.output.parent.mkdir(parents=True, exist_ok=True)
temporary = args.output.with_suffix(args.output.suffix + ".tmp")
temporary.write_bytes(output)
temporary.replace(args.output)
print(f"Wrote {args.output} with solo RDF entries 1258-1262")
if __name__ == "__main__":
main()