rpfm_lib/files/asset_variation_db/
mod.rs1use getset::*;
43use serde_derive::{Serialize, Deserialize};
44
45use std::collections::BTreeMap;
46
47use crate::error::{Result, RLibError};
48
49pub const PATH: &str = "warscape_asset_variation_db/warscape_asset_variation_db.bin";
51
52pub const PATH_XML: &str = "warscape_asset_variation_db/warscape_asset_variation_db.xml";
54
55pub const SIGNATURE: &[u8; 8] = b"FASTBIN0";
57
58const MAP_CATEGORY: &str = "map";
60const DEFAULT_MAP: &str = "default_map";
61
62const FLAG_BITS: usize = 32;
64
65mod v1;
66mod xml;
67
68#[cfg(test)] mod asset_variation_db_test;
69
70#[derive(Default, PartialEq, Clone, Debug, Getters, MutGetters, Setters, Serialize, Deserialize)]
76#[getset(get = "pub", get_mut = "pub", set = "pub")]
77pub struct AssetVariationDb {
78
79 serialise_version: u16,
81
82 categories: Vec<VariationCategory>,
84
85 entries: Vec<VariationEntry>,
87}
88
89#[derive(Default, PartialEq, Clone, Debug, Getters, MutGetters, Setters, Serialize, Deserialize)]
91#[getset(get = "pub", get_mut = "pub", set = "pub")]
92pub struct VariationCategory {
93
94 name: String,
96
97 values: Vec<String>,
99}
100
101#[derive(Default, PartialEq, Clone, Debug, Getters, MutGetters, Setters, Serialize, Deserialize)]
103#[getset(get = "pub", get_mut = "pub", set = "pub")]
104pub struct VariationEntry {
105
106 key: String,
108
109 variations: Vec<Variation>,
111}
112
113#[derive(Default, PartialEq, Clone, Debug, Getters, MutGetters, Setters, Serialize, Deserialize)]
115#[getset(get = "pub", get_mut = "pub", set = "pub")]
116pub struct Variation {
117
118 filename: String,
120
121 flags: u32,
123}
124
125#[derive(Default, PartialEq, Clone, Debug)]
127pub struct VariationMatches<'a> {
128
129 pub variations: Vec<&'a Variation>,
131
132 pub used_default_map: bool,
134}
135
136pub type VariationQuery = BTreeMap<String, String>;
138
139impl AssetVariationDb {
144
145 pub fn value_count(&self) -> usize {
147 self.categories.iter().map(|category| category.values.len()).sum()
148 }
149
150 pub fn category(&self, name: &str) -> Option<&VariationCategory> {
152 self.categories.iter().find(|category| category.name == name)
153 }
154
155 pub fn entry(&self, key: &str) -> Option<&VariationEntry> {
157 self.entries.iter().find(|entry| entry.key == key)
158 }
159
160 fn first_bit(&self, name: &str) -> Option<usize> {
162 let mut index = 0;
163 for category in &self.categories {
164 if category.name == name {
165 return Some(index);
166 }
167
168 index += category.values.len();
169 }
170
171 None
172 }
173
174 pub fn bit(&self, category: &str, value: &str) -> u32 {
176 match (self.category(category), self.first_bit(category)) {
177 (Some(category), Some(first)) => category.values.iter()
178 .position(|known| known == value)
179 .map_or(0, |index| 1 << ((first + index) % FLAG_BITS)),
180 _ => 0,
181 }
182 }
183
184 pub fn query_mask(&self, query: &VariationQuery) -> u32 {
186 query.iter().fold(0, |mask, (category, value)| mask | self.bit(category, value))
187 }
188
189 pub fn matches(&self, key: &str, query: &VariationQuery) -> VariationMatches<'_> {
200 let mut matches = VariationMatches::default();
201 let Some(entry) = self.entry(key) else { return matches };
202
203 let find = |query: &VariationQuery| {
204 let mask = self.query_mask(query);
205 entry.variations.iter()
206 .filter(|variation| variation.flags == 0 || variation.flags & mask == mask)
207 .collect::<Vec<_>>()
208 };
209
210 matches.variations = find(query);
211 if matches.variations.is_empty() {
212 let mut query = query.clone();
213 query.insert(MAP_CATEGORY.to_owned(), DEFAULT_MAP.to_owned());
214 matches.variations = find(&query);
215 matches.used_default_map = true;
216 }
217
218 matches
219 }
220
221 pub fn matches_listed_map(&self, key: &str, query: &VariationQuery) -> VariationMatches<'_> {
235 let unlisted_map = match (self.category(MAP_CATEGORY), query.get(MAP_CATEGORY)) {
236 (Some(category), Some(map)) => !map.is_empty() && !category.values.contains(map),
237 _ => false,
238 };
239
240 if unlisted_map {
241 let mut query = query.clone();
242 query.insert(MAP_CATEGORY.to_owned(), DEFAULT_MAP.to_owned());
243
244 let mut matches = self.matches(key, &query);
245 matches.used_default_map = true;
246 matches
247 } else {
248 self.matches(key, query)
249 }
250 }
251
252 pub fn allowed_values(&self, flags: u32) -> Vec<(&str, Vec<&str>)> {
254 let mut index = 0;
255 self.categories.iter().map(|category| {
256 let allowed = category.values.iter().enumerate()
257 .filter(|(value, _)| flags == 0 || flags >> ((index + value) % FLAG_BITS) & 1 == 1)
258 .map(|(_, value)| value.as_str())
259 .collect();
260
261 index += category.values.len();
262 (category.name.as_str(), allowed)
263 }).collect()
264 }
265
266 pub fn flags_for(&self, allowed: &BTreeMap<String, Vec<String>>) -> u32 {
268 let mut flags = 0;
269 let mut index = 0;
270 for category in &self.categories {
271 let allowed = allowed.get(&category.name);
272 for (value_index, value) in category.values.iter().enumerate() {
273 if allowed.is_none_or(|allowed| allowed.contains(value)) {
274 flags |= 1 << ((index + value_index) % FLAG_BITS);
275 }
276 }
277
278 index += category.values.len();
279 }
280
281 flags
282 }
283
284 pub fn validate(&self) -> Vec<String> {
291 let mut problems = vec![];
292 let total = self.value_count();
293 if total > FLAG_BITS {
294 problems.push(format!("{total} values in all categories: the flags have {FLAG_BITS} bits, so bits would collide"));
295 }
296
297 for (index, category) in self.categories.iter().enumerate() {
298 if self.categories[index + 1..].iter().any(|other| other.name == category.name) {
299 problems.push(format!("category '{}' appears twice", category.name));
300 }
301
302 for (value_index, value) in category.values.iter().enumerate() {
303 if category.values[value_index + 1..].contains(value) {
304 problems.push(format!("value '{value}' appears twice in '{}'", category.name));
305 }
306 }
307 }
308
309 if self.category(MAP_CATEGORY).is_some_and(|map| !map.values.iter().any(|value| value == DEFAULT_MAP)) {
310 problems.push(format!("category '{MAP_CATEGORY}' has no value '{DEFAULT_MAP}': the game asks again with it when nothing matched"));
311 }
312
313 let used = if total >= FLAG_BITS { u32::MAX } else { (1 << total) - 1 };
314 for variation in self.entries.iter().flat_map(|entry| &entry.variations) {
315 if variation.flags & !used != 0 {
316 problems.push(format!("{}: flags have bits that belong to no value", variation.filename));
317 }
318
319 if variation.flags != 0 {
321 if let Some((category, _)) = self.allowed_values(variation.flags).into_iter().find(|(_, allowed)| allowed.is_empty()) {
322 problems.push(format!("{}: no {category} allowed, it can never match", variation.filename));
323 }
324 }
325 }
326
327 problems
328 }
329
330 pub fn add_value(&mut self, category: &str, value: &str) -> Result<()> {
339 let Some(category_index) = self.categories.iter().position(|known| known.name == category) else {
340 return Err(refused(format!("no category '{category}'")));
341 };
342
343 let values = &self.categories[category_index].values;
344 if value.is_empty() {
345 return Err(refused("the value is empty".to_owned()));
346 }
347
348 if values.iter().any(|known| known == value) {
349 return Err(refused(format!("'{value}' already exists in '{category}'")));
350 }
351
352 if self.value_count() >= FLAG_BITS {
353 return Err(refused(format!("all {FLAG_BITS} flag bits are in use: remove a value of any category first")));
354 }
355
356 let first = self.first_bit(category).unwrap_or_default();
357 let count = values.len();
358 let new_bit = first + count;
359 let moved = (0..self.value_count()).map(|bit| Some(if bit < new_bit { bit } else { bit + 1 })).collect::<Vec<_>>();
360 let category_bits = (first..first + count).fold(0u32, |bits, bit| bits | 1 << bit);
361
362 for variation in self.entries.iter_mut().flat_map(|entry| &mut entry.variations) {
363 let allowed_all = variation.flags != 0 && count > 0 && variation.flags & category_bits == category_bits;
364 variation.flags = move_bits(variation.flags, &moved);
365 if allowed_all {
366 variation.flags |= 1 << new_bit;
367 }
368 }
369
370 self.categories[category_index].values.push(value.to_owned());
371 Ok(())
372 }
373
374 pub fn remove_value(&mut self, category: &str, value: &str) -> Result<()> {
381 let Some(category_index) = self.categories.iter().position(|known| known.name == category) else {
382 return Err(refused(format!("no category '{category}'")));
383 };
384
385 let Some(value_index) = self.categories[category_index].values.iter().position(|known| known == value) else {
386 return Err(refused(format!("no value '{value}' in '{category}'")));
387 };
388
389 if category == MAP_CATEGORY && value == DEFAULT_MAP {
390 return Err(refused(format!("the game asks again with map '{DEFAULT_MAP}' when nothing matched, so that value must stay")));
391 }
392
393 let removed_bit = self.first_bit(category).unwrap_or_default() + value_index;
394 let moved = (0..self.value_count()).map(|bit| match bit.cmp(&removed_bit) {
395 std::cmp::Ordering::Less => Some(bit),
396 std::cmp::Ordering::Equal => None,
397 std::cmp::Ordering::Greater => Some(bit - 1),
398 }).collect::<Vec<_>>();
399
400 let variations = self.entries.iter().flat_map(|entry| &entry.variations);
401 if let Some(variation) = variations.clone().find(|variation| variation.flags != 0 && move_bits(variation.flags, &moved) == 0) {
402 return Err(refused(format!("{} allows nothing else, removing '{value}' would turn it into an unconstrained variation (flags 0)", variation.filename)));
403 }
404
405 for variation in self.entries.iter_mut().flat_map(|entry| &mut entry.variations) {
406 variation.flags = move_bits(variation.flags, &moved);
407 }
408
409 self.categories[category_index].values.remove(value_index);
410 Ok(())
411 }
412
413 pub fn rename_value(&mut self, category: &str, from: &str, to: &str) -> Result<()> {
420 let Some(category) = self.categories.iter_mut().find(|known| known.name == category) else {
421 return Err(refused(format!("no category '{category}'")));
422 };
423
424 if to.is_empty() {
425 return Err(refused("the new name is empty".to_owned()));
426 }
427
428 if from != to && category.values.iter().any(|known| known == to) {
429 return Err(refused(format!("'{to}' already exists in '{}'", category.name)));
430 }
431
432 let Some(value) = category.values.iter_mut().find(|known| *known == from) else {
433 return Err(refused(format!("no value '{from}' in '{}'", category.name)));
434 };
435
436 *value = to.to_owned();
437 Ok(())
438 }
439}
440
441fn move_bits(flags: u32, moved: &[Option<usize>]) -> u32 {
443 if flags == 0 {
444 return 0;
445 }
446
447 moved.iter().take(FLAG_BITS).enumerate()
448 .filter(|(bit, _)| flags >> bit & 1 == 1)
449 .filter_map(|(_, to)| *to)
450 .fold(0, |out, to| out | 1 << to)
451}
452
453fn refused(reason: String) -> RLibError {
455 RLibError::AssetVariationDbEditRefused(reason)
456}