The recovered system: fire channels = LBE4ControlsManager buttonGroups (0x40/0x45/0x46/0x47); default groups = the per-mech type-6 controls-map resource in BTL4.RES, installed by the T0 CreateStreamedMappings the port already called -- it needed only the TriggerState attribute (id 0x13 PINNED to the binary value; fireImpulse@0x31C is the binary's TriggerState) and an input feed. Keyboard/harness now push press/release edges into the button groups; the gBT*Trigger bypasses, per-type keyboard split and 1,0 pulse hack are retired -- weapons sharing a button fire TOGETHER (madcat Trigger = 4 weapons). Myomers @4b9550/@4b95b8 misattribution corrected (they are MechWeapon ConfigureMappables/ChooseButton). Verified 2-node: kill through the authentic chain (12 hits vs ~36 pre-groups). Config-mode session (regrouping UI) = the remaining stage, KB-scoped. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
65 lines
2.9 KiB
Python
65 lines
2.9 KiB
Python
#!/usr/bin/env python3
|
|
"""Census: per PATCH, list the exact chunk sequence (vertex chunks 0x80-0x8A,
|
|
face chunks 0x47/0x4d, specials 0x2037, material 0x2030) to learn how many
|
|
dpl_GEOMETRY objects the 1995 loader creates for a PUNCH-tagged geogroup and
|
|
in what list order. The i860 damageize handler (VREND.MNG @f040f6f8) writes
|
|
the three vr_damage tokens into the FIRST THREE geometries' geometry_type --
|
|
so the punch triple only functions if the patch has >=3 geometry chunks."""
|
|
import struct, sys
|
|
|
|
VSTRIDE = {0x80:12, 0x81:24, 0x82:28, 0x88:20, 0x89:32, 0x8A:36}
|
|
CONTAINERS = {0x40,0x41,0x42,0x46,0x48,0x70,0x10,0x20,0x30,0x3}
|
|
|
|
def census(path):
|
|
d = open(path,"rb").read()
|
|
out = []
|
|
def read_chunk(p, end):
|
|
if p+2 > end: return None
|
|
tw = struct.unpack_from("<H", d, p)[0]
|
|
cid = tw & 0x2fff
|
|
lw = 4 if (tw & 0x8000) else (2 if (tw & 0x4000) else 1)
|
|
if p+2+lw > end: return None
|
|
if lw==1: ln = d[p+2]
|
|
elif lw==2: ln = struct.unpack_from("<H", d, p+2)[0]
|
|
else: ln = struct.unpack_from("<I", d, p+2)[0]
|
|
body = p+2+lw
|
|
return cid, body, ln, body+ln
|
|
def walk(p, end, patch, depth):
|
|
while p < end:
|
|
r = read_chunk(p, end)
|
|
if not r: break
|
|
cid, body, ln, nxt = r
|
|
if cid == 0x42:
|
|
patch = {"seq":[], "mtl":"", "special":""}
|
|
out.append(patch)
|
|
walk(body, nxt, patch, depth+1)
|
|
elif cid == 0x2030 and patch is not None:
|
|
patch["mtl"] = d[body:body+ln].split(b'\0')[0].decode('latin1','ignore')
|
|
elif cid == 0x2037 and patch is not None:
|
|
patch["special"] = d[body:body+ln].split(b'\0')[0].decode('latin1','ignore')
|
|
patch["seq"].append(("SPECIAL", patch["special"][:40]))
|
|
elif cid in VSTRIDE and patch is not None:
|
|
patch["seq"].append(("VERT%02x" % cid, ln // VSTRIDE[cid]))
|
|
elif cid == 0x47 and patch is not None:
|
|
patch["seq"].append(("CONN", ln//4))
|
|
elif cid == 0x4d and patch is not None:
|
|
ppf = d[body] if ln else 0
|
|
patch["seq"].append(("PCONN(ppf=%d)" % ppf, (ln-1)//4))
|
|
elif cid in CONTAINERS:
|
|
walk(body, nxt, patch, depth+1)
|
|
else:
|
|
if patch is not None and cid not in (0x2046, 0x2031, 0x2032, 0x2033, 0x2034, 0x2035, 0x2036, 0x2039):
|
|
patch["seq"].append(("tag%04x" % cid, ln))
|
|
p = nxt
|
|
walk(8, len(d), None, 0)
|
|
return out
|
|
|
|
if __name__ == "__main__":
|
|
for path in sys.argv[1:]:
|
|
pts = census(path)
|
|
print("== %s: %d patches" % (path, len(pts)))
|
|
for i,pt in enumerate(pts):
|
|
tag = " PUNCH" if "PUNCH" in pt["special"] else ("" if not pt["special"] else " SP:"+pt["special"][:30])
|
|
print(" patch%-3d mtl=%-34s%s" % (i, pt["mtl"][:34], tag))
|
|
print(" seq: %s" % (pt["seq"],))
|