1use directories::ProjectDirs;
24use ron::ser::{PrettyConfig, to_string_pretty};
25use serde_derive::{Serialize, Deserialize};
26use thiserror::Error;
27
28use std::collections::HashMap;
29use std::io::{BufReader, BufWriter, Read, Write};
30use std::fs::{DirBuilder, File};
31use std::path::{Path, PathBuf};
32
33use rpfm_extensions::optimizer::OptimizerOptions;
34use rpfm_extensions::startpos::layout::STARTPOS_LAYOUT_FOLDER;
35use rpfm_extensions::translator::DEFAULT_SRC_LANG;
36
37use crate::settings_keys::*;
38
39use rpfm_lib::error::RLibError;
40use rpfm_lib::games::{GameInfo, LUA_AUTOGEN_FOLDER, supported_games::*};
41use rpfm_lib::schema::{DefinitionPatch, SCHEMA_FOLDER};
42
43const ORG_DOMAIN: &str = "com";
46
47const ORG_NAME: &str = "FrodoWazEre";
49
50const APP_NAME: &str = "rpfm";
52
53pub const SETTINGS_FILE_NAME: &str = "settings.json";
55
56const CONFIG_REDIRECT_FILE_NAME: &str = "config_folder.txt";
58
59const DEPENDENCIES_FOLDER: &str = "dependencies";
60
61const SCRIPTS_FOLDER: &str = "scripts";
63
64const TABLE_PATCHES_FOLDER: &str = "table_patches";
65const TABLE_PROFILES_FOLDER: &str = "table_profiles";
66const TRANSLATIONS_LOCAL_FOLDER: &str = "translations_local";
67const TRANSLATIONS_REMOTE_FOLDER: &str = "translations_remote";
68
69#[derive(Debug, Error)]
75pub enum SettingsError {
76
77 #[error("Failed to get the config path.")]
79 ConfigPathNotFound,
80
81 #[error("Lua Autogen not available for this game.")]
83 LuaAutogenNotAvailable,
84
85 #[error("Path is not a valid directory to clear or does not exist")]
87 InvalidClearPath,
88
89 #[error(transparent)]
90 Io(#[from] std::io::Error),
91
92 #[error(transparent)]
93 Json(#[from] serde_json::Error),
94
95 #[error(transparent)]
96 Ron(#[from] ron::Error),
97
98 #[error(transparent)]
99 RLib(#[from] RLibError),
100}
101
102pub type Result<T> = std::result::Result<T, SettingsError>;
104
105#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
117#[serde(default)]
118pub struct Settings {
119
120 #[serde(skip_serializing, skip_deserializing)]
123 pub block_write: bool,
124
125 pub bool: HashMap<String, bool>,
127 pub i32: HashMap<String, i32>,
129 pub f32: HashMap<String, f32>,
131 pub string: HashMap<String, String>,
134 pub raw_data: HashMap<String, Vec<u8>>,
136 pub vec_string: HashMap<String, Vec<String>>
138}
139
140impl Settings {
145
146 pub fn init(as_new: bool) -> Self {
153 let mut settings = if !as_new {
154 match config_path() {
155 Ok(config) => Self::load_or_recover(&config.join(SETTINGS_FILE_NAME)),
156 Err(error) => {
157 log::warn!("Failed to find the settings folder, using defaults. Error: {error}");
158 Settings::default()
159 }
160 }
161 } else {
162 Settings::default()
163 };
164
165 settings.set_block_write(true);
166
167 settings.initialize_string(MYMOD_BASE_PATH, "");
168 settings.initialize_string(SECONDARY_PATH, "");
169
170 let supported_games = SupportedGames::default();
171 for game in &supported_games.games() {
172 let game_key = game.key();
173
174 let current_path = settings.string(game_key);
176 if current_path.is_empty() {
177 if current_path.contains("\\") {
178 let _ = settings.set_string(game_key, ¤t_path.replace("\\", "/"));
179 }
180
181 let game_path = if let Ok(Some(game_path)) = game.find_game_install_location() {
182 game_path.to_string_lossy().replace("\\", "/")
183 } else {
184 String::new()
185 };
186
187 if !game_path.is_empty() {
189 let _ = settings.set_string(game_key, &game_path);
190 } else {
191 settings.initialize_string(game_key, &game_path);
192 }
193 }
194
195 if game_key != KEY_EMPIRE &&
196 game_key != KEY_NAPOLEON &&
197 game_key != KEY_ARENA {
198
199 let ak_key = game_key.to_owned() + ASSEMBLY_KIT_SUFFIX;
201 let current_path = settings.string(&ak_key);
202
203 if current_path.is_empty() {
204 let ak_path = if let Ok(Some(ak_path)) = game.find_assembly_kit_install_location() {
205 ak_path.join("assembly_kit").to_string_lossy().replace("\\", "/")
206 } else {
207 String::new()
208 };
209
210 if current_path.contains("\\") {
212 let _ = settings.set_string(&ak_key, ¤t_path.replace("\\", "/"));
213 }
214
215 if !ak_path.is_empty() && game_key != KEY_SHOGUN_2 {
217 let _ = settings.set_string(&ak_key, &ak_path);
218 } else {
219 settings.initialize_string(&ak_key, &ak_path);
220 }
221 }
222 }
223 }
224
225 settings.initialize_bool(IMPORT_FROM_QT, false);
227
228 settings.initialize_string(DEFAULT_GAME, KEY_WARHAMMER_3);
230 settings.initialize_string(LANGUAGE, "English_en");
231 settings.initialize_string(THEME, THEME_OS);
232 settings.initialize_i32(AUTOSAVE_AMOUNT, 10);
234 settings.initialize_i32(AUTOSAVE_INTERVAL, 5);
235
236 settings.initialize_bool(START_MAXIMIZED, false);
247 settings.initialize_bool(ALLOW_EDITING_OF_CA_PACKFILES, false);
248 settings.initialize_bool(CHECK_UPDATES_ON_START, true);
249 settings.initialize_bool(CHECK_SCHEMA_UPDATES_ON_START, true);
250 settings.initialize_bool(CHECK_LUA_AUTOGEN_UPDATES_ON_START, true);
251 settings.initialize_bool(CHECK_OLD_AK_UPDATES_ON_START, true);
252 settings.initialize_bool(USE_LAZY_LOADING, true);
253 settings.initialize_bool(DISABLE_UUID_REGENERATION_ON_DB_TABLES, true);
254 settings.initialize_bool(PACKFILE_TREEVIEW_RESIZE_TO_FIT, false);
255 settings.initialize_bool(EXPAND_TREEVIEW_WHEN_ADDING_ITEMS, true);
256 settings.initialize_bool(USE_RIGHT_SIZE_MARKERS, false);
257 settings.initialize_bool(DISABLE_FILE_PREVIEWS, false);
258 settings.initialize_bool(INCLUDE_BASE_FOLDER_ON_ADD_FROM_FOLDER, true);
259 settings.initialize_bool(DELETE_EMPTY_FOLDERS_ON_DELETE, true);
260 settings.initialize_bool(AUTOSAVE_FOLDER_SIZE_WARNING_TRIGGERED, false);
261 settings.initialize_bool(IGNORE_GAME_FILES_IN_AK, false);
262 settings.initialize_bool(ENABLE_MULTIFOLDER_FILEPICKER, false);
263 settings.initialize_bool(ENABLE_PACK_CONTENTS_DRAG_AND_DROP, true);
264 settings.initialize_bool(CLEAN_UI, false);
265 settings.initialize_bool(SINGLE_PACK_MODE, true);
266 settings.initialize_bool(GLOBAL_SEARCH_COLLAPSE_RESULTS, false);
267 settings.initialize_bool(GLOBAL_SEARCH_AUTO_SELECT_OPEN_PACKS, true);
268
269 settings.initialize_bool(ADJUST_COLUMNS_TO_CONTENT, true);
271 settings.initialize_bool(EXTEND_LAST_COLUMN_ON_TABLES, true);
272 settings.initialize_bool(DISABLE_COMBOS_ON_TABLES, false);
273 settings.initialize_bool(TIGHT_TABLE_MODE, false);
274 settings.initialize_bool(TABLE_RESIZE_ON_EDIT, false);
275 settings.initialize_bool(TABLES_USE_OLD_COLUMN_ORDER, true);
276 settings.initialize_bool(TABLES_USE_OLD_COLUMN_ORDER_FOR_TSV, true);
277 settings.initialize_bool(ENABLE_LOOKUPS, true);
278 settings.initialize_bool(ENABLE_ICONS, true);
279 settings.initialize_bool(ENABLE_DIFF_MARKERS, true);
280 settings.initialize_bool(HIDE_UNUSED_COLUMNS, true);
281 settings.initialize_bool(SHOW_TABLE_TOOLBAR, false);
282 settings.initialize_bool(TABLE_FILTER_NEW_CHIPS_SHARE_GROUP, false);
283
284 settings.initialize_bool(CHECK_FOR_MISSING_TABLE_DEFINITIONS, false);
286 settings.initialize_bool(ENABLE_DEBUG_MENU, false);
287 settings.initialize_bool(ENABLE_UNIT_EDITOR, false);
288 settings.initialize_bool(ENABLE_ESF_EDITOR, false);
289 settings.initialize_bool(USE_DEBUG_VIEW_UNIT_VARIANT, false);
290 settings.initialize_bool(USE_DEBUG_VIEW_GROUP_FORMATIONS, false);
291 settings.initialize_bool(ENABLE_RENDERER, true);
292
293 settings.initialize_bool(DIAGNOSTICS_TRIGGER_ON_OPEN, true);
295 settings.initialize_bool(DIAGNOSTICS_TRIGGER_ON_TABLE_EDIT, true);
296
297 settings.initialize_bool(ENABLE_USAGE_TELEMETRY, true);
299 settings.initialize_bool(ENABLE_CRASH_REPORTS, true);
300
301 if settings.string(ANONYMOUS_TELEMETRY_ID).is_empty() {
303 let _ = settings.set_string(ANONYMOUS_TELEMETRY_ID, &uuid::Uuid::new_v4().to_string());
304 }
305
306 settings.initialize_string(AI_API_URL, "https://api.openai.com/v1/chat/completions");
307 settings.initialize_string(AI_API_KEY, "");
308 settings.initialize_string(AI_MODEL, "gpt-4o-mini");
309 settings.initialize_string(DEEPL_API_KEY, "");
310 settings.initialize_string(TRANSLATOR_SOURCE_LANGUAGE, DEFAULT_SRC_LANG);
311 settings.initialize_bool(TRANSLATOR_USE_DEEPL_GLOSSARY, true);
312
313 settings.initialize_vec_string(RECENT_FILE_LIST, &[]);
314 settings.initialize_vec_string(DIAGNOSTICS_DISABLED, &["label_field_with_path_not_found".to_owned()]);
315
316 let opt = OptimizerOptions::default();
332 settings.initialize_bool(PACK_REMOVE_ITM_FILES, *opt.pack_remove_itm_files());
333 settings.initialize_bool(PACK_APPLY_COMPRESSION, *opt.pack_apply_compression());
334 settings.initialize_bool(PACK_APPLY_ENCRYPTION, *opt.pack_apply_encryption());
335 settings.initialize_bool(PACK_REMOVE_DUPLICATED_FILES, *opt.pack_remove_duplicated_files());
336 settings.initialize_bool(DB_IMPORT_DATACORES_INTO_TWAD_KEY_DELETES, *opt.db_import_datacores_into_twad_key_deletes());
337 settings.initialize_bool(DB_OPTIMIZE_DATACORED_TABLES, *opt.db_optimize_datacored_tables());
338 settings.initialize_bool(TABLE_REMOVE_DUPLICATED_ENTRIES, *opt.table_remove_duplicated_entries());
339 settings.initialize_bool(TABLE_REMOVE_ITM_ENTRIES, *opt.table_remove_itm_entries());
340 settings.initialize_bool(TABLE_REMOVE_ITNR_ENTRIES, *opt.table_remove_itnr_entries());
341 settings.initialize_bool(TABLE_REMOVE_EMPTY_FILE, *opt.table_remove_empty_file());
342 settings.initialize_bool(TEXT_REMOVE_UNUSED_XML_MAP_FOLDERS, *opt.text_remove_unused_xml_map_folders());
343 settings.initialize_bool(TEXT_REMOVE_UNUSED_XML_PREFAB_FOLDER, *opt.text_remove_unused_xml_prefab_folder());
344 settings.initialize_bool(TEXT_REMOVE_AGF_FILES, *opt.text_remove_agf_files());
345 settings.initialize_bool(TEXT_REMOVE_MODEL_STATISTICS_FILES, *opt.text_remove_model_statistics_files());
346 settings.initialize_bool(PTS_REMOVE_UNUSED_ART_SETS, *opt.pts_remove_unused_art_sets());
347 settings.initialize_bool(PTS_REMOVE_UNUSED_VARIANTS, *opt.pts_remove_unused_variants());
348 settings.initialize_bool(PTS_REMOVE_EMPTY_MASKS, *opt.pts_remove_empty_masks());
349 settings.initialize_bool(PTS_REMOVE_EMPTY_FILE, *opt.pts_remove_empty_file());
350
351 settings.set_block_write(false);
352 settings
353 }
354
355 pub fn read() -> Result<Self> {
360 Self::read_from(&config_path()?.join(SETTINGS_FILE_NAME))
361 }
362
363 pub(crate) fn read_from(path: &Path) -> Result<Self> {
365 let mut data = vec![];
366 let mut file = BufReader::new(File::open(path)?);
367 file.read_to_end(&mut data)?;
368
369 serde_json::from_slice(&data).map_err(From::from)
370 }
371
372 pub(crate) fn load_or_recover(path: &Path) -> Self {
386 let backup_path = backup_path(path);
387 match Self::read_from(path) {
388 Ok(settings) => {
389 let _ = std::fs::copy(path, &backup_path);
390 settings
391 },
392 Err(error) => {
393 log::warn!("Failed to read settings file. Error: {error}");
394 match Self::read_from(&backup_path) {
395 Ok(settings) => {
396 log::warn!("Restored the settings from their backup.");
397 settings
398 },
399 Err(_) => {
400 if std::fs::metadata(path).is_ok_and(|metadata| metadata.len() > 0) {
401 let _ = std::fs::copy(path, &backup_path);
402 }
403
404 log::warn!("Failed to read the settings backup, using defaults.");
405 Settings::default()
406 },
407 }
408 },
409 }
410 }
411
412 pub fn write(&self) -> Result<()> {
414 if self.block_write {
415 return Ok(());
416 }
417
418 self.write_to(&config_path()?.join(SETTINGS_FILE_NAME))
419 }
420
421 pub(crate) fn write_to(&self, path: &Path) -> Result<()> {
426 let temp_path = path.with_extension("json.tmp");
427 let mut file = BufWriter::new(File::create(&temp_path)?);
428 file.write_all(serde_json::to_string_pretty(self)?.as_bytes())?;
429
430 let file = file.into_inner().map_err(|error| error.into_error())?;
432 file.sync_all()?;
433 std::fs::rename(&temp_path, path)?;
434 Ok(())
435 }
436
437 pub fn set_block_write(&mut self, status: bool) {
439 self.block_write = status;
440 }
441
442 pub fn bool(&self, setting: &str) -> bool {
444 self.bool.get(setting).copied().unwrap_or_default()
445 }
446
447 pub fn i32(&self, setting: &str) -> i32 {
449 self.i32.get(setting).copied().unwrap_or_default()
450 }
451
452 pub fn f32(&self, setting: &str) -> f32 {
454 self.f32.get(setting).copied().unwrap_or_default()
455 }
456
457 pub fn string(&self, setting: &str) -> String {
459 self.string.get(setting).map(|x| x.to_owned()).unwrap_or_default()
460 }
461
462 pub fn path_buf(&self, setting: &str) -> PathBuf {
466 self.string.get(setting).map(PathBuf::from).unwrap_or_default()
467 }
468
469 pub fn raw_data(&self, setting: &str) -> Vec<u8> {
471 self.raw_data.get(setting).map(|x| x.to_vec()).unwrap_or_default()
472 }
473
474 pub fn vec_string(&self, setting: &str) -> Vec<String> {
476 self.vec_string.get(setting).map(|x| x.to_vec()).unwrap_or_default()
477 }
478
479 pub fn set_bool(&mut self, setting: &str, value: bool) -> Result<()> {
481 self.bool.insert(setting.to_owned(), value);
482 self.write()
483 }
484
485 pub fn set_i32(&mut self, setting: &str, value: i32) -> Result<()> {
487 self.i32.insert(setting.to_owned(), value);
488 self.write()
489 }
490
491 pub fn set_f32(&mut self, setting: &str, value: f32) -> Result<()> {
493 self.f32.insert(setting.to_owned(), value);
494 self.write()
495 }
496
497 pub fn set_string(&mut self, setting: &str, value: &str) -> Result<()> {
499 self.string.insert(setting.to_owned(), value.to_owned());
500 self.write()
501 }
502
503 pub fn set_path_buf(&mut self, setting: &str, value: &Path) -> Result<()> {
506 self.string.insert(setting.to_owned(), value.to_string_lossy().to_string());
507 self.write()
508 }
509
510 pub fn set_raw_data(&mut self, setting: &str, value: &[u8]) -> Result<()> {
512 self.raw_data.insert(setting.to_owned(), value.to_vec());
513 self.write()
514 }
515
516 pub fn set_vec_string(&mut self, setting: &str, value: &[String]) -> Result<()> {
518 self.vec_string.insert(setting.to_owned(), value.to_vec());
519 self.write()
520 }
521
522 pub fn initialize_bool(&mut self, setting: &str, value: bool) {
525 if !self.bool.contains_key(setting) {
526 self.bool.insert(setting.to_owned(), value);
527 }
528 }
529
530 pub fn initialize_i32(&mut self, setting: &str, value: i32) {
532 if !self.i32.contains_key(setting) {
533 self.i32.insert(setting.to_owned(), value);
534 }
535 }
536
537 pub fn initialize_f32(&mut self, setting: &str, value: f32) {
539 if !self.f32.contains_key(setting) {
540 self.f32.insert(setting.to_owned(), value);
541 }
542 }
543
544 pub fn initialize_string(&mut self, setting: &str, value: &str) {
546 if !self.string.contains_key(setting) {
547 self.string.insert(setting.to_owned(), value.to_owned());
548 }
549 }
550
551 pub fn initialize_path_buf(&mut self, setting: &str, value: &Path) {
553 if !self.string.contains_key(setting) {
554 self.string.insert(setting.to_owned(), value.to_string_lossy().to_string());
555 }
556 }
557
558 pub fn initialize_raw_data(&mut self, setting: &str, value: &[u8]) {
560 if !self.raw_data.contains_key(setting) {
561 self.raw_data.insert(setting.to_owned(), value.to_vec());
562 }
563 }
564
565 pub fn initialize_vec_string(&mut self, setting: &str, value: &[String]) {
567 if !self.vec_string.contains_key(setting) {
568 self.vec_string.insert(setting.to_owned(), value.to_vec());
569 }
570 }
571
572 pub fn optimizer_options(&self) -> OptimizerOptions {
576 let mut options = OptimizerOptions::default();
577
578 options.set_pack_remove_itm_files(self.bool(PACK_REMOVE_ITM_FILES));
579 options.set_pack_apply_compression(self.bool(PACK_APPLY_COMPRESSION));
580 options.set_pack_apply_encryption(self.bool(PACK_APPLY_ENCRYPTION));
581 options.set_pack_remove_duplicated_files(self.bool(PACK_REMOVE_DUPLICATED_FILES));
582 options.set_db_import_datacores_into_twad_key_deletes(self.bool(DB_IMPORT_DATACORES_INTO_TWAD_KEY_DELETES));
583 options.set_db_optimize_datacored_tables(self.bool(DB_OPTIMIZE_DATACORED_TABLES));
584 options.set_table_remove_duplicated_entries(self.bool(TABLE_REMOVE_DUPLICATED_ENTRIES));
585 options.set_table_remove_itm_entries(self.bool(TABLE_REMOVE_ITM_ENTRIES));
586 options.set_table_remove_itnr_entries(self.bool(TABLE_REMOVE_ITNR_ENTRIES));
587 options.set_table_remove_empty_file(self.bool(TABLE_REMOVE_EMPTY_FILE));
588 options.set_text_remove_unused_xml_map_folders(self.bool(TEXT_REMOVE_UNUSED_XML_MAP_FOLDERS));
589 options.set_text_remove_unused_xml_prefab_folder(self.bool(TEXT_REMOVE_UNUSED_XML_PREFAB_FOLDER));
590 options.set_text_remove_agf_files(self.bool(TEXT_REMOVE_AGF_FILES));
591 options.set_text_remove_model_statistics_files(self.bool(TEXT_REMOVE_MODEL_STATISTICS_FILES));
592 options.set_pts_remove_unused_art_sets(self.bool(PTS_REMOVE_UNUSED_ART_SETS));
593 options.set_pts_remove_unused_variants(self.bool(PTS_REMOVE_UNUSED_VARIANTS));
594 options.set_pts_remove_empty_masks(self.bool(PTS_REMOVE_EMPTY_MASKS));
595 options.set_pts_remove_empty_file(self.bool(PTS_REMOVE_EMPTY_FILE));
596
597 options
598 }
599
600 pub fn assembly_kit_path(&self, game: &GameInfo) -> Result<PathBuf> {
602 let version = *game.raw_db_version();
603 match version {
604
605 2 | 1 => {
607 let mut base_path = self.path_buf(&format!("{}_assembly_kit", game.key()));
608 base_path.push("raw_data/db");
609 Ok(base_path)
610 }
611
612 0 => {
613 let base_path = old_ak_files_path()?.join(game.key());
614 Ok(base_path)
615 },
616
617 _ => Err(RLibError::AssemblyKitUnsupportedVersion(version).into())
619 }
620 }
621}
622
623pub fn default_config_path() -> Result<PathBuf> {
631
632 if cfg!(debug_assertions) {
634 std::env::current_dir().map_err(From::from)
635 } else {
636 match ProjectDirs::from(ORG_DOMAIN, ORG_NAME, APP_NAME) {
637 Some(proj_dirs) => Ok(proj_dirs.config_dir().to_path_buf()),
638 None => Err(SettingsError::ConfigPathNotFound)
639 }
640 }
641}
642
643pub(crate) fn backup_path(path: &Path) -> PathBuf {
645 path.with_extension("json.bak")
646}
647
648pub fn config_path() -> Result<PathBuf> {
652 match custom_config_path()? {
653 Some(path) => Ok(path),
654 None => default_config_path(),
655 }
656}
657
658pub fn custom_config_path() -> Result<Option<PathBuf>> {
662 let redirect_file = default_config_path()?.join(CONFIG_REDIRECT_FILE_NAME);
663 if !redirect_file.is_file() {
664 return Ok(None);
665 }
666
667 let raw = std::fs::read_to_string(&redirect_file)?;
668 let trimmed = raw.trim();
669 if trimmed.is_empty() {
670 Ok(None)
671 } else {
672 Ok(Some(PathBuf::from(trimmed)))
673 }
674}
675
676pub fn set_custom_config_path(path: Option<&Path>) -> Result<()> {
681
682 let default_path = default_config_path()?;
684 DirBuilder::new().recursive(true).create(&default_path)?;
685 let redirect_file = default_path.join(CONFIG_REDIRECT_FILE_NAME);
686
687 match path {
688 Some(path) if !path.as_os_str().is_empty() => {
689 DirBuilder::new().recursive(true).create(path)?;
690 std::fs::write(&redirect_file, path.to_string_lossy().as_bytes())?;
691 }
692 _ => if redirect_file.is_file() {
693 std::fs::remove_file(&redirect_file)?;
694 }
695 }
696
697 init_config_path()
698}
699
700pub fn error_path() -> Result<PathBuf> {
702 Ok(config_path()?.join("error"))
703}
704
705#[must_use = "Many things depend on this folder existing. So better check this worked."]
709pub fn init_config_path() -> Result<()> {
710
711 let config_path = config_path()?;
712 DirBuilder::new().recursive(true).create(&config_path)?;
713 DirBuilder::new().recursive(true).create(backup_autosave_path()?)?;
714 DirBuilder::new().recursive(true).create(error_path()?)?;
715 DirBuilder::new().recursive(true).create(schemas_path()?)?;
716 DirBuilder::new().recursive(true).create(startpos_layouts_path()?)?;
717 DirBuilder::new().recursive(true).create(table_patches_path()?)?;
718 DirBuilder::new().recursive(true).create(table_profiles_path()?)?;
719 DirBuilder::new().recursive(true).create(scripts_path()?)?;
720 DirBuilder::new().recursive(true).create(old_ak_files_path()?)?;
721
722 let games = SupportedGames::default();
724 for game in games.games_sorted() {
725 let path = table_patches_path().unwrap().join(game.schema_file_name());
726 if !path.is_file() {
727 let base: HashMap<String, DefinitionPatch> = HashMap::new();
728 let mut file = BufWriter::new(File::create(path)?);
729 let config = PrettyConfig::default();
730 file.write_all(to_string_pretty(&base, config)?.as_bytes())?;
731 }
732 }
733
734 Ok(())
748}
749
750pub fn schemas_path() -> Result<PathBuf> {
752 Ok(config_path()?.join(SCHEMA_FOLDER))
753}
754
755pub fn startpos_layouts_path() -> Result<PathBuf> {
757 Ok(config_path()?.join(STARTPOS_LAYOUT_FOLDER))
758}
759
760pub fn table_patches_path() -> Result<PathBuf> {
762 Ok(config_path()?.join(TABLE_PATCHES_FOLDER))
763}
764
765pub fn table_profiles_path() -> Result<PathBuf> {
768 Ok(config_path()?.join(TABLE_PROFILES_FOLDER))
769}
770
771pub fn lua_autogen_base_path() -> Result<PathBuf> {
773 Ok(config_path()?.join(LUA_AUTOGEN_FOLDER))
774}
775
776pub fn lua_autogen_game_path(game: &GameInfo) -> Result<PathBuf> {
778 match game.lua_autogen_folder() {
779 Some(folder) => Ok(config_path()?.join(LUA_AUTOGEN_FOLDER).join(folder)),
780 None => Err(SettingsError::LuaAutogenNotAvailable)
781 }
782}
783
784pub fn backup_autosave_path() -> Result<PathBuf> {
786 Ok(config_path()?.join("autosaves"))
787}
788
789pub fn dependencies_cache_path() -> Result<PathBuf> {
791 Ok(config_path()?.join(DEPENDENCIES_FOLDER))
792}
793
794pub fn scripts_path() -> Result<PathBuf> {
797 Ok(config_path()?.join(SCRIPTS_FOLDER))
798}
799
800pub fn old_ak_files_path() -> Result<PathBuf> {
804 Ok(config_path()?.join("old_ak_files"))
805}
806
807pub fn translations_local_path() -> Result<PathBuf> {
810 Ok(config_path()?.join(TRANSLATIONS_LOCAL_FOLDER))
811}
812
813pub fn translations_remote_path() -> Result<PathBuf> {
816 Ok(config_path()?.join(TRANSLATIONS_REMOTE_FOLDER))
817}
818
819pub fn clear_config_path(path: &Path) -> Result<()> {
825 if path.exists() && path.is_dir() && path.starts_with(config_path()?) {
826 std::fs::remove_dir_all(path)?;
827 init_config_path()
828 } else {
829 Err(SettingsError::InvalidClearPath)
830 }
831}
832
833#[cfg(test)]
834#[path = "settings_test.rs"]
835mod settings_test;