1use anyhow::{anyhow, Result};
21use directories::ProjectDirs;
22use ron::ser::{PrettyConfig, to_string_pretty};
23use serde_derive::{Serialize, Deserialize};
24
25use tokio::sync::broadcast;
26
27use std::collections::HashMap;
28use std::io::{BufReader, BufWriter, Read, Write};
29use std::fs::{DirBuilder, File};
30use std::path::{Path, PathBuf};
31use std::sync::{LazyLock, RwLock};
32
33use rpfm_extensions::optimizer::OptimizerOptions;
34use rpfm_extensions::translator::DEFAULT_SRC_LANG;
35
36use rpfm_ipc::settings_keys::*;
37
38use rpfm_lib::error::RLibError;
39use rpfm_lib::games::{GameInfo, LUA_AUTOGEN_FOLDER, supported_games::*};
40use rpfm_lib::schema::{DefinitionPatch, SCHEMA_FOLDER};
41
42use crate::*;
43
44const SETTINGS_FILE_NAME: &str = "settings.json";
45
46const CONFIG_REDIRECT_FILE_NAME: &str = "config_folder.txt";
48
49const DEPENDENCIES_FOLDER: &str = "dependencies";
50
51const SCRIPTS_FOLDER: &str = "scripts";
53
54const TABLE_PATCHES_FOLDER: &str = "table_patches";
55const TABLE_PROFILES_FOLDER: &str = "table_profiles";
56const TRANSLATIONS_LOCAL_FOLDER: &str = "translations_local";
57const TRANSLATIONS_REMOTE_FOLDER: &str = "translations_remote";
58
59#[macro_export]
73macro_rules! set_batch {
74 ($( $rtype:ident, $id:literal, $source:expr), *) => {
75 {
76 let mut set = SETTINGS.write().unwrap();
77 set.set_block_write(true);
78 $(
79 let _ = set.$rtype($id, $source);
80 )*
81 set.set_block_write(false);
82 let _ = set.write();
83 }
84 };
85}
86
87#[derive(Clone, Debug, Default, Serialize, Deserialize)]
103pub struct Settings {
104
105 #[serde(skip_serializing, skip_deserializing)]
108 pub block_write: bool,
109
110 pub bool: HashMap<String, bool>,
112 pub i32: HashMap<String, i32>,
114 pub f32: HashMap<String, f32>,
116 pub string: HashMap<String, String>,
119 pub raw_data: HashMap<String, Vec<u8>>,
121 pub vec_string: HashMap<String, Vec<String>>
123}
124
125pub static SETTINGS: LazyLock<RwLock<Settings>> = LazyLock::new(|| {
136 RwLock::new(Settings::init(false).unwrap_or_else(|error| {
137 rpfm_telemetry::warn!("Failed to initialize settings, falling back to defaults. Error: {error}");
138 Settings::default()
139 }))
140});
141
142pub static SETTINGS_CHANGED: LazyLock<broadcast::Sender<SettingsSnapshot>> = LazyLock::new(|| broadcast::channel(16).0);
146
147pub fn mutate_settings<T>(mutator: impl FnOnce(&mut Settings) -> Result<T>) -> Result<T> {
150 let mut settings = SETTINGS.write().unwrap();
151 let result = mutator(&mut settings)?;
152 let _ = SETTINGS_CHANGED.send(settings.snapshot());
153 Ok(result)
154}
155
156impl Settings {
161
162 pub fn init(as_new: bool) -> Result<Self> {
169 let mut settings = if !as_new {
170 match config_path() {
171 Ok(config) => Self::load_or_recover(&config.join(SETTINGS_FILE_NAME)),
172 Err(error) => {
173 rpfm_telemetry::warn!("Failed to find the settings folder, using defaults. Error: {error}");
174 Settings::default()
175 }
176 }
177 } else {
178 Settings::default()
179 };
180
181 settings.set_block_write(true);
182
183 settings.initialize_string(MYMOD_BASE_PATH, "");
184 settings.initialize_string(SECONDARY_PATH, "");
185
186 let supported_games = SupportedGames::default();
187 for game in &supported_games.games() {
188 let game_key = game.key();
189
190 let current_path = settings.string(game_key);
192 if current_path.is_empty() {
193 if current_path.contains("\\") {
194 let _ = settings.set_string(game_key, ¤t_path.replace("\\", "/"));
195 }
196
197 let game_path = if let Ok(Some(game_path)) = game.find_game_install_location() {
198 game_path.to_string_lossy().replace("\\", "/")
199 } else {
200 String::new()
201 };
202
203 if !game_path.is_empty() {
205 let _ = settings.set_string(game_key, &game_path);
206 } else {
207 settings.initialize_string(game_key, &game_path);
208 }
209 }
210
211 if game_key != KEY_EMPIRE &&
212 game_key != KEY_NAPOLEON &&
213 game_key != KEY_ARENA {
214
215 let ak_key = game_key.to_owned() + ASSEMBLY_KIT_SUFFIX;
217 let current_path = settings.string(&ak_key);
218
219 if current_path.is_empty() {
220 let ak_path = if let Ok(Some(ak_path)) = game.find_assembly_kit_install_location() {
221 ak_path.join("assembly_kit").to_string_lossy().replace("\\", "/")
222 } else {
223 String::new()
224 };
225
226 if current_path.contains("\\") {
228 let _ = settings.set_string(&ak_key, ¤t_path.replace("\\", "/"));
229 }
230
231 if !ak_path.is_empty() && game_key != KEY_SHOGUN_2 {
233 let _ = settings.set_string(&ak_key, &ak_path);
234 } else {
235 settings.initialize_string(&ak_key, &ak_path);
236 }
237 }
238 }
239 }
240
241 settings.initialize_bool(IMPORT_FROM_QT, false);
243
244 settings.initialize_string(DEFAULT_GAME, KEY_WARHAMMER_3);
246 settings.initialize_string(LANGUAGE, "English_en");
247 settings.initialize_string(THEME, THEME_OS);
248 settings.initialize_i32(AUTOSAVE_AMOUNT, 10);
250 settings.initialize_i32(AUTOSAVE_INTERVAL, 5);
251
252 settings.initialize_bool(START_MAXIMIZED, false);
263 settings.initialize_bool(ALLOW_EDITING_OF_CA_PACKFILES, false);
264 settings.initialize_bool(CHECK_UPDATES_ON_START, true);
265 settings.initialize_bool(CHECK_SCHEMA_UPDATES_ON_START, true);
266 settings.initialize_bool(CHECK_LUA_AUTOGEN_UPDATES_ON_START, true);
267 settings.initialize_bool(CHECK_OLD_AK_UPDATES_ON_START, true);
268 settings.initialize_bool(USE_LAZY_LOADING, true);
269 settings.initialize_bool(DISABLE_UUID_REGENERATION_ON_DB_TABLES, true);
270 settings.initialize_bool(PACKFILE_TREEVIEW_RESIZE_TO_FIT, false);
271 settings.initialize_bool(EXPAND_TREEVIEW_WHEN_ADDING_ITEMS, true);
272 settings.initialize_bool(USE_RIGHT_SIZE_MARKERS, false);
273 settings.initialize_bool(DISABLE_FILE_PREVIEWS, false);
274 settings.initialize_bool(INCLUDE_BASE_FOLDER_ON_ADD_FROM_FOLDER, true);
275 settings.initialize_bool(DELETE_EMPTY_FOLDERS_ON_DELETE, true);
276 settings.initialize_bool(AUTOSAVE_FOLDER_SIZE_WARNING_TRIGGERED, false);
277 settings.initialize_bool(IGNORE_GAME_FILES_IN_AK, false);
278 settings.initialize_bool(ENABLE_MULTIFOLDER_FILEPICKER, false);
279 settings.initialize_bool(ENABLE_PACK_CONTENTS_DRAG_AND_DROP, true);
280 settings.initialize_bool(CLEAN_UI, false);
281 settings.initialize_bool(SINGLE_PACK_MODE, true);
282 settings.initialize_bool(GLOBAL_SEARCH_COLLAPSE_RESULTS, false);
283 settings.initialize_bool(GLOBAL_SEARCH_AUTO_SELECT_OPEN_PACKS, true);
284
285 settings.initialize_bool(ADJUST_COLUMNS_TO_CONTENT, true);
287 settings.initialize_bool(EXTEND_LAST_COLUMN_ON_TABLES, true);
288 settings.initialize_bool(DISABLE_COMBOS_ON_TABLES, false);
289 settings.initialize_bool(TIGHT_TABLE_MODE, false);
290 settings.initialize_bool(TABLE_RESIZE_ON_EDIT, false);
291 settings.initialize_bool(TABLES_USE_OLD_COLUMN_ORDER, true);
292 settings.initialize_bool(TABLES_USE_OLD_COLUMN_ORDER_FOR_TSV, true);
293 settings.initialize_bool(ENABLE_LOOKUPS, true);
294 settings.initialize_bool(ENABLE_ICONS, true);
295 settings.initialize_bool(ENABLE_DIFF_MARKERS, true);
296 settings.initialize_bool(HIDE_UNUSED_COLUMNS, true);
297 settings.initialize_bool(SHOW_TABLE_TOOLBAR, false);
298 settings.initialize_bool(TABLE_FILTER_NEW_CHIPS_SHARE_GROUP, false);
299
300 settings.initialize_bool(CHECK_FOR_MISSING_TABLE_DEFINITIONS, false);
302 settings.initialize_bool(ENABLE_DEBUG_MENU, false);
303 settings.initialize_bool(ENABLE_UNIT_EDITOR, false);
304 settings.initialize_bool(ENABLE_ESF_EDITOR, false);
305 settings.initialize_bool(USE_DEBUG_VIEW_UNIT_VARIANT, false);
306 settings.initialize_bool(USE_DEBUG_VIEW_GROUP_FORMATIONS, false);
307 settings.initialize_bool(ENABLE_RENDERER, true);
308
309 settings.initialize_bool(DIAGNOSTICS_TRIGGER_ON_OPEN, true);
311 settings.initialize_bool(DIAGNOSTICS_TRIGGER_ON_TABLE_EDIT, true);
312
313 settings.initialize_bool(ENABLE_USAGE_TELEMETRY, true);
315 settings.initialize_bool(ENABLE_CRASH_REPORTS, true);
316
317 if settings.string(ANONYMOUS_TELEMETRY_ID).is_empty() {
319 let _ = settings.set_string(ANONYMOUS_TELEMETRY_ID, &uuid::Uuid::new_v4().to_string());
320 }
321
322 settings.initialize_string(AI_API_URL, "https://api.openai.com/v1/chat/completions");
323 settings.initialize_string(AI_API_KEY, "");
324 settings.initialize_string(AI_MODEL, "gpt-4o-mini");
325 settings.initialize_string(DEEPL_API_KEY, "");
326 settings.initialize_string(TRANSLATOR_SOURCE_LANGUAGE, DEFAULT_SRC_LANG);
327 settings.initialize_bool(TRANSLATOR_USE_DEEPL_GLOSSARY, true);
328 settings.initialize_string(GITHUB_LOGIN, "");
329
330 settings.initialize_vec_string(RECENT_FILE_LIST, &[]);
331 settings.initialize_vec_string(DIAGNOSTICS_DISABLED, &["label_field_with_path_not_found".to_owned()]);
332
333 let opt = OptimizerOptions::default();
349 settings.initialize_bool(PACK_REMOVE_ITM_FILES, *opt.pack_remove_itm_files());
350 settings.initialize_bool(PACK_APPLY_COMPRESSION, *opt.pack_apply_compression());
351 settings.initialize_bool(PACK_APPLY_ENCRYPTION, *opt.pack_apply_encryption());
352 settings.initialize_bool(PACK_REMOVE_DUPLICATED_FILES, *opt.pack_remove_duplicated_files());
353 settings.initialize_bool(DB_IMPORT_DATACORES_INTO_TWAD_KEY_DELETES, *opt.db_import_datacores_into_twad_key_deletes());
354 settings.initialize_bool(DB_OPTIMIZE_DATACORED_TABLES, *opt.db_optimize_datacored_tables());
355 settings.initialize_bool(TABLE_REMOVE_DUPLICATED_ENTRIES, *opt.table_remove_duplicated_entries());
356 settings.initialize_bool(TABLE_REMOVE_ITM_ENTRIES, *opt.table_remove_itm_entries());
357 settings.initialize_bool(TABLE_REMOVE_ITNR_ENTRIES, *opt.table_remove_itnr_entries());
358 settings.initialize_bool(TABLE_REMOVE_EMPTY_FILE, *opt.table_remove_empty_file());
359 settings.initialize_bool(TEXT_REMOVE_UNUSED_XML_MAP_FOLDERS, *opt.text_remove_unused_xml_map_folders());
360 settings.initialize_bool(TEXT_REMOVE_UNUSED_XML_PREFAB_FOLDER, *opt.text_remove_unused_xml_prefab_folder());
361 settings.initialize_bool(TEXT_REMOVE_AGF_FILES, *opt.text_remove_agf_files());
362 settings.initialize_bool(TEXT_REMOVE_MODEL_STATISTICS_FILES, *opt.text_remove_model_statistics_files());
363 settings.initialize_bool(PTS_REMOVE_UNUSED_ART_SETS, *opt.pts_remove_unused_art_sets());
364 settings.initialize_bool(PTS_REMOVE_UNUSED_VARIANTS, *opt.pts_remove_unused_variants());
365 settings.initialize_bool(PTS_REMOVE_EMPTY_MASKS, *opt.pts_remove_empty_masks());
366 settings.initialize_bool(PTS_REMOVE_EMPTY_FILE, *opt.pts_remove_empty_file());
367
368 settings.set_block_write(false);
369
370 if let Err(error) = settings.write() {
371 rpfm_telemetry::warn!("Failed to persist settings file, continuing with in-memory settings. Error: {error}");
372 }
373
374 Ok(settings)
375 }
376
377 pub fn read() -> Result<Self> {
382 Self::read_from(&config_path()?.join(SETTINGS_FILE_NAME))
383 }
384
385 pub(crate) fn read_from(path: &Path) -> Result<Self> {
387 let mut data = vec![];
388 let mut file = BufReader::new(File::open(path)?);
389 file.read_to_end(&mut data)?;
390
391 serde_json::from_slice(&data).map_err(From::from)
392 }
393
394 pub(crate) fn load_or_recover(path: &Path) -> Self {
408 let backup_path = backup_path(path);
409 match Self::read_from(path) {
410 Ok(settings) => {
411 let _ = std::fs::copy(path, &backup_path);
412 settings
413 },
414 Err(error) => {
415 rpfm_telemetry::warn!("Failed to read settings file. Error: {error}");
416 match Self::read_from(&backup_path) {
417 Ok(settings) => {
418 rpfm_telemetry::warn!("Restored the settings from their backup.");
419 settings
420 },
421 Err(_) => {
422 if std::fs::metadata(path).is_ok_and(|metadata| metadata.len() > 0) {
423 let _ = std::fs::copy(path, &backup_path);
424 }
425
426 rpfm_telemetry::warn!("Failed to read the settings backup, using defaults.");
427 Settings::default()
428 },
429 }
430 },
431 }
432 }
433
434 pub fn write(&self) -> Result<()> {
436 if self.block_write {
437 return Ok(());
438 }
439
440 self.write_to(&config_path()?.join(SETTINGS_FILE_NAME))
441 }
442
443 pub(crate) fn write_to(&self, path: &Path) -> Result<()> {
448 let temp_path = path.with_extension("json.tmp");
449 let mut file = BufWriter::new(File::create(&temp_path)?);
450 file.write_all(serde_json::to_string_pretty(self)?.as_bytes())?;
451
452 let file = file.into_inner().map_err(|error| error.into_error())?;
454 file.sync_all()?;
455 std::fs::rename(&temp_path, path)?;
456 Ok(())
457 }
458
459 pub fn set_block_write(&mut self, status: bool) {
461 self.block_write = status;
462 }
463
464 pub fn bool(&self, setting: &str) -> bool {
466 self.bool.get(setting).copied().unwrap_or_default()
467 }
468
469 pub fn i32(&self, setting: &str) -> i32 {
471 self.i32.get(setting).copied().unwrap_or_default()
472 }
473
474 pub fn f32(&self, setting: &str) -> f32 {
476 self.f32.get(setting).copied().unwrap_or_default()
477 }
478
479 pub fn string(&self, setting: &str) -> String {
481 self.string.get(setting).map(|x| x.to_owned()).unwrap_or_default()
482 }
483
484 pub fn path_buf(&self, setting: &str) -> PathBuf {
488 self.string.get(setting).map(PathBuf::from).unwrap_or_default()
489 }
490
491 pub fn raw_data(&self, setting: &str) -> Vec<u8> {
493 self.raw_data.get(setting).map(|x| x.to_vec()).unwrap_or_default()
494 }
495
496 pub fn vec_string(&self, setting: &str) -> Vec<String> {
498 self.vec_string.get(setting).map(|x| x.to_vec()).unwrap_or_default()
499 }
500
501 pub fn snapshot(&self) -> SettingsSnapshot {
503 SettingsSnapshot {
504 bool: self.bool.clone(),
505 i32: self.i32.clone(),
506 f32: self.f32.clone(),
507 string: self.string.clone(),
508 raw_data: self.raw_data.clone(),
509 vec_string: self.vec_string.clone(),
510 }
511 }
512
513 pub fn set_bool(&mut self, setting: &str, value: bool) -> Result<()> {
515 self.bool.insert(setting.to_owned(), value);
516 self.write()
517 }
518
519 pub fn set_i32(&mut self, setting: &str, value: i32) -> Result<()> {
521 self.i32.insert(setting.to_owned(), value);
522 self.write()
523 }
524
525 pub fn set_f32(&mut self, setting: &str, value: f32) -> Result<()> {
527 self.f32.insert(setting.to_owned(), value);
528 self.write()
529 }
530
531 pub fn set_string(&mut self, setting: &str, value: &str) -> Result<()> {
533 self.string.insert(setting.to_owned(), value.to_owned());
534 self.write()
535 }
536
537 pub fn set_path_buf(&mut self, setting: &str, value: &Path) -> Result<()> {
540 self.string.insert(setting.to_owned(), value.to_string_lossy().to_string());
541 self.write()
542 }
543
544 pub fn set_raw_data(&mut self, setting: &str, value: &[u8]) -> Result<()> {
546 self.raw_data.insert(setting.to_owned(), value.to_vec());
547 self.write()
548 }
549
550 pub fn set_vec_string(&mut self, setting: &str, value: &[String]) -> Result<()> {
552 self.vec_string.insert(setting.to_owned(), value.to_vec());
553 self.write()
554 }
555
556 pub fn initialize_bool(&mut self, setting: &str, value: bool) {
559 if !self.bool.contains_key(setting) {
560 self.bool.insert(setting.to_owned(), value);
561 }
562 }
563
564 pub fn initialize_i32(&mut self, setting: &str, value: i32) {
566 if !self.i32.contains_key(setting) {
567 self.i32.insert(setting.to_owned(), value);
568 }
569 }
570
571 pub fn initialize_f32(&mut self, setting: &str, value: f32) {
573 if !self.f32.contains_key(setting) {
574 self.f32.insert(setting.to_owned(), value);
575 }
576 }
577
578 pub fn initialize_string(&mut self, setting: &str, value: &str) {
580 if !self.string.contains_key(setting) {
581 self.string.insert(setting.to_owned(), value.to_owned());
582 }
583 }
584
585 pub fn initialize_path_buf(&mut self, setting: &str, value: &Path) {
587 if !self.string.contains_key(setting) {
588 self.string.insert(setting.to_owned(), value.to_string_lossy().to_string());
589 }
590 }
591
592 pub fn initialize_raw_data(&mut self, setting: &str, value: &[u8]) {
594 if !self.raw_data.contains_key(setting) {
595 self.raw_data.insert(setting.to_owned(), value.to_vec());
596 }
597 }
598
599 pub fn initialize_vec_string(&mut self, setting: &str, value: &[String]) {
601 if !self.vec_string.contains_key(setting) {
602 self.vec_string.insert(setting.to_owned(), value.to_vec());
603 }
604 }
605
606 pub fn optimizer_options(&self) -> OptimizerOptions {
610 let mut options = OptimizerOptions::default();
611
612 options.set_pack_remove_itm_files(self.bool(PACK_REMOVE_ITM_FILES));
613 options.set_pack_apply_compression(self.bool(PACK_APPLY_COMPRESSION));
614 options.set_pack_apply_encryption(self.bool(PACK_APPLY_ENCRYPTION));
615 options.set_pack_remove_duplicated_files(self.bool(PACK_REMOVE_DUPLICATED_FILES));
616 options.set_db_import_datacores_into_twad_key_deletes(self.bool(DB_IMPORT_DATACORES_INTO_TWAD_KEY_DELETES));
617 options.set_db_optimize_datacored_tables(self.bool(DB_OPTIMIZE_DATACORED_TABLES));
618 options.set_table_remove_duplicated_entries(self.bool(TABLE_REMOVE_DUPLICATED_ENTRIES));
619 options.set_table_remove_itm_entries(self.bool(TABLE_REMOVE_ITM_ENTRIES));
620 options.set_table_remove_itnr_entries(self.bool(TABLE_REMOVE_ITNR_ENTRIES));
621 options.set_table_remove_empty_file(self.bool(TABLE_REMOVE_EMPTY_FILE));
622 options.set_text_remove_unused_xml_map_folders(self.bool(TEXT_REMOVE_UNUSED_XML_MAP_FOLDERS));
623 options.set_text_remove_unused_xml_prefab_folder(self.bool(TEXT_REMOVE_UNUSED_XML_PREFAB_FOLDER));
624 options.set_text_remove_agf_files(self.bool(TEXT_REMOVE_AGF_FILES));
625 options.set_text_remove_model_statistics_files(self.bool(TEXT_REMOVE_MODEL_STATISTICS_FILES));
626 options.set_pts_remove_unused_art_sets(self.bool(PTS_REMOVE_UNUSED_ART_SETS));
627 options.set_pts_remove_unused_variants(self.bool(PTS_REMOVE_UNUSED_VARIANTS));
628 options.set_pts_remove_empty_masks(self.bool(PTS_REMOVE_EMPTY_MASKS));
629 options.set_pts_remove_empty_file(self.bool(PTS_REMOVE_EMPTY_FILE));
630
631 options
632 }
633
634 pub fn assembly_kit_path(&self, game: &GameInfo) -> Result<PathBuf> {
636 let version = *game.raw_db_version();
637 match version {
638
639 2 | 1 => {
641 let mut base_path = self.path_buf(&format!("{}_assembly_kit", game.key()));
642 base_path.push("raw_data/db");
643 Ok(base_path)
644 }
645
646 0 => {
647 let base_path = old_ak_files_path()?.join(game.key());
648 Ok(base_path)
649 },
650
651 _ => Err(RLibError::AssemblyKitUnsupportedVersion(version).into())
653 }
654 }
655}
656
657pub fn default_config_path() -> Result<PathBuf> {
665
666 if cfg!(debug_assertions) {
668 std::env::current_dir().map_err(From::from)
669 } else {
670 match ProjectDirs::from(ORG_DOMAIN, ORG_NAME, APP_NAME) {
671 Some(proj_dirs) => Ok(proj_dirs.config_dir().to_path_buf()),
672 None => Err(anyhow!("Failed to get the config path."))
673 }
674 }
675}
676
677pub(crate) fn backup_path(path: &Path) -> PathBuf {
679 path.with_extension("json.bak")
680}
681
682pub fn config_path() -> Result<PathBuf> {
686 match custom_config_path()? {
687 Some(path) => Ok(path),
688 None => default_config_path(),
689 }
690}
691
692pub fn custom_config_path() -> Result<Option<PathBuf>> {
696 let redirect_file = default_config_path()?.join(CONFIG_REDIRECT_FILE_NAME);
697 if !redirect_file.is_file() {
698 return Ok(None);
699 }
700
701 let raw = std::fs::read_to_string(&redirect_file)?;
702 let trimmed = raw.trim();
703 if trimmed.is_empty() {
704 Ok(None)
705 } else {
706 Ok(Some(PathBuf::from(trimmed)))
707 }
708}
709
710pub fn set_custom_config_path(path: Option<&Path>) -> Result<()> {
715
716 let default_path = default_config_path()?;
718 DirBuilder::new().recursive(true).create(&default_path)?;
719 let redirect_file = default_path.join(CONFIG_REDIRECT_FILE_NAME);
720
721 match path {
722 Some(path) if !path.as_os_str().is_empty() => {
723 DirBuilder::new().recursive(true).create(path)?;
724 std::fs::write(&redirect_file, path.to_string_lossy().as_bytes())?;
725 }
726 _ => if redirect_file.is_file() {
727 std::fs::remove_file(&redirect_file)?;
728 }
729 }
730
731 init_config_path()
732}
733
734pub fn error_path() -> Result<PathBuf> {
736 Ok(config_path()?.join("error"))
737}
738
739#[must_use = "Many things depend on this folder existing. So better check this worked."]
743pub fn init_config_path() -> Result<()> {
744
745 let config_path = config_path()?;
746 DirBuilder::new().recursive(true).create(&config_path)?;
747 DirBuilder::new().recursive(true).create(backup_autosave_path()?)?;
748 DirBuilder::new().recursive(true).create(error_path()?)?;
749 DirBuilder::new().recursive(true).create(schemas_path()?)?;
750 DirBuilder::new().recursive(true).create(table_patches_path()?)?;
751 DirBuilder::new().recursive(true).create(table_profiles_path()?)?;
752 DirBuilder::new().recursive(true).create(scripts_path()?)?;
753 DirBuilder::new().recursive(true).create(old_ak_files_path()?)?;
754
755 let games = SupportedGames::default();
757 for game in games.games_sorted() {
758 let path = table_patches_path().unwrap().join(game.schema_file_name());
759 if !path.is_file() {
760 let base: HashMap<String, DefinitionPatch> = HashMap::new();
761 let mut file = BufWriter::new(File::create(path)?);
762 let config = PrettyConfig::default();
763 file.write_all(to_string_pretty(&base, config)?.as_bytes())?;
764 }
765 }
766
767 Ok(())
781}
782
783pub fn schemas_path() -> Result<PathBuf> {
785 Ok(config_path()?.join(SCHEMA_FOLDER))
786}
787
788pub fn table_patches_path() -> Result<PathBuf> {
790 Ok(config_path()?.join(TABLE_PATCHES_FOLDER))
791}
792
793pub fn table_profiles_path() -> Result<PathBuf> {
796 Ok(config_path()?.join(TABLE_PROFILES_FOLDER))
797}
798
799pub fn lua_autogen_base_path() -> Result<PathBuf> {
801 Ok(config_path()?.join(LUA_AUTOGEN_FOLDER))
802}
803
804pub fn lua_autogen_game_path(game: &GameInfo) -> Result<PathBuf> {
806 match game.lua_autogen_folder() {
807 Some(folder) => Ok(config_path()?.join(LUA_AUTOGEN_FOLDER).join(folder)),
808 None => Err(anyhow!("Lua Autogen not available for this game."))
809 }
810}
811
812pub fn backup_autosave_path() -> Result<PathBuf> {
814 Ok(config_path()?.join("autosaves"))
815}
816
817pub fn dependencies_cache_path() -> Result<PathBuf> {
819 Ok(config_path()?.join(DEPENDENCIES_FOLDER))
820}
821
822pub fn scripts_path() -> Result<PathBuf> {
825 Ok(config_path()?.join(SCRIPTS_FOLDER))
826}
827
828pub fn old_ak_files_path() -> Result<PathBuf> {
832 Ok(config_path()?.join("old_ak_files"))
833}
834
835pub fn translations_local_path() -> Result<PathBuf> {
838 Ok(config_path()?.join(TRANSLATIONS_LOCAL_FOLDER))
839}
840
841pub fn translations_remote_path() -> Result<PathBuf> {
844 Ok(config_path()?.join(TRANSLATIONS_REMOTE_FOLDER))
845}
846
847pub fn clear_config_path(path: &Path) -> Result<()> {
853 if path.exists() && path.is_dir() && path.starts_with(config_path()?) {
854 std::fs::remove_dir_all(path)?;
855 init_config_path()
856 } else {
857 Err(anyhow!("Path is not a valid directory to clear or does not exist"))
858 }
859}