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
129 lines
4.1 KiB
Python
129 lines
4.1 KiB
Python
#!/usr/bin/env python3
|
|
"""Generate typed 1000:0270 collision-call data from preserved raw evidence."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
from pathlib import Path
|
|
|
|
ROOT = Path(__file__).resolve().parent.parent
|
|
RAW = ROOT / "TDKPIN_GHIDRA_COMPLETE_RAW.c"
|
|
OUTPUT = ROOT / "reconstructed" / "tdkpin_game_setup_collision_data.inc"
|
|
|
|
|
|
def calls_from_raw() -> list[list[int]]:
|
|
text = RAW.read_text(encoding="utf-8")
|
|
start = text.index("/* ===== 1000:0270 ")
|
|
end = text.index("/* ===== 1000:51f5 ", start)
|
|
text = text[start:end]
|
|
needle = "FUN_1008_0138("
|
|
calls: list[list[int]] = []
|
|
position = 0
|
|
while True:
|
|
begin = text.find(needle, position)
|
|
if begin < 0:
|
|
break
|
|
cursor = begin + len(needle)
|
|
depth = 1
|
|
while depth:
|
|
character = text[cursor]
|
|
if character == "(":
|
|
depth += 1
|
|
elif character == ")":
|
|
depth -= 1
|
|
cursor += 1
|
|
arguments = text[begin + len(needle) : cursor - 1]
|
|
words = [
|
|
int(item.strip(), 0) & 0xFFFF
|
|
for item in arguments.replace("\n", "").split(",")
|
|
]
|
|
if len(words) != 40:
|
|
raise RuntimeError(f"expected 40 words, found {len(words)}")
|
|
calls.append(words)
|
|
position = cursor
|
|
if len(calls) != 177:
|
|
raise RuntimeError(f"expected 177 calls, found {len(calls)}")
|
|
return calls
|
|
|
|
|
|
def u16(value: int) -> str:
|
|
return f"0x{value:04x}u"
|
|
|
|
|
|
def i32(low: int, high: int) -> str:
|
|
value = low | high << 16
|
|
return f"(int32_t)UINT32_C(0x{value:08x})"
|
|
|
|
|
|
def real48(words: list[int]) -> str:
|
|
bytes_ = []
|
|
for word in words:
|
|
bytes_.extend((word & 0xFF, word >> 8))
|
|
return "{{" + ", ".join(f"0x{byte:02x}" for byte in bytes_) + "}}"
|
|
|
|
|
|
def initializer(words: list[int], ordinal: int) -> str:
|
|
fields = [
|
|
f".render_bottom={u16(words[0])}",
|
|
f".render_right={u16(words[1])}",
|
|
f".render_top={u16(words[2])}",
|
|
f".render_left={u16(words[3])}",
|
|
f".fallback_y_adjust={i32(words[4], words[5])}",
|
|
f".fallback_x_adjust={i32(words[6], words[7])}",
|
|
f".layer_mask={u16(words[8])}",
|
|
f".score_low={u16(words[9])}",
|
|
f".score_high={u16(words[10])}",
|
|
f".contact_state={u16(words[11])}",
|
|
f".flags={u16(words[12])}",
|
|
f".response_kick={real48(words[13:16])}",
|
|
f".response_auxiliary={real48(words[16:19])}",
|
|
f".response_tangent={real48(words[19:22])}",
|
|
f".response_normal={real48(words[22:25])}",
|
|
f".radius={i32(words[25], words[26])}",
|
|
f".point2_y={i32(words[27], words[28])}",
|
|
f".point2_x={i32(words[29], words[30])}",
|
|
f".point1_y={i32(words[31], words[32])}",
|
|
f".point1_x={i32(words[33], words[34])}",
|
|
f".coordinate_mode={u16(words[35])}",
|
|
f".subtype={u16(words[36])}",
|
|
f".type={u16(words[37])}",
|
|
f".active={u16(words[38])}",
|
|
f".index={u16(words[39])}",
|
|
]
|
|
return (
|
|
f" /* call {ordinal:03d}, record {words[39]:3d} */ "
|
|
"{" + ", ".join(fields) + "},"
|
|
)
|
|
|
|
|
|
def generated_text() -> str:
|
|
calls = calls_from_raw()
|
|
lines = [
|
|
"/* Generated by tools/generate_game_setup_collision_data.py.",
|
|
" * Source: TDKPIN_GHIDRA_COMPLETE_RAW.c, 1000:0270 call order.",
|
|
" * Do not hand-edit; run the generator and its --check mode.",
|
|
" */",
|
|
"static const TdkpinCollisionInitArgs g_game_setup_collision_calls[177] = {",
|
|
]
|
|
lines.extend(initializer(words, index + 1) for index, words in enumerate(calls))
|
|
lines.append("};")
|
|
lines.append("")
|
|
return "\n".join(lines)
|
|
|
|
|
|
def main() -> int:
|
|
parser = argparse.ArgumentParser()
|
|
parser.add_argument("--check", action="store_true")
|
|
arguments = parser.parse_args()
|
|
generated = generated_text()
|
|
if arguments.check:
|
|
if not OUTPUT.exists() or OUTPUT.read_text(encoding="utf-8") != generated:
|
|
raise SystemExit(f"generated collision data is stale: {OUTPUT}")
|
|
return 0
|
|
OUTPUT.write_text(generated, encoding="utf-8")
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|