import struct
data = open(r'C:/git/bt411/content/BTL4.RES','rb').read()
n = len(data)

def cstr(off, maxlen=32):
    s = data[off:off+maxlen].split(b'\0')[0]
    try: return s.decode('ascii')
    except: return None

def f32(off): return struct.unpack_from('<f', data, off)[0]
def i32(off): return struct.unpack_from('<i', data, off)[0]

DMGTYPE = {0:'Collision',1:'Ballistic',2:'Explosive',3:'Laser',4:'Energy'}

# find ALL subsystem records: printable name @+0, classID @+0x20 in 0xBB0..0xBE0, size @+0x24 plausible
recs = []
i = 0
while i < n - 0x28:
    cid = struct.unpack_from('<I', data, i+0x20)[0]
    if 0xBB0 <= cid <= 0xBE5:
        sz = struct.unpack_from('<I', data, i+0x24)[0]
        if 0xE0 <= sz <= 0x300:
            name = cstr(i)
            if name and 2 <= len(name) <= 31 and all(32 < ord(c) < 127 for c in name):
                # name buffer should be null-padded to 32
                pad = data[i+len(name):i+0x20]
                if pad.count(0) >= len(pad) - 2:
                    recs.append((i, name, cid, sz))
                    i += 0x28
                    continue
    i += 1

print(f'total subsystem records: {len(recs)}')

# group into chains (consecutive: next record at p+sz)
chains = []
cur = []
for k,(p,name,cid,sz) in enumerate(recs):
    if cur and cur[-1][0] + cur[-1][3] == p:
        cur.append(recs[k])
    else:
        if cur: chains.append(cur)
        cur = [recs[k]]
if cur: chains.append(cur)

# mech-name strings for labeling
mechnames = [b'madcat', b'bhk', b'vulture', b'thor', b'blackhawk', b'blh', b'owens', b'strider', b'sunder', b'cauldron', b'avatar', b'mdc']
low = data.lower()
namepos = []
for m in mechnames:
    j = 0
    while True:
        j = low.find(m, j)
        if j < 0: break
        namepos.append((j, m.decode()))
        j += 1
namepos.sort()

def nearest_label(p):
    best = None
    for pos, name in namepos:
        if pos < p: best = (pos, name)
        else: break
    return best

WEAP = {0xBC8:'Emitter', 0xBD4:'PPC', 0xBCD:'MechWeapon/Projectile', 0xBCE:'GaussRifle?', 0xBD0:'MissileLauncher'}

for ci, ch in enumerate(chains):
    lbl = nearest_label(ch[0][0])
    has_weap = any(c[2] in WEAP for c in ch)
    print(f'--- chain {ci} @0x{ch[0][0]:x} n={len(ch)} nearest-name={lbl}  roster={[c[1] for c in ch]}')
    for (p,name,cid,sz) in ch:
        if cid not in WEAP: continue
        rr, wr = f32(p+0x190), f32(p+0x194)
        dmg = f32(p+0x19C); dt = i32(p+0x1A0); heat = f32(p+0x1A4)
        pip = i32(p+0x1A8); pc = (f32(p+0x1AC), f32(p+0x1B0), f32(p+0x1B4)); per = i32(p+0x1B8)
        line = (f'    {name:24s} cid=0x{cid:X}({WEAP[cid]}) sz=0x{sz:X}  Recharge={rr:g} Range={wr:g} '
                f'Damage={dmg:g} Type={dt}({DMGTYPE.get(dt,"?")}) Heat={heat:g} Pip={pip} PipColor={pc} PipExt={per}')
        if cid in (0xBC8, 0xBD4) and sz >= 0x1DC:
            gl, dtm = f32(p+0x1BC), f32(p+0x1C0)
            sv = [f32(p+0x1C4+4*k) for k in range(5)]
            ri = i32(p+0x1D8)
            line += f'  | GraphicLen={gl:g} DischargeTime={dtm:g} SeekV={["%g"%v for v in sv]} RecIdx={ri}'
        if cid in (0xBCD, 0xBD0) and sz >= 0x1D0:
            ti, abi = i32(p+0x1BC), i32(p+0x1C0)
            mtof, mvp, mjc = f32(p+0x1C4), f32(p+0x1C8), f32(p+0x1CC)
            line += f'  | TracerInt={ti} AmmoBin={abi} MinTOF={mtof:g} MinVolt%={mvp:g} MinJam={mjc:g}'
        if cid == 0xBD0 and sz >= 0x1E0:
            mc = i32(p+0x1D0); mv = (f32(p+0x1D4), f32(p+0x1D8), f32(p+0x1DC))
            line += f'  | MissileCount={mc} MuzzleVel={mv}'
        print(line)
