#!/usr/bin/env python3 """Generate crates/horizon-core/src/version.rs: what the pinned Minecraft version states about itself in its client jar's version.json (name, protocol, data version, pack formats, Java, build time). Every Rust site that needs one of these facts reads it from there, so a version bump rewrites this one file (docs/version-ports.md, "Regenerate"). gen_version_consts.py [--jar CLIENT_JAR] [--out PATH] [--check] The default jar is the pinned one (.local/minecraft//jars/client.jar). --check exits 1 when the committed file differs from what the jar says. """ import argparse from datetime import datetime, timezone import json from pathlib import Path import sys try: from . import lab except ImportError: import lab OUT = lab.ROOT / "crates/horizon-core/src/version.rs" def java_date(iso): """`Date.toString()` of an ISO-8601 instant, in UTC (Locale.ROOT names).""" return datetime.fromisoformat(iso).astimezone(timezone.utc).strftime("%a %b %d %H:%M:%S UTC %Y") def render(info): pack = info["pack_version"] return "\n".join([ f"//! What Minecraft {info['name']} states about itself (the client jar's `version.json`,", "//! `SharedConstants.getCurrentVersion()`). Generated by tools/gen_version_consts.py; do not edit by hand.", "", "/// `WorldVersion.id()`.", f"pub const VERSION_ID: &str = {json.dumps(info['id'])};", "/// `WorldVersion.name()`: level.dat, the server list, crash reports.", f"pub const VANILLA_VERSION: &str = {json.dumps(info['name'])};", "/// `SharedConstants.getProtocolVersion()`.", f"pub const PROTOCOL_VERSION: i32 = {info['protocol_version']};", "/// `WorldVersion.dataVersion()`: the DataVersion of saved chunks, entities and level.dat.", f"pub const DATA_VERSION: i32 = {info['world_version']};", "/// `WorldVersion.packVersion(PackType.CLIENT_RESOURCES)`: (major, minor).", f"pub const RESOURCE_PACK_FORMAT: (i64, i64) = ({pack['resource_major']}, {pack['resource_minor']});", "/// `WorldVersion.packVersion(PackType.SERVER_DATA)`: (major, minor).", f"pub const DATA_PACK_FORMAT: (i64, i64) = ({pack['data_major']}, {pack['data_minor']});", "/// The Java major version the game runs on.", f"pub const JAVA_VERSION: u32 = {info['java_version']};", "/// `WorldVersion.stable()`: false for snapshots and pre-releases.", f"pub const STABLE: bool = {'true' if info['stable'] else 'false'};", "/// `WorldVersion.buildTime()` as `Date.toString()` prints it in UTC (`/version`).", f"pub const BUILD_TIME: &str = {json.dumps(java_date(info['build_time']))};", "", ]) def main(): parser = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter) parser.add_argument("--jar", type=Path, help="client jar (default: the pinned one in .local)") parser.add_argument("--out", type=Path, default=OUT) parser.add_argument("--check", action="store_true", help="exit 1 if --out differs instead of writing it") args = parser.parse_args() jar = args.jar or lab.GAME / "jars" / "client.jar" text = render(lab.version_info(jar)) if args.check: if not args.out.exists() or args.out.read_text() != text: print(f"{args.out} does not match {jar}; run tools/gen_version_consts.py", file=sys.stderr) return 1 return 0 args.out.write_text(text) print(f"wrote {args.out}") return 0 if __name__ == "__main__": sys.exit(main())