Skip to main content

rpfm_lib/files/unit_variant/
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//! This is a module to read/write binary UnitVariant files.
12//!
13//! UnitVariants define data about what textures and parts unit models use within a unit.
14//! They're usually xml files covered by the [`super::text`] module, but in older games
15//! (Shogun 2) they're binary files instead, and thus they need their own logic.
16//!
17//! # UnitVariant Structure
18//!
19//! ## Header
20//! ### V1
21//!
22//! | Bytes | Type     | Data                                                         |
23//! | ----- | -------- | ------------------------------------------------------------ |
24//! | 4     | StringU8 | Signature of the file.                                       |
25//! | 4     | [u32]    | Version of the file.                                         |
26//! | 4     | [u32]    | Category count.                                              |
27//! | 4     | [u32]    | Index from the start to the begining of the categories data. |
28//! | 4     | [u32]    | Index from the start to the begining of the variants data.   |
29//!
30//! ### V2
31//!
32//! | Bytes | Type     | Data                                                         |
33//! | ----- | -------- | ------------------------------------------------------------ |
34//! | 4     | StringU8 | Signature of the file.                                       |
35//! | 4     | [u32]    | Version of the file.                                         |
36//! | 4     | [u32]    | Category count.                                              |
37//! | 4     | [u32]    | Index from the start to the begining of the categories data. |
38//! | 4     | [u32]    | Index from the start to the begining of the variants data.   |
39//! | 4     | [u32]    | Unknown.                                                     |
40//!
41//! ## Data
42//!
43//! This is valid for all versions.
44//!
45//! | Bytes                | Type                                 | Data                                                   |
46//! | -------------------- | ------------------------------------ | ------------------------------------------------------ |
47//! | 528 * Category Count | [Category](#category-structure) List | List of categories in the UnitVariant.                 |
48//! | 1026 * Variant Count | [Variant](#variant-structure) List   | List of variants in the categories of the UnitVariant. |
49//!
50//! ### Category Structure
51//!
52//! This is valid for all versions.
53//!
54//! | Bytes | Type      | Data                                          |
55//! | ----- | --------- | --------------------------------------------- |
56//! | 512   | StringU16 | Category name. 00-Padded, Max 256 characters. |
57//! | 8     | [u64]     | Category Id.                                  |
58//! | 4     | [u32]     | Variant count for this category.              |
59//! | 4     | [u32]     | Variants before the ones of this category.    |
60//!
61//!  ### Variant Structure
62//!
63//! This is valid for all versions.
64//!
65//! | Bytes | Type      | Data                                                |
66//! | ----- | --------- | --------------------------------------------------- |
67//! | 512   | StringU16 | Mesh file path. 00-Padded, Max 256 characters.      |
68//! | 512   | StringU16 | Texture folder path. 00-Padded, Max 256 characters. |
69//! | 2     | [u16]     | Unknown, possibly a termination.                    |
70
71use getset::*;
72use serde_derive::{Serialize, Deserialize};
73
74use crate::error::{RLibError, Result};
75use crate::binary::{ReadBytes, WriteBytes};
76use crate::files::{DecodeableExtraData, Decodeable, EncodeableExtraData, Encodeable};
77use crate::utils::check_size_mismatch;
78
79/// Signature/Magic Numbers/Whatever of an UnitVariant.
80const SIGNATURE: &str = "VRNT";
81
82const HEADER_LENGTH_V1: u32 = 20;
83const HEADER_LENGTH_V2: u32 = 24;
84
85/// Extension used by UnitVariants.
86pub const EXTENSION: &str = ".unit_variant";
87
88#[cfg(test)] mod unit_variant_test;
89
90//---------------------------------------------------------------------------//
91//                              Enum & Structs
92//---------------------------------------------------------------------------//
93
94/// This holds an entire UnitVariant decoded in memory.
95#[derive(Eq, PartialEq, Clone, Debug, Default, Getters, MutGetters, Setters, Serialize, Deserialize)]
96#[getset(get = "pub", get_mut = "pub", set = "pub")]
97pub struct UnitVariant {
98
99    /// Version of the UnitVariant.
100    version: u32,
101
102    /// Not sure what this is.
103    unknown_1: u32,
104
105    /// Variant categories.
106    categories: Vec<Category>,
107}
108
109/// This holds a variant category.
110#[derive(Eq, PartialEq, Clone, Debug, Default, Getters, MutGetters, Setters, Serialize, Deserialize)]
111#[getset(get = "pub", get_mut = "pub", set = "pub")]
112pub struct Category {
113
114    /// Name of the category.
115    name: String,
116
117    /// Id of the category.
118    id: u64,
119
120    /// Variants of this category.
121    variants: Vec<Variant>,
122}
123
124/// This holds a `Variant` of a Category.
125#[derive(Eq, PartialEq, Clone, Debug, Default, Getters, MutGetters, Setters, Serialize, Deserialize)]
126#[getset(get = "pub", get_mut = "pub", set = "pub")]
127pub struct Variant {
128
129    /// The file path (case insensitive) of the mesh file of this variant.
130    mesh_file: String,
131
132    /// The folder path (case insensitive) of the textures of this variant.
133    texture_folder: String,
134
135    /// Unknown value.
136    unknown_value: u16
137}
138
139//---------------------------------------------------------------------------//
140//                           Implementation of Text
141//---------------------------------------------------------------------------//
142
143/// Implementation of `UnitVariant`.
144impl UnitVariant {
145
146    /// This function tries to read the header of an UnitVariant from a raw data input.
147    ///
148    /// It returns, in this order: version, amount of categories, and unknown_1.
149    fn read_header<R: ReadBytes>(data: &mut R) -> Result<(u32, u32, u32)> {
150        let signature = data.read_string_u8(SIGNATURE.len())?;
151        if signature != SIGNATURE {
152            return Err(RLibError::DecodingUnitVariantNotAUnitVariant)
153        }
154
155        let version = data.read_u32()?;
156        let categories_count = data.read_u32()?;
157
158        // We don't use them, but it's good to know what they are.
159        let _categories_index = data.read_u32()?;
160        let _variants_index = data.read_u32()?;
161
162        // V2 has an extra number here. No idea what it is.
163        let unknown_1 = if version == 2 { data.read_u32()? } else { 0 };
164
165        Ok((version, categories_count, unknown_1))
166    }
167
168    /// This function returns the header binary lenght of the UnitVariant.
169    pub fn get_header_size(&self) -> u32 {
170        if self.version == 2 { HEADER_LENGTH_V2 } else { HEADER_LENGTH_V1 }
171    }
172}
173
174impl Decodeable for UnitVariant {
175
176    fn decode<R: ReadBytes>(data: &mut R, _extra_data: &Option<DecodeableExtraData>) -> Result<Self> {
177        let (version, categories_count, unknown_1) = Self::read_header(data)?;
178
179        // Get the categories.
180        let mut categories = Vec::with_capacity(categories_count as usize);
181        for _ in 0..categories_count {
182            let name = data.read_string_u16_0padded(512)?;
183            let id = data.read_u64()?;
184            let variants_on_this_category = data.read_u32()?;
185            let _variants_before_this_category = data.read_u32()?;
186
187            let category = Category {
188                name,
189                id,
190                variants: Vec::with_capacity(variants_on_this_category as usize),
191            };
192
193            categories.push(category)
194        }
195
196        // Read the variants.
197        for category in &mut categories {
198            for _ in 0..category.variants.capacity() {
199                let mesh_file = data.read_string_u16_0padded(512)?;
200                let texture_folder = data.read_string_u16_0padded(512)?;
201                let unknown_value = data.read_u16()?;
202
203                category.variants.push(Variant {
204                    mesh_file,
205                    texture_folder,
206                    unknown_value
207                });
208            }
209        }
210
211        // Trigger an error if there's left data on the source.
212        check_size_mismatch(data.stream_position()? as usize, data.len()? as usize)?;
213
214        // If we've reached this, we've successfully decoded the entire UnitVariant.
215        Ok(Self {
216            version,
217            unknown_1,
218            categories
219        })
220    }
221}
222
223impl Encodeable for UnitVariant {
224
225    fn encode<W: WriteBytes>(&mut self, buffer: &mut W, _extra_data: &Option<EncodeableExtraData>) -> Result<()> {
226
227        let mut encoded_variants = vec![];
228        let mut encoded_categories = vec![];
229
230        let mut variants_count = 0;
231        for category in &self.categories {
232            encoded_categories.write_string_u16_0padded(&category.name, 512, true)?;
233            encoded_categories.write_u64(category.id)?;
234            encoded_categories.write_u32(category.variants.len() as u32)?;
235            encoded_categories.write_u32(variants_count)?;
236            for variant in &category.variants {
237                encoded_variants.write_string_u16_0padded(&variant.mesh_file, 512, true)?;
238                encoded_variants.write_string_u16_0padded(&variant.texture_folder, 512, true)?;
239                encoded_variants.write_u16(variant.unknown_value)?;
240            }
241
242            variants_count += category.variants.len() as u32;
243        }
244
245        buffer.write_string_u8(SIGNATURE)?;
246        buffer.write_u32(self.version)?;
247        buffer.write_u32(self.categories.len() as u32)?;
248
249        buffer.write_u32(self.get_header_size())?;
250        buffer.write_u32(self.get_header_size() + encoded_categories.len() as u32)?;
251
252        if self.version == 2 {
253            buffer.write_u32(self.unknown_1)?;
254        }
255
256        buffer.write_all(&encoded_categories)?;
257        buffer.write_all(&encoded_variants)?;
258
259        Ok(())
260    }
261}