#!/usr/bin/env python3 from __future__ import annotations import argparse import json import struct from pathlib import Path IMAGE_BASE = 0x400000 PAIRED_YEAR_VALUE_TABLE_VA = 0x005F3980 PAIR_COUNT = 21 TERMINAL_SCALAR_VA = 0x005F3A24 DIRECT_MULTIPLIER_VAS = [0x005C8888, 0x005C9ED8, 0x005C8680] COMPLEMENT_DIVISOR_VA = 0x005C8A20 COMPLEMENT_MULTIPLIER_VA = 0x005C8DA8 COMPLEMENT_BIAS_VA = 0x005C8618 COMPLEMENT_SCALE_VA = 0x005C9ED0 COMPLEMENT_FLOOR_VA = 0x005C88A0 COMPLEMENT_BUILD_106_MULTIPLIER_VA = 0x005C8878 COMPLEMENT_CAP_VA = 0x005C8988 SCALED_COMPANION_NUMERATOR_VA = 0x005C8A20 SCALED_COMPANION_MULTIPLIER_VA = 0x005C8680 SCALED_COMPANION_BIAS_VA = 0x005C88C8 SCALED_COMPANION_SCALE_VA = 0x005C9EC8 def read_u32_table(blob: bytes, va: int, count: int) -> list[int]: offset = va - IMAGE_BASE data = blob[offset : offset + count * 4] if len(data) != count * 4: raise ValueError(f"table at {va:#x} truncated") return [struct.unpack(" float: offset = va - IMAGE_BASE data = blob[offset : offset + 8] if len(data) != 8: raise ValueError(f"f64 at {va:#x} truncated") return struct.unpack(" dict[str, object]: raw_pairs = read_u32_table(exe_bytes, PAIRED_YEAR_VALUE_TABLE_VA, PAIR_COUNT * 2) terminal_scalar_raw = read_u32_table(exe_bytes, TERMINAL_SCALAR_VA, 1)[0] entries = [] for year, value in zip(raw_pairs[::2], raw_pairs[1::2]): entries.append({"year": year, "value": float(value)}) return { "source_kind": "rt3.exe-static-table", "reader_family": "world_refresh_selected_year_bucket_scalar_band", "table_virtual_address": f"0x{PAIRED_YEAR_VALUE_TABLE_VA:08x}", "pair_count": PAIR_COUNT, "terminal_scalar_virtual_address": f"0x{TERMINAL_SCALAR_VA:08x}", "terminal_scalar_value": float(terminal_scalar_raw), "direct_lane_multipliers": [ read_f64(exe_bytes, va) for va in DIRECT_MULTIPLIER_VAS ], "complement_formula": { "divisor": read_f64(exe_bytes, COMPLEMENT_DIVISOR_VA), "multiplier": read_f64(exe_bytes, COMPLEMENT_MULTIPLIER_VA), "bias": read_f64(exe_bytes, COMPLEMENT_BIAS_VA), "scale": read_f64(exe_bytes, COMPLEMENT_SCALE_VA), "floor": read_f64(exe_bytes, COMPLEMENT_FLOOR_VA), "build_106_multiplier": read_f64(exe_bytes, COMPLEMENT_BUILD_106_MULTIPLIER_VA), "cap": read_f64(exe_bytes, COMPLEMENT_CAP_VA), }, "scaled_companion_formula": { "numerator": read_f64(exe_bytes, SCALED_COMPANION_NUMERATOR_VA), "multiplier": read_f64(exe_bytes, SCALED_COMPANION_MULTIPLIER_VA), "bias": read_f64(exe_bytes, SCALED_COMPANION_BIAS_VA), "scale": read_f64(exe_bytes, SCALED_COMPANION_SCALE_VA), }, "entries": entries, } def main() -> int: parser = argparse.ArgumentParser( description="Extract the selected-year bucket ladder used by RT3 world year refresh." ) parser.add_argument("exe", type=Path, help="Path to RT3.exe") parser.add_argument("output", type=Path, help="Output JSON path") args = parser.parse_args() artifact = build_artifact(args.exe.read_bytes()) args.output.write_text(json.dumps(artifact, indent=2) + "\n", encoding="utf-8") return 0 if __name__ == "__main__": raise SystemExit(main())