Files
CydandClaude Fable 5 afc3fd839e Vendor dpl3-revive: the Division/DPL3 renderer, now ours
Bring the graphics-dev collaborator's dpl3-revive into the repo as first-class
project code (they've handed it off; it's ours now). This is the proven
Division renderer that our in-process rt_draw has been trying to be.

What's here:
- parser/  B2Z/V2Z/SVT/SCN/SPL/BGF/BMF/BSL decoders (pure Python).
- spec/    reverse-engineered format + the definitive VelociRender wire
           protocol (from the original DIVISION source, matches our live
           VPX node/action tables exactly).
- source-ref/  read-only copies of the original DIVISION C (BIZREAD.C,
           DPLTYPES.H, DPL.H) that define the formats.
- patha/   the "virtual VelociRender board": vrboard.py (24-action protocol
           server), vrview.py (numpy software rasterizer, the reference),
           vrview_gl.py (moderngl GPU backend, 832x512@60Hz), plus the
           run/replay/regress tooling and evidence renders. Drives FLYK/BLADE/
           Star Trek demos AND our btl4opt/rpl4opt game binaries.
- viewer/  WebGL archive generators (.py); prebuilt HTML/data regeneratable.
- samples/ small test models/textures.
- bt*.raw.bin  real BTL4OPT arena wire captures (kept for offline renderer
           testing/regression against OUR game).

.gitignore keeps the multi-hundred-MB demo capture dumps + debug logs +
regeneratable viewer bundles out of history (they stay on disk).

Phase 0 of the integration is validated: their board decodes our bt8 capture
with zero errors (3748 nodes, 507 instances, 4 mechs) and renders our arena
(terrain/dome/sky, correct Division DAC gamma). Plan + status in memory;
integration continues in emulator/RENDERER-COLLAB.md.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-05 22:06:25 -05:00

129 lines
5.1 KiB
Python

#!/usr/bin/env python3
"""
hunt_actions.py -- static hunt for undecoded vr_action ids in shipped FLYK.EXE.
Method: parse the LE fixup tables to find code sites that reference a given
data-segment string (raw LE pages hold placeholders at fixup sites, so naive
scanning cannot work), then disassemble the surrounding function with capstone
and report the immediate constants loaded near velocirender_transmit calls --
the action id is among them.
python hunt_actions.py "get_geom_vertices" [path-to-exe]
"""
import os, sys, struct
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
from leimage import LE, rd
EXE = sys.argv[2] if len(sys.argv) > 2 else \
r"s:\OneDrive\Tesla III\DaveMcCoy\sda4\HPDAVE\FLYK.EXE"
def parse_fixups(le):
"""Yield (src_linear, target_obj, target_off) for 32-bit offset fixups."""
with open(le.path, 'rb') as f:
# page fixup offset table: n_pages+1 dwords
pgtab = rd(f, le.fixup_pg_off, 4 * (le.n_pages + 1))
offs = struct.unpack('<%dI' % (le.n_pages + 1), pgtab)
recs_base = le.fixup_rec_off
# page -> linear address mapping: need object bases + page ranges
objs = rd(f, le.obj_tab_off, 24 * le.n_objects)
page_lin = {}
for i in range(le.n_objects):
vsize, base, flags, pgidx, pgcnt, _ = struct.unpack_from('<6I', objs, i * 24)
for k in range(pgcnt):
page_lin[pgidx + k] = base + k * le.page_size
out = []
for pg in range(1, le.n_pages + 1):
lo, hi = offs[pg - 1], offs[pg]
if hi <= lo:
continue
blob = rd(f, recs_base + lo, hi - lo)
p = 0
while p < len(blob) - 3:
src = blob[p]; flags = blob[p + 1]; p += 2
srcoffs = []
if src & 0x20: # source list
cnt = blob[p]; p += 1
else:
srcoffs.append(struct.unpack_from('<h', blob, p)[0]); p += 2
cnt = 0
tobj_big = flags & 0x40
if tobj_big:
tobj = struct.unpack_from('<H', blob, p)[0]; p += 2
else:
tobj = blob[p]; p += 1
stype = src & 0x0F
toff = 0
if stype != 0x02: # not 16-bit selector fixup
if flags & 0x10:
toff = struct.unpack_from('<I', blob, p)[0]; p += 4
else:
toff = struct.unpack_from('<H', blob, p)[0]; p += 2
if src & 0x20:
for _ in range(cnt):
so = struct.unpack_from('<h', blob, p)[0]; p += 2
srcoffs.append(so)
if stype == 0x07: # 32-bit offset fixup
for so in srcoffs:
if pg in page_lin and 0 <= so < le.page_size:
out.append((page_lin[pg] + so, tobj, toff))
return out
def main():
pat = sys.argv[1].encode('latin1') if len(sys.argv) > 1 else b"get_geom_vertices"
le = LE(EXE)
base1, code = le.image(1)
base2, data = le.image(2)
print(f"code obj1 @ {base1:#x} ({len(code)} bytes), data obj2 @ {base2:#x}")
# all occurrences of the pattern in the data object
targets = []
i = 0
while True:
i = data.find(pat, i)
if i < 0:
break
s = max(0, i - 60)
start = data.rfind(b'\x00', s, i) + 1
targets.append((start, data[start:data.find(b'\x00', i)]))
i += 1
if not targets:
print("string not found"); return
for off, s in targets:
print(f"data+{off:#x} (lin {base2+off:#x}): {s[:70]!r}")
fixups = parse_fixups(le)
print(f"{len(fixups)} 32-bit fixups parsed")
import capstone
md = capstone.Cs(capstone.CS_ARCH_X86, capstone.CS_MODE_32)
md.detail = False
for off, s in targets:
want = {base2 + off}
sites = [src for src, tobj, toff in fixups
if tobj == 2 and (base2 + toff) in want]
print(f"\n=== refs to {s[:50]!r}: {len(sites)} site(s)")
for src in sites:
# disassemble a window before the reference site
lo = max(base1, src - 0x90)
blob = code[lo - base1: src - base1 + 0x60]
print(f"-- code around {src:#x}:")
for ins in md.disasm(blob, lo):
mark = ' <== strref' if ins.address <= src < ins.address + ins.size else ''
if ins.mnemonic in ('mov', 'push', 'call', 'cmp') or mark:
txt = f" {ins.address:#x}: {ins.mnemonic} {ins.op_str}{mark}"
if ins.mnemonic in ('mov', 'push') and ',' in ins.op_str:
try:
imm = int(ins.op_str.split(',')[1].strip(), 0)
if 0 < imm < 0x60:
txt += f" ** small imm {imm:#x}"
except ValueError:
pass
print(txt)
if __name__ == '__main__':
main()