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

98 lines
3.6 KiB
Python

#!/usr/bin/env python3
"""
hunt_pe.py -- static protocol hunt for the Borland PE game binaries
(RPL4OPT/BTL4OPT under 32RTM), the PE counterpart of hunt_actions.py (LE).
Find an error-string's data VA, locate the code xref (32-bit immediate),
and disassemble the surrounding function to read off the protocol constants
(expected reply actions, request sizes) that the DOS-side source never had.
python hunt_pe.py <exe> <needle-string> [--back N] [--fwd N]
"""
import struct
import sys
import capstone
def pe_sections(data):
e_lfanew = struct.unpack_from('<I', data, 0x3c)[0]
machine, nsec = struct.unpack_from('<HH', data, e_lfanew + 4)
opt_size = struct.unpack_from('<H', data, e_lfanew + 20)[0]
image_base = struct.unpack_from('<I', data, e_lfanew + 24 + 28)[0]
secs = []
off = e_lfanew + 24 + opt_size
for i in range(nsec):
name = data[off:off + 8].rstrip(b'\0').decode('latin1')
vsize, va, rsize, roff = struct.unpack_from('<4I', data, off + 8)
flags = struct.unpack_from('<I', data, off + 36)[0]
secs.append({'name': name, 'va': va, 'vsize': vsize,
'roff': roff, 'rsize': rsize, 'flags': flags})
off += 40
return image_base, secs
def file_to_va(secs, base, foff):
for s in secs:
if s['roff'] <= foff < s['roff'] + s['rsize']:
return base + s['va'] + (foff - s['roff'])
return None
def va_to_file(secs, base, va):
rva = va - base
for s in secs:
if s['va'] <= rva < s['va'] + max(s['vsize'], s['rsize']):
return s['roff'] + (rva - s['va'])
return None
def hunt(path, needle, back=0x120, fwd=0x60):
data = open(path, 'rb').read()
base, secs = pe_sections(data)
print(f"{path}: base {base:#x}, sections "
+ " ".join(f"{s['name']}@{s['va']:#x}" for s in secs))
hits = []
i = data.find(needle.encode('latin1'))
while i != -1:
hits.append(i); i = data.find(needle.encode('latin1'), i + 1)
if not hits:
raise SystemExit(f"string {needle!r} not found")
md = capstone.Cs(capstone.CS_ARCH_X86, capstone.CS_MODE_32)
md.detail = False
for h in hits:
# xrefs point at the start of the full string literal, which may begin
# before the needle -- scan back to the previous NUL
s0 = h
while s0 > 0 and data[s0 - 1] != 0:
s0 -= 1
va = file_to_va(secs, base, s0)
print(f"\nstring @ file {s0:#x} va {va:#x}: "
f"{data[s0:s0+70].rstrip(bytes(1))!r}")
if va is None:
continue
pat = struct.pack('<I', va)
j = data.find(pat)
while j != -1:
code_va = file_to_va(secs, base, j)
sec = next((s for s in secs if s['roff'] <= j < s['roff'] + s['rsize']), None)
if sec and code_va and (sec['flags'] & 0x20000000 or sec['name'] in ('.text', 'CODE')):
start = max(j - back, sec['roff'])
code = data[start:j + fwd]
print(f"--- xref imm @ file {j:#x} va {code_va:#x} ({sec['name']}) ---")
for ins in md.disasm(code, file_to_va(secs, base, start)):
mark = " <-- xref" if ins.address <= code_va < ins.address + ins.size else ""
print(f" {ins.address:#010x} {ins.mnemonic:8} {ins.op_str}{mark}")
j = data.find(pat, j + 1)
if __name__ == '__main__':
argv = [a for a in sys.argv[1:] if not a.startswith('--')]
kw = {}
for k in ('back', 'fwd'):
if f'--{k}' in sys.argv:
kw[k] = int(sys.argv[sys.argv.index(f'--{k}') + 1], 0)
hunt(argv[0], argv[1], **kw)