use anyhow::{Result, anyhow}; use tokio::fs; use std::path::{Path, PathBuf}; use ini::Ini; use crate::util; use crate::profile::Profile; #[cfg(target_os = "linux")] async fn symlink(src: impl AsRef, dst: impl AsRef) -> std::io::Result<()> { fs::symlink(src, dst).await } #[cfg(target_os = "windows")] async fn symlink(src: impl AsRef, dst: impl AsRef) -> std::io::Result<()> { //std::os::windows::fs::junction_point(src, dst) // is unstable junction::create(src, dst) } pub async fn line_up(p: &Profile, pkg_hash: String) -> Result<()> { let dir_out = p.dir(); if dir_out.join("option").exists() { fs::remove_dir_all(dir_out.join("option")).await?; } fs::create_dir_all(dir_out.join("option")).await?; let hash_path = p.dir().join(".sl-state"); let prev_hash = fs::read_to_string(&hash_path).await.unwrap_or_default(); if prev_hash != pkg_hash { log::debug!("state {} -> {}", prev_hash, pkg_hash); fs::write(hash_path, pkg_hash).await .map_err(|e| anyhow!("Unable to write the state file: {}", e))?; prepare_packages(p).await?; } prepare_config(p).await?; Ok(()) } async fn prepare_packages(p: &Profile) -> Result<()> { let dir_out = p.dir(); if dir_out.join("BepInEx").exists() { fs::remove_dir_all(dir_out.join("BepInEx")).await?; } for m in &p.mods { log::debug!("Preparing {}", m); let (namespace, name) = m.0.split_at(m.0.find("-").expect("Invalid mod definition")); let bpx_dir = util::pkg_dir_of(namespace, &name[1..]) // cut the hyphen .join("app") .join("BepInEx"); if bpx_dir.exists() { util::copy_recursive(&bpx_dir, &dir_out.join("BepInEx"))?; } let opt_dir = util::pkg_dir_of(namespace, &name[1..]).join("option"); if opt_dir.exists() { let x = opt_dir.read_dir().unwrap().next().unwrap()?; if x.metadata()?.is_dir() { symlink(&x.path(), &dir_out.join("option").join(x.file_name())).await?; } } } Ok(()) } pub async fn prepare_config(p: &Profile) -> Result<()> { let dir_out = p.dir(); let ini_in_raw = fs::read_to_string(p.exe_dir.join("segatools.ini")).await?; let ini_in = Ini::load_from_str(&ini_in_raw)?; let mut opt_dir_in = PathBuf::from( ini_in.section(Some("vfs")) .ok_or_else(|| anyhow!("No VFS section in segatools.ini"))? .get("option") .ok_or_else(|| anyhow!("No option specified in segatools.ini"))? ); if opt_dir_in.is_relative() { opt_dir_in = p.exe_dir.join(opt_dir_in); } let opt_dir_out = &dir_out.join("option"); let mut ini_out = ini_in.clone(); ini_out.with_section(Some("vfs")).set( "option", util::path_to_str(opt_dir_out)? ); ini_out.with_section(Some("unity")) .set("enable", "1") .set( "targetAssembly", util::path_to_str(dir_out.join("BepInEx").join("core").join("BepInEx.Preloader.dll"))? ); if p.get_bool("aime", false) { ini_out.with_section(Some("aime")) .set("enable", "1") .set("aimePath", util::path_to_str(dir_out.join("aime.txt"))?); } ini_out.write_to_file(dir_out.join("segatools.ini"))?; log::debug!("Option dir: {} -> {}", opt_dir_in.to_string_lossy(), opt_dir_out.to_string_lossy()); for opt in opt_dir_in.read_dir()? { let opt = opt?; symlink(&opt.path(), opt_dir_out.join(opt.file_name())).await?; } Ok(()) }