Predator/IR vision: reverse-engineered from the original firmware and
confirmed by the build team -- it is the Division board's TEXTURE-VALUE RAMP
mode (a "check your texture maps" diagnostic the devs hijacked), NOT a
grayscale squash or a false-colour palette. Located in VREND.MNG (effect
handler @0xe6c0, wire action 0x1b, type -1 ON / -2 OFF); ramp colours from
VR_DRAW.C. Renderer reworked to match: vrview_gl now does the 4-ramp
lerp(color0,color1,luminance(texel)) in the mesh pass (grayscale+defog
removed). Live-rendered on a new night-clear arena egg; crew A/B verdict
pending.
Firmware-decomp toolchain (emulator/firmware-decomp/), all built from the
project's own artifacts and validated:
- coff860.py i860 COFF reader (symbols/sections), names match AS860 source
- derive860.py derives the i860 opcode map from matched .S<->.O pairs
- dis860.py i860 disassembler (98% on clean ground truth; proven on
VREND.MNG -- velocirender_statistics decodes correctly)
- sigmatch860.py reloc-invariant signature matcher onto the stripped image
- i860-encoding.md / FIRMWARE-SYMBOLS.txt / README.md
PVISION-IMPLEMENTATION-GUIDE.md: self-contained hand-off for the BT411 team.
HARDWARE-ARCHITECTURE.md + hardware-photos/ (15 board shots): the Division
VelociRender card is a 2-board stack driving a 3-processor pipeline --
INMOS IMS T425-J25S (comms/control, runs vrendmon.btl) + Intel i860 XP-50 (FP
geometry, runs vrender.mng) + Division PXPL IGC 5.2 ASIC with ~48x PXPL EMC
5.1 (UNC Pixel-Planes-5 SIMD array; "EMC" = the firmware's configEMCs) +
Analog Devices ADV7150 RAMDAC + NTSC. Plus the VWE Video Distribution Board
(P/N 1404: AMD MACH130 + 3x Brooktree Bt477) for the 3-VGA-head cockpit split.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
101 lines
4.3 KiB
Python
101 lines
4.3 KiB
Python
"""Empirically derive the i860 opcode table from matched .S (PGI/AS860 assembly)
|
||
and .O (COFF machine code) pairs. Byte-accounting is proven correct by
|
||
cross-checking every .S label against its .O symbol offset; only then is the
|
||
op6->mnemonic harvest trusted."""
|
||
import sys, struct, re, glob, os
|
||
sys.path.insert(0, r'C:\VWE\TeslaRel410\emulator\firmware-decomp')
|
||
from coff860 import parse
|
||
|
||
DATASZ = {'.long':4, '.word':4, '.int':4, '.float':4, '.single':4,
|
||
'.double':8, '.short':2, '.half':2, '.byte':1, '.string':None}
|
||
|
||
def parse_S(path):
|
||
"""Return (labels{name:off}, instrs[(off,mnem,line)]) for the .text section."""
|
||
labels, instrs = {}, []
|
||
off = 0; sect = '.text'; in_text = True
|
||
for raw in open(path, encoding='latin1'):
|
||
line = raw.split('//', 1)[0].rstrip()
|
||
s = line.strip()
|
||
if not s:
|
||
continue
|
||
# label(s): possibly "name:" / "name::" possibly followed by an instr
|
||
while True:
|
||
m = re.match(r'^([A-Za-z_.$][\w.$]*)::?\s*(.*)$', s)
|
||
if m and (m.group(2) == '' or not m.group(2).startswith('.')) and ':' in s[:m.end(1)+2]:
|
||
# confirm it's really a label (colon right after the name)
|
||
after = s[m.end(1):]
|
||
if after.startswith('::') or after.startswith(':'):
|
||
if in_text: labels[m.group(1)] = off
|
||
s = after.lstrip(':').strip()
|
||
if not s: break
|
||
continue
|
||
break
|
||
if not s:
|
||
continue
|
||
if s.startswith('.'):
|
||
d = s.split()[0].lower()
|
||
if d in ('.text',): sect='.text'; in_text=True
|
||
elif d in ('.data','.bss','.section'): sect=d; in_text=False
|
||
elif d == '.align' and in_text:
|
||
n = int(s.split()[1]); off = (off + n - 1) & ~(n - 1)
|
||
elif d in DATASZ and in_text:
|
||
sz = DATASZ[d]
|
||
if sz is None: # .string -> count bytes incl NUL
|
||
txt = s[s.index('"')+1:s.rindex('"')]
|
||
off += len(txt.encode('latin1').decode('unicode_escape')) + 1
|
||
else:
|
||
vals = s[len(d):].split(',')
|
||
off += sz * len([v for v in vals if v.strip()])
|
||
# other directives: no size
|
||
continue
|
||
if in_text:
|
||
mnem = s.split()[0]
|
||
instrs.append((off, mnem, s))
|
||
off += 4
|
||
return labels, instrs
|
||
|
||
def load_text(opath):
|
||
o = parse(opath); d = open(opath, 'rb').read()
|
||
text = syms = None
|
||
for (name, vaddr, size, scnptr, nreloc, sf) in o['sections']:
|
||
if name == '.text': text = d[scnptr:scnptr+size]
|
||
syms = {nm: v for (nm, v, sn, ty, sc, na) in o['syms'] if sn == 1 and sc in (2,3)}
|
||
return text, syms
|
||
|
||
def derive(spath, opath, table):
|
||
labels, instrs = parse_S(spath)
|
||
text, syms = load_text(opath)
|
||
# cross-check label offsets vs symbol table
|
||
common = [k for k in labels if k in syms]
|
||
good = [k for k in common if labels[k] == syms[k]]
|
||
bad = [(k, labels[k], syms[k]) for k in common if labels[k] != syms[k]]
|
||
tag = os.path.basename(opath)
|
||
print(f"[{tag}] instrs={len(instrs)} textwords={len(text)//4} "
|
||
f"labels_ok={len(good)}/{len(common)}", end='')
|
||
if bad:
|
||
print(f" MISALIGNED e.g. {bad[:3]}")
|
||
return False
|
||
print(" ALIGNED")
|
||
for (off, mnem, line) in instrs:
|
||
if off + 4 > len(text): break
|
||
w = struct.unpack_from('<I', text, off)[0]
|
||
op6 = (w >> 26) & 0x3f
|
||
table.setdefault(op6, {}).setdefault(mnem, 0)
|
||
table[op6][mnem] += 1
|
||
return True
|
||
|
||
if __name__ == '__main__':
|
||
base = r'C:\VWE\TeslaRel410\sda4\DPL3\VRENDER\AS860'
|
||
pairs = []
|
||
for s in glob.glob(os.path.join(base, '*.S')):
|
||
o = s[:-2] + '.O'
|
||
if os.path.exists(o): pairs.append((s, o))
|
||
table = {}
|
||
for s, o in sorted(pairs):
|
||
try: derive(s, o, table)
|
||
except Exception as e: print(f"[{os.path.basename(o)}] ERROR {e}")
|
||
print("\n=== derived op6 -> mnemonic(count) ===")
|
||
for op6 in sorted(table):
|
||
items = sorted(table[op6].items(), key=lambda x: -x[1])
|
||
print(f" {op6:#04x} ({op6:06b}): " + ", ".join(f"{m}×{c}" for m,c in items))
|