Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

LIP (Lip Synching)

LIP files provide keyframed facial morph data directly bound to audio streams, instructing character models how to physically animate their mouths to match speech.

At a Glance

PropertyValue
Extension(s).lip
Magic SignatureLIP V1.0
TypeFacial Animation Keyframes
Rust ReferenceView rakata_formats::Lip in Rustdocs

File Layout

Two blocks and no offsets to follow: a 16-byte header, then the keyframe array immediately after it. Nothing in the file points anywhere, because nothing needs to.

OffsetFieldTypeNotes
0x00signaturechar[8]LIP V1.0, checked as one eight-byte run rather than as a magic and version pair.
0x08animation lengthf32Total duration in seconds.
0x0Centry_countu32
0x10keyframesarray5 bytes each, packed with no padding.

Keyframe Entry (5 bytes each)

OffsetFieldTypeNotes
0x00timef32Timestamp in seconds, from the start of the animation.
0x04shapeu8Viseme index 015. See below.

The 16 visemes

Every one of these occurs across a full install’s 1,073,460 keyframes, and no value outside the range appears anywhere. The names are phonetic groupings rather than individual sounds: one mouth shape serves every consonant made the same way.

#Shape#Shape#Shape#Shape
0Neutral, mouth closed4“oh”8“f”, “v”12“t”, “d”
1“ee”5“ooh”9“ng”13“sh”
2“eh”6“y”10“th”14“l”
3“ah”7“s”, “t”, “s” cluster11“m”, “p”, “b”15“k”, “g”

Warning

The on-disk layout is the in-memory layout As the audit below records, the engine does not parse the keyframe array at all. It points an internal pointer at file offset 0x10 and animates directly off the raw buffer. That makes the five-byte stride load-bearing in a way most formats’ are not: there is no padding to the natural four-byte alignment a f32 would normally want, and adding any would not produce a slightly different file, it would produce one the engine reads as garbage from the second keyframe onward.

Engine Audits & Decompilation

Read from CLIP::LoadLip at 0x0070c590 in swkotor.exe. Provenance: derived, not attested. The rows below have not been separately re-derived, so they sit on the reverse-engineering queue.

Pipeline EventGhidra Provenance & Engine Behavior
Zero-Copy LoadingThe engine handles LIP files as completely flat structures. Instead of parsing the variables out individually, it simply verifies the "LIP V1.0" signature and pulls the animation length and entry count directly from offsets +0x08 and +0x0C.
Direct Array AssignmentThe keyframes are packed into identical 5-byte chunks (a 4-byte float for the timestamp, and a 1-byte integer determining the mouth shape). Because of this flat layout, the engine never loops through the data to read it. It simply points its internal animations memory pointer perfectly to file offset +0x10 and natively runs the animation straight off the raw file buffer.