From 17411e8f0df9a22a2e8fe131767bea0c4f103d03 Mon Sep 17 00:00:00 2001 From: akanyan Date: Sat, 29 Mar 2025 00:55:31 +0000 Subject: [PATCH] feat: audio mode --- rust/src/model/misc.rs | 10 ++++ rust/src/model/profile.rs | 28 ++++++++++ rust/src/modules/mod.rs | 1 + rust/src/modules/mu3ini.rs | 30 +++++++++++ rust/src/profiles/mod.rs | 33 +++++++++--- src/components/OptionList.vue | 91 +++++++++++++++++++++++++++++++++ src/components/ProfileList.vue | 1 + src/components/options/Aime.vue | 10 ++++ src/types.ts | 7 +++ 9 files changed, 203 insertions(+), 8 deletions(-) create mode 100644 rust/src/modules/mu3ini.rs diff --git a/rust/src/model/misc.rs b/rust/src/model/misc.rs index 4529adc..25eb101 100644 --- a/rust/src/model/misc.rs +++ b/rust/src/model/misc.rs @@ -1,6 +1,9 @@ +use enumflags2::make_bitflags; use serde::{Deserialize, Serialize}; use crate::pkg::PkgKey; +use super::profile::ProfileModule; + #[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Copy)] pub enum Game { #[serde(rename = "ongeki")] @@ -59,6 +62,13 @@ impl Game { Game::Chunithm => vec!["-c", "config_common.json", "config_server.json", "config_client.json", "config_cvt.json", "config_sp.json", "config_hook.json"] } } + + pub fn has_module(&self, module: ProfileModule) -> bool { + match self { + Game::Ongeki => make_bitflags!(ProfileModule::{Segatools | Display | Network | BepInEx | Mu3Ini}), + Game::Chunithm => make_bitflags!(ProfileModule::{Segatools | Network}), + }.contains(module) + } } impl std::fmt::Display for Game { diff --git a/rust/src/model/profile.rs b/rust/src/model/profile.rs index 81d9afa..7bdb63d 100644 --- a/rust/src/model/profile.rs +++ b/rust/src/model/profile.rs @@ -1,6 +1,7 @@ use std::path::PathBuf; use serde::{Deserialize, Serialize}; use crate::pkg::PkgKey; +use enumflags2::bitflags; use super::misc::Game; @@ -135,4 +136,31 @@ impl Default for Wine { .unwrap_or_default() } } +} + +#[derive(Deserialize, Serialize, Clone, PartialEq, Debug, Copy)] +pub enum Mu3Audio { + Shared, + Excl6Ch, + Excl2Ch, +} + +#[derive(Deserialize, Serialize, Clone, Debug)] +pub struct Mu3Ini { + #[serde(skip_serializing_if = "Option::is_none")] + pub audio: Option, + + #[serde(skip_serializing_if = "Option::is_none")] + pub blacklist: Option<(i32, i32)>, +} + +#[bitflags] +#[repr(u8)] +#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)] +pub enum ProfileModule { + Segatools, + Network, + Display, + BepInEx, + Mu3Ini } \ No newline at end of file diff --git a/rust/src/modules/mod.rs b/rust/src/modules/mod.rs index 9291840..edb39c7 100644 --- a/rust/src/modules/mod.rs +++ b/rust/src/modules/mod.rs @@ -2,6 +2,7 @@ pub mod package; pub mod segatools; pub mod network; pub mod bepinex; +pub mod mu3ini; #[cfg(target_os = "windows")] pub mod display_windows; \ No newline at end of file diff --git a/rust/src/modules/mu3ini.rs b/rust/src/modules/mu3ini.rs new file mode 100644 index 0000000..70ce1c2 --- /dev/null +++ b/rust/src/modules/mu3ini.rs @@ -0,0 +1,30 @@ +use std::path::Path; +use anyhow::Result; +use ini::Ini; +use crate::model::profile::{Mu3Audio, Mu3Ini}; + +impl Mu3Ini { + pub fn line_up(&self, game_path: impl AsRef) -> Result<()> { + let file = game_path.as_ref().join("mu3.ini"); + + if !file.exists() { + std::fs::write(&file, "")?; + } + + let mut ini = Ini::load_from_file(&file)?; + + if let Some(audio) = self.audio { + let value = match audio { + Mu3Audio::Shared => "0", + Mu3Audio::Excl6Ch => "1", + Mu3Audio::Excl2Ch => "2", + }; + + ini.with_section(Some("Sound")).set("WasapiExclusive", value); + } + + ini.write_to_file(file)?; + + Ok(()) + } +} \ No newline at end of file diff --git a/rust/src/profiles/mod.rs b/rust/src/profiles/mod.rs index 80f22f5..63d9059 100644 --- a/rust/src/profiles/mod.rs +++ b/rust/src/profiles/mod.rs @@ -1,7 +1,7 @@ use serde::{Deserialize, Serialize}; use tauri::AppHandle; use std::{collections::BTreeSet, path::{Path, PathBuf}}; -use crate::{model::{misc::Game, profile::Aime}, modules::package::prepare_packages, pkg::PkgKey, pkg_store::PackageStore, util}; +use crate::{model::{misc::Game, profile::{Aime, Mu3Ini, ProfileModule}}, modules::package::prepare_packages, pkg::PkgKey, pkg_store::PackageStore, util}; use tauri::Emitter; use std::process::Stdio; use crate::model::profile::BepInEx; @@ -42,14 +42,19 @@ pub struct Profile { pub struct ProfileData { pub mods: BTreeSet, pub sgt: Segatools, - pub display: Option, 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 } impl Profile { @@ -68,6 +73,7 @@ impl Profile { bepinex: if meta.game == Game::Ongeki { Some(BepInEx::default()) } else { None }, #[cfg(not(target_os = "windows"))] wine: crate::model::profile::Wine::default(), + mu3_ini: if meta.game == Game::Ongeki { Some(Mu3Ini { audio: None, blacklist: None }) } else { None }, }, meta: meta.clone() }; @@ -138,18 +144,25 @@ impl Profile { self.data.sgt.fix(store); } pub fn sync(&mut self, source: ProfileData) { - if self.data.bepinex.is_some() { + if self.meta.game.has_module(ProfileModule::BepInEx) && source.bepinex.is_some() { self.data.bepinex = source.bepinex; } - if self.data.display.is_some() { + + if self.meta.game.has_module(ProfileModule::Display) && source.display.is_some() { self.data.display = source.display; } - // if self.data.network.is_some() { + + if self.meta.game.has_module(ProfileModule::Network) { self.data.network = source.network; - // } - // if self.data.sgt.is_some() { + } + + if self.meta.game.has_module(ProfileModule::Segatools) { self.data.sgt = source.sgt; - // } + } + + if self.meta.game.has_module(ProfileModule::Mu3Ini) && source.mu3_ini.is_some() { + self.data.mu3_ini = source.mu3_ini; + } } pub async fn line_up(&self, pkg_hash: String, refresh: bool, _app: AppHandle) -> Result<()> { let info = match &self.data.display { @@ -194,6 +207,10 @@ impl Profile { bepinex.line_up(&self.meta)?; } + if let Some(mu3ini) = &self.data.mu3_ini { + mu3ini.line_up(&self.data.sgt.target.parent().unwrap())?; + } + Ok(()) } diff --git a/src/components/OptionList.vue b/src/components/OptionList.vue index 9565b94..d7f6cba 100644 --- a/src/components/OptionList.vue +++ b/src/components/OptionList.vue @@ -1,4 +1,6 @@ @@ -36,6 +86,47 @@ prf.reload(); + + + + + diff --git a/src/components/ProfileList.vue b/src/components/ProfileList.vue index c594675..eb420b7 100644 --- a/src/components/ProfileList.vue +++ b/src/components/ProfileList.vue @@ -19,6 +19,7 @@ const prf = usePrfStore(); icon="pi pi-plus" class="chunithm-button profile-button" @click="() => prf.create('chunithm')" + v-tooltip="'!!! Experimental !!!'" />
diff --git a/src/components/options/Aime.vue b/src/components/options/Aime.vue index adb942f..2de00c5 100644 --- a/src/components/options/Aime.vue +++ b/src/components/options/Aime.vue @@ -31,6 +31,15 @@ const aimeCodeModel = computed({ }, }); +const aimeCodePaste = (ev: ClipboardEvent) => { + aimeCodeModel.value = + ev.clipboardData + ?.getData('text/plain') + .split('') + .filter((c) => c >= '0' && c <= '9') + .join('') ?? ''; +}; + (async () => { const aime_path = await path.join(await prf.configDir, 'aime.txt'); aimeCode.value = await readTextFile(aime_path).catch(() => ''); @@ -67,6 +76,7 @@ const aimeCodeModel = computed({ :maxlength="20" placeholder="00000000000000000000" v-model="aimeCodeModel" + @paste="aimeCodePaste" />
diff --git a/src/types.ts b/src/types.ts index 37ded15..585fef4 100644 --- a/src/types.ts +++ b/src/types.ts @@ -52,6 +52,7 @@ export interface ProfileData { display: DisplayConfig; network: NetworkConfig; bepinex: BepInExConfig; + mu3_ini: Mu3IniConfig | undefined; } export interface SegatoolsConfig { @@ -88,10 +89,16 @@ export interface NetworkConfig { subnet: string; suffix: number | null; } + export interface BepInExConfig { console: boolean; } +export interface Mu3IniConfig { + audio?: 'Shared' | 'Excl6Ch' | 'Excl2Ch'; + // blacklist?: [number, number]; +} + export interface Profile { meta: ProfileMeta; data: ProfileData;