From caead1e70f322e8c0f81d39f7ce104dff1077bb1 Mon Sep 17 00:00:00 2001 From: akanyan Date: Sun, 23 Feb 2025 18:12:20 +0000 Subject: [PATCH] feat: port to Microsoft Windows --- bun.lockb | Bin 146775 -> 146807 bytes rust/Cargo.lock | 11 ++++++ rust/Cargo.toml | 1 + rust/src/cmd.rs | 8 ++--- rust/src/download_handler.rs | 2 ++ rust/src/liner.rs | 53 +++++++++++++++++++++++++---- rust/src/profile.rs | 8 ++--- rust/src/start.rs | 64 +++++++++++++++++++++++++++++++++-- rust/src/util.rs | 6 ++++ rust/tauri.conf.json | 4 +-- src/components/App.vue | 10 +++--- src/stores.ts | 4 +-- 12 files changed, 145 insertions(+), 26 deletions(-) diff --git a/bun.lockb b/bun.lockb index 38a8f277a2e95e79e36bd7e2afdd313983d8de03..5341a6b3ef60d52205981bd47767efd7cc18fca0 100755 GIT binary patch delta 1515 zcmXZcTTE0(9LMqV2UI|j%8FHpB}!nynAI&2a0Rqg3$;l}+VIea;&laDqyoF_qG?x9 z(l#d6cpLj-G+wGw+fr&*)Ll{Wh6>hRXd4w#(W(#=HPQHB`<*hAeDeRD|IAs=?ChcE zMf9^5(Hq~Ddkw{hi{KKg*=uv5UG2|w*ZiVT^T9(WD+%&69b0iDDG(G*tBDfxf z^`@scqhp+M)`%@Inz+eK>HX>D@z_Jw&+Mh@=~pmC2S%OgjUm^Q^}c+jmq5-l>-?r? zqnB@bdcBTg(J$5%i%nRoOf!*ujjZ>u+VqOdx1Ot{R+`^%@qoF=$1LPwCJHbIvyp>r ztm7lr;xp*;S%U^OSS!<1u17M`7ID28ORyBnP>O|cL!V`tn&6R?%tE3KiHO4(jKwHy zVgpvH>mEs-_yO@f*pY#BWFiess=_O?;`*OUd9`3Koqf2EhZw*h9$`8=cfg5suIVs#LX;5)<;3U3@1$Y~UmJ0hMDY1+1 z?=mjpG&<0(`g~$@9UvaUel+7Cgrn1M=S%Xxu#;1eMSdM||f|-V-$slYHVJudqthQ#xdXTao1EWu6*B9Q&Cl>3PNI6w)jm;0*R z)H*71-#1|lxd~&&F`35oe(dwiyyp4*zR&a9eSX(dS07kiAGl_OTw0J;nkIK=hGpf7 zMYo@M-o9b7J#T_d#pOz@O3#(y+5QarVTO0yGKF48UC)MYQ%$V_xwDy4^mYLc# zQwyZF!qleI`*IwlM&?PVyCHF<8PZGAu@Tmqnw{Q<8o>2zt4*Of7_* ztOsc>Q)@!*ZEE?Z)|6Vbk7bme_y0%qcr&~q=xdm5CN?Kmll3*sF||I)q@po`(F~zzftFaya@JuzHmExe32pO{_z9m;i&a>SHAhJ-z_=r#Vf~C+e%yO})RHt-R zi=EOmYzvjG*oN)cf!(5Nof7U&BI{?YpJ`w8gMLl3k%gfch6r>-B=TAPAcT|KqKxdp zah$+OoI*J&P>ItxgDRZGIh^NFSuPMR;u0?73a;WBuHy!7A_W7n13R$`yRip*QG{ZY zU?2A501o014&w-p;uuO{5#{5Ou(8jHHF$xSc!k$^gSU8x_xOM(c#2!NjXSuDd$^AW zc!+8|!eixhNyMO3;t&i)ECzsY#?l_$xrcEGBnRUc`8Vnz9P5wv5BaaCVwZ&5u90ux zCQhOhWr)RI6rmU;*e@z7UlLS$zO;5v;J4zC$Ur=vqXy4#3s-OoIASn~u@m%ZXo8JU*o4i{n+=;wh{eUkE*$8Mp;}3Sw2z!e9EcP|aG{6ktDyVA zdPkxSSN#hl!jn}XenCNn3%$i~k8`11^a;>6h~^ovM1CfA%+l+kSu4H0cTdiqZg*tl h>) -> Result<(), String> { if let Some(p) = &appd.profile { // TODO if p.needsUpdate liner::line_up(p).await.expect("Line-up failed"); - start::start(p).map_err(|e| e.to_string()).map(|_| ()) + start::start(p).map_err(|e| { log::error!("Error launching: {}", e.to_string()); e.to_string() }).map(|_| ()) //Ok(()) } else { Err("No profile".to_owned()) @@ -126,12 +126,12 @@ pub async fn save_profile(state: State<'_, Mutex>) -> Result<(), ()> { #[tauri::command] pub async fn init_profile( state: State<'_, Mutex>, - path: PathBuf, + exe_path: PathBuf ) -> Result { - log::debug!("invoke: init_profile"); + log::debug!("invoke: init_profile({})", exe_path.to_string_lossy()); let mut appd = state.lock().await; - let new_profile = Profile::new(path); + let new_profile = Profile::new(exe_path); new_profile.save().await; appd.profile = Some(new_profile.clone()); diff --git a/rust/src/download_handler.rs b/rust/src/download_handler.rs index b3e186d..a730e75 100644 --- a/rust/src/download_handler.rs +++ b/rust/src/download_handler.rs @@ -50,6 +50,8 @@ impl DownloadHandler { cache_file_w.sync_all().await?; tokio::fs::rename(&zip_path_part, &zip_path).await?; + log::debug!("Downloaded to {}", zip_path.to_string_lossy()); + app.emit("download-end", pkg_key)?; Ok(()) diff --git a/rust/src/liner.rs b/rust/src/liner.rs index c36cf55..c3d4757 100644 --- a/rust/src/liner.rs +++ b/rust/src/liner.rs @@ -1,9 +1,22 @@ use tokio::task::JoinSet; -use anyhow::Result; +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) + 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()); @@ -19,9 +32,8 @@ pub async fn line_up(p: &Profile) -> Result<()> { fs::create_dir_all(dir_out.join("option")).await?; - log::debug!("--"); for m in &p.mods { - log::debug!("{}", m.0); + 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..]) @@ -35,15 +47,42 @@ pub async fn line_up(p: &Profile) -> Result<()> { if opt.exists() { let x = opt.read_dir().unwrap().next().unwrap()?; if x.metadata()?.is_dir() { - fs::symlink(&x.path(), &dir_out.join("option").join(x.file_name())).await?; + symlink(&x.path(), &dir_out.join("option").join(x.file_name())).await?; } } } - log::debug!("--"); - for opt in p.path.join("option").read_dir()? { + // 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"))? + ); + 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?; - fs::symlink(&opt.path(), &dir_out.join("option").join(opt.file_name())).await?; + symlink(&opt.path(), opt_dir_out.join(opt.file_name())).await?; } Ok(()) diff --git a/rust/src/profile.rs b/rust/src/profile.rs index d35b4ca..359afa2 100644 --- a/rust/src/profile.rs +++ b/rust/src/profile.rs @@ -1,4 +1,4 @@ -use std::{collections::HashSet, path::{Path, PathBuf}}; +use std::{collections::HashSet, path::PathBuf}; use crate::{model::misc, pkg::PkgKey, util}; use serde::{Deserialize, Serialize}; @@ -10,7 +10,7 @@ use tokio::fs; #[allow(dead_code)] pub struct Profile { pub game: misc::Game, - pub path: PathBuf, + pub exe_dir: PathBuf, pub name: String, pub mods: HashSet, pub wine_runtime: Option, @@ -18,10 +18,10 @@ pub struct Profile { } impl Profile { - pub fn new(path: PathBuf) -> Profile { + pub fn new(exe_path: PathBuf) -> Profile { Profile { game: misc::Game::Ongeki, - path: path.parent().unwrap().to_owned(), + exe_dir: exe_path.parent().unwrap().to_owned(), name: "ongeki-default".to_owned(), mods: HashSet::new(), diff --git a/rust/src/start.rs b/rust/src/start.rs index eac41db..b7626a4 100644 --- a/rust/src/start.rs +++ b/rust/src/start.rs @@ -1,5 +1,7 @@ use anyhow::Result; -use tokio::process::{Child, Command}; +use std::process::Stdio; +use tokio::task::JoinSet; +use tokio::process::Command; use crate::profile::Profile; use crate::util; @@ -11,6 +13,64 @@ pub fn start(p: &Profile) -> Result { util::profile_dir(&p).join("segatools.ini"), ) .env("WINEPREFIX", p.wine_prefix.as_ref().unwrap()) - .arg(p.path.join("start.bat")) + .arg(p.exe_dir.join("start.bat")) .spawn()?) +} + +#[cfg(target_os = "windows")] +pub fn start(p: &Profile) -> Result<()> { + let ini_path = util::profile_dir(&p).join("segatools.ini"); + + log::debug!("With path {}", ini_path.to_string_lossy()); + log::info!("Launching amdaemon"); + let mut amd = Command::new("cmd.exe") + .env( + "SEGATOOLS_CONFIG_PATH", + &ini_path, + ) + .env("OPENSSL_ia32cap", ":~0x20000000") + .current_dir(&p.exe_dir) + .args(["/C", &util::path_to_str(p.exe_dir.join( "inject.exe"))?, "-d", "-k", "mu3hook.dll", "amdaemon.exe", "-f", "-c", "config_common.json", "config_server.json", "config_client.json"]) + // Obviously this is a meme + // Output will be handled properly at a later time + .stdout(Stdio::null()) + .stderr(Stdio::null()) + .spawn()?; + + log::info!("Launching mu3"); + let mut game = Command::new(p.exe_dir.join( "inject.exe")) + .env( + "SEGATOOLS_CONFIG_PATH", + ini_path, + ) + .current_dir(&p.exe_dir) + .args(["-d", "-k", "mu3hook.dll", "mu3.exe", "-monitor 1", "-screen-fullscreen", "0", "-popupwindow", "-screen-width", "1080", "-screen-height", "1920"]) + .stdout(Stdio::null()) + .stderr(Stdio::null()) + .spawn()?; + + tauri::async_runtime::spawn(async move { + let mut set = JoinSet::new(); + set.spawn(async move { + amd.wait().await.expect("amdaemon failed to run") + }); + + set.spawn(async move { + game.wait().await.expect("mu3 failed to run") + }); + + let res = set.join_next().await.expect("No spawn").expect("No result"); + + log::info!("One of the processes died with return code {}", res); + + _ = Command::new("taskkill.exe").arg("/f").arg("/im").arg("amdaemon.exe").output().await; + _ = Command::new("taskkill.exe").arg("/f").arg("/im").arg("mu3.exe").output().await; + + set.join_next().await.expect("No spawn").expect("No result"); + + log::debug!("Fin"); + }); + + Ok(()) + //Ok((amd, game)) } \ No newline at end of file diff --git a/rust/src/util.rs b/rust/src/util.rs index 4e75b65..9a5b341 100644 --- a/rust/src/util.rs +++ b/rust/src/util.rs @@ -1,3 +1,4 @@ +use anyhow::{anyhow, Result}; use directories::ProjectDirs; use std::path::{Path, PathBuf}; @@ -31,6 +32,11 @@ pub fn cache_dir() -> PathBuf { get_dirs().cache_dir().to_owned() } +pub fn path_to_str(p: impl AsRef) -> Result { + Ok(p.as_ref().to_str() + .ok_or_else(|| anyhow!("Invalid path: {}", p.as_ref().to_string_lossy()))?.to_owned()) +} + pub fn copy_recursive(src: &Path, dst: &Path) -> std::io::Result<()> { std::fs::create_dir_all(&dst).unwrap(); for entry in std::fs::read_dir(src)? { diff --git a/rust/tauri.conf.json b/rust/tauri.conf.json index 86fdd9c..37db1df 100644 --- a/rust/tauri.conf.json +++ b/rust/tauri.conf.json @@ -31,11 +31,11 @@ ], "security": { "csp": { - "img-src": "'self' asset: https: blob: data:" + "img-src": "'self' asset: https: http://asset.localhost blob: data:" }, "assetProtocol": { "enable": true, - "scope": ["**/*", "**/.*/**/*"] + "scope": ["**", "**/*", "**/.*/**/*", "**\\*", "**\\.*\\**\\*"] } } }, diff --git a/src/components/App.vue b/src/components/App.vue index 6ec25f3..11eb019 100644 --- a/src/components/App.vue +++ b/src/components/App.vue @@ -24,7 +24,7 @@ const loadProfile = async () => { await store.reloadProfile(); if (store.profile === null) { - const file = await open({ + const exePath = await open({ multiple: false, directory: false, filters: [ @@ -34,8 +34,8 @@ const loadProfile = async () => { }, ], }); - if (file !== null) { - await store.initProfile(file); + if (exePath !== null) { + await store.initProfile(exePath); } } if (store.profile !== null) { @@ -48,8 +48,8 @@ const loadProfile = async () => { const isProfileDisabled = computed(() => store.profile === null); -const startline = () => { - invoke('startline'); +const startline = async () => { + await invoke('startline'); //startDisabled.value = true; }; diff --git a/src/stores.ts b/src/stores.ts index 34dd1e6..18983e0 100644 --- a/src/stores.ts +++ b/src/stores.ts @@ -77,8 +77,8 @@ export const usePkgStore = defineStore('pkg', { Object.assign(this.pkg[key], rv); }, - async initProfile(path: string) { - this.prf = await invoke('init_profile', { path }); + async initProfile(exePath: string) { + this.prf = await invoke('init_profile', { exePath }); }, async saveProfile() {