#!/usr/bin/env python3 """ contents.py - decompiler for a mech `.contents` file. `.contents` is the thin half of the pair `armature.py` already handles. It `!include`s `.armature` and then gives every joint and site exactly two entries: [joint_torso] Model=basic.data ExecutionState=AlwaysExecuteState Both values ride in the same CreateMessages `armature.py` walks -- the `.armature` source contributes a page's geometry, the `.contents` source contributes its Model and ExecutionState, and the packer merges them into one message per page. So nothing new has to be located: `Model` is `dataListID` (record id in the HIGH word) and `ExecutionState` is the enum at offset 76. Site pages are the exception. `{sites}` records store only name, rotation and translation, so a site's Model and ExecutionState are not in the package at all. They do not need to be: all **2926** site pages across the 89 mech `.contents` files carry the identical pair `basic.data` / `AlwaysExecuteState`, so they are emitted as constants rather than guessed per mech. python3 contents.py [-o out.contents] python3 contents.py --verify """ import argparse, collections, glob, os, re, struct, subprocess, sys sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) import mw4msg, subsystems MANIFEST = "/home/rich/Repositories/FS_Ours_extracted/_manifest.tsv" REC_RE = re.compile(r'^(?P.+?)\.contents\[(?P[^\]]+)\]\{(?Parmature|sites)\}$', re.I) EXEC_STATE = {1: "NeverExecuteState", 2: "AlwaysExecuteState", 6: "ActiveState"} SITE_DEFAULTS = [("Model", "basic.data"), ("ExecutionState", "AlwaysExecuteState")] EXEC_OFF = 76 DATALIST_OFF = 84 def relative(path, chassis, folder=None): """Model= is written relative to the mech folder, not from the package root. The package path uses the FOLDER name, which need not match the record stem (V4H's jenner2c/ holds jenner_2c.* records), so both are tried. """ if not path: return path for name in (chassis, folder): if name: path = re.sub(rf'^mechs[\\/]{re.escape(name)}[\\/]', '', path, flags=re.I) return path def decompile(mech_dir, manifest=None): """-> (chassis, [(pageName, [(key, value)])])""" manifest = manifest if manifest is not None else subsystems.load_manifest(MANIFEST) chassis = None folder = os.path.basename(mech_dir.rstrip("/")) pages = collections.OrderedDict() root = [p for p in glob.glob(os.path.join(mech_dir, "*.contents")) if not re.search(r'[\[\{]', os.path.basename(p))] for path in sorted(glob.glob(os.path.join(mech_dir, "*{armature}"))) + root: base = os.path.basename(path) m = REC_RE.match(base) if m: chassis = chassis or m.group("chassis") else: chassis = chassis or base[:-len(".contents")] with open(path, "rb") as fh: data = fh.read() for _off, msg in mw4msg.walk(data): name = mw4msg.joint_name(msg) if not name: continue model = manifest.get(mw4msg.record_id(msg, DATALIST_OFF)) state = EXEC_STATE.get(struct.unpack_from("