#!/usr/bin/env python3 """Build the verified resource ledger from immutable extraction evidence.""" from __future__ import annotations import argparse import csv import hashlib import io import json from pathlib import Path ROOT = Path(__file__).resolve().parent.parent TARGET_SHA256 = "a9022f1894e3e6e21fc42e8f6c932f7c549ca77f63aaa0c488bb9d55d9d0174c" FIELDS = ( "type", "id", "file_offset", "allocated_size", "raw_sha256", "raw_path", "decoded_paths", "status", "stage", "users", "behavior", "notes", ) def read_tsv(path: Path) -> list[dict[str, str]]: with path.open(newline="", encoding="utf-8") as stream: return list(csv.DictReader(stream, delimiter="\t")) def verified_function_addresses() -> set[str]: rows = read_tsv(ROOT / "FUNCTION_RECONSTRUCTION.tsv") assert len(rows) == 391 assert all(row["status"] == "verified" for row in rows) return {row["address"] for row in rows} def require_closed_ledger(name: str) -> None: rows = read_tsv(ROOT / name) assert rows assert all(row["status"] == "verified" for row in rows), name def role(resource_type: str, resource_id: int) -> str: if resource_type == "DAT": if 400 <= resource_id <= 407: return f"42x123 player-emblem sprite {resource_id - 400}" if resource_id == 600: return "544x180 rotating-target and panel-animation atlas" if 701 <= resource_id <= 704: return f"146x142 score-status level {resource_id - 700}" if 801 <= resource_id <= 809: return f"68x61 collected-item display count {resource_id - 800}" return { 900: "900x296 18-frame claw atlas", 901: "230x17 launcher and velocity-row decoration atlas", 993: "360x300 high-score background", 994: "237x13 asset-loading progress strip", 995: "640x460 one-shot board splash", 997: "640x460 immutable restore overlay A", 998: "640x460 restore overlay B and mutable background", 1001: "640x460 board background selected by index 1", 1002: "640x460 board background selected by index 2", 1003: "640x460 board background selected by index 3", 1004: "640x460 board background selected by index 4", 1005: "640x460 board background selected by index 5", }[resource_id] if resource_type == "PAL": return "256 RGB triples shared by all custom DAT DIBs" if resource_type == "WAV": return { 2001: "add-player cue", 2002: "launcher-release cue", 2004: "trigger-activation cue", 2006: "timed target 51..53 hit cue", 2007: "score-marker or full-item-bank award cue", 2008: "normal ball-end cue", 2011: "rotating-target mechanism cue", 2012: "target and panel transition cue", 2013: "panel-mechanism motion cue", 2015: "ball-capture cue", 2016: "claw ball-release cue", 2017: "completed target-bank/item award cue", 2019: "table-nudge or kick-response cue", 2020: "tilt cue", 2021: "flipper-motion cue", 2022: "loaded and freed sound slot 22 with no in-image playback selector", }[resource_id] if resource_type == "BITMAP": return { 101: "16x16 SRCPAINT object-composition image", 102: "16x16 SRCAND object-composition mask", 500: "944x28 score-digit and timer-indicator atlas", }[resource_id] return { ("ICON", 1): "32x32 image selected by GROUP_ICON 996", ("GROUP_ICON", 996): "main-window icon group loaded at 1000:016c", ("VERSION", 1): "Windows version metadata with no image-code loader", ("DIALOG", 32512): "Borland 2D File Open template", ("DIALOG", 32513): "Borland 2D File Save As template", ("DIALOG", 32514): "2D text-entry template selected at 1008:1607", ("DIALOG", 32515): "Borland CTL3D File Open template", ("DIALOG", 32516): "Borland CTL3D File Save As template", ("DIALOG", 32517): "CTL3D text-entry template selected at 1008:1607", }[(resource_type, resource_id)] def mapping(resource_type: str, resource_id: int) -> tuple[str, str]: if resource_type == "DAT": if 400 <= resource_id <= 407: return "fully-mapped", "1000:5390,1008:149d,1000:0002,1000:51f5" if resource_id == 600: return "fully-mapped", "1000:5390,1008:149d,1000:6f27,1000:7440,1000:51f5" if 701 <= resource_id <= 704: return "fully-mapped", "1000:5390,1008:149d,1008:0b9e,1000:51f5" if 801 <= resource_id <= 809: return "fully-mapped", "1000:5390,1008:149d,1008:0dbd,1000:51f5" return "fully-mapped", { 900: "1000:5390,1008:149d,1008:0e08,1000:51f5", 901: "1000:5390,1008:149d,1000:638e,1000:6b37,1000:51f5", 993: "1000:5be5,1008:149d,1000:5c13,1000:5ba6", 994: "1000:5390,1000:531e,1008:149d", 995: "1000:5390,1008:149d", 997: "1000:5390,1008:149d,1008:0837,1000:7440,1000:eab1,1000:51f5", 998: "1000:5390,1008:149d,1008:07b6,1008:08bb,1008:093f,1008:0996,1008:0e08,1000:51f5", 1001: "1000:6129,1008:149d,1000:616e,1000:60e7", 1002: "1000:6129,1008:149d,1000:616e,1000:60e7", 1003: "1000:6129,1008:149d,1000:616e,1000:60e7", 1004: "1000:6129,1008:149d,1000:616e,1000:60e7", 1005: "1000:6129,1008:149d,1000:616e,1000:60e7", }[resource_id] if resource_type == "PAL": return "fully-mapped", "1008:1326,1008:11c5,1008:13a8,1008:149d" if resource_type == "WAV": events = { 2001: "1000:638e", 2002: "1000:638e", 2004: "1000:c79c", 2006: "1000:b476", 2007: "1000:b476,1000:bc36", 2008: "1000:ae6e", 2011: "1000:6f27", 2012: "1000:b476,1000:7440", 2013: "1000:7440", 2015: "1000:c79c", 2016: "1000:eab1", 2017: "1000:b476", 2019: "1000:638e,1000:c79c", 2020: "1000:638e", 2021: "1000:7ed9", 2022: "", }[resource_id] users = "1008:0f2e,1008:1131,1008:10e3" if events: users += "," + events return "fully-mapped", users if resource_type == "BITMAP": return "fully-mapped", { 101: "1000:5390,1000:9bca,1000:9f73,1000:a38e,1000:51f5", 102: "1000:5390,1000:9bca,1000:9f73,1000:a38e", 500: "1000:5390,1008:0996,1000:eab1,1000:51f5", }[resource_id] if (resource_type, resource_id) == ("GROUP_ICON", 996): return "system-resolved", "1000:016c" if (resource_type, resource_id) == ("ICON", 1): return "system-resolved", "GROUP_ICON:996 -> USER:LoadIcon" if resource_type == "VERSION": return "system-resolved", "Windows resource/version consumers; no in-image call" if resource_id in {32514, 32517}: return "fully-mapped", "1008:1607,1008:2996,1008:2a45,1008:2ae3" return "linked-unused", "none (complete verified template-selector inventory)" def build_rows() -> list[dict[str, str]]: target = (ROOT / "TDKPIN.EXE").read_bytes() assert hashlib.sha256(target).hexdigest() == TARGET_SHA256 manifest = json.loads((ROOT / "assets/manifest.json").read_text(encoding="utf-8")) assert manifest["source_sha256"] == TARGET_SHA256 assert manifest["resource_count"] == len(manifest["resources"]) == 63 functions = verified_function_addresses() require_closed_ledger("IMPORT_RECONSTRUCTION.tsv") require_closed_ledger("DATA_RECONSTRUCTION.tsv") require_closed_ledger("DATA_COVERAGE.tsv") rows = [] for entry in manifest["resources"]: resource_type = entry["type"] resource_id = int(entry["id"]) raw = (ROOT / entry["raw_path"]).read_bytes() start = entry["file_offset"] end = start + entry["allocated_size"] assert raw == target[start:end] assert hashlib.sha256(raw).hexdigest() == entry["raw_sha256"] stage, users = mapping(resource_type, resource_id) for token in users.split(","): token = token.strip() if len(token) == 9 and token[4] == ":": assert token in functions, (resource_type, resource_id, token) if stage == "linked-unused": notes = ( "Lossless extraction and decoded template structure are verified; " "the complete selector inventory across 391 verified functions has no user, " "so this Borland library template is explicitly retained as unused." ) elif stage == "system-resolved": notes = ( "Lossless extraction is verified; the Windows resource-system relationship " "and the absence of any additional in-image loader are fully accounted." ) else: notes = ( "Lossless extraction, decoded derivative, exact load/use/release lifecycle, " "and complete verified function closure are represented in reconstructed/" "tdkpin_resources.c and the cited reconstruction units." ) rows.append({ "type": resource_type, "id": str(resource_id), "file_offset": str(entry["file_offset"]), "allocated_size": str(entry["allocated_size"]), "raw_sha256": entry["raw_sha256"], "raw_path": entry["raw_path"], "decoded_paths": ",".join(entry.get("decoded_paths", [])), "status": "verified", "stage": stage, "users": users, "behavior": role(resource_type, resource_id) + ".", "notes": notes, }) assert len({(row["type"], row["id"]) for row in rows}) == 63 return rows def render(rows: list[dict[str, str]]) -> str: stream = io.StringIO(newline="") writer = csv.DictWriter(stream, fieldnames=FIELDS, delimiter="\t", lineterminator="\n") writer.writeheader() writer.writerows(rows) return stream.getvalue() def main() -> None: parser = argparse.ArgumentParser() parser.add_argument("--check", action="store_true") args = parser.parse_args() output = render(build_rows()) destination = ROOT / "RESOURCE_RECONSTRUCTION.tsv" if args.check: assert destination.read_text(encoding="utf-8") == output else: destination.write_text(output, encoding="utf-8") if __name__ == "__main__": main()