import struct, sys, collections

RES = r"C:\git\nick-games\decomp\BTL4.RES"
data = open(RES, "rb").read()

TYPE_NAMES = {
 0:"Null",1:"ModelList",2:"MapList",3:"AudioStreamList",4:"VideoList",5:"SubsystemList",
 6:"ControlMappingsList",7:"DamageZoneList",8:"SkeletonList",9:"BoxedSolidStream",
 10:"VideoModel",11:"StaticAudioStream",12:"InternalAudioStream",13:"ExternalAudioStream",
 14:"MakeMessageStream",15:"GameModel",16:"Animation",17:"SubsystemModelStream",
 18:"GaugeImageStream",19:"ControlMappingStream",20:"DamageZoneStream",21:"SkeletonStream",
 22:"DropZone",23:"EnvironmentZone",24:"InterestZone",25:"VehicleTable",26:"ExistanceBoxStream",
 27:"CameraStream",28:"RegistryStaticObjectStream",29:"DamageLookupTableStream",
 30:"ExplosionTableStream",31:"GaugeAlarmStream",32:"GaugeMissionReviewStream",33:"ScenarioRole"}

ver = data[0:4]
labOnly, maxID = struct.unpack_from("<ii", data, 4)
print("version bytes:", list(ver), "labOnly:", labOnly, "maxResourceID:", maxID)
off = 12
offsets = struct.unpack_from("<%dI" % maxID, data, off)

Res = collections.namedtuple("Res", "rid rtype name prio flags off length")
resources = {}
for o in offsets:
    if o == 0: continue
    rid, rtype = struct.unpack_from("<ii", data, o)
    name = data[o+8:o+40].split(b"\0")[0].decode("ascii", "replace")
    prio, flags, roff, rlen = struct.unpack_from("<iIII", data, o+40)
    resources[rid] = Res(rid, rtype, name, prio, flags, roff, rlen)

bytype = collections.Counter(r.rtype for r in resources.values())
print("\n== resource count per type ==")
for t, c in sorted(bytype.items()):
    print("type %2d %-28s : %d" % (t, TYPE_NAMES.get(t, "?"), c))

solids = [r for r in resources.values() if r.rtype == 9]
print("\n== BoxedSolidStream resources (%d) ==" % len(solids))
for r in sorted(solids, key=lambda x: x.name):
    print("  id=%4d name=%-14s bytes=%6d records=%s" % (r.rid, r.name, r.length, r.length//60))

maps14 = [r for r in resources.values() if r.rtype == 14]
print("\n== MakeMessageStream resources (%d) ==" % len(maps14))
for r in sorted(maps14, key=lambda x: x.name):
    print("  id=%4d name=%-14s bytes=%d" % (r.rid, r.name, r.length))

exist = [r for r in resources.values() if r.rtype == 26]
print("\n== ExistanceBoxStream (%d) ==" % len(exist))
for r in sorted(exist, key=lambda x: x.name):
    print("  id=%4d name=%-14s bytes=%d boxes=%s" % (r.rid, r.name, r.length, r.length//24))

# Model lists: type 1
mlists = {r.rid: r for r in resources.values() if r.rtype == 1}
def list_members(r):
    cnt = struct.unpack_from("<i", data, r.off)[0]
    ids = struct.unpack_from("<%di" % cnt, data, r.off+4)
    return ids

print("\n== ModelList count:", len(mlists))
# which model lists contain a BoxedSolidStream member?
with_solid = []
without_solid = []
for rid, r in mlists.items():
    try:
        ids = list_members(r)
    except Exception as e:
        print("  list decode fail", r.name, e); continue
    types = [resources[i].rtype if i in resources else None for i in ids]
    if 9 in types: with_solid.append(r.name)
    else: without_solid.append(r.name)
print("model lists WITH solid member: %d" % len(with_solid))
print("model lists WITHOUT solid member: %d" % len(without_solid))
