Source: src/public/studio.h in the ValveSoftware/source-sdk-2013 repository on
GitHub, read in full as a web page on 13 November 2026. It defines STUDIO_VERSION 48, so
these tables are version 48; version 49 is not in that file. Offsets and sizes are not printed in the
header: they are the sums of the declared field types for the 32-bit layout (4-byte int and
float, 12-byte Vector, 16-byte Quaternion, 48-byte
matrix3x4_t). All numbers are little-endian. Every …index is a byte offset; the
header’s own indexes are from the start of the file.
studiohdr_t
408 bytes at the start of the file.
| Offset | Size | Type | Name | Meaning |
|---|---|---|---|---|
| 0 | 4 | char[4] | id | IDST (49 44 53 54) |
| 4 | 4 | int32 | version | 48 in this header |
| 8 | 4 | int32 | checksum | Must be the same in the .phy and .vtx files (and the .vvd) for the model to load |
| 12 | 64 | char[64] | name | Model name. If studiohdr2index is non-zero and studiohdr2_t::sznameindex is non-zero, that longer name is used instead |
| 76 | 4 | int32 | length | File length |
| 80 | 12 | Vector | eyeposition | Ideal eye position |
| 92 | 12 | Vector | illumposition | Illumination centre |
| 104 | 12 | Vector | hull_min | Ideal movement hull, lower corner |
| 116 | 12 | Vector | hull_max | Ideal movement hull, upper corner |
| 128 | 12 | Vector | view_bbmin | Clipping bounding box, lower corner |
| 140 | 12 | Vector | view_bbmax | Clipping bounding box, upper corner |
| 152 | 4 | int32 | flags | See the flags table below |
| 156, 160 | 4, 4 | int32 | numbones, boneindex | mstudiobone_t, 216 bytes each |
| 164, 168 | 4, 4 | int32 | numbonecontrollers, bonecontrollerindex | mstudiobonecontroller_t, 56 bytes |
| 172, 176 | 4, 4 | int32 | numhitboxsets, hitboxsetindex | mstudiohitboxset_t, 12 bytes |
| 180, 184 | 4, 4 | int32 | numlocalanim, localanimindex | mstudioanimdesc_t, 100 bytes |
| 188, 192 | 4, 4 | int32 | numlocalseq, localseqindex | mstudioseqdesc_t, 212 bytes |
| 196, 200 | 4, 4 | int32 | activitylistversion, eventsindexed | Runtime initialisation flags (mutable) |
| 204, 208 | 4, 4 | int32 | numtextures, textureindex | mstudiotexture_t, 64 bytes |
| 212, 216 | 4, 4 | int32 | numcdtextures, cdtextureindex | Texture search paths: an array of offsets (from the file start) to strings |
| 220, 224, 228 | 4, 4, 4 | int32 | numskinref, numskinfamilies, skinindex | Replaceable texture table: short values, numskinref × numskinfamilies of them |
| 232, 236 | 4, 4 | int32 | numbodyparts, bodypartindex | mstudiobodyparts_t, 16 bytes |
| 240, 244 | 4, 4 | int32 | numlocalattachments, localattachmentindex | mstudioattachment_t, 92 bytes |
| 248, 252, 256 | 4, 4, 4 | int32 | numlocalnodes, localnodeindex, localnodenameindex | Transition graph: numlocalnodes² bytes at localnodeindex, and an array of name offsets at localnodenameindex |
| 260, 264 | 4, 4 | int32 | numflexdesc, flexdescindex | mstudioflexdesc_t, 4 bytes |
| 268, 272 | 4, 4 | int32 | numflexcontrollers, flexcontrollerindex | mstudioflexcontroller_t, 20 bytes |
| 276, 280 | 4, 4 | int32 | numflexrules, flexruleindex | mstudioflexrule_t, 12 bytes |
| 284, 288 | 4, 4 | int32 | numikchains, ikchainindex | mstudioikchain_t, 16 bytes |
| 292, 296 | 4, 4 | int32 | nummouths, mouthindex | mstudiomouth_t, 20 bytes |
| 300, 304 | 4, 4 | int32 | numlocalposeparameters, localposeparamindex | mstudioposeparamdesc_t, 20 bytes |
| 308 | 4 | int32 | surfacepropindex | Offset of the default surface property name |
| 312, 316 | 4, 4 | int32 | keyvalueindex, keyvaluesize | Key-values text |
| 320, 324 | 4, 4 | int32 | numlocalikautoplaylocks, localikautoplaylockindex | mstudioiklock_t, 32 bytes |
| 328 | 4 | float | mass | Collision model mass |
| 332 | 4 | int32 | contents | Collision contents |
| 336, 340 | 4, 4 | int32 | numincludemodels, includemodelindex | mstudiomodelgroup_t, 8 bytes: external animation and model files |
| 344 | 4 | pointer | virtualModel | Runtime pointer; unused_virtualModel (an int) in the 64-bit build |
| 348, 352, 356 | 4, 4, 4 | int32 | szanimblocknameindex, numanimblocks, animblockindex | Demand-loaded animation blocks: the .ani file name, and mstudioanimblock_t (8 bytes: datastart, dataend) |
| 360 | 4 | pointer | animblockModel | Runtime pointer |
| 364 | 4 | int32 | bonetablebynameindex | A byte table of bones sorted by name (GetBoneTableSortedByName) |
| 368, 372 | 4, 4 | pointer | pVertexBase, pIndexBase | Runtime pointers |
| 376 | 1 | byte | constdirectionallightdot | Used with STUDIOHDR_FLAGS_CONSTANT_DIRECTIONAL_LIGHT_DOT |
| 377 | 1 | byte | rootLOD | Desired root LOD, set on load |
| 378 | 1 | byte | numAllowedRootLODs | 0 means no restriction; N allows only LODs 0 to N − 1 as root |
| 379 | 1 | byte | unused | |
| 380 | 4 | int32 | unused4 | Zero it if the version is below 47 |
| 384, 388 | 4, 4 | int32 | numflexcontrollerui, flexcontrolleruiindex | mstudioflexcontrollerui_t, 20 bytes |
| 392 | 4 | float | flVertAnimFixedPointScale | Scale of vertex-animation fixed point values; used only if flag 0x00200000 is set, otherwise 1/4096 |
| 396 | 4 | int32 | unused3 | |
| 400 | 4 | int32 | studiohdr2index | Offset of the studiohdr2_t block (0 if none) |
| 404 | 4 | int32 | unused2 |
A comment in the header says that at the next format version studiohdr2_t should be merged into
studiohdr_t.
studiohdr2_t
256 bytes, at studiohdr2index. Offsets are from the start of this block, and the block’s
own indexes are relative to it.
| Offset | Size | Type | Name | Meaning |
|---|---|---|---|---|
| 0, 4 | 4, 4 | int32 | numsrcbonetransform, srcbonetransformindex | mstudiosrcbonetransform_t, 100 bytes each; see Skeleton |
| 8 | 4 | int32 | illumpositionattachmentindex | Attachment used as the illumination position |
| 12 | 4 | float | flMaxEyeDeflection | 0 means the default, 0.866 (cos 30°) |
| 16 | 4 | int32 | linearboneindex | mstudiolinearbone_t, 64 bytes; see Skeleton |
| 20 | 4 | int32 | sznameindex | Offset (from the start of this block) of the model name |
| 24, 28 | 4, 4 | int32 | m_nBoneFlexDriverCount, m_nBoneFlexDriverIndex | mstudioboneflexdriver_t, 24 bytes; see Skeleton |
| 32 | 224 | int32[56] | reserved | Reserved. In the 64-bit build the first slots instead hold four runtime pointers (virtualModel, animblockModel, pVertexBase, pIndexBase) and reserved shrinks to keep the block at 256 bytes |
Header flags
The values of studiohdr_t.flags.
| Value | Name | Meaning |
|---|---|---|
| 0x00000001 | STUDIOHDR_FLAGS_AUTOGENERATED_HITBOX | No hitbox information was specified |
| 0x00000002 | …_USES_ENV_CUBEMAP | Set at load time |
| 0x00000004 | …_FORCE_OPAQUE | Translucent parts present but not sorted |
| 0x00000008 | …_TRANSLUCENT_TWOPASS | Opaque parts drawn in the opaque pass, translucent in the translucent pass |
| 0x00000010 | …_STATIC_PROP | $staticprop: no bones and no transforms |
| 0x00000020 | …_USES_FB_TEXTURE | Set at load time |
| 0x00000040 | …_HASSHADOWLOD | A $shadowlod entry exists; the shadow LOD is the last LOD |
| 0x00000080 | …_USES_BUMPMAPPING | Set at load time |
| 0x00000100 | …_USE_SHADOWLOD_MATERIALS | Use the real materials on the shadow LOD |
| 0x00000200 | …_OBSOLETE | |
| 0x00000400 | …_UNUSED | |
| 0x00000800 | …_NO_FORCED_FADE | Set at build time |
| 0x00001000 | …_FORCE_PHONEME_CROSSFADE | NPC always checks two phonemes |
| 0x00002000 | …_CONSTANT_DIRECTIONAL_LIGHT_DOT | $constantdirectionallight; valid only with the static prop flag |
| 0x00004000 | …_FLEXES_CONVERTED | Delta flexes already converted from disk to memory format |
| 0x00008000 | …_BUILT_IN_PREVIEW_MODE | studiomdl ran in preview mode |
| 0x00010000 | …_AMBIENT_BOOST | Runtime flag |
| 0x00020000 | …_DO_NOT_CAST_SHADOWS | Useful on first-person models |
| 0x00040000 | …_CAST_TEXTURE_SHADOWS | Alpha textures cast shadows in VRAD; only for prop_static |
| 0x00200000 | …_VERT_ANIM_FIXED_POINT_SCALE | flVertAnimFixedPointScale is valid |
Limits
Constants in the header. The first two rows have smaller Xbox values.
| Constant | Value | Limit on |
|---|---|---|
MAXSTUDIOTRIANGLES | 65536 (25000 on Xbox) | Triangles |
MAXSTUDIOVERTS | 65536 (10000 on Xbox) | Vertices |
MAXSTUDIOFLEXVERTS | 10000 (1000 on Xbox) | Vertices that can be flexed per mesh |
MAXSTUDIOSKINS | 32 | Textures in total |
MAXSTUDIOBONES | 128 | Bones actually used (MAXSTUDIOBONEBITS 7 must match) |
MAXSTUDIOFLEXDESC | 1024 | Low-level flexes (morph targets) |
MAXSTUDIOFLEXCTRL | 96 | Flex controllers (input sliders) |
MAXSTUDIOPOSEPARAM | 24 | Pose parameters |
MAXSTUDIOBONECTRLS | 4 | Bone controllers |
MAXSTUDIOANIMBLOCKS | 256 | Animation blocks |
MAX_NUM_LODS | 8 | Levels of detail |
MAX_NUM_BONES_PER_VERT | 3 | Bones per vertex; the header notes that changing it changes the .vtx format |
Versions
See Versions on the overview. The routine
Studio_ConvertStudioHdrToNewVersion in the header accepts versions below 48 by
clearing the fields listed there and then setting the version to 48.