M2 format¶
The M2 model format (doodads, creatures, characters, spell effects),
modelled as versioned entities across every targeted client — vanilla's
monolithic MD20 through the Legion+ chunked shell. M2 is the common base;
each expansion has a concrete subclass (M2Wotlk, M2Shadowlands, …)
exposing that version's layout. Satellite files bake into the entity on
read() and split back out on write():
.skinLOD views (WotLK+; embedded in the body before that) land inskins/lod_skins..animexternal sequences — a sequence with(flags & 0x130) == 0keeps its track data in a per-sequence file; reading merges it into the tracks, writing re-splits it from the sequence flags..skelshared skeletons (Legion 7.3+) are first-class:Skeletonreads/writes standalone (they are shared between models via the parent link), and a skel-based model carries one fully decoded onskel..bonefacial poses and the opaque.physphysics blob ride along on the Legion+ assembly.
Pages:
- M2 entity — the user-facing compound
M2(the assembly with the body, chunks and every satellite baked in), plusSkin,SkeletonandBoneFile. - M2 root — the MD20 body: every field with the expansion range it is available in (generated from the sources, so the ranges never drift).
- M2 chunks — the Legion+ chunked shell, each chunk with its FourCC and expansion badges.
- M2 records — the decoded data records (sequences, bones, animation tracks, textures, cameras, emitters, skin tables).
Version-agnostic unions¶
Each family exposes an Any… type alias — the union of all its per-version
classes, bound as a real types.UnionType on its module (importable, and usable
in isinstance on Python ≥ 3.10). It is the runtime return type of
for_version(expansion: Expansion) and the natural annotation when your code
handles any version:
| Alias | Module | Union of |
|---|---|---|
AnyM2 |
wowlib.formats.m2 |
every M2⟨version⟩ |
AnyM2Root |
wowlib.formats.m2.root |
every M2Root⟨version⟩ |
AnyM2ChunkedFile |
wowlib.formats.m2.chunked |
every Legion+ M2ChunkedFile⟨version⟩ |
AnySkin |
wowlib.formats.m2.skin |
every WotLK+ Skin⟨version⟩ |
AnySkeleton |
wowlib.formats.m2 |
every Legion+ Skeleton⟨version⟩ |
Offset format: canonical writes, semantic round-trip
Unlike the chunked formats, M2's wire layout is offset-addressed, and wowlib re-lays it out canonically on every write. The guarantee is semantic: a written model re-reads equal (the chunked Legion+ shell itself still rewrites byte-for-byte while untouched).
Version-gated fields do not exist off-era
A version's class carries only the fields that client defines —
M2RootVanilla has skin_profiles (embedded views), which WotLK+
bodies drop in favour of external .skin files on the assembly's
skins; touching an off-era field raises AttributeError instead of
silently writing nothing. (Derived wire counters like the body's skin
count are stamped on write and hidden from Python entirely.)