use tokio::task::JoinSet; 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) -> Result<()> { let dir_out = util::profile_dir(&p); log::info!("Preparing {}", dir_out.to_string_lossy()); let mut futures = JoinSet::new(); if dir_out.join("BepInEx").exists() { futures.spawn(fs::remove_dir_all(dir_out.join("BepInEx"))); } if dir_out.join("option").exists() { futures.spawn(fs::remove_dir_all(dir_out.join("option"))); } while let Some(_) = futures.join_next().await {} fs::create_dir_all(dir_out.join("option")).await?; for m in &p.mods { log::debug!("Preparing {}", m); let dir_out = util::profile_dir(&p); let (namespace, name) = m.0.split_at(m.0.find("-").expect("Invalid mod definition")); let bpx = util::pkg_dir_of(namespace, &name[1..]) .join("app") .join("BepInEx"); if bpx.exists() { util::copy_recursive(&bpx, &dir_out.join("BepInEx"))?; } let opt = util::pkg_dir_of(namespace, &name[1..]).join("option"); if opt.exists() { let x = opt.read_dir().unwrap().next().unwrap()?; if x.metadata()?.is_dir() { symlink(&x.path(), &dir_out.join("option").join(x.file_name())).await?; } } } // Todo temporary 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 prepare_aime(p).await.unwrap_or(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(()) } // Todo multiple codes async fn prepare_aime(p: &Profile) -> Result { if p.get_bool("aime", true) { if let Some(code) = p.cfg.get("aime-code") { let code = code.as_str().expect("Invalid config"); fs::write(util::profile_dir(&p).join("aime.txt"), code).await?; return Ok(true); } } Ok(false) }