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
67use std::cmp::Ordering;
68use std::collections::{BTreeMap, HashMap, HashSet};
69use std::fs::File;
70use std::hash::{DefaultHasher, Hash, Hasher};
71use std::io::{BufReader, BufWriter, Cursor, SeekFrom, Write};
72use std::path::{Path, PathBuf};
73use std::str::FromStr;
74
75use crate::binary::{ReadBytes, WriteBytes};
76use crate::compression::{Compressible, CompressionFormat};
77use crate::error::{RLibError, Result};
78use crate::files::{Container, ContainerPath, Decodeable, DecodeableExtraData, Encodeable, EncodeableExtraData, FileType, Loc, RFile, RFileDecoded, table::DecodedData};
79use crate::games::{GameInfo, pfh_file_type::PFHFileType, pfh_version::PFHVersion};
80use crate::notes::Note;
81use crate::utils::{current_time, last_modified_time_from_file};
82
83#[cfg(test)]
84mod pack_test;
85mod pack_versions;
86
87pub const EXTENSION: &str = ".pack";
89
90const MFH_PREAMBLE: &str = "MFH";
92
93const TERRY_MAP_PATH: &str = "terrain/tiles/battle/_assembly_kit";
95
96const DEFAULT_BMD_DATA: &str = "bmd_data.bin";
98
99const MISSING_LOCS_PATH_START_EXISTING: &str = "text/aaa_missing_locs_";
101const MISSING_LOCS_PATH_START_NEW: &str = "text/zzz_missing_locs_";
103
104const FORT_PERIMETER_HINT: &[u8; 18] = b"AIH_FORT_PERIMETER";
106const DEFENSIVE_HILL_HINT: &[u8; 18] = b"AIH_DEFENSIVE_HILL";
107const SIEGE_AREA_NODE_HINT: &[u8; 19] = b"AIH_SIEGE_AREA_NODE";
108
109pub const RESERVED_NAME_DEPENDENCIES_MANAGER: &str = "dependencies_manager.rpfm_reserved";
111pub const RESERVED_NAME_DEPENDENCIES_MANAGER_V2: &str = "dependencies_manager_v2.rpfm_reserved";
113pub const RESERVED_NAME_EXTRA_PACKFILE: &str = "extra_packfile.rpfm_reserved";
115pub const RESERVED_NAME_SETTINGS: &str = "settings.rpfm_reserved";
117pub const RESERVED_NAME_SETTINGS_EXTRACTED: &str = "settings.rpfm_reserved.json";
119pub const RESERVED_NAME_NOTES: &str = "notes.rpfm_reserved";
121pub const RESERVED_NAME_NOTES_EXTRACTED: &str = "notes.rpfm_reserved.md";
123
124pub const RESERVED_RFILE_NAMES: [&str; 3] = [RESERVED_NAME_EXTRA_PACKFILE, RESERVED_NAME_SETTINGS, RESERVED_NAME_NOTES];
129
130const AUTHORING_TOOL_CA: &str = "CA_TOOL";
132const AUTHORING_TOOL_RPFM: &str = "RPFM";
134const AUTHORING_TOOL_SIZE: u32 = 8;
136
137pub const SETTING_KEY_CF: &str = "compression_format";
139
140bitflags! {
141
142 #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
147 pub struct PFHFlags: u32 {
148
149 const HAS_EXTENDED_HEADER = 0b0000_0001_0000_0000;
151
152 const HAS_ENCRYPTED_INDEX = 0b0000_0000_1000_0000;
154
155 const HAS_INDEX_WITH_TIMESTAMPS = 0b0000_0000_0100_0000;
157
158 const HAS_ENCRYPTED_DATA = 0b0000_0000_0001_0000;
160 }
161}
162
163#[derive(Debug, Clone, PartialEq, Getters, MutGetters, Setters, Default, Serialize, Deserialize)]
207#[getset(get = "pub", get_mut = "pub", set = "pub")]
208pub struct Pack {
209
210 disk_file_path: String,
212
213 disk_file_offset: u64,
215
216 local_timestamp: u64,
218
219 #[getset(skip)]
221 compress: bool,
222
223 header: PackHeader,
225
226 dependencies: Vec<(bool, String)>,
230
231 files: HashMap<String, RFile>,
233
234 paths: HashMap<String, Vec<String>>,
236
237 notes: PackNotes,
239
240 settings: PackSettings,
242}
243
244#[derive(Debug, Clone, PartialEq, Eq, Getters, Setters, Serialize, Deserialize)]
274#[getset(get = "pub", set = "pub")]
275pub struct PackHeader {
276
277 pfh_version: PFHVersion,
279
280 pfh_file_type: PFHFileType,
282
283 bitmask: PFHFlags,
285
286 internal_timestamp: u64,
288
289 game_version: u32,
291
292 build_number: u32,
294
295 authoring_tool: String,
297
298 extra_subheader_data: Vec<u8>,
300}
301
302#[derive(Clone, Debug, PartialEq, Eq, Getters, MutGetters, Setters, Serialize, Deserialize)]
316#[getset(get = "pub", get_mut = "pub", set = "pub")]
317pub struct PackSettings {
318
319 settings_text: BTreeMap<String, String>,
321
322 settings_string: BTreeMap<String, String>,
324
325 settings_bool: BTreeMap<String, bool>,
327
328 settings_number: BTreeMap<String, i32>,
330}
331
332#[derive(Clone, Debug, PartialEq, Eq, Default, Getters, MutGetters, Setters, Serialize, Deserialize)]
338#[getset(get = "pub", get_mut = "pub", set = "pub")]
339pub struct PackNotes {
340
341 pack_notes: String,
343
344 file_notes: HashMap<String, Vec<Note>>,
349}
350
351pub type DiagnosticIgnoreEntry = (String, Vec<String>, Vec<String>);
355
356impl Container for Pack {
361
362 fn extract_metadata(&mut self, destination_path: &Path) -> Result<Vec<PathBuf>> {
366 let mut paths = vec![];
367 let mut data = vec![];
368 data.write_all(to_string_pretty(&self.notes)?.as_bytes())?;
369 data.extend_from_slice(b"\n"); let path = destination_path.join(RESERVED_NAME_NOTES_EXTRACTED);
372 paths.push(path.to_owned());
373 let mut file = BufWriter::new(File::create(path)?);
374 file.write_all(&data)?;
375 file.flush()?;
376
377 let mut data = vec![];
378 data.write_all(to_string_pretty(&self.settings)?.as_bytes())?;
379 data.extend_from_slice(b"\n"); let path = destination_path.join(RESERVED_NAME_SETTINGS_EXTRACTED);
382 paths.push(path.to_owned());
383 let mut file = BufWriter::new(File::create(path)?);
384 file.write_all(&data)?;
385 file.flush()?;
386
387 Ok(paths)
388 }
389
390 fn insert(&mut self, mut file: RFile) -> Result<Option<ContainerPath>> {
391
392 let path_container = file.path_in_container();
394 let path = file.path_in_container_raw();
395 if path == RESERVED_NAME_NOTES_EXTRACTED {
396 self.notes = PackNotes::load(&file.encode(&None, false, false, true)?.unwrap())?;
397 Ok(None)
398 } else if path == RESERVED_NAME_SETTINGS_EXTRACTED {
399 self.settings = PackSettings::load(&file.encode(&None, false, false, true)?.unwrap())?;
400 Ok(None)
401 } else if path == RESERVED_NAME_DEPENDENCIES_MANAGER_V2 {
402 self.dependencies = from_slice(&file.encode(&None, false, false, true)?.unwrap())?;
403 Ok(None)
404 }
405
406 else {
408 self.paths_cache_insert_path(path);
409 self.files.insert(path.to_owned(), file);
410
411 Ok(Some(path_container))
412 }
413 }
414
415 fn disk_file_path(&self) -> &str {
416 &self.disk_file_path
417 }
418
419 fn files(&self) -> &HashMap<String, RFile> {
420 &self.files
421 }
422
423 fn files_mut(&mut self) -> &mut HashMap<String, RFile> {
424 &mut self.files
425 }
426
427 fn disk_file_offset(&self) -> u64 {
428 self.disk_file_offset
429 }
430
431 fn paths_cache(&self) -> &HashMap<String, Vec<String>> {
432 &self.paths
433 }
434
435 fn paths_cache_mut(&mut self) -> &mut HashMap<String, Vec<String>> {
436 &mut self.paths
437 }
438
439 fn internal_timestamp(&self) -> u64 {
440 self.header.internal_timestamp
441 }
442
443 fn local_timestamp(&self) -> u64 {
444 self.local_timestamp
445 }
446
447 fn move_path(&mut self, source_path: &ContainerPath, destination_path: &ContainerPath) -> Result<Vec<(ContainerPath, ContainerPath)>> {
451 match source_path {
452 ContainerPath::File(source_path) => match destination_path {
453 ContainerPath::File(destination_path) => {
454 if RESERVED_RFILE_NAMES.contains(&&**destination_path) {
455 return Err(RLibError::ReservedFiles);
456 }
457
458 if destination_path.is_empty() {
459 return Err(RLibError::EmptyDestiny);
460 }
461
462 self.paths_cache_remove_path(source_path);
463 let mut moved = self.files_mut()
464 .remove(source_path)
465 .ok_or_else(|| RLibError::FileNotFound(source_path.to_string()))?;
466
467 moved.set_path_in_container_raw(destination_path);
468
469 self.insert(moved).map(|x| match x {
470 Some(x) => vec![(ContainerPath::File(source_path.to_string()), x); 1],
471 None => Vec::with_capacity(0),
472 })
473 },
474 ContainerPath::Folder(_) => unreachable!("move_path_pack_1"),
475 },
476 ContainerPath::Folder(source_path) => match destination_path {
477 ContainerPath::File(_) => unreachable!("move_path_pack_2"),
478 ContainerPath::Folder(destination_path) => {
479 if destination_path.is_empty() {
480 return Err(RLibError::EmptyDestiny);
481 }
482
483 let mut source_path_end = source_path.to_owned();
485 if !source_path_end.ends_with('/') {
486 source_path_end.push('/');
487 }
488
489 let moved_paths = self.files()
490 .par_iter()
491 .filter_map(|(path, _)| if path.starts_with(&source_path_end) { Some(path.to_owned()) } else { None })
492 .collect::<Vec<_>>();
493
494 let moved = moved_paths.iter()
495 .filter_map(|x| {
496 self.paths_cache_remove_path(x);
497 self.files_mut().remove(x)
498 })
499 .collect::<Vec<_>>();
500
501 let mut new_paths = Vec::with_capacity(moved.len());
502 for mut moved in moved {
503 let old_path = moved.path_in_container();
504 let new_path = moved.path_in_container_raw().replacen(source_path, destination_path, 1);
505 moved.set_path_in_container_raw(&new_path);
506
507 if let Some(new_path) = self.insert(moved)? {
508 new_paths.push((old_path, new_path));
509 }
510 }
511
512 Ok(new_paths)
513 },
514 },
515 }
516 }
517}
518
519impl Decodeable for Pack {
520
521 fn decode<R: ReadBytes>(data: &mut R, extra_data: &Option<DecodeableExtraData>) -> Result<Self> {
522 Self::read(data, extra_data)
523 }
524}
525
526impl Encodeable for Pack {
527
528 fn encode<W: WriteBytes>(&mut self, buffer: &mut W, extra_data: &Option<EncodeableExtraData>) -> Result<()> {
529 self.write(buffer, extra_data)
530 }
531}
532
533impl Pack {
535
536 pub fn new_with_version(pfh_version: PFHVersion) -> Self {
538 let mut pack = Self::default();
539 pack.header.pfh_version = pfh_version;
540 pack
541 }
542
543 pub fn new_with_name_and_version(name: &str, pfh_version: PFHVersion) -> Self {
545 let mut pack = Self::default();
546 pack.header.pfh_version = pfh_version;
547 pack.disk_file_path = name.to_owned();
548 pack
549 }
550
551 fn read<R: ReadBytes>(data: &mut R, extra_data: &Option<DecodeableExtraData>) -> Result<Self> {
555 let extra_data = extra_data.as_ref().ok_or(RLibError::DecodingMissingExtraData)?;
556
557 let game_info = match extra_data.game_info {
559 Some(game_info) => game_info,
560 None => return Err(RLibError::GameInfoMissingFromDecodingFunction),
561 };
562
563 let disk_file_path = match extra_data.disk_file_path {
566 Some(path) => {
567 let file_path = PathBuf::from_str(path).map_err(|_|RLibError::DecodingMissingExtraDataField("disk_file_path".to_owned()))?;
568 if file_path.is_file() {
569 path.to_owned()
570 } else {
571 return Err(RLibError::DecodingMissingExtraData)
572 }
573 }
574 None => String::new()
575 };
576
577 let disk_file_offset = extra_data.disk_file_offset;
578 let disk_file_size = if extra_data.data_size > 0 { extra_data.data_size } else { data.len()? };
579 let timestamp = extra_data.timestamp;
580 let is_encrypted = extra_data.is_encrypted;
581 let skip_path_cache_generation = extra_data.skip_path_cache_generation;
582
583 let lazy_load = !disk_file_path.is_empty() && !is_encrypted && extra_data.lazy_load;
585
586 let data_len = disk_file_size;
589 if data_len < 24 {
590 return Err(RLibError::PackHeaderNotComplete);
591 }
592
593 let start = if data.read_string_u8(3)? == MFH_PREAMBLE { 8 } else { 0 };
595 data.seek(SeekFrom::Current(-3))?;
596 data.seek(SeekFrom::Current(start))?;
597
598 let mut pack = Self {
600 disk_file_path,
601 disk_file_offset,
602 local_timestamp: timestamp,
603 ..Default::default()
604 };
605
606 pack.header.pfh_version = PFHVersion::version(&data.read_string_u8(4)?)?;
607
608 let pack_type = data.read_u32()?;
609 pack.header.pfh_file_type = PFHFileType::try_from(pack_type & 15)?;
610 pack.header.bitmask = PFHFlags::from_bits_truncate(pack_type & !15);
611
612 let expected_data_len = match pack.header.pfh_version {
615 PFHVersion::PFH6 => pack.read_pfh6(data, extra_data)?,
616 PFHVersion::PFH5 => pack.read_pfh5(data, extra_data)?,
617 PFHVersion::PFH4 => pack.read_pfh4(data, extra_data)?,
618 PFHVersion::PFH3 => pack.read_pfh3(data, extra_data)?,
619 PFHVersion::PFH2 => pack.read_pfh2(data, extra_data)?,
620 PFHVersion::PFH0 => pack.read_pfh0(data, extra_data)?,
621 };
622
623 if let Some(mut notes) = pack.files.remove(RESERVED_NAME_NOTES) {
625 notes.load()?;
626 let data = notes.cached()?;
627
628 match PackNotes::load(data) {
631 Ok(notes) => pack.notes = notes,
632 Err(_) => {
633 let len = data.len();
634 let mut data = Cursor::new(data);
635 pack.notes = PackNotes::default();
636 pack.notes.pack_notes = data.read_string_u8(len)?;
637 }
638 }
639 }
640
641 if let Some(mut settings) = pack.files.remove(RESERVED_NAME_SETTINGS) {
642 settings.load()?;
643 let data = settings.cached()?;
644 pack.settings.load_and_update(data)?;
645 }
646
647 if let Some(mut deps) = pack.files.remove(RESERVED_NAME_DEPENDENCIES_MANAGER_V2) {
648 deps.load()?;
649 let data = deps.cached()?;
650 pack.dependencies = from_slice(data)?;
651 }
652
653 if !skip_path_cache_generation {
655 pack.paths_cache_generate();
656 }
657
658 let preferred_cf = game_info.compression_formats_supported().first().cloned().unwrap_or_default();
663 let current_cf_str = pack.settings().setting_string(SETTING_KEY_CF).cloned().unwrap_or_default();
664 let current_cf = CompressionFormat::from(&*current_cf_str);
665
666 if pack.compress && current_cf == CompressionFormat::None {
667 pack.settings_mut().set_setting_string(SETTING_KEY_CF, preferred_cf.to_string().as_str());
668 }
669
670 if expected_data_len != data_len { return Err(RLibError::DecodingMismatchSizeError(data_len as usize, expected_data_len as usize)) }
676
677 pack.files.par_iter_mut().map(|(_, file)| file.guess_file_type()).collect::<Result<()>>()?;
679
680 if !lazy_load {
682 pack.files.par_iter_mut().try_for_each(|(_, file)| file.load())?;
683 }
684
685 Ok(pack)
687 }
688
689 fn write<W: WriteBytes>(&mut self, buffer: &mut W, extra_data: &Option<EncodeableExtraData>) -> Result<()> {
691 let test_mode = if let Some(extra_data) = extra_data {
692 extra_data.test_mode
693 } else {
694 false
695 };
696
697 if !test_mode {
698
699 if self.header.pfh_file_type == PFHFileType::Mod || self.header.pfh_file_type == PFHFileType::Movie {
701
702 let mut data = vec![];
704 data.write_all(to_string_pretty(&self.notes)?.as_bytes())?;
705 let file = RFile::new_from_vec(&data, FileType::Text, 0, RESERVED_NAME_NOTES);
706 self.files.insert(RESERVED_NAME_NOTES.to_owned(), file);
707
708 let mut data = vec![];
710 data.write_all(to_string_pretty(&self.settings)?.as_bytes())?;
711 let file = RFile::new_from_vec(&data, FileType::Text, 0, RESERVED_NAME_SETTINGS);
712 self.files.insert(RESERVED_NAME_SETTINGS.to_owned(), file);
713
714 let mut data = vec![];
716 data.write_all(to_string_pretty(&self.dependencies)?.as_bytes())?;
717 let file = RFile::new_from_vec(&data, FileType::Text, 0, RESERVED_NAME_DEPENDENCIES_MANAGER_V2);
718 self.files.insert(RESERVED_NAME_DEPENDENCIES_MANAGER_V2.to_owned(), file);
719
720 }
721 }
722
723 match self.header.pfh_version {
724 PFHVersion::PFH6 => self.write_pfh6(buffer, extra_data)?,
725 PFHVersion::PFH5 => self.write_pfh5(buffer, extra_data)?,
726 PFHVersion::PFH4 => self.write_pfh4(buffer, extra_data)?,
727 PFHVersion::PFH3 => self.write_pfh3(buffer, extra_data)?,
728 PFHVersion::PFH2 => self.write_pfh2(buffer, extra_data)?,
729 PFHVersion::PFH0 => self.write_pfh0(buffer, extra_data)?,
730 }
731
732 self.remove(&ContainerPath::File(RESERVED_NAME_NOTES.to_owned()));
734 self.remove(&ContainerPath::File(RESERVED_NAME_SETTINGS.to_owned()));
735 self.remove(&ContainerPath::File(RESERVED_NAME_DEPENDENCIES_MANAGER_V2.to_owned()));
736
737 Ok(())
739 }
740
741 pub fn read_and_merge_ca_packs(game: &GameInfo, game_path: &Path) -> Result<Self> {
749 let paths = game.ca_packs_paths(game_path)?;
750 let mut pack = Self::read_and_merge(&paths, game, true, true, false)?;
751
752 pack.header_mut().set_pfh_file_type(PFHFileType::Release);
754 Ok(pack)
755 }
756
757 pub fn read_and_merge(pack_paths: &[PathBuf], game: &GameInfo, lazy_load: bool, ignore_mods: bool, keep_order: bool) -> Result<Self> {
761 if pack_paths.is_empty() {
762 return Err(RLibError::NoPacksProvided);
763 }
764
765 let mut extra_data = DecodeableExtraData {
766 lazy_load,
767 game_info: Some(game),
768 ..Default::default()
769 };
770
771 if pack_paths.len() == 1 {
773 let mut data = BufReader::new(File::open(&pack_paths[0])
774 .map_err(|error| RLibError::IOErrorPath(Box::new(RLibError::IOError(error)), pack_paths[0].to_path_buf()))?);
775 let path_str = pack_paths[0].to_string_lossy().replace('\\', "/");
776
777 extra_data.set_disk_file_path(Some(&path_str));
778 extra_data.set_timestamp(last_modified_time_from_file(data.get_ref()).unwrap());
779
780 return Self::read(&mut data, &Some(extra_data))
781 }
782
783 extra_data.set_skip_path_cache_generation(true);
785
786 let mut pack_new = Pack::default();
788 let mut packs = pack_paths.par_iter()
789 .map(|path| {
790 let mut data = BufReader::new(File::open(path)
791 .map_err(|error| RLibError::IOErrorPath(Box::new(RLibError::IOError(error)), pack_paths[0].to_path_buf()))?);
792 let path_str = path.to_string_lossy().replace('\\', "/");
793
794 let mut extra_data = extra_data.to_owned();
795 extra_data.set_disk_file_path(Some(&path_str));
796 extra_data.set_timestamp(last_modified_time_from_file(data.get_ref())?);
797
798 Self::read(&mut data, &Some(extra_data))
799 }).collect::<Result<Vec<Pack>>>()?;
800
801 packs.sort_by(|pack_a, pack_b| if pack_a.pfh_file_type() != pack_b.pfh_file_type() {
803 pack_a.pfh_file_type().cmp(&pack_b.pfh_file_type())
804 } else if !keep_order {
805 pack_a.disk_file_path.cmp(&pack_b.disk_file_path)
806 } else {
807 Ordering::Equal
808 });
809
810 packs.iter()
811 .chunk_by(|pack| pack.header.pfh_file_type)
812 .into_iter()
813 .for_each(|(pfh_type, packs)| {
814 if pfh_type != PFHFileType::Mod || !ignore_mods {
815 let mut packs = packs.collect::<Vec<_>>();
816 packs.reverse();
817 packs.iter()
818 .for_each(|pack| {
819 pack_new.files_mut().extend(pack.files().clone())
820 });
821 }
822 });
823
824 let pack_names = packs.iter().map(|pack| pack.disk_file_name()).collect::<Vec<_>>();
826 let mut dependencies = packs.iter()
827 .flat_map(|pack| pack.dependencies()
828 .iter()
829 .filter(|(_, dependency)| !pack_names.contains(dependency))
830 .cloned()
831 .collect::<Vec<_>>())
832 .collect::<Vec<_>>();
833
834 let mut set = HashSet::new();
836 dependencies.retain(|x| set.insert(x.clone()));
837 pack_new.set_dependencies(dependencies);
838
839 pack_new.set_pfh_file_type(packs[0].pfh_file_type());
841 pack_new.set_pfh_version(game.pfh_version_by_file_type(pack_new.pfh_file_type()));
842
843 pack_new.paths_cache_generate();
845
846 Ok(pack_new)
847 }
848
849 pub fn merge(packs: &[Self]) -> Result<Self> {
856 if packs.is_empty() {
857 return Err(RLibError::NoPacksProvided);
858 }
859
860 if packs.len() == 1 {
862 return Ok(packs[0].clone());
863 }
864
865 let mut pack_new = Pack::default();
867
868 let mut pfh_types = packs.iter().map(|pack| pack.pfh_file_type()).collect::<Vec<_>>();
869 pfh_types.sort();
870 pfh_types.dedup();
871
872 if pfh_types.len() == 1 {
873 pack_new.set_pfh_file_type(pfh_types[0]);
874 }
875
876 packs.iter()
877 .chunk_by(|pack| pack.header.pfh_file_type)
878 .into_iter()
879 .for_each(|(_, packs)| {
880 let mut packs = packs.collect::<Vec<_>>();
881 packs.reverse();
882 packs.iter()
883 .for_each(|pack| {
884 pack_new.files_mut().extend(pack.files().clone())
885 });
886 });
887
888 let pack_names = packs.iter().map(|pack| pack.disk_file_name()).collect::<Vec<_>>();
890 let mut dependencies = packs.iter()
891 .flat_map(|pack| pack.dependencies()
892 .iter()
893 .filter(|(_, dependency)| !pack_names.contains(dependency))
894 .cloned()
895 .collect::<Vec<_>>())
896 .collect::<Vec<_>>();
897
898 let mut set = HashSet::new();
900 dependencies.retain(|x| set.insert(x.clone()));
901 pack_new.set_dependencies(dependencies);
902
903 pack_new.set_pfh_file_type(packs[0].pfh_file_type());
905 pack_new.set_pfh_version(packs[0].pfh_version());
906
907 pack_new.paths_cache_generate();
909
910 Ok(pack_new)
911 }
912
913 pub fn save(&mut self, path: Option<&Path>, game_info: &GameInfo, extra_data: &Option<EncodeableExtraData>) -> Result<()> {
917 if let Some(path) = path {
918 self.disk_file_path = path.to_string_lossy().to_string();
919 }
920
921 let extra_data = if extra_data.is_some() {
922 extra_data.clone()
923 } else {
924 Some(EncodeableExtraData::new_from_game_info(game_info))
925 };
926
927 let target = PathBuf::from(&self.disk_file_path);
930 let parent = match target.parent() {
931 Some(parent) if !parent.as_os_str().is_empty() => parent.to_path_buf(),
932 _ => PathBuf::from("."),
933 };
934
935 let mut temp_file = NamedTempFile::new_in(&parent)?;
936 {
937 let mut buffer = BufWriter::new(&mut temp_file);
938 self.encode(&mut buffer, &extra_data)?;
939 buffer.flush()?;
940 }
941 temp_file.persist(&target).map_err(|error| RLibError::from(error.error))?;
942
943 let mut reloaded = Self::read_and_merge(&[target], game_info, true, false, false)?;
945 self.local_timestamp = reloaded.local_timestamp;
946 for (key, file) in self.files.iter_mut() {
947 if file.decoded().is_ok() {
948 continue;
949 }
950
951 if let Some(reloaded_file) = reloaded.files.remove(key) {
952 *file = reloaded_file;
953 }
954 }
955
956 Ok(())
957 }
958
959 pub fn pfh_version(&self) -> PFHVersion {
965 *self.header.pfh_version()
966 }
967
968 pub fn pfh_file_type(&self) -> PFHFileType {
970 *self.header.pfh_file_type()
971 }
972
973 pub fn bitmask(&self) -> PFHFlags {
975 *self.header.bitmask()
976 }
977
978 pub fn internal_timestamp(&self) -> u64 {
980 *self.header.internal_timestamp()
981 }
982
983 pub fn game_version(&self) -> u32 {
985 *self.header.game_version()
986 }
987
988 pub fn build_number(&self) -> u32 {
990 *self.header.build_number()
991 }
992
993 pub fn authoring_tool(&self) -> &str {
995 self.header.authoring_tool()
996 }
997
998 pub fn extra_subheader_data(&self) -> &[u8] {
1000 self.header.extra_subheader_data()
1001 }
1002pub fn compression_format(&self) -> CompressionFormat {
1018 let cf = self.settings().setting_string(SETTING_KEY_CF).map(|x| x.to_owned());
1019 CompressionFormat::from(cf.unwrap_or_default().as_str())
1020 }
1021
1022 pub fn set_pfh_version(&mut self, version: PFHVersion) {
1024 self.header.set_pfh_version(version);
1025 }
1026
1027 pub fn set_pfh_file_type(&mut self, file_type: PFHFileType) {
1029 self.header.set_pfh_file_type(file_type);
1030 }
1031
1032 pub fn set_bitmask(&mut self, bitmask: PFHFlags) {
1034 self.header.set_bitmask(bitmask);
1035 }
1036
1037 pub fn set_internal_timestamp(&mut self, timestamp: u64) {
1039 self.header.set_internal_timestamp(timestamp);
1040 }
1041
1042 pub fn set_game_version(&mut self, game_version: u32) {
1044 self.header.set_game_version(game_version);
1045 }
1046
1047 pub fn set_build_number(&mut self, build_number: u32) {
1049 self.header.set_build_number(build_number);
1050 }
1051
1052 pub fn set_authoring_tool(&mut self, authoring_tool: &str) {
1054 self.header.set_authoring_tool(authoring_tool.to_string());
1055 }
1056
1057 pub fn set_extra_subheader_data(&mut self, extra_subheader_data: &[u8]) {
1059 self.header.set_extra_subheader_data(extra_subheader_data.to_vec());
1060 }
1061
1062 pub fn set_compression_format(&mut self, cf: CompressionFormat, game_info: &GameInfo) -> CompressionFormat {
1067 if cf == CompressionFormat::None || !game_info.compression_formats_supported().contains(&cf) {
1068 self.compress = false;
1069 self.settings_mut().set_setting_string(SETTING_KEY_CF, CompressionFormat::None.to_string().as_str());
1070 CompressionFormat::None
1071 } else {
1072 self.compress = true;
1073 self.settings_mut().set_setting_string(SETTING_KEY_CF, cf.to_string().as_str());
1074 cf
1075 }
1076 }
1077
1078 pub fn spoof_ca_authoring_tool(&mut self, spoof: bool) {
1086 if spoof {
1087 self.header.set_authoring_tool(AUTHORING_TOOL_CA.to_string());
1088 } else {
1089 self.header.set_authoring_tool(AUTHORING_TOOL_RPFM.to_string());
1090 }
1091 }
1092
1093 pub fn is_compressible(&self) -> bool {
1095 matches!(self.header.pfh_version, PFHVersion::PFH6 | PFHVersion::PFH5)
1096 }
1097
1098 pub fn missing_locs_paths(&self) -> (String, String) {
1102 (
1103 MISSING_LOCS_PATH_START_EXISTING.to_owned() + &self.disk_file_name() + ".loc",
1104 MISSING_LOCS_PATH_START_NEW.to_owned() + &self.disk_file_name() + ".loc"
1105 )
1106 }
1107
1108 pub fn generate_missing_loc_data(&mut self, existing_locs: &HashMap<String, String>) -> Result<Vec<ContainerPath>> {
1110 let mut new_files = vec![];
1111
1112 let (missing_locs_path_existing, missing_locs_path_new) = self.missing_locs_paths();
1113
1114 let db_tables = self.files_by_type(&[FileType::DB]);
1115 let loc_tables = self.files_by_type(&[FileType::Loc]);
1116 let mut missing_trads_file_new = Loc::new();
1117 let mut missing_trads_file_overwritten = Loc::new();
1118
1119 let loc_keys_from_memory = loc_tables.par_iter().filter_map(|rfile| {
1120 if rfile.path_in_container_raw() != missing_locs_path_new && rfile.path_in_container_raw() != missing_locs_path_existing {
1121 if let Ok(RFileDecoded::Loc(table)) = rfile.decoded() {
1122 Some(table.data().iter().filter_map(|x| {
1123 if let DecodedData::StringU16(data) = &x[0] {
1124 Some(data.to_owned())
1125 } else {
1126 None
1127 }
1128 }).collect::<HashSet<String>>())
1129 } else { None }
1130 } else { None }
1131 }).flatten().collect::<HashSet<String>>();
1132
1133 let (missing_trads_new, missing_trads_overwritten) = db_tables.par_iter().filter_map(|rfile| {
1134 if let Ok(RFileDecoded::DB(table)) = rfile.decoded() {
1135 let definition = table.definition();
1136 let loc_fields = definition.localised_fields();
1137 let table_data = table.data();
1138 let table_name = table.table_name_without_tables();
1139 let fields_processed = definition.fields_processed();
1140
1141 let has_loc_fields = !loc_fields.is_empty();
1142 let is_building_culture_variants = table_name == "building_culture_variants";
1143
1144 if has_loc_fields || is_building_culture_variants {
1145 let localised_order = definition.localised_key_order();
1147 let mut new_rows_new = vec![];
1148 let mut new_rows_overwritten = vec![];
1149
1150 for row in table_data.iter() {
1151
1152 if has_loc_fields {
1154 for loc_field in loc_fields {
1155 let key = localised_order.iter().map(|pos| row[*pos as usize].data_to_string()).join("");
1156
1157 if !key.is_empty() {
1159 let loc_key = format!("{}_{}_{}", table_name, loc_field.name(), key);
1160
1161 if let Some(value) = existing_locs.get(&loc_key) {
1162 let mut new_row = missing_trads_file_overwritten.new_row();
1163 new_row[0] = DecodedData::StringU16(loc_key);
1164 new_row[1] = DecodedData::StringU16(value.to_owned());
1165 new_rows_overwritten.push(new_row);
1166
1167 } else if !loc_keys_from_memory.contains(&*loc_key) {
1168 let mut new_row = missing_trads_file_new.new_row();
1169 new_row[0] = DecodedData::StringU16(loc_key);
1170 new_row[1] = DecodedData::StringU16("PLACEHOLDER".to_owned());
1171 new_rows_new.push(new_row);
1172 }
1173 }
1174 }
1175 }
1176
1177 if is_building_culture_variants {
1181 if let Some(short_desc_index) = fields_processed.iter().position(|x| x.name() == "short_description") {
1182 let key = row[short_desc_index].data_to_string();
1183 if !key.is_empty() {
1184 let loc_key = format!("building_short_description_texts_short_description_{}", key);
1185
1186 if let Some(value) = existing_locs.get(&loc_key) {
1187 let mut new_row = missing_trads_file_overwritten.new_row();
1188 new_row[0] = DecodedData::StringU16(loc_key);
1189 new_row[1] = DecodedData::StringU16(value.to_owned());
1190 new_rows_overwritten.push(new_row);
1191
1192 } else if !loc_keys_from_memory.contains(&*loc_key) {
1193 let mut new_row = missing_trads_file_new.new_row();
1194 new_row[0] = DecodedData::StringU16(loc_key);
1195 new_row[1] = DecodedData::StringU16("PLACEHOLDER".to_owned());
1196 new_rows_new.push(new_row);
1197 }
1198 }
1199 }
1200 }
1201 }
1202
1203 return Some((new_rows_new, new_rows_overwritten))
1204 }
1205 }
1206 None
1207 }).collect::<(Vec<Vec<Vec<DecodedData>>>, Vec<Vec<Vec<DecodedData>>>)>();
1208
1209 let missing_trads_new = missing_trads_new.into_iter().flatten().collect::<Vec<_>>();
1211 let missing_trads_overwritten = missing_trads_overwritten.into_iter().flatten().collect::<Vec<_>>();
1212
1213 if !missing_trads_new.is_empty() {
1215 let _ = missing_trads_file_new.set_data(&missing_trads_new);
1216 let packed_file = RFile::new_from_decoded(&RFileDecoded::Loc(missing_trads_file_new), 0, &missing_locs_path_new);
1217 new_files.push(self.insert(packed_file)?.unwrap());
1218 }
1219
1220 if !missing_trads_overwritten.is_empty() && !self.settings.setting_bool("do_not_generate_existing_locs").unwrap_or(&false) {
1221 let _ = missing_trads_file_overwritten.set_data(&missing_trads_overwritten);
1222 let packed_file = RFile::new_from_decoded(&RFileDecoded::Loc(missing_trads_file_overwritten), 0, &missing_locs_path_existing);
1223 new_files.push(self.insert(packed_file)?.unwrap());
1224 }
1225
1226 Ok(new_files)
1227 }
1228
1229 pub fn patch_siege_ai(&mut self) -> Result<(String, Vec<ContainerPath>)> {
1233
1234 if self.files().is_empty() {
1236 return Err(RLibError::PatchSiegeAIEmptyPack)
1237 }
1238
1239 let mut files_patched = 0;
1240 let mut files_to_delete: Vec<ContainerPath> = vec![];
1241 let mut multiple_defensive_hill_hints = false;
1242
1243 for file in self.files_by_path_mut(&ContainerPath::Folder(TERRY_MAP_PATH.to_owned()), true) {
1245 let path = file.path_in_container_raw();
1246 let idx = path.rfind('/').unwrap_or(0);
1247 let name = if path.get(idx + 1..).is_some() {
1248 &path[idx + 1..]
1249 } else {
1250 continue
1251 };
1252
1253 if name == DEFAULT_BMD_DATA || (name.starts_with("catchment_") && name.ends_with(".bin")) {
1255 file.load()?;
1256 let data = file.cached_mut()?;
1257
1258 if data.windows(19).any(|window: &[u8]|window == SIEGE_AREA_NODE_HINT) {
1261 if let Some(index) = data.windows(18).position(|window: &[u8]|window == DEFENSIVE_HILL_HINT) {
1262 data.splice(index..index + 18, FORT_PERIMETER_HINT.iter().cloned());
1263 files_patched += 1;
1264 }
1265
1266 if data.windows(18).any(|window: &[u8]|window == DEFENSIVE_HILL_HINT) {
1268 multiple_defensive_hill_hints = true;
1269 }
1270 }
1271 }
1272
1273 else if name.ends_with(".xml") {
1275 files_to_delete.push(ContainerPath::File(file.path_in_container_raw().to_string()));
1276 }
1277 }
1278
1279 files_to_delete.iter().for_each(|x| { self.remove(x); });
1281
1282 if files_patched == 0 && files_to_delete.is_empty() { Err(RLibError::PatchSiegeAINoPatchableFiles) }
1284
1285 else if files_patched == 0 {
1288 Ok((format!("No file suitable for patching has been found.\n{} files deleted.", files_to_delete.len()), files_to_delete))
1289 }
1290
1291 else if multiple_defensive_hill_hints {
1293
1294 if files_to_delete.is_empty() {
1296 Ok((format!("{files_patched} files patched.\nNo file suitable for deleting has been found.\
1297 \n\n\
1298 WARNING: Multiple Defensive Hints have been found and we only patched the first one.\
1299 If you are using SiegeAI, you should only have one Defensive Hill in the map (the \
1300 one acting as the perimeter of your fort/city/castle). Due to SiegeAI being present, \
1301 in the map, normal Defensive Hills will not work anyways, and the only thing they do \
1302 is interfere with the patching process. So, if your map doesn't work properly after \
1303 patching, delete all the extra Defensive Hill Hints. They are the culprit."), files_to_delete))
1304 }
1305 else {
1306 Ok((format!("{} files patched.\n{} files deleted.\
1307 \n\n\
1308 WARNING: Multiple Defensive Hints have been found and we only patched the first one.\
1309 If you are using SiegeAI, you should only have one Defensive Hill in the map (the \
1310 one acting as the perimeter of your fort/city/castle). Due to SiegeAI being present, \
1311 in the map, normal Defensive Hills will not work anyways, and the only thing they do \
1312 is interfere with the patching process. So, if your map doesn't work properly after \
1313 patching, delete all the extra Defensive Hill Hints. They are the culprit.",
1314 files_patched, files_to_delete.len()), files_to_delete))
1315 }
1316 }
1317
1318 else if files_to_delete.is_empty() {
1320 Ok((format!("{files_patched} files patched.\nNo file suitable for deleting has been found."), files_to_delete))
1321 }
1322
1323 else {
1325 Ok((format!("{} files patched.\n{} files deleted.", files_patched, files_to_delete.len()), files_to_delete))
1326 }
1327 }
1328
1329 pub fn live_export(&mut self, game: &GameInfo, game_path: &Path, disable_regen_table_guid: bool, keys_first: bool) -> Result<()> {
1334
1335 if self.files().is_empty() {
1337 return Err(RLibError::LiveExportNoFilesToExport);
1338 }
1339
1340 let extra_data = Some(EncodeableExtraData::new_from_game_info_and_settings(game, self.compression_format(), disable_regen_table_guid));
1341 let data_path = game.data_path(game_path)?;
1342
1343 let files = self.files_by_type_and_paths(&[FileType::Text], &[ContainerPath::Folder("script/".to_string()), ContainerPath::Folder("ui/".to_string())], true)
1345 .into_iter()
1346 .cloned()
1347 .collect::<Vec<RFile>>();
1348
1349 let mut correlations = HashMap::new();
1350 for mut file in files.into_iter() {
1351 let mut path_split = file.path_in_container_split().iter().map(|x| x.to_owned()).collect::<Vec<_>>();
1352 let mut hasher = DefaultHasher::new();
1353
1354 std::time::SystemTime::now().hash(&mut hasher);
1356 let value = hasher.finish();
1357 let new_name = format!("{}_{}", value, path_split.last().unwrap());
1358
1359 *path_split.last_mut().unwrap() = &new_name;
1360 let new_path = path_split.join("/");
1361
1362 correlations.insert(file.path_in_container_raw().to_owned(), new_path.to_owned());
1363 file.set_path_in_container_raw(&new_path);
1364
1365 let container_path = file.path_in_container();
1367 self.insert(file)?;
1368 self.extract(container_path.clone(), &data_path, true, &None, false, keys_first, &extra_data)?;
1369
1370 self.remove(&container_path);
1371 }
1372
1373 let summary_data_str = correlations.iter().map(|(key, value)| format!(" [\"{key}\"] = \"{value}\",")).join("\n");
1375 let summary_data_lua = format!("return {{\n{summary_data_str}\n}}");
1376 let summary_path = game_path.join("lua_path_mappings.txt");
1377 let mut file = BufWriter::new(File::create(summary_path)?);
1378 file.write_all(summary_data_lua.as_bytes())?;
1379
1380 Ok(())
1381 }
1382
1383 pub fn update_anim_ids(&mut self, game: &GameInfo, starting_id: i32, offset: i32) -> Result<Vec<ContainerPath>> {
1385 if offset == 0 {
1386 return Err(RLibError::UpdateAnimIdsError("Offset must be different than 0.".to_owned()))
1387 }
1388
1389 if starting_id < 0 {
1390 return Err(RLibError::UpdateAnimIdsError("Starting Id must be greater than 0.".to_owned()))
1391 }
1392
1393 let mut extra_data = DecodeableExtraData::default();
1395 extra_data.set_game_info(Some(game));
1396 let extra_data = Some(extra_data);
1397
1398 let mut files = self.files_by_type_mut(&[FileType::AnimFragmentBattle]);
1399 let mut paths = files.par_iter_mut()
1400 .filter_map(|file| {
1401 let mut changed = false;
1402 if let Ok(Some(RFileDecoded::AnimFragmentBattle(mut table))) = file.decode(&extra_data, false, true) {
1403 if *table.max_id() >= starting_id as u32 {
1404 table.set_max_id(*table.max_id() + offset as u32);
1405 changed = true;
1406 }
1407
1408 for entry in table.entries_mut() {
1409 if *entry.animation_id() >= starting_id as u32 {
1410 entry.set_animation_id(*entry.animation_id() + offset as u32);
1411 changed = true;
1412 }
1413
1414 if *entry.slot_id() >= starting_id as u32 {
1415 entry.set_slot_id(*entry.slot_id() + offset as u32);
1416 changed = true;
1417 }
1418 }
1419
1420 if changed {
1421 let _ = file.set_decoded(RFileDecoded::AnimFragmentBattle(table));
1422 Some(file.path_in_container())
1423 } else {
1424 None
1425 }
1426 } else {
1427 None
1428 }
1429 }
1430 ).collect::<Vec<_>>();
1431
1432 let mut anim_packs = self.files_by_type_mut(&[FileType::AnimPack]);
1434
1435 for anim_pack in anim_packs.iter_mut() {
1436 let mut changed = false;
1437 if let Ok(Some(RFileDecoded::AnimPack(mut pack))) = anim_pack.decode(&extra_data, false, true) {
1438
1439 let mut files = pack.files_by_type_mut(&[FileType::AnimFragmentBattle]);
1440 for file in files.iter_mut() {
1441 if let Ok(Some(RFileDecoded::AnimFragmentBattle(mut table))) = file.decode(&extra_data, false, true) {
1442 if *table.max_id() >= starting_id as u32 {
1443 table.set_max_id(*table.max_id() + offset as u32);
1444 changed = true;
1445 }
1446
1447 for entry in table.entries_mut() {
1448 if *entry.animation_id() >= starting_id as u32 {
1449 entry.set_animation_id(*entry.animation_id() + offset as u32);
1450 changed = true;
1451 }
1452
1453 if *entry.slot_id() >= starting_id as u32 {
1454 entry.set_slot_id(*entry.slot_id() + offset as u32);
1455 changed = true;
1456 }
1457 }
1458
1459 if changed {
1460 let _ = file.set_decoded(RFileDecoded::AnimFragmentBattle(table));
1461 }
1462 }
1463 }
1464
1465 if changed {
1466 let _ = anim_pack.set_decoded(RFileDecoded::AnimPack(pack));
1467 paths.push(anim_pack.path_in_container());
1468 }
1469 }
1470 }
1471
1472 Ok(paths)
1473 }
1474}
1475
1476impl PackNotes {
1477
1478 pub fn load(data: &[u8]) -> Result<Self> {
1480 from_slice(data).map_err(From::from)
1481 }
1482
1483 pub fn notes_by_path(&self, path: &str) -> Vec<Note> {
1485 let path_lower = path.to_lowercase();
1486 self.file_notes()
1487 .iter()
1488 .filter(|(path, _)| path.is_empty() || path_lower.starts_with(*path) || &&path_lower == path)
1489 .flat_map(|(_, notes)| notes.to_vec())
1490 .collect()
1491 }
1492
1493 pub fn add_note(&mut self, mut note: Note) -> Note {
1497
1498 let mut path = note.path().to_lowercase();
1500 if path.starts_with("db/") || path.starts_with("ceo_db/") {
1501 let mut new_path = path.split('/').collect::<Vec<_>>();
1502 if new_path.len() == 3 {
1503 new_path.pop();
1504 }
1505 path = new_path.join("/");
1506 }
1507 note.set_path(path.to_owned());
1508
1509 match self.file_notes_mut().get_mut(&path) {
1510 Some(notes) => {
1511
1512 if *note.id() == 0 {
1514 let id = notes.iter().map(|note| note.id()).max().unwrap();
1515 note.set_id(*id + 1);
1516 } else {
1517 notes.retain(|x| x.id() != note.id());
1518 }
1519
1520 notes.push(note.clone());
1521 note
1522 },
1523 None => {
1524 let notes = vec![note.clone()];
1525 self.file_notes_mut().insert(path.to_owned(), notes);
1526 note
1527 }
1528 }
1529 }
1530
1531 pub fn delete_note(&mut self, path: &str, id: u64) {
1533 let path_lower = path.to_lowercase();
1534
1535 if let Some(notes) = self.file_notes_mut().get_mut(&path_lower) {
1536 notes.retain(|note| note.id() != &id);
1537 if notes.is_empty() {
1538 self.file_notes_mut().remove(&path_lower);
1539 }
1540 }
1541 }
1542}
1543
1544impl PackSettings {
1545
1546 pub fn load(data: &[u8]) -> Result<Self> {
1548 from_slice(data).map_err(From::from)
1549 }
1550
1551 pub fn load_and_update(&mut self, data: &[u8]) -> Result<()> {
1553 let settings: Self = from_slice(data)?;
1554
1555 self.settings_bool.extend(settings.settings_bool);
1556 self.settings_number.extend(settings.settings_number);
1557 self.settings_string.extend(settings.settings_string);
1558 self.settings_text.extend(settings.settings_text);
1559
1560 Ok(())
1561 }
1562
1563 pub fn setting_string(&self, key: &str) -> Option<&String> {
1565 self.settings_string.get(key)
1566 }
1567
1568 pub fn setting_text(&self, key: &str) -> Option<&String> {
1570 self.settings_text.get(key)
1571 }
1572
1573 pub fn setting_bool(&self, key: &str) -> Option<&bool> {
1575 self.settings_bool.get(key)
1576 }
1577
1578 pub fn setting_number(&self, key: &str) -> Option<&i32> {
1580 self.settings_number.get(key)
1581 }
1582
1583 pub fn set_setting_string(&mut self, key: &str, value: &str) {
1587 self.settings_string.insert(key.to_owned(), value.to_owned());
1588 }
1589
1590 pub fn set_setting_text(&mut self, key: &str, value: &str) {
1594 self.settings_text.insert(key.to_owned(), value.to_owned());
1595 }
1596
1597 pub fn set_setting_bool(&mut self, key: &str, value: bool) {
1601 self.settings_bool.insert(key.to_owned(), value);
1602 }
1603
1604 pub fn set_setting_number(&mut self, key: &str, value: i32) {
1608 self.settings_number.insert(key.to_owned(), value);
1609 }
1610
1611 pub fn diagnostics_files_to_ignore(&self) -> Option<Vec<DiagnosticIgnoreEntry>> {
1615 self.settings_text.get("diagnostics_files_to_ignore").map(|files_to_ignore| {
1616 let files = files_to_ignore.split('\n').collect::<Vec<&str>>();
1617
1618 files.iter().filter_map(|x| {
1620 if !x.starts_with('#') {
1621 let path = x.splitn(3, ';').collect::<Vec<&str>>();
1622 if path.len() == 3 {
1623 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>>()))
1624 } else if path.len() == 2 {
1625 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![]))
1626 } else if path.len() == 1 {
1627 Some((path[0].to_string(), vec![], vec![]))
1628 } else {
1629 None
1630 }
1631 } else {
1632 None
1633 }
1634 }).collect::<Vec<DiagnosticIgnoreEntry>>()
1635 })
1636 }
1637}
1638
1639impl Default for PackHeader {
1640 fn default() -> Self {
1641 Self {
1642 pfh_version: Default::default(),
1643 pfh_file_type: Default::default(),
1644 bitmask: Default::default(),
1645 internal_timestamp: Default::default(),
1646 game_version: Default::default(),
1647 build_number: Default::default(),
1648 authoring_tool: AUTHORING_TOOL_RPFM.to_owned(),
1649 extra_subheader_data: Default::default(),
1650 }
1651 }
1652}
1653
1654impl Default for PFHFlags {
1655 fn default() -> Self {
1656 Self::empty()
1657 }
1658}
1659
1660impl Default for PackSettings {
1661 fn default() -> Self {
1662 let mut settings = Self {
1663 settings_text: BTreeMap::new(),
1664 settings_string: BTreeMap::new(),
1665 settings_bool: BTreeMap::new(),
1666 settings_number: BTreeMap::new(),
1667 };
1668
1669 settings.settings_text_mut().insert("diagnostics_files_to_ignore".to_owned(), "".to_owned());
1670 settings.settings_text_mut().insert("import_files_to_ignore".to_owned(), "".to_owned());
1671 settings.settings_bool_mut().insert("disable_autosaves".to_owned(), false);
1672 settings.settings_bool_mut().insert("do_not_generate_existing_locs".to_owned(), false);
1673 settings.settings_string_mut().insert(SETTING_KEY_CF.to_owned(), "None".to_owned());
1674 settings
1675 }
1676}