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