229 lines
7.3 KiB
Rust
229 lines
7.3 KiB
Rust
use serde::{Deserialize, Serialize};
|
|
use tauri::AppHandle;
|
|
use tauri::Emitter;
|
|
use std::{collections::BTreeSet, path::PathBuf, process::Stdio};
|
|
use crate::model::config::BepInEx;
|
|
use crate::profiles::fixed_name;
|
|
use crate::{model::{config::{Display, DisplayMode, Network, Segatools}, misc::Game, segatools_base::segatools_base}, pkg::PkgKey, util};
|
|
use super::{Profile, ProfileMeta, ProfilePaths};
|
|
use anyhow::{anyhow, Result};
|
|
use std::fs::File;
|
|
use tokio::process::Command;
|
|
use tokio::task::JoinSet;
|
|
|
|
#[derive(Deserialize, Serialize, Clone)]
|
|
pub struct OngekiProfile {
|
|
// this is an Option only to deceive serde
|
|
// file serialization doesn't need the name, but IPC does
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub name: Option<String>,
|
|
|
|
pub mods: BTreeSet<PkgKey>,
|
|
pub sgt: Segatools,
|
|
pub display: Display,
|
|
pub network: Network,
|
|
pub bepinex: BepInEx,
|
|
|
|
#[cfg(not(target_os = "windows"))]
|
|
pub wine: crate::model::config::Wine,
|
|
}
|
|
|
|
impl Profile for OngekiProfile {
|
|
fn new(name: String) -> Result<Self> {
|
|
let name = fixed_name(&ProfileMeta { name, game: Game::Ongeki }, true);
|
|
|
|
let p = OngekiProfile {
|
|
name: Some(name.clone()),
|
|
mods: BTreeSet::new(),
|
|
sgt: Segatools::default(),
|
|
display: Display::default(),
|
|
network: Network::default(),
|
|
bepinex: BepInEx::default(),
|
|
#[cfg(not(target_os = "windows"))]
|
|
wine: crate::model::config::Wine::default(),
|
|
};
|
|
p.save()?;
|
|
std::fs::write(p.config_dir().join("segatools-base.ini"), segatools_base())?;
|
|
log::debug!("created profile-ongeki-{}", &name);
|
|
|
|
Ok(p)
|
|
}
|
|
|
|
fn load(name: String) -> Result<Self> {
|
|
let path = util::profile_config_dir(&Game::Ongeki, &name).join("profile.json");
|
|
if let Ok(s) = std::fs::read_to_string(&path) {
|
|
let mut data = serde_json::from_str::<OngekiProfile>(&s)
|
|
.map_err(|e| anyhow!("Unable to parse {:?}: {:?}", path, e))?;
|
|
data.name = Some(name);
|
|
Ok(data)
|
|
} else {
|
|
Err(anyhow!("Unable to open {:?}", path))
|
|
}
|
|
}
|
|
|
|
fn save(&self) -> Result<()> {
|
|
let path = self.config_dir().join("profile.json");
|
|
|
|
let mut cpy = self.clone();
|
|
cpy.name = None;
|
|
let s = serde_json::to_string_pretty(&cpy)?;
|
|
if !self.config_dir().exists() {
|
|
std::fs::create_dir(self.config_dir())
|
|
.map_err(|e| anyhow!("error when creating profile directory: {}", e))?;
|
|
}
|
|
std::fs::write(&path, s)
|
|
.map_err(|e| anyhow!("error when writing to {:?}: {}", path, e))?;
|
|
log::info!("Written to {:?}", path);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
async fn start(&self, app: AppHandle) -> Result<()> {
|
|
let ini_path = self.data_dir().join("segatools.ini");
|
|
|
|
log::debug!("With path {:?}", ini_path);
|
|
|
|
let mut game_builder;
|
|
let mut amd_builder;
|
|
|
|
let target_path = PathBuf::from(&self.sgt.target);
|
|
let exe_dir = target_path.parent().ok_or_else(|| anyhow!("Invalid target path"))?;
|
|
let sgt_dir = self.sgt.hook_dir()?;
|
|
|
|
#[cfg(target_os = "windows")]
|
|
{
|
|
game_builder = Command::new(sgt_dir.join("inject.exe"));
|
|
amd_builder = Command::new("cmd.exe");
|
|
}
|
|
#[cfg(target_os = "linux")]
|
|
{
|
|
game_builder = Command::new(&self.wine.runtime);
|
|
amd_builder = Command::new(&self.wine.runtime);
|
|
|
|
game_builder.arg(sgt_dir.join("inject.exe"));
|
|
amd_builder.arg("cmd.exe");
|
|
}
|
|
|
|
amd_builder.env(
|
|
"SEGATOOLS_CONFIG_PATH",
|
|
&ini_path,
|
|
)
|
|
.current_dir(&exe_dir)
|
|
.arg("/C")
|
|
.arg(&sgt_dir.join("inject.exe"))
|
|
.args(["-d", "-k"])
|
|
.arg(sgt_dir.join("mu3hook.dll"))
|
|
.args(["amdaemon.exe", "-f", "-c", "config_common.json", "config_server.json", "config_client.json"
|
|
]);
|
|
game_builder
|
|
.env(
|
|
"SEGATOOLS_CONFIG_PATH",
|
|
ini_path,
|
|
)
|
|
.env(
|
|
"INOHARA_CONFIG_PATH",
|
|
self.config_dir().join("inohara.cfg"),
|
|
)
|
|
.current_dir(&exe_dir)
|
|
.args(["-d", "-k"])
|
|
.arg(sgt_dir.join("mu3hook.dll"))
|
|
.args([
|
|
"mu3.exe", "-monitor 1",
|
|
"-screen-width", &self.display.rez.0.to_string(),
|
|
"-screen-height", &self.display.rez.1.to_string(),
|
|
"-screen-fullscreen", if self.display.mode == DisplayMode::Fullscreen { "1" } else { "0" }
|
|
]);
|
|
|
|
if self.display.mode == DisplayMode::Borderless {
|
|
game_builder.arg("-popupwindow");
|
|
}
|
|
|
|
#[cfg(target_os = "linux")]
|
|
{
|
|
amd_builder.env("WINEPREFIX", &self.wine.prefix);
|
|
game_builder.env("WINEPREFIX", &self.wine.prefix);
|
|
}
|
|
|
|
let amd_log = File::create(self.data_dir().join("amdaemon.log"))?;
|
|
let game_log = File::create(self.data_dir().join("mu3.log"))?;
|
|
|
|
amd_builder
|
|
.stdout(Stdio::from(amd_log));
|
|
// do they use stderr?
|
|
|
|
game_builder
|
|
.stdout(Stdio::from(game_log));
|
|
|
|
#[cfg(target_os = "windows")]
|
|
{
|
|
amd_builder.creation_flags(util::CREATE_NO_WINDOW);
|
|
game_builder.creation_flags(util::CREATE_NO_WINDOW);
|
|
}
|
|
|
|
if self.sgt.intel == true {
|
|
amd_builder.env("OPENSSL_ia32cap", ":~0x20000000");
|
|
}
|
|
|
|
util::pkill("amdaemon.exe").await;
|
|
|
|
log::info!("Launching amdaemon: {:?}", amd_builder);
|
|
log::info!("Launching mu3: {:?}", game_builder);
|
|
|
|
let mut amd = amd_builder.spawn()?;
|
|
let mut game = game_builder.spawn()?;
|
|
|
|
let mut set = JoinSet::new();
|
|
|
|
set.spawn(async move {
|
|
(amd.wait().await.expect("amdaemon failed to run"), "amdaemon.exe")
|
|
});
|
|
|
|
set.spawn(async move {
|
|
(game.wait().await.expect("mu3 failed to run"), "mu3.exe")
|
|
});
|
|
|
|
if let Err(e) = app.emit("launch-start", "") {
|
|
log::warn!("Unable to emit launch-start: {}", e);
|
|
}
|
|
|
|
let (rc, process_name) = set.join_next().await.expect("No spawn").expect("No result");
|
|
|
|
log::info!("{} died with return code {}", process_name, rc);
|
|
|
|
if process_name == "amdaemon.exe" {
|
|
util::pkill("mu3.exe").await;
|
|
} else {
|
|
util::pkill("amdaemon.exe").await;
|
|
}
|
|
|
|
set.join_next().await.expect("No spawn").expect("No result");
|
|
|
|
log::debug!("Fin");
|
|
|
|
if let Err(e) = app.emit("launch-end", "") {
|
|
log::warn!("Unable to emit launch-end: {}", e);
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
impl ProfilePaths for OngekiProfile {
|
|
fn config_dir(&self) -> PathBuf {
|
|
util::profile_config_dir(&Game::Ongeki, &self.name.as_ref().unwrap())
|
|
}
|
|
|
|
fn data_dir(&self) -> PathBuf {
|
|
util::data_dir().join(format!("profile-{}-{}", &Game::Ongeki, self.name.as_ref().unwrap()))
|
|
}
|
|
}
|
|
|
|
impl ProfilePaths for ProfileMeta {
|
|
fn config_dir(&self) -> PathBuf {
|
|
util::profile_config_dir(&self.game, &self.name)
|
|
}
|
|
|
|
fn data_dir(&self) -> PathBuf {
|
|
util::data_dir().join(format!("profile-{}-{}", &self.game, &self.name))
|
|
}
|
|
} |