feat: chusanApp.exe patching

This commit is contained in:
2025-04-13 18:15:41 +00:00
parent 6270fce05f
commit 4247e19996
18 changed files with 406 additions and 187 deletions

View File

@ -2,7 +2,7 @@ use std::hash::{DefaultHasher, Hash, Hasher};
use crate::model::config::GlobalConfig;
use crate::model::patch::PatchFileVec;
use crate::pkg::{Feature, Status};
use crate::profiles::Profile;
use crate::profiles::types::Profile;
use crate::{model::misc::Game, pkg::PkgKey};
use crate::pkg_store::PackageStore;
use crate::util;
@ -18,7 +18,7 @@ pub struct AppData {
pub pkgs: PackageStore,
pub cfg: GlobalConfig,
pub state: GlobalState,
pub patch_set: PatchFileVec,
pub patch_vec: PatchFileVec,
}
#[derive(PartialEq, Debug, Copy, Clone)]
@ -39,7 +39,7 @@ impl AppData {
None => None
};
let patch_set = PatchFileVec::new(util::config_dir())
let patch_vec = PatchFileVec::new(util::config_dir())
.map_err(|e| log::error!("unable to load patch set: {e}"))
.unwrap_or_default();
@ -50,7 +50,7 @@ impl AppData {
pkgs: PackageStore::new(apph.clone()),
cfg,
state: GlobalState { remain_open: true },
patch_set
patch_vec
}
}
@ -85,8 +85,7 @@ impl AppData {
.clone()
.ok_or_else(|| anyhow!("Attempted to enable a non-existent package"))?;
if let Status::OK(feature_set) = loc.status {
log::debug!("{:?}", feature_set);
if let Status::OK(feature_set, _) = loc.status {
if feature_set.contains(Feature::Mod) {
profile.mod_pkgs_mut().insert(key);
}

View File

@ -8,9 +8,10 @@ use tauri::{AppHandle, Manager, State};
use crate::model::config::GlobalConfigField;
use crate::model::misc::Game;
use crate::model::patch::Patch;
use crate::modules::package::prepare_dlls;
use crate::pkg::{Package, PkgKey};
use crate::pkg_store::{InstallResult, PackageStore};
use crate::profiles::{self, Profile, ProfileData, ProfileMeta, ProfilePaths};
use crate::profiles::{self, Profile, ProfileData, ProfileMeta, ProfilePaths, StartPayload};
use crate::appdata::{AppData, ToggleAction};
use crate::model::misc::StartCheckError;
use crate::util;
@ -59,18 +60,40 @@ pub async fn startline(app: AppHandle, refresh: bool) -> Result<(), String> {
let state = app.state::<Mutex<AppData>>();
let mut hash = "".to_owned();
let mut appd = state.lock().await;
let appd = state.lock().await;
let mut game_dlls = Vec::new();
let mut amd_dlls = Vec::new();
if let Some(p) = &appd.profile {
hash = appd.sum_packages(p);
(game_dlls, amd_dlls) = prepare_dlls(p.mod_pkgs(), &appd.pkgs).map_err(|e| e.to_string())?
}
if let Some(p) = &mut appd.profile {
if let Some(p) = &appd.profile {
log::debug!("{}", hash);
p.line_up(hash, refresh, app.clone()).await
let info = p.prepare_display()
.map_err(|e| e.to_string())?;
let lineup_res = p.line_up(hash, refresh, &appd.patch_vec).await
.map_err(|e| e.to_string());
#[cfg(target_os = "windows")]
if let Some(info) = info {
use crate::model::profile::Display;
if lineup_res.is_ok() {
Display::wait_for_exit(app.clone(), info);
} else {
Display::clean_up(&info).map_err(|e| e.to_string())?;
}
}
lineup_res?;
let app_clone = app.clone();
let p_clone = p.clone();
tauri::async_runtime::spawn(async move {
if let Err(e) = p_clone.start(app_clone).await {
if let Err(e) = p_clone.start(StartPayload {
app: app_clone,
game_dlls,
amd_dlls
}).await {
log::error!("Startup failed:\n{}", e);
}
});
@ -152,6 +175,10 @@ pub async fn get_all_packages(state: State<'_, Mutex<AppData>>) -> Result<HashMa
let appd = state.lock().await;
let pkgs_all = appd.pkgs.get_all();
log::debug!("pkgs_all: {:?}", pkgs_all);
Ok(appd.pkgs.get_all())
}
@ -466,7 +493,7 @@ pub async fn list_patches(state: State<'_, Mutex<AppData>>, target: String) -> R
let mut appd = state.lock().await;
appd.fix();
let list = appd.patch_set.find_patches(target).map_err(|e| e.to_string())?;
let list = appd.patch_vec.find_patches(target).map_err(|e| e.to_string())?;
Ok(list)
}

View File

@ -8,6 +8,7 @@ use anyhow::Result;
pub struct PatchSelection(pub BTreeMap<String, PatchSelectionData>);
#[derive(Deserialize, Serialize, Clone, Debug)]
#[serde(rename_all = "snake_case")]
pub enum PatchSelectionData {
Enabled,
Number(i8),
@ -108,10 +109,10 @@ impl<'de> serde::Deserialize<'de> for Patch {
.and_then(Value::as_i64)
.ok_or_else(|| de::Error::missing_field("min"))?
).map_err(|_| de::Error::missing_field("min"))?,
max: i32::try_from(value.get("min")
max: i32::try_from(value.get("max")
.and_then(Value::as_i64)
.ok_or_else(|| de::Error::missing_field("min"))?
).map_err(|_| de::Error::missing_field("min"))?
.ok_or_else(|| de::Error::missing_field("max"))?
).map_err(|_| de::Error::missing_field("max"))?
}),
None => {
let mut patches = vec![];

View File

@ -164,8 +164,11 @@ pub struct Mu3Ini {
pub blacklist: Option<(i32, i32)>,
}
fn default_true() -> bool { true }
#[derive(Deserialize, Serialize, Clone, Debug)]
pub struct OngekiKeyboard {
#[serde(default = "default_true")] pub enabled: bool,
pub use_mouse: bool,
pub coin: i32,
pub svc: i32,
@ -185,6 +188,7 @@ pub struct OngekiKeyboard {
impl Default for OngekiKeyboard {
fn default() -> Self {
Self {
enabled: true,
use_mouse: true,
test: 0x70,
svc: 0x71,
@ -205,6 +209,7 @@ impl Default for OngekiKeyboard {
#[derive(Deserialize, Serialize, Clone, Debug)]
pub struct ChunithmKeyboard {
#[serde(default = "default_true")] pub enabled: bool,
pub coin: i32,
pub svc: i32,
pub test: i32,
@ -215,6 +220,7 @@ pub struct ChunithmKeyboard {
impl Default for ChunithmKeyboard {
fn default() -> Self {
Self {
enabled: true,
test: 0x70,
svc: 0x71,
coin: 0x72,

View File

@ -77,34 +77,46 @@ impl Keyboard {
pub fn line_up(&self, ini: &mut Ini) -> Result<()> {
match self {
Keyboard::Ongeki(kb) => {
ini.with_section(Some("io4"))
.set("test", kb.test.to_string())
.set("service", kb.svc.to_string())
.set("coin", kb.coin.to_string())
.set("left1", kb.l1.to_string())
.set("left2", kb.l2.to_string())
.set("left3", kb.l3.to_string())
.set("right1", kb.r1.to_string())
.set("right2", kb.r2.to_string())
.set("right3", kb.r3.to_string())
.set("leftSide", kb.lwad.to_string())
.set("rightSide", kb.rwad.to_string())
.set("leftMenu", kb.lmenu.to_string())
.set("rightMenu", kb.rmenu.to_string())
.set("mouse", if kb.use_mouse { "1" } else { "0" });
if kb.enabled {
ini.with_section(Some("io4"))
.set("test", kb.test.to_string())
.set("service", kb.svc.to_string())
.set("coin", kb.coin.to_string())
.set("left1", kb.l1.to_string())
.set("left2", kb.l2.to_string())
.set("left3", kb.l3.to_string())
.set("right1", kb.r1.to_string())
.set("right2", kb.r2.to_string())
.set("right3", kb.r3.to_string())
.set("leftSide", kb.lwad.to_string())
.set("rightSide", kb.rwad.to_string())
.set("leftMenu", kb.lmenu.to_string())
.set("rightMenu", kb.rmenu.to_string())
.set("mouse", if kb.use_mouse { "1" } else { "0" });
} else {
ini.with_section(Some("io4"))
.set("enable", "0");
}
}
Keyboard::Chunithm(kb) => {
for (i, cell) in kb.cell.iter().enumerate() {
ini.with_section(Some("slider")).set(format!("cell{}", i + 1), cell.to_string());
if kb.enabled {
for (i, cell) in kb.cell.iter().enumerate() {
ini.with_section(Some("slider")).set(format!("cell{}", i + 1), cell.to_string());
}
for (i, ir) in kb.ir.iter().enumerate() {
ini.with_section(Some("ir")).set(format!("ir{}", i + 1), ir.to_string());
}
ini.with_section(Some("io3"))
.set("test", kb.test.to_string())
.set("service", kb.svc.to_string())
.set("coin", kb.coin.to_string())
.set("ir", "0");
} else {
ini.with_section(Some("io4"))
.set("enable", "0");
ini.with_section(Some("slider"))
.set("enable", "0");
}
for (i, ir) in kb.ir.iter().enumerate() {
ini.with_section(Some("ir")).set(format!("ir{}", i + 1), ir.to_string());
}
ini.with_section(Some("io3"))
.set("test", kb.test.to_string())
.set("service", kb.svc.to_string())
.set("coin", kb.coin.to_string())
.set("ir", "0");
}
}

View File

@ -0,0 +1,59 @@
use std::path::Path;
use anyhow::Result;
use crate::model::patch::{Patch, PatchData, PatchFileVec, PatchSelection, PatchSelectionData};
impl PatchSelection {
pub async fn render_to_file(
&self,
filename: &str,
patches: &PatchFileVec,
path: impl AsRef<Path>
) -> Result<()> {
let mut res = "".to_owned();
for file in &patches.0 {
for list in &file.0 {
if list.filename != filename {
continue;
}
for patch in &list.patches {
if let Some(selection) = self.0.get(&patch.id) {
res += &Self::render(filename, patch, selection);
}
}
}
}
tokio::fs::write(path, res).await?;
Ok(())
}
fn render(filename: &str, patch: &Patch, sel: &PatchSelectionData) -> String {
let mut res = "".to_owned();
match &patch.data {
PatchData::Normal(data) => {
for p in &data.patches {
res += &format!("{} F+{:X} ", filename, p.offset);
for on in &p.on {
res += &format!("{:02X}", on);
}
res += " ";
for off in &p.off {
res += &format!("{:02X}", off);
}
res += "\n";
}
},
PatchData::Number(data) => {
if let PatchSelectionData::Number(val) = sel {
let width = (data.size as usize) * 2usize;
res += &format!("{} F+{:X} {:0width$X} {:0width$X}", filename, data.offset, val, data.default, width = width);
} else {
log::error!("invalid number patch {:?}", patch);
}
}
}
format!("{}\n", res)
}
}

View File

@ -1,8 +1,10 @@
use anyhow::Result;
use std::collections::BTreeSet;
use crate::pkg::PkgKey;
use std::path::PathBuf;
use crate::pkg::{PkgKey, Status};
use crate::pkg_store::PackageStore;
use crate::util;
use crate::profiles::ProfilePaths;
use crate::profiles::types::ProfilePaths;
pub async fn prepare_packages<'a>(p: &'a impl ProfilePaths, pkgs: &BTreeSet<PkgKey>, redo_bepinex: bool) -> Result<()> {
log::debug!("begin prepare packages");
@ -22,10 +24,10 @@ pub async fn prepare_packages<'a>(p: &'a impl ProfilePaths, pkgs: &BTreeSet<PkgK
for m in pkgs {
log::debug!("preparing {}", m);
let (namespace, name) = m.0.split_at(m.0.find("-").expect("Invalid mod definition"));
let (namespace, name) = m.split()?;
if redo_bepinex {
let bpx_dir = util::pkg_dir_of(namespace, &name[1..]) // cut the hyphen
let bpx_dir = util::pkg_dir_of(&namespace, &name)
.join("app")
.join("BepInEx");
if bpx_dir.exists() {
@ -33,7 +35,7 @@ pub async fn prepare_packages<'a>(p: &'a impl ProfilePaths, pkgs: &BTreeSet<PkgK
}
}
let opt_dir = util::pkg_dir_of(namespace, &name[1..]).join("option");
let opt_dir = util::pkg_dir_of(&namespace, &name).join("option");
if opt_dir.exists() {
let x = opt_dir.read_dir().unwrap().next().unwrap()?;
if x.metadata()?.is_dir() {
@ -45,4 +47,27 @@ pub async fn prepare_packages<'a>(p: &'a impl ProfilePaths, pkgs: &BTreeSet<PkgK
log::debug!("end prepare packages");
Ok(())
}
pub fn prepare_dlls(
enabled_pkgs: &BTreeSet<PkgKey>,
store: &PackageStore,
) -> Result<(Vec<PathBuf>, Vec<PathBuf>)> {
let mut res_game = Vec::new();
let mut res_amd = Vec::new();
for pkg in enabled_pkgs {
if let Ok(pkg) = store.get(&pkg) {
if let Some(loc) = &pkg.loc {
if let Status::OK(_, dlls) = &loc.status {
if let Some(game_dll) = &dlls.game {
res_game.push(pkg.path().join(game_dll.clone()));
}
if let Some(amd_dll) = &dlls.amd {
res_amd.push(pkg.path().join(amd_dll.clone()));
}
}
}
}
}
Ok((res_game, res_amd))
}

View File

@ -20,7 +20,7 @@ pub enum PackageSource {
Local(Game)
}
#[derive(Clone, Default, Serialize, Deserialize)]
#[derive(Clone, Default, Serialize, Deserialize, Debug)]
#[allow(dead_code)]
pub struct Package {
pub namespace: String,
@ -31,11 +31,17 @@ pub struct Package {
pub source: PackageSource,
}
#[derive(Clone, PartialEq, Serialize, Deserialize)]
#[derive(Clone, PartialEq, Serialize, Deserialize, Debug)]
pub enum Status {
Unchecked,
Unsupported,
OK(BitFlags<Feature>)
OK(BitFlags<Feature>, DLLs),
}
#[derive(Clone, PartialEq, Serialize, Deserialize, Debug)]
pub struct DLLs {
pub game: Option<String>,
pub amd: Option<String>
}
#[bitflags]
@ -54,7 +60,7 @@ pub enum Feature {
AmdDLL
}
#[derive(Clone, Serialize, Deserialize)]
#[derive(Clone, Serialize, Deserialize, Debug)]
#[allow(dead_code)]
pub struct Local {
pub version: String,
@ -64,7 +70,7 @@ pub struct Local {
pub icon: String,
}
#[derive(Clone, Serialize, Deserialize)]
#[derive(Clone, Serialize, Deserialize, Debug)]
#[allow(dead_code)]
pub struct Remote {
pub version: String,
@ -77,6 +83,14 @@ pub struct Remote {
pub dependencies: BTreeSet<PkgKey>,
}
impl PkgKey {
pub fn split(&self) -> Result<(String, String)> {
let (namespace, name) = self.0
.split_at(self.0.find("-").ok_or_else(|| anyhow!("Invalid package key"))?);
Ok((namespace.to_owned(), name[1..].to_owned())) // cut the hyphen
}
}
impl Package {
pub fn from_rainy(mut p: rainy::V1Package) -> Option<Package> {
if p.versions.len() == 0 {
@ -209,37 +223,50 @@ impl Package {
fn parse_status(mft: &PackageManifest) -> Status {
if mft.installers.len() == 0 {
return Status::OK(make_bitflags!(Feature::Mod));//Unchecked
} else if mft.installers.len() == 1 {
if let Some(serde_json::Value::String(id)) = &mft.installers[0].get("identifier") {
if id == "rainycolor" {
return Status::OK(make_bitflags!(Feature::Mod));
} else if id == "segatools" {
// Multiple features in the same dll (yubideck etc.) should be supported at some point
let mut flags = BitFlags::default();
if let Some(serde_json::Value::String(module)) = mft.installers[0].get("module") {
if module == "mu3hook" {
flags |= Feature::Mu3Hook;
} else if module == "chusanhook" {
flags |= Feature::ChusanHook;
} else if module == "amnet" {
flags |= Feature::AMNet | Feature::Aime;
} else if module == "aimeio" {
flags |= Feature::Aime;
} else if module == "mu3io" {
flags |= Feature::Mu3IO;
} else if module == "chuniio" {
flags |= Feature::ChuniIO;
} else if module == "mempatcher" {
flags |= Feature::Mempatcher;
} else if module == "game-dll" {
flags |= Feature::GameDLL;
return Status::OK(make_bitflags!(Feature::Mod), DLLs { game: None, amd: None }); //Unchecked
} else {
let mut flags = BitFlags::default();
let mut game_dll = None;
let mut amd_dll = None;
for installer in &mft.installers {
if let Some(serde_json::Value::String(id)) = installer.get("identifier") {
if id == "rainycolor" {
flags |= Feature::Mod;
} else if id == "segatools" {
// Multiple features in the same dll (yubideck etc.) should be supported at some point
if let Some(serde_json::Value::String(module)) = installer.get("module") {
if module == "mu3hook" {
flags |= Feature::Mu3Hook;
} else if module == "chusanhook" {
flags |= Feature::ChusanHook;
} else if module == "amnet" {
flags |= Feature::AMNet | Feature::Aime;
} else if module == "aimeio" {
flags |= Feature::Aime;
} else if module == "mu3io" {
flags |= Feature::Mu3IO;
} else if module == "chuniio" {
flags |= Feature::ChuniIO;
}
}
} else if id == "native-mod" {
if let Some(serde_json::Value::String(path)) = installer.get("dll-game") {
flags |= Feature::GameDLL;
flags |= Feature::Mod;
game_dll = Some(path.to_owned());
}
if let Some(serde_json::Value::String(path)) = installer.get("dll-amdaemon") {
flags |= Feature::AmdDLL;
flags |= Feature::Mod;
amd_dll = Some(path.to_owned());
}
} else {
return Status::Unsupported;
}
return Status::OK(flags);
}
}
log::debug!("{} parse result: {:?} {:?} {:?}", mft.name, flags, game_dll, amd_dll);
Status::OK(flags, DLLs { game: game_dll, amd: amd_dll })
}
Status::Unsupported
}
}

View File

@ -8,7 +8,7 @@ use tokio::task::JoinSet;
use crate::model::local::{PackageList, PackageListEntry};
use crate::model::misc::Game;
use crate::model::rainy;
use crate::pkg::{Package, PackageSource, PkgKey, Remote, Status};
use crate::pkg::{Feature, Package, PackageSource, PkgKey, Remote, Status};
use crate::util;
use crate::download_handler::DownloadHandler;
@ -67,6 +67,21 @@ impl PackageStore {
.collect()
}
#[allow(dead_code)]
pub fn get_by_feature(&self, feature: Feature) -> Vec<PkgKey> {
self.store.iter()
.filter(|(_, v)| {
if let Some(loc) = &v.loc {
if let Status::OK(flags, _) = loc.status {
return flags.contains(feature);
}
}
return false
})
.map(|(k, _)| k.clone())
.collect()
}
pub async fn reload_package(&mut self, key: PkgKey) {
let dir = util::pkg_dir().join(&key.0);
if let Ok(pkg) = Package::from_dir(dir, PackageSource::Rainy).await {

View File

@ -1,7 +1,6 @@
use serde::{Deserialize, Serialize};
use tauri::AppHandle;
pub use types::{Profile, ProfileData, ProfileMeta, ProfilePaths, StartPayload};
use std::{collections::{BTreeMap, BTreeSet}, path::{Path, PathBuf}};
use crate::{model::{misc::Game, patch::PatchSelection, profile::{Aime, ChunithmKeyboard, Keyboard, Mu3Ini, OngekiKeyboard, ProfileModule}}, modules::package::prepare_packages, pkg::PkgKey, pkg_store::PackageStore, util};
use crate::{model::{misc::Game, patch::{PatchFileVec, PatchSelection}, profile::{Aime, ChunithmKeyboard, Keyboard, Mu3Ini, OngekiKeyboard, ProfileModule}}, modules::{display_windows::DisplayInfo, package::prepare_packages}, pkg::PkgKey, pkg_store::PackageStore, util};
use tauri::Emitter;
use std::process::Stdio;
use crate::model::profile::BepInEx;
@ -11,57 +10,7 @@ use std::fs::File;
use tokio::process::Command;
use tokio::task::JoinSet;
pub trait ProfilePaths {
fn config_dir(&self) -> PathBuf;
fn data_dir(&self) -> PathBuf;
}
#[derive(Deserialize, Serialize, Clone, Debug, PartialEq)]
pub struct ProfileMeta {
pub game: Game,
pub name: String
}
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))
}
}
#[derive(Deserialize, Serialize, Clone)]
pub struct Profile {
pub meta: ProfileMeta,
pub data: ProfileData,
}
#[derive(Deserialize, Serialize, Clone, Debug)]
pub struct ProfileData {
pub mods: BTreeSet<PkgKey>,
pub sgt: Segatools,
pub network: Network,
#[serde(skip_serializing_if = "Option::is_none")]
pub display: Option<Display>,
#[serde(skip_serializing_if = "Option::is_none")]
pub bepinex: Option<BepInEx>,
#[cfg(not(target_os = "windows"))]
pub wine: crate::model::profile::Wine,
#[serde(skip_serializing_if = "Option::is_none")]
pub mu3_ini: Option<Mu3Ini>,
#[serde(skip_serializing_if = "Option::is_none")]
pub keyboard: Option<Keyboard>,
#[serde(skip_serializing_if = "Option::is_none")]
pub patches: Option<PatchSelection>,
}
pub mod types;
impl Profile {
pub fn new(mut meta: ProfileMeta) -> Result<Self> {
@ -205,27 +154,15 @@ impl Profile {
self.data.patches = source.patches;
}
}
pub async fn line_up(&self, pkg_hash: String, refresh: bool, _app: AppHandle) -> Result<()> {
pub fn prepare_display(&self) -> Result<Option<DisplayInfo>> {
let info = match &self.data.display {
None => None,
Some(display) => display.prepare()?
};
let res = self.line_up_the_rest(pkg_hash, refresh).await;
#[cfg(target_os = "windows")]
if let Some(info) = info {
use crate::model::profile::Display;
if res.is_ok() {
Display::wait_for_exit(_app, info);
} else {
Display::clean_up(&info)?;
}
}
res
Ok(info)
}
async fn line_up_the_rest(&self, pkg_hash: String, refresh: bool) -> Result<()> {
pub async fn line_up(&self, pkg_hash: String, refresh: bool, patch_files: &PatchFileVec) -> Result<()> {
if !self.data_dir().exists() {
tokio::fs::create_dir(self.data_dir()).await?;
}
@ -235,10 +172,13 @@ impl Profile {
util::clean_up_opts(self.data_dir().join("option"))?;
let hash_check = Self::hash_check(&hash_path, &pkg_hash).await? || refresh;
prepare_packages(&self.meta, &self.data.mods, hash_check).await
.map_err(|e| anyhow!("package configuration failed:\n{:?}", e))?;
let mut ini = self.data.sgt.line_up(&self.meta, self.meta.game).await
.map_err(|e| anyhow!("segatools configuration failed:\n{:?}", e))?;
self.data.network.line_up(&mut ini)?;
if let Some(display) = &self.data.display {
@ -260,10 +200,18 @@ impl Profile {
mu3ini.line_up(&self.data.sgt.target.parent().unwrap())?;
}
if let Some(patches) = &self.data.patches {
futures::try_join!(
patches.render_to_file("amdaemon.exe", patch_files, self.data_dir().join("patch-amd.mph")),
patches.render_to_file("chusanApp.exe", patch_files, self.data_dir().join("patch-game.mph"))
)?;
}
Ok(())
}
pub async fn start(&self, app: AppHandle) -> Result<()> {
pub async fn start(&self, payload: StartPayload) -> Result<()> {
let ini_path = self.data_dir().join("segatools.ini");
log::debug!("With path {:?}", ini_path);
@ -294,10 +242,23 @@ impl Profile {
&ini_path,
)
.current_dir(&exe_dir)
.arg("/C")
.raw_arg("/C")
.arg(&sgt_dir.join(self.meta.game.inject_amd()))
.args(["-d", "-k"])
.arg(sgt_dir.join(self.meta.game.hook_amd()))
.raw_arg("-d")
.raw_arg("-k")
.arg(sgt_dir.join(self.meta.game.hook_amd()));
// for dll in payload.amd_dlls {
// amd_builder.arg("-k");
// amd_builder.arg(dll);
// }
// if self.meta.game.has_module(ProfileModule::Mempatcher) {
// amd_builder.arg("--mempatch");
// amd_builder.arg(self.data_dir().join("patch-amd.mph"));
// }
amd_builder
.arg("amdaemon.exe")
.args(self.meta.game.amd_args());
@ -317,9 +278,16 @@ impl Profile {
self.config_dir().join("saekawa.toml"),
)
.current_dir(&exe_dir)
.args(["-d", "-k"])
.arg(sgt_dir.join(self.meta.game.hook_exe()))
.arg(self.meta.game.exe());
.raw_arg("-d")
.raw_arg("-k")
.arg(sgt_dir.join(self.meta.game.hook_exe()));
for dll in payload.game_dlls {
game_builder.raw_arg("-k");
game_builder.arg(dll);
}
game_builder.arg(self.meta.game.exe());
if self.meta.game.has_module(ProfileModule::BepInEx) {
if let Some(display) = &self.data.display {
@ -339,6 +307,11 @@ impl Profile {
}
}
if self.meta.game.has_module(ProfileModule::Mempatcher) {
game_builder.arg("--mempatch");
game_builder.arg(self.data_dir().join("patch-game.mph"));
}
#[cfg(target_os = "linux")]
{
amd_builder.env("WINEPREFIX", &self.wine.prefix);
@ -383,7 +356,7 @@ impl Profile {
(game.wait().await.expect("game failed to run"), "game")
});
if let Err(e) = app.emit("launch-start", "") {
if let Err(e) = payload.app.emit("launch-start", "") {
log::warn!("Unable to emit launch-start: {}", e);
}
@ -401,7 +374,7 @@ impl Profile {
log::debug!("Fin");
if let Err(e) = app.emit("launch-end", "") {
if let Err(e) = payload.app.emit("launch-end", "") {
log::warn!("Unable to emit launch-end: {}", e);
}

View File

@ -0,0 +1,64 @@
use serde::{Deserialize, Serialize};
use tauri::AppHandle;
use std::{collections::BTreeSet, path::PathBuf};
use crate::{model::{misc::Game, patch::PatchSelection, profile::{Keyboard, Mu3Ini}}, pkg::PkgKey, util};
use crate::model::profile::BepInEx;
use crate::model::profile::{Display, Network, Segatools};
pub trait ProfilePaths {
fn config_dir(&self) -> PathBuf;
fn data_dir(&self) -> PathBuf;
}
#[derive(Deserialize, Serialize, Clone, Debug, PartialEq)]
pub struct ProfileMeta {
pub game: Game,
pub name: String
}
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))
}
}
#[derive(Deserialize, Serialize, Clone)]
pub struct Profile {
pub meta: ProfileMeta,
pub data: ProfileData,
}
#[derive(Deserialize, Serialize, Clone, Debug)]
pub struct ProfileData {
pub mods: BTreeSet<PkgKey>,
pub sgt: Segatools,
pub network: Network,
#[serde(skip_serializing_if = "Option::is_none")]
pub display: Option<Display>,
#[serde(skip_serializing_if = "Option::is_none")]
pub bepinex: Option<BepInEx>,
#[cfg(not(target_os = "windows"))]
pub wine: crate::model::profile::Wine,
#[serde(skip_serializing_if = "Option::is_none")]
pub mu3_ini: Option<Mu3Ini>,
#[serde(skip_serializing_if = "Option::is_none")]
pub keyboard: Option<Keyboard>,
#[serde(skip_serializing_if = "Option::is_none")]
pub patches: Option<PatchSelection>,
}
pub struct StartPayload {
pub app: AppHandle,
pub game_dlls: Vec<PathBuf>,
pub amd_dlls: Vec<PathBuf>,
}

View File

@ -90,8 +90,8 @@
type: 'number',
default: 3,
offset: 3768513,
size: 4,
min: 3,
size: 1,
min: 1,
max: 12,
},
{