Combat: AUTHENTIC weapon groups -- streamed per-mech button bindings + the real fire chain (task #5)

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>
This commit is contained in:
arcattack
2026-07-11 13:06:29 -05:00
co-authored by Claude Fable 5
parent 4554ea543a
commit 8ed6184d65
582 changed files with 6546 additions and 42 deletions
+20 -10
View File
@@ -110,16 +110,26 @@ authentic path scoped.
- **MechControlsMapper look/eyepoint commit** — reconstructed but its offsets collide with declared
members; arbitrate before enabling (no port consumer yet). The fire-trigger-through-mapper is
still the `gBTWeaponTrigger` bring-up.
- **Weapon groups / per-button fire channels (decomp-anchored 2026-07-09)** — MechWeapon message
handlers id 9 **`ConfigureMappables`** (`FUN_004b9550`) and id 10 **`ChooseButton`**
(`FUN_004b95b8`), table @0x511860 region: ConfigureMappables binds the weapon's
`fireImpulse`@0x31C to mapper fire-channel #N (msg value 1-based; <1 unbinds; mapper = roster
slot 0, vtable +0x38 register / +0x3c unregister; weapon+0x110 = the bound-state), ChooseButton
re-binds (vtable +0x44) — the pod's in-cockpit weapon→button GROUPING. Weapons sharing a button
fire together; grouping was a PILOT choice (heat management: alpha vs split). The port's
SPACE=all-energy / CTRL=missiles split is a stand-in default; the authentic system needs the
mapper channel registration + who SENDS the default ConfigureMappables (mission/player setup —
not yet traced). Payoff: authentic weapon groups + a path to chain-fire.
- **Weapon groups — the AUTHENTIC defaults + fire path LIVE (task #5, 2026-07-11) [T1/T2].**
The system decoded end-to-end: a fire "channel" = a physical `buttonGroup` slot in the global
`LBE4ControlsManager` (fire buttons 0x40 Trigger / 0x45 Pinky / 0x46 ThumbLow / 0x47 ThumbHigh);
binding = a permanent mode-0x10000 DIRECT mapping button → the weapon's **TriggerState** attr
(id **0x13**, = `fireImpulse@0x31C`; the binary MechWeapon table's ONLY attribute @0x511890);
the weapon self-edge-detects (CheckFireEdge @4b9608). **The default grouping is NOT messages —
it is the per-mech type-6 controls-map resource in BTL4.RES** ("Thrustmaster"/"L4" variants;
e.g. madcat: Trigger={4 weapons}, ThumbHigh={2}), installed at `MakeViewpointEntity` by the
ENGINE's `CreateStreamedMappings` (T0, L4CTRL.cpp) — which the port already called; it needed
only the TriggerState attribute published (id PINNED to 0x13 — our attr-id chain is 6 short of
the binary's; full binary attr-table dump in the task #5 scan) + an input feed. Keyboard now
pushes press/release edges into the buttonGroups (SPACE→Trigger, '2'→ThumbLow, '3'/CTRL→
ThumbHigh; BT_AUTOFIRE pulses the Trigger); the gBT*Trigger bypasses + pulse hack are retired.
VERIFIED 2-node: full kill through the authentic chain (12 hits vs ~36 pre-groups = the
4-weapon trigger volley + crit cascade). **Remaining (the config-mode session):** handlers
id 9/10 (@4b9550/@4b95b8 — corrected: they were mislabeled as Myomers fns), the L4 mapper
Enter/ExitConfiguration completion (StartMappableButtonsConfigure + mode 0x8000), the
ConfigMapGauge unguard — the pod's in-cockpit REGROUPING UI (pilot heat-management choice);
mapper vtable corrections: +0x38 Enter / +0x3c Exit / +0x44 RIO AddOrErase (Thrustmaster
no-ops it).
- **Myomers authentic coupling** — the structural un-stub is INERT (mover feed + heat-gen no-op).
Real coupling needs the advanced-damage gate (`OwnerAdvancedDamage`/`FUN_004ad7d4` → the owning
**BTPlayer** `mech+0x190`+0x260, NOT 0xBD3 — see the mech+0x190 item above) + `MoverAttach` routing
+7 -13
View File
@@ -267,19 +267,13 @@ void
// what makes weapon self-heat propagate to the mech's heat sinks.
PoweredSubsystem::PoweredSubsystemSimulation(time_slice);
// E8 (bring-up trigger): write the weapon's fireImpulse from the player fire input
// (the controls mapper is bypassed). The triggers are pulsed per-FRAME by mech4
// (1,0,1,0...) so CheckFireEdge sees clean rising edges for repeated auto-fire under a
// held/forced trigger. HasActiveTarget (E3) limits firing to the mech with a locked
// target (the player) -- enemy emitters read this too but never fire (no target).
// WEAPON GROUPS (task #43, interim keyboard split standing in for the authentic
// ConfigureMappables/ChooseButton mapper channels): lasers (0xBC8=3016) and PPCs
// (0xBD4=3028) read SEPARATE fire channels, like two pod buttons.
extern int gBTWeaponTrigger; // channel 1: lasers
extern int gBTPPCTrigger; // channel 2: PPCs
fireImpulse = (Scalar)(((int)GetClassID() == 3028) ? gBTPPCTrigger
: gBTWeaponTrigger);
// AUTHENTIC TRIGGER (task #5): fireImpulse IS the published TriggerState
// attribute (id 0x13); the streamed per-mech controls map (type-6 resource,
// installed at MakeViewpointEntity) binds each grouped fire BUTTON directly
// onto it, and the keyboard/harness pushes press/release edges into the
// LBE4 buttonGroup channels (mech4). The old gBT*Trigger bypass + the
// per-type keyboard split are retired -- weapons sharing a button now fire
// TOGETHER (the pod's authored groups: e.g. madcat Trigger = 4 weapons).
Logical fireEdge = CheckFireEdge(); // @004b9608
targetWithinRange = UpdateTargeting(); // @004b9bdc -> this+0x34c
+53 -6
View File
@@ -112,6 +112,17 @@
//
#include <bt.hpp>
// LBE4ControlsManager (the fire buttonGroup push, task #5) -- needs the
// btl4app/btl4mode forward-decl order btl4mppr.cpp uses.
#if !defined(BTL4APP_HPP)
# include <btl4app.hpp>
#endif
#if !defined(BTL4MODE_HPP)
# include <btl4mode.hpp>
#endif
#if !defined(L4CTRL_HPP)
# include <l4ctrl.hpp>
#endif
#pragma hdrstop
#if !defined(MECH_HPP)
@@ -2990,12 +3001,48 @@ void
// (user-reported regression). targetInArc is the explicit BT_FIRE_ARC
// presentation clamp (default true). BT_AUTOFIRE holds all three.
{
const int laserWanted = gBTDrive.fireForced || gBTLaserKey;
const int ppcWanted = gBTDrive.fireForced || gBTPPCKey;
const int missileWanted = gBTDrive.fireForced || gBTMissileKey;
gBTWeaponTrigger = (laserWanted && targetInArc) ? (gBTWeaponTrigger ? 0 : 1) : 0;
gBTPPCTrigger = (ppcWanted && targetInArc) ? (gBTPPCTrigger ? 0 : 1) : 0;
gBTMissileTrigger = (missileWanted && targetInArc) ? (gBTMissileTrigger ? 0 : 1) : 0;
// AUTHENTIC FIRE INPUT (task #5): key states become PRESS/RELEASE
// edges pushed into the LBE4 buttonGroup channels -- the pod's own
// joystick path (L4CTRL Execute: Update(&v, mode) with v =
// +(button+1) press / -(button+1) release). The streamed per-mech
// controls map binds these buttons DIRECTLY onto each grouped
// weapon's TriggerState@0x31C; CheckFireEdge does the rest. This
// retires the gBT*Trigger globals + the 1,0 pulse hack.
// Desktop map: SPACE/'1' -> Trigger(0x40) [the main fire group],
// '2' -> ThumbLow(0x46), '3'/CTRL -> ThumbHigh(0x47) [missiles on
// most mechs]. BT_AUTOFIRE pulses the Trigger (edge per 2 frames).
// targetInArc stays the opt-in BT_FIRE_ARC presentation clamp.
LBE4ControlsManager *cm =
(LBE4ControlsManager *)application->GetControlsManager();
ModeMask modeMask = application->GetModeManager()->GetModeMask();
static int s_prevBtn[4] = {0, 0, 0, 0};
static int s_afPhase = 0;
s_afPhase ^= 1;
const int autofire = (gBTDrive.fireForced && targetInArc) ? s_afPhase : 0;
const int buttons[4] = {
LBE4ControlsManager::ButtonJoystickTrigger, // 0x40
LBE4ControlsManager::ButtonJoystickPinky, // 0x45
LBE4ControlsManager::ButtonJoystickThumbLow, // 0x46
LBE4ControlsManager::ButtonJoystickThumbHigh, // 0x47
};
int want[4];
want[0] = ((gBTLaserKey && targetInArc) || autofire) ? 1 : 0;
want[1] = 0; // pinky: unmapped on desktop
want[2] = (gBTPPCKey && targetInArc) ? 1 : 0;
want[3] = (gBTMissileKey && targetInArc) ? 1 : 0;
if (cm != 0)
{
for (int b = 0; b < 4; ++b)
{
if (want[b] != s_prevBtn[b])
{
s_prevBtn[b] = want[b];
ControlsButton v = (ControlsButton)(want[b]
? (buttons[b] + 1) : -(buttons[b] + 1));
cm->buttonGroup[buttons[b]].Update(&v, modeMask);
}
}
}
}
{
+4
View File
@@ -89,6 +89,10 @@ Simulation::AttributeIndexSet MechWeapon::AttributeIndex; // legacy empty member
const MechWeapon::IndexEntry
MechWeapon::AttributePointers[]=
{
// The authentic entry (binary table @0x511890): the streamed controls maps
// bind the pod fire buttons directly onto this Scalar -- fireImpulse IS the
// binary's "TriggerState" (the edge-detected fire channel destination).
ATTRIBUTE_ENTRY(MechWeapon, TriggerState, fireImpulse), // @0x31C id 0x13
ATTRIBUTE_ENTRY(MechWeapon, OutputVoltage, rechargeLevel), // @0x320 (charge/ready level)
ATTRIBUTE_ENTRY(MechWeapon, PercentDone, rechargeLevel) // @0x320 (recharge dial fraction)
};
+15 -2
View File
@@ -122,8 +122,21 @@ class CockpitHud;
//
public:
enum {
OutputVoltageAttributeID = PoweredSubsystem::NextAttributeID,
PercentDoneAttributeID,
// TriggerState is the BINARY's only MechWeapon attribute
// ({0x13, "TriggerState", +0x31d} @0x511890 -- fireImpulse@0x31C).
// The streamed per-mech controls maps (type-6 resources) bind the
// fire buttons DIRECTLY onto it by this NUMERIC id, so it is
// PINNED to the binary value: the binary's subsystem chain reaches
// 0x12 before MechWeapon (ConfigureActivePress@MechSubsystem=2,
// heat family 3..0xC, powered/aux-screen family 0xD..0x11 -- full
// dump in the task #5 attr-table scan), while our reconstruction
// publishes fewer base attrs (chain value 0xD). The id gap wastes
// a few dense-index slots (Build sizes by max id) -- harmless.
// OutputVoltage/PercentDone are port gauge additions
// (name-resolved) renumbered after it.
TriggerStateAttributeID = 0x13,
OutputVoltageAttributeID, // 0x14
PercentDoneAttributeID, // 0x15
NextAttributeID
};
private:
+8 -1
View File
@@ -464,9 +464,16 @@ void Myomers::ResetToInitialState(Logical powered)
//***************************************************************************
// Myomers::ConnectToMover @004b9550
// Myomers::ConnectToMover (mislabeled)
//***************************************************************************
//
// ⚠ MISATTRIBUTION (task #5 correction): @004b9550/@004b95b8 are NOT Myomers
// functions -- the weapon message-table @0x511860 proves they are
// MechWeapon::ConfigureMappables (id 9) / ChooseButton (id 10), the weapon->
// button grouping handlers (the +0x31C coincidence: Myomers speedEffect vs
// MechWeapon TriggerState). The bodies below are retained as the port's
// Myomers stand-ins but carry no binary address.
//
// Attaches the live SpeedEffect output (@0x31C) into the Mech's JointedMover
// roster (the mover at **(Mech[0xD0]+0x128)). Driven by a connect message
// whose payload index is at message+0xC:
+4 -10
View File
@@ -534,21 +534,15 @@ void
DrawFiringCharge(time_slice); // @004bbc78 -- bleed recoil (+0x3E8)
UpdateEject(time_slice); // @004bbb50 -- idle unless ejectState==0
// 2. Rising-edge trigger from the owning Mech's discharge signal.
// Bring-up: the controls mapper is bypassed, so drive fireImpulse from the
// per-frame trigger pulse -- else CheckFireEdge never sees an edge and a
// projectile weapon never fires. SPLIT CONTROLS (interim, toward the real
// controls-mapper weapon groups): projectile weapons (the missile
// launchers, cannon) fire on the MISSILE channel (CTRL), not the
// energy-weapon channel (SPACE) -- one key no longer fires everything.
extern int gBTMissileTrigger;
fireImpulse = (Scalar)gBTMissileTrigger; // this+0x31C
// 2. AUTHENTIC TRIGGER (task #5): fireImpulse = the TriggerState attribute
// the streamed controls map binds to the pod fire buttons (see emitter.cpp
// note). The gBTMissileTrigger bypass is retired.
Logical trigger = CheckFireEdge(); // MechWeapon @004b9608
static int s_pwtick = 0;
if (getenv("BT_PROJ_LOG") && (((++s_pwtick) % 240) == 0 || trigger))
DEBUG_STREAM << "[projweap] tick " << GetName() << " trig=" << (int)trigger
<< " gTrig=" << gBTMissileTrigger << " ready=" << (int)ReadyToFire()
<< " trigState=" << (float)fireImpulse << " ready=" << (int)ReadyToFire()
<< " state=" << weaponAlarm.GetState() << " recoil=" << recoil << " x\n" << std::flush;
// 3. Firing state machine (weaponAlarm @0x350). Modelled on the RP analog
Binary file not shown.

After

Width:  |  Height:  |  Size: 114 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 185 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 123 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 123 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 147 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 122 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 163 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 254 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 290 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 117 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 118 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 104 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 161 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 204 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 224 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 276 KiB

Binary file not shown.
File diff suppressed because one or more lines are too long
View File
+9
View File
@@ -0,0 +1,9 @@
from PIL import Image
import numpy as np
a=np.array(Image.open('scratchpad/cop_fwd_s.png').convert('RGB').resize((360,270)))
b=np.array(Image.open('scratchpad/cop_flip_s.png').convert('RGB').resize((360,270)))
gap=np.full((270,6,3),255,np.uint8)
Image.fromarray(np.concatenate([a,gap,b],axis=1)).save('scratchpad/cop_ab.png')
def stat(x): return "mean=%s greenfrac=%.2f"%(x.reshape(-1,3).mean(0).round(0), ((x[:,:,1]>100)&(x[:,:,0]<80)).mean())
print("forward:",stat(a)); print("flip: ",stat(b))
print("saved cop_ab.png [forward | flip]")
Binary file not shown.

After

Width:  |  Height:  |  Size: 105 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 105 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 125 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 125 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 140 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 140 KiB

+102
View File
@@ -0,0 +1,102 @@
#!/usr/bin/env python3
"""Robust BGF (DIV-BIZ2) parser for the cockpit-shell reconstruction harness.
Handles the vertex-format chunks the engine loader (bgfload.cpp) does:
0x80 XYZ(12) 0x81 XYZ_N(24) 0x82 XYZ_RGBA(28)
0x88 XYZ_UV(20) 0x89 XYZ_N_UV(32) 0x8A XYZ_RGBA_UV(36)
Faces: 0x47 index list (u32 tri indices), 0x4d poly list (ppf-prefixed u32).
Tree: 0x40 container / 0x41 object / 0x42 patch / 0x46 mesh; 0x2037 SV_SPECIAL
(carries "PUNCH"); 0x2030 material name. Face indices are patch-local (base=patch
vert start). Returns per-patch geometry so we see frame vs glass vs punch."""
import struct
VSTRIDE = {0x80:12, 0x81:24, 0x82:28, 0x88:20, 0x89:32, 0x8A:36}
VHASN = {0x81, 0x89}
VHASUV = {0x88, 0x89, 0x8A}
CONTAINERS = {0x40,0x41,0x42,0x46,0x48,0x70,0x10,0x20,0x30,0x3}
def parse(path):
d = open(path,"rb").read()
patches = [] # each: dict(material, punch, verts[list (x,y,z,has_n,nx,ny,nz,u,v)], faces[list (a,b,c)])
cur = {"material":"", "punch":False}
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):
while p < end:
r = read_chunk(p, end)
if not r: break
cid, body, ln, nxt = r
if cid == 0x42: # new patch
patch = {"material":"", "punch":False, "verts":[], "faces":[]}
patches.append(patch)
walk(body, nxt, patch)
elif cid == 0x2030: # material name (string)
if patch is not None:
patch["material"] = d[body:body+ln].split(b'\0')[0].decode('latin1','ignore')
elif cid == 0x2037: # SV_SPECIAL -> PUNCH?
if patch is not None and b"PUNCH" in d[body:body+ln]:
patch["punch"] = True
elif cid in VSTRIDE: # vertex chunk -> this submesh's face indices are
st = VSTRIDE[cid]; hasn = cid in VHASN; hasuv = cid in VHASUV
patch["_base"] = len(patch["verts"]) # local to THIS chunk (base offset)
n = ln // st
for i in range(n):
o = body + i*st
x,y,z = struct.unpack_from("<fff", d, o)
nx=ny=nz=0.0; u=v=0.0; off=12
if hasn:
nx,ny,nz = struct.unpack_from("<fff", d, o+off); off+=12
if hasuv:
u,v = struct.unpack_from("<ff", d, o+off)
patch["verts"].append((x,y,z,hasn,nx,ny,nz,u,v))
elif cid == 0x47: # tri index list (indices local to last vertex chunk)
b = patch.get("_base",0); n = ln//4
idx = [struct.unpack_from("<I", d, body+i*4)[0] for i in range(n)]
for f in range(0,(n//3)*3,3):
patch["faces"].append((b+idx[f],b+idx[f+1],b+idx[f+2]))
elif cid == 0x4d: # poly list (ppf-prefixed)
if ln>=1:
b = patch.get("_base",0); ppf = d[body]; n=(ln-1)//4
idx=[struct.unpack_from("<I",d,body+1+i*4)[0] for i in range(n)]
for f in range(0,(n//ppf)*ppf,ppf):
poly=[idx[f+j] for j in range(ppf)]
for k in range(1,ppf-1):
patch["faces"].append((b+poly[0],b+poly[k],b+poly[k+1]))
elif cid in CONTAINERS:
walk(body, nxt, patch)
p = nxt
walk(8, len(d), None)
return [pt for pt in patches if pt["verts"] and pt["faces"]]
if __name__ == "__main__":
import sys, numpy as np
from collections import Counter
path = sys.argv[1] if len(sys.argv)>1 else "content/VIDEO/GEO/MAX_COP.BGF"
P = parse(path)
print(f"{path}: {len(P)} patches")
allV=[]; ec=Counter(); tot_faces=0; punch_faces=0
for i,pt in enumerate(P):
V=np.array([(v[0],v[1],v[2]) for v in pt["verts"]])
hasuv = any(v[7]!=0 or v[8]!=0 for v in pt["verts"])
hasn = any(v[3] for v in pt["verts"])
F=pt["faces"]
lo=V.min(0); hi=V.max(0); cen=V.mean(0)
for a,b,c in F:
if max(a,b,c)>=len(V): continue
for e in ((a,b),(b,c),(c,a)): ec[tuple(sorted(e))]+=1
tot_faces+=len(F)
if pt["punch"]: punch_faces+=len(F)
print(f" patch{i}: mtl='{pt['material'][:30]}' punch={pt['punch']} "
f"verts={len(V)} faces={len(F)} uv={hasuv} n={hasn} "
f"cen=({cen[0]:.2f},{cen[1]:.2f},{cen[2]:.2f}) "
f"bbox=[{lo[0]:.2f},{lo[1]:.2f},{lo[2]:.2f}]..[{hi[0]:.2f},{hi[1]:.2f},{hi[2]:.2f}]")
bound=sum(1 for k,vv in ec.items() if vv==1)
print(f"TOTAL faces={tot_faces} punch_faces={punch_faces} boundary_edges={bound}/{len(ec)}")
+51
View File
@@ -0,0 +1,51 @@
import struct,sys
def rdU16(b,p): return struct.unpack_from('<H',b,p)[0]
def rdU32(b,p): return struct.unpack_from('<I',b,p)[0]
def rdF32(b,p): return struct.unpack_from('<f',b,p)[0]
TAGNAME={0x0003:'HEADER',0x0005:'BIZ_DONE',0x0040:'OBJECT',0x0041:'LOD',0x0042:'PATCH',
0x0046:'PMESH',0x0047:'CONN_LIST',0x0048:'SPHERE_LIST',0x004d:'PCONN_LIST',0x0070:'BOUND',
0x0080:'VTX_XYZ',0x0081:'VTX_XYZ_N',0x0082:'VTX_XYZ_RGBA',0x0088:'VTX_XYZ_UV',
0x0089:'VTX_XYZ_N_UV',0x008A:'VTX_XYZ_RGBA_UV',0x2030:'SV_F_MATERIAL',0x2037:'SV_SPECIAL',
0x0010:'TEXTURE',0x0011:'TEXTURE_MAP',0x0018:'BITSLICE',0x0020:'MATERIAL',
0x0021:'MAT_TEXTURE',0x0023:'AMBIENT',0x0024:'DIFFUSE',0x0026:'EMISSIVE',
0x0028:'RAMP_REF',0x0030:'RAMP',0x0031:'RAMP_DATA',0x2008:'NAME',
0x0043:'FACE',0x0044:'FACE?',0x0045:'FACE?'}
CONTAINERS={0x0003,0x0070,0x0040,0x0041,0x0042,0x0046,0x0048,0x0010,0x0020,0x0030}
def parse(b,p,end,depth,out):
while p+3<=end:
tw=rdU16(b,p); p+=2
idv=tw & 0x2fff
lw=4 if (tw&0x8000) else (2 if (tw&0x4000) else 1)
if p+lw>end: break
ln = b[p] if lw==1 else (rdU16(b,p) if lw==2 else rdU32(b,p))
p+=lw
if p+ln>end: break
out.append((depth,idv,p,ln))
if idv in CONTAINERS:
parse(b,p,p+ln,depth+1,out)
p+=ln
if idv==0x0005: break
return
fn=sys.argv[1]
b=open(fn,'rb').read()
assert b[:8]==b'DIV-BIZ2', b[:8]
out=[]
parse(b,8,len(b),0,out)
for depth,idv,p,ln in out:
nm=TAGNAME.get(idv,'?%04x'%idv)
extra=''
if idv==0x2008 or idv==0x0011 or idv==0x0028:
s=b[p:p+ln].split(b'\0')[0].decode('latin1',errors='replace')
extra=' "%s"'%s
if idv==0x2037: # SV_SPECIAL
s=b[p:p+ln].split(b'\0')[0].decode('latin1',errors='replace')
extra=' SPECIAL="%s"'%s
if idv==0x2030: # SV_F_MATERIAL
s=b[p:p+ln].split(b'\0')[0].decode('latin1',errors='replace')
extra=' MAT="%s" (len %d)'%(s,ln)
if idv==0x0018 and ln>=1: extra=' slice=%d'%b[p]
print(' '*depth + '%-14s off=0x%04x len=%d%s'%(nm,p,ln,extra))
Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 331 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 231 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 233 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 242 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 156 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 200 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 134 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 140 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 140 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 138 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 168 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 168 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 93 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 93 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 98 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 98 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 156 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 156 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 232 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 142 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 133 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 292 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 250 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 297 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 432 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 358 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 183 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 252 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 365 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 368 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 368 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 459 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 127 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 127 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 432 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 109 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 105 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 121 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 118 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 109 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 115 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 118 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 115 KiB

Some files were not shown because too many files have changed in this diff Show More