#!/usr/bin/env python3 """Generate crates/horizon-protocol/src/ids.rs from the vanilla data generator's packets report (`java -DbundlerMainClass=net.minecraft.data.Main -jar server.jar --reports`). Packet ids are protocol facts (the order packets are registered in each connection state); the report is the authoritative source for them. """ import argparse import json from pathlib import Path try: from . import lab except ImportError: import lab ROOT = Path(__file__).resolve().parents[1] STATES = ["handshake", "status", "login", "configuration", "play"] DIRECTIONS = ["clientbound", "serverbound"] def const_name(name): return name.split(":", 1)[1].replace("/", "_").upper() def main(): parser = argparse.ArgumentParser(description=__doc__) parser.add_argument("--report", type=Path, default=ROOT / ".local/datagen/out/reports/packets.json") parser.add_argument("--out", type=Path, default=ROOT / "crates/horizon-protocol/src/ids.rs") args = parser.parse_args() report = json.loads(args.report.read_text()) info = lab.version_info() lines = [ f"//! Packet ids per connection state and direction for protocol {info['protocol_version']} ({info['name']}).", "//! Generated by tools/generate_packet_ids.py from the data generator's", "//! packets report; do not edit by hand.", "#![allow(missing_docs)]", "use crate::{Direction, State};", "", ] tables = [] for state in STATES: lines.append(f"pub mod {state} {{") for direction in DIRECTIONS: packets = report.get(state, {}).get(direction, {}) if not packets: continue ordered = sorted(packets.items(), key=lambda kv: kv[1]["protocol_id"]) assert [e["protocol_id"] for _, e in ordered] == list(range(len(ordered))), (state, direction) lines.append(f" pub mod {direction} {{") for name, entry in ordered: lines.append(f" pub const {const_name(name)}: i32 = {entry['protocol_id']};") lines.append(" /// Packet names indexed by id.") names = ", ".join(f'"{name.split(":", 1)[1]}"' for name, _ in ordered) lines.append(f" pub const NAMES: &[&str] = &[{names}];") lines.append(" }") tables.append((state, direction)) lines.append("}") lines.append("") lines.append("/// The packet name for an id, if the state and direction define it.") lines.append("pub fn name(state: State, direction: Direction, id: i32) -> Option<&'static str> {") lines.append(" let names: &[&str] = match (state, direction) {") for state, direction in tables: lines.append( f" (State::{state.capitalize()}, Direction::{direction.capitalize()}) => {state}::{direction}::NAMES," ) lines.append(" _ => &[],") lines.append(" };") lines.append(" usize::try_from(id).ok().and_then(|i| names.get(i)).copied()") lines.append("}") args.out.write_text("\n".join(lines) + "\n") if __name__ == "__main__": main()