#!/usr/bin/env python3 """Find the TMS34010 bit-address constants in a VT1200 ROM image. The firmware stores absolute **bit** addresses: a 32-bit value ``P`` refers to physical byte ``P >> 3``. A byte-addressed tool such as Ghidra therefore cannot resolve those constants directly. This tool scans an address-ordered image (see ``reconstruct.py``) for 32-bit little-endian values whose ``>> 3`` falls inside the ROM, and reports where each one points. Typical uses: python3 tools/ptr_constants.py vt1200_v22.bin python3 tools/ptr_constants.py vt1200_v22.bin --tsv pointers.tsv python3 tools/ptr_constants.py vt1200_v22.bin --strings The report is the input for the next step: a Ghidra script that rewrites these constants (``P`` -> ``P >> 3``) so cross-references resolve. See MEMORY_MAP.md. """ import argparse import struct import sys ROM_BASE = 0x07E40000 # physical byte base of the ROM DEFAULT_STEP = 2 # TMS34010 is bit-addressed; 2-byte alignment MIN_STRING = 4 # printable run length that counts as a string def classify(image, target): """Return 'string' if *target* starts a printable run, else 'other'. Returns None when *target* is outside the image. """ if not 0 <= target < len(image): return None if 0x20 <= image[target] < 0x7F: end = target while end < len(image) and 0x20 <= image[end] < 0x7F: end += 1 if end - target >= MIN_STRING: return "string" return "other" def scan(image, base=ROM_BASE, step=DEFAULT_STEP): """Yield (offset, value, target_offset, kind) for each pointer constant. *offset* is the file offset of the 32-bit constant, *value* its raw value, *target_offset* the ROM-relative byte it refers to. """ size = len(image) for off in range(0, size - 3, step): value = struct.unpack_from("> 3) - base if 0 <= rel < size: yield off, value, rel, classify(image, rel) def cstring(image, offset, limit=64): end = offset while end < len(image) and end - offset < limit and 0x20 <= image[end] < 0x7F: end += 1 return image[offset:end].decode("latin1") def main(argv=None): ap = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter) ap.add_argument("image", help="address-ordered ROM image") ap.add_argument("--base", type=lambda s: int(s, 0), default=ROM_BASE, help=f"ROM physical byte base (default 0x{ROM_BASE:X})") ap.add_argument("--step", type=int, default=DEFAULT_STEP, help=f"scan step in bytes (default {DEFAULT_STEP})") ap.add_argument("--tsv", metavar="FILE", help="write every hit as offsetvaluetargetkind") ap.add_argument("--strings", action="store_true", help="list distinct string targets") args = ap.parse_args(argv) image = open(args.image, "rb").read() hits = list(scan(image, base=args.base, step=args.step)) strings = {} other = 0 for off, value, tgt, kind in hits: if kind == "string": strings.setdefault(tgt, (off, value)) else: other += 1 print(f"image : {args.image} ({len(image)} bytes)") print(f"pointer constants: {len(hits)} " f"({len(strings)} distinct string targets, {other} other)") print(f"ROM byte range : 0x{args.base:08X}-0x{args.base + len(image) - 1:08X}") print(f"bit range : 0x{args.base << 3:08X}-0x{((args.base + len(image)) << 3) - 1:08X}") if args.strings: print(f"\ndistinct string targets ({len(strings)}):") for tgt in sorted(strings): off, value = strings[tgt] print(f" 0x{tgt:06X} <- 0x{value:08X} @file 0x{off:06X} {cstring(image, tgt)!r}") if args.tsv: with open(args.tsv, "w") as fh: fh.write("offset\tvalue\ttarget\tkind\n") for off, value, tgt, kind in hits: fh.write(f"0x{off:06X}\t0x{value:08X}\t0x{tgt:06X}\t{kind}\n") print(f"\nwrote {len(hits)} hits to {args.tsv}") return 0 if __name__ == "__main__": sys.exit(main())