1use bitflags::bitflags;
60use getset::*;
61use rayon::prelude::*;
62use serde_derive::{Serialize, Deserialize};
63use serde_json::{from_slice, to_string_pretty};
64use itertools::Itertools;
65use tempfile::NamedTempFile;
66#[cfg(unix)]
67use tempfile::Builder;
68
69use std::cmp::Ordering;
70use std::collections::{BTreeMap, HashMap, HashSet};
71use std::fs::File;
72#[cfg(unix)]
73use std::fs::Permissions;
74use std::hash::{DefaultHasher, Hash, Hasher};
75use std::io::{BufReader, BufWriter, Cursor, SeekFrom, Write};
76#[cfg(unix)]
77use std::os::unix::fs::PermissionsExt;
78use std::path::{Path, PathBuf};
79use std::str::FromStr;
80
81use crate::binary::{ReadBytes, WriteBytes};
82use crate::compression::{Compressible, CompressionFormat};
83use crate::error::{RLibError, Result};
84use crate::files::{Container, ContainerPath, Decodeable, DecodeableExtraData, Encodeable, EncodeableExtraData, FileType, Loc, RFile, RFileDecoded, table::DecodedData};
85use crate::games::{GameInfo, pfh_file_type::PFHFileType, pfh_version::PFHVersion, supported_games::KEY_SHOGUN_2_COMPLETE_EDITION};
86use crate::notes::Note;
87use crate::utils::{current_time, last_modified_time_from_file};
88
89#[cfg(test)]
90mod pack_test;
91mod pack_versions;
92
93pub const EXTENSION: &str = ".pack";
95
96const MFH_PREAMBLE: &str = "MFH";
98
99const TERRY_MAP_PATH: &str = "terrain/tiles/battle/_assembly_kit";
101
102const DEFAULT_BMD_DATA: &str = "bmd_data.bin";
104
105const MISSING_LOCS_PATH_START_EXISTING: &str = "text/aaa_missing_locs_";
107const MISSING_LOCS_PATH_START_NEW: &str = "text/zzz_missing_locs_";
109
110const FORT_PERIMETER_HINT: &[u8; 18] = b"AIH_FORT_PERIMETER";
112const DEFENSIVE_HILL_HINT: &[u8; 18] = b"AIH_DEFENSIVE_HILL";
113const SIEGE_AREA_NODE_HINT: &[u8; 19] = b"AIH_SIEGE_AREA_NODE";
114
115pub const RESERVED_NAME_DEPENDENCIES_MANAGER: &str = "dependencies_manager.rpfm_reserved";
117pub const RESERVED_NAME_DEPENDENCIES_MANAGER_V2: &str = "dependencies_manager_v2.rpfm_reserved";
119pub const RESERVED_NAME_EXTRA_PACKFILE: &str = "extra_packfile.rpfm_reserved";
121pub const RESERVED_NAME_SETTINGS: &str = "settings.rpfm_reserved";
123pub const RESERVED_NAME_SETTINGS_EXTRACTED: &str = "settings.rpfm_reserved.json";
125pub const RESERVED_NAME_NOTES: &str = "notes.rpfm_reserved";
127pub const RESERVED_NAME_NOTES_EXTRACTED: &str = "notes.rpfm_reserved.md";
129
130pub const RESERVED_RFILE_NAMES: [&str; 3] = [RESERVED_NAME_EXTRA_PACKFILE, RESERVED_NAME_SETTINGS, RESERVED_NAME_NOTES];
135
136const AUTHORING_TOOL_CA: &str = "CA_TOOL";
138const AUTHORING_TOOL_RPFM: &str = "RPFM";
140const AUTHORING_TOOL_SIZE: u32 = 8;
142
143pub const SETTING_KEY_CF: &str = "compression_format";
145
146bitflags! {
147
148 #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
154 pub struct PFHFlags: u32 {
155
156 const HAS_EXTENDED_HEADER = 0b0000_0001_0000_0000;
158
159 const HAS_ENCRYPTED_INDEX = 0b0000_0000_1000_0000;
161
162 const HAS_INDEX_WITH_TIMESTAMPS = 0b0000_0000_0100_0000;
164
165 const HAS_ENCRYPTED_DATA = 0b0000_0000_0001_0000;
167 const UK1 = 0b1000_0000_0000_0000;
168 const UK2 = 0b0100_0000_0000_0000;
169 const UK3 = 0b0010_0000_0000_0000;
170 const UK4 = 0b0001_0000_0000_0000;
171 const UK5 = 0b0000_1000_0000_0000;
172 const UK6 = 0b0000_0100_0000_0000;
173 const UK7 = 0b0000_0010_0000_0000;
174
175 const HAS_COMPRESSED_DATA = 0b0000_0000_0010_0000;
178 const UK9 = 0b0000_0000_0000_1000;
179 const UK10 = 0b0000_0000_0000_0100;
180 const UK11 = 0b0000_0000_0000_0010;
181 const UK12 = 0b0000_0000_0000_0001;
182 }
183
184 #[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
188 pub struct PFHVersionFlags: u8 {
189
190 const HAS_ALIGNED_DATA = 0b0100_0000;
192 }
193}
194
195#[derive(Debug, Clone, PartialEq, Getters, MutGetters, Setters, Default, Serialize, Deserialize)]
239#[getset(get = "pub", get_mut = "pub", set = "pub")]
240pub struct Pack {
241
242 disk_file_path: String,
244
245 disk_file_offset: u64,
247
248 local_timestamp: u64,
250
251 #[getset(skip)]
253 compress: bool,
254
255 header: PackHeader,
257
258 dependencies: Vec<(bool, String)>,
262
263 files: HashMap<String, RFile>,
265
266 paths: HashMap<String, Vec<String>>,
268
269 notes: PackNotes,
271
272 settings: PackSettings,
274}
275
276#[derive(Debug, Clone, PartialEq, Eq, Getters, Setters, Serialize, Deserialize)]
306#[getset(get = "pub", set = "pub")]
307pub struct PackHeader {
308
309 pfh_version: PFHVersion,
311
312 pfh_version_flags: PFHVersionFlags,
314
315 pfh_file_type: PFHFileType,
317
318 bitmask: PFHFlags,
320
321 internal_timestamp: u64,
323
324 game_version: u32,
326
327 build_number: u32,
329
330 authoring_tool: String,
332
333 extra_subheader_data: Vec<u8>,
335}
336
337#[derive(Clone, Debug, PartialEq, Eq, Getters, MutGetters, Setters, Serialize, Deserialize)]
351#[getset(get = "pub", get_mut = "pub", set = "pub")]
352pub struct PackSettings {
353
354 settings_text: BTreeMap<String, String>,
356
357 settings_string: BTreeMap<String, String>,
359
360 settings_bool: BTreeMap<String, bool>,
362
363 settings_number: BTreeMap<String, i32>,
365}
366
367#[derive(Clone, Debug, PartialEq, Eq, Default, Getters, MutGetters, Setters, Serialize, Deserialize)]
373#[getset(get = "pub", get_mut = "pub", set = "pub")]
374pub struct PackNotes {
375
376 pack_notes: String,
378
379 file_notes: HashMap<String, Vec<Note>>,
384}
385
386pub type DiagnosticIgnoreEntry = (String, Vec<String>, Vec<String>);
390
391impl Container for Pack {
396
397 fn extract_metadata(&mut self, destination_path: &Path) -> Result<Vec<PathBuf>> {
401 let mut paths = vec![];
402 let mut data = vec![];
403 data.write_all(to_string_pretty(&self.notes)?.as_bytes())?;
404 data.extend_from_slice(b"\n"); let path = destination_path.join(RESERVED_NAME_NOTES_EXTRACTED);
407 paths.push(path.to_owned());
408 let mut file = BufWriter::new(File::create(path)?);
409 file.write_all(&data)?;
410 file.flush()?;
411
412 let mut data = vec![];
413 data.write_all(to_string_pretty(&self.settings)?.as_bytes())?;
414 data.extend_from_slice(b"\n"); let path = destination_path.join(RESERVED_NAME_SETTINGS_EXTRACTED);
417 paths.push(path.to_owned());
418 let mut file = BufWriter::new(File::create(path)?);
419 file.write_all(&data)?;
420 file.flush()?;
421
422 Ok(paths)
423 }
424
425 fn insert(&mut self, mut file: RFile) -> Result<Option<ContainerPath>> {
426
427 let path_container = file.path_in_container();
429 let path = file.path_in_container_raw();
430 if path == RESERVED_NAME_NOTES_EXTRACTED {
431 self.notes = PackNotes::load(&file.encode(&None, false, false, true)?.unwrap()).unwrap_or_default();
432 Ok(None)
433 } else if path == RESERVED_NAME_SETTINGS_EXTRACTED {
434 self.settings = PackSettings::load(&file.encode(&None, false, false, true)?.unwrap()).unwrap_or_default();
435 Ok(None)
436 } else if path == RESERVED_NAME_DEPENDENCIES_MANAGER_V2 {
437 self.dependencies = from_slice(&file.encode(&None, false, false, true)?.unwrap()).unwrap_or_default();
438 Ok(None)
439 }
440
441 else {
443 self.paths_cache_insert_path(path);
444 self.files.insert(path.to_owned(), file);
445
446 Ok(Some(path_container))
447 }
448 }
449
450 fn disk_file_path(&self) -> &str {
451 &self.disk_file_path
452 }
453
454 fn files(&self) -> &HashMap<String, RFile> {
455 &self.files
456 }
457
458 fn files_mut(&mut self) -> &mut HashMap<String, RFile> {
459 &mut self.files
460 }
461
462 fn disk_file_offset(&self) -> u64 {
463 self.disk_file_offset
464 }
465
466 fn paths_cache(&self) -> &HashMap<String, Vec<String>> {
467 &self.paths
468 }
469
470 fn paths_cache_mut(&mut self) -> &mut HashMap<String, Vec<String>> {
471 &mut self.paths
472 }
473
474 fn internal_timestamp(&self) -> u64 {
475 self.header.internal_timestamp
476 }
477
478 fn local_timestamp(&self) -> u64 {
479 self.local_timestamp
480 }
481
482 fn move_path(&mut self, source_path: &ContainerPath, destination_path: &ContainerPath) -> Result<Vec<(ContainerPath, ContainerPath)>> {
486 match source_path {
487 ContainerPath::File(source_path) => match destination_path {
488 ContainerPath::File(destination_path) => {
489 if RESERVED_RFILE_NAMES.contains(&&**destination_path) {
490 return Err(RLibError::ReservedFiles);
491 }
492
493 if destination_path.is_empty() {
494 return Err(RLibError::EmptyDestiny);
495 }
496
497 self.paths_cache_remove_path(source_path);
498 let mut moved = self.files_mut()
499 .remove(source_path)
500 .ok_or_else(|| RLibError::FileNotFound(source_path.to_string()))?;
501
502 moved.set_path_in_container_raw(destination_path);
503
504 self.insert(moved).map(|x| match x {
505 Some(x) => vec![(ContainerPath::File(source_path.to_string()), x); 1],
506 None => Vec::with_capacity(0),
507 })
508 },
509 ContainerPath::Folder(_) => unreachable!("move_path_pack_1"),
510 },
511 ContainerPath::Folder(source_path) => match destination_path {
512 ContainerPath::File(_) => unreachable!("move_path_pack_2"),
513 ContainerPath::Folder(destination_path) => {
514 if destination_path.is_empty() {
515 return Err(RLibError::EmptyDestiny);
516 }
517
518 let mut source_path_end = source_path.to_owned();
520 if !source_path_end.ends_with('/') {
521 source_path_end.push('/');
522 }
523
524 let moved_paths = self.files()
525 .par_iter()
526 .filter_map(|(path, _)| if path.starts_with(&source_path_end) { Some(path.to_owned()) } else { None })
527 .collect::<Vec<_>>();
528
529 let moved = moved_paths.iter()
530 .filter_map(|x| {
531 self.paths_cache_remove_path(x);
532 self.files_mut().remove(x)
533 })
534 .collect::<Vec<_>>();
535
536 let mut new_paths = Vec::with_capacity(moved.len());
537 for mut moved in moved {
538 let old_path = moved.path_in_container();
539 let new_path = moved.path_in_container_raw().replacen(source_path, destination_path, 1);
540 moved.set_path_in_container_raw(&new_path);
541
542 if let Some(new_path) = self.insert(moved)? {
543 new_paths.push((old_path, new_path));
544 }
545 }
546
547 Ok(new_paths)
548 },
549 },
550 }
551 }
552}
553
554impl Decodeable for Pack {
555
556 fn decode<R: ReadBytes>(data: &mut R, extra_data: &Option<DecodeableExtraData>) -> Result<Self> {
557 Self::read(data, extra_data)
558 }
559}
560
561impl Encodeable for Pack {
562
563 fn encode<W: WriteBytes>(&mut self, buffer: &mut W, extra_data: &Option<EncodeableExtraData>) -> Result<()> {
564 self.write(buffer, extra_data)
565 }
566}
567
568impl Pack {
570
571 pub fn new_with_version(pfh_version: PFHVersion) -> Self {
573 let mut pack = Self::default();
574 pack.header.pfh_version = pfh_version;
575 pack
576 }
577
578 pub fn new_with_name_and_version(name: &str, pfh_version: PFHVersion) -> Self {
580 let mut pack = Self::default();
581 pack.header.pfh_version = pfh_version;
582 pack.disk_file_path = name.to_owned();
583 pack
584 }
585
586 fn read<R: ReadBytes>(data: &mut R, extra_data: &Option<DecodeableExtraData>) -> Result<Self> {
590 let extra_data = extra_data.as_ref().ok_or(RLibError::DecodingMissingExtraData)?;
591
592 let game_info = match extra_data.game_info {
594 Some(game_info) => game_info,
595 None => return Err(RLibError::GameInfoMissingFromDecodingFunction),
596 };
597
598 let disk_file_path = match extra_data.disk_file_path {
601 Some(path) => {
602 let file_path = PathBuf::from_str(path).map_err(|_|RLibError::DecodingMissingExtraDataField("disk_file_path".to_owned()))?;
603 if file_path.is_file() {
604 path.to_owned()
605 } else {
606 return Err(RLibError::DecodingMissingExtraData)
607 }
608 }
609 None => String::new()
610 };
611
612 let disk_file_offset = extra_data.disk_file_offset;
613 let disk_file_size = if extra_data.data_size > 0 { extra_data.data_size } else { data.len()? };
614 let timestamp = extra_data.timestamp;
615 let is_encrypted = extra_data.is_encrypted;
616 let skip_path_cache_generation = extra_data.skip_path_cache_generation;
617
618 let lazy_load = !disk_file_path.is_empty() && !is_encrypted && extra_data.lazy_load;
620
621 let data_len = disk_file_size;
624 if data_len < 24 {
625 return Err(RLibError::PackHeaderNotComplete);
626 }
627
628 let start = if data.read_string_u8(3)? == MFH_PREAMBLE { 8 } else { 0 };
630 data.seek(SeekFrom::Current(-3))?;
631 data.seek(SeekFrom::Current(start))?;
632
633 let mut pack = Self {
635 disk_file_path,
636 disk_file_offset,
637 local_timestamp: timestamp,
638 ..Default::default()
639 };
640
641 let mut signature = data.read_slice(4, false)?;
643 pack.header.pfh_version_flags = PFHVersionFlags::from_bits_truncate(signature[3] & 0b1100_0000);
644 signature[3] &= 0b0011_1111;
645 pack.header.pfh_version = PFHVersion::version(&String::from_utf8_lossy(&signature))?;
646
647 let pack_type = data.read_u32()?;
648 pack.header.pfh_file_type = PFHFileType::try_from(pack_type & 15)?;
649 pack.header.bitmask = PFHFlags::from_bits_truncate(pack_type & !15);
650
651 let expected_data_len = match pack.header.pfh_version {
654 PFHVersion::PFH6 => pack.read_pfh6(data, extra_data)?,
655 PFHVersion::PFH5 => pack.read_pfh5(data, extra_data)?,
656 PFHVersion::PFH4 => pack.read_pfh4(data, extra_data)?,
657 PFHVersion::PFH3 => pack.read_pfh3(data, extra_data)?,
658 PFHVersion::PFH2 => pack.read_pfh2(data, extra_data)?,
659 PFHVersion::PFH0 => pack.read_pfh0(data, extra_data)?,
660 };
661
662 if let Some(mut notes) = pack.files.remove(RESERVED_NAME_NOTES) {
664 notes.load()?;
665 let data = notes.cached()?;
666
667 match PackNotes::load(data) {
670 Ok(notes) => pack.notes = notes,
671 Err(_) => {
672 let len = data.len();
673 let mut data = Cursor::new(data);
674 pack.notes = PackNotes::default();
675 pack.notes.pack_notes = data.read_string_u8(len)?;
676 }
677 }
678 }
679
680 if let Some(mut settings) = pack.files.remove(RESERVED_NAME_SETTINGS) {
681 settings.load()?;
682 let data = settings.cached()?;
683 pack.settings.load_and_update(data)?;
684 }
685
686 if let Some(mut deps) = pack.files.remove(RESERVED_NAME_DEPENDENCIES_MANAGER_V2) {
687 deps.load()?;
688 let data = deps.cached()?;
689 pack.dependencies = from_slice(data)?;
690 }
691
692 if !skip_path_cache_generation {
694 pack.paths_cache_generate();
695 }
696
697 let preferred_cf = game_info.compression_formats_supported().first().cloned().unwrap_or_default();
702 let current_cf_str = pack.settings().setting_string(SETTING_KEY_CF).cloned().unwrap_or_default();
703 let current_cf = CompressionFormat::from(&*current_cf_str);
704
705 if pack.compress && current_cf == CompressionFormat::None {
706 pack.settings_mut().set_setting_string(SETTING_KEY_CF, preferred_cf.to_string().as_str());
707 }
708
709 if expected_data_len != data_len { return Err(RLibError::DecodingMismatchSizeError(data_len as usize, expected_data_len as usize)) }
715
716 pack.files.par_iter_mut().map(|(_, file)| file.guess_file_type()).collect::<Result<()>>()?;
718
719 if !lazy_load {
721 pack.files.par_iter_mut().try_for_each(|(_, file)| file.load())?;
722 }
723
724 Ok(pack)
726 }
727
728 fn write<W: WriteBytes>(&mut self, buffer: &mut W, extra_data: &Option<EncodeableExtraData>) -> Result<()> {
730 let test_mode = if let Some(extra_data) = extra_data {
731 extra_data.test_mode
732 } else {
733 false
734 };
735
736 if !test_mode {
737
738 if self.header.pfh_file_type == PFHFileType::Mod || self.header.pfh_file_type == PFHFileType::Movie {
740
741 let mut data = vec![];
743 data.write_all(to_string_pretty(&self.notes)?.as_bytes())?;
744 let file = RFile::new_from_vec(&data, FileType::Text, 0, RESERVED_NAME_NOTES);
745 self.files.insert(RESERVED_NAME_NOTES.to_owned(), file);
746
747 let mut data = vec![];
749 data.write_all(to_string_pretty(&self.settings)?.as_bytes())?;
750 let file = RFile::new_from_vec(&data, FileType::Text, 0, RESERVED_NAME_SETTINGS);
751 self.files.insert(RESERVED_NAME_SETTINGS.to_owned(), file);
752
753 let mut data = vec![];
755 data.write_all(to_string_pretty(&self.dependencies)?.as_bytes())?;
756 let file = RFile::new_from_vec(&data, FileType::Text, 0, RESERVED_NAME_DEPENDENCIES_MANAGER_V2);
757 self.files.insert(RESERVED_NAME_DEPENDENCIES_MANAGER_V2.to_owned(), file);
758
759 }
760 }
761
762 match self.header.pfh_version {
763 PFHVersion::PFH6 => self.write_pfh6(buffer, extra_data)?,
764 PFHVersion::PFH5 => self.write_pfh5(buffer, extra_data)?,
765 PFHVersion::PFH4 => self.write_pfh4(buffer, extra_data)?,
766 PFHVersion::PFH3 => self.write_pfh3(buffer, extra_data)?,
767 PFHVersion::PFH2 => self.write_pfh2(buffer, extra_data)?,
768 PFHVersion::PFH0 => self.write_pfh0(buffer, extra_data)?,
769 }
770
771 self.remove(&ContainerPath::File(RESERVED_NAME_NOTES.to_owned()));
773 self.remove(&ContainerPath::File(RESERVED_NAME_SETTINGS.to_owned()));
774 self.remove(&ContainerPath::File(RESERVED_NAME_DEPENDENCIES_MANAGER_V2.to_owned()));
775
776 Ok(())
778 }
779
780 pub fn read_and_merge_ca_packs(game: &GameInfo, game_path: &Path) -> Result<Self> {
788 let paths = game.ca_packs_paths(game_path)?;
789 let mut pack = Self::read_and_merge(&paths, game, true, true, false)?;
790
791 pack.header_mut().set_pfh_file_type(PFHFileType::Release);
793 Ok(pack)
794 }
795
796 pub fn read_and_merge(pack_paths: &[PathBuf], game: &GameInfo, lazy_load: bool, ignore_mods: bool, keep_order: bool) -> Result<Self> {
800 if pack_paths.is_empty() {
801 return Err(RLibError::NoPacksProvided);
802 }
803
804 let mut extra_data = DecodeableExtraData {
805 lazy_load,
806 game_info: Some(game),
807 ..Default::default()
808 };
809
810 if pack_paths.len() == 1 {
812 let mut data = BufReader::new(File::open(&pack_paths[0])
813 .map_err(|error| RLibError::IOErrorPath(Box::new(RLibError::IOError(error)), pack_paths[0].to_path_buf()))?);
814 let path_str = pack_paths[0].to_string_lossy().replace('\\', "/");
815
816 extra_data.set_disk_file_path(Some(&path_str));
817 extra_data.set_timestamp(last_modified_time_from_file(data.get_ref()).unwrap());
818
819 return Self::read(&mut data, &Some(extra_data))
820 }
821
822 extra_data.set_skip_path_cache_generation(true);
824
825 let mut pack_new = Pack::default();
827 let mut packs = pack_paths.par_iter()
828 .map(|path| {
829 let mut data = BufReader::new(File::open(path)
830 .map_err(|error| RLibError::IOErrorPath(Box::new(RLibError::IOError(error)), pack_paths[0].to_path_buf()))?);
831 let path_str = path.to_string_lossy().replace('\\', "/");
832
833 let mut extra_data = extra_data.to_owned();
834 extra_data.set_disk_file_path(Some(&path_str));
835 extra_data.set_timestamp(last_modified_time_from_file(data.get_ref())?);
836
837 Self::read(&mut data, &Some(extra_data))
838 }).collect::<Result<Vec<Pack>>>()?;
839
840 packs.sort_by(|pack_a, pack_b| if pack_a.pfh_file_type() != pack_b.pfh_file_type() {
842 pack_a.pfh_file_type().cmp(&pack_b.pfh_file_type())
843 } else if !keep_order {
844 pack_a.disk_file_path.cmp(&pack_b.disk_file_path)
845 } else {
846 Ordering::Equal
847 });
848
849 packs.iter()
850 .chunk_by(|pack| pack.header.pfh_file_type)
851 .into_iter()
852 .for_each(|(pfh_type, packs)| {
853 if pfh_type != PFHFileType::Mod || !ignore_mods {
854 let mut packs = packs.collect::<Vec<_>>();
855 packs.reverse();
856 packs.iter()
857 .for_each(|pack| {
858 pack_new.files_mut().extend(pack.files().clone())
859 });
860 }
861 });
862
863 let pack_names = packs.iter().map(|pack| pack.disk_file_name()).collect::<Vec<_>>();
865 let mut dependencies = packs.iter()
866 .flat_map(|pack| pack.dependencies()
867 .iter()
868 .filter(|(_, dependency)| !pack_names.contains(dependency))
869 .cloned()
870 .collect::<Vec<_>>())
871 .collect::<Vec<_>>();
872
873 let mut set = HashSet::new();
875 dependencies.retain(|x| set.insert(x.clone()));
876 pack_new.set_dependencies(dependencies);
877
878 pack_new.set_pfh_file_type(packs[0].pfh_file_type());
880 pack_new.set_pfh_version(game.pfh_version_by_file_type(pack_new.pfh_file_type()));
881
882 pack_new.paths_cache_generate();
884
885 Ok(pack_new)
886 }
887
888 pub fn merge(packs: &[Self]) -> Result<Self> {
895 if packs.is_empty() {
896 return Err(RLibError::NoPacksProvided);
897 }
898
899 if packs.len() == 1 {
901 return Ok(packs[0].clone());
902 }
903
904 let mut pack_new = Pack::default();
906
907 let mut pfh_types = packs.iter().map(|pack| pack.pfh_file_type()).collect::<Vec<_>>();
908 pfh_types.sort();
909 pfh_types.dedup();
910
911 if pfh_types.len() == 1 {
912 pack_new.set_pfh_file_type(pfh_types[0]);
913 }
914
915 packs.iter()
916 .chunk_by(|pack| pack.header.pfh_file_type)
917 .into_iter()
918 .for_each(|(_, packs)| {
919 let mut packs = packs.collect::<Vec<_>>();
920 packs.reverse();
921 packs.iter()
922 .for_each(|pack| {
923 pack_new.files_mut().extend(pack.files().clone())
924 });
925 });
926
927 let pack_names = packs.iter().map(|pack| pack.disk_file_name()).collect::<Vec<_>>();
929 let mut dependencies = packs.iter()
930 .flat_map(|pack| pack.dependencies()
931 .iter()
932 .filter(|(_, dependency)| !pack_names.contains(dependency))
933 .cloned()
934 .collect::<Vec<_>>())
935 .collect::<Vec<_>>();
936
937 let mut set = HashSet::new();
939 dependencies.retain(|x| set.insert(x.clone()));
940 pack_new.set_dependencies(dependencies);
941
942 pack_new.set_pfh_file_type(packs[0].pfh_file_type());
944 pack_new.set_pfh_version(packs[0].pfh_version());
945
946 pack_new.paths_cache_generate();
948
949 Ok(pack_new)
950 }
951
952 pub fn save(&mut self, path: Option<&Path>, game_info: &GameInfo, extra_data: &Option<EncodeableExtraData>) -> Result<()> {
956 let disk_file_path = match path {
957 Some(path) => path.to_string_lossy().to_string(),
958 None => self.disk_file_path.clone(),
959 };
960
961 let extra_data = if extra_data.is_some() {
962 extra_data.clone()
963 } else {
964 Some(EncodeableExtraData::new_from_game_info(game_info))
965 };
966
967 let target = PathBuf::from(&disk_file_path);
970 let target = target.canonicalize().unwrap_or(target);
971 let parent = match target.parent() {
972 Some(parent) if !parent.as_os_str().is_empty() => parent.to_path_buf(),
973 _ => PathBuf::from("."),
974 };
975
976 let mut temp_file = Self::save_temp_file(&parent, &target)?;
977 {
978 let mut buffer = BufWriter::new(&mut temp_file);
979 self.encode(&mut buffer, &extra_data)?;
980 buffer.flush()?;
981 }
982 temp_file.persist(&target).map_err(|error| RLibError::from(error.error))?;
983 self.disk_file_path = disk_file_path;
984
985 let mut reloaded = Self::read_and_merge(&[target], game_info, true, false, false)?;
987 self.local_timestamp = reloaded.local_timestamp;
988 for (key, file) in self.files.iter_mut() {
989 if file.decoded().is_ok() {
990 continue;
991 }
992
993 if let Some(reloaded_file) = reloaded.files.remove(key) {
994 *file = reloaded_file;
995 }
996 }
997
998 Ok(())
999 }
1000
1001 fn save_temp_file(parent: &Path, target: &Path) -> Result<NamedTempFile> {
1014 #[cfg(unix)]
1016 let temp_file = Builder::new().permissions(Permissions::from_mode(0o666)).tempfile_in(parent)?;
1017 #[cfg(not(unix))]
1018 let temp_file = NamedTempFile::new_in(parent)?;
1019
1020 if let Ok(metadata) = target.metadata() {
1021 temp_file.as_file().set_permissions(metadata.permissions())?;
1022 }
1023
1024 Ok(temp_file)
1025 }
1026
1027 pub fn pfh_version(&self) -> PFHVersion {
1033 *self.header.pfh_version()
1034 }
1035
1036 pub fn pfh_file_type(&self) -> PFHFileType {
1038 *self.header.pfh_file_type()
1039 }
1040
1041 pub fn bitmask(&self) -> PFHFlags {
1043 *self.header.bitmask()
1044 }
1045
1046 pub fn internal_timestamp(&self) -> u64 {
1048 *self.header.internal_timestamp()
1049 }
1050
1051 pub fn game_version(&self) -> u32 {
1053 *self.header.game_version()
1054 }
1055
1056 pub fn build_number(&self) -> u32 {
1058 *self.header.build_number()
1059 }
1060
1061 pub fn authoring_tool(&self) -> &str {
1063 self.header.authoring_tool()
1064 }
1065
1066 pub fn extra_subheader_data(&self) -> &[u8] {
1068 self.header.extra_subheader_data()
1069 }
1070pub fn compression_format(&self) -> CompressionFormat {
1086 let cf = self.settings().setting_string(SETTING_KEY_CF).map(|x| x.to_owned());
1087 CompressionFormat::from(cf.unwrap_or_default().as_str())
1088 }
1089
1090 pub fn set_pfh_version(&mut self, version: PFHVersion) {
1092 self.header.set_pfh_version(version);
1093 }
1094
1095 pub fn set_pfh_file_type(&mut self, file_type: PFHFileType) {
1097 self.header.set_pfh_file_type(file_type);
1098 }
1099
1100 pub fn set_bitmask(&mut self, bitmask: PFHFlags) {
1102 self.header.set_bitmask(bitmask);
1103 }
1104
1105 pub fn set_internal_timestamp(&mut self, timestamp: u64) {
1107 self.header.set_internal_timestamp(timestamp);
1108 }
1109
1110 pub fn set_game_version(&mut self, game_version: u32) {
1112 self.header.set_game_version(game_version);
1113 }
1114
1115 pub fn set_build_number(&mut self, build_number: u32) {
1117 self.header.set_build_number(build_number);
1118 }
1119
1120 pub fn set_authoring_tool(&mut self, authoring_tool: &str) {
1122 self.header.set_authoring_tool(authoring_tool.to_string());
1123 }
1124
1125 pub fn set_extra_subheader_data(&mut self, extra_subheader_data: &[u8]) {
1127 self.header.set_extra_subheader_data(extra_subheader_data.to_vec());
1128 }
1129
1130 pub fn set_compression_format(&mut self, cf: CompressionFormat, game_info: &GameInfo) -> CompressionFormat {
1135 let cf = if game_info.key() == KEY_SHOGUN_2_COMPLETE_EDITION && self.supports_compression_format(CompressionFormat::Lz4Block) {
1136 CompressionFormat::Lz4Block
1137 } else {
1138 cf
1139 };
1140
1141 if cf == CompressionFormat::None || !game_info.compression_formats_supported().contains(&cf) || !self.supports_compression_format(cf) {
1142 self.compress = false;
1143 self.settings_mut().set_setting_string(SETTING_KEY_CF, CompressionFormat::None.to_string().as_str());
1144 CompressionFormat::None
1145 } else {
1146 self.compress = true;
1147 self.settings_mut().set_setting_string(SETTING_KEY_CF, cf.to_string().as_str());
1148 cf
1149 }
1150 }
1151
1152 pub fn spoof_ca_authoring_tool(&mut self, spoof: bool) {
1160 if spoof {
1161 self.header.set_authoring_tool(AUTHORING_TOOL_CA.to_string());
1162 } else {
1163 self.header.set_authoring_tool(AUTHORING_TOOL_RPFM.to_string());
1164 }
1165 }
1166
1167 pub fn is_compressible(&self) -> bool {
1169 matches!(self.header.pfh_version, PFHVersion::PFH6 | PFHVersion::PFH5 | PFHVersion::PFH3)
1170 }
1171
1172 pub fn supports_compression_format(&self, cf: CompressionFormat) -> bool {
1176 match self.header.pfh_version {
1177 PFHVersion::PFH6 | PFHVersion::PFH5 => cf != CompressionFormat::Lz4Block,
1178 PFHVersion::PFH3 => cf == CompressionFormat::Lz4Block,
1179 _ => false,
1180 }
1181 }
1182
1183 pub fn missing_locs_paths(&self) -> (String, String) {
1187 (
1188 MISSING_LOCS_PATH_START_EXISTING.to_owned() + &self.disk_file_name() + ".loc",
1189 MISSING_LOCS_PATH_START_NEW.to_owned() + &self.disk_file_name() + ".loc"
1190 )
1191 }
1192
1193 pub fn generate_missing_loc_data(&mut self, existing_locs: &HashMap<String, String>) -> Result<Vec<ContainerPath>> {
1195 let mut new_files = vec![];
1196
1197 let (missing_locs_path_existing, missing_locs_path_new) = self.missing_locs_paths();
1198
1199 let db_tables = self.files_by_type(&[FileType::DB]);
1200 let loc_tables = self.files_by_type(&[FileType::Loc]);
1201 let mut missing_trads_file_new = Loc::new();
1202 let mut missing_trads_file_overwritten = Loc::new();
1203
1204 let loc_keys_from_memory = loc_tables.par_iter().filter_map(|rfile| {
1205 if rfile.path_in_container_raw() != missing_locs_path_new && rfile.path_in_container_raw() != missing_locs_path_existing {
1206 if let Ok(RFileDecoded::Loc(table)) = rfile.decoded() {
1207 Some(table.data().iter().filter_map(|x| {
1208 if let DecodedData::StringU16(data) = &x[0] {
1209 Some(data.to_owned())
1210 } else {
1211 None
1212 }
1213 }).collect::<HashSet<String>>())
1214 } else { None }
1215 } else { None }
1216 }).flatten().collect::<HashSet<String>>();
1217
1218 let (missing_trads_new, missing_trads_overwritten) = db_tables.par_iter().filter_map(|rfile| {
1219 if let Ok(RFileDecoded::DB(table)) = rfile.decoded() {
1220 let definition = table.definition();
1221 let loc_fields = definition.localised_fields();
1222 let table_data = table.data();
1223 let table_name = table.table_name_without_tables();
1224 let fields_processed = definition.fields_processed();
1225
1226 let has_loc_fields = !loc_fields.is_empty();
1227 let is_building_culture_variants = table_name == "building_culture_variants";
1228
1229 if has_loc_fields || is_building_culture_variants {
1230 let localised_order = definition.localised_key_order();
1232 let mut new_rows_new = vec![];
1233 let mut new_rows_overwritten = vec![];
1234
1235 for row in table_data.iter() {
1236
1237 if has_loc_fields {
1239 for loc_field in loc_fields {
1240 let key = localised_order.iter().map(|pos| row[*pos as usize].data_to_string()).join("");
1241
1242 if !key.is_empty() {
1244 let loc_key = format!("{}_{}_{}", table_name, loc_field.name(), key);
1245
1246 if let Some(value) = existing_locs.get(&loc_key) {
1247 let mut new_row = missing_trads_file_overwritten.new_row();
1248 new_row[0] = DecodedData::StringU16(loc_key);
1249 new_row[1] = DecodedData::StringU16(value.to_owned());
1250 new_rows_overwritten.push(new_row);
1251
1252 } else if !loc_keys_from_memory.contains(&*loc_key) {
1253 let mut new_row = missing_trads_file_new.new_row();
1254 new_row[0] = DecodedData::StringU16(loc_key);
1255 new_row[1] = DecodedData::StringU16("PLACEHOLDER".to_owned());
1256 new_rows_new.push(new_row);
1257 }
1258 }
1259 }
1260 }
1261
1262 if is_building_culture_variants {
1266 if let Some(short_desc_index) = fields_processed.iter().position(|x| x.name() == "short_description") {
1267 let key = row[short_desc_index].data_to_string();
1268 if !key.is_empty() {
1269 let loc_key = format!("building_short_description_texts_short_description_{}", key);
1270
1271 if let Some(value) = existing_locs.get(&loc_key) {
1272 let mut new_row = missing_trads_file_overwritten.new_row();
1273 new_row[0] = DecodedData::StringU16(loc_key);
1274 new_row[1] = DecodedData::StringU16(value.to_owned());
1275 new_rows_overwritten.push(new_row);
1276
1277 } else if !loc_keys_from_memory.contains(&*loc_key) {
1278 let mut new_row = missing_trads_file_new.new_row();
1279 new_row[0] = DecodedData::StringU16(loc_key);
1280 new_row[1] = DecodedData::StringU16("PLACEHOLDER".to_owned());
1281 new_rows_new.push(new_row);
1282 }
1283 }
1284 }
1285 }
1286 }
1287
1288 return Some((new_rows_new, new_rows_overwritten))
1289 }
1290 }
1291 None
1292 }).collect::<(Vec<Vec<Vec<DecodedData>>>, Vec<Vec<Vec<DecodedData>>>)>();
1293
1294 let missing_trads_new = missing_trads_new.into_iter().flatten().collect::<Vec<_>>();
1296 let missing_trads_overwritten = missing_trads_overwritten.into_iter().flatten().collect::<Vec<_>>();
1297
1298 if !missing_trads_new.is_empty() {
1300 let _ = missing_trads_file_new.set_data(&missing_trads_new);
1301 let packed_file = RFile::new_from_decoded(&RFileDecoded::Loc(missing_trads_file_new), 0, &missing_locs_path_new);
1302 new_files.push(self.insert(packed_file)?.unwrap());
1303 }
1304
1305 if !missing_trads_overwritten.is_empty() && !self.settings.setting_bool("do_not_generate_existing_locs").unwrap_or(&false) {
1306 let _ = missing_trads_file_overwritten.set_data(&missing_trads_overwritten);
1307 let packed_file = RFile::new_from_decoded(&RFileDecoded::Loc(missing_trads_file_overwritten), 0, &missing_locs_path_existing);
1308 new_files.push(self.insert(packed_file)?.unwrap());
1309 }
1310
1311 Ok(new_files)
1312 }
1313
1314 pub fn patch_siege_ai(&mut self) -> Result<(String, Vec<ContainerPath>)> {
1318
1319 if self.files().is_empty() {
1321 return Err(RLibError::PatchSiegeAIEmptyPack)
1322 }
1323
1324 let mut files_patched = 0;
1325 let mut files_to_delete: Vec<ContainerPath> = vec![];
1326 let mut multiple_defensive_hill_hints = false;
1327
1328 for file in self.files_by_path_mut(&ContainerPath::Folder(TERRY_MAP_PATH.to_owned()), true) {
1330 let path = file.path_in_container_raw();
1331 let idx = path.rfind('/').unwrap_or(0);
1332 let name = if path.get(idx + 1..).is_some() {
1333 &path[idx + 1..]
1334 } else {
1335 continue
1336 };
1337
1338 if name == DEFAULT_BMD_DATA || (name.starts_with("catchment_") && name.ends_with(".bin")) {
1340 file.load()?;
1341 let data = file.cached_mut()?;
1342
1343 if data.windows(19).any(|window: &[u8]|window == SIEGE_AREA_NODE_HINT) {
1346 if let Some(index) = data.windows(18).position(|window: &[u8]|window == DEFENSIVE_HILL_HINT) {
1347 data.splice(index..index + 18, FORT_PERIMETER_HINT.iter().cloned());
1348 files_patched += 1;
1349 }
1350
1351 if data.windows(18).any(|window: &[u8]|window == DEFENSIVE_HILL_HINT) {
1353 multiple_defensive_hill_hints = true;
1354 }
1355 }
1356 }
1357
1358 else if name.ends_with(".xml") {
1360 files_to_delete.push(ContainerPath::File(file.path_in_container_raw().to_string()));
1361 }
1362 }
1363
1364 files_to_delete.iter().for_each(|x| { self.remove(x); });
1366
1367 if files_patched == 0 && files_to_delete.is_empty() { Err(RLibError::PatchSiegeAINoPatchableFiles) }
1369
1370 else if files_patched == 0 {
1373 Ok((format!("No file suitable for patching has been found.\n{} files deleted.", files_to_delete.len()), files_to_delete))
1374 }
1375
1376 else if multiple_defensive_hill_hints {
1378
1379 if files_to_delete.is_empty() {
1381 Ok((format!("{files_patched} files patched.\nNo file suitable for deleting has been found.\
1382 \n\n\
1383 WARNING: Multiple Defensive Hints have been found and we only patched the first one.\
1384 If you are using SiegeAI, you should only have one Defensive Hill in the map (the \
1385 one acting as the perimeter of your fort/city/castle). Due to SiegeAI being present, \
1386 in the map, normal Defensive Hills will not work anyways, and the only thing they do \
1387 is interfere with the patching process. So, if your map doesn't work properly after \
1388 patching, delete all the extra Defensive Hill Hints. They are the culprit."), files_to_delete))
1389 }
1390 else {
1391 Ok((format!("{} files patched.\n{} files deleted.\
1392 \n\n\
1393 WARNING: Multiple Defensive Hints have been found and we only patched the first one.\
1394 If you are using SiegeAI, you should only have one Defensive Hill in the map (the \
1395 one acting as the perimeter of your fort/city/castle). Due to SiegeAI being present, \
1396 in the map, normal Defensive Hills will not work anyways, and the only thing they do \
1397 is interfere with the patching process. So, if your map doesn't work properly after \
1398 patching, delete all the extra Defensive Hill Hints. They are the culprit.",
1399 files_patched, files_to_delete.len()), files_to_delete))
1400 }
1401 }
1402
1403 else if files_to_delete.is_empty() {
1405 Ok((format!("{files_patched} files patched.\nNo file suitable for deleting has been found."), files_to_delete))
1406 }
1407
1408 else {
1410 Ok((format!("{} files patched.\n{} files deleted.", files_patched, files_to_delete.len()), files_to_delete))
1411 }
1412 }
1413
1414 pub fn live_export(&mut self, game: &GameInfo, game_path: &Path, disable_regen_table_guid: bool, keys_first: bool) -> Result<()> {
1419
1420 if self.files().is_empty() {
1422 return Err(RLibError::LiveExportNoFilesToExport);
1423 }
1424
1425 let extra_data = Some(EncodeableExtraData::new_from_game_info_and_settings(game, self.compression_format(), disable_regen_table_guid));
1426 let data_path = game.data_path(game_path)?;
1427
1428 let files = self.files_by_type_and_paths(&[FileType::Text], &[ContainerPath::Folder("script/".to_string()), ContainerPath::Folder("ui/".to_string())], true)
1430 .into_iter()
1431 .cloned()
1432 .collect::<Vec<RFile>>();
1433
1434 let mut correlations = HashMap::new();
1435 for mut file in files.into_iter() {
1436 let mut path_split = file.path_in_container_split().iter().map(|x| x.to_owned()).collect::<Vec<_>>();
1437 let mut hasher = DefaultHasher::new();
1438
1439 std::time::SystemTime::now().hash(&mut hasher);
1441 let value = hasher.finish();
1442 let new_name = format!("{}_{}", value, path_split.last().unwrap());
1443
1444 *path_split.last_mut().unwrap() = &new_name;
1445 let new_path = path_split.join("/");
1446
1447 correlations.insert(file.path_in_container_raw().to_owned(), new_path.to_owned());
1448 file.set_path_in_container_raw(&new_path);
1449
1450 let container_path = file.path_in_container();
1452 self.insert(file)?;
1453 self.extract(container_path.clone(), &data_path, true, &None, false, keys_first, &extra_data)?;
1454
1455 self.remove(&container_path);
1456 }
1457
1458 let summary_data_str = correlations.iter().map(|(key, value)| format!(" [\"{key}\"] = \"{value}\",")).join("\n");
1460 let summary_data_lua = format!("return {{\n{summary_data_str}\n}}");
1461 let summary_path = game_path.join("lua_path_mappings.txt");
1462 let mut file = BufWriter::new(File::create(summary_path)?);
1463 file.write_all(summary_data_lua.as_bytes())?;
1464
1465 Ok(())
1466 }
1467
1468 pub fn update_anim_ids(&mut self, game: &GameInfo, starting_id: i32, offset: i32) -> Result<Vec<ContainerPath>> {
1470 if offset == 0 {
1471 return Err(RLibError::UpdateAnimIdsError("Offset must be different than 0.".to_owned()))
1472 }
1473
1474 if starting_id < 0 {
1475 return Err(RLibError::UpdateAnimIdsError("Starting Id must be greater than 0.".to_owned()))
1476 }
1477
1478 let mut extra_data = DecodeableExtraData::default();
1480 extra_data.set_game_info(Some(game));
1481 let extra_data = Some(extra_data);
1482
1483 let mut files = self.files_by_type_mut(&[FileType::AnimFragmentBattle]);
1484 let mut paths = files.par_iter_mut()
1485 .filter_map(|file| {
1486 let mut changed = false;
1487 if let Ok(Some(RFileDecoded::AnimFragmentBattle(mut table))) = file.decode(&extra_data, false, true) {
1488 if *table.max_id() >= starting_id as u32 {
1489 table.set_max_id(*table.max_id() + offset as u32);
1490 changed = true;
1491 }
1492
1493 for entry in table.entries_mut() {
1494 if *entry.animation_id() >= starting_id as u32 {
1495 entry.set_animation_id(*entry.animation_id() + offset as u32);
1496 changed = true;
1497 }
1498
1499 if *entry.slot_id() >= starting_id as u32 {
1500 entry.set_slot_id(*entry.slot_id() + offset as u32);
1501 changed = true;
1502 }
1503 }
1504
1505 if changed {
1506 let _ = file.set_decoded(RFileDecoded::AnimFragmentBattle(table));
1507 Some(file.path_in_container())
1508 } else {
1509 None
1510 }
1511 } else {
1512 None
1513 }
1514 }
1515 ).collect::<Vec<_>>();
1516
1517 let mut anim_packs = self.files_by_type_mut(&[FileType::AnimPack]);
1519
1520 for anim_pack in anim_packs.iter_mut() {
1521 let mut changed = false;
1522 if let Ok(Some(RFileDecoded::AnimPack(mut pack))) = anim_pack.decode(&extra_data, false, true) {
1523
1524 let mut files = pack.files_by_type_mut(&[FileType::AnimFragmentBattle]);
1525 for file in files.iter_mut() {
1526 if let Ok(Some(RFileDecoded::AnimFragmentBattle(mut table))) = file.decode(&extra_data, false, true) {
1527 if *table.max_id() >= starting_id as u32 {
1528 table.set_max_id(*table.max_id() + offset as u32);
1529 changed = true;
1530 }
1531
1532 for entry in table.entries_mut() {
1533 if *entry.animation_id() >= starting_id as u32 {
1534 entry.set_animation_id(*entry.animation_id() + offset as u32);
1535 changed = true;
1536 }
1537
1538 if *entry.slot_id() >= starting_id as u32 {
1539 entry.set_slot_id(*entry.slot_id() + offset as u32);
1540 changed = true;
1541 }
1542 }
1543
1544 if changed {
1545 let _ = file.set_decoded(RFileDecoded::AnimFragmentBattle(table));
1546 }
1547 }
1548 }
1549
1550 if changed {
1551 let _ = anim_pack.set_decoded(RFileDecoded::AnimPack(pack));
1552 paths.push(anim_pack.path_in_container());
1553 }
1554 }
1555 }
1556
1557 Ok(paths)
1558 }
1559}
1560
1561impl PackNotes {
1562
1563 pub fn load(data: &[u8]) -> Result<Self> {
1565 from_slice(data).map_err(From::from)
1566 }
1567
1568 pub fn notes_by_path(&self, path: &str) -> Vec<Note> {
1570 let path_lower = path.to_lowercase();
1571 self.file_notes()
1572 .iter()
1573 .filter(|(path, _)| path.is_empty() || path_lower.starts_with(*path) || &&path_lower == path)
1574 .flat_map(|(_, notes)| notes.to_vec())
1575 .collect()
1576 }
1577
1578 pub fn add_note(&mut self, mut note: Note, replace: bool) -> Note {
1591
1592 let mut path = note.path().to_lowercase();
1594 if path.starts_with("db/") || path.starts_with("ceo_db/") {
1595 let mut new_path = path.split('/').collect::<Vec<_>>();
1596 if new_path.len() == 3 {
1597 new_path.pop();
1598 }
1599 path = new_path.join("/");
1600 }
1601 note.set_path(path.to_owned());
1602
1603 let notes = self.file_notes_mut().entry(path).or_default();
1604 if replace {
1605 notes.retain(|x| x.id() != note.id());
1606 } else {
1607 note.set_id(notes.iter().map(|note| *note.id() + 1).max().unwrap_or_default());
1608 }
1609
1610 notes.push(note.clone());
1611 note
1612 }
1613
1614 pub fn delete_note(&mut self, path: &str, id: u64) {
1616 let path_lower = path.to_lowercase();
1617
1618 if let Some(notes) = self.file_notes_mut().get_mut(&path_lower) {
1619 notes.retain(|note| note.id() != &id);
1620 if notes.is_empty() {
1621 self.file_notes_mut().remove(&path_lower);
1622 }
1623 }
1624 }
1625}
1626
1627impl PackSettings {
1628
1629 pub fn load(data: &[u8]) -> Result<Self> {
1631 from_slice(data).map_err(From::from)
1632 }
1633
1634 pub fn load_and_update(&mut self, data: &[u8]) -> Result<()> {
1636 let settings: Self = from_slice(data)?;
1637
1638 self.settings_bool.extend(settings.settings_bool);
1639 self.settings_number.extend(settings.settings_number);
1640 self.settings_string.extend(settings.settings_string);
1641 self.settings_text.extend(settings.settings_text);
1642
1643 Ok(())
1644 }
1645
1646 pub fn setting_string(&self, key: &str) -> Option<&String> {
1648 self.settings_string.get(key)
1649 }
1650
1651 pub fn setting_text(&self, key: &str) -> Option<&String> {
1653 self.settings_text.get(key)
1654 }
1655
1656 pub fn setting_bool(&self, key: &str) -> Option<&bool> {
1658 self.settings_bool.get(key)
1659 }
1660
1661 pub fn setting_number(&self, key: &str) -> Option<&i32> {
1663 self.settings_number.get(key)
1664 }
1665
1666 pub fn set_setting_string(&mut self, key: &str, value: &str) {
1670 self.settings_string.insert(key.to_owned(), value.to_owned());
1671 }
1672
1673 pub fn set_setting_text(&mut self, key: &str, value: &str) {
1677 self.settings_text.insert(key.to_owned(), value.to_owned());
1678 }
1679
1680 pub fn set_setting_bool(&mut self, key: &str, value: bool) {
1684 self.settings_bool.insert(key.to_owned(), value);
1685 }
1686
1687 pub fn set_setting_number(&mut self, key: &str, value: i32) {
1691 self.settings_number.insert(key.to_owned(), value);
1692 }
1693
1694 pub fn diagnostics_files_to_ignore(&self) -> Option<Vec<DiagnosticIgnoreEntry>> {
1698 self.settings_text.get("diagnostics_files_to_ignore").map(|files_to_ignore| {
1699 let files = files_to_ignore.split('\n').collect::<Vec<&str>>();
1700
1701 files.iter().filter_map(|x| {
1703 if !x.starts_with('#') {
1704 let path = x.splitn(3, ';').collect::<Vec<&str>>();
1705 if path.len() == 3 {
1706 Some((path[0].to_string(), path[1].split(',').filter_map(|y| if !y.is_empty() { Some(y.to_owned()) } else { None }).collect::<Vec<String>>(), path[2].split(',').filter_map(|y| if !y.is_empty() { Some(y.to_owned()) } else { None }).collect::<Vec<String>>()))
1707 } else if path.len() == 2 {
1708 Some((path[0].to_string(), path[1].split(',').filter_map(|y| if !y.is_empty() { Some(y.to_owned()) } else { None }).collect::<Vec<String>>(), vec![]))
1709 } else if path.len() == 1 {
1710 Some((path[0].to_string(), vec![], vec![]))
1711 } else {
1712 None
1713 }
1714 } else {
1715 None
1716 }
1717 }).collect::<Vec<DiagnosticIgnoreEntry>>()
1718 })
1719 }
1720}
1721
1722impl Default for PackHeader {
1723 fn default() -> Self {
1724 Self {
1725 pfh_version: Default::default(),
1726 pfh_version_flags: Default::default(),
1727 pfh_file_type: Default::default(),
1728 bitmask: Default::default(),
1729 internal_timestamp: Default::default(),
1730 game_version: Default::default(),
1731 build_number: Default::default(),
1732 authoring_tool: AUTHORING_TOOL_RPFM.to_owned(),
1733 extra_subheader_data: Default::default(),
1734 }
1735 }
1736}
1737
1738impl Default for PFHFlags {
1739 fn default() -> Self {
1740 Self::empty()
1741 }
1742}
1743
1744impl Default for PackSettings {
1745 fn default() -> Self {
1746 let mut settings = Self {
1747 settings_text: BTreeMap::new(),
1748 settings_string: BTreeMap::new(),
1749 settings_bool: BTreeMap::new(),
1750 settings_number: BTreeMap::new(),
1751 };
1752
1753 settings.settings_text_mut().insert("diagnostics_files_to_ignore".to_owned(), "".to_owned());
1754 settings.settings_text_mut().insert("import_files_to_ignore".to_owned(), "".to_owned());
1755 settings.settings_bool_mut().insert("disable_autosaves".to_owned(), false);
1756 settings.settings_bool_mut().insert("do_not_generate_existing_locs".to_owned(), false);
1757 settings.settings_string_mut().insert(SETTING_KEY_CF.to_owned(), "None".to_owned());
1758 settings
1759 }
1760}