use std::path::{Path, PathBuf};
use anyhow::{Context, Result};
use modde_core::resolver::GameId;
use tracing::{debug, info};
const SKYRIM_INIS: &[&str] = &["Skyrim.ini", "SkyrimPrefs.ini", "SkyrimCustom.ini"];
const FALLOUT4_INIS: &[&str] = &["Fallout4.ini", "Fallout4Prefs.ini", "Fallout4Custom.ini"];
const FALLOUT76_INIS: &[&str] = &["Fallout76.ini", "Fallout76Prefs.ini", "Fallout76Custom.ini"];
#[must_use]
pub fn tracked_inis(game_id: &str) -> &'static [&'static str] {
match game_id {
"skyrim-se" | "skyrim-ae" => SKYRIM_INIS,
"fallout4" => FALLOUT4_INIS,
"fallout76" => FALLOUT76_INIS,
_ => &[],
}
}
#[must_use]
pub fn profile_ini_dir(profile_name: &str) -> PathBuf {
modde_core::paths::profiles_dir()
.join(profile_name)
.join("ini")
}
#[must_use]
pub fn game_ini_dir(steam_app_id: &str, my_games_dir: &str) -> Option<PathBuf> {
let home = std::env::var("HOME").ok()?;
let path = PathBuf::from(home)
.join(".local/share/Steam/steamapps/compatdata")
.join(steam_app_id)
.join("pfx/drive_c/Users/steamuser/Documents/My Games")
.join(my_games_dir);
if path.exists() { Some(path) } else { None }
}
pub fn capture_inis(game_id: &GameId, profile_name: &str, game_ini_path: &Path) -> Result<usize> {
let inis = tracked_inis(game_id.as_str());
if inis.is_empty() {
return Ok(0);
}
let dest_dir = profile_ini_dir(profile_name);
std::fs::create_dir_all(&dest_dir)
.with_context(|| format!("failed to create profile INI dir: {}", dest_dir.display()))?;
let mut count = 0;
for ini_name in inis {
let src = game_ini_path.join(ini_name);
if src.exists() {
let dst = dest_dir.join(ini_name);
std::fs::copy(&src, &dst).with_context(|| {
format!(
"failed to capture INI: {} -> {}",
src.display(),
dst.display()
)
})?;
debug!(ini = *ini_name, "captured INI to profile");
count += 1;
}
}
if count > 0 {
info!(
profile = profile_name,
ini_count = count,
"captured game INIs to profile"
);
}
Ok(count)
}
pub fn restore_inis(game_id: &GameId, profile_name: &str, game_ini_path: &Path) -> Result<usize> {
let inis = tracked_inis(game_id.as_str());
if inis.is_empty() {
return Ok(0);
}
let src_dir = profile_ini_dir(profile_name);
if !src_dir.exists() {
debug!(
profile = profile_name,
"no stored INIs for profile, skipping restore"
);
return Ok(0);
}
let mut count = 0;
for ini_name in inis {
let src = src_dir.join(ini_name);
if src.exists() {
let dst = game_ini_path.join(ini_name);
std::fs::copy(&src, &dst).with_context(|| {
format!(
"failed to restore INI: {} -> {}",
src.display(),
dst.display()
)
})?;
debug!(ini = *ini_name, "restored INI from profile");
count += 1;
}
}
if count > 0 {
info!(
profile = profile_name,
ini_count = count,
"restored profile INIs to game"
);
}
Ok(count)
}
pub fn swap_inis(
game_id: &GameId,
outgoing_profile: Option<&str>,
incoming_profile: &str,
game_ini_path: &Path,
) -> Result<()> {
if let Some(outgoing) = outgoing_profile {
capture_inis(game_id, outgoing, game_ini_path)?;
}
restore_inis(game_id, incoming_profile, game_ini_path)?;
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use tempfile::TempDir;
#[test]
fn test_tracked_inis() {
assert_eq!(tracked_inis("skyrim-se").len(), 3);
assert_eq!(tracked_inis("skyrim-ae").len(), 3);
assert_eq!(tracked_inis("fallout4").len(), 3);
assert!(tracked_inis("cyberpunk2077").is_empty());
}
#[test]
fn test_capture_and_restore_inis() {
let game_dir = TempDir::new().unwrap();
let profiles_dir = TempDir::new().unwrap();
std::fs::write(game_dir.path().join("Skyrim.ini"), "[General]\nbOK=1\n").unwrap();
std::fs::write(
game_dir.path().join("SkyrimPrefs.ini"),
"[Display]\niRes=1920\n",
)
.unwrap();
let profile_ini = profiles_dir.path().join("test_profile").join("ini");
std::fs::create_dir_all(&profile_ini).unwrap();
for ini in &["Skyrim.ini", "SkyrimPrefs.ini"] {
let src = game_dir.path().join(ini);
let dst = profile_ini.join(ini);
std::fs::copy(&src, &dst).unwrap();
}
std::fs::write(game_dir.path().join("Skyrim.ini"), "[General]\nbOK=0\n").unwrap();
for ini in &["Skyrim.ini", "SkyrimPrefs.ini"] {
let src = profile_ini.join(ini);
let dst = game_dir.path().join(ini);
std::fs::copy(&src, &dst).unwrap();
}
let content = std::fs::read_to_string(game_dir.path().join("Skyrim.ini")).unwrap();
assert!(content.contains("bOK=1"));
}
#[test]
fn test_swap_with_no_outgoing() {
let game_dir = TempDir::new().unwrap();
std::fs::write(game_dir.path().join("Skyrim.ini"), "[General]\n").unwrap();
let result = swap_inis(
&GameId::from("skyrim-se"),
None,
"new_profile",
game_dir.path(),
);
assert!(result.is_ok());
}
}