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