1use bitflags::bitflags;
60use getset::*;
61use rayon::prelude::*;
62use serde_derive::{Serialize, Deserialize};
63use serde_json::{from_slice, to_string_pretty};
64use itertools::Itertools;
65
66use std::cmp::Ordering;
67use std::collections::{BTreeMap, HashMap, HashSet};
68use std::fs::File;
69use std::hash::{DefaultHasher, Hash, Hasher};
70use std::io::{BufReader, BufWriter, Cursor, SeekFrom, Write};
71use std::path::{Path, PathBuf};
72use std::str::FromStr;
73
74use crate::binary::{ReadBytes, WriteBytes};
75use crate::compression::{Compressible, CompressionFormat};
76use crate::error::{RLibError, Result};
77use crate::files::{Container, ContainerPath, Decodeable, DecodeableExtraData, Encodeable, EncodeableExtraData, FileType, Loc, RFile, RFileDecoded, table::DecodedData};
78use crate::games::{GameInfo, pfh_file_type::PFHFileType, pfh_version::PFHVersion};
79use crate::notes::Note;
80use crate::utils::{current_time, last_modified_time_from_file};
81
82#[cfg(test)]
83mod pack_test;
84mod pack_versions;
85
86pub const EXTENSION: &str = ".pack";
88
89const MFH_PREAMBLE: &str = "MFH";
91
92const TERRY_MAP_PATH: &str = "terrain/tiles/battle/_assembly_kit";
94
95const DEFAULT_BMD_DATA: &str = "bmd_data.bin";
97
98const MISSING_LOCS_PATH_START_EXISTING: &str = "text/aaa_missing_locs_";
100const MISSING_LOCS_PATH_START_NEW: &str = "text/zzz_missing_locs_";
102
103const FORT_PERIMETER_HINT: &[u8; 18] = b"AIH_FORT_PERIMETER";
105const DEFENSIVE_HILL_HINT: &[u8; 18] = b"AIH_DEFENSIVE_HILL";
106const SIEGE_AREA_NODE_HINT: &[u8; 19] = b"AIH_SIEGE_AREA_NODE";
107
108pub const RESERVED_NAME_DEPENDENCIES_MANAGER: &str = "dependencies_manager.rpfm_reserved";
110pub const RESERVED_NAME_DEPENDENCIES_MANAGER_V2: &str = "dependencies_manager_v2.rpfm_reserved";
112pub const RESERVED_NAME_EXTRA_PACKFILE: &str = "extra_packfile.rpfm_reserved";
114pub const RESERVED_NAME_SETTINGS: &str = "settings.rpfm_reserved";
116pub const RESERVED_NAME_SETTINGS_EXTRACTED: &str = "settings.rpfm_reserved.json";
118pub const RESERVED_NAME_NOTES: &str = "notes.rpfm_reserved";
120pub const RESERVED_NAME_NOTES_EXTRACTED: &str = "notes.rpfm_reserved.md";
122
123pub const RESERVED_RFILE_NAMES: [&str; 3] = [RESERVED_NAME_EXTRA_PACKFILE, RESERVED_NAME_SETTINGS, RESERVED_NAME_NOTES];
128
129const AUTHORING_TOOL_CA: &str = "CA_TOOL";
131const AUTHORING_TOOL_RPFM: &str = "RPFM";
133const AUTHORING_TOOL_SIZE: u32 = 8;
135
136pub const SETTING_KEY_CF: &str = "compression_format";
138
139bitflags! {
140
141 #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
146 pub struct PFHFlags: u32 {
147
148 const HAS_EXTENDED_HEADER = 0b0000_0001_0000_0000;
150
151 const HAS_ENCRYPTED_INDEX = 0b0000_0000_1000_0000;
153
154 const HAS_INDEX_WITH_TIMESTAMPS = 0b0000_0000_0100_0000;
156
157 const HAS_ENCRYPTED_DATA = 0b0000_0000_0001_0000;
159 }
160}
161
162#[derive(Debug, Clone, PartialEq, Getters, MutGetters, Setters, Default, Serialize, Deserialize)]
206#[getset(get = "pub", get_mut = "pub", set = "pub")]
207pub struct Pack {
208
209 disk_file_path: String,
211
212 disk_file_offset: u64,
214
215 local_timestamp: u64,
217
218 #[getset(skip)]
220 compress: bool,
221
222 header: PackHeader,
224
225 dependencies: Vec<(bool, String)>,
229
230 files: HashMap<String, RFile>,
232
233 paths: HashMap<String, Vec<String>>,
235
236 notes: PackNotes,
238
239 settings: PackSettings,
241}
242
243#[derive(Debug, Clone, PartialEq, Eq, Getters, Setters, Serialize, Deserialize)]
273#[getset(get = "pub", set = "pub")]
274pub struct PackHeader {
275
276 pfh_version: PFHVersion,
278
279 pfh_file_type: PFHFileType,
281
282 bitmask: PFHFlags,
284
285 internal_timestamp: u64,
287
288 game_version: u32,
290
291 build_number: u32,
293
294 authoring_tool: String,
296
297 extra_subheader_data: Vec<u8>,
299}
300
301#[derive(Clone, Debug, PartialEq, Eq, Getters, MutGetters, Setters, Serialize, Deserialize)]
315#[getset(get = "pub", get_mut = "pub", set = "pub")]
316pub struct PackSettings {
317
318 settings_text: BTreeMap<String, String>,
320
321 settings_string: BTreeMap<String, String>,
323
324 settings_bool: BTreeMap<String, bool>,
326
327 settings_number: BTreeMap<String, i32>,
329}
330
331#[derive(Clone, Debug, PartialEq, Eq, Default, Getters, MutGetters, Setters, Serialize, Deserialize)]
337#[getset(get = "pub", get_mut = "pub", set = "pub")]
338pub struct PackNotes {
339
340 pack_notes: String,
342
343 file_notes: HashMap<String, Vec<Note>>,
348}
349
350pub type DiagnosticIgnoreEntry = (String, Vec<String>, Vec<String>);
354
355impl Container for Pack {
360
361 fn extract_metadata(&mut self, destination_path: &Path) -> Result<Vec<PathBuf>> {
365 let mut paths = vec![];
366 let mut data = vec![];
367 data.write_all(to_string_pretty(&self.notes)?.as_bytes())?;
368 data.extend_from_slice(b"\n"); let path = destination_path.join(RESERVED_NAME_NOTES_EXTRACTED);
371 paths.push(path.to_owned());
372 let mut file = BufWriter::new(File::create(path)?);
373 file.write_all(&data)?;
374 file.flush()?;
375
376 let mut data = vec![];
377 data.write_all(to_string_pretty(&self.settings)?.as_bytes())?;
378 data.extend_from_slice(b"\n"); let path = destination_path.join(RESERVED_NAME_SETTINGS_EXTRACTED);
381 paths.push(path.to_owned());
382 let mut file = BufWriter::new(File::create(path)?);
383 file.write_all(&data)?;
384 file.flush()?;
385
386 Ok(paths)
387 }
388
389 fn insert(&mut self, mut file: RFile) -> Result<Option<ContainerPath>> {
390
391 let path_container = file.path_in_container();
393 let path = file.path_in_container_raw();
394 if path == RESERVED_NAME_NOTES_EXTRACTED {
395 self.notes = PackNotes::load(&file.encode(&None, false, false, true)?.unwrap())?;
396 Ok(None)
397 } else if path == RESERVED_NAME_SETTINGS_EXTRACTED {
398 self.settings = PackSettings::load(&file.encode(&None, false, false, true)?.unwrap())?;
399 Ok(None)
400 } else if path == RESERVED_NAME_DEPENDENCIES_MANAGER_V2 {
401 self.dependencies = from_slice(&file.encode(&None, false, false, true)?.unwrap())?;
402 Ok(None)
403 }
404
405 else {
407 self.paths_cache_insert_path(path);
408 self.files.insert(path.to_owned(), file);
409
410 Ok(Some(path_container))
411 }
412 }
413
414 fn disk_file_path(&self) -> &str {
415 &self.disk_file_path
416 }
417
418 fn files(&self) -> &HashMap<String, RFile> {
419 &self.files
420 }
421
422 fn files_mut(&mut self) -> &mut HashMap<String, RFile> {
423 &mut self.files
424 }
425
426 fn disk_file_offset(&self) -> u64 {
427 self.disk_file_offset
428 }
429
430 fn paths_cache(&self) -> &HashMap<String, Vec<String>> {
431 &self.paths
432 }
433
434 fn paths_cache_mut(&mut self) -> &mut HashMap<String, Vec<String>> {
435 &mut self.paths
436 }
437
438 fn internal_timestamp(&self) -> u64 {
439 self.header.internal_timestamp
440 }
441
442 fn local_timestamp(&self) -> u64 {
443 self.local_timestamp
444 }
445
446 fn move_path(&mut self, source_path: &ContainerPath, destination_path: &ContainerPath) -> Result<Vec<(ContainerPath, ContainerPath)>> {
450 match source_path {
451 ContainerPath::File(source_path) => match destination_path {
452 ContainerPath::File(destination_path) => {
453 if RESERVED_RFILE_NAMES.contains(&&**destination_path) {
454 return Err(RLibError::ReservedFiles);
455 }
456
457 if destination_path.is_empty() {
458 return Err(RLibError::EmptyDestiny);
459 }
460
461 self.paths_cache_remove_path(source_path);
462 let mut moved = self.files_mut()
463 .remove(source_path)
464 .ok_or_else(|| RLibError::FileNotFound(source_path.to_string()))?;
465
466 moved.set_path_in_container_raw(destination_path);
467
468 self.insert(moved).map(|x| match x {
469 Some(x) => vec![(ContainerPath::File(source_path.to_string()), x); 1],
470 None => Vec::with_capacity(0),
471 })
472 },
473 ContainerPath::Folder(_) => unreachable!("move_path_pack_1"),
474 },
475 ContainerPath::Folder(source_path) => match destination_path {
476 ContainerPath::File(_) => unreachable!("move_path_pack_2"),
477 ContainerPath::Folder(destination_path) => {
478 if destination_path.is_empty() {
479 return Err(RLibError::EmptyDestiny);
480 }
481
482 let mut source_path_end = source_path.to_owned();
484 if !source_path_end.ends_with('/') {
485 source_path_end.push('/');
486 }
487
488 let moved_paths = self.files()
489 .par_iter()
490 .filter_map(|(path, _)| if path.starts_with(&source_path_end) { Some(path.to_owned()) } else { None })
491 .collect::<Vec<_>>();
492
493 let moved = moved_paths.iter()
494 .filter_map(|x| {
495 self.paths_cache_remove_path(x);
496 self.files_mut().remove(x)
497 })
498 .collect::<Vec<_>>();
499
500 let mut new_paths = Vec::with_capacity(moved.len());
501 for mut moved in moved {
502 let old_path = moved.path_in_container();
503 let new_path = moved.path_in_container_raw().replacen(source_path, destination_path, 1);
504 moved.set_path_in_container_raw(&new_path);
505
506 if let Some(new_path) = self.insert(moved)? {
507 new_paths.push((old_path, new_path));
508 }
509 }
510
511 Ok(new_paths)
512 },
513 },
514 }
515 }
516}
517
518impl Decodeable for Pack {
519
520 fn decode<R: ReadBytes>(data: &mut R, extra_data: &Option<DecodeableExtraData>) -> Result<Self> {
521 Self::read(data, extra_data)
522 }
523}
524
525impl Encodeable for Pack {
526
527 fn encode<W: WriteBytes>(&mut self, buffer: &mut W, extra_data: &Option<EncodeableExtraData>) -> Result<()> {
528 self.write(buffer, extra_data)
529 }
530}
531
532impl Pack {
534
535 pub fn new_with_version(pfh_version: PFHVersion) -> Self {
537 let mut pack = Self::default();
538 pack.header.pfh_version = pfh_version;
539 pack
540 }
541
542 pub fn new_with_name_and_version(name: &str, pfh_version: PFHVersion) -> Self {
544 let mut pack = Self::default();
545 pack.header.pfh_version = pfh_version;
546 pack.disk_file_path = name.to_owned();
547 pack
548 }
549
550 fn read<R: ReadBytes>(data: &mut R, extra_data: &Option<DecodeableExtraData>) -> Result<Self> {
554 let extra_data = extra_data.as_ref().ok_or(RLibError::DecodingMissingExtraData)?;
555
556 let game_info = match extra_data.game_info {
558 Some(game_info) => game_info,
559 None => return Err(RLibError::GameInfoMissingFromDecodingFunction),
560 };
561
562 let disk_file_path = match extra_data.disk_file_path {
565 Some(path) => {
566 let file_path = PathBuf::from_str(path).map_err(|_|RLibError::DecodingMissingExtraDataField("disk_file_path".to_owned()))?;
567 if file_path.is_file() {
568 path.to_owned()
569 } else {
570 return Err(RLibError::DecodingMissingExtraData)
571 }
572 }
573 None => String::new()
574 };
575
576 let disk_file_offset = extra_data.disk_file_offset;
577 let disk_file_size = if extra_data.data_size > 0 { extra_data.data_size } else { data.len()? };
578 let timestamp = extra_data.timestamp;
579 let is_encrypted = extra_data.is_encrypted;
580 let skip_path_cache_generation = extra_data.skip_path_cache_generation;
581
582 let lazy_load = !disk_file_path.is_empty() && !is_encrypted && extra_data.lazy_load;
584
585 let data_len = disk_file_size;
588 if data_len < 24 {
589 return Err(RLibError::PackHeaderNotComplete);
590 }
591
592 let start = if data.read_string_u8(3)? == MFH_PREAMBLE { 8 } else { 0 };
594 data.seek(SeekFrom::Current(-3))?;
595 data.seek(SeekFrom::Current(start))?;
596
597 let mut pack = Self {
599 disk_file_path,
600 disk_file_offset,
601 local_timestamp: timestamp,
602 ..Default::default()
603 };
604
605 pack.header.pfh_version = PFHVersion::version(&data.read_string_u8(4)?)?;
606
607 let pack_type = data.read_u32()?;
608 pack.header.pfh_file_type = PFHFileType::try_from(pack_type & 15)?;
609 pack.header.bitmask = PFHFlags::from_bits_truncate(pack_type & !15);
610
611 let expected_data_len = match pack.header.pfh_version {
614 PFHVersion::PFH6 => pack.read_pfh6(data, extra_data)?,
615 PFHVersion::PFH5 => pack.read_pfh5(data, extra_data)?,
616 PFHVersion::PFH4 => pack.read_pfh4(data, extra_data)?,
617 PFHVersion::PFH3 => pack.read_pfh3(data, extra_data)?,
618 PFHVersion::PFH2 => pack.read_pfh2(data, extra_data)?,
619 PFHVersion::PFH0 => pack.read_pfh0(data, extra_data)?,
620 };
621
622 if let Some(mut notes) = pack.files.remove(RESERVED_NAME_NOTES) {
624 notes.load()?;
625 let data = notes.cached()?;
626
627 match PackNotes::load(data) {
630 Ok(notes) => pack.notes = notes,
631 Err(_) => {
632 let len = data.len();
633 let mut data = Cursor::new(data);
634 pack.notes = PackNotes::default();
635 pack.notes.pack_notes = data.read_string_u8(len)?;
636 }
637 }
638 }
639
640 if let Some(mut settings) = pack.files.remove(RESERVED_NAME_SETTINGS) {
641 settings.load()?;
642 let data = settings.cached()?;
643 pack.settings.load_and_update(data)?;
644 }
645
646 if let Some(mut deps) = pack.files.remove(RESERVED_NAME_DEPENDENCIES_MANAGER_V2) {
647 deps.load()?;
648 let data = deps.cached()?;
649 pack.dependencies = from_slice(data)?;
650 }
651
652 if !skip_path_cache_generation {
654 pack.paths_cache_generate();
655 }
656
657 let preferred_cf = game_info.compression_formats_supported().first().cloned().unwrap_or_default();
662 let current_cf_str = pack.settings().setting_string(SETTING_KEY_CF).cloned().unwrap_or_default();
663 let current_cf = CompressionFormat::from(&*current_cf_str);
664
665 if pack.compress && current_cf == CompressionFormat::None {
666 pack.settings_mut().set_setting_string(SETTING_KEY_CF, preferred_cf.to_string().as_str());
667 }
668
669 if expected_data_len != data_len { return Err(RLibError::DecodingMismatchSizeError(data_len as usize, expected_data_len as usize)) }
675
676 pack.files.par_iter_mut().map(|(_, file)| file.guess_file_type()).collect::<Result<()>>()?;
678
679 if !lazy_load {
681 pack.files.par_iter_mut().try_for_each(|(_, file)| file.load())?;
682 }
683
684 Ok(pack)
686 }
687
688 fn write<W: WriteBytes>(&mut self, buffer: &mut W, extra_data: &Option<EncodeableExtraData>) -> Result<()> {
690 let test_mode = if let Some(extra_data) = extra_data {
691 extra_data.test_mode
692 } else {
693 false
694 };
695
696 if !test_mode {
697
698 if self.header.pfh_file_type == PFHFileType::Mod || self.header.pfh_file_type == PFHFileType::Movie {
700
701 let mut data = vec![];
703 data.write_all(to_string_pretty(&self.notes)?.as_bytes())?;
704 let file = RFile::new_from_vec(&data, FileType::Text, 0, RESERVED_NAME_NOTES);
705 self.files.insert(RESERVED_NAME_NOTES.to_owned(), file);
706
707 let mut data = vec![];
709 data.write_all(to_string_pretty(&self.settings)?.as_bytes())?;
710 let file = RFile::new_from_vec(&data, FileType::Text, 0, RESERVED_NAME_SETTINGS);
711 self.files.insert(RESERVED_NAME_SETTINGS.to_owned(), file);
712
713 let mut data = vec![];
715 data.write_all(to_string_pretty(&self.dependencies)?.as_bytes())?;
716 let file = RFile::new_from_vec(&data, FileType::Text, 0, RESERVED_NAME_DEPENDENCIES_MANAGER_V2);
717 self.files.insert(RESERVED_NAME_DEPENDENCIES_MANAGER_V2.to_owned(), file);
718
719 }
720 }
721
722 match self.header.pfh_version {
723 PFHVersion::PFH6 => self.write_pfh6(buffer, extra_data)?,
724 PFHVersion::PFH5 => self.write_pfh5(buffer, extra_data)?,
725 PFHVersion::PFH4 => self.write_pfh4(buffer, extra_data)?,
726 PFHVersion::PFH3 => self.write_pfh3(buffer, extra_data)?,
727 PFHVersion::PFH2 => self.write_pfh2(buffer, extra_data)?,
728 PFHVersion::PFH0 => self.write_pfh0(buffer, extra_data)?,
729 }
730
731 self.remove(&ContainerPath::File(RESERVED_NAME_NOTES.to_owned()));
733 self.remove(&ContainerPath::File(RESERVED_NAME_SETTINGS.to_owned()));
734 self.remove(&ContainerPath::File(RESERVED_NAME_DEPENDENCIES_MANAGER_V2.to_owned()));
735
736 Ok(())
738 }
739
740 pub fn read_and_merge_ca_packs(game: &GameInfo, game_path: &Path) -> Result<Self> {
748 let paths = game.ca_packs_paths(game_path)?;
749 let mut pack = Self::read_and_merge(&paths, game, true, true, false)?;
750
751 pack.header_mut().set_pfh_file_type(PFHFileType::Release);
753 Ok(pack)
754 }
755
756 pub fn read_and_merge(pack_paths: &[PathBuf], game: &GameInfo, lazy_load: bool, ignore_mods: bool, keep_order: bool) -> Result<Self> {
760 if pack_paths.is_empty() {
761 return Err(RLibError::NoPacksProvided);
762 }
763
764 let mut extra_data = DecodeableExtraData {
765 lazy_load,
766 game_info: Some(game),
767 ..Default::default()
768 };
769
770 if pack_paths.len() == 1 {
772 let mut data = BufReader::new(File::open(&pack_paths[0])
773 .map_err(|error| RLibError::IOErrorPath(Box::new(RLibError::IOError(error)), pack_paths[0].to_path_buf()))?);
774 let path_str = pack_paths[0].to_string_lossy().replace('\\', "/");
775
776 extra_data.set_disk_file_path(Some(&path_str));
777 extra_data.set_timestamp(last_modified_time_from_file(data.get_ref()).unwrap());
778
779 return Self::read(&mut data, &Some(extra_data))
780 }
781
782 extra_data.set_skip_path_cache_generation(true);
784
785 let mut pack_new = Pack::default();
787 let mut packs = pack_paths.par_iter()
788 .map(|path| {
789 let mut data = BufReader::new(File::open(path)
790 .map_err(|error| RLibError::IOErrorPath(Box::new(RLibError::IOError(error)), pack_paths[0].to_path_buf()))?);
791 let path_str = path.to_string_lossy().replace('\\', "/");
792
793 let mut extra_data = extra_data.to_owned();
794 extra_data.set_disk_file_path(Some(&path_str));
795 extra_data.set_timestamp(last_modified_time_from_file(data.get_ref())?);
796
797 Self::read(&mut data, &Some(extra_data))
798 }).collect::<Result<Vec<Pack>>>()?;
799
800 packs.sort_by(|pack_a, pack_b| if pack_a.pfh_file_type() != pack_b.pfh_file_type() {
802 pack_a.pfh_file_type().cmp(&pack_b.pfh_file_type())
803 } else if !keep_order {
804 pack_a.disk_file_path.cmp(&pack_b.disk_file_path)
805 } else {
806 Ordering::Equal
807 });
808
809 packs.iter()
810 .chunk_by(|pack| pack.header.pfh_file_type)
811 .into_iter()
812 .for_each(|(pfh_type, packs)| {
813 if pfh_type != PFHFileType::Mod || !ignore_mods {
814 let mut packs = packs.collect::<Vec<_>>();
815 packs.reverse();
816 packs.iter()
817 .for_each(|pack| {
818 pack_new.files_mut().extend(pack.files().clone())
819 });
820 }
821 });
822
823 let pack_names = packs.iter().map(|pack| pack.disk_file_name()).collect::<Vec<_>>();
825 let mut dependencies = packs.iter()
826 .flat_map(|pack| pack.dependencies()
827 .iter()
828 .filter(|(_, dependency)| !pack_names.contains(dependency))
829 .cloned()
830 .collect::<Vec<_>>())
831 .collect::<Vec<_>>();
832
833 let mut set = HashSet::new();
835 dependencies.retain(|x| set.insert(x.clone()));
836 pack_new.set_dependencies(dependencies);
837
838 pack_new.set_pfh_file_type(packs[0].pfh_file_type());
840 pack_new.set_pfh_version(game.pfh_version_by_file_type(pack_new.pfh_file_type()));
841
842 pack_new.paths_cache_generate();
844
845 Ok(pack_new)
846 }
847
848 pub fn merge(packs: &[Self]) -> Result<Self> {
855 if packs.is_empty() {
856 return Err(RLibError::NoPacksProvided);
857 }
858
859 if packs.len() == 1 {
861 return Ok(packs[0].clone());
862 }
863
864 let mut pack_new = Pack::default();
866
867 let mut pfh_types = packs.iter().map(|pack| pack.pfh_file_type()).collect::<Vec<_>>();
868 pfh_types.sort();
869 pfh_types.dedup();
870
871 if pfh_types.len() == 1 {
872 pack_new.set_pfh_file_type(pfh_types[0]);
873 }
874
875 packs.iter()
876 .chunk_by(|pack| pack.header.pfh_file_type)
877 .into_iter()
878 .for_each(|(_, packs)| {
879 let mut packs = packs.collect::<Vec<_>>();
880 packs.reverse();
881 packs.iter()
882 .for_each(|pack| {
883 pack_new.files_mut().extend(pack.files().clone())
884 });
885 });
886
887 let pack_names = packs.iter().map(|pack| pack.disk_file_name()).collect::<Vec<_>>();
889 let mut dependencies = packs.iter()
890 .flat_map(|pack| pack.dependencies()
891 .iter()
892 .filter(|(_, dependency)| !pack_names.contains(dependency))
893 .cloned()
894 .collect::<Vec<_>>())
895 .collect::<Vec<_>>();
896
897 let mut set = HashSet::new();
899 dependencies.retain(|x| set.insert(x.clone()));
900 pack_new.set_dependencies(dependencies);
901
902 pack_new.set_pfh_file_type(packs[0].pfh_file_type());
904 pack_new.set_pfh_version(packs[0].pfh_version());
905
906 pack_new.paths_cache_generate();
908
909 Ok(pack_new)
910 }
911
912 pub fn save(&mut self, path: Option<&Path>, game_info: &GameInfo, extra_data: &Option<EncodeableExtraData>) -> Result<()> {
916 if let Some(path) = path {
917 self.disk_file_path = path.to_string_lossy().to_string();
918 }
919
920 self.files.iter_mut().try_for_each(|(_, file)| file.load())?;
922
923 let mut file = BufWriter::new(File::create(&self.disk_file_path)?);
924 let extra_data = if extra_data.is_some() {
925 extra_data.clone()
926 } else {
927 Some(EncodeableExtraData::new_from_game_info(game_info))
928 };
929
930 self.encode(&mut file, &extra_data)
931 }
932
933 pub fn pfh_version(&self) -> PFHVersion {
939 *self.header.pfh_version()
940 }
941
942 pub fn pfh_file_type(&self) -> PFHFileType {
944 *self.header.pfh_file_type()
945 }
946
947 pub fn bitmask(&self) -> PFHFlags {
949 *self.header.bitmask()
950 }
951
952 pub fn internal_timestamp(&self) -> u64 {
954 *self.header.internal_timestamp()
955 }
956
957 pub fn game_version(&self) -> u32 {
959 *self.header.game_version()
960 }
961
962 pub fn build_number(&self) -> u32 {
964 *self.header.build_number()
965 }
966
967 pub fn authoring_tool(&self) -> &str {
969 self.header.authoring_tool()
970 }
971
972 pub fn extra_subheader_data(&self) -> &[u8] {
974 self.header.extra_subheader_data()
975 }
976pub fn compression_format(&self) -> CompressionFormat {
992 let cf = self.settings().setting_string(SETTING_KEY_CF).map(|x| x.to_owned());
993 CompressionFormat::from(cf.unwrap_or_default().as_str())
994 }
995
996 pub fn set_pfh_version(&mut self, version: PFHVersion) {
998 self.header.set_pfh_version(version);
999 }
1000
1001 pub fn set_pfh_file_type(&mut self, file_type: PFHFileType) {
1003 self.header.set_pfh_file_type(file_type);
1004 }
1005
1006 pub fn set_bitmask(&mut self, bitmask: PFHFlags) {
1008 self.header.set_bitmask(bitmask);
1009 }
1010
1011 pub fn set_internal_timestamp(&mut self, timestamp: u64) {
1013 self.header.set_internal_timestamp(timestamp);
1014 }
1015
1016 pub fn set_game_version(&mut self, game_version: u32) {
1018 self.header.set_game_version(game_version);
1019 }
1020
1021 pub fn set_build_number(&mut self, build_number: u32) {
1023 self.header.set_build_number(build_number);
1024 }
1025
1026 pub fn set_authoring_tool(&mut self, authoring_tool: &str) {
1028 self.header.set_authoring_tool(authoring_tool.to_string());
1029 }
1030
1031 pub fn set_extra_subheader_data(&mut self, extra_subheader_data: &[u8]) {
1033 self.header.set_extra_subheader_data(extra_subheader_data.to_vec());
1034 }
1035
1036 pub fn set_compression_format(&mut self, cf: CompressionFormat, game_info: &GameInfo) -> CompressionFormat {
1041 if cf == CompressionFormat::None || !game_info.compression_formats_supported().contains(&cf) {
1042 self.compress = false;
1043 self.settings_mut().set_setting_string(SETTING_KEY_CF, CompressionFormat::None.to_string().as_str());
1044 CompressionFormat::None
1045 } else {
1046 self.compress = true;
1047 self.settings_mut().set_setting_string(SETTING_KEY_CF, cf.to_string().as_str());
1048 cf
1049 }
1050 }
1051
1052 pub fn spoof_ca_authoring_tool(&mut self, spoof: bool) {
1060 if spoof {
1061 self.header.set_authoring_tool(AUTHORING_TOOL_CA.to_string());
1062 } else {
1063 self.header.set_authoring_tool(AUTHORING_TOOL_RPFM.to_string());
1064 }
1065 }
1066
1067 pub fn is_compressible(&self) -> bool {
1069 matches!(self.header.pfh_version, PFHVersion::PFH6 | PFHVersion::PFH5)
1070 }
1071
1072 pub fn missing_locs_paths(&self) -> (String, String) {
1076 (
1077 MISSING_LOCS_PATH_START_EXISTING.to_owned() + &self.disk_file_name() + ".loc",
1078 MISSING_LOCS_PATH_START_NEW.to_owned() + &self.disk_file_name() + ".loc"
1079 )
1080 }
1081
1082 pub fn generate_missing_loc_data(&mut self, existing_locs: &HashMap<String, String>) -> Result<Vec<ContainerPath>> {
1084 let mut new_files = vec![];
1085
1086 let (missing_locs_path_existing, missing_locs_path_new) = self.missing_locs_paths();
1087
1088 let db_tables = self.files_by_type(&[FileType::DB]);
1089 let loc_tables = self.files_by_type(&[FileType::Loc]);
1090 let mut missing_trads_file_new = Loc::new();
1091 let mut missing_trads_file_overwritten = Loc::new();
1092
1093 let loc_keys_from_memory = loc_tables.par_iter().filter_map(|rfile| {
1094 if rfile.path_in_container_raw() != missing_locs_path_new && rfile.path_in_container_raw() != missing_locs_path_existing {
1095 if let Ok(RFileDecoded::Loc(table)) = rfile.decoded() {
1096 Some(table.data().iter().filter_map(|x| {
1097 if let DecodedData::StringU16(data) = &x[0] {
1098 Some(data.to_owned())
1099 } else {
1100 None
1101 }
1102 }).collect::<HashSet<String>>())
1103 } else { None }
1104 } else { None }
1105 }).flatten().collect::<HashSet<String>>();
1106
1107 let (missing_trads_new, missing_trads_overwritten) = db_tables.par_iter().filter_map(|rfile| {
1108 if let Ok(RFileDecoded::DB(table)) = rfile.decoded() {
1109 let definition = table.definition();
1110 let loc_fields = definition.localised_fields();
1111 let table_data = table.data();
1112 let table_name = table.table_name_without_tables();
1113 let fields_processed = definition.fields_processed();
1114
1115 let has_loc_fields = !loc_fields.is_empty();
1116 let is_building_culture_variants = table_name == "building_culture_variants";
1117
1118 if has_loc_fields || is_building_culture_variants {
1119 let localised_order = definition.localised_key_order();
1121 let mut new_rows_new = vec![];
1122 let mut new_rows_overwritten = vec![];
1123
1124 for row in table_data.iter() {
1125
1126 if has_loc_fields {
1128 for loc_field in loc_fields {
1129 let key = localised_order.iter().map(|pos| row[*pos as usize].data_to_string()).join("");
1130
1131 if !key.is_empty() {
1133 let loc_key = format!("{}_{}_{}", table_name, loc_field.name(), key);
1134
1135 if let Some(value) = existing_locs.get(&loc_key) {
1136 let mut new_row = missing_trads_file_overwritten.new_row();
1137 new_row[0] = DecodedData::StringU16(loc_key);
1138 new_row[1] = DecodedData::StringU16(value.to_owned());
1139 new_rows_overwritten.push(new_row);
1140
1141 } else if !loc_keys_from_memory.contains(&*loc_key) {
1142 let mut new_row = missing_trads_file_new.new_row();
1143 new_row[0] = DecodedData::StringU16(loc_key);
1144 new_row[1] = DecodedData::StringU16("PLACEHOLDER".to_owned());
1145 new_rows_new.push(new_row);
1146 }
1147 }
1148 }
1149 }
1150
1151 if is_building_culture_variants {
1155 if let Some(short_desc_index) = fields_processed.iter().position(|x| x.name() == "short_description") {
1156 let key = row[short_desc_index].data_to_string();
1157 if !key.is_empty() {
1158 let loc_key = format!("building_short_description_texts_short_description_{}", key);
1159
1160 if let Some(value) = existing_locs.get(&loc_key) {
1161 let mut new_row = missing_trads_file_overwritten.new_row();
1162 new_row[0] = DecodedData::StringU16(loc_key);
1163 new_row[1] = DecodedData::StringU16(value.to_owned());
1164 new_rows_overwritten.push(new_row);
1165
1166 } else if !loc_keys_from_memory.contains(&*loc_key) {
1167 let mut new_row = missing_trads_file_new.new_row();
1168 new_row[0] = DecodedData::StringU16(loc_key);
1169 new_row[1] = DecodedData::StringU16("PLACEHOLDER".to_owned());
1170 new_rows_new.push(new_row);
1171 }
1172 }
1173 }
1174 }
1175 }
1176
1177 return Some((new_rows_new, new_rows_overwritten))
1178 }
1179 }
1180 None
1181 }).collect::<(Vec<Vec<Vec<DecodedData>>>, Vec<Vec<Vec<DecodedData>>>)>();
1182
1183 let missing_trads_new = missing_trads_new.into_iter().flatten().collect::<Vec<_>>();
1185 let missing_trads_overwritten = missing_trads_overwritten.into_iter().flatten().collect::<Vec<_>>();
1186
1187 if !missing_trads_new.is_empty() {
1189 let _ = missing_trads_file_new.set_data(&missing_trads_new);
1190 let packed_file = RFile::new_from_decoded(&RFileDecoded::Loc(missing_trads_file_new), 0, &missing_locs_path_new);
1191 new_files.push(self.insert(packed_file)?.unwrap());
1192 }
1193
1194 if !missing_trads_overwritten.is_empty() && !self.settings.setting_bool("do_not_generate_existing_locs").unwrap_or(&false) {
1195 let _ = missing_trads_file_overwritten.set_data(&missing_trads_overwritten);
1196 let packed_file = RFile::new_from_decoded(&RFileDecoded::Loc(missing_trads_file_overwritten), 0, &missing_locs_path_existing);
1197 new_files.push(self.insert(packed_file)?.unwrap());
1198 }
1199
1200 Ok(new_files)
1201 }
1202
1203 pub fn patch_siege_ai(&mut self) -> Result<(String, Vec<ContainerPath>)> {
1207
1208 if self.files().is_empty() {
1210 return Err(RLibError::PatchSiegeAIEmptyPack)
1211 }
1212
1213 let mut files_patched = 0;
1214 let mut files_to_delete: Vec<ContainerPath> = vec![];
1215 let mut multiple_defensive_hill_hints = false;
1216
1217 for file in self.files_by_path_mut(&ContainerPath::Folder(TERRY_MAP_PATH.to_owned()), true) {
1219 let path = file.path_in_container_raw();
1220 let idx = path.rfind('/').unwrap_or(0);
1221 let name = if path.get(idx + 1..).is_some() {
1222 &path[idx + 1..]
1223 } else {
1224 continue
1225 };
1226
1227 if name == DEFAULT_BMD_DATA || (name.starts_with("catchment_") && name.ends_with(".bin")) {
1229 file.load()?;
1230 let data = file.cached_mut()?;
1231
1232 if data.windows(19).any(|window: &[u8]|window == SIEGE_AREA_NODE_HINT) {
1235 if let Some(index) = data.windows(18).position(|window: &[u8]|window == DEFENSIVE_HILL_HINT) {
1236 data.splice(index..index + 18, FORT_PERIMETER_HINT.iter().cloned());
1237 files_patched += 1;
1238 }
1239
1240 if data.windows(18).any(|window: &[u8]|window == DEFENSIVE_HILL_HINT) {
1242 multiple_defensive_hill_hints = true;
1243 }
1244 }
1245 }
1246
1247 else if name.ends_with(".xml") {
1249 files_to_delete.push(ContainerPath::File(file.path_in_container_raw().to_string()));
1250 }
1251 }
1252
1253 files_to_delete.iter().for_each(|x| { self.remove(x); });
1255
1256 if files_patched == 0 && files_to_delete.is_empty() { Err(RLibError::PatchSiegeAINoPatchableFiles) }
1258
1259 else if files_patched == 0 {
1262 Ok((format!("No file suitable for patching has been found.\n{} files deleted.", files_to_delete.len()), files_to_delete))
1263 }
1264
1265 else if multiple_defensive_hill_hints {
1267
1268 if files_to_delete.is_empty() {
1270 Ok((format!("{files_patched} files patched.\nNo file suitable for deleting has been found.\
1271 \n\n\
1272 WARNING: Multiple Defensive Hints have been found and we only patched the first one.\
1273 If you are using SiegeAI, you should only have one Defensive Hill in the map (the \
1274 one acting as the perimeter of your fort/city/castle). Due to SiegeAI being present, \
1275 in the map, normal Defensive Hills will not work anyways, and the only thing they do \
1276 is interfere with the patching process. So, if your map doesn't work properly after \
1277 patching, delete all the extra Defensive Hill Hints. They are the culprit."), files_to_delete))
1278 }
1279 else {
1280 Ok((format!("{} files patched.\n{} files deleted.\
1281 \n\n\
1282 WARNING: Multiple Defensive Hints have been found and we only patched the first one.\
1283 If you are using SiegeAI, you should only have one Defensive Hill in the map (the \
1284 one acting as the perimeter of your fort/city/castle). Due to SiegeAI being present, \
1285 in the map, normal Defensive Hills will not work anyways, and the only thing they do \
1286 is interfere with the patching process. So, if your map doesn't work properly after \
1287 patching, delete all the extra Defensive Hill Hints. They are the culprit.",
1288 files_patched, files_to_delete.len()), files_to_delete))
1289 }
1290 }
1291
1292 else if files_to_delete.is_empty() {
1294 Ok((format!("{files_patched} files patched.\nNo file suitable for deleting has been found."), files_to_delete))
1295 }
1296
1297 else {
1299 Ok((format!("{} files patched.\n{} files deleted.", files_patched, files_to_delete.len()), files_to_delete))
1300 }
1301 }
1302
1303 pub fn live_export(&mut self, game: &GameInfo, game_path: &Path, disable_regen_table_guid: bool, keys_first: bool) -> Result<()> {
1308
1309 if self.files().is_empty() {
1311 return Err(RLibError::LiveExportNoFilesToExport);
1312 }
1313
1314 let extra_data = Some(EncodeableExtraData::new_from_game_info_and_settings(game, self.compression_format(), disable_regen_table_guid));
1315 let data_path = game.data_path(game_path)?;
1316
1317 let files = self.files_by_type_and_paths(&[FileType::Text], &[ContainerPath::Folder("script/".to_string()), ContainerPath::Folder("ui/".to_string())], true)
1319 .into_iter()
1320 .cloned()
1321 .collect::<Vec<RFile>>();
1322
1323 let mut correlations = HashMap::new();
1324 for mut file in files.into_iter() {
1325 let mut path_split = file.path_in_container_split().iter().map(|x| x.to_owned()).collect::<Vec<_>>();
1326 let mut hasher = DefaultHasher::new();
1327
1328 std::time::SystemTime::now().hash(&mut hasher);
1330 let value = hasher.finish();
1331 let new_name = format!("{}_{}", value, path_split.last().unwrap());
1332
1333 *path_split.last_mut().unwrap() = &new_name;
1334 let new_path = path_split.join("/");
1335
1336 correlations.insert(file.path_in_container_raw().to_owned(), new_path.to_owned());
1337 file.set_path_in_container_raw(&new_path);
1338
1339 let container_path = file.path_in_container();
1341 self.insert(file)?;
1342 self.extract(container_path.clone(), &data_path, true, &None, false, keys_first, &extra_data)?;
1343
1344 self.remove(&container_path);
1345 }
1346
1347 let summary_data_str = correlations.iter().map(|(key, value)| format!(" [\"{key}\"] = \"{value}\",")).join("\n");
1349 let summary_data_lua = format!("return {{\n{summary_data_str}\n}}");
1350 let summary_path = game_path.join("lua_path_mappings.txt");
1351 let mut file = BufWriter::new(File::create(summary_path)?);
1352 file.write_all(summary_data_lua.as_bytes())?;
1353
1354 Ok(())
1355 }
1356
1357 pub fn update_anim_ids(&mut self, game: &GameInfo, starting_id: i32, offset: i32) -> Result<Vec<ContainerPath>> {
1359 if offset == 0 {
1360 return Err(RLibError::UpdateAnimIdsError("Offset must be different than 0.".to_owned()))
1361 }
1362
1363 if starting_id < 0 {
1364 return Err(RLibError::UpdateAnimIdsError("Starting Id must be greater than 0.".to_owned()))
1365 }
1366
1367 let mut extra_data = DecodeableExtraData::default();
1369 extra_data.set_game_info(Some(game));
1370 let extra_data = Some(extra_data);
1371
1372 let mut files = self.files_by_type_mut(&[FileType::AnimFragmentBattle]);
1373 let mut paths = files.par_iter_mut()
1374 .filter_map(|file| {
1375 let mut changed = false;
1376 if let Ok(Some(RFileDecoded::AnimFragmentBattle(mut table))) = file.decode(&extra_data, false, true) {
1377 if *table.max_id() >= starting_id as u32 {
1378 table.set_max_id(*table.max_id() + offset as u32);
1379 changed = true;
1380 }
1381
1382 for entry in table.entries_mut() {
1383 if *entry.animation_id() >= starting_id as u32 {
1384 entry.set_animation_id(*entry.animation_id() + offset as u32);
1385 changed = true;
1386 }
1387
1388 if *entry.slot_id() >= starting_id as u32 {
1389 entry.set_slot_id(*entry.slot_id() + offset as u32);
1390 changed = true;
1391 }
1392 }
1393
1394 if changed {
1395 let _ = file.set_decoded(RFileDecoded::AnimFragmentBattle(table));
1396 Some(file.path_in_container())
1397 } else {
1398 None
1399 }
1400 } else {
1401 None
1402 }
1403 }
1404 ).collect::<Vec<_>>();
1405
1406 let mut anim_packs = self.files_by_type_mut(&[FileType::AnimPack]);
1408
1409 for anim_pack in anim_packs.iter_mut() {
1410 let mut changed = false;
1411 if let Ok(Some(RFileDecoded::AnimPack(mut pack))) = anim_pack.decode(&extra_data, false, true) {
1412
1413 let mut files = pack.files_by_type_mut(&[FileType::AnimFragmentBattle]);
1414 for file in files.iter_mut() {
1415 if let Ok(Some(RFileDecoded::AnimFragmentBattle(mut table))) = file.decode(&extra_data, false, true) {
1416 if *table.max_id() >= starting_id as u32 {
1417 table.set_max_id(*table.max_id() + offset as u32);
1418 changed = true;
1419 }
1420
1421 for entry in table.entries_mut() {
1422 if *entry.animation_id() >= starting_id as u32 {
1423 entry.set_animation_id(*entry.animation_id() + offset as u32);
1424 changed = true;
1425 }
1426
1427 if *entry.slot_id() >= starting_id as u32 {
1428 entry.set_slot_id(*entry.slot_id() + offset as u32);
1429 changed = true;
1430 }
1431 }
1432
1433 if changed {
1434 let _ = file.set_decoded(RFileDecoded::AnimFragmentBattle(table));
1435 }
1436 }
1437 }
1438
1439 if changed {
1440 let _ = anim_pack.set_decoded(RFileDecoded::AnimPack(pack));
1441 paths.push(anim_pack.path_in_container());
1442 }
1443 }
1444 }
1445
1446 Ok(paths)
1447 }
1448}
1449
1450impl PackNotes {
1451
1452 pub fn load(data: &[u8]) -> Result<Self> {
1454 from_slice(data).map_err(From::from)
1455 }
1456
1457 pub fn notes_by_path(&self, path: &str) -> Vec<Note> {
1459 let path_lower = path.to_lowercase();
1460 self.file_notes()
1461 .iter()
1462 .filter(|(path, _)| path.is_empty() || path_lower.starts_with(*path) || &&path_lower == path)
1463 .flat_map(|(_, notes)| notes.to_vec())
1464 .collect()
1465 }
1466
1467 pub fn add_note(&mut self, mut note: Note) -> Note {
1471
1472 let mut path = note.path().to_lowercase();
1474 if path.starts_with("db/") || path.starts_with("ceo_db/") {
1475 let mut new_path = path.split('/').collect::<Vec<_>>();
1476 if new_path.len() == 3 {
1477 new_path.pop();
1478 }
1479 path = new_path.join("/");
1480 }
1481 note.set_path(path.to_owned());
1482
1483 match self.file_notes_mut().get_mut(&path) {
1484 Some(notes) => {
1485
1486 if *note.id() == 0 {
1488 let id = notes.iter().map(|note| note.id()).max().unwrap();
1489 note.set_id(*id + 1);
1490 } else {
1491 notes.retain(|x| x.id() != note.id());
1492 }
1493
1494 notes.push(note.clone());
1495 note
1496 },
1497 None => {
1498 let notes = vec![note.clone()];
1499 self.file_notes_mut().insert(path.to_owned(), notes);
1500 note
1501 }
1502 }
1503 }
1504
1505 pub fn delete_note(&mut self, path: &str, id: u64) {
1507 let path_lower = path.to_lowercase();
1508
1509 if let Some(notes) = self.file_notes_mut().get_mut(&path_lower) {
1510 notes.retain(|note| note.id() != &id);
1511 if notes.is_empty() {
1512 self.file_notes_mut().remove(&path_lower);
1513 }
1514 }
1515 }
1516}
1517
1518impl PackSettings {
1519
1520 pub fn load(data: &[u8]) -> Result<Self> {
1522 from_slice(data).map_err(From::from)
1523 }
1524
1525 pub fn load_and_update(&mut self, data: &[u8]) -> Result<()> {
1527 let settings: Self = from_slice(data)?;
1528
1529 self.settings_bool.extend(settings.settings_bool);
1530 self.settings_number.extend(settings.settings_number);
1531 self.settings_string.extend(settings.settings_string);
1532 self.settings_text.extend(settings.settings_text);
1533
1534 Ok(())
1535 }
1536
1537 pub fn setting_string(&self, key: &str) -> Option<&String> {
1539 self.settings_string.get(key)
1540 }
1541
1542 pub fn setting_text(&self, key: &str) -> Option<&String> {
1544 self.settings_text.get(key)
1545 }
1546
1547 pub fn setting_bool(&self, key: &str) -> Option<&bool> {
1549 self.settings_bool.get(key)
1550 }
1551
1552 pub fn setting_number(&self, key: &str) -> Option<&i32> {
1554 self.settings_number.get(key)
1555 }
1556
1557 pub fn set_setting_string(&mut self, key: &str, value: &str) {
1561 self.settings_string.insert(key.to_owned(), value.to_owned());
1562 }
1563
1564 pub fn set_setting_text(&mut self, key: &str, value: &str) {
1568 self.settings_text.insert(key.to_owned(), value.to_owned());
1569 }
1570
1571 pub fn set_setting_bool(&mut self, key: &str, value: bool) {
1575 self.settings_bool.insert(key.to_owned(), value);
1576 }
1577
1578 pub fn set_setting_number(&mut self, key: &str, value: i32) {
1582 self.settings_number.insert(key.to_owned(), value);
1583 }
1584
1585 pub fn diagnostics_files_to_ignore(&self) -> Option<Vec<DiagnosticIgnoreEntry>> {
1589 self.settings_text.get("diagnostics_files_to_ignore").map(|files_to_ignore| {
1590 let files = files_to_ignore.split('\n').collect::<Vec<&str>>();
1591
1592 files.iter().filter_map(|x| {
1594 if !x.starts_with('#') {
1595 let path = x.splitn(3, ';').collect::<Vec<&str>>();
1596 if path.len() == 3 {
1597 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>>()))
1598 } else if path.len() == 2 {
1599 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![]))
1600 } else if path.len() == 1 {
1601 Some((path[0].to_string(), vec![], vec![]))
1602 } else {
1603 None
1604 }
1605 } else {
1606 None
1607 }
1608 }).collect::<Vec<DiagnosticIgnoreEntry>>()
1609 })
1610 }
1611}
1612
1613impl Default for PackHeader {
1614 fn default() -> Self {
1615 Self {
1616 pfh_version: Default::default(),
1617 pfh_file_type: Default::default(),
1618 bitmask: Default::default(),
1619 internal_timestamp: Default::default(),
1620 game_version: Default::default(),
1621 build_number: Default::default(),
1622 authoring_tool: AUTHORING_TOOL_RPFM.to_owned(),
1623 extra_subheader_data: Default::default(),
1624 }
1625 }
1626}
1627
1628impl Default for PFHFlags {
1629 fn default() -> Self {
1630 Self::empty()
1631 }
1632}
1633
1634impl Default for PackSettings {
1635 fn default() -> Self {
1636 let mut settings = Self {
1637 settings_text: BTreeMap::new(),
1638 settings_string: BTreeMap::new(),
1639 settings_bool: BTreeMap::new(),
1640 settings_number: BTreeMap::new(),
1641 };
1642
1643 settings.settings_text_mut().insert("diagnostics_files_to_ignore".to_owned(), "".to_owned());
1644 settings.settings_text_mut().insert("import_files_to_ignore".to_owned(), "".to_owned());
1645 settings.settings_bool_mut().insert("disable_autosaves".to_owned(), false);
1646 settings.settings_bool_mut().insert("do_not_generate_existing_locs".to_owned(), false);
1647 settings.settings_string_mut().insert(SETTING_KEY_CF.to_owned(), "None".to_owned());
1648 settings
1649 }
1650}