From 4247e19996103151bc524caf62a06f1b0a5285fe Mon Sep 17 00:00:00 2001 From: akanyan Date: Sun, 13 Apr 2025 18:15:41 +0000 Subject: [PATCH] feat: chusanApp.exe patching --- rust/src/appdata.rs | 11 ++- rust/src/cmd.rs | 39 +++++++-- rust/src/model/patch.rs | 7 +- rust/src/model/profile.rs | 6 ++ rust/src/modules/keyboard.rs | 62 +++++++------ rust/src/modules/mempatcher.rs | 59 +++++++++++++ rust/src/modules/package.rs | 35 ++++++-- rust/src/pkg.rs | 91 +++++++++++++------- rust/src/pkg_store.rs | 17 +++- rust/src/profiles/mod.rs | 129 +++++++++++----------------- rust/src/profiles/types.rs | 64 ++++++++++++++ rust/static/standard-chunithm.json5 | 4 +- src/components/ModTitlecard.vue | 1 - src/components/PatchEntry.vue | 40 ++++++--- src/components/PatchList.vue | 12 +-- src/components/options/Keyboard.vue | 6 +- src/types.ts | 8 +- src/util.ts | 2 +- 18 files changed, 406 insertions(+), 187 deletions(-) create mode 100644 rust/src/profiles/types.rs diff --git a/rust/src/appdata.rs b/rust/src/appdata.rs index 9bc0649..8d71dd3 100644 --- a/rust/src/appdata.rs +++ b/rust/src/appdata.rs @@ -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); } diff --git a/rust/src/cmd.rs b/rust/src/cmd.rs index 360f0f2..0c09e9b 100644 --- a/rust/src/cmd.rs +++ b/rust/src/cmd.rs @@ -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::>(); 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>) -> Result>, 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) } \ No newline at end of file diff --git a/rust/src/model/patch.rs b/rust/src/model/patch.rs index be45619..b15d553 100644 --- a/rust/src/model/patch.rs +++ b/rust/src/model/patch.rs @@ -8,6 +8,7 @@ use anyhow::Result; pub struct PatchSelection(pub BTreeMap); #[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![]; diff --git a/rust/src/model/profile.rs b/rust/src/model/profile.rs index e93a6e0..5a34aca 100644 --- a/rust/src/model/profile.rs +++ b/rust/src/model/profile.rs @@ -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, diff --git a/rust/src/modules/keyboard.rs b/rust/src/modules/keyboard.rs index 713584f..4df7ede 100644 --- a/rust/src/modules/keyboard.rs +++ b/rust/src/modules/keyboard.rs @@ -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"); } } diff --git a/rust/src/modules/mempatcher.rs b/rust/src/modules/mempatcher.rs index e69de29..51080d9 100644 --- a/rust/src/modules/mempatcher.rs +++ b/rust/src/modules/mempatcher.rs @@ -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 + ) -> 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) + } +} \ No newline at end of file diff --git a/rust/src/modules/package.rs b/rust/src/modules/package.rs index aafbcd2..aa18cdd 100644 --- a/rust/src/modules/package.rs +++ b/rust/src/modules/package.rs @@ -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, 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(p: &'a impl ProfilePaths, pkgs: &BTreeSet(p: &'a impl ProfilePaths, pkgs: &BTreeSet, + store: &PackageStore, +) -> Result<(Vec, Vec)> { + 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)) } \ No newline at end of file diff --git a/rust/src/pkg.rs b/rust/src/pkg.rs index 659b1ca..2bd4513 100644 --- a/rust/src/pkg.rs +++ b/rust/src/pkg.rs @@ -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) + OK(BitFlags, DLLs), +} + +#[derive(Clone, PartialEq, Serialize, Deserialize, Debug)] +pub struct DLLs { + pub game: Option, + pub amd: Option } #[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, } +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 { 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 } } \ No newline at end of file diff --git a/rust/src/pkg_store.rs b/rust/src/pkg_store.rs index cec1488..213e8a1 100644 --- a/rust/src/pkg_store.rs +++ b/rust/src/pkg_store.rs @@ -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 { + 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 { diff --git a/rust/src/profiles/mod.rs b/rust/src/profiles/mod.rs index 776c865..870bfc9 100644 --- a/rust/src/profiles/mod.rs +++ b/rust/src/profiles/mod.rs @@ -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, - pub sgt: Segatools, - pub network: Network, - - #[serde(skip_serializing_if = "Option::is_none")] - pub display: Option, - - #[serde(skip_serializing_if = "Option::is_none")] - pub bepinex: Option, - - #[cfg(not(target_os = "windows"))] - pub wine: crate::model::profile::Wine, - - #[serde(skip_serializing_if = "Option::is_none")] - pub mu3_ini: Option, - - #[serde(skip_serializing_if = "Option::is_none")] - pub keyboard: Option, - - #[serde(skip_serializing_if = "Option::is_none")] - pub patches: Option, -} +pub mod types; impl Profile { pub fn new(mut meta: ProfileMeta) -> Result { @@ -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> { 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); } diff --git a/rust/src/profiles/types.rs b/rust/src/profiles/types.rs new file mode 100644 index 0000000..8979ce6 --- /dev/null +++ b/rust/src/profiles/types.rs @@ -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, + pub sgt: Segatools, + pub network: Network, + + #[serde(skip_serializing_if = "Option::is_none")] + pub display: Option, + + #[serde(skip_serializing_if = "Option::is_none")] + pub bepinex: Option, + + #[cfg(not(target_os = "windows"))] + pub wine: crate::model::profile::Wine, + + #[serde(skip_serializing_if = "Option::is_none")] + pub mu3_ini: Option, + + #[serde(skip_serializing_if = "Option::is_none")] + pub keyboard: Option, + + #[serde(skip_serializing_if = "Option::is_none")] + pub patches: Option, +} + +pub struct StartPayload { + pub app: AppHandle, + pub game_dlls: Vec, + pub amd_dlls: Vec, +} \ No newline at end of file diff --git a/rust/static/standard-chunithm.json5 b/rust/static/standard-chunithm.json5 index 4dddb19..91fbdc9 100644 --- a/rust/static/standard-chunithm.json5 +++ b/rust/static/standard-chunithm.json5 @@ -90,8 +90,8 @@ type: 'number', default: 3, offset: 3768513, - size: 4, - min: 3, + size: 1, + min: 1, max: 12, }, { diff --git a/src/components/ModTitlecard.vue b/src/components/ModTitlecard.vue index 1e60cb1..28fae69 100644 --- a/src/components/ModTitlecard.vue +++ b/src/components/ModTitlecard.vue @@ -78,7 +78,6 @@ const iconSrc = computed(() => { { if (val) { - prf.current!.data.patches[key] = 'Enabled'; + prf.current!.data.patches[key] = 'enabled'; + } else { + delete prf.current!.data.patches[key]; + } +}; + +const setNumber = (key: string, val: number) => { + if (val) { + prf.current!.data.patches[key] = { number: val }; } else { delete prf.current!.data.patches[key]; } }; defineProps({ - id: String, - name: String, - tooltip: String, - type: String, - defaultValue: Number, + patch: Object as () => Patch, }); diff --git a/src/components/PatchList.vue b/src/components/PatchList.vue index 25f04ec..e2a6ab5 100644 --- a/src/components/PatchList.vue +++ b/src/components/PatchList.vue @@ -39,11 +39,7 @@ const errorMessage =
Loading...
@@ -54,11 +50,7 @@ const errorMessage =
Loading...
diff --git a/src/components/options/Keyboard.vue b/src/components/options/Keyboard.vue index e28d314..f3848c4 100644 --- a/src/components/options/Keyboard.vue +++ b/src/components/options/Keyboard.vue @@ -6,13 +6,14 @@ import OptionCategory from '../OptionCategory.vue'; import OptionRow from '../OptionRow.vue'; import { usePrfStore } from '../../stores'; -ToggleSwitch; - const prf = usePrfStore();