Skip to main content

rpfm_lib/files/anim/
mod.rs

1//---------------------------------------------------------------------------//
2// Copyright (c) 2017-2026 Ismael Gutiérrez González. All rights reserved.
3//
4// This file is part of the Rusted PackFile Manager (RPFM) project,
5// which can be found here: https://github.com/Frodo45127/rpfm.
6//
7// This file is licensed under the MIT license, which can be found here:
8// https://github.com/Frodo45127/rpfm/blob/master/LICENSE.
9//---------------------------------------------------------------------------//
10
11//! Animation file handler with partial support.
12//!
13//! This module provides the [`Anim`] type for handling animation files (`.anim`) in
14//! Total War PackFiles. Animation files contain skeletal animation data used by the
15//! game engine for character and unit animations.
16//!
17//! # File Format
18//!
19//! Animation files consist of a header followed by binary animation data. The header
20//! contains metadata about the animation:
21//! - Version number
22//! - Frame rate
23//! - Skeleton name
24//! - End time (duration)
25//! - Bone count
26//!
27//! # Limited Support
28//!
29//! Support is currently limited because:
30//! - **Header only**: Only the header is parsed into structured fields
31//! - **Binary data**: Animation data remains as raw bytes
32//! - **No keyframe access**: Individual animation keyframes cannot be accessed or modified
33//!
34//! This allows basic metadata inspection and preservation of the animation data when
35//! re-encoding, but does not enable deep editing of animation curves or keyframes.
36//!
37//! # Use Cases
38//!
39//! - Extracting animation metadata (skeleton name, frame rate, duration)
40//! - Identifying which skeleton an animation is for
41//! - Re-packing animations without modification
42//! - Bulk operations on animation files
43//!
44//! # Example
45//!
46//! ```no_run
47//! use rpfm_lib::files::{Decodeable, anim::Anim};
48//! use std::io::Cursor;
49//!
50//! # let anim_bytes = vec![];
51//! let mut reader = Cursor::new(anim_bytes);
52//! let anim = Anim::decode(&mut reader, &None).unwrap();
53//!
54//! // Access metadata
55//! println!("Skeleton: {}", anim.skeleton_name());
56//! println!("Frame rate: {} fps", anim.frame_rate());
57//! println!("Duration: {} seconds", anim.end_time());
58//! println!("Bones: {}", anim.bone_count());
59//! ```
60
61use getset::*;
62use serde_derive::{Serialize, Deserialize};
63
64use crate::binary::{ReadBytes, WriteBytes};
65use crate::error::Result;
66use crate::files::{DecodeableExtraData, Decodeable, EncodeableExtraData, Encodeable};
67use crate::utils::check_size_mismatch;
68
69/// Extension for animation files.
70pub const EXTENSION: &str = ".anim";
71
72//mod versions;
73
74#[cfg(test)] mod anim_test;
75
76//---------------------------------------------------------------------------//
77//                              Enum & Structs
78//---------------------------------------------------------------------------//
79
80/// Partially decoded animation file.
81///
82/// Contains parsed header metadata and raw binary animation data. The header provides
83/// information about the animation's properties, while the actual keyframe data remains
84/// in binary form.
85///
86/// # Fields
87///
88/// * `version` - Animation file format version
89/// * `uk_1` - Unknown field (purpose not yet identified)
90/// * `frame_rate` - Animation playback speed in frames per second
91/// * `skeleton_name` - Name of the skeleton this animation is for
92/// * `end_time` - Animation duration in seconds
93/// * `bone_count` - Number of bones animated in this file
94/// * `data` - Raw binary animation data (keyframes, curves, etc.)
95///
96/// # Getters/Setters
97///
98/// All fields have public getters, mutable getters, and setters via the `getset` crate:
99/// - `version()`, `version_mut()`, `set_version()`
100/// - `uk_1()`, `uk_1_mut()`, `set_uk_1()`
101/// - `frame_rate()`, `frame_rate_mut()`, `set_frame_rate()`
102/// - `skeleton_name()`, `skeleton_name_mut()`, `set_skeleton_name()`
103/// - `end_time()`, `end_time_mut()`, `set_end_time()`
104/// - `bone_count()`, `bone_count_mut()`, `set_bone_count()`
105/// - `data()`, `data_mut()`, `set_data()`
106///
107/// # Example
108///
109/// ```no_run
110/// use rpfm_lib::files::{Decodeable, anim::Anim};
111/// use std::io::Cursor;
112///
113/// # let anim_data = vec![];
114/// let mut reader = Cursor::new(anim_data);
115/// let anim = Anim::decode(&mut reader, &None).unwrap();
116///
117/// // Check if animation matches expected skeleton
118/// if anim.skeleton_name().contains("humanoid") {
119///     println!("Found humanoid animation: {} seconds at {} fps",
120///         anim.end_time(), anim.frame_rate());
121/// }
122/// ```
123#[derive(PartialEq, Clone, Debug, Default, Getters, MutGetters, Setters, Serialize, Deserialize)]
124#[getset(get = "pub", get_mut = "pub", set = "pub")]
125pub struct Anim {
126    /// Animation file format version.
127    version: u32,
128
129    /// Unknown field (purpose not yet identified).
130    uk_1: u32,
131
132    /// Animation playback speed in frames per second.
133    frame_rate: f32,
134
135    /// Name of the skeleton this animation targets.
136    skeleton_name: String,
137
138    /// Animation duration in seconds.
139    end_time: f32,
140
141    /// Number of bones animated in this file.
142    bone_count: u32,
143
144    /// Raw binary animation data (keyframes, curves, etc.).
145    data: Vec<u8>,
146}
147
148//---------------------------------------------------------------------------//
149//                          Implementation of Anim
150//---------------------------------------------------------------------------//
151
152impl Decodeable for Anim {
153
154    fn decode<R: ReadBytes>(data: &mut R, _extra_data: &Option<DecodeableExtraData>) -> Result<Self> {
155        let mut anim = Self::default();
156        anim.version = data.read_u32()?;
157        anim.uk_1 = data.read_u32()?;
158        anim.frame_rate = data.read_f32()?;
159        anim.skeleton_name = data.read_sized_string_u8()?;
160        anim.end_time = data.read_f32()?;
161        anim.bone_count = data.read_u32()?;
162
163        let data_left = data.len()?.checked_sub(data.stream_position()?);
164        if let Some(data_left) = data_left {
165            anim.data = data.read_slice(data_left as usize, false)?;
166        }
167
168        // If we are not in the last byte, it means we didn't parse the entire file, which means this file is corrupt.
169        check_size_mismatch(data.stream_position()? as usize, data.len()? as usize)?;
170
171        Ok(anim)
172    }
173}
174
175impl Encodeable for Anim {
176
177    fn encode<W: WriteBytes>(&mut self, buffer: &mut W, _extra_data: &Option<EncodeableExtraData>) -> Result<()> {
178        buffer.write_u32(self.version)?;
179        buffer.write_u32(self.uk_1)?;
180        buffer.write_f32(self.frame_rate)?;
181        buffer.write_sized_string_u8(self.skeleton_name())?;
182        buffer.write_f32(self.end_time)?;
183        buffer.write_u32(self.bone_count)?;
184        buffer.write_all(self.data())?;
185
186        Ok(())
187    }
188}