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>
129 lines
5.1 KiB
Python
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()
|