DEV Community

Gregory Felix
Gregory Felix

Posted on

I decoded The Sims 4 animations outside the game. The hard part wasn't the animation

It started with a PowerShell script that computed SHA-256 hashes of .package files to find duplicates in my wife's Mods folder. It worked. Then I made the classic mistake: I started wondering what was inside those files.

A few nights later, this was running in my browser: a Sim animated by a real game clip, wearing a textured CC outfit, assembled from the raw bytes of game and mod packages — no game running, no internet, no Blender.

The assembled figure: a CC outfit with real texture, EA base head and EA hair — all posed by a real game clip in my WebGL renderer

This post is about the road there. It's not a complete tutorial and it's not a discovery claim — almost everything I used already existed as code scattered across repositories. What didn't exist was anyone telling the story of how the pieces fit. That's the post I wished existed.

Piece 1: the CLIP (the animation)

TS4 animation resources have type 0x6B20C4F3. The header is friendly: version 14, duration, rig name, clip events. The interesting part is the S3CLIP payload — per-bone channels with sparse, quantized keyframes.

The detail that cost me hours: orientation indices aren't 16-bit. They're 12-bit, and the sign of each quaternion component is not in the index — it lives in the frame's flags field. The community encoder stores |v| × 4095 and parks the sign elsewhere. Read the index as idx/65535 and you get an abomination: quaternions with norms around 0.93 and a skeleton that looks like an accident.

The skeleton after a wrong quaternion decode — contorted limbs, detached head, blue bone lines crossing the body

The fix came from reading Sims 4 Studio's Blender-side decoder, not from guessing formulas. Once it was right, every single quaternion norm hit exactly 1.000 — that's how you know a decoder is correct.

Piece 2: the rig and the skin

EA's humanoid rig (type 0x8EAF13DE, "Clear format") has 165 bones. Clip channels match by bone-name hash. Then came GEOM (0x015A1849): positions, normals, UVs, and the essential part — blend indices and weights, with the bone table at the end of the resource. Linear blend skinning: delta = W_pose ∘ W_bind⁻¹ per bone.

Result: a real CC outfit (8,113 vertices) deforming for real as the bones move.

Piece 3: the texture — the part that nearly killed me

The package held an image 0x00B2D882 with fourCC = "DST5". I decoded it as regular DXT5. The result was one of the ugliest things I've ever produced: structured gray noise in a grid, with pink blotches.

DST5 decoded as plain DXT5: a grid of gray noise with pink static blotches

The corrupted texture applied to the mesh — moiré skin with red wounds

Wrong hypotheses I tested: UV flip, channel order, mip boundaries. The answer lived in an s4pi file called DSTResource.cs, method Unshuffle: DST5 is not contiguous-block DXT5. EA splits the fields into four planes spanning the entire mip chain — all alpha endpoints first, then color endpoints, then alpha indices, then color indices. Unshuffle the planes and a standard DXT5 stream falls out, which any decoder can eat.

The same texture after plane unshuffling: a clean black-and-white UV atlas

That was the moment of the night. Nightmare noise → clean UV atlas in a ~20-line change — after finding the right source.

Piece 4: turning resources into "parts"

A package is a bag of resources with no obvious hierarchy. The piece that ties it together is the CASP (0x034AEECB): a UTF-16 name in the header and a TGI reference list at the tail — entries pointing at GEOMs, textures, MODLs, thumbnails. Following the TGI list of a CASP named yfHead got me the game's base head mesh. The TGI-list trick: entries are contiguous 16-byte [instance][group][type] records running to EOF — find them by walking backwards while the fields stay valid.

Stacking three meshes — the mod's outfit, EA's head, EA's hair — on the same animated rig produced the "human" from the opening image. A slightly unsettling mannequin, honestly, but ours.

What's still missing (honesty section)

  • Generic face: a real Sim's face is base mesh + dozens of BGEO morph deltas from CAS sliders — a whole other system
  • No real skin texture: TS4 skin tone is a composition pipeline, not a DDS
  • No eyes/teeth (separate CASPs), no IK, no cross-clip blending
  • Two-actor clips (a1/a2) currently render on top of each other

What I learned (worth more than the code)

Every time I guessed a format, I lost hours. Every breakthrough came from finding the authoritative source: the codec from S4S's animation/__init__.py, DST5 from s4pi's DSTResource.cs, CASP layout from catalog.py. The modding community has mapped almost everything — but almost none of it exists as prose. It's scattered as code across dozens of repos.

If this post saves someone my wasted hours, it did its job.


Tools consulted: Sims 4 Studio (Blender scripts), s4pi/s4pe, and a lot of hex reading. Everything ran locally — not a single byte left my machine.


Top comments (0)