rpfm_lib/files/asset_variation_db/
mod.rs1use getset::*;
45use serde_derive::{Serialize, Deserialize};
46
47use std::collections::BTreeMap;
48
49use crate::error::{Result, RLibError};
50
51pub const PATH: &str = "warscape_asset_variation_db/warscape_asset_variation_db.bin";
53
54pub const PATH_XML: &str = "warscape_asset_variation_db/warscape_asset_variation_db.xml";
56
57pub const SIGNATURE: &[u8; 8] = b"FASTBIN0";
59
60const MAP_CATEGORY: &str = "map";
62const DEFAULT_MAP: &str = "default_map";
63
64const FLAG_BITS: usize = 32;
66
67mod v1;
68mod xml;
69
70#[cfg(test)] mod asset_variation_db_test;
71
72#[derive(Default, PartialEq, Clone, Debug, Getters, MutGetters, Setters, Serialize, Deserialize)]
78#[getset(get = "pub", get_mut = "pub", set = "pub")]
79pub struct AssetVariationDb {
80
81 serialise_version: u16,
83
84 categories: Vec<VariationCategory>,
86
87 entries: Vec<VariationEntry>,
89}
90
91#[derive(Default, PartialEq, Clone, Debug, Getters, MutGetters, Setters, Serialize, Deserialize)]
93#[getset(get = "pub", get_mut = "pub", set = "pub")]
94pub struct VariationCategory {
95
96 name: String,
98
99 values: Vec<String>,
101}
102
103#[derive(Default, PartialEq, Clone, Debug, Getters, MutGetters, Setters, Serialize, Deserialize)]
105#[getset(get = "pub", get_mut = "pub", set = "pub")]
106pub struct VariationEntry {
107
108 key: String,
110
111 variations: Vec<Variation>,
113}
114
115#[derive(Default, PartialEq, Clone, Debug, Getters, MutGetters, Setters, Serialize, Deserialize)]
117#[getset(get = "pub", get_mut = "pub", set = "pub")]
118pub struct Variation {
119
120 filename: String,
122
123 flags: u32,
125}
126
127#[derive(Default, PartialEq, Clone, Debug)]
129pub struct VariationMatches<'a> {
130
131 pub variations: Vec<&'a Variation>,
133
134 pub used_default_map: bool,
136}
137
138pub type VariationQuery = BTreeMap<String, String>;
140
141impl AssetVariationDb {
146
147 pub fn value_count(&self) -> usize {
149 self.categories.iter().map(|category| category.values.len()).sum()
150 }
151
152 pub fn category(&self, name: &str) -> Option<&VariationCategory> {
154 self.categories.iter().find(|category| category.name == name)
155 }
156
157 pub fn entry(&self, key: &str) -> Option<&VariationEntry> {
159 self.entries.iter().find(|entry| entry.key == key)
160 }
161
162 fn first_bit(&self, name: &str) -> Option<usize> {
164 let mut index = 0;
165 for category in &self.categories {
166 if category.name == name {
167 return Some(index);
168 }
169
170 index += category.values.len();
171 }
172
173 None
174 }
175
176 pub fn bit(&self, category: &str, value: &str) -> u32 {
178 match (self.category(category), self.first_bit(category)) {
179 (Some(category), Some(first)) => category.values.iter()
180 .position(|known| known == value)
181 .map_or(0, |index| 1 << ((first + index) % FLAG_BITS)),
182 _ => 0,
183 }
184 }
185
186 pub fn value_bit(&self, value: &str) -> Option<u32> {
188 self.categories.iter()
189 .flat_map(|category| &category.values)
190 .position(|known| known == value)
191 .map(|index| 1 << (index % FLAG_BITS))
192 }
193
194 pub fn query_mask(&self, query: &VariationQuery) -> Option<u32> {
196 query.values()
197 .filter(|value| !value.is_empty())
198 .try_fold(0, |mask, value| self.value_bit(value).map(|bit| mask | bit))
199 }
200
201 pub fn matches(&self, key: &str, query: &VariationQuery) -> VariationMatches<'_> {
212 let mut matches = VariationMatches::default();
213 let Some(entry) = self.entry(key) else { return matches };
214
215 let find = |query: &VariationQuery| match self.query_mask(query) {
216 Some(mask) => entry.variations.iter()
217 .filter(|variation| variation.flags == 0 || variation.flags & mask == mask)
218 .collect::<Vec<_>>(),
219 None => vec![],
220 };
221
222 matches.variations = find(query);
223 if matches.variations.is_empty() {
224 let mut query = query.clone();
225 query.insert(MAP_CATEGORY.to_owned(), DEFAULT_MAP.to_owned());
226 matches.variations = find(&query);
227 matches.used_default_map = true;
228 }
229
230 matches
231 }
232
233 pub fn allowed_values(&self, flags: u32) -> Vec<(&str, Vec<&str>)> {
235 let mut index = 0;
236 self.categories.iter().map(|category| {
237 let allowed = category.values.iter().enumerate()
238 .filter(|(value, _)| flags == 0 || flags >> ((index + value) % FLAG_BITS) & 1 == 1)
239 .map(|(_, value)| value.as_str())
240 .collect();
241
242 index += category.values.len();
243 (category.name.as_str(), allowed)
244 }).collect()
245 }
246
247 pub fn flags_for(&self, allowed: &BTreeMap<String, Vec<String>>) -> u32 {
249 let mut flags = 0;
250 let mut index = 0;
251 for category in &self.categories {
252 let allowed = allowed.get(&category.name);
253 for (value_index, value) in category.values.iter().enumerate() {
254 if allowed.is_none_or(|allowed| allowed.contains(value)) {
255 flags |= 1 << ((index + value_index) % FLAG_BITS);
256 }
257 }
258
259 index += category.values.len();
260 }
261
262 flags
263 }
264
265 pub fn validate(&self) -> Vec<String> {
272 let mut problems = vec![];
273 let total = self.value_count();
274 if total > FLAG_BITS {
275 problems.push(format!("{total} values in all categories: the flags have {FLAG_BITS} bits, so bits would collide"));
276 }
277
278 for (index, category) in self.categories.iter().enumerate() {
279 if self.categories[index + 1..].iter().any(|other| other.name == category.name) {
280 problems.push(format!("category '{}' appears twice", category.name));
281 }
282
283 for (value_index, value) in category.values.iter().enumerate() {
284 if category.values[value_index + 1..].contains(value) {
285 problems.push(format!("value '{value}' appears twice in '{}'", category.name));
286 }
287
288 if let Some(first) = self.categories[..index].iter().find(|other| other.values.contains(value)) {
289 problems.push(format!(
290 "value '{value}' is in '{}' and '{}': the game finds a value by its name in all categories, so the one in '{}' is never used",
291 first.name, category.name, category.name
292 ));
293 }
294 }
295 }
296
297 if self.category(MAP_CATEGORY).is_some_and(|map| !map.values.iter().any(|value| value == DEFAULT_MAP)) {
298 problems.push(format!("category '{MAP_CATEGORY}' has no value '{DEFAULT_MAP}': the game asks again with it when nothing matched"));
299 }
300
301 let used = if total >= FLAG_BITS { u32::MAX } else { (1 << total) - 1 };
302 for variation in self.entries.iter().flat_map(|entry| &entry.variations) {
303 if variation.flags & !used != 0 {
304 problems.push(format!("{}: flags have bits that belong to no value", variation.filename));
305 }
306
307 if variation.flags != 0 {
309 if let Some((category, _)) = self.allowed_values(variation.flags).into_iter().find(|(_, allowed)| allowed.is_empty()) {
310 problems.push(format!("{}: no {category} allowed, it can never match", variation.filename));
311 }
312 }
313 }
314
315 problems
316 }
317
318 pub fn add_value(&mut self, category: &str, value: &str) -> Result<()> {
327 let Some(category_index) = self.categories.iter().position(|known| known.name == category) else {
328 return Err(refused(format!("no category '{category}'")));
329 };
330
331 let values = &self.categories[category_index].values;
332 if value.is_empty() {
333 return Err(refused("the value is empty".to_owned()));
334 }
335
336 if let Some(owner) = self.category_of(value) {
337 return Err(refused(format!("'{value}' already exists in '{owner}'")));
338 }
339
340 if self.value_count() >= FLAG_BITS {
341 return Err(refused(format!("all {FLAG_BITS} flag bits are in use: remove a value of any category first")));
342 }
343
344 let first = self.first_bit(category).unwrap_or_default();
345 let count = values.len();
346 let new_bit = first + count;
347 let moved = (0..self.value_count()).map(|bit| Some(if bit < new_bit { bit } else { bit + 1 })).collect::<Vec<_>>();
348 let category_bits = (first..first + count).fold(0u32, |bits, bit| bits | 1 << bit);
349
350 for variation in self.entries.iter_mut().flat_map(|entry| &mut entry.variations) {
351 let allowed_all = variation.flags != 0 && count > 0 && variation.flags & category_bits == category_bits;
352 variation.flags = move_bits(variation.flags, &moved);
353 if allowed_all {
354 variation.flags |= 1 << new_bit;
355 }
356 }
357
358 self.categories[category_index].values.push(value.to_owned());
359 Ok(())
360 }
361
362 pub fn remove_value(&mut self, category: &str, value: &str) -> Result<()> {
369 let Some(category_index) = self.categories.iter().position(|known| known.name == category) else {
370 return Err(refused(format!("no category '{category}'")));
371 };
372
373 let Some(value_index) = self.categories[category_index].values.iter().position(|known| known == value) else {
374 return Err(refused(format!("no value '{value}' in '{category}'")));
375 };
376
377 if category == MAP_CATEGORY && value == DEFAULT_MAP {
378 return Err(refused(format!("the game asks again with map '{DEFAULT_MAP}' when nothing matched, so that value must stay")));
379 }
380
381 let removed_bit = self.first_bit(category).unwrap_or_default() + value_index;
382 let moved = (0..self.value_count()).map(|bit| match bit.cmp(&removed_bit) {
383 std::cmp::Ordering::Less => Some(bit),
384 std::cmp::Ordering::Equal => None,
385 std::cmp::Ordering::Greater => Some(bit - 1),
386 }).collect::<Vec<_>>();
387
388 let variations = self.entries.iter().flat_map(|entry| &entry.variations);
389 if let Some(variation) = variations.clone().find(|variation| variation.flags != 0 && move_bits(variation.flags, &moved) == 0) {
390 return Err(refused(format!("{} allows nothing else, removing '{value}' would turn it into an unconstrained variation (flags 0)", variation.filename)));
391 }
392
393 for variation in self.entries.iter_mut().flat_map(|entry| &mut entry.variations) {
394 variation.flags = move_bits(variation.flags, &moved);
395 }
396
397 self.categories[category_index].values.remove(value_index);
398 Ok(())
399 }
400
401 pub fn rename_value(&mut self, category: &str, from: &str, to: &str) -> Result<()> {
408 if to.is_empty() {
409 return Err(refused("the new name is empty".to_owned()));
410 }
411
412 if let Some(owner) = self.category_of(to).filter(|_| from != to) {
413 return Err(refused(format!("'{to}' already exists in '{owner}'")));
414 }
415
416 let Some(category) = self.categories.iter_mut().find(|known| known.name == category) else {
417 return Err(refused(format!("no category '{category}'")));
418 };
419
420 let Some(value) = category.values.iter_mut().find(|known| *known == from) else {
421 return Err(refused(format!("no value '{from}' in '{}'", category.name)));
422 };
423
424 *value = to.to_owned();
425 Ok(())
426 }
427
428 pub fn category_of(&self, value: &str) -> Option<&str> {
430 self.categories.iter().find(|category| category.values.iter().any(|known| known == value)).map(|category| category.name.as_str())
431 }
432
433 pub fn clone_value(&mut self, category: &str, from: &str, value: &str) -> Result<()> {
441 if self.category(category).is_none_or(|known| !known.values.iter().any(|known| known == from)) {
442 return Err(refused(format!("no value '{from}' in '{category}'")));
443 }
444
445 self.add_value(category, value)?;
446 let from_bit = self.bit(category, from);
447 let new_bit = self.bit(category, value);
448 for variation in self.entries.iter_mut().flat_map(|entry| &mut entry.variations).filter(|variation| variation.flags != 0) {
449 if variation.flags & from_bit != 0 {
450 variation.flags |= new_bit;
451 } else {
452 variation.flags &= !new_bit;
453 }
454 }
455
456 Ok(())
457 }
458
459 pub fn add_category(&mut self, name: &str) -> Result<()> {
465 if name.is_empty() {
466 return Err(refused("the category name is empty".to_owned()));
467 }
468
469 if self.category(name).is_some() {
470 return Err(refused(format!("category '{name}' already exists")));
471 }
472
473 let mut category = VariationCategory::default();
474 category.name = name.to_owned();
475 self.categories.push(category);
476 Ok(())
477 }
478
479 pub fn remove_category(&mut self, name: &str) -> Result<()> {
487 let Some(category_index) = self.categories.iter().position(|known| known.name == name) else {
488 return Err(refused(format!("no category '{name}'")));
489 };
490
491 let first = self.first_bit(name).unwrap_or_default();
492 let count = self.categories[category_index].values.len();
493 let moved = (0..self.value_count()).map(|bit| match bit {
494 bit if bit < first => Some(bit),
495 bit if bit < first + count => None,
496 bit => Some(bit - count),
497 }).collect::<Vec<_>>();
498
499 let variations = self.entries.iter().flat_map(|entry| &entry.variations);
500 if let Some(variation) = variations.clone().find(|variation| variation.flags != 0 && move_bits(variation.flags, &moved) == 0) {
501 return Err(refused(format!("{} only has bits in '{name}', removing it would turn it into an unconstrained variation (flags 0)", variation.filename)));
502 }
503
504 for variation in self.entries.iter_mut().flat_map(|entry| &mut entry.variations) {
505 variation.flags = move_bits(variation.flags, &moved);
506 }
507
508 self.categories.remove(category_index);
509 Ok(())
510 }
511
512 pub fn rename_category(&mut self, from: &str, to: &str) -> Result<()> {
518 if to.is_empty() {
519 return Err(refused("the new name is empty".to_owned()));
520 }
521
522 if from != to && self.category(to).is_some() {
523 return Err(refused(format!("category '{to}' already exists")));
524 }
525
526 let Some(category) = self.categories.iter_mut().find(|known| known.name == from) else {
527 return Err(refused(format!("no category '{from}'")));
528 };
529
530 category.name = to.to_owned();
531 Ok(())
532 }
533}
534
535fn move_bits(flags: u32, moved: &[Option<usize>]) -> u32 {
537 if flags == 0 {
538 return 0;
539 }
540
541 moved.iter().take(FLAG_BITS).enumerate()
542 .filter(|(bit, _)| flags >> bit & 1 == 1)
543 .filter_map(|(_, to)| *to)
544 .fold(0, |out, to| out | 1 << to)
545}
546
547fn refused(reason: String) -> RLibError {
549 RLibError::AssetVariationDbEditRefused(reason)
550}