#!/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(" int: return struct.unpack_from(" 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