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 USER_SCRIPT_FILE_NAME: &str = "user.script.txt";
134
135pub const VICTORY_OBJECTIVES_FILE_NAME: &str = "db/victory_objectives.txt";
137
138pub const VICTORY_OBJECTIVES_EXTRACTED_FILE_NAME: &str = "victory_objectives.txt";
140
141pub const GAMES_NEEDING_VICTORY_OBJECTIVES: [&str; 9] = [
146 KEY_PHARAOH_DYNASTIES,
147 KEY_PHARAOH,
148 KEY_TROY,
149 KEY_THREE_KINGDOMS,
150 KEY_WARHAMMER_2,
151 KEY_WARHAMMER,
152 KEY_THRONES_OF_BRITANNIA,
153 KEY_ATTILA,
154 KEY_ROME_2
155];
156
157#[derive(Default, Debug, Clone, Getters, Serialize, Deserialize)]
197#[getset(get = "pub")]
198pub struct Dependencies {
199
200 build_date: u64,
202
203 version: String,
205
206 #[serde(skip_serializing, skip_deserializing)]
210 vanilla_loose_files: HashMap<String, RFile>,
211
212 vanilla_files: HashMap<String, RFile>,
214
215 #[serde(skip_serializing, skip_deserializing)]
219 parent_files: HashMap<String, RFile>,
220
221 #[serde(skip_serializing, skip_deserializing)]
223 vanilla_loose_tables: HashMap<String, Vec<String>>,
224
225 vanilla_tables: HashMap<String, Vec<String>>,
227
228 #[serde(skip_serializing, skip_deserializing)]
232 parent_tables: HashMap<String, Vec<String>>,
233
234 #[serde(skip_serializing, skip_deserializing)]
236 vanilla_loose_locs: HashSet<String>,
237
238 vanilla_locs: HashSet<String>,
240
241 #[serde(skip_serializing, skip_deserializing)]
245 parent_locs: HashSet<String>,
246
247 #[serde(skip_serializing, skip_deserializing)]
249 vanilla_loose_folders: HashSet<String>,
250
251 vanilla_folders: HashSet<String>,
253
254 #[serde(skip_serializing, skip_deserializing)]
256 parent_folders: HashSet<String>,
257
258 #[serde(skip_serializing, skip_deserializing)]
260 vanilla_loose_paths: HashMap<String, Vec<String>>,
261
262 vanilla_paths: HashMap<String, Vec<String>>,
264
265 #[serde(skip_serializing, skip_deserializing)]
269 parent_paths: HashMap<String, Vec<String>>,
270
271 #[serde(skip_serializing, skip_deserializing)]
275 local_tables_references: HashMap<String, HashMap<i32, TableReferences>>,
276
277 #[serde(skip_serializing, skip_deserializing)]
279 localisation_data: HashMap<String, String>,
280
281 asskit_only_db_tables: HashMap<String, DB>,
283}
284
285#[derive(Eq, PartialEq, Clone, Default, Debug, Getters, MutGetters, Serialize, Deserialize)]
298#[getset(get = "pub", get_mut = "pub")]
299pub struct TableReferences {
300
301 field_name: String,
306
307 referenced_table_is_ak_only: bool,
313
314 referenced_column_is_localised: bool,
320
321 data: HashMap<String, String>,
326}
327
328type Pak3Cache = (
332 HashMap<String, Vec<String>>,
333 HashSet<String>,
334 HashSet<String>,
335 HashMap<String, Vec<String>>,
336 HashMap<String, DB>,
337);
338
339impl Dependencies {
344
345 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<()> {
354
355 if let Some(file_path) = file_path {
357
358 *self = Self::default();
360
361 let stored_data = Self::load(file_path, schema)?;
363 if !stored_data.needs_updating(game_info, game_path)? {
364 *self = stored_data;
365 }
366 }
367
368 self.local_tables_references.clear();
370
371 self.load_loose_files(schema, game_info, game_path)?;
373
374 self.load_parent_files(schema, parent_pack_names, game_info, game_path, secondary_path)?;
376
377 let loc_files = self.loc_data(true, true).unwrap_or_default();
379 let loc_decoded = loc_files.iter()
380 .filter_map(|file| if let Ok(RFileDecoded::Loc(loc)) = file.decoded() { Some(loc) } else { None })
381 .map(|file| file.data())
382 .collect::<Vec<_>>();
383
384 self.localisation_data = loc_decoded.par_iter()
385 .flat_map(|data| data.par_iter()
386 .map(|entry| (entry[0].data_to_string().to_string(), entry[1].data_to_string().to_string()))
387 .collect::<Vec<(_,_)>>()
388 ).collect::<HashMap<_,_>>();
389
390 Ok(())
391 }
392
393 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> {
395 let mut cache = Self {
396 build_date: current_time()?,
397 version: VERSION.to_owned(),
398 vanilla_files: Pack::read_and_merge_ca_packs(game_info, game_path)?.files().clone(),
399 ..Default::default()
400 };
401
402 let cacheable = cache.vanilla_files.par_iter_mut()
403 .filter_map(|(_, file)| {
404 let _ = file.guess_file_type();
405
406 match file.file_type() {
407 FileType::DB |
408 FileType::Loc => Some(file),
409 _ => None,
410 }
411 })
412 .collect::<Vec<&mut RFile>>();
413
414 cacheable.iter()
415 .for_each(|file| {
416 match file.file_type() {
417 FileType::DB => {
418 if let Some(table_name) = file.db_table_name_from_path() {
419 match cache.vanilla_tables.get_mut(table_name) {
420 Some(table_paths) => table_paths.push(file.path_in_container_raw().to_owned()),
421 None => { cache.vanilla_tables.insert(table_name.to_owned(), vec![file.path_in_container_raw().to_owned()]); },
422 }
423 }
424 }
425 FileType::Loc => {
426 cache.vanilla_locs.insert(file.path_in_container_raw().to_owned());
427 }
428 _ => {}
429 }
430 }
431 );
432
433 cache.vanilla_folders = cache.vanilla_files.par_iter().filter_map(|(path, _)| {
434 let file_path_split = path.split('/').collect::<Vec<&str>>();
435 let folder_path_len = file_path_split.len() - 1;
436 if folder_path_len == 0 {
437 None
438 } else {
439
440 let mut paths = Vec::with_capacity(folder_path_len);
441
442 for (index, folder) in file_path_split.iter().enumerate() {
443 if index < path.len() - 1 && !folder.is_empty() {
444 paths.push(file_path_split[0..=index].join("/"))
445 }
446 }
447
448 Some(paths)
449 }
450 }).flatten().collect::<HashSet<String>>();
451
452 cache.vanilla_files.keys().for_each(|path| {
453 let lower = path.to_lowercase();
454 match cache.vanilla_paths.get_mut(&lower) {
455 Some(paths) => paths.push(path.to_owned()),
456 None => { cache.vanilla_paths.insert(lower, vec![path.to_owned()]); },
457 }
458 });
459
460 cache.load_loose_files(&None, game_info, game_path)?;
464
465 if let Some(path) = asskit_path {
467 let _ = cache.generate_asskit_only_db_tables(schema, path, *game_info.raw_db_version(), ignore_game_files_in_ak);
468 }
469
470 Ok(cache)
471 }
472
473 fn generate_asskit_only_db_tables(&mut self, schema: &Option<Schema>, raw_db_path: &Path, version: i16, ignore_game_files: bool) -> Result<()> {
480 let files_to_ignore = if ignore_game_files {
481 self.vanilla_tables.keys().map(|table_name| &table_name[..table_name.len() - 7]).collect::<Vec<_>>()
482 } else {
483 vec![]
484 };
485 let raw_tables = RawTable::read_all(raw_db_path, version, &files_to_ignore)?;
486 let asskit_only_db_tables = raw_tables.par_iter()
487 .map(|x| match schema {
488 Some(schema) => {
489 let mut table_name = x.definition.clone().unwrap().name.unwrap().to_owned();
490 table_name.pop();
491 table_name.pop();
492 table_name.pop();
493 table_name.pop();
494
495 table_name = format!("{table_name}_tables");
496
497 let definition = schema.definitions().get(&table_name).and_then(|x| x.first());
498
499 x.to_db(definition)
500 }
501 None => x.to_db(None),
502 })
503 .collect::<Result<Vec<DB>>>()?;
504
505 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>>();
507
508 let decode_extra_data = DecodeableExtraData::default();
509 let extra_data = Some(decode_extra_data);
510
511 let mut files = self.vanilla_loose_locs.iter().filter_map(|path| {
513 self.vanilla_loose_files.remove(path).map(|file| (path.to_owned(), file))
514 }).collect::<Vec<_>>();
515
516 files.par_iter_mut().for_each(|(_, file)| {
517 let _ = file.decode(&extra_data, true, false);
518 });
519
520 self.vanilla_loose_files.par_extend(files);
521
522 let mut files = self.vanilla_locs.iter().filter_map(|path| {
524 self.vanilla_files.remove(path).map(|file| (path.to_owned(), file))
525 }).collect::<Vec<_>>();
526
527 files.par_iter_mut().for_each(|(_, file)| {
528 let _ = file.decode(&extra_data, true, false);
529 });
530
531 self.vanilla_files.par_extend(files);
532
533 self.bruteforce_loc_key_order(&mut Schema::default(), None, None, Some(&mut asskit_only_db_tables))?;
534 self.asskit_only_db_tables = asskit_only_db_tables;
535
536 Ok(())
537 }
538
539 pub fn generate_local_db_references(&mut self, schema: &Schema, packs: &BTreeMap<String, Pack>, table_names: &[String]) {
545
546 let local_tables_references = packs.values()
547 .flat_map(|pack| pack.files_by_type(&[FileType::DB]))
548 .par_bridge()
549 .filter_map(|file| {
550 if let Ok(RFileDecoded::DB(db)) = file.decoded() {
551
552 if table_names.is_empty() || table_names.iter().any(|x| x == db.table_name()) {
554 Some((db.table_name().to_owned(), self.generate_references(schema, db.table_name(), db.definition())))
555 } else { None }
556 } else { None }
557 }).collect::<HashMap<_, _>>();
558
559 self.local_tables_references.extend(local_tables_references);
560 }
561
562 pub fn generate_local_definition_references(&mut self, schema: &Schema, table_name: &str, definition: &Definition) {
564 self.local_tables_references.insert(table_name.to_owned(), self.generate_references(schema, table_name, definition));
565 }
566
567 pub fn generate_references(&self, schema: &Schema, local_table_name: &str, definition: &Definition) -> HashMap<i32, TableReferences> {
569
570 let mut definition = definition.clone();
573
574 if let Some(table_patches) = schema.patches_for_table(local_table_name) {
578 definition.set_patches(table_patches.clone());
579 }
580
581 self.add_recursive_lookups_to_definition(schema, &mut definition, local_table_name);
582
583 let patches = Some(definition.patches());
584 let fields_processed = definition.fields_processed();
585
586 if local_table_name == KEY_DELETES_TABLE_NAME {
589 let mut hashmap = HashMap::new();
590 let mut references = TableReferences::default();
591 *references.field_name_mut() = "table_name".to_owned();
592
593 for key in schema.definitions().keys() {
594 if key.len() > 7 {
595 let table_name = key.to_owned().drain(..key.len() - 7).collect::<String>();
596 references.data.insert(table_name, String::new());
597 }
598 }
599
600 hashmap.insert(1, references);
601 return hashmap;
602 }
603
604 fields_processed.par_iter().enumerate().filter_map(|(column, field)| {
605 match field.is_reference(patches) {
606 Some((ref ref_table, ref ref_column)) => {
607 if !ref_table.is_empty() && !ref_column.is_empty() {
608 let ref_table = format!("{ref_table}_tables");
609
610 let lookup_data = if let Some(ref data) = field.lookup_no_patch() { data.to_vec() } else { Vec::with_capacity(0) };
612 let mut references = TableReferences::default();
613 *references.field_name_mut() = field.name().to_owned();
614
615 let fake_found = self.db_reference_data_from_asskit_tables(&mut references, (&ref_table, ref_column, &lookup_data));
616 let real_found = self.db_reference_data_from_vanilla_and_modded_tables(&mut references, (&ref_table, ref_column, &lookup_data));
617
618 if fake_found && real_found.is_none() {
619 references.referenced_table_is_ak_only = true;
620 }
621
622 if let Some(ref_definition) = real_found {
623 if ref_definition.localised_fields().iter().any(|x| x.name() == ref_column) {
624 references.referenced_column_is_localised = true;
625 }
626 }
627
628 Some((column as i32, references))
629 } else { None }
630 },
631
632 None => {
634 if let Some(ref lookup_data) = field.lookup_no_patch() {
635
636 if field.is_key(patches) && fields_processed.iter().filter(|x| x.is_key(patches)).count() == 1 {
638 let ref_table = local_table_name;
639 let ref_column = field.name();
640
641 let mut references = TableReferences::default();
643 *references.field_name_mut() = field.name().to_owned();
644
645 let fake_found = self.db_reference_data_from_asskit_tables(&mut references, (ref_table, ref_column, lookup_data));
646 let real_found = self.db_reference_data_from_vanilla_and_modded_tables(&mut references, (ref_table, ref_column, lookup_data));
647
648 if fake_found && real_found.is_none() {
649 references.referenced_table_is_ak_only = true;
650 }
651
652 if let Some(ref_definition) = real_found {
653 if ref_definition.localised_fields().iter().any(|x| x.name() == ref_column) {
654 references.referenced_column_is_localised = true;
655 }
656 }
657
658 Some((column as i32, references))
659 } else { None }
660 } else { None }
661 },
662 }
663 }).collect::<HashMap<_, _>>()
664 }
665
666 pub fn load(file_path: &Path, schema: &Option<Schema>) -> Result<Self> {
668
669 let mut file_path_1 = file_path.to_path_buf();
673 let handle_1: JoinHandle<Result<(u64, String, Vec<RFile>)>> = spawn(move || {
674 file_path_1.set_extension("pak1");
675 let mut file = BufReader::new(File::open(&file_path_1)?);
676 let mut data = Vec::with_capacity(file.get_ref().metadata()?.len() as usize);
677 file.read_to_end(&mut data)?;
678
679 bitcode::deserialize(&data).map_err(From::from)
681 });
682
683 let mut file_path_2 = file_path.to_path_buf();
684 let handle_2: JoinHandle<Result<Vec<RFile>>> = spawn(move || {
685 file_path_2.set_extension("pak2");
686 let mut file = BufReader::new(File::open(&file_path_2)?);
687 let mut data = Vec::with_capacity(file.get_ref().metadata()?.len() as usize);
688 file.read_to_end(&mut data)?;
689
690 bitcode::deserialize(&data).map_err(From::from)
692 });
693
694 let mut file_path_3 = file_path.to_path_buf();
695 let handle_3: JoinHandle<Result<Pak3Cache>> = spawn(move || {
696 file_path_3.set_extension("pak3");
697 let mut file = BufReader::new(File::open(&file_path_3)?);
698 let mut data = Vec::with_capacity(file.get_ref().metadata()?.len() as usize);
699 file.read_to_end(&mut data)?;
700
701 bitcode::deserialize(&data).map_err(From::from)
703 });
704
705 let mut dependencies = Self::default();
707 let data_3 = handle_3.join().unwrap()?;
708 let data_2 = handle_2.join().unwrap()?;
709 let data_1 = handle_1.join().unwrap()?;
710
711 let mut vanilla_files: HashMap<_,_> = data_1.2.into_par_iter().map(|file| (file.path_in_container_raw().to_owned(), file)).collect();
714 vanilla_files.par_extend(data_2.into_par_iter().map(|file| (file.path_in_container_raw().to_owned(), file)));
715
716 dependencies.build_date = data_1.0;
717 dependencies.version = data_1.1;
718 dependencies.vanilla_files = vanilla_files;
719 dependencies.vanilla_tables = data_3.0;
720 dependencies.vanilla_locs = data_3.1;
721 dependencies.vanilla_folders = data_3.2;
722 dependencies.vanilla_paths = data_3.3;
723 dependencies.asskit_only_db_tables = data_3.4;
724
725 if let Some(schema) = schema {
727 let mut decode_extra_data = DecodeableExtraData::default();
728 decode_extra_data.set_schema(Some(schema));
729 let extra_data = Some(decode_extra_data);
730
731 let mut files = dependencies.vanilla_locs.iter().chain(dependencies.vanilla_tables.values().flatten()).filter_map(|path| {
732 dependencies.vanilla_files.remove(path).map(|file| (path.to_owned(), file))
733 }).collect::<Vec<_>>();
734
735 files.par_iter_mut().for_each(|(_, file)| {
736 let _ = file.decode(&extra_data, true, false);
737 });
738
739 dependencies.vanilla_files.par_extend(files);
740 }
741
742 Ok(dependencies)
743 }
744
745 pub fn save(&mut self, file_path: &Path) -> Result<()> {
747 let mut folder_path = file_path.to_owned();
748 folder_path.pop();
749 DirBuilder::new().recursive(true).create(&folder_path)?;
750
751 let mut file_path_1 = file_path.to_path_buf();
752 let mut file_path_2 = file_path.to_path_buf();
753 let mut file_path_3 = file_path.to_path_buf();
754
755 file_path_1.set_extension("pak1");
756 file_path_2.set_extension("pak2");
757 file_path_3.set_extension("pak3");
758
759 let mut file_1 = File::create(&file_path_1)?;
760 let mut file_2 = File::create(&file_path_2)?;
761 let mut file_3 = File::create(&file_path_3)?;
762
763 let mut vanilla_files_1 = self.vanilla_files.par_iter().map(|(_, b)| b.clone()).collect::<Vec<RFile>>();
767 let vanilla_files_2 = vanilla_files_1.split_off(self.vanilla_files.len() / 2);
768
769 let serialized_1: Vec<u8> = bitcode::serialize(&(&self.build_date, &self.version, &vanilla_files_1))?;
771 let serialized_2: Vec<u8> = bitcode::serialize(&vanilla_files_2)?;
772 let serialized_3: Vec<u8> = bitcode::serialize(&(&self.vanilla_tables, &self.vanilla_locs, &self.vanilla_folders, &self.vanilla_paths, &self.asskit_only_db_tables))?;
773
774 file_1.write_all(&serialized_1).map_err(RLibError::from)?;
775 file_2.write_all(&serialized_2).map_err(RLibError::from)?;
776 file_3.write_all(&serialized_3).map_err(From::from)
777 }
778
779 pub fn needs_updating(&self, game_info: &GameInfo, game_path: &Path) -> Result<bool> {
781 let ca_paths = game_info.ca_packs_paths(game_path)?;
782 let last_date = last_modified_time_from_files(&ca_paths)?;
783 Ok(last_date > self.build_date || self.version != VERSION)
784 }
785
786 fn load_loose_files(&mut self, schema: &Option<Schema>, game_info: &GameInfo, game_path: &Path) -> Result<()> {
788 self.vanilla_loose_files.clear();
789 self.vanilla_loose_tables.clear();
790 self.vanilla_loose_locs.clear();
791 self.vanilla_loose_folders.clear();
792 self.vanilla_loose_paths.clear();
793
794 let game_data_path = game_info.data_path(game_path)?;
795 let game_data_path_str = game_data_path.to_string_lossy().replace('\\', "/");
796
797 self.vanilla_loose_files = files_from_subdir(&game_data_path, true)?
798 .into_par_iter()
799 .filter_map(|path| {
800 let mut path = path.to_string_lossy().replace('\\', "/");
801 if !path.ends_with(".pack") {
802 if let Ok(mut rfile) = RFile::new_from_file(&path) {
803 let subpath = path.split_off(game_data_path_str.len() + 1);
804 rfile.set_path_in_container_raw(&subpath);
805 let _ = rfile.guess_file_type();
806 Some((subpath, rfile))
807 } else {
808 None
809 }
810 } else {
811 None
812 }
813 })
814 .collect::<HashMap<String, RFile>>();
815
816 let cacheable = self.vanilla_loose_files.par_iter_mut()
817 .filter_map(|(_, file)| {
818 let _ = file.guess_file_type();
819
820 match file.file_type() {
821 FileType::DB |
822 FileType::Loc => Some(file),
823 _ => None,
824 }
825 })
826 .collect::<Vec<&mut RFile>>();
827
828 cacheable.iter()
829 .for_each(|file| {
830 match file.file_type() {
831 FileType::DB => {
832 if let Some(table_name) = file.db_table_name_from_path() {
833 match self.vanilla_loose_tables.get_mut(table_name) {
834 Some(table_paths) => table_paths.push(file.path_in_container_raw().to_owned()),
835 None => { self.vanilla_loose_tables.insert(table_name.to_owned(), vec![file.path_in_container_raw().to_owned()]); },
836 }
837 }
838 }
839 FileType::Loc => {
840 self.vanilla_loose_locs.insert(file.path_in_container_raw().to_owned());
841 }
842 _ => {}
843 }
844 }
845 );
846
847 self.vanilla_loose_folders = self.vanilla_loose_files.par_iter().filter_map(|(path, _)| {
848 let file_path_split = path.split('/').collect::<Vec<&str>>();
849 let folder_path_len = file_path_split.len() - 1;
850 if folder_path_len == 0 {
851 None
852 } else {
853
854 let mut paths = Vec::with_capacity(folder_path_len);
855
856 for (index, folder) in file_path_split.iter().enumerate() {
857 if index < path.len() - 1 && !folder.is_empty() {
858 paths.push(file_path_split[0..=index].join("/"))
859 }
860 }
861
862 Some(paths)
863 }
864 }).flatten().collect::<HashSet<String>>();
865
866 self.vanilla_loose_files.keys().for_each(|path| {
867 let lower = path.to_lowercase();
868 match self.vanilla_loose_paths.get_mut(&lower) {
869 Some(paths) => paths.push(path.to_owned()),
870 None => { self.vanilla_loose_paths.insert(lower, vec![path.to_owned()]); },
871 }
872 });
873
874 if let Some(schema) = schema {
876 let mut decode_extra_data = DecodeableExtraData::default();
877 decode_extra_data.set_schema(Some(schema));
878 let extra_data = Some(decode_extra_data);
879
880 let mut files = self.vanilla_loose_locs.iter().chain(self.vanilla_loose_tables.values().flatten()).filter_map(|path| {
881 self.vanilla_loose_files.remove(path).map(|file| (path.to_owned(), file))
882 }).collect::<Vec<_>>();
883
884 files.par_iter_mut().for_each(|(_, file)| {
885 let _ = file.decode(&extra_data, true, false);
886 });
887
888 self.vanilla_loose_files.par_extend(files);
889 }
890
891 Ok(())
892 }
893
894
895 fn load_parent_files(&mut self, schema: &Option<Schema>, parent_pack_names: &[String], game_info: &GameInfo, game_path: &Path, secondary_path: &Path) -> Result<()> {
897 self.parent_files.clear();
898 self.parent_tables.clear();
899 self.parent_locs.clear();
900 self.parent_folders.clear();
901 self.parent_paths.clear();
902
903 self.load_parent_packs(parent_pack_names, game_info, game_path, secondary_path)?;
905 self.parent_files.par_iter_mut().map(|(_, file)| file.guess_file_type()).collect::<Result<()>>()?;
906
907 self.parent_files.iter()
909 .for_each(|(path, file)| {
910 match file.file_type() {
911 FileType::DB => {
912 if let Some(table_name) = file.db_table_name_from_path() {
913 match self.parent_tables.get_mut(table_name) {
914 Some(table_paths) => table_paths.push(path.to_owned()),
915 None => { self.parent_tables.insert(table_name.to_owned(), vec![path.to_owned()]); },
916 }
917 }
918 }
919 FileType::Loc => {
920 self.parent_locs.insert(path.to_owned());
921 }
922 _ => {}
923 }
924 }
925 );
926
927 self.parent_folders = self.parent_files.par_iter().filter_map(|(path, _)| {
929 let file_path_split = path.split('/').collect::<Vec<&str>>();
930 let folder_path_len = file_path_split.len() - 1;
931 if folder_path_len == 0 {
932 None
933 } else {
934
935 let mut paths = Vec::with_capacity(folder_path_len);
936
937 for (index, folder) in file_path_split.iter().enumerate() {
938 if index < path.len() - 1 && !folder.is_empty() {
939 paths.push(file_path_split[0..=index].join("/"))
940 }
941 }
942
943 Some(paths)
944 }
945 }).flatten().collect::<HashSet<String>>();
946
947 self.parent_files.keys().for_each(|path| {
948 let lower = path.to_lowercase();
949 match self.parent_paths.get_mut(&lower) {
950 Some(paths) => paths.push(path.to_owned()),
951 None => { self.parent_paths.insert(lower, vec![path.to_owned()]); },
952 }
953 });
954
955 if let Some(schema) = schema {
957 let mut decode_extra_data = DecodeableExtraData::default();
958 decode_extra_data.set_schema(Some(schema));
959 let extra_data = Some(decode_extra_data);
960
961 let mut files = self.parent_tables.values().flatten().filter_map(|path| {
962 self.parent_files.remove(path).map(|file| (path.to_owned(), file))
963 }).collect::<Vec<_>>();
964
965 files.par_iter_mut().for_each(|(_, file)| {
966 let _ = file.decode(&extra_data, true, false);
967 });
968
969 self.parent_files.par_extend(files);
970 }
971
972 let mut files = self.parent_locs.iter().filter_map(|path| {
974 self.parent_files.remove(path).map(|file| (path.to_owned(), file))
975 }).collect::<Vec<_>>();
976
977 files.par_iter_mut().for_each(|(_, file)| {
978 let _ = file.decode(&None, true, false);
979 });
980
981 self.parent_files.par_extend(files);
982
983 Ok(())
984 }
985
986 fn load_parent_packs(&mut self, parent_pack_names: &[String], game_info: &GameInfo, game_path: &Path, secondary_path: &Path) -> Result<()> {
989 let data_packs_paths = game_info.data_packs_paths(game_path).unwrap_or_default();
990 let secondary_packs_paths = game_info.secondary_packs_paths(secondary_path);
991 let content_packs_paths = game_info.content_packs_paths(game_path);
992 let mut loaded_packfiles = vec![];
993
994 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));
995
996 Ok(())
997 }
998
999 fn load_parent_pack(
1002 &mut self,
1003 pack_name: &str,
1004 already_loaded: &mut Vec<String>,
1005 data_paths: &[PathBuf],
1006 secondary_paths: &Option<Vec<PathBuf>>,
1007 content_paths: &Option<Vec<PathBuf>>,
1008 game_info: &GameInfo
1009 ) {
1010 if !already_loaded.contains(&pack_name.to_owned()) {
1012
1013 if let Some(path) = data_paths.iter().find(|x| x.file_name().unwrap().to_string_lossy() == pack_name) {
1015 if let Ok(pack) = Pack::read_and_merge(&[path.to_path_buf()], game_info, true, false, false) {
1016 already_loaded.push(pack_name.to_owned());
1017 pack.dependencies().iter().for_each(|(_, pack_name)| self.load_parent_pack(pack_name, already_loaded, data_paths, secondary_paths, content_paths, game_info));
1018 self.parent_files.extend(pack.files().clone());
1019
1020 return;
1021 }
1022 }
1023
1024 if let Some(ref paths) = secondary_paths {
1026 if let Some(path) = paths.iter().find(|x| x.file_name().unwrap().to_string_lossy() == pack_name) {
1027 if let Ok(pack) = Pack::read_and_merge(&[path.to_path_buf()], game_info, true, false, false) {
1028 already_loaded.push(pack_name.to_owned());
1029 pack.dependencies().iter().for_each(|(_, pack_name)| self.load_parent_pack(pack_name, already_loaded, data_paths, secondary_paths, content_paths, game_info));
1030 self.parent_files.extend(pack.files().clone());
1031
1032 return;
1033 }
1034 }
1035 }
1036
1037 if let Some(ref paths) = content_paths {
1039 if let Some(path) = paths.iter().find(|x| x.file_name().unwrap().to_string_lossy() == pack_name) {
1040 if let Ok(pack) = Pack::read_and_merge(&[path.to_path_buf()], game_info, true, false, false) {
1041 already_loaded.push(pack_name.to_owned());
1042 pack.dependencies().iter().for_each(|(_, pack_name)| self.load_parent_pack(pack_name, already_loaded, data_paths, secondary_paths, content_paths, game_info));
1043 self.parent_files.extend(pack.files().clone());
1044 }
1045 }
1046 }
1047 }
1048 }
1049
1050 pub fn decode_tables(&mut self, schema: &Option<Schema>) {
1054 if let Some(schema) = schema {
1055
1056 let mut decode_extra_data = DecodeableExtraData::default();
1057 decode_extra_data.set_schema(Some(schema));
1058 let extra_data = Some(decode_extra_data);
1059
1060 let mut files = self.vanilla_loose_locs.iter().chain(self.vanilla_loose_tables.values().flatten()).filter_map(|path| {
1062 self.vanilla_loose_files.remove(path).map(|file| (path.to_owned(), file))
1063 }).collect::<Vec<_>>();
1064
1065 files.par_iter_mut().for_each(|(_, file)| {
1066 let _ = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| file.decode(&extra_data, true, false)));
1067 });
1068
1069 self.vanilla_loose_files.par_extend(files);
1070
1071 let mut files = self.vanilla_locs.iter().chain(self.vanilla_tables.values().flatten()).filter_map(|path| {
1073 self.vanilla_files.remove(path).map(|file| (path.to_owned(), file))
1074 }).collect::<Vec<_>>();
1075
1076 files.par_iter_mut().for_each(|(_, file)| {
1077 let _ = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| file.decode(&extra_data, true, false)));
1078 });
1079
1080 self.vanilla_files.par_extend(files);
1081
1082 let mut files = self.parent_locs.iter().chain(self.parent_tables.values().flatten()).filter_map(|path| {
1084 self.parent_files.remove(path).map(|file| (path.to_owned(), file))
1085 }).collect::<Vec<_>>();
1086
1087 files.par_iter_mut().for_each(|(_, file)| {
1088 let _ = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| file.decode(&extra_data, true, false)));
1089 });
1090
1091 self.parent_files.par_extend(files);
1092 }
1093 }
1094
1095 pub fn file(&self, file_path: &str, include_vanilla: bool, include_parent: bool, case_insensitive: bool) -> Result<&RFile> {
1101 let file_path = if let Some(file_path) = file_path.strip_prefix('/') {
1102 file_path
1103 } else {
1104 file_path
1105 };
1106
1107 if include_parent {
1108
1109 if let Some(file) = self.parent_files.get(file_path) {
1111 return Ok(file);
1112 }
1113
1114 if case_insensitive {
1115 let lower = file_path.to_lowercase();
1116 if let Some(file) = self.parent_paths.get(&lower).and_then(|paths| self.parent_files.get(&paths[0])) {
1117 return Ok(file);
1118 }
1119 }
1120 }
1121
1122 if include_vanilla {
1123
1124 if let Some(file) = self.vanilla_files.get(file_path) {
1126 return Ok(file);
1127 }
1128
1129 if case_insensitive {
1130 let lower = file_path.to_lowercase();
1131 if let Some(file) = self.vanilla_paths.get(&lower).and_then(|paths| self.vanilla_files.get(&paths[0])) {
1132 return Ok(file);
1133 }
1134
1135 }
1136
1137 if let Some(file) = self.vanilla_loose_files.get(file_path) {
1139 return Ok(file);
1140 }
1141
1142 if case_insensitive {
1143 let lower = file_path.to_lowercase();
1144 if let Some(file) = self.vanilla_loose_paths.get(&lower).and_then(|paths| self.vanilla_loose_files.get(&paths[0])) {
1145 return Ok(file);
1146 }
1147 }
1148 }
1149
1150 Err(RLibError::DependenciesCacheFileNotFound(file_path.to_owned()))
1151 }
1152
1153 pub fn file_mut(&mut self, file_path: &str, include_vanilla: bool, include_parent: bool) -> Result<&mut RFile> {
1155 if include_parent {
1156 if let Some(file) = self.parent_files.get_mut(file_path) {
1157 return Ok(file);
1158 }
1159 }
1160
1161 if include_vanilla {
1162 if let Some(file) = self.vanilla_files.get_mut(file_path) {
1163 return Ok(file);
1164 }
1165
1166 if let Some(file) = self.vanilla_loose_files.get_mut(file_path) {
1167 return Ok(file);
1168 }
1169 }
1170
1171 Err(RLibError::DependenciesCacheFileNotFound(file_path.to_owned()))
1172 }
1173
1174 pub fn files_mut_by_paths(
1176 &mut self,
1177 paths: &HashSet<String>,
1178 include_vanilla: bool,
1179 include_parent: bool,
1180 ) -> HashMap<String, &mut RFile> {
1181 let mut result: HashMap<String, &mut RFile> = HashMap::with_capacity(paths.len());
1182
1183 if include_parent {
1184 for (k, v) in self.parent_files.iter_mut() {
1185 if paths.contains(k) {
1186 result.insert(k.clone(), v);
1187 }
1188 }
1189 }
1190
1191 if include_vanilla {
1192 for (k, v) in self.vanilla_files.iter_mut() {
1193 if paths.contains(k) && !result.contains_key(k) {
1194 result.insert(k.clone(), v);
1195 }
1196 }
1197
1198 for (k, v) in self.vanilla_loose_files.iter_mut() {
1199 if paths.contains(k) && !result.contains_key(k) {
1200 result.insert(k.clone(), v);
1201 }
1202 }
1203 }
1204
1205 result
1206 }
1207
1208 pub fn files_by_path(&self, file_paths: &[ContainerPath], include_vanilla: bool, include_parent: bool, case_insensitive: bool) -> HashMap<String, &RFile> {
1210 let (file_paths, folder_paths): (Vec<_>, Vec<_>) = file_paths.iter().partition_map(|file_path| match file_path {
1211 ContainerPath::File(file_path) => Either::Left(file_path.to_owned()),
1212 ContainerPath::Folder(file_path) => Either::Right(file_path.to_owned()),
1213 });
1214
1215 let mut hashmap = HashMap::new();
1216
1217 if !file_paths.is_empty() {
1219 hashmap.extend(file_paths.par_iter()
1220 .filter_map(|file_path| self.file(file_path, include_vanilla, include_parent, case_insensitive)
1221 .ok()
1222 .map(|file| (file_path.to_owned(), file)))
1223 .collect::<Vec<(_,_)>>()
1224 );
1225 }
1226
1227 if !folder_paths.is_empty() {
1229 hashmap.extend(folder_paths.into_par_iter().flat_map(|folder_path| {
1230 let mut folder = vec![];
1231 let folder_path = folder_path.to_owned() + "/";
1232 if include_vanilla {
1233
1234 if folder_path == "/" {
1235 folder.extend(self.vanilla_loose_files.par_iter()
1236 .map(|(path, file)| (path.to_owned(), file))
1237 .collect::<Vec<(_,_)>>());
1238
1239 folder.extend(self.vanilla_files.par_iter()
1240 .map(|(path, file)| (path.to_owned(), file))
1241 .collect::<Vec<(_,_)>>());
1242
1243 } else {
1244 folder.extend(self.vanilla_loose_files.par_iter()
1245 .filter(|(path, _)| {
1246 if case_insensitive {
1247 starts_with_case_insensitive(path, &folder_path)
1248 } else {
1249 path.starts_with(&folder_path)
1250 }
1251 })
1252 .map(|(path, file)| (path.to_owned(), file))
1253 .collect::<Vec<(_,_)>>());
1254
1255 folder.extend(self.vanilla_files.par_iter()
1256 .filter(|(path, _)| {
1257 if case_insensitive {
1258 starts_with_case_insensitive(path, &folder_path)
1259 } else {
1260 path.starts_with(&folder_path)
1261 }
1262 })
1263 .map(|(path, file)| (path.to_owned(), file))
1264 .collect::<Vec<(_,_)>>());
1265 }
1266 }
1267
1268 if include_parent {
1269 if folder_path == "/" {
1270 folder.extend(self.parent_files.par_iter()
1271 .map(|(path, file)| (path.to_owned(), file))
1272 .collect::<Vec<(_,_)>>());
1273
1274 } else {
1275 folder.extend(self.parent_files.par_iter()
1276 .filter(|(path, _)| {
1277 if case_insensitive {
1278 starts_with_case_insensitive(path, &folder_path)
1279 } else {
1280 path.starts_with(&folder_path)
1281 }
1282 })
1283 .map(|(path, file)| (path.to_owned(), file))
1284 .collect::<Vec<(_,_)>>());
1285 }
1286 }
1287 folder
1288 }).collect::<Vec<(_,_)>>());
1289 }
1290
1291 hashmap
1292 }
1293
1294 pub fn files_by_types(&self, file_types: &[FileType], include_vanilla: bool, include_parent: bool) -> HashMap<String, &RFile> {
1296 let mut files = HashMap::new();
1297
1298 if include_vanilla {
1300 files.extend(self.vanilla_loose_files.par_iter().chain(self.vanilla_files.par_iter())
1301 .filter(|(_, file)| file_types.contains(&file.file_type()))
1302 .map(|(path, file)| (path.to_owned(), file))
1303 .collect::<HashMap<_,_>>());
1304 }
1305
1306 if include_parent {
1307 files.extend(self.parent_files.par_iter()
1308 .filter(|(_, file)| file_types.contains(&file.file_type()))
1309 .map(|(path, file)| (path.to_owned(), file))
1310 .collect::<HashMap<_,_>>());
1311 }
1312
1313 files
1314 }
1315
1316 pub fn files_by_types_mut(&mut self, file_types: &[FileType], include_vanilla: bool, include_parent: bool) -> HashMap<String, &mut RFile> {
1318 let mut files = HashMap::new();
1319
1320 if include_vanilla {
1322 files.extend(self.vanilla_loose_files.par_iter_mut().chain(self.vanilla_files.par_iter_mut())
1323 .filter(|(_, file)| file_types.contains(&file.file_type()))
1324 .map(|(path, file)| (path.to_owned(), file))
1325 .collect::<HashMap<_,_>>());
1326 }
1327
1328 if include_parent {
1329 files.extend(self.parent_files.par_iter_mut()
1330 .filter(|(_, file)| file_types.contains(&file.file_type()))
1331 .map(|(path, file)| (path.to_owned(), file))
1332 .collect::<HashMap<_,_>>());
1333 }
1334
1335 files
1336 }
1337
1338 pub fn loc_data(&self, include_vanilla: bool, include_parent: bool) -> Result<Vec<&RFile>> {
1342 let mut cache = vec![];
1343
1344 if include_vanilla {
1345 let mut vanilla_loose_locs = self.vanilla_loose_locs.iter().collect::<Vec<_>>();
1346 vanilla_loose_locs.sort();
1347
1348 for path in &vanilla_loose_locs {
1349 if let Some(file) = self.vanilla_loose_files.get(*path) {
1350 cache.push(file);
1351 }
1352 }
1353
1354 let mut vanilla_locs = self.vanilla_locs.iter().collect::<Vec<_>>();
1355 vanilla_locs.sort();
1356
1357 for path in &vanilla_locs {
1358 if let Some(file) = self.vanilla_files.get(*path) {
1359 cache.push(file);
1360 }
1361 }
1362 }
1363
1364 if include_parent {
1365 let mut parent_locs = self.parent_locs.iter().collect::<Vec<_>>();
1366 parent_locs.sort();
1367
1368 for path in &parent_locs {
1369 if let Some(file) = self.parent_files.get(*path) {
1370 cache.push(file);
1371 }
1372 }
1373 }
1374
1375 Ok(cache)
1376 }
1377
1378 pub fn db_data(&self, table_name: &str, include_vanilla: bool, include_parent: bool) -> Result<Vec<&RFile>> {
1384 let mut cache = vec![];
1385
1386 if include_vanilla {
1387 if let Some(vanilla_loose_tables) = self.vanilla_loose_tables.get(table_name) {
1388 let mut vanilla_loose_tables = vanilla_loose_tables.to_vec();
1389 vanilla_loose_tables.sort();
1390
1391 for path in &vanilla_loose_tables {
1392 if let Some(file) = self.vanilla_loose_files.get(path) {
1393 cache.push(file);
1394 }
1395 }
1396 }
1397
1398 if let Some(vanilla_tables) = self.vanilla_tables.get(table_name) {
1399 let mut vanilla_tables = vanilla_tables.to_vec();
1400 vanilla_tables.sort();
1401
1402 for path in &vanilla_tables {
1403 if let Some(file) = self.vanilla_files.get(path) {
1404 cache.push(file);
1405 }
1406 }
1407 }
1408 }
1409
1410 if include_parent {
1411 if let Some(parent_tables) = self.parent_tables.get(table_name) {
1412 let mut parent_tables = parent_tables.to_vec();
1413 parent_tables.sort();
1414
1415 for path in &parent_tables {
1416 if let Some(file) = self.parent_files.get(path) {
1417 cache.push(file);
1418 }
1419 }
1420 }
1421 }
1422
1423 Ok(cache)
1424 }
1425
1426 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>> {
1433 let mut cache = vec![];
1434
1435 if include_vanilla {
1436 if let Some(vanilla_loose_tables) = self.vanilla_loose_tables.get(table_name) {
1437 let mut vanilla_loose_tables = vanilla_loose_tables.to_vec();
1438 vanilla_loose_tables.sort();
1439
1440 for path in &vanilla_loose_tables {
1441 if let Some(file) = self.vanilla_loose_files.get(path) {
1442 cache.push(file);
1443 }
1444 }
1445 }
1446
1447 if let Some(vanilla_tables) = self.vanilla_tables.get(table_name) {
1448 let mut vanilla_tables = vanilla_tables.to_vec();
1449 vanilla_tables.sort();
1450
1451 for path in &vanilla_tables {
1452 if let Some(file) = self.vanilla_files.get(path) {
1453 cache.push(file);
1454 }
1455 }
1456 }
1457 }
1458
1459 if include_parent {
1460 if let Some(parent_tables) = self.parent_tables.get(table_name) {
1461 let mut parent_tables = parent_tables.to_vec();
1462 parent_tables.sort();
1463
1464 for path in &parent_tables {
1465 if let Some(file) = self.parent_files.get(path) {
1466 cache.push(file);
1467 }
1468 }
1469 }
1470 }
1471
1472 let paths = cache.iter()
1473 .map(|x| x.path_in_container())
1474 .collect::<Vec<_>>();
1475
1476 for pack in packs.values() {
1477 for pack_file in pack.files_by_paths(&paths, true) {
1478 for cache_file in &mut cache {
1479 if cache_file.path_in_container() == pack_file.path_in_container() {
1480 *cache_file = pack_file;
1481 break;
1482 }
1483 }
1484 }
1485 }
1486
1487 Ok(cache)
1488 }
1489
1490 pub fn db_and_loc_data(&self, include_db: bool, include_loc: bool, include_vanilla: bool, include_parent: bool) -> Result<Vec<&RFile>> {
1494 let mut cache = vec![];
1495
1496 if include_vanilla {
1497 if include_db {
1498 let mut vanilla_loose_tables = self.vanilla_loose_tables.values().flatten().collect::<Vec<_>>();
1499 vanilla_loose_tables.sort();
1500
1501 for path in &vanilla_loose_tables {
1502 if let Some(file) = self.vanilla_loose_files.get(*path) {
1503 cache.push(file);
1504 }
1505 }
1506
1507 let mut vanilla_tables = self.vanilla_tables.values().flatten().collect::<Vec<_>>();
1508 vanilla_tables.sort();
1509
1510 for path in &vanilla_tables {
1511 if let Some(file) = self.vanilla_files.get(*path) {
1512 cache.push(file);
1513 }
1514 }
1515 }
1516
1517 if include_loc {
1518 let mut vanilla_loose_locs = self.vanilla_loose_locs.iter().collect::<Vec<_>>();
1519 vanilla_loose_locs.sort();
1520
1521 for path in &vanilla_loose_locs {
1522 if let Some(file) = self.vanilla_loose_files.get(*path) {
1523 cache.push(file);
1524 }
1525 }
1526
1527 let mut vanilla_locs = self.vanilla_locs.iter().collect::<Vec<_>>();
1528 vanilla_locs.sort();
1529
1530 for path in &vanilla_locs {
1531 if let Some(file) = self.vanilla_files.get(*path) {
1532 cache.push(file);
1533 }
1534 }
1535 }
1536 }
1537
1538 if include_parent {
1539 if include_db {
1540 let mut parent_tables = self.parent_tables.values().flatten().collect::<Vec<_>>();
1541 parent_tables.sort();
1542
1543 for path in &parent_tables {
1544 if let Some(file) = self.parent_files.get(*path) {
1545 cache.push(file);
1546 }
1547 }
1548 }
1549
1550 if include_loc {
1551 let mut parent_locs = self.parent_locs.iter().collect::<Vec<_>>();
1552 parent_locs.sort();
1553
1554 for path in &parent_locs {
1555 if let Some(file) = self.parent_files.get(*path) {
1556 cache.push(file);
1557 }
1558 }
1559 }
1560 }
1561
1562 Ok(cache)
1563 }
1564
1565 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> {
1573
1574 let mut vanilla_references = match self.local_tables_references.get(table_name) {
1578 Some(cached_data) => cached_data.clone(),
1579 None => HashMap::new(),
1580 };
1581
1582 let (_loc_files, loc_decoded) = if loc_data.is_some() {
1584 (vec![], vec![])
1585 } else {
1586 let loc_files: Vec<_> = packs.values().flat_map(|pack| pack.files_by_type(&[FileType::Loc])).collect();
1587 let loc_decoded = loc_files.iter()
1588 .filter_map(|file| if let Ok(RFileDecoded::Loc(loc)) = file.decoded() { Some(loc) } else { None })
1589 .map(|file| file.data())
1590 .collect::<Vec<_>>();
1591 (loc_files, loc_decoded)
1592 };
1593
1594 let mut _loc_data_dummy = HashMap::new();
1595 let loc_data = if let Some(ref loc_data) = loc_data {
1596 loc_data
1597 } else {
1598 _loc_data_dummy = loc_decoded.par_iter()
1599 .flat_map(|data| data.par_iter()
1600 .map(|entry| (entry[0].data_to_string(), entry[1].data_to_string()))
1601 .collect::<Vec<(_,_)>>()
1602 ).collect::<HashMap<_,_>>();
1603 &_loc_data_dummy
1604 };
1605
1606 let mut definition = definition.clone();
1609
1610 if let Some(table_patches) = schema.patches_for_table(table_name) {
1614 definition.set_patches(table_patches.clone());
1615 }
1616
1617 self.add_recursive_lookups_to_definition(schema, &mut definition, table_name);
1618
1619 let patches = Some(definition.patches());
1620 let fields_processed = definition.fields_processed();
1621 let local_references = fields_processed.par_iter().enumerate().filter_map(|(column, field)| {
1622 match field.is_reference(patches) {
1623 Some((ref ref_table, ref ref_column)) => {
1624 if !ref_table.is_empty() && !ref_column.is_empty() {
1625
1626 let lookup_data = if let Some(ref data) = field.lookup_no_patch() { data.to_vec() } else { Vec::with_capacity(0) };
1628 let mut references = TableReferences::default();
1629 *references.field_name_mut() = field.name().to_owned();
1630
1631 let _local_found = self.db_reference_data_from_local_pack(&mut references, (ref_table, ref_column, &lookup_data), packs, loc_data);
1632
1633 Some((column as i32, references))
1634 } else { None }
1635 }
1636
1637 None => {
1639 if let Some(ref lookup_data) = field.lookup_no_patch() {
1640
1641 if field.is_key(patches) && fields_processed.iter().filter(|x| x.is_key(patches)).count() == 1 {
1643
1644 let ref_table = if table_name.ends_with("_tables") && table_name.len() > 7 {
1646 table_name.to_owned().drain(..table_name.len() - 7).collect()
1647 } else {
1648 table_name.to_owned()
1649 };
1650
1651 let ref_column = field.name();
1652
1653 let mut references = TableReferences::default();
1655 *references.field_name_mut() = field.name().to_owned();
1656
1657 let _local_found = self.db_reference_data_from_local_pack(&mut references, (&ref_table, ref_column, lookup_data), packs, loc_data);
1658
1659 Some((column as i32, references))
1660 } else { None }
1661 } else { None }
1662 }
1663 }
1664 }).collect::<HashMap<_, _>>();
1665
1666 vanilla_references.par_iter_mut().for_each(|(key, value)|
1667 if let Some(local_value) = local_references.get(key) {
1668 value.data.extend(local_value.data.iter().map(|(k, v)| (k.clone(), v.clone())));
1669 }
1670 );
1671
1672 for (index, field) in fields_processed.iter().enumerate() {
1673 match vanilla_references.get_mut(&(index as i32)) {
1674 Some(references) => {
1675 let hardcoded_lookup = field.lookup_hardcoded(patches);
1676 if !hardcoded_lookup.is_empty() {
1677 references.data.extend(hardcoded_lookup);
1678 }
1679 },
1680 None => {
1681 let mut references = TableReferences::default();
1682 *references.field_name_mut() = field.name().to_owned();
1683 let hardcoded_lookup = field.lookup_hardcoded(patches);
1684 if !hardcoded_lookup.is_empty() {
1685 references.data.extend(hardcoded_lookup);
1686 vanilla_references.insert(index as i32, references);
1687 }
1688 },
1689 }
1690 }
1691
1692 vanilla_references
1693 }
1694
1695 fn db_reference_data_from_vanilla_and_modded_tables(&self, references: &mut TableReferences, reference_info: (&str, &str, &[String])) -> Option<Definition> {
1699 self.db_reference_data_generic(references, reference_info, None, &HashMap::new())
1700 }
1701
1702 fn db_reference_data_from_asskit_tables(&self, references: &mut TableReferences, reference_info: (&str, &str, &[String])) -> bool {
1706 let ref_table = reference_info.0;
1707 let ref_column = reference_info.1;
1708 let ref_lookup_columns = reference_info.2;
1709
1710 match self.asskit_only_db_tables.get(ref_table) {
1711 Some(table) => {
1712 let fields_processed = table.definition().fields_processed();
1713 let ref_column_index = fields_processed.iter().position(|x| x.name() == ref_column);
1714 let ref_lookup_columns_index = ref_lookup_columns.iter().map(|column| fields_processed.iter().position(|x| x.name() == column)).collect::<Vec<_>>();
1715
1716 for row in &*table.data() {
1717 let mut reference_data = String::new();
1718 let mut lookup_data = vec![];
1719
1720 if let Some(index) = ref_column_index {
1722 reference_data = row[index].data_to_string().to_string();
1723 }
1724
1725 for column in ref_lookup_columns_index.iter().flatten() {
1727 lookup_data.push(row[*column].data_to_string());
1728 }
1729
1730 references.data.insert(reference_data, lookup_data.join(" "));
1731 }
1732 true
1733 },
1734 None => false,
1735 }
1736 }
1737
1738 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> {
1740 self.db_reference_data_generic(references, reference_info, Some(packs), loc_data)
1741 }
1742
1743 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> {
1744 let mut data_found: Option<Definition> = None;
1745
1746 let ref_table = reference_info.0;
1747 let ref_column = reference_info.1;
1748 let ref_lookup_columns = reference_info.2;
1749
1750 let mut cache = HashMap::new();
1751
1752 let ref_table_full = if ref_table.ends_with("_tables") {
1754 ref_table.to_owned()
1755 } else {
1756 ref_table.to_owned() + "_tables"
1757 };
1758
1759 let files = match packs {
1760 Some(packs) => {
1761 let mut files: Vec<&RFile> = packs.values().flat_map(|pack| pack.files_by_path(&ContainerPath::Folder(format!("db/{ref_table_full}")), true)).collect();
1762 files.append(&mut self.db_data(&ref_table_full, true, true).unwrap_or_else(|_| vec![]));
1763 files
1764 },
1765 None => self.db_data(&ref_table_full, true, true).unwrap_or_else(|_| vec![]),
1766 };
1767
1768 let mut table_data_cache: HashMap<String, HashMap<String, String>> = HashMap::new();
1769
1770 files.iter().for_each(|file| {
1771 if let Ok(RFileDecoded::DB(db)) = file.decoded() {
1772 let definition = db.definition();
1773 let fields_processed = definition.fields_processed();
1774
1775 if let Some(ref_column_index) = fields_processed.iter().position(|x| x.name() == ref_column) {
1777
1778 let lookups_analyzed = ref_lookup_columns.iter().map(|ref_lookup_path| {
1780 let ref_lookup_steps = ref_lookup_path.split(':').map(|x| x.split('#').collect::<Vec<_>>()).collect::<Vec<_>>();
1781 let mut is_loc = false;
1782 let mut col_pos = 0;
1783
1784 for (index, ref_lookup_step) in ref_lookup_steps.iter().enumerate() {
1785 if ref_lookup_step.len() == 3 {
1786 let lookup_ref_table = ref_lookup_step[0];
1787 let lookup_ref_key = ref_lookup_step[1];
1788 let lookup_ref_lookup = ref_lookup_step[2];
1789 let lookup_ref_table_long = lookup_ref_table.to_owned() + "_tables";
1790
1791 if !cache.contains_key(lookup_ref_table) {
1793 let mut files = vec![];
1794
1795 if let Some(packs) = packs {
1796 for pack in packs.values() {
1797 files.append(&mut pack.files_by_path(&ContainerPath::Folder(format!("db/{lookup_ref_table_long}")), true));
1798 }
1799 }
1800
1801 for file in self.db_data(&lookup_ref_table_long, true, true).unwrap_or_else(|_| vec![]) {
1803 if files.iter().all(|x| x.path_in_container_raw() != file.path_in_container_raw()) {
1804 files.push(file);
1805 }
1806 }
1807
1808 if !files.is_empty() {
1809
1810 files.sort_by(|a, b| a.path_in_container_raw().cmp(b.path_in_container_raw()));
1812 cache.insert(lookup_ref_table.to_owned(), files);
1813 }
1814 }
1815
1816 if index == ref_lookup_steps.len() - 1 {
1818 if let Some(file) = cache.get(lookup_ref_table) {
1819 if let Some(file) = file.first() {
1820 if let Ok(RFileDecoded::DB(db)) = file.decoded() {
1821 let definition = db.definition();
1822 let fields_processed = definition.fields_processed();
1823 let localised_fields = definition.localised_fields();
1824
1825 match localised_fields.iter().position(|x| x.name() == lookup_ref_lookup) {
1826 Some(loc_pos) => {
1827 is_loc = true;
1828 col_pos = loc_pos;
1829 },
1830 None => match fields_processed.iter().position(|x| x.name() == lookup_ref_lookup) {
1831 Some(pos) => {
1832 is_loc = false;
1833 col_pos = pos;
1834 },
1835 None => {
1836 },
1838 }
1839 }
1840 }
1841 }
1842 }
1843 }
1844
1845 if let Some(files) = cache.get(lookup_ref_table) {
1847 for file in files {
1848 let table_data_column_cache_key = file.path_in_container_raw().to_owned() + &ref_lookup_step.join("++");
1849 if !table_data_cache.contains_key(&table_data_column_cache_key) {
1850 if let Ok(RFileDecoded::DB(db)) = file.decoded() {
1851 let definition = db.definition();
1852 let fields_processed = definition.fields_processed();
1853 let localised_fields = definition.localised_fields();
1854 let localised_order = definition.localised_key_order();
1855
1856 let loc_key = if is_loc {
1857 if let Some(loc_field) = localised_fields.get(col_pos) {
1858 let mut loc_key = String::with_capacity(2 + lookup_ref_table.len() + loc_field.name().len());
1859 loc_key.push_str(lookup_ref_table);
1860 loc_key.push('_');
1861 loc_key.push_str(loc_field.name());
1862 loc_key.push('_');
1863 loc_key
1864 } else {
1865 String::new()
1866 }
1867 } else {
1868 String::new()
1869 };
1870
1871 if let Some(source_key_column) = fields_processed.iter().position(|x| x.name() == lookup_ref_key) {
1872
1873 if index < ref_lookup_steps.len() - 1 {
1875 if let Some(source_lookup_column) = fields_processed.iter().position(|x| x.name() == lookup_ref_lookup) {
1876 let cache = db.data().iter()
1877 .filter_map(|row| Some((row.get(source_key_column)?.data_to_string().to_string(), row.get(source_lookup_column)?.data_to_string().to_string())))
1878 .collect::<HashMap<_,_>>();
1879
1880 table_data_cache.insert(table_data_column_cache_key.clone(), cache);
1881 }
1882 }
1883
1884 else if is_loc {
1886 let cache = db.data().iter()
1887 .filter_map(|row| {
1888 let mut loc_key = loc_key.to_owned();
1889 for pos in localised_order.iter() {
1890 loc_key.push_str(&row.get(*pos as usize)?.data_to_string());
1891 }
1892 Some((row.get(source_key_column)?.data_to_string().to_string(), loc_key))
1893 })
1894 .collect::<HashMap<_,_>>();
1895 table_data_cache.insert(table_data_column_cache_key.clone(), cache);
1896 }
1897
1898 else {
1899 let cache = db.data().iter()
1900 .filter_map(|row| Some((row.get(source_key_column)?.data_to_string().to_string(), row.get(col_pos)?.data_to_string().to_string())))
1901 .collect::<HashMap<_,_>>();
1902
1903 table_data_cache.insert(table_data_column_cache_key.clone(), cache);
1904 }
1905 }
1906 }
1907 }
1908 }
1909 }
1910 } else {
1911 error!("Badly built lookup. This is a bug.");
1912 }
1913 }
1914
1915 (ref_lookup_steps, is_loc)
1916
1917 }).collect::<Vec<_>>();
1918
1919 let data = db.data();
1920 for row in &*data {
1921 let mut lookup_data = Vec::with_capacity(lookups_analyzed.len());
1922
1923 let reference_data = row[ref_column_index].data_to_string();
1925
1926 for (lookup_steps, is_loc) in lookups_analyzed.iter() {
1928 if !reference_data.is_empty() {
1929
1930 if let Some(lookup) = self.db_reference_data_generic_lookup(&cache, loc_data, &reference_data, lookup_steps, *is_loc, &table_data_cache) {
1931 lookup_data.push(lookup);
1932 }
1933 }
1934 }
1935
1936 references.data.insert(reference_data.to_string(), lookup_data.into_iter().join(":"));
1937 }
1938
1939 match data_found {
1941 Some(ref definition) => {
1942 if db.definition().version() > definition.version() {
1943 data_found = Some(db.definition().clone());
1944 }
1945 }
1946
1947 None => data_found = Some(db.definition().clone()),
1948 }
1949 }
1950 }
1951 });
1952
1953 data_found
1954 }
1955
1956 fn db_reference_data_generic_lookup(
1957 &self,
1958 cache: &HashMap<String, Vec<&RFile>>,
1959 loc_data: &HashMap<Cow<str>, Cow<str>>,
1960 lookup_key: &str,
1961 lookup_steps: &[Vec<&str>],
1962 is_loc: bool,
1963 table_data_cache: &HashMap<String, HashMap<String, String>>
1964 ) -> Option<String> {
1965 let mut data_found: Option<String> = None;
1966
1967 if lookup_steps.is_empty() {
1968 return None;
1969 }
1970
1971 let current_step = &lookup_steps[0];
1972 let source_table = current_step[0];
1973
1974 if let Some(files) = cache.get(source_table) {
1975 for file in files {
1976 let table_data_column_cache_key = file.path_in_container_raw().to_owned() + ¤t_step.join("++");
1977 if let Some(table_data_column_cache) = table_data_cache.get(&table_data_column_cache_key) {
1978
1979 if let Some(lookup_value) = table_data_column_cache.get(lookup_key) {
1980
1981 if lookup_steps.len() > 1 {
1983 if !lookup_value.is_empty() {
1984 data_found = self.db_reference_data_generic_lookup(cache, loc_data, lookup_value, &lookup_steps[1..], is_loc, table_data_cache);
1985 }
1986 }
1987
1988 else if is_loc {
1990
1991 if let Some(data) = loc_data.get(&**lookup_value) {
1992 data_found = Some(data.to_string());
1993 } else if let Some(data) = self.localisation_data.get(&**lookup_value) {
1994 data_found = Some(data.to_string());
1995 } else {
1996 data_found = Some(lookup_value.to_string())
1997 }
1998 }
1999
2000 else {
2002 data_found = Some(lookup_value.to_owned());
2003 }
2004
2005 break;
2007 }
2008 }
2009 }
2010 }
2011
2012 data_found
2013 }
2014
2015 pub fn loc_key_source(&self, key: &str) -> Option<(String, String, Vec<String>)> {
2019 let key_split = key.split('_').collect::<Vec<_>>();
2020
2021 for (index, _) in key_split.iter().enumerate().rev() {
2024
2025 if index >= 1 {
2027
2028 let mut table_name = key_split[..index].join("_");
2029 let full_table_name = format!("{table_name}_tables");
2030
2031 if let Ok(rfiles) = self.db_data(&full_table_name, true, false) {
2032 let mut decoded = rfiles.iter()
2033 .filter_map(|x| if let Ok(RFileDecoded::DB(table)) = x.decoded() {
2034 Some(table)
2035 } else {
2036 None
2037 }).collect::<Vec<_>>();
2038
2039 if let Some(ak_file) = self.asskit_only_db_tables().get(&full_table_name) {
2041 decoded.push(ak_file);
2042 }
2043
2044 for table in decoded {
2045 let definition = table.definition();
2046 let localised_fields = definition.localised_fields();
2047 let localised_key_order = definition.localised_key_order();
2048 if !localised_fields.is_empty() {
2049 let mut field = String::new();
2050
2051 for (second_index, value) in key_split[index..].iter().enumerate() {
2053 field.push_str(value);
2054
2055 if localised_fields.iter().any(|x| x.name() == field) {
2056
2057 let key_data = &key_split[index + second_index + 1..].join("_");
2059
2060 let data = table.data();
2063 for row in data.iter() {
2064 let generated_key_split = localised_key_order.iter().map(|col| row[*col as usize].data_to_string()).collect::<Vec<_>>();
2065 let generated_key = generated_key_split.join("");
2066 if &generated_key == key_data {
2067 return Some((table_name, field, generated_key_split.iter().map(|x| x.to_string()).collect()));
2068 }
2069 }
2070 }
2071
2072 field.push('_');
2073 }
2074 }
2075 }
2076 }
2077
2078 table_name.push('_');
2080 }
2081 }
2082
2083 None
2084 }
2085
2086 pub fn file_exists(&self, file_path: &str, include_vanilla: bool, include_parent: bool, case_insensitive: bool) -> bool {
2092 if include_parent {
2093 if self.parent_files.contains_key(file_path) {
2094 return true
2095 } else if case_insensitive {
2096 let lower = file_path.to_lowercase();
2097 if self.parent_paths.contains_key(&lower) {
2098 return true
2099 }
2100 }
2101 }
2102
2103 if include_vanilla {
2104
2105 if self.vanilla_files.contains_key(file_path) || self.vanilla_loose_files.contains_key(file_path) {
2106 return true
2107 } else if case_insensitive {
2108 let lower = file_path.to_lowercase();
2109 if self.vanilla_paths.contains_key(&lower) || self.vanilla_loose_paths.contains_key(&lower) {
2110 return true
2111 }
2112 }
2113 }
2114
2115 false
2116 }
2117
2118 pub fn folder_exists(&self, folder_path: &str, include_vanilla: bool, include_parent: bool, case_insensitive: bool) -> bool {
2120 if include_parent && (
2121 self.parent_folders.contains(folder_path) ||
2122 (case_insensitive && self.parent_folders.par_iter().any(|path| caseless::canonical_caseless_match_str(path, folder_path)))
2123 ) {
2124 return true
2125 }
2126
2127 if include_vanilla && (
2128 (self.vanilla_folders.contains(folder_path) || self.vanilla_loose_folders.contains(folder_path)) ||
2129 (case_insensitive && self.vanilla_folders.par_iter().chain(self.vanilla_loose_folders.par_iter()).any(|path| caseless::canonical_caseless_match_str(path, folder_path)))
2130 ) {
2131 return true
2132 }
2133
2134 false
2135 }
2136
2137 pub fn are_dependencies_generated(file_path: &Path) -> bool {
2139 file_path.is_file()
2140 }
2141
2142 pub fn is_vanilla_data_loaded(&self, include_asskit: bool) -> bool {
2144 if include_asskit {
2145 !self.vanilla_files.is_empty() && self.is_asskit_data_loaded()
2146 } else {
2147 !self.vanilla_files.is_empty()
2148 }
2149 }
2150
2151 pub fn is_asskit_data_loaded(&self) -> bool {
2153 !self.asskit_only_db_tables.is_empty()
2154 }
2155
2156 pub fn is_db_outdated(&self, rfile: &RFileDecoded) -> bool {
2158 if let RFileDecoded::DB(data) = rfile {
2159 let dep_db_undecoded = if let Ok(undecoded) = self.db_data(data.table_name(), true, false) { undecoded } else { return false };
2160 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<_>>();
2161
2162 if let Some(vanilla_db) = dep_db_decoded.iter().max_by(|x, y| x.definition().version().cmp(y.definition().version())) {
2163 if vanilla_db.definition().version() > data.definition().version() {
2164 return true;
2165 }
2166 }
2167 }
2168
2169 false
2170 }
2171
2172 pub fn db_version(&self, table_name: &str) -> Option<i32> {
2174 let tables = self.vanilla_tables.get(table_name)?;
2175 for table_path in tables {
2176
2177 let table = self.vanilla_files.get(table_path)?;
2178 if let RFileDecoded::DB(table) = table.decoded().ok()? {
2179 return Some(*table.definition().version());
2180 }
2181
2182 let table = self.vanilla_loose_files.get(table_path)?;
2183 if let RFileDecoded::DB(table) = table.decoded().ok()? {
2184 return Some(*table.definition().version());
2185 }
2186 }
2187
2188 None
2189 }
2190
2191 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> {
2193 let mut values = HashSet::new();
2194
2195 if let Ok(files) = self.db_data(table_name, include_vanilla, include_parent) {
2196 values.extend(files.par_iter().filter_map(|file| {
2197 if let Ok(RFileDecoded::DB(table)) = file.decoded() {
2198 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<_>>())
2199 } else { None }
2200 }).flatten().collect::<Vec<_>>());
2201 }
2202
2203 if let Some(packs) = packs {
2204 for pack in packs.values() {
2205 let files = pack.files_by_path(&ContainerPath::Folder(format!("db/{table_name}")), true);
2206 values.extend(files.par_iter().filter_map(|file| {
2207 if let Ok(RFileDecoded::DB(table)) = file.decoded() {
2208 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<_>>())
2209 } else { None }
2210 }).flatten().collect::<Vec<_>>());
2211 }
2212 }
2213
2214 values
2215 }
2216
2217 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> {
2219 let mut values = HashMap::new();
2220
2221 if let Ok(files) = self.db_data(table_name, include_vanilla, include_parent) {
2222 values.extend(files.par_iter().filter_map(|file| {
2223 if let Ok(RFileDecoded::DB(table)) = file.decoded() {
2224 if let Some(column) = table.definition().column_position_by_name(key_column_name) {
2225 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<_>>())
2226 } else { None }
2227 } else { None }
2228 }).flatten().collect::<Vec<_>>());
2229 }
2230
2231 if let Some(packs) = packs {
2232 for pack in packs.values() {
2233 let files = pack.files_by_path(&ContainerPath::Folder(format!("db/{table_name}")), true);
2234 values.extend(files.par_iter().filter_map(|file| {
2235 if let Ok(RFileDecoded::DB(table)) = file.decoded() {
2236 if let Some(column) = table.definition().column_position_by_name(key_column_name) {
2237 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<_>>())
2238 } else { None }
2239 } else { None }
2240 }).flatten().collect::<Vec<_>>());
2241 }
2242 }
2243
2244 values
2245 }
2246
2247 pub fn update_db(&mut self, rfile: &mut RFileDecoded) -> Result<(i32, i32, Vec<String>, Vec<String>)> {
2251 match rfile {
2252 RFileDecoded::DB(data) => {
2253 let dep_db_undecoded = self.db_data(data.table_name(), true, false)?;
2254 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<_>>();
2255
2256 if let Some(vanilla_db) = dep_db_decoded.iter().max_by(|x, y| x.definition().version().cmp(y.definition().version())) {
2257
2258 let definition_new = vanilla_db.definition();
2259 let definition_old = data.definition().clone();
2260 if definition_old != *definition_new {
2261 data.set_definition(definition_new);
2262
2263 let fields_old = definition_old.fields_processed();
2265 let fields_new = definition_new.fields_processed();
2266 let fields_deleted = fields_old.iter()
2267 .filter(|x| fields_new.iter().all(|y| y.name() != x.name()))
2268 .map(|x| x.name().to_owned())
2269 .collect::<Vec<_>>();
2270 let fields_added = fields_new.iter()
2271 .filter(|x| fields_old.iter().all(|y| y.name() != x.name()))
2272 .map(|x| x.name().to_owned())
2273 .collect::<Vec<_>>();
2274
2275 Ok((*definition_old.version(), *definition_new.version(), fields_deleted, fields_added))
2276 }
2277 else {
2278 Err(RLibError::NoDefinitionUpdateAvailable)
2279 }
2280 }
2281 else { Err(RLibError::NoTableInGameFilesToCompare) }
2282 }
2283 _ => Err(RLibError::DecodingDBNotADBTable),
2284 }
2285 }
2286
2287 pub fn generate_missing_loc_data(&self, packs: &mut BTreeMap<String, Pack>) -> Result<Vec<ContainerPath>> {
2289 let loc_data = self.loc_data(true, true)?;
2290 let mut existing_locs = HashMap::new();
2291
2292 for loc in &loc_data {
2293 if let Ok(RFileDecoded::Loc(ref data)) = loc.decoded() {
2294 existing_locs.extend(data.table().data().iter().map(|x| (x[0].data_to_string().to_string(), x[1].data_to_string().to_string())));
2295 }
2296 }
2297
2298 let mut all_paths = vec![];
2299 for pack in packs.values_mut() {
2300 all_paths.extend(pack.generate_missing_loc_data(&existing_locs)?);
2301 }
2302 Ok(all_paths)
2303 }
2304
2305 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<()> {
2307 let mut fields_still_not_found = vec![];
2308
2309 let loc_files = self.loc_data(true, false)?;
2311 let loc_table = loc_files.iter()
2312 .filter_map(|file| if let Ok(RFileDecoded::Loc(loc)) = file.decoded() { Some(loc) } else { None })
2313 .flat_map(|file| file.data().to_vec())
2314 .map(|entry| (entry[0].data_to_string().to_string(), entry[1].data_to_string().to_string()))
2315 .collect::<HashMap<_,_>>();
2316
2317 let ak_tables = match ak_files {
2318 Some(ref tables) => (**tables).clone(),
2319 None => HashMap::new(),
2320 };
2321
2322 let local_files: Vec<_> = match local_packs {
2324 Some(packs) => packs.values()
2325 .flat_map(|pack| pack.files_by_type(&[FileType::DB]))
2326 .filter_map(|x| match x.decoded() {
2327 Ok(RFileDecoded::DB(db)) => Some(db),
2328 _ => None,
2329 })
2330 .collect(),
2331 None => Vec::new(),
2332 };
2333
2334 let mut db_tables = if ak_files.is_some() {
2336 ak_tables.values().collect::<Vec<_>>()
2337 } else {
2338 self.db_and_loc_data(true, false, true, false)?
2339 .iter()
2340 .filter_map(|file| if let Ok(RFileDecoded::DB(table)) = file.decoded() { Some(table) } else { None })
2341 .collect::<Vec<_>>()
2342 };
2343
2344 db_tables.extend_from_slice(&local_files);
2345
2346 let mut db_tables_dedup: Vec<DB> = vec![];
2348 for table in &db_tables {
2349 match db_tables_dedup.iter_mut().find(|x| x.table_name() == table.table_name() && x.definition().version() == table.definition().version()) {
2350 Some(db_source) => *db_source = DB::merge(&[db_source, table])?,
2351 None => db_tables_dedup.push((*table).clone()),
2352 }
2353 }
2354
2355 for table in &db_tables_dedup {
2356 let definition = table.definition();
2357 let mut loc_fields = definition.localised_fields().to_vec();
2358
2359 let mut loc_fields_final = loc_fields.to_vec();
2362
2363 if let Some(ref loc_fields_info) = locs {
2365 loc_fields.clear();
2366
2367 if let Some(loc_names) = loc_fields_info.get(&table.table_name_without_tables()) {
2368 for name in loc_names {
2369 if loc_fields.iter().all(|x| x.name() != name) {
2370
2371 let mut field = Field::default();
2372 field.set_name(name.to_string());
2373 field.set_field_type(FieldType::StringU8);
2374
2375 loc_fields.push(field);
2376 }
2377 }
2378 }
2379 }
2380
2381 let fields = definition.fields_processed();
2382 let key_fields = fields.iter()
2383 .enumerate()
2384 .filter(|(_, field)| field.is_key(None))
2385 .collect::<Vec<_>>();
2386
2387 let short_table_name = table.table_name_without_tables();
2389 for localised_field in &loc_fields {
2390 let localised_key = format!("{}_{}_", short_table_name, localised_field.name());
2391
2392 if loc_table.keys().any(|x| x.starts_with(&localised_key)) && loc_fields_final.iter().all(|x| x.name() != localised_field.name()) {
2394 loc_fields_final.push(localised_field.clone());
2395 }
2396 }
2397
2398 for table_field in &fields {
2401 if loc_fields_final.iter().all(|x| !x.name().starts_with(table_field.name())) {
2402 let localised_key = format!("{}_{}_", short_table_name, table_field.name());
2403 if loc_table.keys().any(|x| x.starts_with(&localised_key)) && loc_fields_final.iter().all(|x| x.name() != table_field.name()) {
2404 loc_fields_final.push(table_field.clone());
2405 }
2406 }
2407 }
2408
2409 for loc_field in &loc_fields {
2410 if loc_fields_final.iter().all(|x| x.name() != loc_field.name()) {
2411 fields_still_not_found.push(format!("{}/{}", table.table_name_without_tables(), loc_field.name()));
2412 }
2413 }
2414
2415 if let Some(ak_files) = &mut ak_files {
2417 let ak_table = ak_files.get_mut(table.table_name()).unwrap();
2418 let mut definition = ak_table.definition().clone();
2419 definition.set_localised_fields(loc_fields_final.to_vec());
2420 ak_table.set_definition(&definition);
2421
2422 } else if let Some(schema_definition) = schema.definition_by_name_and_version_mut(table.table_name(), *definition.version()) {
2423 schema_definition.set_localised_fields(loc_fields_final.to_vec());
2424 }
2425
2426 if !loc_fields_final.is_empty() {
2428
2429 let order = if key_fields.len() == 1 {
2431 vec![key_fields[0].0 as u32]
2432 }
2433
2434 else {
2436 let mut order = Vec::with_capacity(key_fields.len());
2437 let combos = key_fields.iter().permutations(key_fields.len());
2438 let table_data = table.data();
2439 for combo in combos {
2440
2441 let mut combo_is_valid = true;
2444 for row in table_data.iter() {
2445 let mut combined_key = String::new();
2453 for (index, _) in &combo {
2454 combined_key.push_str(&row[*index].data_to_string());
2455 }
2456
2457 for localised_field in &loc_fields_final {
2458 let localised_key = format!("{}_{}_{}", short_table_name, localised_field.name(), combined_key);
2459 match loc_table.get(&localised_key) {
2460 Some(_) => {
2461 if order.is_empty() {
2462 order = combo.iter().map(|(index, _)| *index as u32).collect();
2463 }
2464 }
2465 None => {
2466 combo_is_valid = false;
2467 break;
2468 }
2469 }
2470 }
2471
2472 if !combo_is_valid {
2474 break;
2475 }
2476 }
2477
2478 if !combo_is_valid {
2480 order = vec![];
2481 continue;
2482 }
2483
2484 if !order.is_empty() {
2485 break;
2486 }
2487 }
2488
2489 order
2490 };
2491
2492 if !order.is_empty() && !loc_fields_final.is_empty() {
2493 info!("Bruteforce: loc key order found for table {}, version {}.", table.table_name(), definition.version());
2494 if let Some(ak_files) = &mut ak_files {
2495 let ak_table = ak_files.get_mut(table.table_name()).unwrap();
2496 let mut definition = ak_table.definition().clone();
2497 definition.set_localised_key_order(order);
2498 ak_table.set_definition(&definition);
2499 } else if let Some(schema_definition) = schema.definition_by_name_and_version_mut(table.table_name(), *definition.version()) {
2500 schema_definition.set_localised_key_order(order);
2501 }
2502 } else {
2503 info!("Bruteforce: loc key order found (but may be incorrect) for table {}, version {}.", table.table_name(), definition.version());
2504
2505 if loc_fields_final.is_empty() {
2507 if let Some(ak_files) = &mut ak_files {
2508 let ak_table = ak_files.get_mut(table.table_name()).unwrap();
2509 let mut definition = ak_table.definition().clone();
2510 definition.set_localised_key_order(vec![]);
2511 ak_table.set_definition(&definition);
2512 } else if let Some(schema_definition) = schema.definition_by_name_and_version_mut(table.table_name(), *definition.version()) {
2513 schema_definition.set_localised_key_order(vec![]);
2514 }
2515 }
2516 }
2517 }
2518
2519 else if let Some(ak_files) = &mut ak_files {
2521 let ak_table = ak_files.get_mut(table.table_name()).unwrap();
2522 let mut definition = ak_table.definition().clone();
2523 definition.set_localised_key_order(vec![]);
2524 ak_table.set_definition(&definition);
2525 } else if let Some(schema_definition) = schema.definition_by_name_and_version_mut(table.table_name(), *definition.version()) {
2526 schema_definition.set_localised_key_order(vec![]);
2527 }
2528 }
2529
2530 fields_still_not_found.sort();
2532 fields_still_not_found.dedup();
2533 info!("Bruteforce: fields still not found :{fields_still_not_found:#?}");
2534
2535 if ak_files.is_none() {
2538 for key in loc_table.keys().sorted() {
2539 if self.loc_key_source(key).is_none() {
2540 info!("-- Bruteforce: cannot find source for loc key {key}.");
2541 }
2542 }
2543 }
2544
2545 Ok(())
2546 }
2547
2548 #[allow(clippy::if_same_then_else)]
2550 pub fn generate_automatic_patches(&self, schema: &mut Schema, packs: &BTreeMap<String, Pack>) -> Result<()> {
2551 let mut db_tables = self.db_and_loc_data(true, false, true, false)?
2552 .iter()
2553 .filter_map(|file| if let Ok(RFileDecoded::DB(table)) = file.decoded() { Some(table) } else { None })
2554 .collect::<Vec<_>>();
2555
2556 for pack in packs.values() {
2557 db_tables.extend_from_slice(&pack.files_by_type(&[FileType::DB])
2558 .iter()
2559 .filter_map(|x| if let Ok(RFileDecoded::DB(db)) = x.decoded() {
2560 Some(db)
2561 } else {
2562 None
2563 })
2564 .collect::<Vec<_>>()
2565 );
2566 }
2567
2568 let current_patches = schema.patches_mut();
2569 let mut new_patches: HashMap<String, DefinitionPatch> = HashMap::new();
2570
2571 let image_paths = self.vanilla_files()
2573 .keys()
2574 .filter(|x| x.ends_with(".png") || x.ends_with(".tga"))
2575 .collect::<Vec<_>>();
2576
2577 let video_paths = self.vanilla_files()
2578 .keys()
2579 .filter(|x| x.ends_with(".ca_vp8"))
2580 .collect::<Vec<_>>();
2581
2582 for table in &db_tables {
2583 let definition = table.definition();
2584 let fields = definition.fields_processed();
2585 for (column, field) in fields.iter().enumerate() {
2586 match field.field_type() {
2587 FieldType::StringU8 |
2588 FieldType::StringU16 |
2589 FieldType::OptionalStringU8 |
2590 FieldType::OptionalStringU16 => {
2591
2592 let mut possible_icon = false;
2598 let low_name = field.name().to_lowercase();
2599 if (low_name.contains("icon") || low_name.contains("image")) &&
2600
2601 !(table.table_name() == "building_sets_tables" && field.name() == "icon") &&
2603
2604 !(table.table_name() == "character_traits_tables" && field.name() == "icon") {
2606 possible_icon = true;
2607 }
2608
2609 let mut possible_relative_paths = table.data().par_iter()
2611 .filter_map(|row| {
2612
2613 if !field.is_filename(None) || (
2615 field.is_filename(None) && (
2616 field.filename_relative_path(None).is_none() ||
2617 field.filename_relative_path(None).unwrap().is_empty()
2618 )
2619 ) || (
2620
2621 (table.table_name() == "advisors_tables" && field.name() == "advisor_icon_path") ||
2623
2624 (table.table_name() == "campaign_post_battle_captive_options_tables" && field.name() == "icon_path") ||
2626
2627 (table.table_name() == "narrative_viewer_tabs_tables" && field.name() == "image_path") ||
2629
2630 (table.table_name() == "technology_ui_groups_tables" && field.name() == "optional_background_image")
2632 ) {
2633
2634 let mut data = row[column].data_to_string().to_lowercase().replace("\\", "/");
2639
2640 if data.starts_with("/") {
2642 if data.len() > 1 {
2643 data = data[1..].to_owned();
2644 } else {
2645 data = String::new();
2646 }
2647 }
2648
2649 if !data.is_empty() && !data.ends_with("/") &&
2650 data != "." &&
2651 data != "x" &&
2652 data != "false" &&
2653 data != "building_placeholder" &&
2654 data != "placehoder.png" &&
2655 data != "placeholder" &&
2656 data != "placeholder.tga" &&
2657 data != "placeholder.png" && (
2658 possible_icon ||
2659 data.ends_with(".png") || data.ends_with(".tga")
2660 ) {
2661
2662 let possible_paths = image_paths.iter()
2663
2664 .filter(|x| {
2666 if table.table_name() == "aide_de_camp_speeches_tables" && field.name() == "icon_name" {
2667 x.starts_with("ui/battle ui/adc_icons/")
2668 } else if table.table_name() == "agent_string_subculture_overrides_tables" && field.name() == "icon_path" {
2669 x.starts_with("ui/campaign ui/agents/icons/")
2670 } else if table.table_name() == "ancillary_types_tables" && field.name() == "ui_icon" {
2671 x.starts_with("ui/portraits/ancillaries/")
2672 } else if table.table_name() == "battlefield_building_categories_tables" && field.name() == "icon_path" {
2673 x.starts_with("ui/battle ui/building icons/")
2674 } else if table.table_name() == "bonus_value_uis_tables" && field.name() == "icon" {
2675 x.starts_with("ui/campaign ui/effect_bundles/")
2676 } else if table.table_name() == "building_culture_variants_tables" && field.name() == "icon" {
2677 x.starts_with("ui/buildings/icons/")
2678 } else if table.table_name() == "campaign_payload_ui_details_tables" && field.name() == "icon" {
2679 x.starts_with("ui/campaign ui/effect_bundles/")
2680 } else if table.table_name() == "campaign_post_battle_captive_options_tables" && field.name() == "icon_path" {
2681 x.starts_with("ui/campaign ui/captive_option_icons/")
2682 } else if table.table_name() == "capture_point_types_tables" && field.name() == "icon_name" {
2683 x.starts_with("ui/battle ui/capture_point_icons/")
2684 } else if table.table_name() == "character_skills_tables" && field.name() == "image_path" {
2685 x.starts_with("ui/campaign ui/skills/")
2686 } else if table.table_name() == "character_traits_tables" && field.name() == "icon_custom" {
2687 x.starts_with("ui/campaign ui/effect_bundles/")
2688
2689 } else if table.table_name() == "cursors_tables" && field.name() == "image" {
2691 !x.starts_with(&(data.to_owned() + "_"))
2692 } else if table.table_name() == "dilemmas_tables" && field.name() == "ui_image" {
2693 x.starts_with("ui/eventpics/")
2694 } else if table.table_name() == "effect_bundles_tables" && field.name() == "ui_icon" {
2695 x.starts_with("ui/campaign ui/effect_bundles/")
2696 } else if table.table_name() == "effects_tables" && (field.name() == "icon" || field.name() == "icon_negative") {
2697 x.starts_with("ui/campaign ui/effect_bundles/")
2698 } else if table.table_name() == "faction_groups_tables" && field.name() == "ui_icon" {
2699 x.starts_with("ui/campaign ui/effect_bundles/")
2700 } else if table.table_name() == "incidents_tables" && field.name() == "ui_image" {
2701 x.starts_with("ui/eventpics/")
2702 } else if table.table_name() == "message_event_strings_tables" && field.name() == "image" {
2703 x.starts_with("ui/eventpics/")
2704 } else if table.table_name() == "missions_tables" && field.name() == "ui_icon" {
2705 x.starts_with("ui/campaign ui/message_icons/")
2706
2707 } else if table.table_name() == "missions_tables" && field.name() == "ui_image" {
2709 x.starts_with("ui/eventpics/") && x.ends_with(&(data.to_owned() + ".png"))
2710 } else if table.table_name() == "pooled_resources_tables" && field.name() == "optional_icon_path" {
2711 x.starts_with("ui/skins/")
2712 } else if table.table_name() == "projectile_shot_type_enum_tables" && field.name() == "icon_name" {
2713 x.starts_with("ui/battle ui/ability_icons/")
2714 } else if table.table_name() == "religions_tables" && field.name() == "ui_icon_path" {
2715 x.starts_with("ui/campaign ui/religion_icons/")
2716 } else if table.table_name() == "special_ability_phases_tables" && field.name() == "ticker_icon" {
2717 x.starts_with("ui/battle ui/ability_icons/")
2718 } else if table.table_name() == "technologies_tables" && field.name() == "icon_name" {
2719 x.starts_with("ui/campaign ui/technologies/")
2720 } else if table.table_name() == "technologies_tables" && field.name() == "info_pic" {
2721 x.starts_with("ui/eventpics/")
2722 } else if table.table_name() == "trait_categories_tables" && field.name() == "icon_path" {
2723 x.starts_with("ui/campaign ui/effect_bundles/")
2724 } else if table.table_name() == "ui_unit_groupings_tables" && field.name() == "icon" {
2725 x.starts_with("ui/common ui/unit_category_icons/")
2726 } else if table.table_name() == "victory_types_tables" && field.name() == "icon" {
2727 x.starts_with("ui/campaign ui/victory_type_icons/")
2728
2729 } else if table.table_name() == "videos_tables" && field.name() == "video_name" {
2731 x.starts_with("movies/")
2732 } else {
2733 true
2734 }
2735 })
2736
2737 .filter(|x| if !data.ends_with('_') {
2742 if !data.contains("/") {
2743 if !data.contains('.') {
2744 x.contains(&("/".to_owned() + &data + "."))
2745 } else {
2746 x.contains(&("/".to_owned() + &data))
2747 }
2748 } else {
2749 x.contains(&data)
2750 }
2751 } else {
2752 false
2753 })
2754
2755 .filter_map(|x| x.rfind(&data).map(|pos| (x, pos)))
2757 .map(|(x, pos)| x[..pos].to_owned() + &x[pos..].replacen(&data, "%", 1))
2758 .collect::<Vec<_>>();
2759
2760
2761 if !possible_paths.is_empty() {
2762 return Some(possible_paths)
2763 }
2764 }
2765 }
2766
2767 None
2768 })
2769 .flatten()
2770 .collect::<HashSet<String>>();
2771
2772 let mut possible_video = false;
2778 if low_name.contains("video") {
2779 possible_video = true;
2780 }
2781
2782 possible_relative_paths.extend(
2783 table.data().par_iter().filter_map(|row| {
2784
2785 if !field.is_filename(None) || (
2787 field.is_filename(None) && (
2788 field.filename_relative_path(None).is_none() ||
2789 field.filename_relative_path(None).unwrap().is_empty()
2790 )
2791 ) || (
2792
2793 table.table_name() == "videos_tables" && field.name() == "video_name"
2795 ) {
2796
2797 let mut data = row[column].data_to_string().to_lowercase().replace("\\", "/");
2798
2799 if data.starts_with("/") {
2801 if data.len() > 1 {
2802 data = data[1..].to_owned();
2803 } else {
2804 data = String::new();
2805 }
2806 }
2807
2808 if !data.is_empty() && (
2809 possible_video ||
2810 data.ends_with(".ca_vp8")
2811 ) {
2812
2813 let possible_paths = video_paths.iter()
2814 .filter(|x| {
2815 if table.table_name() == "videos_tables" && field.name() == "video_name" {
2816 x.starts_with("movies/")
2817 } else {
2818 true
2819 }
2820 })
2821 .filter(|x| if !data.contains('.') {
2825 x.contains(&("/".to_owned() + &data + "."))
2826 } else {
2827 x.contains(&("/".to_owned() + &data))
2828 })
2829
2830 .filter_map(|x| x.rfind(&data).map(|pos| (x, pos)))
2832 .map(|(x, pos)| x[..pos].to_owned() + &x[pos..].replacen(&data, "%", 1))
2833 .collect::<Vec<_>>();
2834
2835
2836 if !possible_paths.is_empty() {
2837 return Some(possible_paths)
2838 }
2839 }
2840 }
2841
2842 None
2843 })
2844 .flatten()
2845 .collect::<HashSet<String>>()
2846 );
2847
2848 if !possible_relative_paths.is_empty() && (possible_relative_paths.len() > 1 || (possible_relative_paths.len() == 1 && possible_relative_paths.iter().collect::<Vec<_>>()[0] != "%")) {
2850 info!("Checking table {}, field {} ...", table.table_name(), field.name());
2851 dbg!(&possible_relative_paths);
2852 }
2853
2854 if (table.table_name() == "models_building_tables" && field.name() == "logic_file") ||
2858 (table.table_name() == "models_sieges_tables" && (field.name() == "model_file" || field.name() == "logic_file" || field.name() == "collision_file")) ||
2859 (table.table_name() == "models_deployables_tables" && (field.name() == "model_file" || field.name() == "logic_file" || field.name() == "collision_file")) {
2860 possible_relative_paths.clear();
2861 possible_relative_paths.insert("%".to_owned());
2862 }
2863
2864 if (table.table_name() == "ui_mercenary_recruitment_infos_tables" && field.name() == "hire_button_icon_path") ||
2866 (table.table_name() == "battles_tables" && (field.name() == "specification" || field.name() == "battle_environment_audio")) ||
2867 (table.table_name() == "factions_tables" && field.name() == "key") ||
2868 (table.table_name() == "frontend_faction_leaders_tables" && field.name() == "key") {
2869 let mut patch = HashMap::new();
2870 patch.insert("is_filename".to_owned(), "false".to_owned());
2871
2872 match new_patches.get_mut(table.table_name()) {
2873 Some(patches) => match patches.get_mut(field.name()) {
2874 Some(patches) => patches.extend(patch),
2875 None => { patches.insert(field.name().to_owned(), patch); }
2876 },
2877 None => {
2878 let mut table_patch = HashMap::new();
2879 table_patch.insert(field.name().to_owned(), patch);
2880 new_patches.insert(table.table_name().to_string(), table_patch);
2881 }
2882 }
2883 }
2884
2885 if !possible_relative_paths.is_empty() {
2887 let mut possible_relative_paths = possible_relative_paths.iter().collect::<Vec<_>>();
2888 possible_relative_paths.sort();
2889
2890 let mut patch = HashMap::new();
2891 if !field.is_filename(None) {
2892 patch.insert("is_filename".to_owned(), "true".to_owned());
2893 }
2894
2895 if possible_relative_paths.len() > 1 || (
2897 (
2898 possible_relative_paths.len() == 1 &&
2899 possible_relative_paths[0].contains('%') &&
2900 possible_relative_paths[0] != "%"
2901 ) || (
2902 possible_relative_paths[0] == "%" &&
2903 field.filename_relative_path(None).is_some() &&
2904 !field.filename_relative_path(None).unwrap().is_empty()
2905 )
2906 ) {
2907 patch.insert("filename_relative_path".to_owned(), possible_relative_paths.into_iter().join(";"));
2908 }
2909
2910 if !patch.is_empty() {
2912 match new_patches.get_mut(table.table_name()) {
2913 Some(patches) => match patches.get_mut(field.name()) {
2914 Some(patches) => patches.extend(patch),
2915 None => { patches.insert(field.name().to_owned(), patch); }
2916 },
2917 None => {
2918 let mut table_patch = HashMap::new();
2919 table_patch.insert(field.name().to_owned(), patch);
2920 new_patches.insert(table.table_name().to_string(), table_patch);
2921 }
2922 }
2923 }
2924 }
2925 }
2943 FieldType::I64 |
2944 FieldType::OptionalI64 => {
2945 }
2963 _ => continue
2964 }
2965 }
2966 }
2967
2968 Schema::add_patches_to_patch_set(current_patches, &new_patches);
2969
2970 Ok(())
2971 }
2972
2973 #[allow(clippy::too_many_arguments)]
2977 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>)> {
2978 let mut added_paths = vec![];
2979
2980 let pack = match pack_key {
2982 Some(key) => packs.get_mut(key).ok_or_else(|| RLibError::NoPacksProvided)?,
2983 None => packs.values_mut().next().ok_or_else(|| RLibError::NoPacksProvided)?,
2984 };
2985
2986 for tile_map in &tile_maps {
2988 added_paths.append(&mut pack.insert_folder(tile_map, "terrain/battles", &None, &None, true)?);
2989 }
2990
2991 for (tile, subpath) in &tiles {
2993
2994 let (internal_path, needs_tile_database) = if subpath.is_empty() {
2995 ("terrain/tiles/battle".to_owned(), false)
2996 } else {
2997 (format!("terrain/tiles/battle/{}", subpath.replace('\\', "/")), true)
2998 };
2999 added_paths.append(&mut pack.insert_folder(tile, &internal_path, &None, &None, true)?);
3000
3001 if needs_tile_database {
3003
3004 let subpath_len = subpath.replace('\\', "/").split('/').count();
3006 let mut tile_database = tile.to_path_buf();
3007
3008 (0..=subpath_len).for_each(|_| {
3009 tile_database.pop();
3010 });
3011
3012 let file_name = format!("{}_{}.bin", subpath.replace('/', "_"), tile.file_name().unwrap().to_string_lossy());
3013 tile_database.push(format!("_tile_database/TILES/{file_name}"));
3014 let tile_database_path = format!("terrain/tiles/battle/_tile_database/TILES/{file_name}");
3015
3016 added_paths.push(pack.insert_file(&tile_database, &tile_database_path, &None)?.unwrap());
3017 }
3018 }
3019
3020 let (paths_to_delete, paths_to_add) = pack.optimize(Some(added_paths.clone()), self, schema, game, &options)?;
3021
3022 let paths_to_delete = paths_to_delete.iter()
3023 .map(|path| ContainerPath::File(path.to_string()))
3024 .collect::<Vec<_>>();
3025
3026 added_paths.extend(paths_to_add.into_iter()
3027 .map(|path| ContainerPath::File(path.to_string()))
3028 .collect::<Vec<_>>());
3029
3030 Ok((added_paths, paths_to_delete))
3031 }
3032
3033 #[allow(clippy::too_many_arguments)]
3037 pub fn build_starpos_pre(&self, packs: &mut BTreeMap<String, Pack>, pack_key: Option<&str>, game: &GameInfo, game_path: &Path, campaign_id: &str, process_hlp_spd_data: bool, sub_start_pos: &str) -> Result<()> {
3038
3039 let map_names = if process_hlp_spd_data {
3041 self.db_values_from_table_name_and_column_name_for_value(Some(packs), "campaigns_tables", "campaign_name", "map_name", true, true)
3042 } else {
3043 HashMap::new()
3044 };
3045
3046 let pack_file = match pack_key {
3048 Some(key) => packs.get_mut(key).ok_or_else(|| RLibError::NoPacksProvided)?,
3049 None => packs.values_mut().next().ok_or_else(|| RLibError::NoPacksProvided)?,
3050 };
3051 let pack_name = pack_file.disk_file_name();
3052 if pack_name.is_empty() {
3053 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()));
3054 }
3055
3056 if campaign_id.is_empty() {
3057 return Err(RLibError::BuildStartposError("campaign_id not provided.".to_owned()));
3058 }
3059
3060 let process_hlp_spd_data_string = if process_hlp_spd_data {
3061 String::from("process_campaign_ai_map_data;")
3062 } else {
3063 String::new()
3064 };
3065
3066 let extra_folders = "add_working_directory assembly_kit\\working_data;";
3069 let mut user_script_contents = if game.key() == KEY_ATTILA || game.key() == KEY_THRONES_OF_BRITANNIA { extra_folders.to_owned() } else { String::new() };
3070
3071 user_script_contents.push_str(&format!("
3072 mod {pack_name};
3073 process_campaign_startpos {campaign_id} {sub_start_pos};
3074 {process_hlp_spd_data_string}
3075 quit_after_campaign_processing;"
3076 ));
3077
3078 let game_data_path = game.data_path(game_path)?;
3080 if !game_path.is_dir() {
3081 return Err(RLibError::BuildStartposError("Game path incorrect. Fix it in the settings and try again.".to_owned()));
3082 }
3083
3084 if !PathBuf::from(pack_file.disk_file_path()).starts_with(&game_data_path) {
3085 return Err(RLibError::BuildStartposError("The Pack needs to be in /data. Install it there and try again.".to_owned()));
3086 }
3087
3088 if GAMES_NEEDING_VICTORY_OBJECTIVES.contains(&game.key()) {
3090 let mut game_campaign_path = game_data_path.to_path_buf();
3091 game_campaign_path.push(campaign_id);
3092 DirBuilder::new().recursive(true).create(&game_campaign_path)?;
3093
3094 game_campaign_path.push(VICTORY_OBJECTIVES_EXTRACTED_FILE_NAME);
3095 pack_file.extract(ContainerPath::File(VICTORY_OBJECTIVES_FILE_NAME.to_owned()), &game_campaign_path, false, &None, true, false, &None)?;
3096 }
3097
3098 let config_path = game.config_path(game_path).ok_or(RLibError::BuildStartposError("Error getting the game's config path.".to_owned()))?;
3099 let scripts_path = config_path.join("scripts");
3100 DirBuilder::new().recursive(true).create(&scripts_path)?;
3101
3102 if game.key() != KEY_ROME_2 {
3106
3107 let uspa = scripts_path.join(USER_SCRIPT_FILE_NAME);
3109 let uspb = scripts_path.join(USER_SCRIPT_FILE_NAME.to_owned() + ".bak");
3110
3111 if uspa.is_file() {
3112 std::fs::copy(&uspa, uspb)?;
3113 }
3114
3115 let mut file = BufWriter::new(File::create(uspa)?);
3116
3117 if *game.raw_db_version() < 2 {
3119 file.write_string_u16(&user_script_contents)?;
3120 } else {
3121 file.write_all(user_script_contents.as_bytes())?;
3122 }
3123
3124 file.flush()?;
3125 }
3126
3127 if game.key() != KEY_THRONES_OF_BRITANNIA &&
3132 game.key() != KEY_ATTILA &&
3133 game.key() != KEY_SHOGUN_2 {
3134
3135 let sub_start_pos_suffix = if sub_start_pos.is_empty() {
3136 String::new()
3137 } else {
3138 format!("_{sub_start_pos}")
3139 };
3140
3141 let starpos_path = game_data_path.join(format!("campaigns/{campaign_id}/startpos{sub_start_pos_suffix}.esf"));
3142 if starpos_path.is_file() {
3143 let starpos_path_bak = game_data_path.join(format!("campaigns/{campaign_id}/startpos{sub_start_pos_suffix}.esf.bak"));
3144 std::fs::copy(&starpos_path, starpos_path_bak)?;
3145 std::fs::remove_file(starpos_path)?;
3146 }
3147 }
3148
3149 if process_hlp_spd_data {
3151 if let Some(map_name) = map_names.get(campaign_id) {
3152 match game.key() {
3153
3154 KEY_PHARAOH_DYNASTIES |
3158 KEY_PHARAOH |
3159 KEY_WARHAMMER_3 |
3160 KEY_TROY |
3161 KEY_THREE_KINGDOMS |
3162 KEY_WARHAMMER_2 |
3163 KEY_WARHAMMER => {
3164 let hlp_folder_path = game_data_path.join(format!("campaign_maps/{map_name}"));
3165 if !hlp_folder_path.is_dir() {
3166 DirBuilder::new().recursive(true).create(&hlp_folder_path)?;
3167 }
3168
3169 let hlp_path = game_data_path.join(format!("campaign_maps/{map_name}/hlp_data.esf"));
3170 if hlp_path.is_file() {
3171 let hlp_path_bak = game_data_path.join(format!("campaign_maps/{map_name}/hlp_data.esf.bak"));
3172 std::fs::copy(&hlp_path, hlp_path_bak)?;
3173 std::fs::remove_file(hlp_path)?;
3174 }
3175 },
3176
3177 KEY_THRONES_OF_BRITANNIA |
3185 KEY_ATTILA => {
3186 let folder_path = config_path.join(format!("maps/campaign_maps/{map_name}"));
3187
3188 let (sender, receiver) = channel::<bool>();
3189 let join = thread::spawn(move || {
3190 loop {
3191 match receiver.try_recv() {
3192 Ok(stop) => if stop {
3193 break;
3194 }
3195 Err(_) => {
3196 if !folder_path.is_dir() {
3197 let _ = DirBuilder::new().recursive(true).create(&folder_path);
3198 }
3199
3200 thread::sleep(Duration::from_millis(100));
3201 }
3202 }
3203 }
3204 });
3205
3206 *START_POS_WORKAROUND_THREAD.write().unwrap() = Some(vec![(sender, join)]);
3207 },
3208
3209 KEY_ROME_2 => {
3214 let hlp_folder = game_data_path.join(format!("campaign_maps/{map_name}/"));
3215 if hlp_folder.is_dir() {
3216 let _ = DirBuilder::new().recursive(true).create(&hlp_folder);
3217 }
3218
3219 let hlp_path = hlp_folder.join("hlp_data.esf");
3220 if hlp_path.is_file() {
3221 let hlp_path_bak = game_data_path.join(format!("campaign_maps/{map_name}/hlp_data.esf.bak"));
3222 std::fs::copy(&hlp_path, hlp_path_bak)?;
3223 std::fs::remove_file(hlp_path)?;
3224 }
3225
3226 }
3227 KEY_SHOGUN_2 => return Err(RLibError::BuildStartposError("Unsupported... yet. If you want to test support for this game, let me know.".to_owned())),
3228 KEY_NAPOLEON => return Err(RLibError::BuildStartposError("Unsupported... yet. If you want to test support for this game, let me know.".to_owned())),
3229 KEY_EMPIRE => return Err(RLibError::BuildStartposError("Unsupported... yet. If you want to test support for this game, let me know.".to_owned())),
3230 _ => return Err(RLibError::BuildStartposError("How the fuck did you trigger this?".to_owned())),
3231 }
3232
3233 if game.key() != KEY_THRONES_OF_BRITANNIA &&
3235 game.key() != KEY_ATTILA &&
3236 game.key() != KEY_ROME_2 &&
3237 game.key() != KEY_SHOGUN_2 &&
3238 game.key() != KEY_NAPOLEON &&
3239 game.key() != KEY_EMPIRE {
3240
3241 let spd_path = game_data_path.join(format!("campaign_maps/{map_name}/spd_data.esf"));
3242 if spd_path.is_file() {
3243 let spd_path_bak = game_data_path.join(format!("campaign_maps/{map_name}/spd_data.esf.bak"));
3244 std::fs::copy(&spd_path, spd_path_bak)?;
3245 std::fs::remove_file(spd_path)?;
3246 }
3247 }
3248 }
3249 }
3250
3251 if game.key() == KEY_THREE_KINGDOMS {
3253 let exe_path = game.executable_path(game_path).ok_or_else(|| RLibError::BuildStartposError("Game exe path not found.".to_owned()))?;
3254 let exe_name = exe_path.file_name().ok_or_else(|| RLibError::BuildStartposError("Game exe name not found.".to_owned()))?.to_string_lossy();
3255
3256 let mut command = Command::new("cmd");
3258 command.arg("/C");
3259 command.arg("start");
3260 command.arg("/wait");
3261 command.arg("/d");
3262 command.arg(game_path.to_string_lossy().replace('\\', "/"));
3263 command.arg(exe_name.to_string());
3264 command.arg("temp_file.txt;");
3265
3266 let _ = command.output()?;
3267
3268 let uspa = scripts_path.join(USER_SCRIPT_FILE_NAME);
3270 let uspb = scripts_path.join(USER_SCRIPT_FILE_NAME.to_owned() + ".bak");
3271 if uspb.is_file() {
3272 std::fs::copy(uspb, uspa)?;
3273 }
3274
3275 else if uspa.is_file() {
3277 std::fs::remove_file(uspa)?;
3278 }
3279
3280 } else if game.key() == KEY_ROME_2 {
3282 let exe_path = game.executable_path(game_path).ok_or_else(|| RLibError::BuildStartposError("Game exe path not found.".to_owned()))?;
3283 let exe_name = exe_path.file_name().ok_or_else(|| RLibError::BuildStartposError("Game exe name not found.".to_owned()))?.to_string_lossy();
3284
3285 let mut command = Command::new("cmd");
3287 command.arg("/C");
3288 command.arg("start");
3289 command.arg("/d");
3290 command.arg(game_path.to_string_lossy().replace('\\', "/"));
3291 command.arg(exe_name.to_string());
3292 command.arg("temp_file.txt;");
3293
3294 #[cfg(target_os = "windows")] {
3296 use std::os::windows::process::CommandExt;
3297
3298 command.raw_arg(extra_folders);
3300 command.raw_arg(user_script_contents.replace("\n", " "));
3301 }
3302
3303 command.spawn()?;
3304 } else {
3305 match game.game_launch_command(game_path) {
3306 Ok(command) => { let _ = open::that(command); },
3307 _ => return Err(RLibError::BuildStartposError("The currently selected game cannot be launched from Steam.".to_owned())),
3308 }
3309 }
3310
3311 Ok(())
3312 }
3313
3314 #[allow(clippy::too_many_arguments)]
3321 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>> {
3322
3323 let map_names = if process_hlp_spd_data {
3325 self.db_values_from_table_name_and_column_name_for_value(Some(packs), "campaigns_tables", "campaign_name", "map_name", true, true)
3326 } else {
3327 HashMap::new()
3328 };
3329
3330 let pack_file = match pack_key {
3332 Some(key) => packs.get_mut(key).ok_or_else(|| RLibError::NoPacksProvided)?,
3333 None => packs.values_mut().next().ok_or_else(|| RLibError::NoPacksProvided)?,
3334 };
3335
3336 let mut startpos_failed = false;
3337 let mut sub_startpos_failed = vec![];
3338 let mut hlp_failed = false;
3339 let mut spd_failed = false;
3340
3341 if let Some(data) = START_POS_WORKAROUND_THREAD.write().unwrap().as_mut() {
3343 let (sender, handle) = data.remove(0);
3344 let _ = sender.send(true);
3345 let _ = handle.join();
3346 }
3347
3348 *START_POS_WORKAROUND_THREAD.write().unwrap() = None;
3349
3350 if !game_path.is_dir() {
3351 return Err(RLibError::BuildStartposError("Game path incorrect. Fix it in the settings and try again.".to_owned()));
3352 }
3353
3354 let game_data_path = game.data_path(game_path)?;
3355
3356 if GAMES_NEEDING_VICTORY_OBJECTIVES.contains(&game.key()) {
3358
3359 let mut game_campaign_path = game_data_path.to_path_buf();
3361 game_campaign_path.push(campaign_id);
3362 if game_campaign_path.is_dir() {
3363 let _ = std::fs::remove_dir_all(game_campaign_path);
3364 }
3365 }
3366
3367 let config_path = game.config_path(game_path).ok_or(RLibError::BuildStartposError("Error getting the game's config path.".to_owned()))?;
3368 let scripts_path = config_path.join("scripts");
3369 if !scripts_path.is_dir() {
3370 DirBuilder::new().recursive(true).create(&scripts_path)?;
3371 }
3372
3373 let uspa = scripts_path.join(USER_SCRIPT_FILE_NAME);
3375 let uspb = scripts_path.join(USER_SCRIPT_FILE_NAME.to_owned() + ".bak");
3376 if uspb.is_file() {
3377 std::fs::copy(uspb, uspa)?;
3378 }
3379
3380 else if uspa.is_file() {
3382 std::fs::remove_file(uspa)?;
3383 }
3384
3385 let mut added_paths = vec![];
3386
3387 let starpos_paths = match game.key() {
3389 KEY_PHARAOH_DYNASTIES |
3390 KEY_PHARAOH |
3391 KEY_WARHAMMER_3 |
3392 KEY_TROY |
3393 KEY_THREE_KINGDOMS |
3394 KEY_WARHAMMER_2 |
3395 KEY_WARHAMMER => {
3396 if sub_start_pos.is_empty() {
3397 vec![game_data_path.join(format!("campaigns/{campaign_id}/startpos.esf"))]
3398 } else {
3399 let mut paths = vec![];
3400 for sub in sub_start_pos {
3401 paths.push(game_data_path.join(format!("campaigns/{campaign_id}/startpos_{sub}.esf")));
3402
3403 }
3404 paths
3405 }
3406 }
3407 KEY_THRONES_OF_BRITANNIA |
3408 KEY_ATTILA => vec![config_path.join(format!("maps/campaigns/{campaign_id}/startpos.esf"))],
3409
3410 KEY_ROME_2 => {
3412 match asskit_path {
3413 Some(asskit_path) => {
3414 if !asskit_path.is_dir() {
3415 return Err(RLibError::BuildStartposError("Assembly Kit path is not a valid folder.".to_owned()));
3416 }
3417
3418 vec![asskit_path.join(format!("working_data/campaigns/{campaign_id}/startpos.esf"))]
3419 },
3420 None => return Err(RLibError::BuildStartposError("Assembly Kit path not provided.".to_owned())),
3421 }
3422 },
3423
3424 KEY_SHOGUN_2 |
3427 KEY_NAPOLEON |
3428 KEY_EMPIRE => vec![game_data_path.join(format!("campaigns/{campaign_id}/startpos.esf"))],
3429 _ => return Err(RLibError::BuildStartposError("How the fuck did you trigger this?".to_owned())),
3430 };
3431
3432 let starpos_paths_pack = if sub_start_pos.is_empty() {
3433 vec![format!("campaigns/{}/startpos.esf", campaign_id)]
3434 } else {
3435 let mut paths = vec![];
3436 for sub in sub_start_pos {
3437 paths.push(format!("campaigns/{campaign_id}/startpos_{sub}.esf"));
3438 }
3439 paths
3440 };
3441
3442 if !cleanup_mode {
3443 for (index, starpos_path) in starpos_paths.iter().enumerate() {
3444 if !starpos_path.is_file() {
3445 if sub_start_pos.is_empty() {
3446 startpos_failed = true;
3447 } else {
3448 sub_startpos_failed.push(sub_start_pos[index].to_owned());
3449 }
3450 } else {
3451
3452 let mut rfile = RFile::new_from_file_path(starpos_path)?;
3453 rfile.set_path_in_container_raw(&starpos_paths_pack[index]);
3454 rfile.load()?;
3455 rfile.guess_file_type()?;
3456
3457 added_paths.push(pack_file.insert(rfile).map(|x| x.unwrap())?);
3458 }
3459 }
3460 }
3461
3462 if game.key() != KEY_THRONES_OF_BRITANNIA &&
3468 game.key() != KEY_ATTILA &&
3469 game.key() != KEY_SHOGUN_2 {
3470
3471 for starpos_path in &starpos_paths {
3472 let file_name = starpos_path.file_name().unwrap().to_string_lossy().to_string();
3473 let file_name_bak = file_name + ".bak";
3474
3475 let mut starpos_path_bak = starpos_path.to_path_buf();
3476 starpos_path_bak.set_file_name(file_name_bak);
3477
3478 if starpos_path_bak.is_file() {
3479 std::fs::copy(&starpos_path_bak, starpos_path)?;
3480 std::fs::remove_file(starpos_path_bak)?;
3481 }
3482 }
3483 }
3484
3485 if game.key() == KEY_SHOGUN_2 {
3487 for starpos_path in &starpos_paths {
3488 if starpos_path.is_file() {
3489 std::fs::remove_file(starpos_path)?;
3490 }
3491 }
3492 }
3493
3494 if process_hlp_spd_data {
3496 if let Some(map_name) = map_names.get(campaign_id) {
3497
3498 let hlp_path = match game.key() {
3500 KEY_PHARAOH_DYNASTIES |
3501 KEY_PHARAOH |
3502 KEY_WARHAMMER_3 |
3503 KEY_TROY |
3504 KEY_THREE_KINGDOMS |
3505 KEY_WARHAMMER_2 |
3506 KEY_WARHAMMER => game_data_path.join(format!("campaign_maps/{map_name}/hlp_data.esf")),
3507 KEY_THRONES_OF_BRITANNIA |
3508 KEY_ATTILA => config_path.join(format!("maps/campaign_maps/{map_name}/hlp_data.esf")),
3509 KEY_ROME_2 => game_data_path.join(format!("campaign_maps/{map_name}/hlp_data.esf")),
3510 _ => return Err(RLibError::BuildStartposError("How the fuck did you trigger this?".to_owned())),
3511 };
3512
3513 let hlp_path_pack = format!("campaign_maps/{map_name}/hlp_data.esf");
3514
3515 if !cleanup_mode {
3516
3517 if !hlp_path.is_file() {
3518 hlp_failed = true;
3519 } else {
3520
3521 let mut rfile_hlp = RFile::new_from_file_path(&hlp_path)?;
3522 rfile_hlp.set_path_in_container_raw(&hlp_path_pack);
3523 rfile_hlp.load()?;
3524 rfile_hlp.guess_file_type()?;
3525
3526 added_paths.push(pack_file.insert(rfile_hlp).map(|x| x.unwrap())?);
3527 }
3528 }
3529
3530 if game.key() != KEY_THRONES_OF_BRITANNIA &&
3532 game.key() != KEY_ATTILA {
3533
3534 let hlp_path_bak = game_data_path.join(format!("campaign_maps/{map_name}/hlp_data.esf.bak"));
3535
3536 if hlp_path_bak.is_file() {
3537 std::fs::copy(&hlp_path_bak, hlp_path)?;
3538 std::fs::remove_file(hlp_path_bak)?;
3539 }
3540 }
3541
3542 if game.key() != KEY_THRONES_OF_BRITANNIA &&
3544 game.key() != KEY_ATTILA &&
3545 game.key() != KEY_ROME_2 {
3546
3547 let spd_path = game_data_path.join(format!("campaign_maps/{map_name}/spd_data.esf"));
3548 let spd_path_pack = format!("campaign_maps/{map_name}/spd_data.esf");
3549
3550 if !cleanup_mode {
3551
3552 if !spd_path.is_file() {
3553 spd_failed = true;
3554 } else {
3555
3556 let mut rfile_spd = RFile::new_from_file_path(&spd_path)?;
3557 rfile_spd.set_path_in_container_raw(&spd_path_pack);
3558 rfile_spd.load()?;
3559 rfile_spd.guess_file_type()?;
3560
3561 added_paths.push(pack_file.insert(rfile_spd).map(|x| x.unwrap())?);
3562 }
3563 }
3564
3565 let spd_path_bak = game_data_path.join(format!("campaign_maps/{map_name}/spd_data.esf.bak"));
3566 if spd_path_bak.is_file() {
3567 std::fs::copy(&spd_path_bak, spd_path)?;
3568 std::fs::remove_file(spd_path_bak)?;
3569 }
3570 }
3571 }
3572 }
3573
3574 let mut error = String::new();
3575 if startpos_failed || (!sub_start_pos.is_empty() && !sub_startpos_failed.is_empty()) || hlp_failed || spd_failed {
3576 error.push_str("<p>One or more files failed to generate:</p><ul>")
3577 }
3578 if startpos_failed {
3579 error.push_str("<li>Startpos file failed to generate.</li>");
3580 }
3581
3582 for sub_failed in &sub_startpos_failed {
3583 error.push_str(&format!("<li>\"{sub_failed}\" Startpos file failed to generate.</li>"));
3584 }
3585
3586 if hlp_failed {
3587 error.push_str("<li>HLP file failed to generate.</li>");
3588 }
3589
3590 if spd_failed {
3591 error.push_str("<li>SPD file failed to generate.</li>");
3592 }
3593
3594 if startpos_failed || hlp_failed || spd_failed {
3595 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>")
3596 }
3597
3598 if error.is_empty() {
3599 Ok(added_paths)
3600 } else {
3601 Err(RLibError::BuildStartposError(error))
3602 }
3603 }
3604
3605 pub fn import_from_ak(&self, table_name: &str, schema: &Schema) -> Result<DB> {
3609 let definition = if let Some(definitions) = schema.definitions_by_table_name_cloned(table_name) {
3610 if !definitions.is_empty() {
3611 definitions[0].clone()
3612 } else {
3613 return Err(RLibError::DecodingDBNoDefinitionsFound)
3614 }
3615 } else {
3616 return Err(RLibError::DecodingDBNoDefinitionsFound)
3617 };
3618
3619 if let Some(ak_file) = self.asskit_only_db_tables().get(table_name) {
3621 let mut real_table = ak_file.clone();
3622 real_table.set_definition(&definition);
3623 Ok(real_table)
3624 } else {
3625 Err(RLibError::AssemblyKitTableNotFound(table_name.to_owned()))
3626 }
3627 }
3628
3629 pub fn insert_loc_as_vanilla_loc(&mut self, rfile: RFile) {
3637 let path = rfile.path_in_container_raw().to_owned();
3638 self.vanilla_files.insert(path.to_owned(), rfile);
3639 self.vanilla_locs.insert(path);
3640 }
3641
3642 pub fn add_recursive_lookups_to_definition(&self, schema: &Schema, definition: &mut Definition, table_name: &str) {
3646 let schema_patches = definition.patches().clone();
3647
3648 for field in definition.fields_mut().iter_mut() {
3649
3650 if let Some(lookup_data_old) = field.lookup(Some(&schema_patches)) {
3652 let mut lookup_data = vec![];
3653
3654 if !lookup_data_old.is_empty() {
3656
3657 let table_name = if let Some(table_name) = table_name.strip_suffix("_tables") {
3658 table_name.to_owned()
3659 } else {
3660 table_name.to_owned()
3661 };
3662
3663 for lookup_data_old in &lookup_data_old {
3664 let lookup_string = format!("{}#{}#{}", table_name, field.name(), lookup_data_old);
3665 self.add_recursive_lookups(schema, &schema_patches, lookup_data_old, &mut lookup_data, &lookup_string, &table_name);
3666 }
3667
3668 }
3669
3670 if let Some((ref_table_name, ref_column)) = field.is_reference(Some(&schema_patches)) {
3672 for lookup_data_old in &lookup_data_old {
3673 let lookup_string = format!("{ref_table_name}#{ref_column}#{lookup_data_old}");
3674 self.add_recursive_lookups(schema, &schema_patches, lookup_data_old, &mut lookup_data, &lookup_string, &ref_table_name);
3675 }
3676 }
3677
3678 if !lookup_data.is_empty() {
3679 field.set_lookup(Some(lookup_data));
3680 } else {
3681 field.set_lookup(None);
3682 }
3683 }
3684 }
3685 }
3686
3687 fn add_recursive_lookups(&self,
3688 schema: &Schema,
3689 schema_patches: &HashMap<String, HashMap<String, String>>,
3690 lookup: &str,
3691 lookup_data: &mut Vec<String>,
3692 lookup_string: &str,
3693 table_name: &str
3694 ) {
3695 let mut finish_lookup = false;
3696 let table_name = table_name.to_string() + "_tables";
3697 if let Ok(ref_tables) = self.db_data(&table_name, true, true) {
3698 let candidates = ref_tables.iter()
3699 .filter_map(|rfile| rfile.decoded().ok())
3700 .filter_map(|decoded| if let RFileDecoded::DB(db) = decoded {
3701 Some(db.definition().clone())
3702 } else {
3703 None
3704 })
3705 .collect::<Vec<_>>();
3706
3707 if let Some(definition) = schema.definition_newer(&table_name, &candidates) {
3708
3709 if let Some(pos) = definition.column_position_by_name(lookup) {
3711 if let Some(field) = definition.fields_processed().get(pos) {
3712
3713 if let Some((ref_table_name, ref_column)) = field.is_reference(Some(schema_patches)) {
3715 if let Some(lookups) = field.lookup(Some(schema_patches)) {
3716 for lookup in &lookups {
3717 let lookup_string = format!("{lookup_string}:{ref_table_name}#{ref_column}#{lookup}");
3718
3719 self.add_recursive_lookups(schema, schema_patches, lookup, lookup_data, &lookup_string, &ref_table_name);
3720 }
3721 } else {
3722 finish_lookup = true;
3723 }
3724 } else {
3725 finish_lookup = true;
3726 }
3727 } else {
3728 finish_lookup = true;
3729 }
3730 }
3731
3732 else if definition.localised_fields().iter().any(|x| x.name() == lookup) {
3733 finish_lookup = true;
3734 }
3735 } else {
3736 finish_lookup = true;
3737 }
3738 }
3739
3740 if finish_lookup && !lookup_data.iter().any(|x| x == lookup_string) {
3741 lookup_data.push(lookup_string.to_owned());
3742 }
3743 }
3744}