Replace the partial mechanics transcriptions with a separate, readable C11 reconstruction of the complete Win16 image while preserving the original raw Ghidra export as immutable evidence. Cover all ordinary and overlapping entry points, Borland runtime behavior, Win16 imports, segmented data, callbacks, resources, indirect control flow, physics, rendering, persistence, and startup/shutdown lifecycles. Add deterministic extraction and audit tooling plus address-linked ledgers for functions, imports, DGROUP ranges and objects, relocations, resources, and callbacks. The final gate records zero raw, partial, restored, unknown, blocked, or unclassified required units. Keep the semantic-fidelity boundary explicit: the portable C is not claimed to reproduce a byte-identical Borland NE build. Add strict focused harnesses for every reconstructed C unit, exact resource round-trip checks, and a 16-bit Borland Real48 reference probe. No Rust source or Cargo metadata is changed in this phase. Test Plan: - `bash original/tools/test_reconstructed_c.sh` -- passed - `bash original/tools/probe_real48_reference.sh` -- passed bit-for-bit - `python3 original/tools/audit_reconstruction.py --require-complete` -- passed - `git diff --cached --check` -- passed - `git diff HEAD -- '*.rs' Cargo.toml Cargo.lock` -- empty
243 lines
10 KiB
Python
243 lines
10 KiB
Python
#!/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()
|