Files
tdkpin/original/tools/dump_original_objects.py
ddidderr 9d91cc51c7 feat(reverse): ledger every initialized game object
Add a reproducible dump of all 175 initialized collision and rule records from
the unmodified traceable Wine process. Preserve geometry, bounds, activation,
all four decoded Borland Real48 response values, flags, contact state, score,
layer mask, and render rectangle in a reviewable TSV.

This extends the static function ledger with the runtime-built data table needed
to replace inferred target, lock, gate, and mechanism logic in Rust.

Test Plan:
- generated `OBJECTS.tsv` contains one header plus 175 records
- repeat dump compared byte-for-byte equal with `cmp`
- `ruff check` on all original tracing tools -- passed
- `git diff --cached --check` -- passed
2026-08-22 21:07:18 +02:00

139 lines
4.3 KiB
Python
Executable File

#!/usr/bin/env python3
"""Dump the initialized 175-record collision/rule table from TDKPIN under Wine."""
from __future__ import annotations
import argparse
import csv
import math
import struct
import sys
from pathlib import Path
from trace_original_state import (
ProcessMemory,
find_winevdm_pid,
locate_data_segment,
verify_original,
)
OBJECT_COUNT = 175
OBJECT_STRIDE = 0x53
OBJECT_FIELD_BASE = 0x095B
DATA_SIZE = 0x4D3A
def decode_real48(data: bytes) -> float:
exponent = data[0]
if exponent == 0:
return 0.0
mantissa = int.from_bytes(data[1:], "little")
sign = -1.0 if mantissa & (1 << 39) else 1.0
fraction = mantissa & ((1 << 39) - 1)
return sign * math.ldexp(1.0 + fraction / (1 << 39), exponent - 129)
def real_text(value: float) -> str:
return format(value, ".12g")
def u16_at(data: bytes, offset: int) -> int:
return struct.unpack_from("<H", data, offset)[0]
def i32_at(data: bytes, offset: int) -> int:
return struct.unpack_from("<i", data, offset)[0]
def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--pid", type=int, help="Linux PID of the TDKPIN winevdm.exe process")
parser.add_argument("--output", type=Path, help="TSV path; stdout when omitted")
return parser.parse_args()
FIELDS = [
"id",
"type",
"subtype",
"active",
"bounds_min_x_milli",
"bounds_min_y_milli",
"bounds_max_x_milli",
"bounds_max_y_milli",
"point1_x_milli",
"point1_y_milli",
"point2_x_milli",
"point2_y_milli",
"radius_milli",
"response_normal",
"response_tangent",
"response_auxiliary",
"response_kick",
"flags",
"contact_state",
"score_low",
"score_high",
"layer_mask",
"render_left",
"render_top",
"render_right",
"render_bottom",
]
def main() -> None:
args = parse_args()
verify_original()
pid = args.pid if args.pid is not None else find_winevdm_pid()
output = args.output.open("w", newline="", encoding="utf-8") if args.output else sys.stdout
try:
writer = csv.writer(output, delimiter="\t", lineterminator="\n")
writer.writerow(FIELDS)
with ProcessMemory(pid) as memory:
data_base = locate_data_segment(memory)
data = memory.read(data_base, DATA_SIZE)
for object_id in range(1, OBJECT_COUNT + 1):
base = object_id * OBJECT_STRIDE
writer.writerow(
(
object_id,
data[base + OBJECT_FIELD_BASE],
data[base + 0x095C],
data[base + 0x098F],
i32_at(data, base + 0x095D),
i32_at(data, base + 0x0961),
i32_at(data, base + 0x0965),
i32_at(data, base + 0x0969),
i32_at(data, base + 0x096D),
i32_at(data, base + 0x0971),
i32_at(data, base + 0x0975),
i32_at(data, base + 0x0979),
real_text(decode_real48(data[base + 0x097D : base + 0x0983])),
real_text(decode_real48(data[base + 0x0983 : base + 0x0989])),
real_text(decode_real48(data[base + 0x0989 : base + 0x098F])),
real_text(decode_real48(data[base + 0x0990 : base + 0x0996])),
real_text(decode_real48(data[base + 0x0996 : base + 0x099C])),
f"0x{u16_at(data, base + 0x099C):04x}",
u16_at(data, base + 0x099E),
u16_at(data, base + 0x09A0),
u16_at(data, base + 0x09A2),
f"0x{u16_at(data, base + 0x09A4):04x}",
u16_at(data, base + 0x09A6),
u16_at(data, base + 0x09A8),
u16_at(data, base + 0x09AA),
u16_at(data, base + 0x09AC),
)
)
print(f"dumped {OBJECT_COUNT} objects from TDKPIN pid={pid}", file=sys.stderr)
finally:
if output is not sys.stdout:
output.close()
if __name__ == "__main__":
try:
main()
except (OSError, RuntimeError) as error:
raise SystemExit(f"dump_original_objects.py: {error}") from error