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}