Skip to main content

Watch Dogs MAC (Mesh Animation Clip) Binary Format

Source: Decompiled AnimationMarkupTool.exe (ilspycmd 11.0.0.9375) + hex analysis of 43,253 MAC files from the Ubisoft leak. Confidence: HIGH — derived from official tool source, verified against leak binaries. Python port: blender-io-disrupt/modules/Watch_Dogs/animation_parser.py (767 lines, MAC + markup parser + CLI).


Overview​

MAC is the source/authoring animation format used by the Disrupt engine's AnimationMarkupTool. It stores uncompressed animation curves, skeleton hierarchy, flags, events, and sub-animation parts. MAC files are the input to the compilation pipeline that produces runtime MAB files.

Position in the Pipeline​

animation.fbx (FBX source — DCC tool export)
↓ FBX import via FbxLoaderManaged.dll (C++/CLI, native FBX SDK)
animation.mac (binary source — curves, skeleton, flags, events, parts)
animation.markup (XML source — time-stamped game events)
↓ CMarkupCompiler (AnimationMarkupTool internal)
animation.markup.bin (compiled markup header — 10 bytes, version=3)
animation.mab (compiled animation markup — compressed per-bone data)
  • MAC = source format (uncompressed, full skeleton, FBX path)
  • MAB = compiled runtime format (Oodle compressed, quantized u8 curves, per-clip)
  • Markup XML = event definitions referencing the MAC by name

File Counts (Ubisoft Leak)​

TypeCountLocation
.mac43,253data/animations/
.markup40,691data/animations/
.mab38,419data_win64/animations/
.markup.bin37,971data_win64/animations/scr/

Size Relationship​

MAB is heavily compressed vs MAC — typically 0.01x to 0.1x the MAC size. For example:

  • idle_spidertank.mac: 139KB → idle_spidertank.mab: 3.4KB (0.024x)
  • attack01_spidertank.mac: 46KB → attack01_spidertank.mab: 2.6KB (0.057x)
  • ghost-mode_mplyr.mac: 1.2MB → ghost-mode_mplyr.mab: 7.7KB (0.006x)

Compression uses Oodle (oo2core_4_win64.dll present in AnimationMarkupTool).


MAC Binary Layout​

Source: AnimationFile.LoadFromStream(BinaryReader stream) in AnimationMarkupTool.exe.

OffsetTypeFieldNotes
0x00f32versionVersion indicator. 8.305 = legacy format, 9.0 = new format
0x04(conditional)If version == 9.0: u16 file_version + u8 exporter_major + u8 exporter_minor

The version field determines encoding behavior throughout the file. Legacy (v8.305) and new (v9.0) differ primarily in how _Source is encoded after _FrameRate.

Post-Header​

OffsetTypeFieldNotes
+0StringID_TypeAnimation type name (e.g. "07_SpiderTank")
+Nf32_FrameRateFrame rate (e.g. 30.0)
+NStringID_SourceFBX source path. Only present if version > 7 and version < 2.139e9

Arrays​

All arrays are preceded by a u32 count. Read in this order:

OrderTypeDescription
1AnimFlag[]Animation flags (n_flags items)
2AnimFileEvent[]Embedded animation events (n_events items)
3AnimPart[]Animation parts/sub-animations (n_parts items) — RECURSIVE

Root Motion & Skeleton​

After the arrays, the file contains:

OrderTypeDescription
4AnimDirNodeRoot motion node (direction/speed curves)
5AnimMomentumMomentum data (direction + speed curves)
6AnimSkeletonFull skeleton with per-bone animation curves

Trailing Data​

i32 trailing_string_len
char[] trailing_string (optional, length-prefixed)

Complete Layout (Visual)​

[0x00] f32 version (8.305 or 9.0)
[0x04] (if v9.0: u16 file_version + u8 exporter_major + u8 exporter_minor)
[+0] StringID _Type
[+N] f32 _FrameRate
[+N] StringID _Source (if version > 7 and < 2.139e9)
[+N] u32 n_flags + AnimFlag[n_flags]
[+N] u32 n_events + AnimFileEvent[n_events]
[+N] u32 n_parts + AnimPart[n_parts] (recursive!)
[+N] AnimDirNode _DirNode (root motion)
[+N] AnimMomentum _Momentum
[+N] AnimSkeleton
[+N] i32 trailing_string_len + string

StringID Structure​

Used throughout the MAC format. Recurring structure:

u32 type_id (hash/type identifier)
i32 string_len (length of string in chars, NOT bytes)
char[] string (string_len chars)

The type_id is a hash of the string content. The string is length-prefixed with a signed 32-bit integer indicating character count (not byte count — relevant for Unicode, though most strings are ASCII).


AnimFlag​

StringID _Name (e.g. "MoveMgr_Poses", "GameEvents")
i32 _FlagType (enum FlagType)

FlagType Enum​

ValueNameDescription
0GlobalGlobal animation flag
1CurveCurve-based flag
2StepStep trigger flag
3SpikeSpike trigger flag
4FrameFrame-based flag
5UnknownUnknown/unmapped

AnimFileEvent​

i32 _Type (enum AnimFlagName)
f32 _Time (time in seconds)
u32 _NumParams
AnimFileParameter[_NumParams] (each: StringID _Type + StringID _Name)

AnimFlagName Enum​

ValueNameDescription
0UnknownUnidentified event type
1MoveMgr_PosesMovement manager pose triggers
2GameEventsGeneral game events
3DurationEventDuration-based events
4MaxSentinel
5Force32BitForce 32-bit alignment

AnimPart (Recursive Sub-Animation)​

AnimPart contains a nested AnimationFile, making the MAC format recursive. This is how complex animations compose sub-animations (e.g., an idle animation with separate upper-body and lower-body parts).

i32 version_tag_1 (always 987654321)
i32 version_tag_2 (unknown value)
StringID _Name
f64 _Time
i32 _PartEventType (enum PartEventType)
StringID _ParentId
StringID _Handle
i32 unk1
i32 unk2
AnimationFile _File (RECURSIVE — nested MAC!)
i32 handle_name_len
char[] _HandleName (length-prefixed)
i32 ik_bone_name_len
char[] _IKBoneName (length-prefixed)

PartEventType Enum​

ValueNameDescription
0Unused0Unused
1InPossessionIn possession state
2Unused1Unused
3Unused2Unused
4Unused3Unused
5Unused4Unused
6IKPathInverse kinematics path
7AnchorAnchor point
8SubPartInPossessionSub-part in possession state
9Unused5Unused
10NumberOfSentinel/count

AnimDirNode (Root Motion)​

Root motion node containing directional animation curves.

i32 unk1
i32 unk2
u32 _NumCurves
AnimDiscreteCurve[_NumCurves] _Curves

AnimMomentum​

Momentum data with direction and speed curves.

i32 unk1
i32 unk2
AnimCurve _DirectionCurve (reads i32)
AnimCurve _SpeedCurve (reads i32)

AnimSkeleton​

Full skeleton hierarchy with per-bone animation data.

i32 unk1
i32 unk2
u32 _NumBones
AnimBone[_NumBones] _Bones

AnimBone (extends AnimDirNode)​

Each bone extends AnimDirNode, adding identity and parent information.

StringID _Name
u32 _Id
i32 _ParentId
[AnimDirNode curves: u32 count + AnimDiscreteCurve[]]
  • _Name: Bone name (e.g. "Hip", "Spine1", "LeftHand")
  • _Id: Unique bone identifier
  • _ParentId: Parent bone ID (-1 for root)

AnimDiscreteCurve​

Individual animation curve data. The core building block of MAC animation data.

u32 _NumValues
i32 _CurveDataType (enum CurveDataType)
i32 unk
f32[_NumValues] _Values (only present for Trans/Quat/Rot types)

CurveDataType Enum​

ValueNameDescription
0RotXRotation X axis
1RotYRotation Y axis
2RotZRotation Z axis
3TransXTranslation X axis
4TransYTranslation Y axis
5TransZTranslation Z axis
6ScaleXScale X axis
7ScaleYScale Y axis
8ScaleZScale Z axis
9QuatXQuaternion X component
10QuatYQuaternion Y component
11QuatZQuaternion Z component
12QuatWQuaternion W component
13RadiusRadius (cylindrical)
14HeightHeight (cylindrical)
15UnknownUnknown/unmapped

Value Storage​

  • Rotation, Translation, and Quaternion curves store f32[_NumValues] — one float per keyframe.
  • Scale, Radius, Height, and Unknown types do not store float arrays (the curve is implicit).
  • _NumValues is typically equal to the number of keyframes in the animation.

Key Insights​

Recursive Structure​

The MAC format's recursive AnimPart → AnimationFile nesting allows complex animation compositions. A single .mac file can contain multiple sub-animations, each with their own skeleton, curves, and events. This is how blend trees and layered animations are represented at the source level.

Version Detection​

The version float at offset 0x00 is the primary discriminator:

  • 8.305 = legacy format (most common in the leak)
  • 9.0 = new format (adds file_version + exporter version fields)

The _Source field (FBX path) is only present when version > 7 AND version < 2.139e9, which covers both known versions.

MAC vs MAB​

  • MAC stores f32 keyframe arrays per bone per curve — full precision, uncompressed
  • MAB stores quantized u8 values decoded to [-1.0, +1.0] — heavily compressed
  • MAB does NOT use the same data layout as MAC — it reorganizes data for runtime efficiency
  • Direct byte-for-byte comparison between MAC re-encoded values and MAB shows zero matches