forked from akanyan/STARTLINER
feat: port to Microsoft Windows
This commit is contained in:
11
rust/Cargo.lock
generated
11
rust/Cargo.lock
generated
@ -2359,6 +2359,16 @@ dependencies = [
|
|||||||
"serde_json",
|
"serde_json",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "junction"
|
||||||
|
version = "1.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "72bbdfd737a243da3dfc1f99ee8d6e166480f17ab4ac84d7c34aacd73fc7bd16"
|
||||||
|
dependencies = [
|
||||||
|
"scopeguard",
|
||||||
|
"windows-sys 0.52.0",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "keyboard-types"
|
name = "keyboard-types"
|
||||||
version = "0.7.0"
|
version = "0.7.0"
|
||||||
@ -4317,6 +4327,7 @@ dependencies = [
|
|||||||
"directories",
|
"directories",
|
||||||
"flate2",
|
"flate2",
|
||||||
"futures",
|
"futures",
|
||||||
|
"junction",
|
||||||
"log",
|
"log",
|
||||||
"regex",
|
"regex",
|
||||||
"reqwest",
|
"reqwest",
|
||||||
|
@ -38,6 +38,7 @@ tauri-plugin-deep-link = "2"
|
|||||||
async-std = "1.13.0"
|
async-std = "1.13.0"
|
||||||
closure = "0.3.0"
|
closure = "0.3.0"
|
||||||
derive_more = { version = "2.0.1", features = ["display"] }
|
derive_more = { version = "2.0.1", features = ["display"] }
|
||||||
|
junction = "1.2.0"
|
||||||
|
|
||||||
[target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies]
|
[target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies]
|
||||||
tauri-plugin-single-instance = { version = "2", features = ["deep-link"] }
|
tauri-plugin-single-instance = { version = "2", features = ["deep-link"] }
|
||||||
|
@ -20,7 +20,7 @@ pub async fn startline(state: State<'_, Mutex<AppData>>) -> Result<(), String> {
|
|||||||
if let Some(p) = &appd.profile {
|
if let Some(p) = &appd.profile {
|
||||||
// TODO if p.needsUpdate
|
// TODO if p.needsUpdate
|
||||||
liner::line_up(p).await.expect("Line-up failed");
|
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(())
|
//Ok(())
|
||||||
} else {
|
} else {
|
||||||
Err("No profile".to_owned())
|
Err("No profile".to_owned())
|
||||||
@ -126,12 +126,12 @@ pub async fn save_profile(state: State<'_, Mutex<AppData>>) -> Result<(), ()> {
|
|||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub async fn init_profile(
|
pub async fn init_profile(
|
||||||
state: State<'_, Mutex<AppData>>,
|
state: State<'_, Mutex<AppData>>,
|
||||||
path: PathBuf,
|
exe_path: PathBuf
|
||||||
) -> Result<Profile, String> {
|
) -> Result<Profile, String> {
|
||||||
log::debug!("invoke: init_profile");
|
log::debug!("invoke: init_profile({})", exe_path.to_string_lossy());
|
||||||
|
|
||||||
let mut appd = state.lock().await;
|
let mut appd = state.lock().await;
|
||||||
let new_profile = Profile::new(path);
|
let new_profile = Profile::new(exe_path);
|
||||||
|
|
||||||
new_profile.save().await;
|
new_profile.save().await;
|
||||||
appd.profile = Some(new_profile.clone());
|
appd.profile = Some(new_profile.clone());
|
||||||
|
@ -50,6 +50,8 @@ impl DownloadHandler {
|
|||||||
cache_file_w.sync_all().await?;
|
cache_file_w.sync_all().await?;
|
||||||
tokio::fs::rename(&zip_path_part, &zip_path).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)?;
|
app.emit("download-end", pkg_key)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -1,9 +1,22 @@
|
|||||||
use tokio::task::JoinSet;
|
use tokio::task::JoinSet;
|
||||||
use anyhow::Result;
|
use anyhow::{Result, anyhow};
|
||||||
use tokio::fs;
|
use tokio::fs;
|
||||||
|
use std::path::{Path, PathBuf};
|
||||||
|
use ini::Ini;
|
||||||
use crate::util;
|
use crate::util;
|
||||||
use crate::profile::Profile;
|
use crate::profile::Profile;
|
||||||
|
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
async fn symlink(src: impl AsRef<Path>, dst: impl AsRef<Path>) -> std::io::Result<()> {
|
||||||
|
fs::symlink(src, dst).await
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(target_os = "windows")]
|
||||||
|
async fn symlink(src: impl AsRef<Path>, dst: impl AsRef<Path>) -> std::io::Result<()> {
|
||||||
|
//std::os::windows::fs::junction_point(src, dst)
|
||||||
|
junction::create(src, dst)
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn line_up(p: &Profile) -> Result<()> {
|
pub async fn line_up(p: &Profile) -> Result<()> {
|
||||||
let dir_out = util::profile_dir(&p);
|
let dir_out = util::profile_dir(&p);
|
||||||
log::info!("Preparing {}", dir_out.to_string_lossy());
|
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?;
|
fs::create_dir_all(dir_out.join("option")).await?;
|
||||||
|
|
||||||
log::debug!("--");
|
|
||||||
for m in &p.mods {
|
for m in &p.mods {
|
||||||
log::debug!("{}", m.0);
|
log::debug!("Preparing {}", m);
|
||||||
let dir_out = util::profile_dir(&p);
|
let dir_out = util::profile_dir(&p);
|
||||||
let (namespace, name) = m.0.split_at(m.0.find("-").expect("Invalid mod definition"));
|
let (namespace, name) = m.0.split_at(m.0.find("-").expect("Invalid mod definition"));
|
||||||
let bpx = util::pkg_dir_of(namespace, &name[1..])
|
let bpx = util::pkg_dir_of(namespace, &name[1..])
|
||||||
@ -35,15 +47,42 @@ pub async fn line_up(p: &Profile) -> Result<()> {
|
|||||||
if opt.exists() {
|
if opt.exists() {
|
||||||
let x = opt.read_dir().unwrap().next().unwrap()?;
|
let x = opt.read_dir().unwrap().next().unwrap()?;
|
||||||
if x.metadata()?.is_dir() {
|
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?;
|
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(())
|
Ok(())
|
||||||
|
@ -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 crate::{model::misc, pkg::PkgKey, util};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
@ -10,7 +10,7 @@ use tokio::fs;
|
|||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub struct Profile {
|
pub struct Profile {
|
||||||
pub game: misc::Game,
|
pub game: misc::Game,
|
||||||
pub path: PathBuf,
|
pub exe_dir: PathBuf,
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub mods: HashSet<PkgKey>,
|
pub mods: HashSet<PkgKey>,
|
||||||
pub wine_runtime: Option<PathBuf>,
|
pub wine_runtime: Option<PathBuf>,
|
||||||
@ -18,10 +18,10 @@ pub struct Profile {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Profile {
|
impl Profile {
|
||||||
pub fn new(path: PathBuf) -> Profile {
|
pub fn new(exe_path: PathBuf) -> Profile {
|
||||||
Profile {
|
Profile {
|
||||||
game: misc::Game::Ongeki,
|
game: misc::Game::Ongeki,
|
||||||
path: path.parent().unwrap().to_owned(),
|
exe_dir: exe_path.parent().unwrap().to_owned(),
|
||||||
name: "ongeki-default".to_owned(),
|
name: "ongeki-default".to_owned(),
|
||||||
mods: HashSet::new(),
|
mods: HashSet::new(),
|
||||||
|
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
use anyhow::Result;
|
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::profile::Profile;
|
||||||
use crate::util;
|
use crate::util;
|
||||||
|
|
||||||
@ -11,6 +13,64 @@ pub fn start(p: &Profile) -> Result<Child> {
|
|||||||
util::profile_dir(&p).join("segatools.ini"),
|
util::profile_dir(&p).join("segatools.ini"),
|
||||||
)
|
)
|
||||||
.env("WINEPREFIX", p.wine_prefix.as_ref().unwrap())
|
.env("WINEPREFIX", p.wine_prefix.as_ref().unwrap())
|
||||||
.arg(p.path.join("start.bat"))
|
.arg(p.exe_dir.join("start.bat"))
|
||||||
.spawn()?)
|
.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))
|
||||||
}
|
}
|
@ -1,3 +1,4 @@
|
|||||||
|
use anyhow::{anyhow, Result};
|
||||||
use directories::ProjectDirs;
|
use directories::ProjectDirs;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
@ -31,6 +32,11 @@ pub fn cache_dir() -> PathBuf {
|
|||||||
get_dirs().cache_dir().to_owned()
|
get_dirs().cache_dir().to_owned()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn path_to_str(p: impl AsRef<Path>) -> Result<String> {
|
||||||
|
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<()> {
|
pub fn copy_recursive(src: &Path, dst: &Path) -> std::io::Result<()> {
|
||||||
std::fs::create_dir_all(&dst).unwrap();
|
std::fs::create_dir_all(&dst).unwrap();
|
||||||
for entry in std::fs::read_dir(src)? {
|
for entry in std::fs::read_dir(src)? {
|
||||||
|
@ -31,11 +31,11 @@
|
|||||||
],
|
],
|
||||||
"security": {
|
"security": {
|
||||||
"csp": {
|
"csp": {
|
||||||
"img-src": "'self' asset: https: blob: data:"
|
"img-src": "'self' asset: https: http://asset.localhost blob: data:"
|
||||||
},
|
},
|
||||||
"assetProtocol": {
|
"assetProtocol": {
|
||||||
"enable": true,
|
"enable": true,
|
||||||
"scope": ["**/*", "**/.*/**/*"]
|
"scope": ["**", "**/*", "**/.*/**/*", "**\\*", "**\\.*\\**\\*"]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -24,7 +24,7 @@ const loadProfile = async () => {
|
|||||||
await store.reloadProfile();
|
await store.reloadProfile();
|
||||||
|
|
||||||
if (store.profile === null) {
|
if (store.profile === null) {
|
||||||
const file = await open({
|
const exePath = await open({
|
||||||
multiple: false,
|
multiple: false,
|
||||||
directory: false,
|
directory: false,
|
||||||
filters: [
|
filters: [
|
||||||
@ -34,8 +34,8 @@ const loadProfile = async () => {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
if (file !== null) {
|
if (exePath !== null) {
|
||||||
await store.initProfile(file);
|
await store.initProfile(exePath);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (store.profile !== null) {
|
if (store.profile !== null) {
|
||||||
@ -48,8 +48,8 @@ const loadProfile = async () => {
|
|||||||
|
|
||||||
const isProfileDisabled = computed(() => store.profile === null);
|
const isProfileDisabled = computed(() => store.profile === null);
|
||||||
|
|
||||||
const startline = () => {
|
const startline = async () => {
|
||||||
invoke('startline');
|
await invoke('startline');
|
||||||
|
|
||||||
//startDisabled.value = true;
|
//startDisabled.value = true;
|
||||||
};
|
};
|
||||||
|
@ -77,8 +77,8 @@ export const usePkgStore = defineStore('pkg', {
|
|||||||
Object.assign(this.pkg[key], rv);
|
Object.assign(this.pkg[key], rv);
|
||||||
},
|
},
|
||||||
|
|
||||||
async initProfile(path: string) {
|
async initProfile(exePath: string) {
|
||||||
this.prf = await invoke('init_profile', { path });
|
this.prf = await invoke('init_profile', { exePath });
|
||||||
},
|
},
|
||||||
|
|
||||||
async saveProfile() {
|
async saveProfile() {
|
||||||
|
Reference in New Issue
Block a user