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",
|
||||
]
|
||||
|
||||
[[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]]
|
||||
name = "keyboard-types"
|
||||
version = "0.7.0"
|
||||
@ -4317,6 +4327,7 @@ dependencies = [
|
||||
"directories",
|
||||
"flate2",
|
||||
"futures",
|
||||
"junction",
|
||||
"log",
|
||||
"regex",
|
||||
"reqwest",
|
||||
|
@ -38,6 +38,7 @@ tauri-plugin-deep-link = "2"
|
||||
async-std = "1.13.0"
|
||||
closure = "0.3.0"
|
||||
derive_more = { version = "2.0.1", features = ["display"] }
|
||||
junction = "1.2.0"
|
||||
|
||||
[target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies]
|
||||
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 {
|
||||
// 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<AppData>>) -> Result<(), ()> {
|
||||
#[tauri::command]
|
||||
pub async fn init_profile(
|
||||
state: State<'_, Mutex<AppData>>,
|
||||
path: PathBuf,
|
||||
exe_path: PathBuf
|
||||
) -> 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 new_profile = Profile::new(path);
|
||||
let new_profile = Profile::new(exe_path);
|
||||
|
||||
new_profile.save().await;
|
||||
appd.profile = Some(new_profile.clone());
|
||||
|
@ -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(())
|
||||
|
@ -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<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<()> {
|
||||
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(())
|
||||
|
@ -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<PkgKey>,
|
||||
pub wine_runtime: Option<PathBuf>,
|
||||
@ -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(),
|
||||
|
||||
|
@ -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<Child> {
|
||||
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))
|
||||
}
|
@ -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<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<()> {
|
||||
std::fs::create_dir_all(&dst).unwrap();
|
||||
for entry in std::fs::read_dir(src)? {
|
||||
|
@ -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": ["**", "**/*", "**/.*/**/*", "**\\*", "**\\.*\\**\\*"]
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -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;
|
||||
};
|
||||
|
@ -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() {
|
||||
|
Reference in New Issue
Block a user