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<LoadOrder> {
self.get_load_order_helper()
}
pub fn get_installed_mod_info(&self) -> Result<ModSet> {
self.get_installed_mod_info_helper()
}
pub fn install_all(
&self,
verbose: bool,
dry_run: bool,
refresh: bool,
mod_file_paths: Vec<impl AsRef<Path>>,
) -> Result<()> {
self.install_all_inner(verbose, dry_run, refresh, mod_file_paths)
}
pub fn uninstall_all(&self, dry_run: bool, ordered_indexes: Vec<usize>) -> 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 dump(path: PathBuf) -> Result<()> {
Self::dump_helper(path)
}
pub fn parse(paths: impl IntoIterator<Item = PathBuf>, verbose: bool) -> Result<()> {
Self::parse_helper(paths, verbose)
}
pub fn get_mod_file_path(&self, mod_file_name: impl AsRef<Path>) -> PathBuf {
self.mods_path.join(mod_file_name)
}
pub fn open_mod_file(&self, mod_file_name: impl AsRef<Path>) -> Result<File> {
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<Path>) -> Result<lspk::Reader<File>> {
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<ReadDir> {
let entries = std::fs::read_dir(&self.mods_path)?;
Ok(entries)
}
pub fn open_mod_settings_file(&self) -> Result<impl Read + Write + Seek> {
ReadOnlyGuard::new(&self.settings_path, false)
}
pub fn read_current_installed_mods(&self) -> Result<Save> {
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 = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\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(())
}
}