1use getset::{Getters, MutGetters};
98use itertools::{Either, Itertools};
99use log::{info, error};
100use rayon::prelude::*;
101use serde_derive::{Serialize, Deserialize};
102
103use std::borrow::Cow;
104use std::collections::{BTreeMap, HashMap, HashSet};
105use std::fs::{DirBuilder, File};
106use std::io::{BufReader, BufWriter, Read, Write};
107use std::sync::mpsc::channel;
108use std::path::{Path, PathBuf};
109use std::process::Command;
110use std::{thread, thread::{spawn, JoinHandle}};
111use std::time::Duration;
112
113use rpfm_lib::binary::WriteBytes;
114use rpfm_lib::error::{Result, RLibError};
115use rpfm_lib::files::{Container, ContainerPath, db::DB, DecodeableExtraData, FileType, pack::Pack, RFile, RFileDecoded, table::Table};
116use rpfm_lib::games::{GameInfo, supported_games::*};
117use rpfm_lib::integrations::assembly_kit::table_data::RawTable;
118use rpfm_lib::schema::{Definition, DefinitionPatch, Field, FieldType, Schema};
119use rpfm_lib::utils::{current_time, files_from_subdir, last_modified_time_from_files, starts_with_case_insensitive};
120
121use crate::optimizer::{OptimizableContainer, OptimizerOptions};
122use crate::START_POS_WORKAROUND_THREAD;
123use crate::VERSION;
124
125pub const KEY_DELETES_TABLE_NAME: &str = "twad_key_deletes_tables";
131
132pub const ASSEMBLY_KIT_TABLE_FILE_NAME: &str = "ak_data";
134
135pub const USER_SCRIPT_FILE_NAME: &str = "user.script.txt";
137
138pub const VICTORY_OBJECTIVES_FILE_NAME: &str = "db/victory_objectives.txt";
140
141pub const VICTORY_OBJECTIVES_EXTRACTED_FILE_NAME: &str = "victory_objectives.txt";
143
144pub const GAMES_NEEDING_VICTORY_OBJECTIVES: [&str; 9] = [
149 KEY_PHARAOH_DYNASTIES,
150 KEY_PHARAOH,
151 KEY_TROY,
152 KEY_THREE_KINGDOMS,
153 KEY_WARHAMMER_2,
154 KEY_WARHAMMER,
155 KEY_THRONES_OF_BRITANNIA,
156 KEY_ATTILA,
157 KEY_ROME_2
158];
159
160#[derive(Default, Debug, Clone, Getters, Serialize, Deserialize)]
200#[getset(get = "pub")]
201pub struct Dependencies {
202
203 build_date: u64,
205
206 version: String,
208
209 #[serde(skip_serializing, skip_deserializing)]
213 vanilla_loose_files: HashMap<String, RFile>,
214
215 vanilla_files: HashMap<String, RFile>,
217
218 #[serde(skip_serializing, skip_deserializing)]
222 parent_files: HashMap<String, RFile>,
223
224 #[serde(skip_serializing, skip_deserializing)]
226 vanilla_loose_tables: HashMap<String, Vec<String>>,
227
228 vanilla_tables: HashMap<String, Vec<String>>,
230
231 #[serde(skip_serializing, skip_deserializing)]
235 parent_tables: HashMap<String, Vec<String>>,
236
237 #[serde(skip_serializing, skip_deserializing)]
239 vanilla_loose_locs: HashSet<String>,
240
241 vanilla_locs: HashSet<String>,
243
244 #[serde(skip_serializing, skip_deserializing)]
248 parent_locs: HashSet<String>,
249
250 #[serde(skip_serializing, skip_deserializing)]
252 vanilla_loose_folders: HashSet<String>,
253
254 vanilla_folders: HashSet<String>,
256
257 #[serde(skip_serializing, skip_deserializing)]
259 parent_folders: HashSet<String>,
260
261 #[serde(skip_serializing, skip_deserializing)]
263 vanilla_loose_paths: HashMap<String, Vec<String>>,
264
265 vanilla_paths: HashMap<String, Vec<String>>,
267
268 #[serde(skip_serializing, skip_deserializing)]
272 parent_paths: HashMap<String, Vec<String>>,
273
274 #[serde(skip_serializing, skip_deserializing)]
278 local_tables_references: HashMap<String, HashMap<i32, TableReferences>>,
279
280 #[serde(skip_serializing, skip_deserializing)]
282 localisation_data: HashMap<String, String>,
283
284 asskit_only_db_tables: HashMap<String, DB>,
286}
287
288#[derive(Eq, PartialEq, Clone, Default, Debug, Getters, MutGetters, Serialize, Deserialize)]
301#[getset(get = "pub", get_mut = "pub")]
302pub struct TableReferences {
303
304 field_name: String,
309
310 referenced_table_is_ak_only: bool,
316
317 referenced_column_is_localised: bool,
323
324 data: HashMap<String, String>,
329}
330
331type Pak3Cache = (
335 HashMap<String, Vec<String>>,
336 HashSet<String>,
337 HashSet<String>,
338 HashMap<String, Vec<String>>,
339 HashMap<String, DB>,
340);
341
342impl Dependencies {
347
348 pub fn rebuild(&mut self, schema: &Option<Schema>, parent_pack_names: &[String], file_path: Option<&Path>, game_info: &GameInfo, game_path: &Path, secondary_path: &Path) -> Result<()> {
362 let mut cache_loaded = true;
363
364 if let Some(file_path) = file_path {
366
367 *self = Self::default();
369
370 match Self::load(file_path, schema) {
372 Ok(stored_data) if !stored_data.needs_updating(game_info, game_path)? => *self = stored_data,
373 _ => cache_loaded = false,
374 }
375 }
376
377 self.local_tables_references.clear();
379
380 self.load_loose_files(schema, game_info, game_path)?;
382
383 self.load_parent_files(schema, parent_pack_names, game_info, game_path, secondary_path)?;
385
386 let loc_files = self.loc_data(true, true).unwrap_or_default();
388 let loc_decoded = loc_files.iter()
389 .filter_map(|file| if let Ok(RFileDecoded::Loc(loc)) = file.decoded() { Some(loc) } else { None })
390 .map(|file| file.data())
391 .collect::<Vec<_>>();
392
393 self.localisation_data = loc_decoded.par_iter()
394 .flat_map(|data| data.par_iter()
395 .map(|entry| (entry[0].data_to_string().to_string(), entry[1].data_to_string().to_string()))
396 .collect::<Vec<(_,_)>>()
397 ).collect::<HashMap<_,_>>();
398
399 if cache_loaded {
400 Ok(())
401 } else {
402 Err(RLibError::DependenciesCacheNotGeneratedorOutOfDate)
403 }
404 }
405
406 pub fn generate_dependencies_cache(schema: &Option<Schema>, game_info: &GameInfo, game_path: &Path, asskit_path: &Option<PathBuf>, ignore_game_files_in_ak: bool) -> Result<Self> {
408 let mut cache = Self {
409 build_date: current_time()?,
410 version: VERSION.to_owned(),
411 vanilla_files: Pack::read_and_merge_ca_packs(game_info, game_path)?.files().clone(),
412 ..Default::default()
413 };
414
415 let cacheable = cache.vanilla_files.par_iter_mut()
416 .filter_map(|(_, file)| {
417 let _ = file.guess_file_type();
418
419 match file.file_type() {
420 FileType::DB |
421 FileType::Loc => Some(file),
422 _ => None,
423 }
424 })
425 .collect::<Vec<&mut RFile>>();
426
427 cacheable.iter()
428 .for_each(|file| {
429 match file.file_type() {
430 FileType::DB => {
431 if let Some(table_name) = file.db_table_name_from_path() {
432 match cache.vanilla_tables.get_mut(table_name) {
433 Some(table_paths) => table_paths.push(file.path_in_container_raw().to_owned()),
434 None => { cache.vanilla_tables.insert(table_name.to_owned(), vec![file.path_in_container_raw().to_owned()]); },
435 }
436 }
437 }
438 FileType::Loc => {
439 cache.vanilla_locs.insert(file.path_in_container_raw().to_owned());
440 }
441 _ => {}
442 }
443 }
444 );
445
446 cache.vanilla_folders = cache.vanilla_files.par_iter().filter_map(|(path, _)| {
447 let file_path_split = path.split('/').collect::<Vec<&str>>();
448 let folder_path_len = file_path_split.len() - 1;
449 if folder_path_len == 0 {
450 None
451 } else {
452
453 let mut paths = Vec::with_capacity(folder_path_len);
454
455 for (index, folder) in file_path_split.iter().enumerate() {
456 if index < path.len() - 1 && !folder.is_empty() {
457 paths.push(file_path_split[0..=index].join("/"))
458 }
459 }
460
461 Some(paths)
462 }
463 }).flatten().collect::<HashSet<String>>();
464
465 cache.vanilla_files.keys().for_each(|path| {
466 let lower = path.to_lowercase();
467 match cache.vanilla_paths.get_mut(&lower) {
468 Some(paths) => paths.push(path.to_owned()),
469 None => { cache.vanilla_paths.insert(lower, vec![path.to_owned()]); },
470 }
471 });
472
473 cache.load_loose_files(&None, game_info, game_path)?;
477
478 if let Some(path) = asskit_path {
480 let _ = cache.generate_asskit_only_db_tables(schema, path, *game_info.raw_db_version(), ignore_game_files_in_ak);
481 }
482
483 Ok(cache)
484 }
485
486 fn generate_asskit_only_db_tables(&mut self, schema: &Option<Schema>, raw_db_path: &Path, version: i16, ignore_game_files: bool) -> Result<()> {
493 let files_to_ignore = if ignore_game_files {
494 self.vanilla_tables.keys().map(|table_name| &table_name[..table_name.len() - 7]).collect::<Vec<_>>()
495 } else {
496 vec![]
497 };
498 let raw_tables = RawTable::read_all(raw_db_path, version, &files_to_ignore)?;
499 let asskit_only_db_tables = raw_tables.par_iter()
500 .map(|x| match schema {
501 Some(schema) => {
502 let mut table_name = x.definition.clone().unwrap().name.unwrap().to_owned();
503 table_name.pop();
504 table_name.pop();
505 table_name.pop();
506 table_name.pop();
507
508 table_name = format!("{table_name}_tables");
509
510 let definition = schema.definitions().get(&table_name).and_then(|x| x.first());
511
512 x.to_db(definition)
513 }
514 None => x.to_db(None),
515 })
516 .collect::<Result<Vec<DB>>>()?;
517
518 let mut asskit_only_db_tables = asskit_only_db_tables.par_iter().map(|table| (table.table_name().to_owned(), table.clone())).collect::<HashMap<String, DB>>();
520
521 let decode_extra_data = DecodeableExtraData::default();
522 let extra_data = Some(decode_extra_data);
523
524 let mut files = self.vanilla_loose_locs.iter().filter_map(|path| {
526 self.vanilla_loose_files.remove(path).map(|file| (path.to_owned(), file))
527 }).collect::<Vec<_>>();
528
529 files.par_iter_mut().for_each(|(_, file)| {
530 let _ = file.decode(&extra_data, true, false);
531 });
532
533 self.vanilla_loose_files.par_extend(files);
534
535 let mut files = self.vanilla_locs.iter().filter_map(|path| {
537 self.vanilla_files.remove(path).map(|file| (path.to_owned(), file))
538 }).collect::<Vec<_>>();
539
540 files.par_iter_mut().for_each(|(_, file)| {
541 let _ = file.decode(&extra_data, true, false);
542 });
543
544 self.vanilla_files.par_extend(files);
545
546 self.bruteforce_loc_key_order(&mut Schema::default(), None, None, Some(&mut asskit_only_db_tables))?;
547 self.asskit_only_db_tables = asskit_only_db_tables;
548
549 Ok(())
550 }
551
552 pub fn generate_local_db_references(&mut self, schema: &Schema, packs: &BTreeMap<String, Pack>, table_names: &[String]) {
558
559 let local_tables_references = packs.values()
560 .flat_map(|pack| pack.files_by_type(&[FileType::DB]))
561 .par_bridge()
562 .filter_map(|file| {
563 if let Ok(RFileDecoded::DB(db)) = file.decoded() {
564
565 if table_names.is_empty() || table_names.iter().any(|x| x == db.table_name()) {
567 Some((db.table_name().to_owned(), self.generate_references(schema, db.table_name(), db.definition())))
568 } else { None }
569 } else { None }
570 }).collect::<HashMap<_, _>>();
571
572 self.local_tables_references.extend(local_tables_references);
573 }
574
575 pub fn generate_local_definition_references(&mut self, schema: &Schema, table_name: &str, definition: &Definition) {
577 self.local_tables_references.insert(table_name.to_owned(), self.generate_references(schema, table_name, definition));
578 }
579
580 pub fn generate_references(&self, schema: &Schema, local_table_name: &str, definition: &Definition) -> HashMap<i32, TableReferences> {
582
583 let mut definition = definition.clone();
586 self.add_recursive_lookups_to_definition(schema, &mut definition, local_table_name);
587
588 let patches = Some(definition.patches());
589 let fields_processed = definition.fields_processed();
590
591 if local_table_name == KEY_DELETES_TABLE_NAME {
594 let mut hashmap = HashMap::new();
595 let mut references = TableReferences::default();
596 *references.field_name_mut() = "table_name".to_owned();
597
598 for key in schema.definitions().keys() {
599 if key.len() > 7 {
600 let table_name = key.to_owned().drain(..key.len() - 7).collect::<String>();
601 references.data.insert(table_name, String::new());
602 }
603 }
604
605 hashmap.insert(1, references);
606 return hashmap;
607 }
608
609 fields_processed.par_iter().enumerate().filter_map(|(column, field)| {
610 match field.is_reference(patches) {
611 Some((ref ref_table, ref ref_column)) => {
612 if !ref_table.is_empty() && !ref_column.is_empty() {
613 let ref_table = format!("{ref_table}_tables");
614
615 let lookup_data = if let Some(ref data) = field.lookup_no_patch() { data.to_vec() } else { Vec::with_capacity(0) };
617 let mut references = TableReferences::default();
618 *references.field_name_mut() = field.name().to_owned();
619
620 let fake_found = self.db_reference_data_from_asskit_tables(&mut references, (&ref_table, ref_column, &lookup_data));
621 let real_found = self.db_reference_data_from_vanilla_and_modded_tables(&mut references, (&ref_table, ref_column, &lookup_data));
622
623 if fake_found && real_found.is_none() {
624 references.referenced_table_is_ak_only = true;
625 }
626
627 if let Some(ref_definition) = real_found {
628 if ref_definition.localised_fields().iter().any(|x| x.name() == ref_column) {
629 references.referenced_column_is_localised = true;
630 }
631 }
632
633 Some((column as i32, references))
634 } else { None }
635 },
636
637 None => {
639 if let Some(ref lookup_data) = field.lookup_no_patch() {
640
641 if field.is_key(patches) && fields_processed.iter().filter(|x| x.is_key(patches)).count() == 1 {
643 let ref_table = local_table_name;
644 let ref_column = field.name();
645
646 let mut references = TableReferences::default();
648 *references.field_name_mut() = field.name().to_owned();
649
650 let fake_found = self.db_reference_data_from_asskit_tables(&mut references, (ref_table, ref_column, lookup_data));
651 let real_found = self.db_reference_data_from_vanilla_and_modded_tables(&mut references, (ref_table, ref_column, lookup_data));
652
653 if fake_found && real_found.is_none() {
654 references.referenced_table_is_ak_only = true;
655 }
656
657 if let Some(ref_definition) = real_found {
658 if ref_definition.localised_fields().iter().any(|x| x.name() == ref_column) {
659 references.referenced_column_is_localised = true;
660 }
661 }
662
663 Some((column as i32, references))
664 } else { None }
665 } else { None }
666 },
667 }
668 }).collect::<HashMap<_, _>>()
669 }
670
671 pub fn load(file_path: &Path, schema: &Option<Schema>) -> Result<Self> {
673
674 let mut file_path_1 = file_path.to_path_buf();
678 let handle_1: JoinHandle<Result<(u64, String, Vec<RFile>)>> = spawn(move || {
679 file_path_1.set_extension("pak1");
680 let mut file = BufReader::new(File::open(&file_path_1)?);
681 let mut data = Vec::with_capacity(file.get_ref().metadata()?.len() as usize);
682 file.read_to_end(&mut data)?;
683
684 bitcode::deserialize(&data).map_err(From::from)
686 });
687
688 let mut file_path_2 = file_path.to_path_buf();
689 let handle_2: JoinHandle<Result<Vec<RFile>>> = spawn(move || {
690 file_path_2.set_extension("pak2");
691 let mut file = BufReader::new(File::open(&file_path_2)?);
692 let mut data = Vec::with_capacity(file.get_ref().metadata()?.len() as usize);
693 file.read_to_end(&mut data)?;
694
695 bitcode::deserialize(&data).map_err(From::from)
697 });
698
699 let mut file_path_3 = file_path.to_path_buf();
700 let handle_3: JoinHandle<Result<Pak3Cache>> = spawn(move || {
701 file_path_3.set_extension("pak3");
702 let mut file = BufReader::new(File::open(&file_path_3)?);
703 let mut data = Vec::with_capacity(file.get_ref().metadata()?.len() as usize);
704 file.read_to_end(&mut data)?;
705
706 bitcode::deserialize(&data).map_err(From::from)
708 });
709
710 let mut dependencies = Self::default();
712 let data_3 = handle_3.join().unwrap()?;
713 let data_2 = handle_2.join().unwrap()?;
714 let data_1 = handle_1.join().unwrap()?;
715
716 let mut vanilla_files: HashMap<_,_> = data_1.2.into_par_iter().map(|file| (file.path_in_container_raw().to_owned(), file)).collect();
719 vanilla_files.par_extend(data_2.into_par_iter().map(|file| (file.path_in_container_raw().to_owned(), file)));
720
721 dependencies.build_date = data_1.0;
722 dependencies.version = data_1.1;
723 dependencies.vanilla_files = vanilla_files;
724 dependencies.vanilla_tables = data_3.0;
725 dependencies.vanilla_locs = data_3.1;
726 dependencies.vanilla_folders = data_3.2;
727 dependencies.vanilla_paths = data_3.3;
728 dependencies.asskit_only_db_tables = data_3.4;
729
730 if let Some(schema) = schema {
732 let mut decode_extra_data = DecodeableExtraData::default();
733 decode_extra_data.set_schema(Some(schema));
734 let extra_data = Some(decode_extra_data);
735
736 let mut files = dependencies.vanilla_locs.iter().chain(dependencies.vanilla_tables.values().flatten()).filter_map(|path| {
737 dependencies.vanilla_files.remove(path).map(|file| (path.to_owned(), file))
738 }).collect::<Vec<_>>();
739
740 files.par_iter_mut().for_each(|(_, file)| {
741 let _ = file.decode(&extra_data, true, false);
742 });
743
744 dependencies.vanilla_files.par_extend(files);
745 }
746
747 Ok(dependencies)
748 }
749
750 pub fn save(&mut self, file_path: &Path) -> Result<()> {
752 let mut folder_path = file_path.to_owned();
753 folder_path.pop();
754 DirBuilder::new().recursive(true).create(&folder_path)?;
755
756 let mut file_path_1 = file_path.to_path_buf();
757 let mut file_path_2 = file_path.to_path_buf();
758 let mut file_path_3 = file_path.to_path_buf();
759
760 file_path_1.set_extension("pak1");
761 file_path_2.set_extension("pak2");
762 file_path_3.set_extension("pak3");
763
764 let mut file_1 = File::create(&file_path_1)?;
765 let mut file_2 = File::create(&file_path_2)?;
766 let mut file_3 = File::create(&file_path_3)?;
767
768 let mut vanilla_files_1 = self.vanilla_files.par_iter().map(|(_, b)| b.clone()).collect::<Vec<RFile>>();
772 let vanilla_files_2 = vanilla_files_1.split_off(self.vanilla_files.len() / 2);
773
774 let serialized_1: Vec<u8> = bitcode::serialize(&(&self.build_date, &self.version, &vanilla_files_1))?;
776 let serialized_2: Vec<u8> = bitcode::serialize(&vanilla_files_2)?;
777 let serialized_3: Vec<u8> = bitcode::serialize(&(&self.vanilla_tables, &self.vanilla_locs, &self.vanilla_folders, &self.vanilla_paths, &self.asskit_only_db_tables))?;
778
779 file_1.write_all(&serialized_1).map_err(RLibError::from)?;
780 file_2.write_all(&serialized_2).map_err(RLibError::from)?;
781 file_3.write_all(&serialized_3).map_err(From::from)
782 }
783
784 pub fn needs_updating(&self, game_info: &GameInfo, game_path: &Path) -> Result<bool> {
786 let ca_paths = game_info.ca_packs_paths(game_path)?;
787 let last_date = last_modified_time_from_files(&ca_paths)?;
788 Ok(last_date > self.build_date || self.version != VERSION)
789 }
790
791 fn load_loose_files(&mut self, schema: &Option<Schema>, game_info: &GameInfo, game_path: &Path) -> Result<()> {
793 self.vanilla_loose_files.clear();
794 self.vanilla_loose_tables.clear();
795 self.vanilla_loose_locs.clear();
796 self.vanilla_loose_folders.clear();
797 self.vanilla_loose_paths.clear();
798
799 let game_data_path = game_info.data_path(game_path)?;
800 let game_data_path_str = game_data_path.to_string_lossy().replace('\\', "/");
801
802 self.vanilla_loose_files = files_from_subdir(&game_data_path, true)?
803 .into_par_iter()
804 .filter_map(|path| {
805 let mut path = path.to_string_lossy().replace('\\', "/");
806 if !path.ends_with(".pack") {
807 if let Ok(mut rfile) = RFile::new_from_file(&path) {
808 let subpath = path.split_off(game_data_path_str.len() + 1);
809 rfile.set_path_in_container_raw(&subpath);
810 let _ = rfile.guess_file_type();
811 Some((subpath, rfile))
812 } else {
813 None
814 }
815 } else {
816 None
817 }
818 })
819 .collect::<HashMap<String, RFile>>();
820
821 let cacheable = self.vanilla_loose_files.par_iter_mut()
822 .filter_map(|(_, file)| {
823 let _ = file.guess_file_type();
824
825 match file.file_type() {
826 FileType::DB |
827 FileType::Loc => Some(file),
828 _ => None,
829 }
830 })
831 .collect::<Vec<&mut RFile>>();
832
833 cacheable.iter()
834 .for_each(|file| {
835 match file.file_type() {
836 FileType::DB => {
837 if let Some(table_name) = file.db_table_name_from_path() {
838 match self.vanilla_loose_tables.get_mut(table_name) {
839 Some(table_paths) => table_paths.push(file.path_in_container_raw().to_owned()),
840 None => { self.vanilla_loose_tables.insert(table_name.to_owned(), vec![file.path_in_container_raw().to_owned()]); },
841 }
842 }
843 }
844 FileType::Loc => {
845 self.vanilla_loose_locs.insert(file.path_in_container_raw().to_owned());
846 }
847 _ => {}
848 }
849 }
850 );
851
852 self.vanilla_loose_folders = self.vanilla_loose_files.par_iter().filter_map(|(path, _)| {
853 let file_path_split = path.split('/').collect::<Vec<&str>>();
854 let folder_path_len = file_path_split.len() - 1;
855 if folder_path_len == 0 {
856 None
857 } else {
858
859 let mut paths = Vec::with_capacity(folder_path_len);
860
861 for (index, folder) in file_path_split.iter().enumerate() {
862 if index < path.len() - 1 && !folder.is_empty() {
863 paths.push(file_path_split[0..=index].join("/"))
864 }
865 }
866
867 Some(paths)
868 }
869 }).flatten().collect::<HashSet<String>>();
870
871 self.vanilla_loose_files.keys().for_each(|path| {
872 let lower = path.to_lowercase();
873 match self.vanilla_loose_paths.get_mut(&lower) {
874 Some(paths) => paths.push(path.to_owned()),
875 None => { self.vanilla_loose_paths.insert(lower, vec![path.to_owned()]); },
876 }
877 });
878
879 if let Some(schema) = schema {
881 let mut decode_extra_data = DecodeableExtraData::default();
882 decode_extra_data.set_schema(Some(schema));
883 let extra_data = Some(decode_extra_data);
884
885 let mut files = self.vanilla_loose_locs.iter().chain(self.vanilla_loose_tables.values().flatten()).filter_map(|path| {
886 self.vanilla_loose_files.remove(path).map(|file| (path.to_owned(), file))
887 }).collect::<Vec<_>>();
888
889 files.par_iter_mut().for_each(|(_, file)| {
890 let _ = file.decode(&extra_data, true, false);
891 });
892
893 self.vanilla_loose_files.par_extend(files);
894 }
895
896 Ok(())
897 }
898
899
900 fn load_parent_files(&mut self, schema: &Option<Schema>, parent_pack_names: &[String], game_info: &GameInfo, game_path: &Path, secondary_path: &Path) -> Result<()> {
902 self.parent_files.clear();
903 self.parent_tables.clear();
904 self.parent_locs.clear();
905 self.parent_folders.clear();
906 self.parent_paths.clear();
907
908 self.load_parent_packs(parent_pack_names, game_info, game_path, secondary_path)?;
910 self.parent_files.par_iter_mut().map(|(_, file)| file.guess_file_type()).collect::<Result<()>>()?;
911
912 self.parent_files.iter()
914 .for_each(|(path, file)| {
915 match file.file_type() {
916 FileType::DB => {
917 if let Some(table_name) = file.db_table_name_from_path() {
918 match self.parent_tables.get_mut(table_name) {
919 Some(table_paths) => table_paths.push(path.to_owned()),
920 None => { self.parent_tables.insert(table_name.to_owned(), vec![path.to_owned()]); },
921 }
922 }
923 }
924 FileType::Loc => {
925 self.parent_locs.insert(path.to_owned());
926 }
927 _ => {}
928 }
929 }
930 );
931
932 self.parent_folders = self.parent_files.par_iter().filter_map(|(path, _)| {
934 let file_path_split = path.split('/').collect::<Vec<&str>>();
935 let folder_path_len = file_path_split.len() - 1;
936 if folder_path_len == 0 {
937 None
938 } else {
939
940 let mut paths = Vec::with_capacity(folder_path_len);
941
942 for (index, folder) in file_path_split.iter().enumerate() {
943 if index < path.len() - 1 && !folder.is_empty() {
944 paths.push(file_path_split[0..=index].join("/"))
945 }
946 }
947
948 Some(paths)
949 }
950 }).flatten().collect::<HashSet<String>>();
951
952 self.parent_files.keys().for_each(|path| {
953 let lower = path.to_lowercase();
954 match self.parent_paths.get_mut(&lower) {
955 Some(paths) => paths.push(path.to_owned()),
956 None => { self.parent_paths.insert(lower, vec![path.to_owned()]); },
957 }
958 });
959
960 if let Some(schema) = schema {
962 let mut decode_extra_data = DecodeableExtraData::default();
963 decode_extra_data.set_schema(Some(schema));
964 let extra_data = Some(decode_extra_data);
965
966 let mut files = self.parent_tables.values().flatten().filter_map(|path| {
967 self.parent_files.remove(path).map(|file| (path.to_owned(), file))
968 }).collect::<Vec<_>>();
969
970 files.par_iter_mut().for_each(|(_, file)| {
971 let _ = file.decode(&extra_data, true, false);
972 });
973
974 self.parent_files.par_extend(files);
975 }
976
977 let mut files = self.parent_locs.iter().filter_map(|path| {
979 self.parent_files.remove(path).map(|file| (path.to_owned(), file))
980 }).collect::<Vec<_>>();
981
982 files.par_iter_mut().for_each(|(_, file)| {
983 let _ = file.decode(&None, true, false);
984 });
985
986 self.parent_files.par_extend(files);
987
988 Ok(())
989 }
990
991 fn load_parent_packs(&mut self, parent_pack_names: &[String], game_info: &GameInfo, game_path: &Path, secondary_path: &Path) -> Result<()> {
994 let data_packs_paths = game_info.data_packs_paths(game_path).unwrap_or_default();
995 let secondary_packs_paths = game_info.secondary_packs_paths(secondary_path);
996 let content_packs_paths = game_info.content_packs_paths(game_path);
997 let mut loaded_packfiles = vec![];
998
999 parent_pack_names.iter().for_each(|pack_name| self.load_parent_pack(pack_name, &mut loaded_packfiles, &data_packs_paths, &secondary_packs_paths, &content_packs_paths, game_info));
1000
1001 Ok(())
1002 }
1003
1004 fn load_parent_pack(
1007 &mut self,
1008 pack_name: &str,
1009 already_loaded: &mut Vec<String>,
1010 data_paths: &[PathBuf],
1011 secondary_paths: &Option<Vec<PathBuf>>,
1012 content_paths: &Option<Vec<PathBuf>>,
1013 game_info: &GameInfo
1014 ) {
1015 if !already_loaded.contains(&pack_name.to_owned()) {
1017
1018 if let Some(path) = data_paths.iter().find(|x| x.file_name().unwrap().to_string_lossy() == pack_name) {
1020 if let Ok(pack) = Pack::read_and_merge(std::slice::from_ref(path), game_info, true, false, false) {
1021 already_loaded.push(pack_name.to_owned());
1022 pack.dependencies().iter().for_each(|(_, pack_name)| self.load_parent_pack(pack_name, already_loaded, data_paths, secondary_paths, content_paths, game_info));
1023 self.parent_files.extend(pack.files().clone());
1024
1025 return;
1026 }
1027 }
1028
1029 if let Some(ref paths) = secondary_paths {
1031 if let Some(path) = paths.iter().find(|x| x.file_name().unwrap().to_string_lossy() == pack_name) {
1032 if let Ok(pack) = Pack::read_and_merge(std::slice::from_ref(path), game_info, true, false, false) {
1033 already_loaded.push(pack_name.to_owned());
1034 pack.dependencies().iter().for_each(|(_, pack_name)| self.load_parent_pack(pack_name, already_loaded, data_paths, secondary_paths, content_paths, game_info));
1035 self.parent_files.extend(pack.files().clone());
1036
1037 return;
1038 }
1039 }
1040 }
1041
1042 if let Some(ref paths) = content_paths {
1044 if let Some(path) = paths.iter().find(|x| x.file_name().unwrap().to_string_lossy() == pack_name) {
1045 if let Ok(pack) = Pack::read_and_merge(std::slice::from_ref(path), game_info, true, false, false) {
1046 already_loaded.push(pack_name.to_owned());
1047 pack.dependencies().iter().for_each(|(_, pack_name)| self.load_parent_pack(pack_name, already_loaded, data_paths, secondary_paths, content_paths, game_info));
1048 self.parent_files.extend(pack.files().clone());
1049 }
1050 }
1051 }
1052 }
1053 }
1054
1055 pub fn decode_tables(&mut self, schema: &Option<Schema>) {
1059 if let Some(schema) = schema {
1060
1061 let mut decode_extra_data = DecodeableExtraData::default();
1062 decode_extra_data.set_schema(Some(schema));
1063 let extra_data = Some(decode_extra_data);
1064
1065 let mut files = self.vanilla_loose_locs.iter().chain(self.vanilla_loose_tables.values().flatten()).filter_map(|path| {
1067 self.vanilla_loose_files.remove(path).map(|file| (path.to_owned(), file))
1068 }).collect::<Vec<_>>();
1069
1070 files.par_iter_mut().for_each(|(_, file)| {
1071 let _ = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| file.decode(&extra_data, true, false)));
1072 });
1073
1074 self.vanilla_loose_files.par_extend(files);
1075
1076 let mut files = self.vanilla_locs.iter().chain(self.vanilla_tables.values().flatten()).filter_map(|path| {
1078 self.vanilla_files.remove(path).map(|file| (path.to_owned(), file))
1079 }).collect::<Vec<_>>();
1080
1081 files.par_iter_mut().for_each(|(_, file)| {
1082 let _ = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| file.decode(&extra_data, true, false)));
1083 });
1084
1085 self.vanilla_files.par_extend(files);
1086
1087 let mut files = self.parent_locs.iter().chain(self.parent_tables.values().flatten()).filter_map(|path| {
1089 self.parent_files.remove(path).map(|file| (path.to_owned(), file))
1090 }).collect::<Vec<_>>();
1091
1092 files.par_iter_mut().for_each(|(_, file)| {
1093 let _ = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| file.decode(&extra_data, true, false)));
1094 });
1095
1096 self.parent_files.par_extend(files);
1097 }
1098 }
1099
1100 pub fn file(&self, file_path: &str, include_vanilla: bool, include_parent: bool, case_insensitive: bool) -> Result<&RFile> {
1106 let file_path = if let Some(file_path) = file_path.strip_prefix('/') {
1107 file_path
1108 } else {
1109 file_path
1110 };
1111
1112 if include_parent {
1113
1114 if let Some(file) = self.parent_files.get(file_path) {
1116 return Ok(file);
1117 }
1118
1119 if case_insensitive {
1120 let lower = file_path.to_lowercase();
1121 if let Some(file) = self.parent_paths.get(&lower).and_then(|paths| self.parent_files.get(&paths[0])) {
1122 return Ok(file);
1123 }
1124 }
1125 }
1126
1127 if include_vanilla {
1128
1129 if let Some(file) = self.vanilla_files.get(file_path) {
1131 return Ok(file);
1132 }
1133
1134 if case_insensitive {
1135 let lower = file_path.to_lowercase();
1136 if let Some(file) = self.vanilla_paths.get(&lower).and_then(|paths| self.vanilla_files.get(&paths[0])) {
1137 return Ok(file);
1138 }
1139
1140 }
1141
1142 if let Some(file) = self.vanilla_loose_files.get(file_path) {
1144 return Ok(file);
1145 }
1146
1147 if case_insensitive {
1148 let lower = file_path.to_lowercase();
1149 if let Some(file) = self.vanilla_loose_paths.get(&lower).and_then(|paths| self.vanilla_loose_files.get(&paths[0])) {
1150 return Ok(file);
1151 }
1152 }
1153 }
1154
1155 Err(RLibError::DependenciesCacheFileNotFound(file_path.to_owned()))
1156 }
1157
1158 pub fn file_mut(&mut self, file_path: &str, include_vanilla: bool, include_parent: bool) -> Result<&mut RFile> {
1160 if include_parent {
1161 if let Some(file) = self.parent_files.get_mut(file_path) {
1162 return Ok(file);
1163 }
1164 }
1165
1166 if include_vanilla {
1167 if let Some(file) = self.vanilla_files.get_mut(file_path) {
1168 return Ok(file);
1169 }
1170
1171 if let Some(file) = self.vanilla_loose_files.get_mut(file_path) {
1172 return Ok(file);
1173 }
1174 }
1175
1176 Err(RLibError::DependenciesCacheFileNotFound(file_path.to_owned()))
1177 }
1178
1179 pub fn files_mut_by_paths(
1181 &mut self,
1182 paths: &HashSet<String>,
1183 include_vanilla: bool,
1184 include_parent: bool,
1185 ) -> HashMap<String, &mut RFile> {
1186 let mut result: HashMap<String, &mut RFile> = HashMap::with_capacity(paths.len());
1187
1188 if include_parent {
1189 for (k, v) in self.parent_files.iter_mut() {
1190 if paths.contains(k) {
1191 result.insert(k.clone(), v);
1192 }
1193 }
1194 }
1195
1196 if include_vanilla {
1197 for (k, v) in self.vanilla_files.iter_mut() {
1198 if paths.contains(k) && !result.contains_key(k) {
1199 result.insert(k.clone(), v);
1200 }
1201 }
1202
1203 for (k, v) in self.vanilla_loose_files.iter_mut() {
1204 if paths.contains(k) && !result.contains_key(k) {
1205 result.insert(k.clone(), v);
1206 }
1207 }
1208 }
1209
1210 result
1211 }
1212
1213 pub fn files_by_path(&self, file_paths: &[ContainerPath], include_vanilla: bool, include_parent: bool, case_insensitive: bool) -> HashMap<String, &RFile> {
1215 let (file_paths, folder_paths): (Vec<_>, Vec<_>) = file_paths.iter().partition_map(|file_path| match file_path {
1216 ContainerPath::File(file_path) => Either::Left(file_path.to_owned()),
1217 ContainerPath::Folder(file_path) => Either::Right(file_path.to_owned()),
1218 });
1219
1220 let mut hashmap = HashMap::new();
1221
1222 if !file_paths.is_empty() {
1224 hashmap.extend(file_paths.par_iter()
1225 .filter_map(|file_path| self.file(file_path, include_vanilla, include_parent, case_insensitive)
1226 .ok()
1227 .map(|file| (file_path.to_owned(), file)))
1228 .collect::<Vec<(_,_)>>()
1229 );
1230 }
1231
1232 if !folder_paths.is_empty() {
1234 hashmap.extend(folder_paths.into_par_iter().flat_map(|folder_path| {
1235 let mut folder = vec![];
1236 let folder_path = folder_path.to_owned() + "/";
1237 if include_vanilla {
1238
1239 if folder_path == "/" {
1240 folder.extend(self.vanilla_loose_files.par_iter()
1241 .map(|(path, file)| (path.to_owned(), file))
1242 .collect::<Vec<(_,_)>>());
1243
1244 folder.extend(self.vanilla_files.par_iter()
1245 .map(|(path, file)| (path.to_owned(), file))
1246 .collect::<Vec<(_,_)>>());
1247
1248 } else {
1249 folder.extend(self.vanilla_loose_files.par_iter()
1250 .filter(|(path, _)| {
1251 if case_insensitive {
1252 starts_with_case_insensitive(path, &folder_path)
1253 } else {
1254 path.starts_with(&folder_path)
1255 }
1256 })
1257 .map(|(path, file)| (path.to_owned(), file))
1258 .collect::<Vec<(_,_)>>());
1259
1260 folder.extend(self.vanilla_files.par_iter()
1261 .filter(|(path, _)| {
1262 if case_insensitive {
1263 starts_with_case_insensitive(path, &folder_path)
1264 } else {
1265 path.starts_with(&folder_path)
1266 }
1267 })
1268 .map(|(path, file)| (path.to_owned(), file))
1269 .collect::<Vec<(_,_)>>());
1270 }
1271 }
1272
1273 if include_parent {
1274 if folder_path == "/" {
1275 folder.extend(self.parent_files.par_iter()
1276 .map(|(path, file)| (path.to_owned(), file))
1277 .collect::<Vec<(_,_)>>());
1278
1279 } else {
1280 folder.extend(self.parent_files.par_iter()
1281 .filter(|(path, _)| {
1282 if case_insensitive {
1283 starts_with_case_insensitive(path, &folder_path)
1284 } else {
1285 path.starts_with(&folder_path)
1286 }
1287 })
1288 .map(|(path, file)| (path.to_owned(), file))
1289 .collect::<Vec<(_,_)>>());
1290 }
1291 }
1292 folder
1293 }).collect::<Vec<(_,_)>>());
1294 }
1295
1296 hashmap
1297 }
1298
1299 pub fn files_by_types(&self, file_types: &[FileType], include_vanilla: bool, include_parent: bool) -> HashMap<String, &RFile> {
1301 let mut files = HashMap::new();
1302
1303 if include_vanilla {
1305 files.extend(self.vanilla_loose_files.par_iter().chain(self.vanilla_files.par_iter())
1306 .filter(|(_, file)| file_types.contains(&file.file_type()))
1307 .map(|(path, file)| (path.to_owned(), file))
1308 .collect::<HashMap<_,_>>());
1309 }
1310
1311 if include_parent {
1312 files.extend(self.parent_files.par_iter()
1313 .filter(|(_, file)| file_types.contains(&file.file_type()))
1314 .map(|(path, file)| (path.to_owned(), file))
1315 .collect::<HashMap<_,_>>());
1316 }
1317
1318 files
1319 }
1320
1321 pub fn files_by_types_mut(&mut self, file_types: &[FileType], include_vanilla: bool, include_parent: bool) -> HashMap<String, &mut RFile> {
1323 let mut files = HashMap::new();
1324
1325 if include_vanilla {
1327 files.extend(self.vanilla_loose_files.par_iter_mut().chain(self.vanilla_files.par_iter_mut())
1328 .filter(|(_, file)| file_types.contains(&file.file_type()))
1329 .map(|(path, file)| (path.to_owned(), file))
1330 .collect::<HashMap<_,_>>());
1331 }
1332
1333 if include_parent {
1334 files.extend(self.parent_files.par_iter_mut()
1335 .filter(|(_, file)| file_types.contains(&file.file_type()))
1336 .map(|(path, file)| (path.to_owned(), file))
1337 .collect::<HashMap<_,_>>());
1338 }
1339
1340 files
1341 }
1342
1343 pub fn loc_data(&self, include_vanilla: bool, include_parent: bool) -> Result<Vec<&RFile>> {
1347 let mut cache = vec![];
1348
1349 if include_vanilla {
1350 let mut vanilla_loose_locs = self.vanilla_loose_locs.iter().collect::<Vec<_>>();
1351 vanilla_loose_locs.sort();
1352
1353 for path in &vanilla_loose_locs {
1354 if let Some(file) = self.vanilla_loose_files.get(*path) {
1355 cache.push(file);
1356 }
1357 }
1358
1359 let mut vanilla_locs = self.vanilla_locs.iter().collect::<Vec<_>>();
1360 vanilla_locs.sort();
1361
1362 for path in &vanilla_locs {
1363 if let Some(file) = self.vanilla_files.get(*path) {
1364 cache.push(file);
1365 }
1366 }
1367 }
1368
1369 if include_parent {
1370 let mut parent_locs = self.parent_locs.iter().collect::<Vec<_>>();
1371 parent_locs.sort();
1372
1373 for path in &parent_locs {
1374 if let Some(file) = self.parent_files.get(*path) {
1375 cache.push(file);
1376 }
1377 }
1378 }
1379
1380 Ok(cache)
1381 }
1382
1383 pub fn db_data(&self, table_name: &str, include_vanilla: bool, include_parent: bool) -> Result<Vec<&RFile>> {
1389 let mut cache = vec![];
1390
1391 if include_vanilla {
1392 if let Some(vanilla_loose_tables) = self.vanilla_loose_tables.get(table_name) {
1393 let mut vanilla_loose_tables = vanilla_loose_tables.to_vec();
1394 vanilla_loose_tables.sort();
1395
1396 for path in &vanilla_loose_tables {
1397 if let Some(file) = self.vanilla_loose_files.get(path) {
1398 cache.push(file);
1399 }
1400 }
1401 }
1402
1403 if let Some(vanilla_tables) = self.vanilla_tables.get(table_name) {
1404 let mut vanilla_tables = vanilla_tables.to_vec();
1405 vanilla_tables.sort();
1406
1407 for path in &vanilla_tables {
1408 if let Some(file) = self.vanilla_files.get(path) {
1409 cache.push(file);
1410 }
1411 }
1412 }
1413 }
1414
1415 if include_parent {
1416 if let Some(parent_tables) = self.parent_tables.get(table_name) {
1417 let mut parent_tables = parent_tables.to_vec();
1418 parent_tables.sort();
1419
1420 for path in &parent_tables {
1421 if let Some(file) = self.parent_files.get(path) {
1422 cache.push(file);
1423 }
1424 }
1425 }
1426 }
1427
1428 Ok(cache)
1429 }
1430
1431 pub fn db_data_datacored<'a>(&'a self, table_name: &str, packs: &'a BTreeMap<String, Pack>, include_vanilla: bool, include_parent: bool) -> Result<Vec<&'a RFile>> {
1438 let mut cache = vec![];
1439
1440 if include_vanilla {
1441 if let Some(vanilla_loose_tables) = self.vanilla_loose_tables.get(table_name) {
1442 let mut vanilla_loose_tables = vanilla_loose_tables.to_vec();
1443 vanilla_loose_tables.sort();
1444
1445 for path in &vanilla_loose_tables {
1446 if let Some(file) = self.vanilla_loose_files.get(path) {
1447 cache.push(file);
1448 }
1449 }
1450 }
1451
1452 if let Some(vanilla_tables) = self.vanilla_tables.get(table_name) {
1453 let mut vanilla_tables = vanilla_tables.to_vec();
1454 vanilla_tables.sort();
1455
1456 for path in &vanilla_tables {
1457 if let Some(file) = self.vanilla_files.get(path) {
1458 cache.push(file);
1459 }
1460 }
1461 }
1462 }
1463
1464 if include_parent {
1465 if let Some(parent_tables) = self.parent_tables.get(table_name) {
1466 let mut parent_tables = parent_tables.to_vec();
1467 parent_tables.sort();
1468
1469 for path in &parent_tables {
1470 if let Some(file) = self.parent_files.get(path) {
1471 cache.push(file);
1472 }
1473 }
1474 }
1475 }
1476
1477 let paths = cache.iter()
1478 .map(|x| x.path_in_container())
1479 .collect::<Vec<_>>();
1480
1481 for pack in packs.values() {
1482 for pack_file in pack.files_by_paths(&paths, true) {
1483 for cache_file in &mut cache {
1484 if cache_file.path_in_container() == pack_file.path_in_container() {
1485 *cache_file = pack_file;
1486 break;
1487 }
1488 }
1489 }
1490 }
1491
1492 Ok(cache)
1493 }
1494
1495 pub fn db_and_loc_data(&self, include_db: bool, include_loc: bool, include_vanilla: bool, include_parent: bool) -> Result<Vec<&RFile>> {
1499 let mut cache = vec![];
1500
1501 if include_vanilla {
1502 if include_db {
1503 let mut vanilla_loose_tables = self.vanilla_loose_tables.values().flatten().collect::<Vec<_>>();
1504 vanilla_loose_tables.sort();
1505
1506 for path in &vanilla_loose_tables {
1507 if let Some(file) = self.vanilla_loose_files.get(*path) {
1508 cache.push(file);
1509 }
1510 }
1511
1512 let mut vanilla_tables = self.vanilla_tables.values().flatten().collect::<Vec<_>>();
1513 vanilla_tables.sort();
1514
1515 for path in &vanilla_tables {
1516 if let Some(file) = self.vanilla_files.get(*path) {
1517 cache.push(file);
1518 }
1519 }
1520 }
1521
1522 if include_loc {
1523 let mut vanilla_loose_locs = self.vanilla_loose_locs.iter().collect::<Vec<_>>();
1524 vanilla_loose_locs.sort();
1525
1526 for path in &vanilla_loose_locs {
1527 if let Some(file) = self.vanilla_loose_files.get(*path) {
1528 cache.push(file);
1529 }
1530 }
1531
1532 let mut vanilla_locs = self.vanilla_locs.iter().collect::<Vec<_>>();
1533 vanilla_locs.sort();
1534
1535 for path in &vanilla_locs {
1536 if let Some(file) = self.vanilla_files.get(*path) {
1537 cache.push(file);
1538 }
1539 }
1540 }
1541 }
1542
1543 if include_parent {
1544 if include_db {
1545 let mut parent_tables = self.parent_tables.values().flatten().collect::<Vec<_>>();
1546 parent_tables.sort();
1547
1548 for path in &parent_tables {
1549 if let Some(file) = self.parent_files.get(*path) {
1550 cache.push(file);
1551 }
1552 }
1553 }
1554
1555 if include_loc {
1556 let mut parent_locs = self.parent_locs.iter().collect::<Vec<_>>();
1557 parent_locs.sort();
1558
1559 for path in &parent_locs {
1560 if let Some(file) = self.parent_files.get(*path) {
1561 cache.push(file);
1562 }
1563 }
1564 }
1565 }
1566
1567 Ok(cache)
1568 }
1569
1570 pub fn db_reference_data(&self, schema: &Schema, packs: &BTreeMap<String, Pack>, table_name: &str, definition: &Definition, loc_data: &Option<HashMap<Cow<str>, Cow<str>>>) -> HashMap<i32, TableReferences> {
1578
1579 let mut vanilla_references = match self.local_tables_references.get(table_name) {
1583 Some(cached_data) => cached_data.clone(),
1584 None => HashMap::new(),
1585 };
1586
1587 let (_loc_files, loc_decoded) = if loc_data.is_some() {
1589 (vec![], vec![])
1590 } else {
1591 let loc_files: Vec<_> = packs.values().flat_map(|pack| pack.files_by_type(&[FileType::Loc])).collect();
1592 let loc_decoded = loc_files.iter()
1593 .filter_map(|file| if let Ok(RFileDecoded::Loc(loc)) = file.decoded() { Some(loc) } else { None })
1594 .map(|file| file.data())
1595 .collect::<Vec<_>>();
1596 (loc_files, loc_decoded)
1597 };
1598
1599 let mut _loc_data_dummy = HashMap::new();
1600 let loc_data = if let Some(ref loc_data) = loc_data {
1601 loc_data
1602 } else {
1603 _loc_data_dummy = loc_decoded.par_iter()
1604 .flat_map(|data| data.par_iter()
1605 .map(|entry| (entry[0].data_to_string(), entry[1].data_to_string()))
1606 .collect::<Vec<(_,_)>>()
1607 ).collect::<HashMap<_,_>>();
1608 &_loc_data_dummy
1609 };
1610
1611 let mut definition = definition.clone();
1614 self.add_recursive_lookups_to_definition(schema, &mut definition, table_name);
1615
1616 let patches = Some(definition.patches());
1617 let fields_processed = definition.fields_processed();
1618 let local_references = fields_processed.par_iter().enumerate().filter_map(|(column, field)| {
1619 match field.is_reference(patches) {
1620 Some((ref ref_table, ref ref_column)) => {
1621 if !ref_table.is_empty() && !ref_column.is_empty() {
1622
1623 let lookup_data = if let Some(ref data) = field.lookup_no_patch() { data.to_vec() } else { Vec::with_capacity(0) };
1625 let mut references = TableReferences::default();
1626 *references.field_name_mut() = field.name().to_owned();
1627
1628 let _local_found = self.db_reference_data_from_local_pack(&mut references, (ref_table, ref_column, &lookup_data), packs, loc_data);
1629
1630 Some((column as i32, references))
1631 } else { None }
1632 }
1633
1634 None => {
1636 if let Some(ref lookup_data) = field.lookup_no_patch() {
1637
1638 if field.is_key(patches) && fields_processed.iter().filter(|x| x.is_key(patches)).count() == 1 {
1640
1641 let ref_table = if table_name.ends_with("_tables") && table_name.len() > 7 {
1643 table_name.to_owned().drain(..table_name.len() - 7).collect()
1644 } else {
1645 table_name.to_owned()
1646 };
1647
1648 let ref_column = field.name();
1649
1650 let mut references = TableReferences::default();
1652 *references.field_name_mut() = field.name().to_owned();
1653
1654 let _local_found = self.db_reference_data_from_local_pack(&mut references, (&ref_table, ref_column, lookup_data), packs, loc_data);
1655
1656 Some((column as i32, references))
1657 } else { None }
1658 } else { None }
1659 }
1660 }
1661 }).collect::<HashMap<_, _>>();
1662
1663 vanilla_references.par_iter_mut().for_each(|(key, value)|
1664 if let Some(local_value) = local_references.get(key) {
1665 value.data.extend(local_value.data.iter().map(|(k, v)| (k.clone(), v.clone())));
1666 }
1667 );
1668
1669 for (index, field) in fields_processed.iter().enumerate() {
1670 match vanilla_references.get_mut(&(index as i32)) {
1671 Some(references) => {
1672 let hardcoded_lookup = field.lookup_hardcoded(patches);
1673 if !hardcoded_lookup.is_empty() {
1674 references.data.extend(hardcoded_lookup);
1675 }
1676 },
1677 None => {
1678 let mut references = TableReferences::default();
1679 *references.field_name_mut() = field.name().to_owned();
1680 let hardcoded_lookup = field.lookup_hardcoded(patches);
1681 if !hardcoded_lookup.is_empty() {
1682 references.data.extend(hardcoded_lookup);
1683 vanilla_references.insert(index as i32, references);
1684 }
1685 },
1686 }
1687 }
1688
1689 vanilla_references
1690 }
1691
1692 fn db_reference_data_from_vanilla_and_modded_tables(&self, references: &mut TableReferences, reference_info: (&str, &str, &[String])) -> Option<Definition> {
1696 self.db_reference_data_generic(references, reference_info, None, &HashMap::new())
1697 }
1698
1699 fn db_reference_data_from_asskit_tables(&self, references: &mut TableReferences, reference_info: (&str, &str, &[String])) -> bool {
1703 let ref_table = reference_info.0;
1704 let ref_column = reference_info.1;
1705 let ref_lookup_columns = reference_info.2;
1706
1707 match self.asskit_only_db_tables.get(ref_table) {
1708 Some(table) => {
1709 let fields_processed = table.definition().fields_processed();
1710 let ref_column_index = fields_processed.iter().position(|x| x.name() == ref_column);
1711 let ref_lookup_columns_index = ref_lookup_columns.iter().map(|column| fields_processed.iter().position(|x| x.name() == column)).collect::<Vec<_>>();
1712
1713 for row in &*table.data() {
1714 let mut reference_data = String::new();
1715 let mut lookup_data = vec![];
1716
1717 if let Some(index) = ref_column_index {
1719 reference_data = row[index].data_to_string().to_string();
1720 }
1721
1722 for column in ref_lookup_columns_index.iter().flatten() {
1724 lookup_data.push(row[*column].data_to_string());
1725 }
1726
1727 references.data.insert(reference_data, lookup_data.join(" "));
1728 }
1729 true
1730 },
1731 None => false,
1732 }
1733 }
1734
1735 fn db_reference_data_from_local_pack(&self, references: &mut TableReferences, reference_info: (&str, &str, &[String]), packs: &BTreeMap<String, Pack>, loc_data: &HashMap<Cow<str>, Cow<str>>) -> Option<Definition> {
1737 self.db_reference_data_generic(references, reference_info, Some(packs), loc_data)
1738 }
1739
1740 fn db_reference_data_generic(&self, references: &mut TableReferences, reference_info: (&str, &str, &[String]), packs: Option<&BTreeMap<String, Pack>>, loc_data: &HashMap<Cow<str>, Cow<str>>) -> Option<Definition> {
1741 let mut data_found: Option<Definition> = None;
1742
1743 let ref_table = reference_info.0;
1744 let ref_column = reference_info.1;
1745 let ref_lookup_columns = reference_info.2;
1746
1747 let mut cache = HashMap::new();
1748
1749 let ref_table_full = if ref_table.ends_with("_tables") {
1751 ref_table.to_owned()
1752 } else {
1753 ref_table.to_owned() + "_tables"
1754 };
1755
1756 let files = match packs {
1757 Some(packs) => {
1758 let mut files: Vec<&RFile> = packs.values().flat_map(|pack| pack.files_by_paths(&ContainerPath::db_table_folders(&ref_table_full), true)).collect();
1759 files.append(&mut self.db_data(&ref_table_full, true, true).unwrap_or_else(|_| vec![]));
1760 files
1761 },
1762 None => self.db_data(&ref_table_full, true, true).unwrap_or_else(|_| vec![]),
1763 };
1764
1765 let mut table_data_cache: HashMap<String, HashMap<String, String>> = HashMap::new();
1766
1767 files.iter().for_each(|file| {
1768 if let Ok(RFileDecoded::DB(db)) = file.decoded() {
1769 let definition = db.definition();
1770 let fields_processed = definition.fields_processed();
1771
1772 if let Some(ref_column_index) = fields_processed.iter().position(|x| x.name() == ref_column) {
1774
1775 let lookups_analyzed = ref_lookup_columns.iter().map(|ref_lookup_path| {
1777 let ref_lookup_steps = ref_lookup_path.split(':').map(|x| x.split('#').collect::<Vec<_>>()).collect::<Vec<_>>();
1778 let mut is_loc = false;
1779 let mut col_pos = 0;
1780
1781 for (index, ref_lookup_step) in ref_lookup_steps.iter().enumerate() {
1782 if ref_lookup_step.len() == 3 {
1783 let lookup_ref_table = ref_lookup_step[0];
1784 let lookup_ref_key = ref_lookup_step[1];
1785 let lookup_ref_lookup = ref_lookup_step[2];
1786 let lookup_ref_table_long = lookup_ref_table.to_owned() + "_tables";
1787
1788 if !cache.contains_key(lookup_ref_table) {
1790 let mut files = vec![];
1791
1792 if let Some(packs) = packs {
1793 for pack in packs.values() {
1794 files.append(&mut pack.files_by_paths(&ContainerPath::db_table_folders(&lookup_ref_table_long), true));
1795 }
1796 }
1797
1798 for file in self.db_data(&lookup_ref_table_long, true, true).unwrap_or_else(|_| vec![]) {
1800 if files.iter().all(|x| x.path_in_container_raw() != file.path_in_container_raw()) {
1801 files.push(file);
1802 }
1803 }
1804
1805 if !files.is_empty() {
1806
1807 files.sort_by(|a, b| a.path_in_container_raw().cmp(b.path_in_container_raw()));
1809 cache.insert(lookup_ref_table.to_owned(), files);
1810 }
1811 }
1812
1813 if index == ref_lookup_steps.len() - 1 {
1815 if let Some(file) = cache.get(lookup_ref_table) {
1816 if let Some(file) = file.first() {
1817 if let Ok(RFileDecoded::DB(db)) = file.decoded() {
1818 let definition = db.definition();
1819 let fields_processed = definition.fields_processed();
1820 let localised_fields = definition.localised_fields();
1821
1822 match localised_fields.iter().position(|x| x.name() == lookup_ref_lookup) {
1823 Some(loc_pos) => {
1824 is_loc = true;
1825 col_pos = loc_pos;
1826 },
1827 None => match fields_processed.iter().position(|x| x.name() == lookup_ref_lookup) {
1828 Some(pos) => {
1829 is_loc = false;
1830 col_pos = pos;
1831 },
1832 None => {
1833 },
1835 }
1836 }
1837 }
1838 }
1839 }
1840 }
1841
1842 if let Some(files) = cache.get(lookup_ref_table) {
1844 for file in files {
1845 let table_data_column_cache_key = file.path_in_container_raw().to_owned() + &ref_lookup_step.join("++");
1846 if !table_data_cache.contains_key(&table_data_column_cache_key) {
1847 if let Ok(RFileDecoded::DB(db)) = file.decoded() {
1848 let definition = db.definition();
1849 let fields_processed = definition.fields_processed();
1850 let localised_fields = definition.localised_fields();
1851 let localised_order = definition.localised_key_order();
1852
1853 let loc_key = if is_loc {
1854 if let Some(loc_field) = localised_fields.get(col_pos) {
1855 let mut loc_key = String::with_capacity(2 + lookup_ref_table.len() + loc_field.name().len());
1856 loc_key.push_str(lookup_ref_table);
1857 loc_key.push('_');
1858 loc_key.push_str(loc_field.name());
1859 loc_key.push('_');
1860 loc_key
1861 } else {
1862 String::new()
1863 }
1864 } else {
1865 String::new()
1866 };
1867
1868 if let Some(source_key_column) = fields_processed.iter().position(|x| x.name() == lookup_ref_key) {
1869
1870 if index < ref_lookup_steps.len() - 1 {
1872 if let Some(source_lookup_column) = fields_processed.iter().position(|x| x.name() == lookup_ref_lookup) {
1873 let cache = db.data().iter()
1874 .filter_map(|row| Some((row.get(source_key_column)?.data_to_string().to_string(), row.get(source_lookup_column)?.data_to_string().to_string())))
1875 .collect::<HashMap<_,_>>();
1876
1877 table_data_cache.insert(table_data_column_cache_key.clone(), cache);
1878 }
1879 }
1880
1881 else if is_loc {
1883 let cache = db.data().iter()
1884 .filter_map(|row| {
1885 let mut loc_key = loc_key.to_owned();
1886 for pos in localised_order.iter() {
1887 loc_key.push_str(&row.get(*pos as usize)?.data_to_string());
1888 }
1889 Some((row.get(source_key_column)?.data_to_string().to_string(), loc_key))
1890 })
1891 .collect::<HashMap<_,_>>();
1892 table_data_cache.insert(table_data_column_cache_key.clone(), cache);
1893 }
1894
1895 else {
1896 let cache = db.data().iter()
1897 .filter_map(|row| Some((row.get(source_key_column)?.data_to_string().to_string(), row.get(col_pos)?.data_to_string().to_string())))
1898 .collect::<HashMap<_,_>>();
1899
1900 table_data_cache.insert(table_data_column_cache_key.clone(), cache);
1901 }
1902 }
1903 }
1904 }
1905 }
1906 }
1907 } else {
1908 error!("Badly built lookup. This is a bug.");
1909 }
1910 }
1911
1912 (ref_lookup_steps, is_loc)
1913
1914 }).collect::<Vec<_>>();
1915
1916 let data = db.data();
1917 for row in &*data {
1918 let mut lookup_data = Vec::with_capacity(lookups_analyzed.len());
1919
1920 let reference_data = row[ref_column_index].data_to_string();
1922
1923 for (lookup_steps, is_loc) in lookups_analyzed.iter() {
1925 if !reference_data.is_empty() {
1926
1927 if let Some(lookup) = self.db_reference_data_generic_lookup(&cache, loc_data, &reference_data, lookup_steps, *is_loc, &table_data_cache) {
1928 lookup_data.push(lookup);
1929 }
1930 }
1931 }
1932
1933 references.data.insert(reference_data.to_string(), lookup_data.into_iter().join(":"));
1934 }
1935
1936 match data_found {
1938 Some(ref definition) => {
1939 if db.definition().version() > definition.version() {
1940 data_found = Some(db.definition().clone());
1941 }
1942 }
1943
1944 None => data_found = Some(db.definition().clone()),
1945 }
1946 }
1947 }
1948 });
1949
1950 data_found
1951 }
1952
1953 fn db_reference_data_generic_lookup(
1954 &self,
1955 cache: &HashMap<String, Vec<&RFile>>,
1956 loc_data: &HashMap<Cow<str>, Cow<str>>,
1957 lookup_key: &str,
1958 lookup_steps: &[Vec<&str>],
1959 is_loc: bool,
1960 table_data_cache: &HashMap<String, HashMap<String, String>>
1961 ) -> Option<String> {
1962 let mut data_found: Option<String> = None;
1963
1964 if lookup_steps.is_empty() {
1965 return None;
1966 }
1967
1968 let current_step = &lookup_steps[0];
1969 let source_table = current_step[0];
1970
1971 if let Some(files) = cache.get(source_table) {
1972 for file in files {
1973 let table_data_column_cache_key = file.path_in_container_raw().to_owned() + ¤t_step.join("++");
1974 if let Some(table_data_column_cache) = table_data_cache.get(&table_data_column_cache_key) {
1975
1976 if let Some(lookup_value) = table_data_column_cache.get(lookup_key) {
1977
1978 if lookup_steps.len() > 1 {
1980 if !lookup_value.is_empty() {
1981 data_found = self.db_reference_data_generic_lookup(cache, loc_data, lookup_value, &lookup_steps[1..], is_loc, table_data_cache);
1982 }
1983 }
1984
1985 else if is_loc {
1987
1988 if let Some(data) = loc_data.get(&**lookup_value) {
1989 data_found = Some(data.to_string());
1990 } else if let Some(data) = self.localisation_data.get(&**lookup_value) {
1991 data_found = Some(data.to_string());
1992 } else {
1993 data_found = Some(lookup_value.to_string())
1994 }
1995 }
1996
1997 else {
1999 data_found = Some(lookup_value.to_owned());
2000 }
2001
2002 break;
2004 }
2005 }
2006 }
2007 }
2008
2009 data_found
2010 }
2011
2012 pub fn loc_key_source(&self, key: &str) -> Option<(String, String, Vec<String>)> {
2016 let key_split = key.split('_').collect::<Vec<_>>();
2017
2018 for (index, _) in key_split.iter().enumerate().rev() {
2021
2022 if index >= 1 {
2024
2025 let mut table_name = key_split[..index].join("_");
2026 let full_table_name = format!("{table_name}_tables");
2027
2028 if let Ok(rfiles) = self.db_data(&full_table_name, true, false) {
2029 let mut decoded = rfiles.iter()
2030 .filter_map(|x| if let Ok(RFileDecoded::DB(table)) = x.decoded() {
2031 Some(table)
2032 } else {
2033 None
2034 }).collect::<Vec<_>>();
2035
2036 if let Some(ak_file) = self.asskit_only_db_tables().get(&full_table_name) {
2038 decoded.push(ak_file);
2039 }
2040
2041 for table in decoded {
2042 let definition = table.definition();
2043 let localised_fields = definition.localised_fields();
2044 let localised_key_order = definition.localised_key_order();
2045 if !localised_fields.is_empty() {
2046 let mut field = String::new();
2047
2048 for (second_index, value) in key_split[index..].iter().enumerate() {
2050 field.push_str(value);
2051
2052 if localised_fields.iter().any(|x| x.name() == field) {
2053
2054 let key_data = &key_split[index + second_index + 1..].join("_");
2056
2057 let data = table.data();
2060 for row in data.iter() {
2061 let generated_key_split = localised_key_order.iter().map(|col| row[*col as usize].data_to_string()).collect::<Vec<_>>();
2062 let generated_key = generated_key_split.join("");
2063 if &generated_key == key_data {
2064 return Some((table_name, field, generated_key_split.iter().map(|x| x.to_string()).collect()));
2065 }
2066 }
2067 }
2068
2069 field.push('_');
2070 }
2071 }
2072 }
2073 }
2074
2075 table_name.push('_');
2077 }
2078 }
2079
2080 None
2081 }
2082
2083 pub fn file_exists(&self, file_path: &str, include_vanilla: bool, include_parent: bool, case_insensitive: bool) -> bool {
2089 if include_parent {
2090 if self.parent_files.contains_key(file_path) {
2091 return true
2092 } else if case_insensitive {
2093 let lower = file_path.to_lowercase();
2094 if self.parent_paths.contains_key(&lower) {
2095 return true
2096 }
2097 }
2098 }
2099
2100 if include_vanilla {
2101
2102 if self.vanilla_files.contains_key(file_path) || self.vanilla_loose_files.contains_key(file_path) {
2103 return true
2104 } else if case_insensitive {
2105 let lower = file_path.to_lowercase();
2106 if self.vanilla_paths.contains_key(&lower) || self.vanilla_loose_paths.contains_key(&lower) {
2107 return true
2108 }
2109 }
2110 }
2111
2112 false
2113 }
2114
2115 pub fn folder_exists(&self, folder_path: &str, include_vanilla: bool, include_parent: bool, case_insensitive: bool) -> bool {
2117 if include_parent && (
2118 self.parent_folders.contains(folder_path) ||
2119 (case_insensitive && self.parent_folders.par_iter().any(|path| caseless::canonical_caseless_match_str(path, folder_path)))
2120 ) {
2121 return true
2122 }
2123
2124 if include_vanilla && (
2125 (self.vanilla_folders.contains(folder_path) || self.vanilla_loose_folders.contains(folder_path)) ||
2126 (case_insensitive && self.vanilla_folders.par_iter().chain(self.vanilla_loose_folders.par_iter()).any(|path| caseless::canonical_caseless_match_str(path, folder_path)))
2127 ) {
2128 return true
2129 }
2130
2131 false
2132 }
2133
2134 pub fn are_dependencies_generated(file_path: &Path) -> bool {
2136 file_path.is_file()
2137 }
2138
2139 pub fn is_vanilla_data_loaded(&self, include_asskit: bool) -> bool {
2141 if include_asskit {
2142 !self.vanilla_files.is_empty() && self.is_asskit_data_loaded()
2143 } else {
2144 !self.vanilla_files.is_empty()
2145 }
2146 }
2147
2148 pub fn is_asskit_data_loaded(&self) -> bool {
2150 !self.asskit_only_db_tables.is_empty()
2151 }
2152
2153 pub fn is_db_outdated(&self, rfile: &RFileDecoded) -> bool {
2155 if let RFileDecoded::DB(data) = rfile {
2156 let dep_db_undecoded = if let Ok(undecoded) = self.db_data(data.table_name(), true, false) { undecoded } else { return false };
2157 let dep_db_decoded = dep_db_undecoded.iter().filter_map(|x| if let Ok(RFileDecoded::DB(decoded)) = x.decoded() { Some(decoded) } else { None }).collect::<Vec<_>>();
2158
2159 if let Some(vanilla_db) = dep_db_decoded.iter().max_by(|x, y| x.definition().version().cmp(y.definition().version())) {
2160 if vanilla_db.definition().version() > data.definition().version() {
2161 return true;
2162 }
2163 }
2164 }
2165
2166 false
2167 }
2168
2169 pub fn db_version(&self, table_name: &str) -> Option<i32> {
2171 let tables = self.vanilla_tables.get(table_name)?;
2172 for table_path in tables {
2173
2174 let table = self.vanilla_files.get(table_path)?;
2175 if let RFileDecoded::DB(table) = table.decoded().ok()? {
2176 return Some(*table.definition().version());
2177 }
2178
2179 let table = self.vanilla_loose_files.get(table_path)?;
2180 if let RFileDecoded::DB(table) = table.decoded().ok()? {
2181 return Some(*table.definition().version());
2182 }
2183 }
2184
2185 None
2186 }
2187
2188 pub fn db_values_from_table_name_and_column_name(&self, packs: Option<&BTreeMap<String, Pack>>, table_name: &str, column_name: &str, include_vanilla: bool, include_parent: bool) -> HashSet<String> {
2190 let mut values = HashSet::new();
2191
2192 if let Ok(files) = self.db_data(table_name, include_vanilla, include_parent) {
2193 values.extend(files.par_iter().filter_map(|file| {
2194 if let Ok(RFileDecoded::DB(table)) = file.decoded() {
2195 table.definition().column_position_by_name(column_name).map(|column| table.data().par_iter().map(|row| row[column].data_to_string().to_string()).collect::<Vec<_>>())
2196 } else { None }
2197 }).flatten().collect::<Vec<_>>());
2198 }
2199
2200 if let Some(packs) = packs {
2201 for pack in packs.values() {
2202 let files = pack.files_by_paths(&ContainerPath::db_table_folders(table_name), true);
2203 values.extend(files.par_iter().filter_map(|file| {
2204 if let Ok(RFileDecoded::DB(table)) = file.decoded() {
2205 table.definition().column_position_by_name(column_name).map(|column| table.data().par_iter().map(|row| row[column].data_to_string().to_string()).collect::<Vec<_>>())
2206 } else { None }
2207 }).flatten().collect::<Vec<_>>());
2208 }
2209 }
2210
2211 values
2212 }
2213
2214 pub fn db_key_values(&self, packs: Option<&BTreeMap<String, Pack>>, table_name: &str) -> Option<(String, HashSet<String>)> {
2225 let key_column_name = |file: &RFile| match file.decoded() {
2226 Ok(RFileDecoded::DB(table)) => {
2227 let definition = table.definition();
2228 let fields = definition.fields_processed();
2229 definition.key_column_positions().first().and_then(|position| fields.get(*position)).map(|field| field.name().to_owned())
2230 }
2231 _ => None,
2232 };
2233
2234 let column = self.db_data(table_name, true, true).ok()
2235 .and_then(|files| files.into_iter().find_map(key_column_name))
2236 .or_else(|| packs.into_iter()
2237 .flat_map(|packs| packs.values())
2238 .flat_map(|pack| pack.files_by_paths(&ContainerPath::db_table_folders(table_name), true))
2239 .find_map(key_column_name))?;
2240
2241 let keys = self.db_values_from_table_name_and_column_name(packs, table_name, &column, true, true);
2242 Some((column, keys))
2243 }
2244
2245 pub fn db_values_from_table_name_and_column_name_for_value(&self, packs: Option<&BTreeMap<String, Pack>>, table_name: &str, key_column_name: &str, desired_column_name: &str, include_vanilla: bool, include_parent: bool) -> HashMap<String, String> {
2247 let mut values = HashMap::new();
2248
2249 if let Ok(files) = self.db_data(table_name, include_vanilla, include_parent) {
2250 values.extend(files.par_iter().filter_map(|file| {
2251 if let Ok(RFileDecoded::DB(table)) = file.decoded() {
2252 if let Some(column) = table.definition().column_position_by_name(key_column_name) {
2253 table.definition().column_position_by_name(desired_column_name).map(|desired_column| table.data().par_iter().map(|row| (row[column].data_to_string().to_string(), row[desired_column].data_to_string().to_string())).collect::<Vec<_>>())
2254 } else { None }
2255 } else { None }
2256 }).flatten().collect::<Vec<_>>());
2257 }
2258
2259 if let Some(packs) = packs {
2260 for pack in packs.values() {
2261 let files = pack.files_by_paths(&ContainerPath::db_table_folders(table_name), true);
2262 values.extend(files.par_iter().filter_map(|file| {
2263 if let Ok(RFileDecoded::DB(table)) = file.decoded() {
2264 if let Some(column) = table.definition().column_position_by_name(key_column_name) {
2265 table.definition().column_position_by_name(desired_column_name).map(|desired_column| table.data().par_iter().map(|row| (row[column].data_to_string().to_string(), row[desired_column].data_to_string().to_string())).collect::<Vec<_>>())
2266 } else { None }
2267 } else { None }
2268 }).flatten().collect::<Vec<_>>());
2269 }
2270 }
2271
2272 values
2273 }
2274
2275 pub fn update_db(&mut self, rfile: &mut RFileDecoded) -> Result<(i32, i32, Vec<String>, Vec<String>)> {
2279 match rfile {
2280 RFileDecoded::DB(data) => {
2281 let dep_db_undecoded = self.db_data(data.table_name(), true, false)?;
2282 let dep_db_decoded = dep_db_undecoded.iter().filter_map(|x| if let Ok(RFileDecoded::DB(decoded)) = x.decoded() { Some(decoded) } else { None }).collect::<Vec<_>>();
2283
2284 if let Some(vanilla_db) = dep_db_decoded.iter().max_by(|x, y| x.definition().version().cmp(y.definition().version())) {
2285
2286 let definition_new = vanilla_db.definition();
2287 let definition_old = data.definition().clone();
2288 if definition_old != *definition_new {
2289 data.set_definition(definition_new);
2290
2291 let fields_old = definition_old.fields_processed();
2293 let fields_new = definition_new.fields_processed();
2294 let fields_deleted = fields_old.iter()
2295 .filter(|x| fields_new.iter().all(|y| y.name() != x.name()))
2296 .map(|x| x.name().to_owned())
2297 .collect::<Vec<_>>();
2298 let fields_added = fields_new.iter()
2299 .filter(|x| fields_old.iter().all(|y| y.name() != x.name()))
2300 .map(|x| x.name().to_owned())
2301 .collect::<Vec<_>>();
2302
2303 Ok((*definition_old.version(), *definition_new.version(), fields_deleted, fields_added))
2304 }
2305 else {
2306 Err(RLibError::NoDefinitionUpdateAvailable)
2307 }
2308 }
2309 else { Err(RLibError::NoTableInGameFilesToCompare) }
2310 }
2311 _ => Err(RLibError::DecodingDBNotADBTable),
2312 }
2313 }
2314
2315 pub fn generate_missing_loc_data(&self, packs: &mut BTreeMap<String, Pack>) -> Result<Vec<ContainerPath>> {
2317 let loc_data = self.loc_data(true, true)?;
2318 let mut existing_locs = HashMap::new();
2319
2320 for loc in &loc_data {
2321 if let Ok(RFileDecoded::Loc(ref data)) = loc.decoded() {
2322 existing_locs.extend(data.table().data().iter().map(|x| (x[0].data_to_string().to_string(), x[1].data_to_string().to_string())));
2323 }
2324 }
2325
2326 let mut all_paths = vec![];
2327 for pack in packs.values_mut() {
2328 all_paths.extend(pack.generate_missing_loc_data(&existing_locs)?);
2329 }
2330 Ok(all_paths)
2331 }
2332
2333 pub fn bruteforce_loc_key_order(&self, schema: &mut Schema, locs: Option<HashMap<String, Vec<String>>>, local_packs: Option<&BTreeMap<String, Pack>>, mut ak_files: Option<&mut HashMap<String, DB>>) -> Result<()> {
2335 let mut fields_still_not_found = vec![];
2336
2337 let loc_files = self.loc_data(true, false)?;
2339 let loc_table = loc_files.iter()
2340 .filter_map(|file| if let Ok(RFileDecoded::Loc(loc)) = file.decoded() { Some(loc) } else { None })
2341 .flat_map(|file| file.data().to_vec())
2342 .map(|entry| (entry[0].data_to_string().to_string(), entry[1].data_to_string().to_string()))
2343 .collect::<HashMap<_,_>>();
2344
2345 let ak_tables = match ak_files {
2346 Some(ref tables) => (**tables).clone(),
2347 None => HashMap::new(),
2348 };
2349
2350 let local_files: Vec<_> = match local_packs {
2352 Some(packs) => packs.values()
2353 .flat_map(|pack| pack.files_by_type(&[FileType::DB]))
2354 .filter_map(|x| match x.decoded() {
2355 Ok(RFileDecoded::DB(db)) => Some(db),
2356 _ => None,
2357 })
2358 .collect(),
2359 None => Vec::new(),
2360 };
2361
2362 let mut db_tables = if ak_files.is_some() {
2364 ak_tables.values().collect::<Vec<_>>()
2365 } else {
2366 self.db_and_loc_data(true, false, true, false)?
2367 .iter()
2368 .filter_map(|file| if let Ok(RFileDecoded::DB(table)) = file.decoded() { Some(table) } else { None })
2369 .collect::<Vec<_>>()
2370 };
2371
2372 db_tables.extend_from_slice(&local_files);
2373
2374 let mut db_tables_dedup: Vec<DB> = vec![];
2376 for table in &db_tables {
2377 match db_tables_dedup.iter_mut().find(|x| x.table_name() == table.table_name() && x.definition().version() == table.definition().version()) {
2378 Some(db_source) => *db_source = DB::merge(&[db_source, table])?,
2379 None => db_tables_dedup.push((*table).clone()),
2380 }
2381 }
2382
2383 for table in &db_tables_dedup {
2384 let definition = table.definition();
2385 let mut loc_fields = definition.localised_fields().to_vec();
2386
2387 let mut loc_fields_final = loc_fields.to_vec();
2390
2391 if let Some(ref loc_fields_info) = locs {
2393 loc_fields.clear();
2394
2395 if let Some(loc_names) = loc_fields_info.get(&table.table_name_without_tables()) {
2396 for name in loc_names {
2397 if loc_fields.iter().all(|x| x.name() != name) {
2398
2399 let mut field = Field::default();
2400 field.set_name(name.to_string());
2401 field.set_field_type(FieldType::StringU8);
2402
2403 loc_fields.push(field);
2404 }
2405 }
2406 }
2407 }
2408
2409 let fields = definition.fields_processed();
2411 let raw_key_names = definition.fields().iter().filter(|field| field.is_key(None)).map(|field| field.name()).collect::<HashSet<_>>();
2412 let key_fields = fields.iter()
2413 .enumerate()
2414 .filter(|(_, field)| raw_key_names.contains(field.name()))
2415 .collect::<Vec<_>>();
2416
2417 let short_table_name = table.table_name_without_tables();
2419 for localised_field in &loc_fields {
2420 let localised_key = format!("{}_{}_", short_table_name, localised_field.name());
2421
2422 if loc_table.keys().any(|x| x.starts_with(&localised_key)) && loc_fields_final.iter().all(|x| x.name() != localised_field.name()) {
2424 loc_fields_final.push(localised_field.clone());
2425 }
2426 }
2427
2428 for table_field in fields {
2431 if loc_fields_final.iter().all(|x| !x.name().starts_with(table_field.name())) {
2432 let localised_key = format!("{}_{}_", short_table_name, table_field.name());
2433 if loc_table.keys().any(|x| x.starts_with(&localised_key)) && loc_fields_final.iter().all(|x| x.name() != table_field.name()) {
2434
2435 let mut loc_field = definition.fields().iter().find(|field| field.name() == table_field.name()).cloned().unwrap_or_else(|| table_field.clone());
2437 loc_field.set_field_type(table_field.field_type().clone());
2438 loc_fields_final.push(loc_field);
2439 }
2440 }
2441 }
2442
2443 for loc_field in &loc_fields {
2444 if loc_fields_final.iter().all(|x| x.name() != loc_field.name()) {
2445 fields_still_not_found.push(format!("{}/{}", table.table_name_without_tables(), loc_field.name()));
2446 }
2447 }
2448
2449 if let Some(ak_files) = &mut ak_files {
2451 let ak_table = ak_files.get_mut(table.table_name()).unwrap();
2452 let mut definition = ak_table.definition().clone();
2453 definition.set_localised_fields(loc_fields_final.to_vec());
2454 ak_table.set_definition(&definition);
2455
2456 } else if let Some(schema_definition) = schema.definition_by_name_and_version_mut(table.table_name(), *definition.version()) {
2457 schema_definition.set_localised_fields(loc_fields_final.to_vec());
2458 }
2459
2460 if !loc_fields_final.is_empty() {
2462
2463 let order = if key_fields.len() == 1 {
2465 vec![key_fields[0].0 as u32]
2466 }
2467
2468 else {
2470 let mut order = Vec::with_capacity(key_fields.len());
2471 let combos = key_fields.iter().permutations(key_fields.len());
2472 let table_data = table.data();
2473 for combo in combos {
2474
2475 let mut combo_is_valid = true;
2478 for row in table_data.iter() {
2479 let mut combined_key = String::new();
2487 for (index, _) in &combo {
2488 combined_key.push_str(&row[*index].data_to_string());
2489 }
2490
2491 for localised_field in &loc_fields_final {
2492 let localised_key = format!("{}_{}_{}", short_table_name, localised_field.name(), combined_key);
2493 match loc_table.get(&localised_key) {
2494 Some(_) => {
2495 if order.is_empty() {
2496 order = combo.iter().map(|(index, _)| *index as u32).collect();
2497 }
2498 }
2499 None => {
2500 combo_is_valid = false;
2501 break;
2502 }
2503 }
2504 }
2505
2506 if !combo_is_valid {
2508 break;
2509 }
2510 }
2511
2512 if !combo_is_valid {
2514 order = vec![];
2515 continue;
2516 }
2517
2518 if !order.is_empty() {
2519 break;
2520 }
2521 }
2522
2523 order
2524 };
2525
2526 if !order.is_empty() && !loc_fields_final.is_empty() {
2527 info!("Bruteforce: loc key order found for table {}, version {}.", table.table_name(), definition.version());
2528 if let Some(ak_files) = &mut ak_files {
2529 let ak_table = ak_files.get_mut(table.table_name()).unwrap();
2530 let mut definition = ak_table.definition().clone();
2531 definition.set_localised_key_order(order);
2532 ak_table.set_definition(&definition);
2533 } else if let Some(schema_definition) = schema.definition_by_name_and_version_mut(table.table_name(), *definition.version()) {
2534 schema_definition.set_localised_key_order(order);
2535 }
2536 } else {
2537 info!("Bruteforce: loc key order found (but may be incorrect) for table {}, version {}.", table.table_name(), definition.version());
2538
2539 if loc_fields_final.is_empty() {
2541 if let Some(ak_files) = &mut ak_files {
2542 let ak_table = ak_files.get_mut(table.table_name()).unwrap();
2543 let mut definition = ak_table.definition().clone();
2544 definition.set_localised_key_order(vec![]);
2545 ak_table.set_definition(&definition);
2546 } else if let Some(schema_definition) = schema.definition_by_name_and_version_mut(table.table_name(), *definition.version()) {
2547 schema_definition.set_localised_key_order(vec![]);
2548 }
2549 }
2550 }
2551 }
2552
2553 else if let Some(ak_files) = &mut ak_files {
2555 let ak_table = ak_files.get_mut(table.table_name()).unwrap();
2556 let mut definition = ak_table.definition().clone();
2557 definition.set_localised_key_order(vec![]);
2558 ak_table.set_definition(&definition);
2559 } else if let Some(schema_definition) = schema.definition_by_name_and_version_mut(table.table_name(), *definition.version()) {
2560 schema_definition.set_localised_key_order(vec![]);
2561 }
2562 }
2563
2564 fields_still_not_found.sort();
2566 fields_still_not_found.dedup();
2567 info!("Bruteforce: fields still not found :{fields_still_not_found:#?}");
2568
2569 if ak_files.is_none() {
2572 for key in loc_table.keys().sorted() {
2573 if self.loc_key_source(key).is_none() {
2574 info!("-- Bruteforce: cannot find source for loc key {key}.");
2575 }
2576 }
2577 }
2578
2579 Ok(())
2580 }
2581
2582 #[allow(clippy::if_same_then_else)]
2584 pub fn generate_automatic_patches(&self, schema: &mut Schema, packs: &BTreeMap<String, Pack>) -> Result<()> {
2585 let mut db_tables = self.db_and_loc_data(true, false, true, false)?
2586 .iter()
2587 .filter_map(|file| if let Ok(RFileDecoded::DB(table)) = file.decoded() { Some(table) } else { None })
2588 .collect::<Vec<_>>();
2589
2590 for pack in packs.values() {
2591 db_tables.extend_from_slice(&pack.files_by_type(&[FileType::DB])
2592 .iter()
2593 .filter_map(|x| if let Ok(RFileDecoded::DB(db)) = x.decoded() {
2594 Some(db)
2595 } else {
2596 None
2597 })
2598 .collect::<Vec<_>>()
2599 );
2600 }
2601
2602 let current_patches = schema.patches_mut();
2603 let mut new_patches: HashMap<String, DefinitionPatch> = HashMap::new();
2604
2605 let image_paths = self.vanilla_files()
2607 .keys()
2608 .filter(|x| x.ends_with(".png") || x.ends_with(".tga"))
2609 .collect::<Vec<_>>();
2610
2611 let video_paths = self.vanilla_files()
2612 .keys()
2613 .filter(|x| x.ends_with(".ca_vp8"))
2614 .collect::<Vec<_>>();
2615
2616 for table in &db_tables {
2617 let definition = table.definition();
2618 let fields = definition.fields_processed();
2619 for (column, field) in fields.iter().enumerate() {
2620 match field.field_type() {
2621 FieldType::StringU8 |
2622 FieldType::StringU16 |
2623 FieldType::OptionalStringU8 |
2624 FieldType::OptionalStringU16 => {
2625
2626 let raw_field = definition.fields().iter().find(|raw_field| raw_field.name() == field.name()).unwrap_or(field);
2628
2629 let mut possible_icon = false;
2635 let low_name = field.name().to_lowercase();
2636 if (low_name.contains("icon") || low_name.contains("image")) &&
2637
2638 !(table.table_name() == "building_sets_tables" && field.name() == "icon") &&
2640
2641 !(table.table_name() == "character_traits_tables" && field.name() == "icon") {
2643 possible_icon = true;
2644 }
2645
2646 let mut possible_relative_paths = table.data().par_iter()
2648 .filter_map(|row| {
2649
2650 if !raw_field.is_filename(None) || (
2652 raw_field.is_filename(None) && (
2653 raw_field.filename_relative_path(None).is_none() ||
2654 raw_field.filename_relative_path(None).unwrap().is_empty()
2655 )
2656 ) || (
2657
2658 (table.table_name() == "advisors_tables" && field.name() == "advisor_icon_path") ||
2660
2661 (table.table_name() == "campaign_post_battle_captive_options_tables" && field.name() == "icon_path") ||
2663
2664 (table.table_name() == "narrative_viewer_tabs_tables" && field.name() == "image_path") ||
2666
2667 (table.table_name() == "technology_ui_groups_tables" && field.name() == "optional_background_image")
2669 ) {
2670
2671 let mut data = row[column].data_to_string().to_lowercase().replace("\\", "/");
2676
2677 if data.starts_with("/") {
2679 if data.len() > 1 {
2680 data = data[1..].to_owned();
2681 } else {
2682 data = String::new();
2683 }
2684 }
2685
2686 if !data.is_empty() && !data.ends_with("/") &&
2687 data != "." &&
2688 data != "x" &&
2689 data != "false" &&
2690 data != "building_placeholder" &&
2691 data != "placehoder.png" &&
2692 data != "placeholder" &&
2693 data != "placeholder.tga" &&
2694 data != "placeholder.png" && (
2695 possible_icon ||
2696 data.ends_with(".png") || data.ends_with(".tga")
2697 ) {
2698
2699 let possible_paths = image_paths.iter()
2700
2701 .filter(|x| {
2703 if table.table_name() == "aide_de_camp_speeches_tables" && field.name() == "icon_name" {
2704 x.starts_with("ui/battle ui/adc_icons/")
2705 } else if table.table_name() == "agent_string_subculture_overrides_tables" && field.name() == "icon_path" {
2706 x.starts_with("ui/campaign ui/agents/icons/")
2707 } else if table.table_name() == "ancillary_types_tables" && field.name() == "ui_icon" {
2708 x.starts_with("ui/portraits/ancillaries/")
2709 } else if table.table_name() == "battlefield_building_categories_tables" && field.name() == "icon_path" {
2710 x.starts_with("ui/battle ui/building icons/")
2711 } else if table.table_name() == "bonus_value_uis_tables" && field.name() == "icon" {
2712 x.starts_with("ui/campaign ui/effect_bundles/")
2713 } else if table.table_name() == "building_culture_variants_tables" && field.name() == "icon" {
2714 x.starts_with("ui/buildings/icons/")
2715 } else if table.table_name() == "campaign_payload_ui_details_tables" && field.name() == "icon" {
2716 x.starts_with("ui/campaign ui/effect_bundles/")
2717 } else if table.table_name() == "campaign_post_battle_captive_options_tables" && field.name() == "icon_path" {
2718 x.starts_with("ui/campaign ui/captive_option_icons/")
2719 } else if table.table_name() == "capture_point_types_tables" && field.name() == "icon_name" {
2720 x.starts_with("ui/battle ui/capture_point_icons/")
2721 } else if table.table_name() == "character_skills_tables" && field.name() == "image_path" {
2722 x.starts_with("ui/campaign ui/skills/")
2723 } else if table.table_name() == "character_traits_tables" && field.name() == "icon_custom" {
2724 x.starts_with("ui/campaign ui/effect_bundles/")
2725
2726 } else if table.table_name() == "cursors_tables" && field.name() == "image" {
2728 !x.starts_with(&(data.to_owned() + "_"))
2729 } else if table.table_name() == "dilemmas_tables" && field.name() == "ui_image" {
2730 x.starts_with("ui/eventpics/")
2731 } else if table.table_name() == "effect_bundles_tables" && field.name() == "ui_icon" {
2732 x.starts_with("ui/campaign ui/effect_bundles/")
2733 } else if table.table_name() == "effects_tables" && (field.name() == "icon" || field.name() == "icon_negative") {
2734 x.starts_with("ui/campaign ui/effect_bundles/")
2735 } else if table.table_name() == "faction_groups_tables" && field.name() == "ui_icon" {
2736 x.starts_with("ui/campaign ui/effect_bundles/")
2737 } else if table.table_name() == "incidents_tables" && field.name() == "ui_image" {
2738 x.starts_with("ui/eventpics/")
2739 } else if table.table_name() == "message_event_strings_tables" && field.name() == "image" {
2740 x.starts_with("ui/eventpics/")
2741 } else if table.table_name() == "missions_tables" && field.name() == "ui_icon" {
2742 x.starts_with("ui/campaign ui/message_icons/")
2743
2744 } else if table.table_name() == "missions_tables" && field.name() == "ui_image" {
2746 x.starts_with("ui/eventpics/") && x.ends_with(&(data.to_owned() + ".png"))
2747 } else if table.table_name() == "pooled_resources_tables" && field.name() == "optional_icon_path" {
2748 x.starts_with("ui/skins/")
2749 } else if table.table_name() == "projectile_shot_type_enum_tables" && field.name() == "icon_name" {
2750 x.starts_with("ui/battle ui/ability_icons/")
2751 } else if table.table_name() == "religions_tables" && field.name() == "ui_icon_path" {
2752 x.starts_with("ui/campaign ui/religion_icons/")
2753 } else if table.table_name() == "special_ability_phases_tables" && field.name() == "ticker_icon" {
2754 x.starts_with("ui/battle ui/ability_icons/")
2755 } else if table.table_name() == "technologies_tables" && field.name() == "icon_name" {
2756 x.starts_with("ui/campaign ui/technologies/")
2757 } else if table.table_name() == "technologies_tables" && field.name() == "info_pic" {
2758 x.starts_with("ui/eventpics/")
2759 } else if table.table_name() == "trait_categories_tables" && field.name() == "icon_path" {
2760 x.starts_with("ui/campaign ui/effect_bundles/")
2761 } else if table.table_name() == "ui_unit_groupings_tables" && field.name() == "icon" {
2762 x.starts_with("ui/common ui/unit_category_icons/")
2763 } else if table.table_name() == "victory_types_tables" && field.name() == "icon" {
2764 x.starts_with("ui/campaign ui/victory_type_icons/")
2765
2766 } else if table.table_name() == "videos_tables" && field.name() == "video_name" {
2768 x.starts_with("movies/")
2769 } else {
2770 true
2771 }
2772 })
2773
2774 .filter(|x| if !data.ends_with('_') {
2779 if !data.contains("/") {
2780 if !data.contains('.') {
2781 x.contains(&("/".to_owned() + &data + "."))
2782 } else {
2783 x.contains(&("/".to_owned() + &data))
2784 }
2785 } else {
2786 x.contains(&data)
2787 }
2788 } else {
2789 false
2790 })
2791
2792 .filter_map(|x| x.rfind(&data).map(|pos| (x, pos)))
2794 .map(|(x, pos)| x[..pos].to_owned() + &x[pos..].replacen(&data, "%", 1))
2795 .collect::<Vec<_>>();
2796
2797
2798 if !possible_paths.is_empty() {
2799 return Some(possible_paths)
2800 }
2801 }
2802 }
2803
2804 None
2805 })
2806 .flatten()
2807 .collect::<HashSet<String>>();
2808
2809 let mut possible_video = false;
2815 if low_name.contains("video") {
2816 possible_video = true;
2817 }
2818
2819 possible_relative_paths.extend(
2820 table.data().par_iter().filter_map(|row| {
2821
2822 if !raw_field.is_filename(None) || (
2824 raw_field.is_filename(None) && (
2825 raw_field.filename_relative_path(None).is_none() ||
2826 raw_field.filename_relative_path(None).unwrap().is_empty()
2827 )
2828 ) || (
2829
2830 table.table_name() == "videos_tables" && field.name() == "video_name"
2832 ) {
2833
2834 let mut data = row[column].data_to_string().to_lowercase().replace("\\", "/");
2835
2836 if data.starts_with("/") {
2838 if data.len() > 1 {
2839 data = data[1..].to_owned();
2840 } else {
2841 data = String::new();
2842 }
2843 }
2844
2845 if !data.is_empty() && (
2846 possible_video ||
2847 data.ends_with(".ca_vp8")
2848 ) {
2849
2850 let possible_paths = video_paths.iter()
2851 .filter(|x| {
2852 if table.table_name() == "videos_tables" && field.name() == "video_name" {
2853 x.starts_with("movies/")
2854 } else {
2855 true
2856 }
2857 })
2858 .filter(|x| if !data.contains('.') {
2862 x.contains(&("/".to_owned() + &data + "."))
2863 } else {
2864 x.contains(&("/".to_owned() + &data))
2865 })
2866
2867 .filter_map(|x| x.rfind(&data).map(|pos| (x, pos)))
2869 .map(|(x, pos)| x[..pos].to_owned() + &x[pos..].replacen(&data, "%", 1))
2870 .collect::<Vec<_>>();
2871
2872
2873 if !possible_paths.is_empty() {
2874 return Some(possible_paths)
2875 }
2876 }
2877 }
2878
2879 None
2880 })
2881 .flatten()
2882 .collect::<HashSet<String>>()
2883 );
2884
2885 if !possible_relative_paths.is_empty() && (possible_relative_paths.len() > 1 || (possible_relative_paths.len() == 1 && possible_relative_paths.iter().collect::<Vec<_>>()[0] != "%")) {
2887 info!("Checking table {}, field {} ...", table.table_name(), field.name());
2888 dbg!(&possible_relative_paths);
2889 }
2890
2891 if (table.table_name() == "models_building_tables" && field.name() == "logic_file") ||
2895 (table.table_name() == "models_sieges_tables" && (field.name() == "model_file" || field.name() == "logic_file" || field.name() == "collision_file")) ||
2896 (table.table_name() == "models_deployables_tables" && (field.name() == "model_file" || field.name() == "logic_file" || field.name() == "collision_file")) {
2897 possible_relative_paths.clear();
2898 possible_relative_paths.insert("%".to_owned());
2899 }
2900
2901 if (table.table_name() == "ui_mercenary_recruitment_infos_tables" && field.name() == "hire_button_icon_path") ||
2903 (table.table_name() == "battles_tables" && (field.name() == "specification" || field.name() == "battle_environment_audio")) ||
2904 (table.table_name() == "factions_tables" && field.name() == "key") ||
2905 (table.table_name() == "frontend_faction_leaders_tables" && field.name() == "key") {
2906 let mut patch = HashMap::new();
2907 patch.insert("is_filename".to_owned(), "false".to_owned());
2908
2909 match new_patches.get_mut(table.table_name()) {
2910 Some(patches) => match patches.get_mut(field.name()) {
2911 Some(patches) => patches.extend(patch),
2912 None => { patches.insert(field.name().to_owned(), patch); }
2913 },
2914 None => {
2915 let mut table_patch = HashMap::new();
2916 table_patch.insert(field.name().to_owned(), patch);
2917 new_patches.insert(table.table_name().to_string(), table_patch);
2918 }
2919 }
2920 }
2921
2922 if !possible_relative_paths.is_empty() {
2924 let mut possible_relative_paths = possible_relative_paths.iter().collect::<Vec<_>>();
2925 possible_relative_paths.sort();
2926
2927 let mut patch = HashMap::new();
2928 if !raw_field.is_filename(None) {
2929 patch.insert("is_filename".to_owned(), "true".to_owned());
2930 }
2931
2932 if possible_relative_paths.len() > 1 || (
2934 (
2935 possible_relative_paths.len() == 1 &&
2936 possible_relative_paths[0].contains('%') &&
2937 possible_relative_paths[0] != "%"
2938 ) || (
2939 possible_relative_paths[0] == "%" &&
2940 raw_field.filename_relative_path(None).is_some() &&
2941 !raw_field.filename_relative_path(None).unwrap().is_empty()
2942 )
2943 ) {
2944 patch.insert("filename_relative_path".to_owned(), possible_relative_paths.into_iter().join(";"));
2945 }
2946
2947 if !patch.is_empty() {
2949 match new_patches.get_mut(table.table_name()) {
2950 Some(patches) => match patches.get_mut(field.name()) {
2951 Some(patches) => patches.extend(patch),
2952 None => { patches.insert(field.name().to_owned(), patch); }
2953 },
2954 None => {
2955 let mut table_patch = HashMap::new();
2956 table_patch.insert(field.name().to_owned(), patch);
2957 new_patches.insert(table.table_name().to_string(), table_patch);
2958 }
2959 }
2960 }
2961 }
2962 }
2980 FieldType::I64 |
2981 FieldType::OptionalI64 => {
2982 }
3000 _ => continue
3001 }
3002 }
3003 }
3004
3005 Schema::add_patches_to_patch_set(current_patches, &new_patches);
3006
3007 Ok(())
3008 }
3009
3010 #[allow(clippy::too_many_arguments)]
3014 pub fn add_tile_maps_and_tiles(&mut self, packs: &mut BTreeMap<String, Pack>, pack_key: Option<&str>, game: &GameInfo, schema: &Schema, options: OptimizerOptions, tile_maps: Vec<PathBuf>, tiles: Vec<(PathBuf, String)>) -> Result<(Vec<ContainerPath>, Vec<ContainerPath>)> {
3015 let mut added_paths = vec![];
3016
3017 let pack = match pack_key {
3019 Some(key) => packs.get_mut(key).ok_or_else(|| RLibError::NoPacksProvided)?,
3020 None => packs.values_mut().next().ok_or_else(|| RLibError::NoPacksProvided)?,
3021 };
3022
3023 for tile_map in &tile_maps {
3025 added_paths.append(&mut pack.insert_folder(tile_map, "terrain/battles", &None, &None, true)?);
3026 }
3027
3028 for (tile, subpath) in &tiles {
3030
3031 let (internal_path, needs_tile_database) = if subpath.is_empty() {
3032 ("terrain/tiles/battle".to_owned(), false)
3033 } else {
3034 (format!("terrain/tiles/battle/{}", subpath.replace('\\', "/")), true)
3035 };
3036 added_paths.append(&mut pack.insert_folder(tile, &internal_path, &None, &None, true)?);
3037
3038 if needs_tile_database {
3040
3041 let subpath_len = subpath.replace('\\', "/").split('/').count();
3043 let mut tile_database = tile.to_path_buf();
3044
3045 (0..=subpath_len).for_each(|_| {
3046 tile_database.pop();
3047 });
3048
3049 let file_name = format!("{}_{}.bin", subpath.replace('/', "_"), tile.file_name().unwrap().to_string_lossy());
3050 tile_database.push(format!("_tile_database/TILES/{file_name}"));
3051 let tile_database_path = format!("terrain/tiles/battle/_tile_database/TILES/{file_name}");
3052
3053 added_paths.push(pack.insert_file(&tile_database, &tile_database_path, &None)?.unwrap());
3054 }
3055 }
3056
3057 let (paths_to_delete, paths_to_add) = pack.optimize(Some(added_paths.clone()), self, schema, game, &options)?;
3058
3059 let paths_to_delete = paths_to_delete.iter()
3060 .map(|path| ContainerPath::File(path.to_string()))
3061 .collect::<Vec<_>>();
3062
3063 added_paths.extend(paths_to_add.into_iter()
3064 .map(|path| ContainerPath::File(path.to_string()))
3065 .collect::<Vec<_>>());
3066
3067 Ok((added_paths, paths_to_delete))
3068 }
3069
3070 #[allow(clippy::too_many_arguments)]
3074 pub fn build_starpos_pre(&self, packs: &mut BTreeMap<String, Pack>, pack_key: Option<&str>, game: &GameInfo, game_path: &Path, asskit_path: Option<PathBuf>, campaign_id: &str, process_hlp_spd_data: bool, sub_start_pos: &str) -> Result<()> {
3075
3076 let map_names = if process_hlp_spd_data {
3078 self.db_values_from_table_name_and_column_name_for_value(Some(packs), "campaigns_tables", "campaign_name", "map_name", true, true)
3079 } else {
3080 HashMap::new()
3081 };
3082
3083 let pack_file = match pack_key {
3085 Some(key) => packs.get_mut(key).ok_or_else(|| RLibError::NoPacksProvided)?,
3086 None => packs.values_mut().next().ok_or_else(|| RLibError::NoPacksProvided)?,
3087 };
3088 let pack_name = pack_file.disk_file_name();
3089 if pack_name.is_empty() {
3090 return Err(RLibError::BuildStartposError("The Pack needs to be saved to disk in order to build a startpos. Save it and try again.".to_owned()));
3091 }
3092
3093 if campaign_id.is_empty() {
3094 return Err(RLibError::BuildStartposError("campaign_id not provided.".to_owned()));
3095 }
3096
3097 let process_hlp_spd_data_string = if process_hlp_spd_data {
3098 String::from("process_campaign_ai_map_data;")
3099 } else {
3100 String::new()
3101 };
3102
3103 let extra_folders = "add_working_directory assembly_kit\\working_data;";
3106 let mut user_script_contents = if game.key() == KEY_ATTILA || game.key() == KEY_THRONES_OF_BRITANNIA { extra_folders.to_owned() } else { String::new() };
3107
3108 user_script_contents.push_str(&format!("
3109 mod {pack_name};
3110 process_campaign_startpos {campaign_id} {sub_start_pos};
3111 {process_hlp_spd_data_string}
3112 quit_after_campaign_processing;"
3113 ));
3114
3115 let game_data_path = game.data_path(game_path)?;
3117 if !game_path.is_dir() {
3118 return Err(RLibError::BuildStartposError("Game path incorrect. Fix it in the settings and try again.".to_owned()));
3119 }
3120
3121 if !PathBuf::from(pack_file.disk_file_path()).starts_with(&game_data_path) {
3122 return Err(RLibError::BuildStartposError("The Pack needs to be in /data. Install it there and try again.".to_owned()));
3123 }
3124
3125 if GAMES_NEEDING_VICTORY_OBJECTIVES.contains(&game.key()) {
3127 let mut game_campaign_path = game_data_path.to_path_buf();
3128 game_campaign_path.push(campaign_id);
3129 DirBuilder::new().recursive(true).create(&game_campaign_path)?;
3130
3131 game_campaign_path.push(VICTORY_OBJECTIVES_EXTRACTED_FILE_NAME);
3132 pack_file.extract(ContainerPath::File(VICTORY_OBJECTIVES_FILE_NAME.to_owned()), &game_campaign_path, false, &None, true, false, &None)?;
3133 }
3134
3135 let config_path = game.config_path(game_path).ok_or(RLibError::BuildStartposError("Error getting the game's config path.".to_owned()))?;
3136 let scripts_path = config_path.join("scripts");
3137 DirBuilder::new().recursive(true).create(&scripts_path)?;
3138
3139 if game.key() != KEY_ROME_2 {
3143
3144 let uspa = scripts_path.join(USER_SCRIPT_FILE_NAME);
3146 let uspb = scripts_path.join(USER_SCRIPT_FILE_NAME.to_owned() + ".bak");
3147
3148 if uspa.is_file() {
3149 std::fs::copy(&uspa, uspb)?;
3150 }
3151
3152 let mut file = BufWriter::new(File::create(uspa)?);
3153
3154 if *game.raw_db_version() < 2 {
3156 file.write_string_u16(&user_script_contents)?;
3157 } else {
3158 file.write_all(user_script_contents.as_bytes())?;
3159 }
3160
3161 file.flush()?;
3162 }
3163
3164 if game.key() != KEY_THRONES_OF_BRITANNIA &&
3169 game.key() != KEY_ATTILA &&
3170 game.key() != KEY_ROME_2 &&
3171 game.key() != KEY_SHOGUN_2_COMPLETE_EDITION &&
3172 game.key() != KEY_SHOGUN_2 {
3173
3174 let sub_start_pos_suffix = if sub_start_pos.is_empty() {
3175 String::new()
3176 } else {
3177 format!("_{sub_start_pos}")
3178 };
3179
3180 let starpos_path = game_data_path.join(format!("campaigns/{campaign_id}/startpos{sub_start_pos_suffix}.esf"));
3181 if starpos_path.is_file() {
3182 let starpos_path_bak = game_data_path.join(format!("campaigns/{campaign_id}/startpos{sub_start_pos_suffix}.esf.bak"));
3183 std::fs::copy(&starpos_path, starpos_path_bak)?;
3184 std::fs::remove_file(starpos_path)?;
3185 }
3186 }
3187
3188 else if game.key() == KEY_ROME_2 {
3191 match asskit_path {
3192 Some(ref asskit_path) => {
3193 let starpos_path = asskit_path.join(format!("working_data/campaigns/{campaign_id}/startpos.esf"));
3194 if starpos_path.is_file() {
3195 std::fs::remove_file(starpos_path)?;
3196 }
3197 },
3198 None => return Err(RLibError::BuildStartposError("Assembly Kit path not provided.".to_owned())),
3199 }
3200 }
3201
3202 if process_hlp_spd_data {
3204 if let Some(map_name) = map_names.get(campaign_id) {
3205 match game.key() {
3206
3207 KEY_PHARAOH_DYNASTIES |
3211 KEY_PHARAOH |
3212 KEY_WARHAMMER_3 |
3213 KEY_TROY |
3214 KEY_THREE_KINGDOMS |
3215 KEY_WARHAMMER_2 |
3216 KEY_WARHAMMER => {
3217 let hlp_folder_path = game_data_path.join(format!("campaign_maps/{map_name}"));
3218 if !hlp_folder_path.is_dir() {
3219 DirBuilder::new().recursive(true).create(&hlp_folder_path)?;
3220 }
3221
3222 let hlp_path = game_data_path.join(format!("campaign_maps/{map_name}/hlp_data.esf"));
3223 if hlp_path.is_file() {
3224 let hlp_path_bak = game_data_path.join(format!("campaign_maps/{map_name}/hlp_data.esf.bak"));
3225 std::fs::copy(&hlp_path, hlp_path_bak)?;
3226 std::fs::remove_file(hlp_path)?;
3227 }
3228 },
3229
3230 KEY_THRONES_OF_BRITANNIA |
3238 KEY_ATTILA => {
3239 let folder_path = config_path.join(format!("maps/campaign_maps/{map_name}"));
3240
3241 let (sender, receiver) = channel::<bool>();
3242 let join = thread::spawn(move || {
3243 loop {
3244 match receiver.try_recv() {
3245 Ok(stop) => if stop {
3246 break;
3247 }
3248 Err(_) => {
3249 if !folder_path.is_dir() {
3250 let _ = DirBuilder::new().recursive(true).create(&folder_path);
3251 }
3252
3253 thread::sleep(Duration::from_millis(100));
3254 }
3255 }
3256 }
3257 });
3258
3259 *START_POS_WORKAROUND_THREAD.write().unwrap() = Some(vec![(sender, join)]);
3260 },
3261
3262 KEY_ROME_2 => {
3267 let hlp_folder = game_data_path.join(format!("campaign_maps/{map_name}/"));
3268 if hlp_folder.is_dir() {
3269 let _ = DirBuilder::new().recursive(true).create(&hlp_folder);
3270 }
3271
3272 let hlp_path = hlp_folder.join("hlp_data.esf");
3273 if hlp_path.is_file() {
3274 let hlp_path_bak = game_data_path.join(format!("campaign_maps/{map_name}/hlp_data.esf.bak"));
3275 std::fs::copy(&hlp_path, hlp_path_bak)?;
3276 std::fs::remove_file(hlp_path)?;
3277 }
3278
3279 }
3280 KEY_SHOGUN_2_COMPLETE_EDITION => return Err(RLibError::BuildStartposError("Unsupported... yet. If you want to test support for this game, let me know.".to_owned())),
3281 KEY_SHOGUN_2 => return Err(RLibError::BuildStartposError("Unsupported... yet. If you want to test support for this game, let me know.".to_owned())),
3282 KEY_NAPOLEON => return Err(RLibError::BuildStartposError("Unsupported... yet. If you want to test support for this game, let me know.".to_owned())),
3283 KEY_EMPIRE => return Err(RLibError::BuildStartposError("Unsupported... yet. If you want to test support for this game, let me know.".to_owned())),
3284 _ => return Err(RLibError::BuildStartposError("How the fuck did you trigger this?".to_owned())),
3285 }
3286
3287 if game.key() != KEY_THRONES_OF_BRITANNIA &&
3289 game.key() != KEY_ATTILA &&
3290 game.key() != KEY_ROME_2 &&
3291 game.key() != KEY_SHOGUN_2_COMPLETE_EDITION &&
3292 game.key() != KEY_SHOGUN_2 &&
3293 game.key() != KEY_NAPOLEON &&
3294 game.key() != KEY_EMPIRE {
3295
3296 let spd_path = game_data_path.join(format!("campaign_maps/{map_name}/spd_data.esf"));
3297 if spd_path.is_file() {
3298 let spd_path_bak = game_data_path.join(format!("campaign_maps/{map_name}/spd_data.esf.bak"));
3299 std::fs::copy(&spd_path, spd_path_bak)?;
3300 std::fs::remove_file(spd_path)?;
3301 }
3302 }
3303 }
3304 }
3305
3306 if game.key() == KEY_THREE_KINGDOMS {
3308 let exe_path = game.executable_path(game_path).ok_or_else(|| RLibError::BuildStartposError("Game exe path not found.".to_owned()))?;
3309 let exe_name = exe_path.file_name().ok_or_else(|| RLibError::BuildStartposError("Game exe name not found.".to_owned()))?.to_string_lossy();
3310
3311 let mut command = Command::new("cmd");
3313 command.arg("/C");
3314 command.arg("start");
3315 command.arg("/wait");
3316 command.arg("/d");
3317 command.arg(game_path.to_string_lossy().replace('\\', "/"));
3318 command.arg(exe_name.to_string());
3319 command.arg("temp_file.txt;");
3320
3321 let _ = command.output()?;
3322
3323 let uspa = scripts_path.join(USER_SCRIPT_FILE_NAME);
3325 let uspb = scripts_path.join(USER_SCRIPT_FILE_NAME.to_owned() + ".bak");
3326 if uspb.is_file() {
3327 std::fs::copy(uspb, uspa)?;
3328 }
3329
3330 else if uspa.is_file() {
3332 std::fs::remove_file(uspa)?;
3333 }
3334
3335 } else if game.key() == KEY_ROME_2 {
3337 let exe_path = game.executable_path(game_path).ok_or_else(|| RLibError::BuildStartposError("Game exe path not found.".to_owned()))?;
3338 let exe_name = exe_path.file_name().ok_or_else(|| RLibError::BuildStartposError("Game exe name not found.".to_owned()))?.to_string_lossy();
3339
3340 let mut command = Command::new("cmd");
3342 command.arg("/C");
3343 command.arg("start");
3344 command.arg("/d");
3345 command.arg(game_path.to_string_lossy().replace('\\', "/"));
3346 command.arg(exe_name.to_string());
3347 command.arg("temp_file.txt;");
3348
3349 #[cfg(target_os = "windows")] {
3351 use std::os::windows::process::CommandExt;
3352
3353 command.raw_arg(extra_folders);
3355 command.raw_arg(user_script_contents.replace("\n", " "));
3356 }
3357
3358 command.spawn()?;
3359 } else {
3360 match game.game_launch_command(game_path) {
3361 Ok(command) => { let _ = open::that(command); },
3362 _ => return Err(RLibError::BuildStartposError("The currently selected game cannot be launched from Steam.".to_owned())),
3363 }
3364 }
3365
3366 Ok(())
3367 }
3368
3369 #[allow(clippy::too_many_arguments)]
3376 pub fn build_starpos_post(&self, packs: &mut BTreeMap<String, Pack>, pack_key: Option<&str>, game: &GameInfo, game_path: &Path, asskit_path: Option<PathBuf>,campaign_id: &str, process_hlp_spd_data: bool, cleanup_mode: bool, sub_start_pos: &[String]) -> Result<Vec<ContainerPath>> {
3377
3378 let map_names = if process_hlp_spd_data {
3380 self.db_values_from_table_name_and_column_name_for_value(Some(packs), "campaigns_tables", "campaign_name", "map_name", true, true)
3381 } else {
3382 HashMap::new()
3383 };
3384
3385 let pack_file = match pack_key {
3387 Some(key) => packs.get_mut(key).ok_or_else(|| RLibError::NoPacksProvided)?,
3388 None => packs.values_mut().next().ok_or_else(|| RLibError::NoPacksProvided)?,
3389 };
3390
3391 let mut startpos_failed = false;
3392 let mut sub_startpos_failed = vec![];
3393 let mut hlp_failed = false;
3394 let mut spd_failed = false;
3395
3396 if let Some(data) = START_POS_WORKAROUND_THREAD.write().unwrap().as_mut() {
3398 let (sender, handle) = data.remove(0);
3399 let _ = sender.send(true);
3400 let _ = handle.join();
3401 }
3402
3403 *START_POS_WORKAROUND_THREAD.write().unwrap() = None;
3404
3405 if !game_path.is_dir() {
3406 return Err(RLibError::BuildStartposError("Game path incorrect. Fix it in the settings and try again.".to_owned()));
3407 }
3408
3409 let game_data_path = game.data_path(game_path)?;
3410
3411 if GAMES_NEEDING_VICTORY_OBJECTIVES.contains(&game.key()) {
3413
3414 let mut game_campaign_path = game_data_path.to_path_buf();
3416 game_campaign_path.push(campaign_id);
3417 if game_campaign_path.is_dir() {
3418 let _ = std::fs::remove_dir_all(game_campaign_path);
3419 }
3420 }
3421
3422 let config_path = game.config_path(game_path).ok_or(RLibError::BuildStartposError("Error getting the game's config path.".to_owned()))?;
3423 let scripts_path = config_path.join("scripts");
3424 if !scripts_path.is_dir() {
3425 DirBuilder::new().recursive(true).create(&scripts_path)?;
3426 }
3427
3428 let uspa = scripts_path.join(USER_SCRIPT_FILE_NAME);
3430 let uspb = scripts_path.join(USER_SCRIPT_FILE_NAME.to_owned() + ".bak");
3431 if uspb.is_file() {
3432 std::fs::copy(uspb, uspa)?;
3433 }
3434
3435 else if uspa.is_file() {
3437 std::fs::remove_file(uspa)?;
3438 }
3439
3440 let mut added_paths = vec![];
3441
3442 let starpos_paths = match game.key() {
3444 KEY_PHARAOH_DYNASTIES |
3445 KEY_PHARAOH |
3446 KEY_WARHAMMER_3 |
3447 KEY_TROY |
3448 KEY_THREE_KINGDOMS |
3449 KEY_WARHAMMER_2 |
3450 KEY_WARHAMMER => {
3451 if sub_start_pos.is_empty() {
3452 vec![game_data_path.join(format!("campaigns/{campaign_id}/startpos.esf"))]
3453 } else {
3454 let mut paths = vec![];
3455 for sub in sub_start_pos {
3456 paths.push(game_data_path.join(format!("campaigns/{campaign_id}/startpos_{sub}.esf")));
3457
3458 }
3459 paths
3460 }
3461 }
3462 KEY_THRONES_OF_BRITANNIA |
3463 KEY_ATTILA => vec![config_path.join(format!("maps/campaigns/{campaign_id}/startpos.esf"))],
3464
3465 KEY_ROME_2 => {
3467 match asskit_path {
3468 Some(asskit_path) => {
3469 if !asskit_path.is_dir() {
3470 return Err(RLibError::BuildStartposError("Assembly Kit path is not a valid folder.".to_owned()));
3471 }
3472
3473 vec![asskit_path.join(format!("working_data/campaigns/{campaign_id}/startpos.esf"))]
3474 },
3475 None => return Err(RLibError::BuildStartposError("Assembly Kit path not provided.".to_owned())),
3476 }
3477 },
3478
3479 KEY_SHOGUN_2_COMPLETE_EDITION |
3482 KEY_SHOGUN_2 |
3483 KEY_NAPOLEON |
3484 KEY_EMPIRE => vec![game_data_path.join(format!("campaigns/{campaign_id}/startpos.esf"))],
3485 _ => return Err(RLibError::BuildStartposError("How the fuck did you trigger this?".to_owned())),
3486 };
3487
3488 let starpos_paths_pack = if sub_start_pos.is_empty() {
3489 vec![format!("campaigns/{}/startpos.esf", campaign_id)]
3490 } else {
3491 let mut paths = vec![];
3492 for sub in sub_start_pos {
3493 paths.push(format!("campaigns/{campaign_id}/startpos_{sub}.esf"));
3494 }
3495 paths
3496 };
3497
3498 if !cleanup_mode {
3499 for (index, starpos_path) in starpos_paths.iter().enumerate() {
3500 if !starpos_path.is_file() {
3501 if sub_start_pos.is_empty() {
3502 startpos_failed = true;
3503 } else {
3504 sub_startpos_failed.push(sub_start_pos[index].to_owned());
3505 }
3506 } else {
3507
3508 let mut rfile = RFile::new_from_file_path(starpos_path)?;
3509 rfile.set_path_in_container_raw(&starpos_paths_pack[index]);
3510 rfile.load()?;
3511 rfile.guess_file_type()?;
3512
3513 added_paths.push(pack_file.insert(rfile).map(|x| x.unwrap())?);
3514 }
3515 }
3516 }
3517
3518 if game.key() != KEY_THRONES_OF_BRITANNIA &&
3524 game.key() != KEY_ATTILA &&
3525 game.key() != KEY_ROME_2 &&
3526 game.key() != KEY_SHOGUN_2_COMPLETE_EDITION &&
3527 game.key() != KEY_SHOGUN_2 {
3528
3529 for starpos_path in &starpos_paths {
3530 let file_name = starpos_path.file_name().unwrap().to_string_lossy().to_string();
3531 let file_name_bak = file_name + ".bak";
3532
3533 let mut starpos_path_bak = starpos_path.to_path_buf();
3534 starpos_path_bak.set_file_name(file_name_bak);
3535
3536 if starpos_path_bak.is_file() {
3537 std::fs::copy(&starpos_path_bak, starpos_path)?;
3538 std::fs::remove_file(starpos_path_bak)?;
3539 }
3540 }
3541 }
3542
3543 if game.key() == KEY_SHOGUN_2 || game.key() == KEY_SHOGUN_2_COMPLETE_EDITION {
3545 for starpos_path in &starpos_paths {
3546 if starpos_path.is_file() {
3547 std::fs::remove_file(starpos_path)?;
3548 }
3549 }
3550 }
3551
3552 if process_hlp_spd_data {
3554 if let Some(map_name) = map_names.get(campaign_id) {
3555
3556 let hlp_path = match game.key() {
3558 KEY_PHARAOH_DYNASTIES |
3559 KEY_PHARAOH |
3560 KEY_WARHAMMER_3 |
3561 KEY_TROY |
3562 KEY_THREE_KINGDOMS |
3563 KEY_WARHAMMER_2 |
3564 KEY_WARHAMMER => game_data_path.join(format!("campaign_maps/{map_name}/hlp_data.esf")),
3565 KEY_THRONES_OF_BRITANNIA |
3566 KEY_ATTILA => config_path.join(format!("maps/campaign_maps/{map_name}/hlp_data.esf")),
3567 KEY_ROME_2 => game_data_path.join(format!("campaign_maps/{map_name}/hlp_data.esf")),
3568 _ => return Err(RLibError::BuildStartposError("How the fuck did you trigger this?".to_owned())),
3569 };
3570
3571 let hlp_path_pack = format!("campaign_maps/{map_name}/hlp_data.esf");
3572
3573 if !cleanup_mode {
3574
3575 if !hlp_path.is_file() {
3576 hlp_failed = true;
3577 } else {
3578
3579 let mut rfile_hlp = RFile::new_from_file_path(&hlp_path)?;
3580 rfile_hlp.set_path_in_container_raw(&hlp_path_pack);
3581 rfile_hlp.load()?;
3582 rfile_hlp.guess_file_type()?;
3583
3584 added_paths.push(pack_file.insert(rfile_hlp).map(|x| x.unwrap())?);
3585 }
3586 }
3587
3588 if game.key() != KEY_THRONES_OF_BRITANNIA &&
3590 game.key() != KEY_ATTILA {
3591
3592 let hlp_path_bak = game_data_path.join(format!("campaign_maps/{map_name}/hlp_data.esf.bak"));
3593
3594 if hlp_path_bak.is_file() {
3595 std::fs::copy(&hlp_path_bak, hlp_path)?;
3596 std::fs::remove_file(hlp_path_bak)?;
3597 }
3598 }
3599
3600 if game.key() != KEY_THRONES_OF_BRITANNIA &&
3602 game.key() != KEY_ATTILA &&
3603 game.key() != KEY_ROME_2 {
3604
3605 let spd_path = game_data_path.join(format!("campaign_maps/{map_name}/spd_data.esf"));
3606 let spd_path_pack = format!("campaign_maps/{map_name}/spd_data.esf");
3607
3608 if !cleanup_mode {
3609
3610 if !spd_path.is_file() {
3611 spd_failed = true;
3612 } else {
3613
3614 let mut rfile_spd = RFile::new_from_file_path(&spd_path)?;
3615 rfile_spd.set_path_in_container_raw(&spd_path_pack);
3616 rfile_spd.load()?;
3617 rfile_spd.guess_file_type()?;
3618
3619 added_paths.push(pack_file.insert(rfile_spd).map(|x| x.unwrap())?);
3620 }
3621 }
3622
3623 let spd_path_bak = game_data_path.join(format!("campaign_maps/{map_name}/spd_data.esf.bak"));
3624 if spd_path_bak.is_file() {
3625 std::fs::copy(&spd_path_bak, spd_path)?;
3626 std::fs::remove_file(spd_path_bak)?;
3627 }
3628 }
3629 }
3630 }
3631
3632 let mut error = String::new();
3633 if startpos_failed || (!sub_start_pos.is_empty() && !sub_startpos_failed.is_empty()) || hlp_failed || spd_failed {
3634 error.push_str("<p>One or more files failed to generate:</p><ul>")
3635 }
3636 if startpos_failed {
3637 error.push_str("<li>Startpos file failed to generate.</li>");
3638 }
3639
3640 for sub_failed in &sub_startpos_failed {
3641 error.push_str(&format!("<li>\"{sub_failed}\" Startpos file failed to generate.</li>"));
3642 }
3643
3644 if hlp_failed {
3645 error.push_str("<li>HLP file failed to generate.</li>");
3646 }
3647
3648 if spd_failed {
3649 error.push_str("<li>SPD file failed to generate.</li>");
3650 }
3651
3652 if startpos_failed || hlp_failed || spd_failed {
3653 error.push_str("</ul><p>No files were added and the related files were restored to their pre-build state. Check your tables are correct before trying to generate them again.</p>")
3654 }
3655
3656 if error.is_empty() {
3657 Ok(added_paths)
3658 } else {
3659 Err(RLibError::BuildStartposError(error))
3660 }
3661 }
3662
3663 pub fn import_from_ak(&self, table_name: &str, schema: &Schema) -> Result<DB> {
3667 let ak_file = self.asskit_only_db_tables().get(table_name).ok_or_else(|| RLibError::AssemblyKitTableNotFound(table_name.to_owned()))?;
3668
3669 let ak_fields = ak_file.definition().fields_processed();
3670 let ak_field_names = ak_fields.iter().map(|field| field.name()).collect::<Vec<_>>();
3671 let definition = schema.definition_by_name_and_fields(table_name, &ak_field_names).ok_or(RLibError::DecodingDBNoDefinitionsFound)?;
3672
3673 let mut real_table = ak_file.clone();
3675 real_table.set_definition(definition);
3676 Ok(real_table)
3677 }
3678
3679 pub fn insert_loc_as_vanilla_loc(&mut self, rfile: RFile) {
3687 let path = rfile.path_in_container_raw().to_owned();
3688 self.vanilla_files.insert(path.to_owned(), rfile);
3689 self.vanilla_locs.insert(path);
3690 }
3691
3692 pub fn add_recursive_lookups_to_definition(&self, schema: &Schema, definition: &mut Definition, table_name: &str) {
3696 let schema_patches = definition.patches().clone();
3697
3698 definition.edit_fields(|fields| {
3699 for field in fields.iter_mut() {
3700
3701 if let Some(lookup_data_old) = field.lookup(Some(&schema_patches)) {
3703 let mut lookup_data = vec![];
3704
3705 if !lookup_data_old.is_empty() {
3707
3708 let table_name = if let Some(table_name) = table_name.strip_suffix("_tables") {
3709 table_name.to_owned()
3710 } else {
3711 table_name.to_owned()
3712 };
3713
3714 for lookup_data_old in &lookup_data_old {
3715 let lookup_string = format!("{}#{}#{}", table_name, field.name(), lookup_data_old);
3716 self.add_recursive_lookups(schema, &schema_patches, lookup_data_old, &mut lookup_data, &lookup_string, &table_name);
3717 }
3718
3719 }
3720
3721 if let Some((ref_table_name, ref_column)) = field.is_reference(Some(&schema_patches)) {
3723 for lookup_data_old in &lookup_data_old {
3724 let lookup_string = format!("{ref_table_name}#{ref_column}#{lookup_data_old}");
3725 self.add_recursive_lookups(schema, &schema_patches, lookup_data_old, &mut lookup_data, &lookup_string, &ref_table_name);
3726 }
3727 }
3728
3729 if !lookup_data.is_empty() {
3730 field.set_lookup(Some(lookup_data));
3731 } else {
3732 field.set_lookup(None);
3733 }
3734 }
3735 }
3736 });
3737
3738 definition.edit_patches(|patches| patches.values_mut().for_each(|patch| { patch.remove("lookup"); }));
3740 }
3741
3742 fn add_recursive_lookups(&self,
3743 schema: &Schema,
3744 schema_patches: &HashMap<String, HashMap<String, String>>,
3745 lookup: &str,
3746 lookup_data: &mut Vec<String>,
3747 lookup_string: &str,
3748 table_name: &str
3749 ) {
3750 let mut finish_lookup = false;
3751 let table_name = table_name.to_string() + "_tables";
3752 if let Ok(ref_tables) = self.db_data(&table_name, true, true) {
3753 let candidates = ref_tables.iter()
3754 .filter_map(|rfile| rfile.decoded().ok())
3755 .filter_map(|decoded| if let RFileDecoded::DB(db) = decoded {
3756 Some(db.definition().clone())
3757 } else {
3758 None
3759 })
3760 .collect::<Vec<_>>();
3761
3762 if let Some(definition) = schema.definition_newer(&table_name, &candidates) {
3763
3764 if let Some(pos) = definition.column_position_by_name(lookup) {
3766 if let Some(field) = definition.fields_processed().get(pos) {
3767
3768 if let Some((ref_table_name, ref_column)) = field.is_reference(Some(schema_patches)) {
3770 if let Some(lookups) = field.lookup(Some(schema_patches)) {
3771 for lookup in &lookups {
3772 let lookup_string = format!("{lookup_string}:{ref_table_name}#{ref_column}#{lookup}");
3773
3774 self.add_recursive_lookups(schema, schema_patches, lookup, lookup_data, &lookup_string, &ref_table_name);
3775 }
3776 } else {
3777 finish_lookup = true;
3778 }
3779 } else {
3780 finish_lookup = true;
3781 }
3782 } else {
3783 finish_lookup = true;
3784 }
3785 }
3786
3787 else if definition.localised_fields().iter().any(|x| x.name() == lookup) {
3788 finish_lookup = true;
3789 }
3790 } else {
3791 finish_lookup = true;
3792 }
3793 }
3794
3795 if finish_lookup && !lookup_data.iter().any(|x| x == lookup_string) {
3796 lookup_data.push(lookup_string.to_owned());
3797 }
3798 }
3799}
3800
3801#[cfg(test)]
3802mod dependencies_test;