use crate::{ list::{LoadOrder, ModSet}, read_only_guard::ReadOnlyGuard, }; use anyhow::Result; use larian_formats::{bg3::raw::Save, lspk}; use quick_xml::se::Serializer; use serde::Serialize; use std::{ fs::{File, ReadDir}, io::{BufReader, Read, Seek, Write}, path::{Path, PathBuf}, }; pub const DEFAULT_APP_DATA_DIR: &str = "~/.local/share/Steam/steamapps/compatdata/1086940/pfx/drive_c/users/steamuser/AppData"; #[derive(Debug)] pub struct Settings { mods_path: PathBuf, pub(crate) settings_path: PathBuf, } impl Default for Settings { fn default() -> Self { Self::with_app_data_dir(DEFAULT_APP_DATA_DIR) } } impl Settings { pub fn with_app_data_dir(path: &str) -> Self { let app_data_dir = match std::env::var("HOME") { Ok(home_dir) if path.starts_with('~') => path.replacen('~', &home_dir, 1), _ => path.into(), }; let settings_path = [ &app_data_dir, "Local", "Larian Studios", "Baldur's Gate 3", "PlayerProfiles", "Public", "modsettings.lsx", ] .into_iter() .collect(); let mods_path = [ &app_data_dir, "Local", "Larian Studios", "Baldur's Gate 3", "Mods", ] .into_iter() .collect(); Self { mods_path, settings_path, } } pub fn get_load_order(&self) -> Result { self.get_load_order_helper() } pub fn get_installed_mod_info(&self) -> Result { self.get_installed_mod_info_helper() } pub fn set_load_order(&self, order: LoadOrder) -> Result<()> { self.set_load_order_helper(order) } pub fn install_all( &self, verbose: bool, dry_run: bool, refresh: bool, mod_file_paths: Vec>, ) -> Result<()> { self.install_all_inner(verbose, dry_run, refresh, mod_file_paths) } pub fn uninstall_all(&self, dry_run: bool, ordered_indexes: Vec) -> Result<()> { self.uninstall_all_helper(dry_run, ordered_indexes) } pub fn import(&self, source: &Path) -> Result<()> { self.import_helper(source) } pub fn export(&self, destination: &Path) -> Result<()> { self.export_helper(destination) } pub fn pack(mod_files_root: PathBuf, destination: Option) -> Result<()> { Self::pack_helper(mod_files_root, destination) } pub fn unpack(mod_file_path: &Path, destination: Option) -> Result<()> { Self::unpack_helper(mod_file_path, destination) } pub fn dump(path: PathBuf) -> Result<()> { Self::dump_helper(path) } pub fn parse(paths: impl IntoIterator, verbose: bool) -> Result<()> { Self::parse_helper(paths, verbose) } pub fn get_mod_file_path(&self, mod_file_name: impl AsRef) -> PathBuf { self.mods_path.join(mod_file_name) } pub fn open_mod_file(&self, mod_file_name: impl AsRef) -> Result { let file = File::open(self.get_mod_file_path(mod_file_name))?; Ok(file) } pub fn read_mod_file(&self, mod_file_name: impl AsRef) -> Result> { let file = self.open_mod_file(mod_file_name)?; let reader = lspk::Reader::new(file)?; Ok(reader) } pub fn read_mod_files_dir(&self) -> Result { let entries = std::fs::read_dir(&self.mods_path)?; Ok(entries) } pub fn open_mod_settings_file(&self) -> Result { ReadOnlyGuard::new(&self.settings_path, false) } pub fn read_current_installed_mods(&self) -> Result { let settings_file = self.open_mod_settings_file()?; let parsed_settings = quick_xml::de::from_reader(BufReader::new(settings_file))?; Ok(parsed_settings) } pub fn write_settings_file(&self, save: &Save) -> Result<()> { let mut xml = "\n".to_string(); let mut ser = Serializer::new(&mut xml); ser.indent(' ', 4); save.serialize(ser)?; xml.push('\n'); let mut temp_writable = ReadOnlyGuard::new(&self.settings_path, true)?; temp_writable.write_all(xml.replace("/>\n", " />\n").as_ref())?; Ok(()) } }