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 self.add_recursive_lookups_to_definition(schema, &mut definition, local_table_name);
574
575 let patches = Some(definition.patches());
576 let fields_processed = definition.fields_processed();
577
578 if local_table_name == KEY_DELETES_TABLE_NAME {
581 let mut hashmap = HashMap::new();
582 let mut references = TableReferences::default();
583 *references.field_name_mut() = "table_name".to_owned();
584
585 for key in schema.definitions().keys() {
586 if key.len() > 7 {
587 let table_name = key.to_owned().drain(..key.len() - 7).collect::<String>();
588 references.data.insert(table_name, String::new());
589 }
590 }
591
592 hashmap.insert(1, references);
593 return hashmap;
594 }
595
596 fields_processed.par_iter().enumerate().filter_map(|(column, field)| {
597 match field.is_reference(patches) {
598 Some((ref ref_table, ref ref_column)) => {
599 if !ref_table.is_empty() && !ref_column.is_empty() {
600 let ref_table = format!("{ref_table}_tables");
601
602 let lookup_data = if let Some(ref data) = field.lookup_no_patch() { data.to_vec() } else { Vec::with_capacity(0) };
604 let mut references = TableReferences::default();
605 *references.field_name_mut() = field.name().to_owned();
606
607 let fake_found = self.db_reference_data_from_asskit_tables(&mut references, (&ref_table, ref_column, &lookup_data));
608 let real_found = self.db_reference_data_from_vanilla_and_modded_tables(&mut references, (&ref_table, ref_column, &lookup_data));
609
610 if fake_found && real_found.is_none() {
611 references.referenced_table_is_ak_only = true;
612 }
613
614 if let Some(ref_definition) = real_found {
615 if ref_definition.localised_fields().iter().any(|x| x.name() == ref_column) {
616 references.referenced_column_is_localised = true;
617 }
618 }
619
620 Some((column as i32, references))
621 } else { None }
622 },
623
624 None => {
626 if let Some(ref lookup_data) = field.lookup_no_patch() {
627
628 if field.is_key(patches) && fields_processed.iter().filter(|x| x.is_key(patches)).count() == 1 {
630 let ref_table = local_table_name;
631 let ref_column = field.name();
632
633 let mut references = TableReferences::default();
635 *references.field_name_mut() = field.name().to_owned();
636
637 let fake_found = self.db_reference_data_from_asskit_tables(&mut references, (ref_table, ref_column, lookup_data));
638 let real_found = self.db_reference_data_from_vanilla_and_modded_tables(&mut references, (ref_table, ref_column, lookup_data));
639
640 if fake_found && real_found.is_none() {
641 references.referenced_table_is_ak_only = true;
642 }
643
644 if let Some(ref_definition) = real_found {
645 if ref_definition.localised_fields().iter().any(|x| x.name() == ref_column) {
646 references.referenced_column_is_localised = true;
647 }
648 }
649
650 Some((column as i32, references))
651 } else { None }
652 } else { None }
653 },
654 }
655 }).collect::<HashMap<_, _>>()
656 }
657
658 pub fn load(file_path: &Path, schema: &Option<Schema>) -> Result<Self> {
660
661 let mut file_path_1 = file_path.to_path_buf();
665 let handle_1: JoinHandle<Result<(u64, String, Vec<RFile>)>> = spawn(move || {
666 file_path_1.set_extension("pak1");
667 let mut file = BufReader::new(File::open(&file_path_1)?);
668 let mut data = Vec::with_capacity(file.get_ref().metadata()?.len() as usize);
669 file.read_to_end(&mut data)?;
670
671 bitcode::deserialize(&data).map_err(From::from)
673 });
674
675 let mut file_path_2 = file_path.to_path_buf();
676 let handle_2: JoinHandle<Result<Vec<RFile>>> = spawn(move || {
677 file_path_2.set_extension("pak2");
678 let mut file = BufReader::new(File::open(&file_path_2)?);
679 let mut data = Vec::with_capacity(file.get_ref().metadata()?.len() as usize);
680 file.read_to_end(&mut data)?;
681
682 bitcode::deserialize(&data).map_err(From::from)
684 });
685
686 let mut file_path_3 = file_path.to_path_buf();
687 let handle_3: JoinHandle<Result<Pak3Cache>> = spawn(move || {
688 file_path_3.set_extension("pak3");
689 let mut file = BufReader::new(File::open(&file_path_3)?);
690 let mut data = Vec::with_capacity(file.get_ref().metadata()?.len() as usize);
691 file.read_to_end(&mut data)?;
692
693 bitcode::deserialize(&data).map_err(From::from)
695 });
696
697 let mut dependencies = Self::default();
699 let data_3 = handle_3.join().unwrap()?;
700 let data_2 = handle_2.join().unwrap()?;
701 let data_1 = handle_1.join().unwrap()?;
702
703 let mut vanilla_files: HashMap<_,_> = data_1.2.into_par_iter().map(|file| (file.path_in_container_raw().to_owned(), file)).collect();
706 vanilla_files.par_extend(data_2.into_par_iter().map(|file| (file.path_in_container_raw().to_owned(), file)));
707
708 dependencies.build_date = data_1.0;
709 dependencies.version = data_1.1;
710 dependencies.vanilla_files = vanilla_files;
711 dependencies.vanilla_tables = data_3.0;
712 dependencies.vanilla_locs = data_3.1;
713 dependencies.vanilla_folders = data_3.2;
714 dependencies.vanilla_paths = data_3.3;
715 dependencies.asskit_only_db_tables = data_3.4;
716
717 if let Some(schema) = schema {
719 let mut decode_extra_data = DecodeableExtraData::default();
720 decode_extra_data.set_schema(Some(schema));
721 let extra_data = Some(decode_extra_data);
722
723 let mut files = dependencies.vanilla_locs.iter().chain(dependencies.vanilla_tables.values().flatten()).filter_map(|path| {
724 dependencies.vanilla_files.remove(path).map(|file| (path.to_owned(), file))
725 }).collect::<Vec<_>>();
726
727 files.par_iter_mut().for_each(|(_, file)| {
728 let _ = file.decode(&extra_data, true, false);
729 });
730
731 dependencies.vanilla_files.par_extend(files);
732 }
733
734 Ok(dependencies)
735 }
736
737 pub fn save(&mut self, file_path: &Path) -> Result<()> {
739 let mut folder_path = file_path.to_owned();
740 folder_path.pop();
741 DirBuilder::new().recursive(true).create(&folder_path)?;
742
743 let mut file_path_1 = file_path.to_path_buf();
744 let mut file_path_2 = file_path.to_path_buf();
745 let mut file_path_3 = file_path.to_path_buf();
746
747 file_path_1.set_extension("pak1");
748 file_path_2.set_extension("pak2");
749 file_path_3.set_extension("pak3");
750
751 let mut file_1 = File::create(&file_path_1)?;
752 let mut file_2 = File::create(&file_path_2)?;
753 let mut file_3 = File::create(&file_path_3)?;
754
755 let mut vanilla_files_1 = self.vanilla_files.par_iter().map(|(_, b)| b.clone()).collect::<Vec<RFile>>();
759 let vanilla_files_2 = vanilla_files_1.split_off(self.vanilla_files.len() / 2);
760
761 let serialized_1: Vec<u8> = bitcode::serialize(&(&self.build_date, &self.version, &vanilla_files_1))?;
763 let serialized_2: Vec<u8> = bitcode::serialize(&vanilla_files_2)?;
764 let serialized_3: Vec<u8> = bitcode::serialize(&(&self.vanilla_tables, &self.vanilla_locs, &self.vanilla_folders, &self.vanilla_paths, &self.asskit_only_db_tables))?;
765
766 file_1.write_all(&serialized_1).map_err(RLibError::from)?;
767 file_2.write_all(&serialized_2).map_err(RLibError::from)?;
768 file_3.write_all(&serialized_3).map_err(From::from)
769 }
770
771 pub fn needs_updating(&self, game_info: &GameInfo, game_path: &Path) -> Result<bool> {
773 let ca_paths = game_info.ca_packs_paths(game_path)?;
774 let last_date = last_modified_time_from_files(&ca_paths)?;
775 Ok(last_date > self.build_date || self.version != VERSION)
776 }
777
778 fn load_loose_files(&mut self, schema: &Option<Schema>, game_info: &GameInfo, game_path: &Path) -> Result<()> {
780 self.vanilla_loose_files.clear();
781 self.vanilla_loose_tables.clear();
782 self.vanilla_loose_locs.clear();
783 self.vanilla_loose_folders.clear();
784 self.vanilla_loose_paths.clear();
785
786 let game_data_path = game_info.data_path(game_path)?;
787 let game_data_path_str = game_data_path.to_string_lossy().replace('\\', "/");
788
789 self.vanilla_loose_files = files_from_subdir(&game_data_path, true)?
790 .into_par_iter()
791 .filter_map(|path| {
792 let mut path = path.to_string_lossy().replace('\\', "/");
793 if !path.ends_with(".pack") {
794 if let Ok(mut rfile) = RFile::new_from_file(&path) {
795 let subpath = path.split_off(game_data_path_str.len() + 1);
796 rfile.set_path_in_container_raw(&subpath);
797 let _ = rfile.guess_file_type();
798 Some((subpath, rfile))
799 } else {
800 None
801 }
802 } else {
803 None
804 }
805 })
806 .collect::<HashMap<String, RFile>>();
807
808 let cacheable = self.vanilla_loose_files.par_iter_mut()
809 .filter_map(|(_, file)| {
810 let _ = file.guess_file_type();
811
812 match file.file_type() {
813 FileType::DB |
814 FileType::Loc => Some(file),
815 _ => None,
816 }
817 })
818 .collect::<Vec<&mut RFile>>();
819
820 cacheable.iter()
821 .for_each(|file| {
822 match file.file_type() {
823 FileType::DB => {
824 if let Some(table_name) = file.db_table_name_from_path() {
825 match self.vanilla_loose_tables.get_mut(table_name) {
826 Some(table_paths) => table_paths.push(file.path_in_container_raw().to_owned()),
827 None => { self.vanilla_loose_tables.insert(table_name.to_owned(), vec![file.path_in_container_raw().to_owned()]); },
828 }
829 }
830 }
831 FileType::Loc => {
832 self.vanilla_loose_locs.insert(file.path_in_container_raw().to_owned());
833 }
834 _ => {}
835 }
836 }
837 );
838
839 self.vanilla_loose_folders = self.vanilla_loose_files.par_iter().filter_map(|(path, _)| {
840 let file_path_split = path.split('/').collect::<Vec<&str>>();
841 let folder_path_len = file_path_split.len() - 1;
842 if folder_path_len == 0 {
843 None
844 } else {
845
846 let mut paths = Vec::with_capacity(folder_path_len);
847
848 for (index, folder) in file_path_split.iter().enumerate() {
849 if index < path.len() - 1 && !folder.is_empty() {
850 paths.push(file_path_split[0..=index].join("/"))
851 }
852 }
853
854 Some(paths)
855 }
856 }).flatten().collect::<HashSet<String>>();
857
858 self.vanilla_loose_files.keys().for_each(|path| {
859 let lower = path.to_lowercase();
860 match self.vanilla_loose_paths.get_mut(&lower) {
861 Some(paths) => paths.push(path.to_owned()),
862 None => { self.vanilla_loose_paths.insert(lower, vec![path.to_owned()]); },
863 }
864 });
865
866 if let Some(schema) = schema {
868 let mut decode_extra_data = DecodeableExtraData::default();
869 decode_extra_data.set_schema(Some(schema));
870 let extra_data = Some(decode_extra_data);
871
872 let mut files = self.vanilla_loose_locs.iter().chain(self.vanilla_loose_tables.values().flatten()).filter_map(|path| {
873 self.vanilla_loose_files.remove(path).map(|file| (path.to_owned(), file))
874 }).collect::<Vec<_>>();
875
876 files.par_iter_mut().for_each(|(_, file)| {
877 let _ = file.decode(&extra_data, true, false);
878 });
879
880 self.vanilla_loose_files.par_extend(files);
881 }
882
883 Ok(())
884 }
885
886
887 fn load_parent_files(&mut self, schema: &Option<Schema>, parent_pack_names: &[String], game_info: &GameInfo, game_path: &Path, secondary_path: &Path) -> Result<()> {
889 self.parent_files.clear();
890 self.parent_tables.clear();
891 self.parent_locs.clear();
892 self.parent_folders.clear();
893 self.parent_paths.clear();
894
895 self.load_parent_packs(parent_pack_names, game_info, game_path, secondary_path)?;
897 self.parent_files.par_iter_mut().map(|(_, file)| file.guess_file_type()).collect::<Result<()>>()?;
898
899 self.parent_files.iter()
901 .for_each(|(path, file)| {
902 match file.file_type() {
903 FileType::DB => {
904 if let Some(table_name) = file.db_table_name_from_path() {
905 match self.parent_tables.get_mut(table_name) {
906 Some(table_paths) => table_paths.push(path.to_owned()),
907 None => { self.parent_tables.insert(table_name.to_owned(), vec![path.to_owned()]); },
908 }
909 }
910 }
911 FileType::Loc => {
912 self.parent_locs.insert(path.to_owned());
913 }
914 _ => {}
915 }
916 }
917 );
918
919 self.parent_folders = self.parent_files.par_iter().filter_map(|(path, _)| {
921 let file_path_split = path.split('/').collect::<Vec<&str>>();
922 let folder_path_len = file_path_split.len() - 1;
923 if folder_path_len == 0 {
924 None
925 } else {
926
927 let mut paths = Vec::with_capacity(folder_path_len);
928
929 for (index, folder) in file_path_split.iter().enumerate() {
930 if index < path.len() - 1 && !folder.is_empty() {
931 paths.push(file_path_split[0..=index].join("/"))
932 }
933 }
934
935 Some(paths)
936 }
937 }).flatten().collect::<HashSet<String>>();
938
939 self.parent_files.keys().for_each(|path| {
940 let lower = path.to_lowercase();
941 match self.parent_paths.get_mut(&lower) {
942 Some(paths) => paths.push(path.to_owned()),
943 None => { self.parent_paths.insert(lower, vec![path.to_owned()]); },
944 }
945 });
946
947 if let Some(schema) = schema {
949 let mut decode_extra_data = DecodeableExtraData::default();
950 decode_extra_data.set_schema(Some(schema));
951 let extra_data = Some(decode_extra_data);
952
953 let mut files = self.parent_tables.values().flatten().filter_map(|path| {
954 self.parent_files.remove(path).map(|file| (path.to_owned(), file))
955 }).collect::<Vec<_>>();
956
957 files.par_iter_mut().for_each(|(_, file)| {
958 let _ = file.decode(&extra_data, true, false);
959 });
960
961 self.parent_files.par_extend(files);
962 }
963
964 let mut files = self.parent_locs.iter().filter_map(|path| {
966 self.parent_files.remove(path).map(|file| (path.to_owned(), file))
967 }).collect::<Vec<_>>();
968
969 files.par_iter_mut().for_each(|(_, file)| {
970 let _ = file.decode(&None, true, false);
971 });
972
973 self.parent_files.par_extend(files);
974
975 Ok(())
976 }
977
978 fn load_parent_packs(&mut self, parent_pack_names: &[String], game_info: &GameInfo, game_path: &Path, secondary_path: &Path) -> Result<()> {
981 let data_packs_paths = game_info.data_packs_paths(game_path).unwrap_or_default();
982 let secondary_packs_paths = game_info.secondary_packs_paths(secondary_path);
983 let content_packs_paths = game_info.content_packs_paths(game_path);
984 let mut loaded_packfiles = vec![];
985
986 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));
987
988 Ok(())
989 }
990
991 fn load_parent_pack(
994 &mut self,
995 pack_name: &str,
996 already_loaded: &mut Vec<String>,
997 data_paths: &[PathBuf],
998 secondary_paths: &Option<Vec<PathBuf>>,
999 content_paths: &Option<Vec<PathBuf>>,
1000 game_info: &GameInfo
1001 ) {
1002 if !already_loaded.contains(&pack_name.to_owned()) {
1004
1005 if let Some(path) = data_paths.iter().find(|x| x.file_name().unwrap().to_string_lossy() == pack_name) {
1007 if let Ok(pack) = Pack::read_and_merge(&[path.to_path_buf()], game_info, true, false, false) {
1008 already_loaded.push(pack_name.to_owned());
1009 pack.dependencies().iter().for_each(|(_, pack_name)| self.load_parent_pack(pack_name, already_loaded, data_paths, secondary_paths, content_paths, game_info));
1010 self.parent_files.extend(pack.files().clone());
1011
1012 return;
1013 }
1014 }
1015
1016 if let Some(ref paths) = secondary_paths {
1018 if let Some(path) = paths.iter().find(|x| x.file_name().unwrap().to_string_lossy() == pack_name) {
1019 if let Ok(pack) = Pack::read_and_merge(&[path.to_path_buf()], game_info, true, false, false) {
1020 already_loaded.push(pack_name.to_owned());
1021 pack.dependencies().iter().for_each(|(_, pack_name)| self.load_parent_pack(pack_name, already_loaded, data_paths, secondary_paths, content_paths, game_info));
1022 self.parent_files.extend(pack.files().clone());
1023
1024 return;
1025 }
1026 }
1027 }
1028
1029 if let Some(ref paths) = content_paths {
1031 if let Some(path) = paths.iter().find(|x| x.file_name().unwrap().to_string_lossy() == pack_name) {
1032 if let Ok(pack) = Pack::read_and_merge(&[path.to_path_buf()], game_info, true, false, false) {
1033 already_loaded.push(pack_name.to_owned());
1034 pack.dependencies().iter().for_each(|(_, pack_name)| self.load_parent_pack(pack_name, already_loaded, data_paths, secondary_paths, content_paths, game_info));
1035 self.parent_files.extend(pack.files().clone());
1036 }
1037 }
1038 }
1039 }
1040 }
1041
1042 pub fn decode_tables(&mut self, schema: &Option<Schema>) {
1046 if let Some(schema) = schema {
1047
1048 let mut decode_extra_data = DecodeableExtraData::default();
1049 decode_extra_data.set_schema(Some(schema));
1050 let extra_data = Some(decode_extra_data);
1051
1052 let mut files = self.vanilla_loose_locs.iter().chain(self.vanilla_loose_tables.values().flatten()).filter_map(|path| {
1054 self.vanilla_loose_files.remove(path).map(|file| (path.to_owned(), file))
1055 }).collect::<Vec<_>>();
1056
1057 files.par_iter_mut().for_each(|(_, file)| {
1058 let _ = file.decode(&extra_data, true, false);
1059 });
1060
1061 self.vanilla_loose_files.par_extend(files);
1062
1063 let mut files = self.vanilla_locs.iter().chain(self.vanilla_tables.values().flatten()).filter_map(|path| {
1065 self.vanilla_files.remove(path).map(|file| (path.to_owned(), file))
1066 }).collect::<Vec<_>>();
1067
1068 files.par_iter_mut().for_each(|(_, file)| {
1069 let _ = file.decode(&extra_data, true, false);
1070 });
1071
1072 self.vanilla_files.par_extend(files);
1073
1074 let mut files = self.parent_locs.iter().chain(self.parent_tables.values().flatten()).filter_map(|path| {
1076 self.parent_files.remove(path).map(|file| (path.to_owned(), file))
1077 }).collect::<Vec<_>>();
1078
1079 files.par_iter_mut().for_each(|(_, file)| {
1080 let _ = file.decode(&extra_data, true, false);
1081 });
1082
1083 self.parent_files.par_extend(files);
1084 }
1085 }
1086
1087 pub fn file(&self, file_path: &str, include_vanilla: bool, include_parent: bool, case_insensitive: bool) -> Result<&RFile> {
1093 let file_path = if let Some(file_path) = file_path.strip_prefix('/') {
1094 file_path
1095 } else {
1096 file_path
1097 };
1098
1099 if include_parent {
1100
1101 if let Some(file) = self.parent_files.get(file_path) {
1103 return Ok(file);
1104 }
1105
1106 if case_insensitive {
1107 let lower = file_path.to_lowercase();
1108 if let Some(file) = self.parent_paths.get(&lower).and_then(|paths| self.parent_files.get(&paths[0])) {
1109 return Ok(file);
1110 }
1111 }
1112 }
1113
1114 if include_vanilla {
1115
1116 if let Some(file) = self.vanilla_files.get(file_path) {
1118 return Ok(file);
1119 }
1120
1121 if case_insensitive {
1122 let lower = file_path.to_lowercase();
1123 if let Some(file) = self.vanilla_paths.get(&lower).and_then(|paths| self.vanilla_files.get(&paths[0])) {
1124 return Ok(file);
1125 }
1126
1127 }
1128
1129 if let Some(file) = self.vanilla_loose_files.get(file_path) {
1131 return Ok(file);
1132 }
1133
1134 if case_insensitive {
1135 let lower = file_path.to_lowercase();
1136 if let Some(file) = self.vanilla_loose_paths.get(&lower).and_then(|paths| self.vanilla_loose_files.get(&paths[0])) {
1137 return Ok(file);
1138 }
1139 }
1140 }
1141
1142 Err(RLibError::DependenciesCacheFileNotFound(file_path.to_owned()))
1143 }
1144
1145 pub fn file_mut(&mut self, file_path: &str, include_vanilla: bool, include_parent: bool) -> Result<&mut RFile> {
1147 if include_parent {
1148 if let Some(file) = self.parent_files.get_mut(file_path) {
1149 return Ok(file);
1150 }
1151 }
1152
1153 if include_vanilla {
1154 if let Some(file) = self.vanilla_files.get_mut(file_path) {
1155 return Ok(file);
1156 }
1157
1158 if let Some(file) = self.vanilla_loose_files.get_mut(file_path) {
1159 return Ok(file);
1160 }
1161 }
1162
1163 Err(RLibError::DependenciesCacheFileNotFound(file_path.to_owned()))
1164 }
1165
1166 pub fn files_mut_by_paths(
1168 &mut self,
1169 paths: &HashSet<String>,
1170 include_vanilla: bool,
1171 include_parent: bool,
1172 ) -> HashMap<String, &mut RFile> {
1173 let mut result: HashMap<String, &mut RFile> = HashMap::with_capacity(paths.len());
1174
1175 if include_parent {
1176 for (k, v) in self.parent_files.iter_mut() {
1177 if paths.contains(k) {
1178 result.insert(k.clone(), v);
1179 }
1180 }
1181 }
1182
1183 if include_vanilla {
1184 for (k, v) in self.vanilla_files.iter_mut() {
1185 if paths.contains(k) && !result.contains_key(k) {
1186 result.insert(k.clone(), v);
1187 }
1188 }
1189
1190 for (k, v) in self.vanilla_loose_files.iter_mut() {
1191 if paths.contains(k) && !result.contains_key(k) {
1192 result.insert(k.clone(), v);
1193 }
1194 }
1195 }
1196
1197 result
1198 }
1199
1200 pub fn files_by_path(&self, file_paths: &[ContainerPath], include_vanilla: bool, include_parent: bool, case_insensitive: bool) -> HashMap<String, &RFile> {
1202 let (file_paths, folder_paths): (Vec<_>, Vec<_>) = file_paths.iter().partition_map(|file_path| match file_path {
1203 ContainerPath::File(file_path) => Either::Left(file_path.to_owned()),
1204 ContainerPath::Folder(file_path) => Either::Right(file_path.to_owned()),
1205 });
1206
1207 let mut hashmap = HashMap::new();
1208
1209 if !file_paths.is_empty() {
1211 hashmap.extend(file_paths.par_iter()
1212 .filter_map(|file_path| self.file(file_path, include_vanilla, include_parent, case_insensitive)
1213 .ok()
1214 .map(|file| (file_path.to_owned(), file)))
1215 .collect::<Vec<(_,_)>>()
1216 );
1217 }
1218
1219 if !folder_paths.is_empty() {
1221 hashmap.extend(folder_paths.into_par_iter().flat_map(|folder_path| {
1222 let mut folder = vec![];
1223 let folder_path = folder_path.to_owned() + "/";
1224 if include_vanilla {
1225
1226 if folder_path == "/" {
1227 folder.extend(self.vanilla_loose_files.par_iter()
1228 .map(|(path, file)| (path.to_owned(), file))
1229 .collect::<Vec<(_,_)>>());
1230
1231 folder.extend(self.vanilla_files.par_iter()
1232 .map(|(path, file)| (path.to_owned(), file))
1233 .collect::<Vec<(_,_)>>());
1234
1235 } else {
1236 folder.extend(self.vanilla_loose_files.par_iter()
1237 .filter(|(path, _)| {
1238 if case_insensitive {
1239 starts_with_case_insensitive(path, &folder_path)
1240 } else {
1241 path.starts_with(&folder_path)
1242 }
1243 })
1244 .map(|(path, file)| (path.to_owned(), file))
1245 .collect::<Vec<(_,_)>>());
1246
1247 folder.extend(self.vanilla_files.par_iter()
1248 .filter(|(path, _)| {
1249 if case_insensitive {
1250 starts_with_case_insensitive(path, &folder_path)
1251 } else {
1252 path.starts_with(&folder_path)
1253 }
1254 })
1255 .map(|(path, file)| (path.to_owned(), file))
1256 .collect::<Vec<(_,_)>>());
1257 }
1258 }
1259
1260 if include_parent {
1261 if folder_path == "/" {
1262 folder.extend(self.parent_files.par_iter()
1263 .map(|(path, file)| (path.to_owned(), file))
1264 .collect::<Vec<(_,_)>>());
1265
1266 } else {
1267 folder.extend(self.parent_files.par_iter()
1268 .filter(|(path, _)| {
1269 if case_insensitive {
1270 starts_with_case_insensitive(path, &folder_path)
1271 } else {
1272 path.starts_with(&folder_path)
1273 }
1274 })
1275 .map(|(path, file)| (path.to_owned(), file))
1276 .collect::<Vec<(_,_)>>());
1277 }
1278 }
1279 folder
1280 }).collect::<Vec<(_,_)>>());
1281 }
1282
1283 hashmap
1284 }
1285
1286 pub fn files_by_types(&self, file_types: &[FileType], include_vanilla: bool, include_parent: bool) -> HashMap<String, &RFile> {
1288 let mut files = HashMap::new();
1289
1290 if include_vanilla {
1292 files.extend(self.vanilla_loose_files.par_iter().chain(self.vanilla_files.par_iter())
1293 .filter(|(_, file)| file_types.contains(&file.file_type()))
1294 .map(|(path, file)| (path.to_owned(), file))
1295 .collect::<HashMap<_,_>>());
1296 }
1297
1298 if include_parent {
1299 files.extend(self.parent_files.par_iter()
1300 .filter(|(_, file)| file_types.contains(&file.file_type()))
1301 .map(|(path, file)| (path.to_owned(), file))
1302 .collect::<HashMap<_,_>>());
1303 }
1304
1305 files
1306 }
1307
1308 pub fn files_by_types_mut(&mut self, file_types: &[FileType], include_vanilla: bool, include_parent: bool) -> HashMap<String, &mut RFile> {
1310 let mut files = HashMap::new();
1311
1312 if include_vanilla {
1314 files.extend(self.vanilla_loose_files.par_iter_mut().chain(self.vanilla_files.par_iter_mut())
1315 .filter(|(_, file)| file_types.contains(&file.file_type()))
1316 .map(|(path, file)| (path.to_owned(), file))
1317 .collect::<HashMap<_,_>>());
1318 }
1319
1320 if include_parent {
1321 files.extend(self.parent_files.par_iter_mut()
1322 .filter(|(_, file)| file_types.contains(&file.file_type()))
1323 .map(|(path, file)| (path.to_owned(), file))
1324 .collect::<HashMap<_,_>>());
1325 }
1326
1327 files
1328 }
1329
1330 pub fn loc_data(&self, include_vanilla: bool, include_parent: bool) -> Result<Vec<&RFile>> {
1334 let mut cache = vec![];
1335
1336 if include_vanilla {
1337 let mut vanilla_loose_locs = self.vanilla_loose_locs.iter().collect::<Vec<_>>();
1338 vanilla_loose_locs.sort();
1339
1340 for path in &vanilla_loose_locs {
1341 if let Some(file) = self.vanilla_loose_files.get(*path) {
1342 cache.push(file);
1343 }
1344 }
1345
1346 let mut vanilla_locs = self.vanilla_locs.iter().collect::<Vec<_>>();
1347 vanilla_locs.sort();
1348
1349 for path in &vanilla_locs {
1350 if let Some(file) = self.vanilla_files.get(*path) {
1351 cache.push(file);
1352 }
1353 }
1354 }
1355
1356 if include_parent {
1357 let mut parent_locs = self.parent_locs.iter().collect::<Vec<_>>();
1358 parent_locs.sort();
1359
1360 for path in &parent_locs {
1361 if let Some(file) = self.parent_files.get(*path) {
1362 cache.push(file);
1363 }
1364 }
1365 }
1366
1367 Ok(cache)
1368 }
1369
1370 pub fn db_data(&self, table_name: &str, include_vanilla: bool, include_parent: bool) -> Result<Vec<&RFile>> {
1376 let mut cache = vec![];
1377
1378 if include_vanilla {
1379 if let Some(vanilla_loose_tables) = self.vanilla_loose_tables.get(table_name) {
1380 let mut vanilla_loose_tables = vanilla_loose_tables.to_vec();
1381 vanilla_loose_tables.sort();
1382
1383 for path in &vanilla_loose_tables {
1384 if let Some(file) = self.vanilla_loose_files.get(path) {
1385 cache.push(file);
1386 }
1387 }
1388 }
1389
1390 if let Some(vanilla_tables) = self.vanilla_tables.get(table_name) {
1391 let mut vanilla_tables = vanilla_tables.to_vec();
1392 vanilla_tables.sort();
1393
1394 for path in &vanilla_tables {
1395 if let Some(file) = self.vanilla_files.get(path) {
1396 cache.push(file);
1397 }
1398 }
1399 }
1400 }
1401
1402 if include_parent {
1403 if let Some(parent_tables) = self.parent_tables.get(table_name) {
1404 let mut parent_tables = parent_tables.to_vec();
1405 parent_tables.sort();
1406
1407 for path in &parent_tables {
1408 if let Some(file) = self.parent_files.get(path) {
1409 cache.push(file);
1410 }
1411 }
1412 }
1413 }
1414
1415 Ok(cache)
1416 }
1417
1418 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>> {
1425 let mut cache = vec![];
1426
1427 if include_vanilla {
1428 if let Some(vanilla_loose_tables) = self.vanilla_loose_tables.get(table_name) {
1429 let mut vanilla_loose_tables = vanilla_loose_tables.to_vec();
1430 vanilla_loose_tables.sort();
1431
1432 for path in &vanilla_loose_tables {
1433 if let Some(file) = self.vanilla_loose_files.get(path) {
1434 cache.push(file);
1435 }
1436 }
1437 }
1438
1439 if let Some(vanilla_tables) = self.vanilla_tables.get(table_name) {
1440 let mut vanilla_tables = vanilla_tables.to_vec();
1441 vanilla_tables.sort();
1442
1443 for path in &vanilla_tables {
1444 if let Some(file) = self.vanilla_files.get(path) {
1445 cache.push(file);
1446 }
1447 }
1448 }
1449 }
1450
1451 if include_parent {
1452 if let Some(parent_tables) = self.parent_tables.get(table_name) {
1453 let mut parent_tables = parent_tables.to_vec();
1454 parent_tables.sort();
1455
1456 for path in &parent_tables {
1457 if let Some(file) = self.parent_files.get(path) {
1458 cache.push(file);
1459 }
1460 }
1461 }
1462 }
1463
1464 let paths = cache.iter()
1465 .map(|x| x.path_in_container())
1466 .collect::<Vec<_>>();
1467
1468 for pack in packs.values() {
1469 for pack_file in pack.files_by_paths(&paths, true) {
1470 for cache_file in &mut cache {
1471 if cache_file.path_in_container() == pack_file.path_in_container() {
1472 *cache_file = pack_file;
1473 break;
1474 }
1475 }
1476 }
1477 }
1478
1479 Ok(cache)
1480 }
1481
1482 pub fn db_and_loc_data(&self, include_db: bool, include_loc: bool, include_vanilla: bool, include_parent: bool) -> Result<Vec<&RFile>> {
1486 let mut cache = vec![];
1487
1488 if include_vanilla {
1489 if include_db {
1490 let mut vanilla_loose_tables = self.vanilla_loose_tables.values().flatten().collect::<Vec<_>>();
1491 vanilla_loose_tables.sort();
1492
1493 for path in &vanilla_loose_tables {
1494 if let Some(file) = self.vanilla_loose_files.get(*path) {
1495 cache.push(file);
1496 }
1497 }
1498
1499 let mut vanilla_tables = self.vanilla_tables.values().flatten().collect::<Vec<_>>();
1500 vanilla_tables.sort();
1501
1502 for path in &vanilla_tables {
1503 if let Some(file) = self.vanilla_files.get(*path) {
1504 cache.push(file);
1505 }
1506 }
1507 }
1508
1509 if include_loc {
1510 let mut vanilla_loose_locs = self.vanilla_loose_locs.iter().collect::<Vec<_>>();
1511 vanilla_loose_locs.sort();
1512
1513 for path in &vanilla_loose_locs {
1514 if let Some(file) = self.vanilla_loose_files.get(*path) {
1515 cache.push(file);
1516 }
1517 }
1518
1519 let mut vanilla_locs = self.vanilla_locs.iter().collect::<Vec<_>>();
1520 vanilla_locs.sort();
1521
1522 for path in &vanilla_locs {
1523 if let Some(file) = self.vanilla_files.get(*path) {
1524 cache.push(file);
1525 }
1526 }
1527 }
1528 }
1529
1530 if include_parent {
1531 if include_db {
1532 let mut parent_tables = self.parent_tables.values().flatten().collect::<Vec<_>>();
1533 parent_tables.sort();
1534
1535 for path in &parent_tables {
1536 if let Some(file) = self.parent_files.get(*path) {
1537 cache.push(file);
1538 }
1539 }
1540 }
1541
1542 if include_loc {
1543 let mut parent_locs = self.parent_locs.iter().collect::<Vec<_>>();
1544 parent_locs.sort();
1545
1546 for path in &parent_locs {
1547 if let Some(file) = self.parent_files.get(*path) {
1548 cache.push(file);
1549 }
1550 }
1551 }
1552 }
1553
1554 Ok(cache)
1555 }
1556
1557 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> {
1565
1566 let mut vanilla_references = match self.local_tables_references.get(table_name) {
1570 Some(cached_data) => cached_data.clone(),
1571 None => HashMap::new(),
1572 };
1573
1574 let (_loc_files, loc_decoded) = if loc_data.is_some() {
1576 (vec![], vec![])
1577 } else {
1578 let loc_files: Vec<_> = packs.values().flat_map(|pack| pack.files_by_type(&[FileType::Loc])).collect();
1579 let loc_decoded = loc_files.iter()
1580 .filter_map(|file| if let Ok(RFileDecoded::Loc(loc)) = file.decoded() { Some(loc) } else { None })
1581 .map(|file| file.data())
1582 .collect::<Vec<_>>();
1583 (loc_files, loc_decoded)
1584 };
1585
1586 let mut _loc_data_dummy = HashMap::new();
1587 let loc_data = if let Some(ref loc_data) = loc_data {
1588 loc_data
1589 } else {
1590 _loc_data_dummy = loc_decoded.par_iter()
1591 .flat_map(|data| data.par_iter()
1592 .map(|entry| (entry[0].data_to_string(), entry[1].data_to_string()))
1593 .collect::<Vec<(_,_)>>()
1594 ).collect::<HashMap<_,_>>();
1595 &_loc_data_dummy
1596 };
1597
1598 let mut definition = definition.clone();
1601 self.add_recursive_lookups_to_definition(schema, &mut definition, table_name);
1602
1603 let patches = Some(definition.patches());
1604 let fields_processed = definition.fields_processed();
1605 let local_references = fields_processed.par_iter().enumerate().filter_map(|(column, field)| {
1606 match field.is_reference(patches) {
1607 Some((ref ref_table, ref ref_column)) => {
1608 if !ref_table.is_empty() && !ref_column.is_empty() {
1609
1610 let lookup_data = if let Some(ref data) = field.lookup_no_patch() { data.to_vec() } else { Vec::with_capacity(0) };
1612 let mut references = TableReferences::default();
1613 *references.field_name_mut() = field.name().to_owned();
1614
1615 let _local_found = self.db_reference_data_from_local_pack(&mut references, (ref_table, ref_column, &lookup_data), packs, loc_data);
1616
1617 Some((column as i32, references))
1618 } else { None }
1619 }
1620
1621 None => {
1623 if let Some(ref lookup_data) = field.lookup_no_patch() {
1624
1625 if field.is_key(patches) && fields_processed.iter().filter(|x| x.is_key(patches)).count() == 1 {
1627
1628 let ref_table = if table_name.ends_with("_tables") && table_name.len() > 7 {
1630 table_name.to_owned().drain(..table_name.len() - 7).collect()
1631 } else {
1632 table_name.to_owned()
1633 };
1634
1635 let ref_column = field.name();
1636
1637 let mut references = TableReferences::default();
1639 *references.field_name_mut() = field.name().to_owned();
1640
1641 let _local_found = self.db_reference_data_from_local_pack(&mut references, (&ref_table, ref_column, lookup_data), packs, loc_data);
1642
1643 Some((column as i32, references))
1644 } else { None }
1645 } else { None }
1646 }
1647 }
1648 }).collect::<HashMap<_, _>>();
1649
1650 vanilla_references.par_iter_mut().for_each(|(key, value)|
1651 if let Some(local_value) = local_references.get(key) {
1652 value.data.extend(local_value.data.iter().map(|(k, v)| (k.clone(), v.clone())));
1653 }
1654 );
1655
1656 for (index, field) in fields_processed.iter().enumerate() {
1657 match vanilla_references.get_mut(&(index as i32)) {
1658 Some(references) => {
1659 let hardcoded_lookup = field.lookup_hardcoded(patches);
1660 if !hardcoded_lookup.is_empty() {
1661 references.data.extend(hardcoded_lookup);
1662 }
1663 },
1664 None => {
1665 let mut references = TableReferences::default();
1666 *references.field_name_mut() = field.name().to_owned();
1667 let hardcoded_lookup = field.lookup_hardcoded(patches);
1668 if !hardcoded_lookup.is_empty() {
1669 references.data.extend(hardcoded_lookup);
1670 vanilla_references.insert(index as i32, references);
1671 }
1672 },
1673 }
1674 }
1675
1676 vanilla_references
1677 }
1678
1679 fn db_reference_data_from_vanilla_and_modded_tables(&self, references: &mut TableReferences, reference_info: (&str, &str, &[String])) -> Option<Definition> {
1683 self.db_reference_data_generic(references, reference_info, None, &HashMap::new())
1684 }
1685
1686 fn db_reference_data_from_asskit_tables(&self, references: &mut TableReferences, reference_info: (&str, &str, &[String])) -> bool {
1690 let ref_table = reference_info.0;
1691 let ref_column = reference_info.1;
1692 let ref_lookup_columns = reference_info.2;
1693
1694 match self.asskit_only_db_tables.get(ref_table) {
1695 Some(table) => {
1696 let fields_processed = table.definition().fields_processed();
1697 let ref_column_index = fields_processed.iter().position(|x| x.name() == ref_column);
1698 let ref_lookup_columns_index = ref_lookup_columns.iter().map(|column| fields_processed.iter().position(|x| x.name() == column)).collect::<Vec<_>>();
1699
1700 for row in &*table.data() {
1701 let mut reference_data = String::new();
1702 let mut lookup_data = vec![];
1703
1704 if let Some(index) = ref_column_index {
1706 reference_data = row[index].data_to_string().to_string();
1707 }
1708
1709 for column in ref_lookup_columns_index.iter().flatten() {
1711 lookup_data.push(row[*column].data_to_string());
1712 }
1713
1714 references.data.insert(reference_data, lookup_data.join(" "));
1715 }
1716 true
1717 },
1718 None => false,
1719 }
1720 }
1721
1722 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> {
1724 self.db_reference_data_generic(references, reference_info, Some(packs), loc_data)
1725 }
1726
1727 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> {
1728 let mut data_found: Option<Definition> = None;
1729
1730 let ref_table = reference_info.0;
1731 let ref_column = reference_info.1;
1732 let ref_lookup_columns = reference_info.2;
1733
1734 let mut cache = HashMap::new();
1735
1736 let ref_table_full = if ref_table.ends_with("_tables") {
1738 ref_table.to_owned()
1739 } else {
1740 ref_table.to_owned() + "_tables"
1741 };
1742
1743 let files = match packs {
1744 Some(packs) => {
1745 let mut files: Vec<&RFile> = packs.values().flat_map(|pack| pack.files_by_path(&ContainerPath::Folder(format!("db/{ref_table_full}")), true)).collect();
1746 files.append(&mut self.db_data(&ref_table_full, true, true).unwrap_or_else(|_| vec![]));
1747 files
1748 },
1749 None => self.db_data(&ref_table_full, true, true).unwrap_or_else(|_| vec![]),
1750 };
1751
1752 let mut table_data_cache: HashMap<String, HashMap<String, String>> = HashMap::new();
1753
1754 files.iter().for_each(|file| {
1755 if let Ok(RFileDecoded::DB(db)) = file.decoded() {
1756 let definition = db.definition();
1757 let fields_processed = definition.fields_processed();
1758
1759 if let Some(ref_column_index) = fields_processed.iter().position(|x| x.name() == ref_column) {
1761
1762 let lookups_analyzed = ref_lookup_columns.iter().map(|ref_lookup_path| {
1764 let ref_lookup_steps = ref_lookup_path.split(':').map(|x| x.split('#').collect::<Vec<_>>()).collect::<Vec<_>>();
1765 let mut is_loc = false;
1766 let mut col_pos = 0;
1767
1768 for (index, ref_lookup_step) in ref_lookup_steps.iter().enumerate() {
1769 if ref_lookup_step.len() == 3 {
1770 let lookup_ref_table = ref_lookup_step[0];
1771 let lookup_ref_key = ref_lookup_step[1];
1772 let lookup_ref_lookup = ref_lookup_step[2];
1773 let lookup_ref_table_long = lookup_ref_table.to_owned() + "_tables";
1774
1775 if !cache.contains_key(lookup_ref_table) {
1777 let mut files = vec![];
1778
1779 if let Some(packs) = packs {
1780 for pack in packs.values() {
1781 files.append(&mut pack.files_by_path(&ContainerPath::Folder(format!("db/{lookup_ref_table_long}")), true));
1782 }
1783 }
1784
1785 for file in self.db_data(&lookup_ref_table_long, true, true).unwrap_or_else(|_| vec![]) {
1787 if files.iter().all(|x| x.path_in_container_raw() != file.path_in_container_raw()) {
1788 files.push(file);
1789 }
1790 }
1791
1792 if !files.is_empty() {
1793
1794 files.sort_by(|a, b| a.path_in_container_raw().cmp(b.path_in_container_raw()));
1796 cache.insert(lookup_ref_table.to_owned(), files);
1797 }
1798 }
1799
1800 if index == ref_lookup_steps.len() - 1 {
1802 if let Some(file) = cache.get(lookup_ref_table) {
1803 if let Some(file) = file.first() {
1804 if let Ok(RFileDecoded::DB(db)) = file.decoded() {
1805 let definition = db.definition();
1806 let fields_processed = definition.fields_processed();
1807 let localised_fields = definition.localised_fields();
1808
1809 match localised_fields.iter().position(|x| x.name() == lookup_ref_lookup) {
1810 Some(loc_pos) => {
1811 is_loc = true;
1812 col_pos = loc_pos;
1813 },
1814 None => match fields_processed.iter().position(|x| x.name() == lookup_ref_lookup) {
1815 Some(pos) => {
1816 is_loc = false;
1817 col_pos = pos;
1818 },
1819 None => {
1820 },
1822 }
1823 }
1824 }
1825 }
1826 }
1827 }
1828
1829 if let Some(files) = cache.get(lookup_ref_table) {
1831 for file in files {
1832 let table_data_column_cache_key = file.path_in_container_raw().to_owned() + &ref_lookup_step.join("++");
1833 if !table_data_cache.contains_key(&table_data_column_cache_key) {
1834 if let Ok(RFileDecoded::DB(db)) = file.decoded() {
1835 let definition = db.definition();
1836 let fields_processed = definition.fields_processed();
1837 let localised_fields = definition.localised_fields();
1838 let localised_order = definition.localised_key_order();
1839
1840 let loc_key = if is_loc {
1841 if let Some(loc_field) = localised_fields.get(col_pos) {
1842 let mut loc_key = String::with_capacity(2 + lookup_ref_table.len() + loc_field.name().len());
1843 loc_key.push_str(lookup_ref_table);
1844 loc_key.push('_');
1845 loc_key.push_str(loc_field.name());
1846 loc_key.push('_');
1847 loc_key
1848 } else {
1849 String::new()
1850 }
1851 } else {
1852 String::new()
1853 };
1854
1855 if let Some(source_key_column) = fields_processed.iter().position(|x| x.name() == lookup_ref_key) {
1856
1857 if index < ref_lookup_steps.len() - 1 {
1859 if let Some(source_lookup_column) = fields_processed.iter().position(|x| x.name() == lookup_ref_lookup) {
1860 let cache = db.data().iter()
1861 .map(|row| (row[source_key_column].data_to_string().to_string(), row[source_lookup_column].data_to_string().to_string()))
1862 .collect::<HashMap<_,_>>();
1863
1864 table_data_cache.insert(table_data_column_cache_key.clone(), cache);
1865 }
1866 }
1867
1868 else if is_loc {
1870 let cache = db.data().iter()
1871 .map(|row| {
1872 let mut loc_key = loc_key.to_owned();
1873 loc_key.push_str(&localised_order.iter().map(|pos| row[*pos as usize].data_to_string()).join(""));
1874 (row[source_key_column].data_to_string().to_string(), loc_key)
1875 })
1876 .collect::<HashMap<_,_>>();
1877 table_data_cache.insert(table_data_column_cache_key.clone(), cache);
1878 }
1879
1880 else {
1881 let cache = db.data().iter()
1882 .map(|row| (row[source_key_column].data_to_string().to_string(), row[col_pos].data_to_string().to_string()))
1883 .collect::<HashMap<_,_>>();
1884
1885 table_data_cache.insert(table_data_column_cache_key.clone(), cache);
1886 }
1887 }
1888 }
1889 }
1890 }
1891 }
1892 } else {
1893 error!("Badly built lookup. This is a bug.");
1894 }
1895 }
1896
1897 (ref_lookup_steps, is_loc)
1898
1899 }).collect::<Vec<_>>();
1900
1901 let data = db.data();
1902 for row in &*data {
1903 let mut lookup_data = Vec::with_capacity(lookups_analyzed.len());
1904
1905 let reference_data = row[ref_column_index].data_to_string();
1907
1908 for (lookup_steps, is_loc) in lookups_analyzed.iter() {
1910 if !reference_data.is_empty() {
1911
1912 if let Some(lookup) = self.db_reference_data_generic_lookup(&cache, loc_data, &reference_data, lookup_steps, *is_loc, &table_data_cache) {
1913 lookup_data.push(lookup);
1914 }
1915 }
1916 }
1917
1918 references.data.insert(reference_data.to_string(), lookup_data.into_iter().join(":"));
1919 }
1920
1921 match data_found {
1923 Some(ref definition) => {
1924 if db.definition().version() > definition.version() {
1925 data_found = Some(db.definition().clone());
1926 }
1927 }
1928
1929 None => data_found = Some(db.definition().clone()),
1930 }
1931 }
1932 }
1933 });
1934
1935 data_found
1936 }
1937
1938 fn db_reference_data_generic_lookup(
1939 &self,
1940 cache: &HashMap<String, Vec<&RFile>>,
1941 loc_data: &HashMap<Cow<str>, Cow<str>>,
1942 lookup_key: &str,
1943 lookup_steps: &[Vec<&str>],
1944 is_loc: bool,
1945 table_data_cache: &HashMap<String, HashMap<String, String>>
1946 ) -> Option<String> {
1947 let mut data_found: Option<String> = None;
1948
1949 if lookup_steps.is_empty() {
1950 return None;
1951 }
1952
1953 let current_step = &lookup_steps[0];
1954 let source_table = current_step[0];
1955
1956 if let Some(files) = cache.get(source_table) {
1957 for file in files {
1958 let table_data_column_cache_key = file.path_in_container_raw().to_owned() + ¤t_step.join("++");
1959 if let Some(table_data_column_cache) = table_data_cache.get(&table_data_column_cache_key) {
1960
1961 if let Some(lookup_value) = table_data_column_cache.get(lookup_key) {
1962
1963 if lookup_steps.len() > 1 {
1965 if !lookup_value.is_empty() {
1966 data_found = self.db_reference_data_generic_lookup(cache, loc_data, lookup_value, &lookup_steps[1..], is_loc, table_data_cache);
1967 }
1968 }
1969
1970 else if is_loc {
1972
1973 if let Some(data) = loc_data.get(&**lookup_value) {
1974 data_found = Some(data.to_string());
1975 } else if let Some(data) = self.localisation_data.get(&**lookup_value) {
1976 data_found = Some(data.to_string());
1977 } else {
1978 data_found = Some(lookup_value.to_string())
1979 }
1980 }
1981
1982 else {
1984 data_found = Some(lookup_value.to_owned());
1985 }
1986
1987 break;
1989 }
1990 }
1991 }
1992 }
1993
1994 data_found
1995 }
1996
1997 pub fn loc_key_source(&self, key: &str) -> Option<(String, String, Vec<String>)> {
2001 let key_split = key.split('_').collect::<Vec<_>>();
2002
2003 for (index, _) in key_split.iter().enumerate().rev() {
2006
2007 if index >= 1 {
2009
2010 let mut table_name = key_split[..index].join("_");
2011 let full_table_name = format!("{table_name}_tables");
2012
2013 if let Ok(rfiles) = self.db_data(&full_table_name, true, false) {
2014 let mut decoded = rfiles.iter()
2015 .filter_map(|x| if let Ok(RFileDecoded::DB(table)) = x.decoded() {
2016 Some(table)
2017 } else {
2018 None
2019 }).collect::<Vec<_>>();
2020
2021 if let Some(ak_file) = self.asskit_only_db_tables().get(&full_table_name) {
2023 decoded.push(ak_file);
2024 }
2025
2026 for table in decoded {
2027 let definition = table.definition();
2028 let localised_fields = definition.localised_fields();
2029 let localised_key_order = definition.localised_key_order();
2030 if !localised_fields.is_empty() {
2031 let mut field = String::new();
2032
2033 for (second_index, value) in key_split[index..].iter().enumerate() {
2035 field.push_str(value);
2036
2037 if localised_fields.iter().any(|x| x.name() == field) {
2038
2039 let key_data = &key_split[index + second_index + 1..].join("_");
2041
2042 let data = table.data();
2045 for row in data.iter() {
2046 let generated_key_split = localised_key_order.iter().map(|col| row[*col as usize].data_to_string()).collect::<Vec<_>>();
2047 let generated_key = generated_key_split.join("");
2048 if &generated_key == key_data {
2049 return Some((table_name, field, generated_key_split.iter().map(|x| x.to_string()).collect()));
2050 }
2051 }
2052 }
2053
2054 field.push('_');
2055 }
2056 }
2057 }
2058 }
2059
2060 table_name.push('_');
2062 }
2063 }
2064
2065 None
2066 }
2067
2068 pub fn file_exists(&self, file_path: &str, include_vanilla: bool, include_parent: bool, case_insensitive: bool) -> bool {
2074 if include_parent {
2075 if self.parent_files.contains_key(file_path) {
2076 return true
2077 } else if case_insensitive {
2078 let lower = file_path.to_lowercase();
2079 if self.parent_paths.contains_key(&lower) {
2080 return true
2081 }
2082 }
2083 }
2084
2085 if include_vanilla {
2086
2087 if self.vanilla_files.contains_key(file_path) || self.vanilla_loose_files.contains_key(file_path) {
2088 return true
2089 } else if case_insensitive {
2090 let lower = file_path.to_lowercase();
2091 if self.vanilla_paths.contains_key(&lower) || self.vanilla_loose_paths.contains_key(&lower) {
2092 return true
2093 }
2094 }
2095 }
2096
2097 false
2098 }
2099
2100 pub fn folder_exists(&self, folder_path: &str, include_vanilla: bool, include_parent: bool, case_insensitive: bool) -> bool {
2102 if include_parent && (
2103 self.parent_folders.contains(folder_path) ||
2104 (case_insensitive && self.parent_folders.par_iter().any(|path| caseless::canonical_caseless_match_str(path, folder_path)))
2105 ) {
2106 return true
2107 }
2108
2109 if include_vanilla && (
2110 (self.vanilla_folders.contains(folder_path) || self.vanilla_loose_folders.contains(folder_path)) ||
2111 (case_insensitive && self.vanilla_folders.par_iter().chain(self.vanilla_loose_folders.par_iter()).any(|path| caseless::canonical_caseless_match_str(path, folder_path)))
2112 ) {
2113 return true
2114 }
2115
2116 false
2117 }
2118
2119 pub fn are_dependencies_generated(file_path: &Path) -> bool {
2121 file_path.is_file()
2122 }
2123
2124 pub fn is_vanilla_data_loaded(&self, include_asskit: bool) -> bool {
2126 if include_asskit {
2127 !self.vanilla_files.is_empty() && self.is_asskit_data_loaded()
2128 } else {
2129 !self.vanilla_files.is_empty()
2130 }
2131 }
2132
2133 pub fn is_asskit_data_loaded(&self) -> bool {
2135 !self.asskit_only_db_tables.is_empty()
2136 }
2137
2138 pub fn is_db_outdated(&self, rfile: &RFileDecoded) -> bool {
2140 if let RFileDecoded::DB(data) = rfile {
2141 let dep_db_undecoded = if let Ok(undecoded) = self.db_data(data.table_name(), true, false) { undecoded } else { return false };
2142 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<_>>();
2143
2144 if let Some(vanilla_db) = dep_db_decoded.iter().max_by(|x, y| x.definition().version().cmp(y.definition().version())) {
2145 if vanilla_db.definition().version() > data.definition().version() {
2146 return true;
2147 }
2148 }
2149 }
2150
2151 false
2152 }
2153
2154 pub fn db_version(&self, table_name: &str) -> Option<i32> {
2156 let tables = self.vanilla_tables.get(table_name)?;
2157 for table_path in tables {
2158
2159 let table = self.vanilla_files.get(table_path)?;
2160 if let RFileDecoded::DB(table) = table.decoded().ok()? {
2161 return Some(*table.definition().version());
2162 }
2163
2164 let table = self.vanilla_loose_files.get(table_path)?;
2165 if let RFileDecoded::DB(table) = table.decoded().ok()? {
2166 return Some(*table.definition().version());
2167 }
2168 }
2169
2170 None
2171 }
2172
2173 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> {
2175 let mut values = HashSet::new();
2176
2177 if let Ok(files) = self.db_data(table_name, include_vanilla, include_parent) {
2178 values.extend(files.par_iter().filter_map(|file| {
2179 if let Ok(RFileDecoded::DB(table)) = file.decoded() {
2180 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<_>>())
2181 } else { None }
2182 }).flatten().collect::<Vec<_>>());
2183 }
2184
2185 if let Some(packs) = packs {
2186 for pack in packs.values() {
2187 let files = pack.files_by_path(&ContainerPath::Folder(format!("db/{table_name}")), true);
2188 values.extend(files.par_iter().filter_map(|file| {
2189 if let Ok(RFileDecoded::DB(table)) = file.decoded() {
2190 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<_>>())
2191 } else { None }
2192 }).flatten().collect::<Vec<_>>());
2193 }
2194 }
2195
2196 values
2197 }
2198
2199 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> {
2201 let mut values = HashMap::new();
2202
2203 if let Ok(files) = self.db_data(table_name, include_vanilla, include_parent) {
2204 values.extend(files.par_iter().filter_map(|file| {
2205 if let Ok(RFileDecoded::DB(table)) = file.decoded() {
2206 if let Some(column) = table.definition().column_position_by_name(key_column_name) {
2207 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<_>>())
2208 } else { None }
2209 } else { None }
2210 }).flatten().collect::<Vec<_>>());
2211 }
2212
2213 if let Some(packs) = packs {
2214 for pack in packs.values() {
2215 let files = pack.files_by_path(&ContainerPath::Folder(format!("db/{table_name}")), true);
2216 values.extend(files.par_iter().filter_map(|file| {
2217 if let Ok(RFileDecoded::DB(table)) = file.decoded() {
2218 if let Some(column) = table.definition().column_position_by_name(key_column_name) {
2219 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<_>>())
2220 } else { None }
2221 } else { None }
2222 }).flatten().collect::<Vec<_>>());
2223 }
2224 }
2225
2226 values
2227 }
2228
2229 pub fn update_db(&mut self, rfile: &mut RFileDecoded) -> Result<(i32, i32, Vec<String>, Vec<String>)> {
2233 match rfile {
2234 RFileDecoded::DB(data) => {
2235 let dep_db_undecoded = self.db_data(data.table_name(), true, false)?;
2236 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<_>>();
2237
2238 if let Some(vanilla_db) = dep_db_decoded.iter().max_by(|x, y| x.definition().version().cmp(y.definition().version())) {
2239
2240 let definition_new = vanilla_db.definition();
2241 let definition_old = data.definition().clone();
2242 if definition_old != *definition_new {
2243 data.set_definition(definition_new);
2244
2245 let fields_old = definition_old.fields_processed();
2247 let fields_new = definition_new.fields_processed();
2248 let fields_deleted = fields_old.iter()
2249 .filter(|x| fields_new.iter().all(|y| y.name() != x.name()))
2250 .map(|x| x.name().to_owned())
2251 .collect::<Vec<_>>();
2252 let fields_added = fields_new.iter()
2253 .filter(|x| fields_old.iter().all(|y| y.name() != x.name()))
2254 .map(|x| x.name().to_owned())
2255 .collect::<Vec<_>>();
2256
2257 Ok((*definition_old.version(), *definition_new.version(), fields_deleted, fields_added))
2258 }
2259 else {
2260 Err(RLibError::NoDefinitionUpdateAvailable)
2261 }
2262 }
2263 else { Err(RLibError::NoTableInGameFilesToCompare) }
2264 }
2265 _ => Err(RLibError::DecodingDBNotADBTable),
2266 }
2267 }
2268
2269 pub fn generate_missing_loc_data(&self, packs: &mut BTreeMap<String, Pack>) -> Result<Vec<ContainerPath>> {
2271 let loc_data = self.loc_data(true, true)?;
2272 let mut existing_locs = HashMap::new();
2273
2274 for loc in &loc_data {
2275 if let Ok(RFileDecoded::Loc(ref data)) = loc.decoded() {
2276 existing_locs.extend(data.table().data().iter().map(|x| (x[0].data_to_string().to_string(), x[1].data_to_string().to_string())));
2277 }
2278 }
2279
2280 let mut all_paths = vec![];
2281 for pack in packs.values_mut() {
2282 all_paths.extend(pack.generate_missing_loc_data(&existing_locs)?);
2283 }
2284 Ok(all_paths)
2285 }
2286
2287 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<()> {
2289 let mut fields_still_not_found = vec![];
2290
2291 let loc_files = self.loc_data(true, false)?;
2293 let loc_table = loc_files.iter()
2294 .filter_map(|file| if let Ok(RFileDecoded::Loc(loc)) = file.decoded() { Some(loc) } else { None })
2295 .flat_map(|file| file.data().to_vec())
2296 .map(|entry| (entry[0].data_to_string().to_string(), entry[1].data_to_string().to_string()))
2297 .collect::<HashMap<_,_>>();
2298
2299 let ak_tables = match ak_files {
2300 Some(ref tables) => (**tables).clone(),
2301 None => HashMap::new(),
2302 };
2303
2304 let local_files: Vec<_> = match local_packs {
2306 Some(packs) => packs.values()
2307 .flat_map(|pack| pack.files_by_type(&[FileType::DB]))
2308 .filter_map(|x| match x.decoded() {
2309 Ok(RFileDecoded::DB(db)) => Some(db),
2310 _ => None,
2311 })
2312 .collect(),
2313 None => Vec::new(),
2314 };
2315
2316 let mut db_tables = if ak_files.is_some() {
2318 ak_tables.values().collect::<Vec<_>>()
2319 } else {
2320 self.db_and_loc_data(true, false, true, false)?
2321 .iter()
2322 .filter_map(|file| if let Ok(RFileDecoded::DB(table)) = file.decoded() { Some(table) } else { None })
2323 .collect::<Vec<_>>()
2324 };
2325
2326 db_tables.extend_from_slice(&local_files);
2327
2328 let mut db_tables_dedup: Vec<DB> = vec![];
2330 for table in &db_tables {
2331 match db_tables_dedup.iter_mut().find(|x| x.table_name() == table.table_name() && x.definition().version() == table.definition().version()) {
2332 Some(db_source) => *db_source = DB::merge(&[db_source, table])?,
2333 None => db_tables_dedup.push((*table).clone()),
2334 }
2335 }
2336
2337 for table in &db_tables_dedup {
2338 let definition = table.definition();
2339 let mut loc_fields = definition.localised_fields().to_vec();
2340
2341 let mut loc_fields_final = loc_fields.to_vec();
2344
2345 if let Some(ref loc_fields_info) = locs {
2347 loc_fields.clear();
2348
2349 if let Some(loc_names) = loc_fields_info.get(&table.table_name_without_tables()) {
2350 for name in loc_names {
2351 if loc_fields.iter().all(|x| x.name() != name) {
2352
2353 let mut field = Field::default();
2354 field.set_name(name.to_string());
2355 field.set_field_type(FieldType::StringU8);
2356
2357 loc_fields.push(field);
2358 }
2359 }
2360 }
2361 }
2362
2363 let fields = definition.fields_processed();
2364 let key_fields = fields.iter()
2365 .enumerate()
2366 .filter(|(_, field)| field.is_key(None))
2367 .collect::<Vec<_>>();
2368
2369 let short_table_name = table.table_name_without_tables();
2371 for localised_field in &loc_fields {
2372 let localised_key = format!("{}_{}_", short_table_name, localised_field.name());
2373
2374 if loc_table.keys().any(|x| x.starts_with(&localised_key)) && loc_fields_final.iter().all(|x| x.name() != localised_field.name()) {
2376 loc_fields_final.push(localised_field.clone());
2377 }
2378 }
2379
2380 for table_field in &fields {
2383 if loc_fields_final.iter().all(|x| !x.name().starts_with(table_field.name())) {
2384 let localised_key = format!("{}_{}_", short_table_name, table_field.name());
2385 if loc_table.keys().any(|x| x.starts_with(&localised_key)) && loc_fields_final.iter().all(|x| x.name() != table_field.name()) {
2386 loc_fields_final.push(table_field.clone());
2387 }
2388 }
2389 }
2390
2391 for loc_field in &loc_fields {
2392 if loc_fields_final.iter().all(|x| x.name() != loc_field.name()) {
2393 fields_still_not_found.push(format!("{}/{}", table.table_name_without_tables(), loc_field.name()));
2394 }
2395 }
2396
2397 if let Some(ak_files) = &mut ak_files {
2399 let ak_table = ak_files.get_mut(table.table_name()).unwrap();
2400 let mut definition = ak_table.definition().clone();
2401 definition.set_localised_fields(loc_fields_final.to_vec());
2402 ak_table.set_definition(&definition);
2403
2404 } else if let Some(schema_definition) = schema.definition_by_name_and_version_mut(table.table_name(), *definition.version()) {
2405 schema_definition.set_localised_fields(loc_fields_final.to_vec());
2406 }
2407
2408 if !loc_fields_final.is_empty() {
2410
2411 let order = if key_fields.len() == 1 {
2413 vec![key_fields[0].0 as u32]
2414 }
2415
2416 else {
2418 let mut order = Vec::with_capacity(key_fields.len());
2419 let combos = key_fields.iter().permutations(key_fields.len());
2420 let table_data = table.data();
2421 for combo in combos {
2422
2423 let mut combo_is_valid = true;
2426 for row in table_data.iter() {
2427 let mut combined_key = String::new();
2435 for (index, _) in &combo {
2436 combined_key.push_str(&row[*index].data_to_string());
2437 }
2438
2439 for localised_field in &loc_fields_final {
2440 let localised_key = format!("{}_{}_{}", short_table_name, localised_field.name(), combined_key);
2441 match loc_table.get(&localised_key) {
2442 Some(_) => {
2443 if order.is_empty() {
2444 order = combo.iter().map(|(index, _)| *index as u32).collect();
2445 }
2446 }
2447 None => {
2448 combo_is_valid = false;
2449 break;
2450 }
2451 }
2452 }
2453
2454 if !combo_is_valid {
2456 break;
2457 }
2458 }
2459
2460 if !combo_is_valid {
2462 order = vec![];
2463 continue;
2464 }
2465
2466 if !order.is_empty() {
2467 break;
2468 }
2469 }
2470
2471 order
2472 };
2473
2474 if !order.is_empty() && !loc_fields_final.is_empty() {
2475 info!("Bruteforce: loc key order found for table {}, version {}.", table.table_name(), definition.version());
2476 if let Some(ak_files) = &mut ak_files {
2477 let ak_table = ak_files.get_mut(table.table_name()).unwrap();
2478 let mut definition = ak_table.definition().clone();
2479 definition.set_localised_key_order(order);
2480 ak_table.set_definition(&definition);
2481 } else if let Some(schema_definition) = schema.definition_by_name_and_version_mut(table.table_name(), *definition.version()) {
2482 schema_definition.set_localised_key_order(order);
2483 }
2484 } else {
2485 info!("Bruteforce: loc key order found (but may be incorrect) for table {}, version {}.", table.table_name(), definition.version());
2486
2487 if loc_fields_final.is_empty() {
2489 if let Some(ak_files) = &mut ak_files {
2490 let ak_table = ak_files.get_mut(table.table_name()).unwrap();
2491 let mut definition = ak_table.definition().clone();
2492 definition.set_localised_key_order(vec![]);
2493 ak_table.set_definition(&definition);
2494 } else if let Some(schema_definition) = schema.definition_by_name_and_version_mut(table.table_name(), *definition.version()) {
2495 schema_definition.set_localised_key_order(vec![]);
2496 }
2497 }
2498 }
2499 }
2500
2501 else if let Some(ak_files) = &mut ak_files {
2503 let ak_table = ak_files.get_mut(table.table_name()).unwrap();
2504 let mut definition = ak_table.definition().clone();
2505 definition.set_localised_key_order(vec![]);
2506 ak_table.set_definition(&definition);
2507 } else if let Some(schema_definition) = schema.definition_by_name_and_version_mut(table.table_name(), *definition.version()) {
2508 schema_definition.set_localised_key_order(vec![]);
2509 }
2510 }
2511
2512 fields_still_not_found.sort();
2514 fields_still_not_found.dedup();
2515 info!("Bruteforce: fields still not found :{fields_still_not_found:#?}");
2516
2517 if ak_files.is_none() {
2520 for key in loc_table.keys().sorted() {
2521 if self.loc_key_source(key).is_none() {
2522 info!("-- Bruteforce: cannot find source for loc key {key}.");
2523 }
2524 }
2525 }
2526
2527 Ok(())
2528 }
2529
2530 #[allow(clippy::if_same_then_else)]
2532 pub fn generate_automatic_patches(&self, schema: &mut Schema, packs: &BTreeMap<String, Pack>) -> Result<()> {
2533 let mut db_tables = self.db_and_loc_data(true, false, true, false)?
2534 .iter()
2535 .filter_map(|file| if let Ok(RFileDecoded::DB(table)) = file.decoded() { Some(table) } else { None })
2536 .collect::<Vec<_>>();
2537
2538 for pack in packs.values() {
2539 db_tables.extend_from_slice(&pack.files_by_type(&[FileType::DB])
2540 .iter()
2541 .filter_map(|x| if let Ok(RFileDecoded::DB(db)) = x.decoded() {
2542 Some(db)
2543 } else {
2544 None
2545 })
2546 .collect::<Vec<_>>()
2547 );
2548 }
2549
2550 let current_patches = schema.patches_mut();
2551 let mut new_patches: HashMap<String, DefinitionPatch> = HashMap::new();
2552
2553 let image_paths = self.vanilla_files()
2555 .keys()
2556 .filter(|x| x.ends_with(".png") || x.ends_with(".tga"))
2557 .collect::<Vec<_>>();
2558
2559 let video_paths = self.vanilla_files()
2560 .keys()
2561 .filter(|x| x.ends_with(".ca_vp8"))
2562 .collect::<Vec<_>>();
2563
2564 for table in &db_tables {
2565 let definition = table.definition();
2566 let fields = definition.fields_processed();
2567 for (column, field) in fields.iter().enumerate() {
2568 match field.field_type() {
2569 FieldType::StringU8 |
2570 FieldType::StringU16 |
2571 FieldType::OptionalStringU8 |
2572 FieldType::OptionalStringU16 => {
2573
2574 let mut possible_icon = false;
2580 let low_name = field.name().to_lowercase();
2581 if (low_name.contains("icon") || low_name.contains("image")) &&
2582
2583 !(table.table_name() == "building_sets_tables" && field.name() == "icon") &&
2585
2586 !(table.table_name() == "character_traits_tables" && field.name() == "icon") {
2588 possible_icon = true;
2589 }
2590
2591 let mut possible_relative_paths = table.data().par_iter()
2593 .filter_map(|row| {
2594
2595 if !field.is_filename(None) || (
2597 field.is_filename(None) && (
2598 field.filename_relative_path(None).is_none() ||
2599 field.filename_relative_path(None).unwrap().is_empty()
2600 )
2601 ) || (
2602
2603 (table.table_name() == "advisors_tables" && field.name() == "advisor_icon_path") ||
2605
2606 (table.table_name() == "campaign_post_battle_captive_options_tables" && field.name() == "icon_path") ||
2608
2609 (table.table_name() == "narrative_viewer_tabs_tables" && field.name() == "image_path") ||
2611
2612 (table.table_name() == "technology_ui_groups_tables" && field.name() == "optional_background_image")
2614 ) {
2615
2616 let mut data = row[column].data_to_string().to_lowercase().replace("\\", "/");
2621
2622 if data.starts_with("/") {
2624 if data.len() > 1 {
2625 data = data[1..].to_owned();
2626 } else {
2627 data = String::new();
2628 }
2629 }
2630
2631 if !data.is_empty() && !data.ends_with("/") &&
2632 data != "." &&
2633 data != "x" &&
2634 data != "false" &&
2635 data != "building_placeholder" &&
2636 data != "placehoder.png" &&
2637 data != "placeholder" &&
2638 data != "placeholder.tga" &&
2639 data != "placeholder.png" && (
2640 possible_icon ||
2641 data.ends_with(".png") || data.ends_with(".tga")
2642 ) {
2643
2644 let possible_paths = image_paths.iter()
2645
2646 .filter(|x| {
2648 if table.table_name() == "aide_de_camp_speeches_tables" && field.name() == "icon_name" {
2649 x.starts_with("ui/battle ui/adc_icons/")
2650 } else if table.table_name() == "agent_string_subculture_overrides_tables" && field.name() == "icon_path" {
2651 x.starts_with("ui/campaign ui/agents/icons/")
2652 } else if table.table_name() == "ancillary_types_tables" && field.name() == "ui_icon" {
2653 x.starts_with("ui/portraits/ancillaries/")
2654 } else if table.table_name() == "battlefield_building_categories_tables" && field.name() == "icon_path" {
2655 x.starts_with("ui/battle ui/building icons/")
2656 } else if table.table_name() == "bonus_value_uis_tables" && field.name() == "icon" {
2657 x.starts_with("ui/campaign ui/effect_bundles/")
2658 } else if table.table_name() == "building_culture_variants_tables" && field.name() == "icon" {
2659 x.starts_with("ui/buildings/icons/")
2660 } else if table.table_name() == "campaign_payload_ui_details_tables" && field.name() == "icon" {
2661 x.starts_with("ui/campaign ui/effect_bundles/")
2662 } else if table.table_name() == "campaign_post_battle_captive_options_tables" && field.name() == "icon_path" {
2663 x.starts_with("ui/campaign ui/captive_option_icons/")
2664 } else if table.table_name() == "capture_point_types_tables" && field.name() == "icon_name" {
2665 x.starts_with("ui/battle ui/capture_point_icons/")
2666 } else if table.table_name() == "character_skills_tables" && field.name() == "image_path" {
2667 x.starts_with("ui/campaign ui/skills/")
2668 } else if table.table_name() == "character_traits_tables" && field.name() == "icon_custom" {
2669 x.starts_with("ui/campaign ui/effect_bundles/")
2670
2671 } else if table.table_name() == "cursors_tables" && field.name() == "image" {
2673 !x.starts_with(&(data.to_owned() + "_"))
2674 } else if table.table_name() == "dilemmas_tables" && field.name() == "ui_image" {
2675 x.starts_with("ui/eventpics/")
2676 } else if table.table_name() == "effect_bundles_tables" && field.name() == "ui_icon" {
2677 x.starts_with("ui/campaign ui/effect_bundles/")
2678 } else if table.table_name() == "effects_tables" && (field.name() == "icon" || field.name() == "icon_negative") {
2679 x.starts_with("ui/campaign ui/effect_bundles/")
2680 } else if table.table_name() == "faction_groups_tables" && field.name() == "ui_icon" {
2681 x.starts_with("ui/campaign ui/effect_bundles/")
2682 } else if table.table_name() == "incidents_tables" && field.name() == "ui_image" {
2683 x.starts_with("ui/eventpics/")
2684 } else if table.table_name() == "message_event_strings_tables" && field.name() == "image" {
2685 x.starts_with("ui/eventpics/")
2686 } else if table.table_name() == "missions_tables" && field.name() == "ui_icon" {
2687 x.starts_with("ui/campaign ui/message_icons/")
2688
2689 } else if table.table_name() == "missions_tables" && field.name() == "ui_image" {
2691 x.starts_with("ui/eventpics/") && x.ends_with(&(data.to_owned() + ".png"))
2692 } else if table.table_name() == "pooled_resources_tables" && field.name() == "optional_icon_path" {
2693 x.starts_with("ui/skins/")
2694 } else if table.table_name() == "projectile_shot_type_enum_tables" && field.name() == "icon_name" {
2695 x.starts_with("ui/battle ui/ability_icons/")
2696 } else if table.table_name() == "religions_tables" && field.name() == "ui_icon_path" {
2697 x.starts_with("ui/campaign ui/religion_icons/")
2698 } else if table.table_name() == "special_ability_phases_tables" && field.name() == "ticker_icon" {
2699 x.starts_with("ui/battle ui/ability_icons/")
2700 } else if table.table_name() == "technologies_tables" && field.name() == "icon_name" {
2701 x.starts_with("ui/campaign ui/technologies/")
2702 } else if table.table_name() == "technologies_tables" && field.name() == "info_pic" {
2703 x.starts_with("ui/eventpics/")
2704 } else if table.table_name() == "trait_categories_tables" && field.name() == "icon_path" {
2705 x.starts_with("ui/campaign ui/effect_bundles/")
2706 } else if table.table_name() == "ui_unit_groupings_tables" && field.name() == "icon" {
2707 x.starts_with("ui/common ui/unit_category_icons/")
2708 } else if table.table_name() == "victory_types_tables" && field.name() == "icon" {
2709 x.starts_with("ui/campaign ui/victory_type_icons/")
2710
2711 } else if table.table_name() == "videos_tables" && field.name() == "video_name" {
2713 x.starts_with("movies/")
2714 } else {
2715 true
2716 }
2717 })
2718
2719 .filter(|x| if !data.ends_with('_') {
2724 if !data.contains("/") {
2725 if !data.contains('.') {
2726 x.contains(&("/".to_owned() + &data + "."))
2727 } else {
2728 x.contains(&("/".to_owned() + &data))
2729 }
2730 } else {
2731 x.contains(&data)
2732 }
2733 } else {
2734 false
2735 })
2736
2737 .filter_map(|x| x.rfind(&data).map(|pos| (x, pos)))
2739 .map(|(x, pos)| x[..pos].to_owned() + &x[pos..].replacen(&data, "%", 1))
2740 .collect::<Vec<_>>();
2741
2742
2743 if !possible_paths.is_empty() {
2744 return Some(possible_paths)
2745 }
2746 }
2747 }
2748
2749 None
2750 })
2751 .flatten()
2752 .collect::<HashSet<String>>();
2753
2754 let mut possible_video = false;
2760 if low_name.contains("video") {
2761 possible_video = true;
2762 }
2763
2764 possible_relative_paths.extend(
2765 table.data().par_iter().filter_map(|row| {
2766
2767 if !field.is_filename(None) || (
2769 field.is_filename(None) && (
2770 field.filename_relative_path(None).is_none() ||
2771 field.filename_relative_path(None).unwrap().is_empty()
2772 )
2773 ) || (
2774
2775 table.table_name() == "videos_tables" && field.name() == "video_name"
2777 ) {
2778
2779 let mut data = row[column].data_to_string().to_lowercase().replace("\\", "/");
2780
2781 if data.starts_with("/") {
2783 if data.len() > 1 {
2784 data = data[1..].to_owned();
2785 } else {
2786 data = String::new();
2787 }
2788 }
2789
2790 if !data.is_empty() && (
2791 possible_video ||
2792 data.ends_with(".ca_vp8")
2793 ) {
2794
2795 let possible_paths = video_paths.iter()
2796 .filter(|x| {
2797 if table.table_name() == "videos_tables" && field.name() == "video_name" {
2798 x.starts_with("movies/")
2799 } else {
2800 true
2801 }
2802 })
2803 .filter(|x| if !data.contains('.') {
2807 x.contains(&("/".to_owned() + &data + "."))
2808 } else {
2809 x.contains(&("/".to_owned() + &data))
2810 })
2811
2812 .filter_map(|x| x.rfind(&data).map(|pos| (x, pos)))
2814 .map(|(x, pos)| x[..pos].to_owned() + &x[pos..].replacen(&data, "%", 1))
2815 .collect::<Vec<_>>();
2816
2817
2818 if !possible_paths.is_empty() {
2819 return Some(possible_paths)
2820 }
2821 }
2822 }
2823
2824 None
2825 })
2826 .flatten()
2827 .collect::<HashSet<String>>()
2828 );
2829
2830 if !possible_relative_paths.is_empty() && (possible_relative_paths.len() > 1 || (possible_relative_paths.len() == 1 && possible_relative_paths.iter().collect::<Vec<_>>()[0] != "%")) {
2832 info!("Checking table {}, field {} ...", table.table_name(), field.name());
2833 dbg!(&possible_relative_paths);
2834 }
2835
2836 if (table.table_name() == "models_building_tables" && field.name() == "logic_file") ||
2840 (table.table_name() == "models_sieges_tables" && (field.name() == "model_file" || field.name() == "logic_file" || field.name() == "collision_file")) ||
2841 (table.table_name() == "models_deployables_tables" && (field.name() == "model_file" || field.name() == "logic_file" || field.name() == "collision_file")) {
2842 possible_relative_paths.clear();
2843 possible_relative_paths.insert("%".to_owned());
2844 }
2845
2846 if (table.table_name() == "ui_mercenary_recruitment_infos_tables" && field.name() == "hire_button_icon_path") ||
2848 (table.table_name() == "battles_tables" && (field.name() == "specification" || field.name() == "battle_environment_audio")) ||
2849 (table.table_name() == "factions_tables" && field.name() == "key") ||
2850 (table.table_name() == "frontend_faction_leaders_tables" && field.name() == "key") {
2851 let mut patch = HashMap::new();
2852 patch.insert("is_filename".to_owned(), "false".to_owned());
2853
2854 match new_patches.get_mut(table.table_name()) {
2855 Some(patches) => match patches.get_mut(field.name()) {
2856 Some(patches) => patches.extend(patch),
2857 None => { patches.insert(field.name().to_owned(), patch); }
2858 },
2859 None => {
2860 let mut table_patch = HashMap::new();
2861 table_patch.insert(field.name().to_owned(), patch);
2862 new_patches.insert(table.table_name().to_string(), table_patch);
2863 }
2864 }
2865 }
2866
2867 if !possible_relative_paths.is_empty() {
2869 let mut possible_relative_paths = possible_relative_paths.iter().collect::<Vec<_>>();
2870 possible_relative_paths.sort();
2871
2872 let mut patch = HashMap::new();
2873 if !field.is_filename(None) {
2874 patch.insert("is_filename".to_owned(), "true".to_owned());
2875 }
2876
2877 if possible_relative_paths.len() > 1 || (
2879 (
2880 possible_relative_paths.len() == 1 &&
2881 possible_relative_paths[0].contains('%') &&
2882 possible_relative_paths[0] != "%"
2883 ) || (
2884 possible_relative_paths[0] == "%" &&
2885 field.filename_relative_path(None).is_some() &&
2886 !field.filename_relative_path(None).unwrap().is_empty()
2887 )
2888 ) {
2889 patch.insert("filename_relative_path".to_owned(), possible_relative_paths.into_iter().join(";"));
2890 }
2891
2892 if !patch.is_empty() {
2894 match new_patches.get_mut(table.table_name()) {
2895 Some(patches) => match patches.get_mut(field.name()) {
2896 Some(patches) => patches.extend(patch),
2897 None => { patches.insert(field.name().to_owned(), patch); }
2898 },
2899 None => {
2900 let mut table_patch = HashMap::new();
2901 table_patch.insert(field.name().to_owned(), patch);
2902 new_patches.insert(table.table_name().to_string(), table_patch);
2903 }
2904 }
2905 }
2906 }
2907 }
2925 FieldType::I64 |
2926 FieldType::OptionalI64 => {
2927 }
2945 _ => continue
2946 }
2947 }
2948 }
2949
2950 Schema::add_patches_to_patch_set(current_patches, &new_patches);
2951
2952 Ok(())
2953 }
2954
2955 #[allow(clippy::too_many_arguments)]
2959 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>)> {
2960 let mut added_paths = vec![];
2961
2962 let pack = match pack_key {
2964 Some(key) => packs.get_mut(key).ok_or_else(|| RLibError::NoPacksProvided)?,
2965 None => packs.values_mut().next().ok_or_else(|| RLibError::NoPacksProvided)?,
2966 };
2967
2968 for tile_map in &tile_maps {
2970 added_paths.append(&mut pack.insert_folder(tile_map, "terrain/battles", &None, &None, true)?);
2971 }
2972
2973 for (tile, subpath) in &tiles {
2975
2976 let (internal_path, needs_tile_database) = if subpath.is_empty() {
2977 ("terrain/tiles/battle".to_owned(), false)
2978 } else {
2979 (format!("terrain/tiles/battle/{}", subpath.replace('\\', "/")), true)
2980 };
2981 added_paths.append(&mut pack.insert_folder(tile, &internal_path, &None, &None, true)?);
2982
2983 if needs_tile_database {
2985
2986 let subpath_len = subpath.replace('\\', "/").split('/').count();
2988 let mut tile_database = tile.to_path_buf();
2989
2990 (0..=subpath_len).for_each(|_| {
2991 tile_database.pop();
2992 });
2993
2994 let file_name = format!("{}_{}.bin", subpath.replace('/', "_"), tile.file_name().unwrap().to_string_lossy());
2995 tile_database.push(format!("_tile_database/TILES/{file_name}"));
2996 let tile_database_path = format!("terrain/tiles/battle/_tile_database/TILES/{file_name}");
2997
2998 added_paths.push(pack.insert_file(&tile_database, &tile_database_path, &None)?.unwrap());
2999 }
3000 }
3001
3002 let (paths_to_delete, paths_to_add) = pack.optimize(Some(added_paths.clone()), self, schema, game, &options)?;
3003
3004 let paths_to_delete = paths_to_delete.iter()
3005 .map(|path| ContainerPath::File(path.to_string()))
3006 .collect::<Vec<_>>();
3007
3008 added_paths.extend(paths_to_add.into_iter()
3009 .map(|path| ContainerPath::File(path.to_string()))
3010 .collect::<Vec<_>>());
3011
3012 Ok((added_paths, paths_to_delete))
3013 }
3014
3015 #[allow(clippy::too_many_arguments)]
3019 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<()> {
3020
3021 let map_names = if process_hlp_spd_data {
3023 self.db_values_from_table_name_and_column_name_for_value(Some(packs), "campaigns_tables", "campaign_name", "map_name", true, true)
3024 } else {
3025 HashMap::new()
3026 };
3027
3028 let pack_file = match pack_key {
3030 Some(key) => packs.get_mut(key).ok_or_else(|| RLibError::NoPacksProvided)?,
3031 None => packs.values_mut().next().ok_or_else(|| RLibError::NoPacksProvided)?,
3032 };
3033 let pack_name = pack_file.disk_file_name();
3034 if pack_name.is_empty() {
3035 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()));
3036 }
3037
3038 if campaign_id.is_empty() {
3039 return Err(RLibError::BuildStartposError("campaign_id not provided.".to_owned()));
3040 }
3041
3042 let process_hlp_spd_data_string = if process_hlp_spd_data {
3043 String::from("process_campaign_ai_map_data;")
3044 } else {
3045 String::new()
3046 };
3047
3048 let extra_folders = "add_working_directory assembly_kit\\working_data;";
3051 let mut user_script_contents = if game.key() == KEY_ATTILA || game.key() == KEY_THRONES_OF_BRITANNIA { extra_folders.to_owned() } else { String::new() };
3052
3053 user_script_contents.push_str(&format!("
3054 mod {pack_name};
3055 process_campaign_startpos {campaign_id} {sub_start_pos};
3056 {process_hlp_spd_data_string}
3057 quit_after_campaign_processing;"
3058 ));
3059
3060 let game_data_path = game.data_path(game_path)?;
3062 if !game_path.is_dir() {
3063 return Err(RLibError::BuildStartposError("Game path incorrect. Fix it in the settings and try again.".to_owned()));
3064 }
3065
3066 if !PathBuf::from(pack_file.disk_file_path()).starts_with(&game_data_path) {
3067 return Err(RLibError::BuildStartposError("The Pack needs to be in /data. Install it there and try again.".to_owned()));
3068 }
3069
3070 if GAMES_NEEDING_VICTORY_OBJECTIVES.contains(&game.key()) {
3072 let mut game_campaign_path = game_data_path.to_path_buf();
3073 game_campaign_path.push(campaign_id);
3074 DirBuilder::new().recursive(true).create(&game_campaign_path)?;
3075
3076 game_campaign_path.push(VICTORY_OBJECTIVES_EXTRACTED_FILE_NAME);
3077 pack_file.extract(ContainerPath::File(VICTORY_OBJECTIVES_FILE_NAME.to_owned()), &game_campaign_path, false, &None, true, false, &None)?;
3078 }
3079
3080 let config_path = game.config_path(game_path).ok_or(RLibError::BuildStartposError("Error getting the game's config path.".to_owned()))?;
3081 let scripts_path = config_path.join("scripts");
3082 DirBuilder::new().recursive(true).create(&scripts_path)?;
3083
3084 if game.key() != KEY_ROME_2 {
3088
3089 let uspa = scripts_path.join(USER_SCRIPT_FILE_NAME);
3091 let uspb = scripts_path.join(USER_SCRIPT_FILE_NAME.to_owned() + ".bak");
3092
3093 if uspa.is_file() {
3094 std::fs::copy(&uspa, uspb)?;
3095 }
3096
3097 let mut file = BufWriter::new(File::create(uspa)?);
3098
3099 if *game.raw_db_version() < 2 {
3101 file.write_string_u16(&user_script_contents)?;
3102 } else {
3103 file.write_all(user_script_contents.as_bytes())?;
3104 }
3105
3106 file.flush()?;
3107 }
3108
3109 if game.key() != KEY_THRONES_OF_BRITANNIA &&
3114 game.key() != KEY_ATTILA &&
3115 game.key() != KEY_SHOGUN_2 {
3116
3117 let sub_start_pos_suffix = if sub_start_pos.is_empty() {
3118 String::new()
3119 } else {
3120 format!("_{sub_start_pos}")
3121 };
3122
3123 let starpos_path = game_data_path.join(format!("campaigns/{campaign_id}/startpos{sub_start_pos_suffix}.esf"));
3124 if starpos_path.is_file() {
3125 let starpos_path_bak = game_data_path.join(format!("campaigns/{campaign_id}/startpos{sub_start_pos_suffix}.esf.bak"));
3126 std::fs::copy(&starpos_path, starpos_path_bak)?;
3127 std::fs::remove_file(starpos_path)?;
3128 }
3129 }
3130
3131 if process_hlp_spd_data {
3133 if let Some(map_name) = map_names.get(campaign_id) {
3134 match game.key() {
3135
3136 KEY_PHARAOH_DYNASTIES |
3140 KEY_PHARAOH |
3141 KEY_WARHAMMER_3 |
3142 KEY_TROY |
3143 KEY_THREE_KINGDOMS |
3144 KEY_WARHAMMER_2 |
3145 KEY_WARHAMMER => {
3146 let hlp_folder_path = game_data_path.join(format!("campaign_maps/{map_name}"));
3147 if !hlp_folder_path.is_dir() {
3148 DirBuilder::new().recursive(true).create(&hlp_folder_path)?;
3149 }
3150
3151 let hlp_path = game_data_path.join(format!("campaign_maps/{map_name}/hlp_data.esf"));
3152 if hlp_path.is_file() {
3153 let hlp_path_bak = game_data_path.join(format!("campaign_maps/{map_name}/hlp_data.esf.bak"));
3154 std::fs::copy(&hlp_path, hlp_path_bak)?;
3155 std::fs::remove_file(hlp_path)?;
3156 }
3157 },
3158
3159 KEY_THRONES_OF_BRITANNIA |
3167 KEY_ATTILA => {
3168 let folder_path = config_path.join(format!("maps/campaign_maps/{map_name}"));
3169
3170 let (sender, receiver) = channel::<bool>();
3171 let join = thread::spawn(move || {
3172 loop {
3173 match receiver.try_recv() {
3174 Ok(stop) => if stop {
3175 break;
3176 }
3177 Err(_) => {
3178 if !folder_path.is_dir() {
3179 let _ = DirBuilder::new().recursive(true).create(&folder_path);
3180 }
3181
3182 thread::sleep(Duration::from_millis(100));
3183 }
3184 }
3185 }
3186 });
3187
3188 *START_POS_WORKAROUND_THREAD.write().unwrap() = Some(vec![(sender, join)]);
3189 },
3190
3191 KEY_ROME_2 => {
3196 let hlp_folder = game_data_path.join(format!("campaign_maps/{map_name}/"));
3197 if hlp_folder.is_dir() {
3198 let _ = DirBuilder::new().recursive(true).create(&hlp_folder);
3199 }
3200
3201 let hlp_path = hlp_folder.join("hlp_data.esf");
3202 if hlp_path.is_file() {
3203 let hlp_path_bak = game_data_path.join(format!("campaign_maps/{map_name}/hlp_data.esf.bak"));
3204 std::fs::copy(&hlp_path, hlp_path_bak)?;
3205 std::fs::remove_file(hlp_path)?;
3206 }
3207
3208 }
3209 KEY_SHOGUN_2 => return Err(RLibError::BuildStartposError("Unsupported... yet. If you want to test support for this game, let me know.".to_owned())),
3210 KEY_NAPOLEON => return Err(RLibError::BuildStartposError("Unsupported... yet. If you want to test support for this game, let me know.".to_owned())),
3211 KEY_EMPIRE => return Err(RLibError::BuildStartposError("Unsupported... yet. If you want to test support for this game, let me know.".to_owned())),
3212 _ => return Err(RLibError::BuildStartposError("How the fuck did you trigger this?".to_owned())),
3213 }
3214
3215 if game.key() != KEY_THRONES_OF_BRITANNIA &&
3217 game.key() != KEY_ATTILA &&
3218 game.key() != KEY_ROME_2 &&
3219 game.key() != KEY_SHOGUN_2 &&
3220 game.key() != KEY_NAPOLEON &&
3221 game.key() != KEY_EMPIRE {
3222
3223 let spd_path = game_data_path.join(format!("campaign_maps/{map_name}/spd_data.esf"));
3224 if spd_path.is_file() {
3225 let spd_path_bak = game_data_path.join(format!("campaign_maps/{map_name}/spd_data.esf.bak"));
3226 std::fs::copy(&spd_path, spd_path_bak)?;
3227 std::fs::remove_file(spd_path)?;
3228 }
3229 }
3230 }
3231 }
3232
3233 if game.key() == KEY_THREE_KINGDOMS {
3235 let exe_path = game.executable_path(game_path).ok_or_else(|| RLibError::BuildStartposError("Game exe path not found.".to_owned()))?;
3236 let exe_name = exe_path.file_name().ok_or_else(|| RLibError::BuildStartposError("Game exe name not found.".to_owned()))?.to_string_lossy();
3237
3238 let mut command = Command::new("cmd");
3240 command.arg("/C");
3241 command.arg("start");
3242 command.arg("/wait");
3243 command.arg("/d");
3244 command.arg(game_path.to_string_lossy().replace('\\', "/"));
3245 command.arg(exe_name.to_string());
3246 command.arg("temp_file.txt;");
3247
3248 let _ = command.output()?;
3249
3250 let uspa = scripts_path.join(USER_SCRIPT_FILE_NAME);
3252 let uspb = scripts_path.join(USER_SCRIPT_FILE_NAME.to_owned() + ".bak");
3253 if uspb.is_file() {
3254 std::fs::copy(uspb, uspa)?;
3255 }
3256
3257 else if uspa.is_file() {
3259 std::fs::remove_file(uspa)?;
3260 }
3261
3262 } else if game.key() == KEY_ROME_2 {
3264 let exe_path = game.executable_path(game_path).ok_or_else(|| RLibError::BuildStartposError("Game exe path not found.".to_owned()))?;
3265 let exe_name = exe_path.file_name().ok_or_else(|| RLibError::BuildStartposError("Game exe name not found.".to_owned()))?.to_string_lossy();
3266
3267 let mut command = Command::new("cmd");
3269 command.arg("/C");
3270 command.arg("start");
3271 command.arg("/d");
3272 command.arg(game_path.to_string_lossy().replace('\\', "/"));
3273 command.arg(exe_name.to_string());
3274 command.arg("temp_file.txt;");
3275
3276 #[cfg(target_os = "windows")] {
3278 use std::os::windows::process::CommandExt;
3279
3280 command.raw_arg(extra_folders);
3282 command.raw_arg(user_script_contents.replace("\n", " "));
3283 }
3284
3285 command.spawn()?;
3286 } else {
3287 match game.game_launch_command(game_path) {
3288 Ok(command) => { let _ = open::that(command); },
3289 _ => return Err(RLibError::BuildStartposError("The currently selected game cannot be launched from Steam.".to_owned())),
3290 }
3291 }
3292
3293 Ok(())
3294 }
3295
3296 #[allow(clippy::too_many_arguments)]
3303 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>> {
3304
3305 let map_names = if process_hlp_spd_data {
3307 self.db_values_from_table_name_and_column_name_for_value(Some(packs), "campaigns_tables", "campaign_name", "map_name", true, true)
3308 } else {
3309 HashMap::new()
3310 };
3311
3312 let pack_file = match pack_key {
3314 Some(key) => packs.get_mut(key).ok_or_else(|| RLibError::NoPacksProvided)?,
3315 None => packs.values_mut().next().ok_or_else(|| RLibError::NoPacksProvided)?,
3316 };
3317
3318 let mut startpos_failed = false;
3319 let mut sub_startpos_failed = vec![];
3320 let mut hlp_failed = false;
3321 let mut spd_failed = false;
3322
3323 if let Some(data) = START_POS_WORKAROUND_THREAD.write().unwrap().as_mut() {
3325 let (sender, handle) = data.remove(0);
3326 let _ = sender.send(true);
3327 let _ = handle.join();
3328 }
3329
3330 *START_POS_WORKAROUND_THREAD.write().unwrap() = None;
3331
3332 if !game_path.is_dir() {
3333 return Err(RLibError::BuildStartposError("Game path incorrect. Fix it in the settings and try again.".to_owned()));
3334 }
3335
3336 let game_data_path = game.data_path(game_path)?;
3337
3338 if GAMES_NEEDING_VICTORY_OBJECTIVES.contains(&game.key()) {
3340
3341 let mut game_campaign_path = game_data_path.to_path_buf();
3343 game_campaign_path.push(campaign_id);
3344 if game_campaign_path.is_dir() {
3345 let _ = std::fs::remove_dir_all(game_campaign_path);
3346 }
3347 }
3348
3349 let config_path = game.config_path(game_path).ok_or(RLibError::BuildStartposError("Error getting the game's config path.".to_owned()))?;
3350 let scripts_path = config_path.join("scripts");
3351 if !scripts_path.is_dir() {
3352 DirBuilder::new().recursive(true).create(&scripts_path)?;
3353 }
3354
3355 let uspa = scripts_path.join(USER_SCRIPT_FILE_NAME);
3357 let uspb = scripts_path.join(USER_SCRIPT_FILE_NAME.to_owned() + ".bak");
3358 if uspb.is_file() {
3359 std::fs::copy(uspb, uspa)?;
3360 }
3361
3362 else if uspa.is_file() {
3364 std::fs::remove_file(uspa)?;
3365 }
3366
3367 let mut added_paths = vec![];
3368
3369 let starpos_paths = match game.key() {
3371 KEY_PHARAOH_DYNASTIES |
3372 KEY_PHARAOH |
3373 KEY_WARHAMMER_3 |
3374 KEY_TROY |
3375 KEY_THREE_KINGDOMS |
3376 KEY_WARHAMMER_2 |
3377 KEY_WARHAMMER => {
3378 if sub_start_pos.is_empty() {
3379 vec![game_data_path.join(format!("campaigns/{campaign_id}/startpos.esf"))]
3380 } else {
3381 let mut paths = vec![];
3382 for sub in sub_start_pos {
3383 paths.push(game_data_path.join(format!("campaigns/{campaign_id}/startpos_{sub}.esf")));
3384
3385 }
3386 paths
3387 }
3388 }
3389 KEY_THRONES_OF_BRITANNIA |
3390 KEY_ATTILA => vec![config_path.join(format!("maps/campaigns/{campaign_id}/startpos.esf"))],
3391
3392 KEY_ROME_2 => {
3394 match asskit_path {
3395 Some(asskit_path) => {
3396 if !asskit_path.is_dir() {
3397 return Err(RLibError::BuildStartposError("Assembly Kit path is not a valid folder.".to_owned()));
3398 }
3399
3400 vec![asskit_path.join(format!("working_data/campaigns/{campaign_id}/startpos.esf"))]
3401 },
3402 None => return Err(RLibError::BuildStartposError("Assembly Kit path not provided.".to_owned())),
3403 }
3404 },
3405
3406 KEY_SHOGUN_2 |
3409 KEY_NAPOLEON |
3410 KEY_EMPIRE => vec![game_data_path.join(format!("campaigns/{campaign_id}/startpos.esf"))],
3411 _ => return Err(RLibError::BuildStartposError("How the fuck did you trigger this?".to_owned())),
3412 };
3413
3414 let starpos_paths_pack = if sub_start_pos.is_empty() {
3415 vec![format!("campaigns/{}/startpos.esf", campaign_id)]
3416 } else {
3417 let mut paths = vec![];
3418 for sub in sub_start_pos {
3419 paths.push(format!("campaigns/{campaign_id}/startpos_{sub}.esf"));
3420 }
3421 paths
3422 };
3423
3424 if !cleanup_mode {
3425 for (index, starpos_path) in starpos_paths.iter().enumerate() {
3426 if !starpos_path.is_file() {
3427 if sub_start_pos.is_empty() {
3428 startpos_failed = true;
3429 } else {
3430 sub_startpos_failed.push(sub_start_pos[index].to_owned());
3431 }
3432 } else {
3433
3434 let mut rfile = RFile::new_from_file_path(starpos_path)?;
3435 rfile.set_path_in_container_raw(&starpos_paths_pack[index]);
3436 rfile.load()?;
3437 rfile.guess_file_type()?;
3438
3439 added_paths.push(pack_file.insert(rfile).map(|x| x.unwrap())?);
3440 }
3441 }
3442 }
3443
3444 if game.key() != KEY_THRONES_OF_BRITANNIA &&
3450 game.key() != KEY_ATTILA &&
3451 game.key() != KEY_SHOGUN_2 {
3452
3453 for starpos_path in &starpos_paths {
3454 let file_name = starpos_path.file_name().unwrap().to_string_lossy().to_string();
3455 let file_name_bak = file_name + ".bak";
3456
3457 let mut starpos_path_bak = starpos_path.to_path_buf();
3458 starpos_path_bak.set_file_name(file_name_bak);
3459
3460 if starpos_path_bak.is_file() {
3461 std::fs::copy(&starpos_path_bak, starpos_path)?;
3462 std::fs::remove_file(starpos_path_bak)?;
3463 }
3464 }
3465 }
3466
3467 if game.key() == KEY_SHOGUN_2 {
3469 for starpos_path in &starpos_paths {
3470 if starpos_path.is_file() {
3471 std::fs::remove_file(starpos_path)?;
3472 }
3473 }
3474 }
3475
3476 if process_hlp_spd_data {
3478 if let Some(map_name) = map_names.get(campaign_id) {
3479
3480 let hlp_path = match game.key() {
3482 KEY_PHARAOH_DYNASTIES |
3483 KEY_PHARAOH |
3484 KEY_WARHAMMER_3 |
3485 KEY_TROY |
3486 KEY_THREE_KINGDOMS |
3487 KEY_WARHAMMER_2 |
3488 KEY_WARHAMMER => game_data_path.join(format!("campaign_maps/{map_name}/hlp_data.esf")),
3489 KEY_THRONES_OF_BRITANNIA |
3490 KEY_ATTILA => config_path.join(format!("maps/campaign_maps/{map_name}/hlp_data.esf")),
3491 KEY_ROME_2 => game_data_path.join(format!("campaign_maps/{map_name}/hlp_data.esf")),
3492 _ => return Err(RLibError::BuildStartposError("How the fuck did you trigger this?".to_owned())),
3493 };
3494
3495 let hlp_path_pack = format!("campaign_maps/{map_name}/hlp_data.esf");
3496
3497 if !cleanup_mode {
3498
3499 if !hlp_path.is_file() {
3500 hlp_failed = true;
3501 } else {
3502
3503 let mut rfile_hlp = RFile::new_from_file_path(&hlp_path)?;
3504 rfile_hlp.set_path_in_container_raw(&hlp_path_pack);
3505 rfile_hlp.load()?;
3506 rfile_hlp.guess_file_type()?;
3507
3508 added_paths.push(pack_file.insert(rfile_hlp).map(|x| x.unwrap())?);
3509 }
3510 }
3511
3512 if game.key() != KEY_THRONES_OF_BRITANNIA &&
3514 game.key() != KEY_ATTILA {
3515
3516 let hlp_path_bak = game_data_path.join(format!("campaign_maps/{map_name}/hlp_data.esf.bak"));
3517
3518 if hlp_path_bak.is_file() {
3519 std::fs::copy(&hlp_path_bak, hlp_path)?;
3520 std::fs::remove_file(hlp_path_bak)?;
3521 }
3522 }
3523
3524 if game.key() != KEY_THRONES_OF_BRITANNIA &&
3526 game.key() != KEY_ATTILA &&
3527 game.key() != KEY_ROME_2 {
3528
3529 let spd_path = game_data_path.join(format!("campaign_maps/{map_name}/spd_data.esf"));
3530 let spd_path_pack = format!("campaign_maps/{map_name}/spd_data.esf");
3531
3532 if !cleanup_mode {
3533
3534 if !spd_path.is_file() {
3535 spd_failed = true;
3536 } else {
3537
3538 let mut rfile_spd = RFile::new_from_file_path(&spd_path)?;
3539 rfile_spd.set_path_in_container_raw(&spd_path_pack);
3540 rfile_spd.load()?;
3541 rfile_spd.guess_file_type()?;
3542
3543 added_paths.push(pack_file.insert(rfile_spd).map(|x| x.unwrap())?);
3544 }
3545 }
3546
3547 let spd_path_bak = game_data_path.join(format!("campaign_maps/{map_name}/spd_data.esf.bak"));
3548 if spd_path_bak.is_file() {
3549 std::fs::copy(&spd_path_bak, spd_path)?;
3550 std::fs::remove_file(spd_path_bak)?;
3551 }
3552 }
3553 }
3554 }
3555
3556 let mut error = String::new();
3557 if startpos_failed || (!sub_start_pos.is_empty() && !sub_startpos_failed.is_empty()) || hlp_failed || spd_failed {
3558 error.push_str("<p>One or more files failed to generate:</p><ul>")
3559 }
3560 if startpos_failed {
3561 error.push_str("<li>Startpos file failed to generate.</li>");
3562 }
3563
3564 for sub_failed in &sub_startpos_failed {
3565 error.push_str(&format!("<li>\"{sub_failed}\" Startpos file failed to generate.</li>"));
3566 }
3567
3568 if hlp_failed {
3569 error.push_str("<li>HLP file failed to generate.</li>");
3570 }
3571
3572 if spd_failed {
3573 error.push_str("<li>SPD file failed to generate.</li>");
3574 }
3575
3576 if startpos_failed || hlp_failed || spd_failed {
3577 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>")
3578 }
3579
3580 if error.is_empty() {
3581 Ok(added_paths)
3582 } else {
3583 Err(RLibError::BuildStartposError(error))
3584 }
3585 }
3586
3587 pub fn import_from_ak(&self, table_name: &str, schema: &Schema) -> Result<DB> {
3591 let definition = if let Some(definitions) = schema.definitions_by_table_name_cloned(table_name) {
3592 if !definitions.is_empty() {
3593 definitions[0].clone()
3594 } else {
3595 return Err(RLibError::DecodingDBNoDefinitionsFound)
3596 }
3597 } else {
3598 return Err(RLibError::DecodingDBNoDefinitionsFound)
3599 };
3600
3601 if let Some(ak_file) = self.asskit_only_db_tables().get(table_name) {
3603 let mut real_table = ak_file.clone();
3604 real_table.set_definition(&definition);
3605 Ok(real_table)
3606 } else {
3607 Err(RLibError::AssemblyKitTableNotFound(table_name.to_owned()))
3608 }
3609 }
3610
3611 pub fn insert_loc_as_vanilla_loc(&mut self, rfile: RFile) {
3619 let path = rfile.path_in_container_raw().to_owned();
3620 self.vanilla_files.insert(path.to_owned(), rfile);
3621 self.vanilla_locs.insert(path);
3622 }
3623
3624 pub fn add_recursive_lookups_to_definition(&self, schema: &Schema, definition: &mut Definition, table_name: &str) {
3628 let schema_patches = definition.patches().clone();
3629
3630 for field in definition.fields_mut().iter_mut() {
3631
3632 if let Some(lookup_data_old) = field.lookup(Some(&schema_patches)) {
3634 let mut lookup_data = vec![];
3635
3636 if !lookup_data_old.is_empty() {
3638
3639 let table_name = if let Some(table_name) = table_name.strip_suffix("_tables") {
3640 table_name.to_owned()
3641 } else {
3642 table_name.to_owned()
3643 };
3644
3645 for lookup_data_old in &lookup_data_old {
3646 let lookup_string = format!("{}#{}#{}", table_name, field.name(), lookup_data_old);
3647 self.add_recursive_lookups(schema, &schema_patches, lookup_data_old, &mut lookup_data, &lookup_string, &table_name);
3648 }
3649
3650 }
3651
3652 if let Some((ref_table_name, ref_column)) = field.is_reference(Some(&schema_patches)) {
3654 for lookup_data_old in &lookup_data_old {
3655 let lookup_string = format!("{ref_table_name}#{ref_column}#{lookup_data_old}");
3656 self.add_recursive_lookups(schema, &schema_patches, lookup_data_old, &mut lookup_data, &lookup_string, &ref_table_name);
3657 }
3658 }
3659
3660 if !lookup_data.is_empty() {
3661 field.set_lookup(Some(lookup_data));
3662 } else {
3663 field.set_lookup(None);
3664 }
3665 }
3666 }
3667 }
3668
3669 fn add_recursive_lookups(&self,
3670 schema: &Schema,
3671 schema_patches: &HashMap<String, HashMap<String, String>>,
3672 lookup: &str,
3673 lookup_data: &mut Vec<String>,
3674 lookup_string: &str,
3675 table_name: &str
3676 ) {
3677 let mut finish_lookup = false;
3678 let table_name = table_name.to_string() + "_tables";
3679 if let Ok(ref_tables) = self.db_data(&table_name, true, true) {
3680 let candidates = ref_tables.iter()
3681 .filter_map(|rfile| rfile.decoded().ok())
3682 .filter_map(|decoded| if let RFileDecoded::DB(db) = decoded {
3683 Some(db.definition().clone())
3684 } else {
3685 None
3686 })
3687 .collect::<Vec<_>>();
3688
3689 if let Some(definition) = schema.definition_newer(&table_name, &candidates) {
3690
3691 if let Some(pos) = definition.column_position_by_name(lookup) {
3693 if let Some(field) = definition.fields_processed().get(pos) {
3694
3695 if let Some((ref_table_name, ref_column)) = field.is_reference(Some(schema_patches)) {
3697 if let Some(lookups) = field.lookup(Some(schema_patches)) {
3698 for lookup in &lookups {
3699 let lookup_string = format!("{lookup_string}:{ref_table_name}#{ref_column}#{lookup}");
3700
3701 self.add_recursive_lookups(schema, schema_patches, lookup, lookup_data, &lookup_string, &ref_table_name);
3702 }
3703 } else {
3704 finish_lookup = true;
3705 }
3706 } else {
3707 finish_lookup = true;
3708 }
3709 } else {
3710 finish_lookup = true;
3711 }
3712 }
3713
3714 else if definition.localised_fields().iter().any(|x| x.name() == lookup) {
3715 finish_lookup = true;
3716 }
3717 } else {
3718 finish_lookup = true;
3719 }
3720 }
3721
3722 if finish_lookup && !lookup_data.iter().any(|x| x == lookup_string) {
3723 lookup_data.push(lookup_string.to_owned());
3724 }
3725 }
3726}