#!/usr/bin/env python3 """Dumps a puppet .mdl file's MDLS bone array: parent index, bind-pose matrix determinant (to spot a mirrored/negative-scale bone), and translation - without needing a debug build. Mirrors the parsing in CImage.cpp's parsePuppetBones()/readPuppetMeshData(): MDLS header is 9 bytes, then nextSectionOffset(u32) + boneCount(u32), then per bone: null-terminated name + type(u32) + parent(i32) + matrixBytes(u32) + [16 floats if matrixBytes==64] + null-terminated extra string (empty for most rigs, jiggle/physics JSON for some). The 16 floats are the file's row-major matrix - reshaping them row-major (not glm's column-major convention) gives the actual authored matrix back, which is what this script does. Usage: mdl_bones.py Needs: pip install numpy """ import struct import sys import numpy as np def read_cstr(data, offset): end = data.index(b'\x00', offset) return data[offset:end].decode('latin1', errors='replace'), end + 1 def main(path): data = open(path, 'rb').read() marker_size = 9 mdls_offset = data.find(b'MDLS', marker_size) if mdls_offset < 0: raise SystemExit("no MDLS section found - not a puppet mesh, or a layout this script doesn't handle") print('MDLS at', mdls_offset, 'file size', len(data)) offset = mdls_offset + 9 # skip the MDLS0001-style 9-byte magic next_section_offset = struct.unpack_from(' 4096: raise SystemExit(f"bone {i} has an implausible matrix byte count ({matrix_bytes}), layout desynced") else: offset += matrix_bytes extra, offset = read_cstr(data, offset) extra_note = f" extra={extra}" if extra else "" if matrix is None: print(f"{i} parent={parent} name={name!r} NO MATRIX ({matrix_bytes} bytes){extra_note}") continue det = np.linalg.det(matrix[:3, :3]) translation = matrix[3, :3] # row-vector convention: translation lives in the last row flag = " <-- MIRRORED/NEGATIVE SCALE" if det < 0 else "" print(f"{i} parent={parent} name={name!r} det3x3={det:.5f} translation={translation}{flag}{extra_note}") if __name__ == '__main__': if len(sys.argv) != 2: print(__doc__) raise SystemExit(1) main(sys.argv[1])