Skip to content

M2 entity

The user-facing compound M2 — the assembly with everything baked in (the MD20 body, the Legion+ chunks and every satellite file) — plus the other file-level entities of the family: the external Skin LOD view, the shared Skeleton and the .bone facial-pose file. Each family is documented as its version-agnostic base plus a representative per-version class, shown generically as M2⟨version⟩ — the field set is identical across the versions a family supports. Construct a concrete version with for_version(expansion); read/write speak the filesystem gateway.

The MD20 body (M2Root) and the Legion+ chunked shell (M2ChunkedFile) are the assembly's two halves; their field-by-field references, with expansion and FourCC badges, live on M2 root and M2 chunks.

The M2 assembly

M2

M2()

Bases: FileEntity

A whole model, abstract over the client version — the MD20 body with its satellite files (.skin, .anim) baked in. Construct the concrete version with M2.for_version(expansion), then read()/write(); the per-version M2* classes are subclasses. See https://wowdev.wiki/M2.

read

read(source: FileSystem, key: FileKey) -> None

Load the model and every satellite file it references (.skin/.anim/ .skel/.bone/.phys) from a client filesystem, replacing this entity's contents.

for_version staticmethod

for_version(expansion: Expansion) -> M2⟨version⟩

Construct the concrete M2 for a client version — the abstract M2 is never instantiated directly. The return type narrows per expansion (a typed overload per Expansion member), so for_version(Expansion.Wotlk) returns a M2Wotlk; a runtime Expansion value yields the AnyM2 union.

Parameters:

Name Type Description Default
source FileSystem

the filesystem gateway

required
key FileKey

the .m2 identity (path and/or FileDataID)

required

Returns:

Type Description
None

nothing; raises on a missing file or malformed model

write

write(dest: FileSystem, key: FileKey) -> None

Serialize the model, re-splitting and writing its satellite files, through the filesystem's project overlay; satellite names derive from the key.

Parameters:

Name Type Description Default
dest FileSystem

the filesystem gateway

required
key FileKey

the .m2 identity; must resolve to a path

required

Returns:

Type Description
None

nothing; raises when the key has no path or a file fails to write

convert

convert(target: Literal[Vanilla]) -> M2⟨version⟩
convert(target: Literal[Tbc]) -> M2⟨version⟩
convert(target: Literal[Wotlk]) -> M2⟨version⟩
convert(target: Literal[Cata]) -> M2⟨version⟩
convert(target: Literal[Mop]) -> M2⟨version⟩
convert(target: Literal[Wod]) -> M2⟨version⟩
convert(target: Literal[Legion]) -> M2⟨version⟩
convert(target: Literal[Bfa]) -> M2⟨version⟩
convert(target: Literal[Shadowlands]) -> M2⟨version⟩
convert(target: Literal[Dragonflight]) -> M2⟨version⟩
convert(target: Literal[TheWarWithin]) -> M2⟨version⟩
convert(target: Expansion) -> AnyM2

Rebuild this model as the target expansion's concrete class, stepping the version ladder one adjacent release at a time (this instance is left unchanged). The return type narrows when the target is a literal.

Parameters:

Name Type Description Default
target Literal[Vanilla]

the expansion to convert to

required

Returns:

Type Description
M2⟨version⟩

the converted model; raises when a ladder step is not implemented

validate

validate() -> ValidationReport

Check the logical integrity contracts this file must satisfy to LOAD in the client, which write() deliberately never enforces. Call it before writing when you want to know the result will load. A file read from a client and left unmodified reports no errors; warnings mark states real client files ship.

Returns:

Type Description
ValidationReport

every violated contract, each with its member path

ensure_valid

ensure_valid() -> None

Validate and raise on the first error instead of returning a report — the assert-style face of validate().

Returns:

Type Description
None

nothing; raises when validate() finds any error

M2⟨version⟩

M2⟨version⟩()

Bases: M2

A whole model for one client version: the MD20 body with skins and external sequence data baked in. A written model is canonical-layout and re-reads equal (no byte-perfect guarantee for offset formats). See https://wowdev.wiki/M2.

skins property

skins: list[Skin⟨version⟩]

The model's LOD views, in view order ("{model}0N.skin" files, WotLK+). The source of truth for the view count: write() stamps the body's num_skin_profiles layout field from this vector's length.

chunks property writable

The chunked .m2 stream (Legion+): the satellite chunks (FileDataIDs, extended particles, parent overrides) plus preserved unknown chunks. The MD21 transport blob inside is TRANSIENT: read() decodes it into root and drops the bytes; write() re-encodes root into the stream and refreshes the FileDataID chunks.

lod_skins property

lod_skins: list[Skin⟨version⟩]

The LOD-band skins (the SFID entries beyond num_skin_profiles), in band order.

phys property writable

phys: ChunkBlob

The referenced .phys file bytes (PFID), baked in verbatim — structured physics is a follow-up milestone. Inline physics (PFDC) stays a chunk on the stream.

skel property writable

The shared skeleton (SKID), fully baked in — bones, attachments, sequences and the parent link. Engaged exactly when the chunk stream carries a skeleton FileDataID; skeletons are shared between models, so edit with care or write the .skel standalone.

bone_files property

bone_files: list[BoneFile]

The .bone facial-pose files (BFID; the skeleton's when skel-based), in variant order.

root property writable

The MD20 body — the model's root, uniform across every era (pre-Legion it IS the file; Legion+ it is the decoded MD21 image, whose transport blob chunks drops after read).

validate

validate() -> ValidationReport

Check the logical integrity contracts this model must satisfy to LOAD in the client — across the body AND every skin — which write() deliberately never enforces. Call it before writing when you want to know the model will load. A model read from a client and left unmodified reports no errors; warnings mark states real client files ship.

Returns:

Type Description
ValidationReport

every violated contract, each with its member path

ValidationReport

("root..." / "skins[i]...")

ensure_valid

ensure_valid() -> None

Validate and raise on the first error instead of returning a report — the assert-style face of validate().

Returns:

Type Description
None

nothing; raises when validate() finds any error

Skin — the .skin LOD view

The external skin file (WotLK+; embedded in the body before that): submeshes, render batches and the index/triangle tables of one LOD view.

Skin

Skin()

An external model LOD view (.skin file), abstract over the client version. Construct a concrete version with Skin.for_version(expansion); the per-version Skin* classes are subclasses. See https://wowdev.wiki/M2/.skin.

for_version staticmethod

for_version(expansion: Expansion) -> Skin⟨version⟩

Construct the concrete Skin for a client version — the abstract Skin is never instantiated directly. The return type narrows per expansion (a typed overload per Expansion member), so for_version(Expansion.Wotlk) returns a SkinWotlk; a runtime Expansion value yields the AnySkin union.

validate

validate() -> ValidationReport

Check the logical integrity contracts this file must satisfy to LOAD in the client, which write() deliberately never enforces. Call it before writing when you want to know the result will load. A file read from a client and left unmodified reports no errors; warnings mark states real client files ship.

Returns:

Type Description
ValidationReport

every violated contract, each with its member path

ensure_valid

ensure_valid() -> None

Validate and raise on the first error instead of returning a report — the assert-style face of validate().

Returns:

Type Description
None

nothing; raises when validate() finds any error

Skin⟨version⟩

Skin⟨version⟩()

Bases: Skin

One external LOD view (.skin file, WotLK+): the 'SKIN' magic plus the profile tables (local lookups, submeshes, render batches). See https://wowdev.wiki/M2/.skin.

profile property writable

The LOD view's tables (local lookups, submeshes, batches).

read

read(data: bytes) -> None

Deserialize file bytes into this entity, replacing its contents. Offsets resolve against the given buffer; sequence-gated data is read inline.

Parameters:

Name Type Description Default
data bytes

the file (or containing-chunk payload) bytes

required

write

write() -> bytes

Serialize this entity in wowlib's canonical layout (an offset format has no byte-perfect round-trip guarantee; a written entity re-reads equal instead).

Returns:

Type Description
bytes

the file bytes

validate

validate() -> ValidationReport

Check the logical integrity contracts this file must satisfy to LOAD in the client — companion-array counts, lookup ranges — which write() deliberately never enforces. Call it before writing when you want to know the result will load. A file read from a client and left unmodified reports no errors; warnings mark states real client files ship.

Returns:

Type Description
ValidationReport

every violated contract, in member order

ensure_valid

ensure_valid() -> None

Validate and raise on the first error instead of returning a report — the assert-style face of validate().

Returns:

Type Description
None

nothing; raises when validate() finds any error

Skeleton — the shared .skel

The shared model skeleton (Legion 7.3+): bones, attachments and sequences a skel-based model moved out of its MD20 image, shareable between models via the parent link. The SK*1 chunk payload records (SkelHeader, SkelBones, …) are documented with the other records.

Skeleton

Skeleton()

Bases: FileEntity

A shared model skeleton (.skel, Legion 7.3+), abstract over the client version. Construct a concrete version with Skeleton.for_version(expansion); the per-version Skeleton* classes are subclasses. See https://wowdev.wiki/M2/.skel.

validate

validate() -> ValidationReport

Check the logical integrity contracts this file must satisfy to LOAD in the client, which write() deliberately never enforces. Call it before writing when you want to know the result will load. A file read from a client and left unmodified reports no errors; warnings mark states real client files ship.

for_version staticmethod

for_version(expansion: Expansion) -> Skeleton⟨version⟩

Construct the concrete Skeleton for a client version — the abstract Skeleton is never instantiated directly. The return type narrows per expansion (a typed overload per Expansion member), so for_version(Expansion.Legion) returns a SkeletonLegion; a runtime Expansion value yields the AnySkeleton union.

Returns:

Type Description
ValidationReport

every violated contract, each with its member path

ensure_valid

ensure_valid() -> None

Validate and raise on the first error instead of returning a report — the assert-style face of validate().

Returns:

Type Description
None

nothing; raises when validate() finds any error

Skeleton⟨version⟩

Skeleton⟨version⟩()

Bases: Skeleton

A shared model skeleton (.skel, Legion 7.3+): bones, attachments and sequences for skel-based models, shareable between models via the parent link. See https://wowdev.wiki/M2/.skel.

header_block property writable

The skeleton identity (SKL1).

sequence_block property writable

The sequence tables (SKS1).

parent_link: list[SkelParentData]

The parent-skeleton link (SKPD); 0 or 1 entries.

anim_fdids property

anim_fdids: list[AnimFileEntry]

.anim FileDataIDs (AFID); absent on child skeletons, which share the parent's (see parent_anim_fdids).

bone_fdids property

bone_fdids: list[int]

.bone FileDataIDs (BFID); absent on child skeletons, which share the parent's (see parent_bone_fdids).

bone_block property writable

The bones (decoded SKB1).

attachment_block property writable

attachment_block: SkelAttachments⟨version⟩

The attachments (decoded SKA1).

parent_anim_fdids property

parent_anim_fdids: list[AnimFileEntry]

The parent's AFID entries when this skeleton is a child (filled by read(fs, key); not part of this file).

parent_bone_fdids property

parent_bone_fdids: list[int]

The parent's BFID entries when this skeleton is a child (filled by read(fs, key); not part of this file).

read

read(data: bytes) -> None
read(source: FileSystem, key: FileKey) -> None

Deserialize file bytes into this entity, replacing its contents. Unmodeled chunks are preserved so an unmodified entity rewrites byte-for-byte.

Parameters:

Name Type Description Default
data bytes

the file bytes

required

Load the .skel (and its .bone files) from a client filesystem, replacing this entity's contents.

Parameters:

Name Description Default
source

the filesystem gateway

required
key

the .skel identity (path and/or FileDataID)

required

Returns:

Type Description
None

nothing; raises on a missing file or malformed chunk stream

write

write() -> bytes
write(dest: FileSystem, key: FileKey) -> None

Serialize this entity.

Returns:

Type Description
bytes

the file bytes

Serialize the .skel (and its .bone files) through the filesystem's project overlay; file names derive from the key.

Parameters:

Name Description Default
dest

the filesystem gateway

required
key

the .skel identity; must resolve to a path

required

Returns:

Type Description
bytes

nothing; raises when the key has no path or a file fails to write

validate

validate() -> ValidationReport

Check the logical integrity contracts this file must satisfy to LOAD in the client — companion-chunk counts, index ranges, flag/presence coherence — which write() deliberately never enforces. Call it before writing when you want to know the result will load. A file read from a client and left unmodified reports no errors; warnings mark states real client files ship.

Returns:

Type Description
ValidationReport

every violated contract, in member order

ensure_valid

ensure_valid() -> None

Validate and raise on the first error instead of returning a report — the assert-style face of validate().

Returns:

Type Description
None

nothing; raises when validate() finds any error

effective_anim_fdids

effective_anim_fdids() -> list[AnimFileEntry]

The effective AFID entries: own when present, else the parent's.

effective_bone_fdids

effective_bone_fdids() -> list[int]

The effective BFID entries: own when present, else the parent's.

BoneFile — the .bone facial poses

One .bone file per FacePose (808) sequence variant (WoD+), referenced by BFID: which bones get facial-pose offset matrices, and the matrices. The layout is stable across every client that ships them, so the entity is not version-templated.

BoneFile

BoneFile()

A .bone file (WoD+): facial-pose (FacePose 808) bone offset matrices, one file per pose variant, referenced by BFID. See https://wowdev.wiki/BONE.

prelude property writable

prelude: BoneFilePrelude

The raw u32 prelude (version marker).

bone_ids property

bone_ids: list[int]

The affected bone ids (BIDA).

bone_offset_matrices property

bone_offset_matrices: list[C44Matrix]

The per-bone offset matrices (BOMT), same count as bone_ids.

read

read(data: bytes) -> None

Deserialize file bytes into this entity, replacing its contents. Unmodeled chunks are preserved so an unmodified entity rewrites byte-for-byte.

Parameters:

Name Type Description Default
data bytes

the file bytes

required

write

write() -> bytes

Serialize this entity.

Returns:

Type Description
bytes

the file bytes

validate

validate() -> ValidationReport

Check the logical integrity contracts this file must satisfy to LOAD in the client — companion-chunk counts, index ranges, flag/presence coherence — which write() deliberately never enforces. Call it before writing when you want to know the result will load. A file read from a client and left unmodified reports no errors; warnings mark states real client files ship.

Returns:

Type Description
ValidationReport

every violated contract, in member order

ensure_valid

ensure_valid() -> None

Validate and raise on the first error instead of returning a report — the assert-style face of validate().

Returns:

Type Description
None

nothing; raises when validate() finds any error

BoneFilePrelude

BoneFilePrelude()
BoneFilePrelude(version: int = 1)

The .bone file prelude: a version marker (always 1).

version property writable

version: Annotated[int, uint32]

The format version; always 1 so far.