feat: 0.12 update

This commit is contained in:
2025-04-19 19:48:08 +00:00
parent aaeed669df
commit 3479804dca
14 changed files with 366 additions and 74 deletions

View File

@ -1,3 +1,11 @@
## 0.12.0
- Ongeki: cache and mu3.ini config are now split per-profile (requires mu3-mods 3.7+)
- Ongeki: added the few config options of mu3.ini that aren't available in TestMenuConfig, or require a restart
- Chunithm: added Lumi+ patches
- Added a button linking to the profile config folder
- Fixed the button linking to the data folder showing up when the folder does not exist
## 0.11.1 ## 0.11.1
- Improved help pages - Improved help pages

View File

@ -9,7 +9,7 @@ STARTLINER is four things:
- a glorified `start.bat` clicker, with automatic monitor setup and rollback, - a glorified `start.bat` clicker, with automatic monitor setup and rollback,
- [an abstraction allowing data configuration without touching the game directory](https://gitea.tendokyu.moe/akanyan/STARTLINER/wiki/Architecture-details). - [an abstraction allowing data configuration without touching the game directory](https://gitea.tendokyu.moe/akanyan/STARTLINER/wiki/Architecture-details).
STARTLINER's core design principle is to modify, configure and launch games without tampering with them. STARTLINER's core design principle is to modify, configure and launch games without tampering with them.
This makes it possible to keep data cleaner than ever, and to have several configurations pointing at the same data. This makes it possible to keep data cleaner than ever, and to have several configurations pointing at the same data.
Made with Rust (Tauri) and Vue. Technically multiplatform. Contributions welcome. Made with Rust (Tauri) and Vue. Technically multiplatform. Contributions welcome.

View File

@ -1,6 +1,6 @@
use std::collections::{BTreeMap, BTreeSet}; use std::collections::{BTreeMap, BTreeSet};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::pkg::{Status, PkgKey, PkgKeyVersion}; use crate::pkg::{PkgKey, PkgKeyVersion};
use super::misc::Game; use super::misc::Game;
@ -22,6 +22,5 @@ pub type PackageList = BTreeMap<PkgKey, PackageListEntry>;
#[derive(Serialize, Deserialize, Clone)] #[derive(Serialize, Deserialize, Clone)]
pub struct PackageListEntry { pub struct PackageListEntry {
pub version: String, pub version: String,
pub status: Status,
pub games: Vec<Game>, pub games: Vec<Game>,
} }

View File

@ -166,11 +166,26 @@ pub enum Mu3Audio {
Excl2Ch, Excl2Ch,
} }
#[derive(Deserialize, Serialize, Clone, Debug, Default)] #[derive(Deserialize, Serialize, Clone, Debug)]
#[serde(default)] #[serde(default)]
pub struct Mu3Ini { pub struct Mu3Ini {
pub audio: Option<Mu3Audio>, pub audio: Option<Mu3Audio>,
pub sample_rate: i32,
pub blacklist: Option<(i32, i32)>, pub blacklist: Option<(i32, i32)>,
pub gp: i32,
pub enable_bonus_tracks: bool,
}
impl Default for Mu3Ini {
fn default() -> Self {
Self {
audio: Some(Mu3Audio::Shared),
sample_rate: 48_000,
blacklist: Some((10000, 19999)),
gp: 999,
enable_bonus_tracks: true
}
}
} }
#[derive(Deserialize, Serialize, Clone, Debug)] #[derive(Deserialize, Serialize, Clone, Debug)]

View File

@ -1,11 +1,11 @@
use std::path::Path; use std::path::Path;
use anyhow::Result; use anyhow::{anyhow, Result};
use ini::Ini; use ini::Ini;
use crate::model::profile::{Mu3Audio, Mu3Ini}; use crate::model::profile::{Mu3Audio, Mu3Ini};
impl Mu3Ini { impl Mu3Ini {
pub fn line_up(&self, game_path: impl AsRef<Path>) -> Result<()> { pub fn line_up(&self, data_dir: impl AsRef<Path>, cfg_dir: impl AsRef<Path>) -> Result<()> {
let file = game_path.as_ref().join("mu3.ini"); let file = cfg_dir.as_ref().join("mu3.ini");
if !file.exists() { if !file.exists() {
std::fs::write(&file, "")?; std::fs::write(&file, "")?;
@ -20,9 +20,26 @@ impl Mu3Ini {
Mu3Audio::Excl2Ch => "2", Mu3Audio::Excl2Ch => "2",
}; };
ini.with_section(Some("Sound")).set("WasapiExclusive", value); ini.with_section(Some("Sound"))
.set("WasapiExclusive", value)
.set("SampleRate", self.sample_rate.to_string());
} }
if let Some(blacklist) = self.blacklist {
ini.with_section(Some("Extra"))
.set("BlacklistMin", blacklist.0.to_string())
.set("BlacklistMax", blacklist.1.to_string());
}
let cache_path = data_dir.as_ref().join("mu3-mods-cache");
let cache_path = cache_path.to_str()
.ok_or_else(|| anyhow!("Invalid cache path"))?;
ini.with_section(Some("Extra"))
.set("GP", self.gp.to_string())
.set("CacheDir", cache_path)
.set("UnlockBonusTracks", crate::util::bool_to_01(self.enable_bonus_tracks));
ini.write_to_file(file)?; ini.write_to_file(file)?;
Ok(()) Ok(())

View File

@ -15,10 +15,14 @@ impl PatchFileVec {
let mut res = Vec::new(); let mut res = Vec::new();
for f in std::fs::read_dir(path)? { for f in std::fs::read_dir(path)? {
let f = f?; let f = f?;
let f = f.path(); let f = &f.path();
res.push( match serde_json5::from_str::<PatchFile>(&std::fs::read_to_string(f)?) {
serde_json5::from_str::<PatchFile>(&std::fs::read_to_string(f)?)? Ok(parsed) => res.push(parsed),
); Err(e) => {
log::error!("Error parsing {f:?}: {e}");
anyhow::bail!("Error parsing {f:?}: {e}");
}
}
} }
Ok(PatchFileVec(res)) Ok(PatchFileVec(res))
} }
@ -29,8 +33,8 @@ impl PatchFileVec {
let mut res = Vec::new(); let mut res = Vec::new();
for pfile in &self.0 { for pfile in &self.0 {
for plist in &pfile.0 { for plist in &pfile.0 {
log::debug!("checking {}", plist.sha256); log::debug!("checking {}", plist.sha256.to_ascii_lowercase());
if plist.sha256 == checksum { if plist.sha256.to_ascii_lowercase() == checksum {
let mut cloned = plist.clone().patches; let mut cloned = plist.clone().patches;
res.append(&mut cloned); res.append(&mut cloned);
} }

View File

@ -152,7 +152,6 @@ impl PackageStore {
PackageListEntry { PackageListEntry {
// from_rainy() is guaranteed to include rmt // from_rainy() is guaranteed to include rmt
version: r.rmt.as_ref().unwrap().version.clone(), version: r.rmt.as_ref().unwrap().version.clone(),
status: Status::Unchecked,
games: vec![ game ], games: vec![ game ],
} }
}); });

View File

@ -28,7 +28,7 @@ impl Profile {
bepinex: if meta.game == Game::Ongeki { Some(BepInEx::default()) } else { None }, bepinex: if meta.game == Game::Ongeki { Some(BepInEx::default()) } else { None },
#[cfg(not(target_os = "windows"))] #[cfg(not(target_os = "windows"))]
wine: crate::model::profile::Wine::default(), wine: crate::model::profile::Wine::default(),
mu3_ini: if meta.game == Game::Ongeki { Some(Mu3Ini { audio: None, blacklist: None }) } else { None }, mu3_ini: if meta.game == Game::Ongeki { Some(Mu3Ini::default()) } else { None },
keyboard: keyboard:
if meta.game == Game::Ongeki { if meta.game == Game::Ongeki {
Some(Keyboard::Ongeki(OngekiKeyboard::default())) Some(Keyboard::Ongeki(OngekiKeyboard::default()))
@ -43,6 +43,12 @@ impl Profile {
std::fs::create_dir_all(p.config_dir())?; std::fs::create_dir_all(p.config_dir())?;
std::fs::create_dir_all(p.data_dir())?; std::fs::create_dir_all(p.data_dir())?;
if meta.game == Game::Ongeki {
if let Err(e) = Self::load_existing_mu3_ini(&p.data, &p.meta) {
log::error!("unable to load existing mu3.ini: {e}");
}
}
match meta.game { match meta.game {
Game::Ongeki => std::fs::write(p.config_dir().join("segatools-base.ini"), include_bytes!("../../static/segatools-ongeki.ini"))?, Game::Ongeki => std::fs::write(p.config_dir().join("segatools-base.ini"), include_bytes!("../../static/segatools-ongeki.ini"))?,
Game::Chunithm => std::fs::write(p.config_dir().join("segatools-base.ini"), include_bytes!("../../static/segatools-chunithm.ini"))?, Game::Chunithm => std::fs::write(p.config_dir().join("segatools-base.ini"), include_bytes!("../../static/segatools-chunithm.ini"))?,
@ -67,6 +73,18 @@ impl Profile {
data.sgt.io2 = IOSelection::Custom(io); data.sgt.io2 = IOSelection::Custom(io);
data.sgt.io = None; data.sgt.io = None;
} }
if let Some(ini) = &mut data.mu3_ini {
if ini.audio.is_none() {
ini.audio = Some(crate::model::profile::Mu3Audio::Shared);
}
if ini.blacklist.is_none() {
ini.blacklist = Some((10000, 19999));
}
} else {
data.mu3_ini = Some(Mu3Ini::default());
}
Self::load_existing_mu3_ini(&data, &ProfileMeta { game, name: name.clone() })?;
} }
if game == Game::Chunithm { if game == Game::Chunithm {
if data.keyboard.is_none() { if data.keyboard.is_none() {
@ -203,7 +221,7 @@ impl Profile {
} }
if let Some(mu3ini) = &self.data.mu3_ini { if let Some(mu3ini) = &self.data.mu3_ini {
mu3ini.line_up(&self.data.sgt.target.parent().unwrap())?; mu3ini.line_up(&self.data_dir(), &self.config_dir())?;
} }
if let Some(patches) = &self.data.patches { if let Some(patches) = &self.data.patches {
@ -283,6 +301,14 @@ impl Profile {
"ONGEKI_LANG_PATH", "ONGEKI_LANG_PATH",
self.data_dir().join("lang"), self.data_dir().join("lang"),
) )
.env(
"MU3_MODS_CONFIG_PATH",
self.config_dir().join("mu3.ini"),
)
.env(
"STARTLINER",
"1"
)
.current_dir(&exe_dir) .current_dir(&exe_dir)
.raw_arg("-d") .raw_arg("-d")
.raw_arg("-k") .raw_arg("-k")
@ -403,6 +429,17 @@ impl Profile {
Ok(false) Ok(false)
} }
} }
fn load_existing_mu3_ini(data: &ProfileData, meta: &ProfileMeta) -> Result<()> {
let mu3_ini_target_path = data.sgt.target.parent().ok_or_else(|| anyhow!("invalid target directory"))?.join("mu3.ini");
let mu3_ini_profile_path = util::profile_config_dir(meta.game, &meta.name).join("mu3.ini");
log::debug!("mu3.ini paths: {:?} {:?}", mu3_ini_target_path, mu3_ini_profile_path);
if mu3_ini_target_path.exists() && !mu3_ini_profile_path.exists() {
std::fs::copy(&mu3_ini_target_path, &mu3_ini_profile_path)?;
log::info!("copied mu3.ini from {:?}", &mu3_ini_target_path);
}
Ok(())
}
} }
impl ProfilePaths for Profile { impl ProfilePaths for Profile {

View File

@ -199,7 +199,7 @@ pub fn create_shortcut(
obj.SetDescription(&format!("{} {} (STARTLINER)", &meta.game.print(), &meta.name))?; obj.SetDescription(&format!("{} {} (STARTLINER)", &meta.game.print(), &meta.name))?;
obj.SetArguments(&format!("--start --game {} --profile {}", &meta.game, &meta.name))?; obj.SetArguments(&format!("--start --game {} --profile {}", &meta.game, &meta.name))?;
obj.SetIconLocation( obj.SetIconLocation(
target_dir.join(format!("icon-{}.ico", &meta.game)).to_str().ok_or_else(|| anyhow!("Illegal icon path"))?, target_dir.join(format!("icon-{}.ico", &meta.game)).to_str().ok_or_else(|| anyhow!("Illegal icon path"))?,
0 0
)?; )?;

View File

@ -1,4 +1,154 @@
[ [
{
filename: 'chusanApp.exe',
version: '2.26.00',
sha256: 'AD2DCC02CE52B3FFF24A2919F8617854581DD2E2C0378EA13D84438FCCA2D522',
patches: [
{
id: 'standard-shared-audio',
name: "Force shared audio mode, system audio sample rate must be 48000Hz",
tooltip: "Improves compatibility, but may increase latency",
patches: [
{offset: 0xF233DA, off: [0x01], on: [0x00]}
]
},
{
id: 'standard-2ch',
name: "Force 2 channel audio output",
tooltip: "May cause bass overload",
patches: [
{offset: 0xF234B1, off: [0x75, 0x3f], on: [0x90, 0x90]}
]
},
{
id: 'standard-song-timer',
name: "Disable song select timer",
patches: [
{offset: 0xA03916, off: [0x74], on: [0xeb]}
]
},
{
id: 'standard-map-timer',
name: "Map selection timer",
tooltip: "If set to negative, the timer becomes 968 + value (e.g. 968 + -1 = 967)",
type: "number",
offset: 0x965B37,
default: 30,
size: 1,
min: -128,
max: 127,
},
{
id: 'standard-ticket-timer',
name: "Ticket selection timer",
tooltip: "If set to negative, the timer becomes 968 + value (e.g. 968 + -1 = 967)",
type: "number",
offset: 0x9592C2,
default: 60,
size: 1,
min: -128,
max: 127,
},
{
id: 'standard-course-timer',
name: "Course selection timer",
tooltip: "If set to negative, the timer becomes 968 + value (e.g. 968 + -1 = 967)",
type: "number",
offset: 0xA0EADB,
default: 30,
size: 1,
min: -128,
max: 127,
},
{
id: 'standard-unlimited-tracks',
name: "Unlimited maximum tracks",
tooltip: "Must check to play more than 7 tracks per credit",
patches: [
{offset: 0x71E2E0, off: [0xf0], on: [0xc0]}
]
},
{
id: 'standard-maximum-tracks',
type: "number",
name: "Maximum tracks",
offset: 0x3980C1,
default: 3,
size: 1,
min: 3,
max: 12
},
{
id: 'standard-no-encryption',
name: "No encryption",
tooltip: "Will also disable TLS",
patches: [
{offset: 0x1DE29E8, off: [0xE1], on: [0x00]},
{offset: 0x1DE29EC, off: [0xE1], on: [0x00]}
]
},
{
id: 'standard-no-tls',
name: "No TLS",
tooltip: "Title server workaround",
patches: [
{offset: 0xF06447, off: [0x80], on: [0x00]}
]
},
{
id: 'standard-head-to-head',
name: "Patch for head-to-head play",
tooltip: "Fix infinite sync while trying to connect to head to head play",
patches: [
{offset: 0x6533A3, off: [0x01], on: [0x00]}
]
},
{
id: 'standard-bypass-1080p',
name: "Bypass 1080p monitor check",
patches: [
{offset: 0x1CCBF, off: [0x81, 0xbc, 0x24, 0xb8, 0x02, 0x00, 0x00, 0x80, 0x07, 0x00, 0x00, 0x75, 0x1f, 0x81, 0xbc, 0x24, 0xbc, 0x02, 0x00, 0x00, 0x38, 0x04, 0x00, 0x00, 0x75, 0x12], on: [0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90]}
]
},
{
id: 'standard-bypass-120hz',
name: "Bypass 120Hz monitor check",
patches: [
{offset: 0x1CCB1, off: [0x85, 0xc0], on: [0xeb, 0x30]}
]
},
{
id: 'standard-force-free-play-text',
name: "Force FREE PLAY credit text",
tooltip: "Replaces the credit count with FREE PLAY",
patches: [
{offset: 0x3875A4, off: [0x3c, 0x01], on: [0x38, 0xc0]}
]
},
],
},
{
filename: 'amdaemon.exe',
version: '2.25.00',
sha256: '00FB867D1EE821033101B8773FAC116A45DF1939D23C38E9DAFC9B86CD5A3777',
patches: [
{
id: 'standard-localhost',
name: "Allow 127.0.0.1/localhost as the network server",
patches: [
{ offset: 0x6E28A4, off: [0x31, 0x32, 0x37, 0x2F], on: [0x30, 0x2F, 0x38, 0x00] },
{ offset: 0x3C94C4, off: [0xFF, 0x15, 0xC6, 0x2F, 0x1B, 0x00, 0x8B], on: [0x33, 0xC0, 0x48, 0x83, 0xC4, 0x28, 0xC3] }
]
},
{
id: 'standard-credit-freeze',
name: "Infinite credits",
patches: [
{ offset: 0x2BBBC8, off: [0x28], on: [0x08] }
]
}
]
},
{ {
filename: 'chusanApp.exe', filename: 'chusanApp.exe',
version: '2.30.00', version: '2.30.00',

View File

@ -1,7 +1,7 @@
{ {
"$schema": "https://schema.tauri.app/config/2", "$schema": "https://schema.tauri.app/config/2",
"productName": "STARTLINER", "productName": "STARTLINER",
"version": "0.11.1", "version": "0.12.0",
"identifier": "zip.patafour.startliner", "identifier": "zip.patafour.startliner",
"build": { "build": {
"beforeDevCommand": "bun run dev", "beforeDevCommand": "bun run dev",

View File

@ -1,5 +1,6 @@
<script setup lang="ts"> <script setup lang="ts">
import { computed } from 'vue'; import { computed } from 'vue';
import InputNumber from 'primevue/inputnumber';
import SelectButton from 'primevue/selectbutton'; import SelectButton from 'primevue/selectbutton';
import ToggleSwitch from 'primevue/toggleswitch'; import ToggleSwitch from 'primevue/toggleswitch';
import FileEditor from './FileEditor.vue'; import FileEditor from './FileEditor.vue';
@ -16,53 +17,35 @@ import { usePrfStore } from '../stores';
const prf = usePrfStore(); const prf = usePrfStore();
const audioModel = computed({ const blacklistMinModel = computed({
get() { get() {
return prf.current?.data.mu3_ini?.audio ?? null; if (prf.current?.data.mu3_ini?.blacklist === undefined) {
}, return null;
set(value: 'Shared' | 'Excl6Ch' | 'Excl2Ch') {
if (prf.current!.data.mu3_ini === undefined) {
prf.current!.data.mu3_ini = {};
} }
prf.current!.data.mu3_ini!.audio = value; return prf.current?.data.mu3_ini?.blacklist[0];
},
set(value: number) {
prf.current!.data.mu3_ini!.blacklist = [
value,
prf.current!.data.mu3_ini!.blacklist?.[1] ?? 19999,
];
}, },
}); });
// const blacklistMinModel = computed({ const blacklistMaxModel = computed({
// get() { get() {
// if (prf.current?.data.mu3_ini?.blacklist === undefined) { if (prf.current?.data.mu3_ini?.blacklist === undefined) {
// return null; return null;
// } }
// return prf.current?.data.mu3_ini?.blacklist[0]; return prf.current?.data.mu3_ini.blacklist[1];
// }, },
// set(value: number) { set(value: number) {
// if (prf.current!.data.mu3_ini === undefined) { prf.current!.data.mu3_ini!.blacklist = [
// prf.current!.data.mu3_ini = {}; prf.current!.data.mu3_ini!.blacklist?.[0] ?? 10000,
// } value,
// prf.current!.data.mu3_ini!.blacklist = [ ];
// value, },
// prf.current!.data.mu3_ini!.blacklist?.[1] ?? 19999, });
// ];
// },
// });
// const blacklistMaxModel = computed({
// get() {
// if (prf.current?.data.mu3_ini?.blacklist === undefined) {
// return null;
// }
// return prf.current?.data.mu3_ini?.blacklist[1];
// },
// set(value: number) {
// if (prf.current!.data.mu3_ini === undefined) {
// prf.current!.data.mu3_ini = {};
// }
// prf.current!.data.mu3_ini!.blacklist = [
// prf.current!.data.mu3_ini!.blacklist?.[0] ?? 10000,
// value,
// ];
// },
// });
prf.reload(); prf.reload();
</script> </script>
@ -102,34 +85,59 @@ prf.reload();
<OptionRow <OptionRow
title="Audio mode" title="Audio mode"
tooltip="Exclusive 2-channel mode requires a patch" tooltip="Exclusive 2-channel mode requires 7EVENDAYSHOLIDAYS-ExclusiveAudio"
> >
<SelectButton <SelectButton
v-model="audioModel" v-model="prf.current!.data.mu3_ini!.audio"
:options="[ :options="[
{ title: 'Shared', value: 'Shared' }, { title: 'Shared', value: 'Shared' },
{ title: 'Exclusive 6-channel', value: 'Excl6Ch' }, { title: 'Exclusive 6-channel', value: 'Excl6Ch' },
{ title: 'Exclusive 2-channel', value: 'Excl2Ch' }, { title: 'Exclusive 2-channel', value: 'Excl2Ch' },
]" ]"
:allow-empty="true" :allow-empty="false"
option-label="title" option-label="title"
option-value="value" option-value="value"
/></OptionRow> /></OptionRow>
<!-- <OptionRow <OptionRow
title="Sample rate"
v-if="
prf.current?.data.mods.includes(
'7EVENDAYSHOLIDAYS-ExclusiveAudio'
)
"
>
<SelectButton
v-model="prf.current!.data.mu3_ini!.sample_rate"
:disabled="prf.current!.data.mu3_ini!.audio === 'Shared'"
:options="[
{ title: '44.1KHz', value: 44100 },
{ title: '48KHz', value: 48000 },
{ title: '96KHz', value: 96000 },
{ title: '192KHz', value: 192000 },
]"
:allow-empty="false"
option-label="title"
option-value="value"
/></OptionRow>
<OptionRow
v-if="
prf.current?.data.mods.includes('7EVENDAYSHOLIDAYS-Blacklist')
"
class="number-input" class="number-input"
title="Song ID Blacklist" title="Song ID Blacklist"
tooltip="Requires a patch" tooltip="Scores on charts within this ID range will not be saved nor uploaded"
><InputNumber ><InputNumber
class="shrink" class="shrink"
size="small" size="small"
:min="10000" :min="9000"
:max="99999" :max="99999"
placeholder="10000" placeholder="10000"
:use-grouping="false" :use-grouping="false"
:allow-empty="false" :allow-empty="false"
v-model="blacklistMinModel" /> v-model="blacklistMinModel" />
x ~
<InputNumber <InputNumber
class="shrink" class="shrink"
size="small" size="small"
@ -139,7 +147,36 @@ prf.reload();
:use-grouping="false" :use-grouping="false"
:allow-empty="false" :allow-empty="false"
v-model="blacklistMaxModel" v-model="blacklistMaxModel"
/></OptionRow> --> /></OptionRow>
<OptionRow
class="number-input"
title="GP"
v-if="
prf.current?.data.mods.includes('7EVENDAYSHOLIDAYS-DisableGP')
"
><InputNumber
class="shrink"
size="small"
:min="0"
:max="9999"
:use-grouping="false"
:allow-empty="false"
v-model="prf.current!.data.mu3_ini!.gp"
/>
</OptionRow>
<OptionRow
title="Unlock Bonus Tracks"
tooltip="Disabling this option can help declutter the song list"
v-if="
prf.current?.data.mods.includes(
'7EVENDAYSHOLIDAYS-UnlockAllMusic'
)
"
>
<ToggleSwitch
v-model="prf.current!.data.mu3_ini!.enable_bonus_tracks"
/>
</OptionRow>
</OptionCategory> </OptionCategory>
<KeyboardOptions /> <KeyboardOptions />
<StartlinerOptions /> <StartlinerOptions />

View File

@ -65,6 +65,14 @@ const promptDeleteProfile = async () => {
accept: deleteProfile, accept: deleteProfile,
}); });
}; };
const dataExists = ref(false);
path.join(general.dataDir, `profile-${props.p!.game}-${props.p!.name}`).then(
async (p) => {
dataExists.value = await invoke('file_exists', { path: p });
}
);
</script> </script>
<template> <template>
@ -124,10 +132,27 @@ const promptDeleteProfile = async () => {
@click="isEditing = true" @click="isEditing = true"
/> />
<Button <Button
rounded
icon="pi pi-cog"
severity="help"
aria-label="open-config-directory"
size="small"
class="self-center"
style="width: 2rem; height: 2rem"
@click="
path
.join(general.configDir, `profile-${p!.game}-${p!.name}`)
.then(async (path) => {
await invoke('open_file', { path });
})
"
/>
<Button
v-if="dataExists"
rounded rounded
icon="pi pi-folder" icon="pi pi-folder"
severity="help" severity="help"
aria-label="open-directory" aria-label="open-data-directory"
size="small" size="small"
class="self-center" class="self-center"
style="width: 2rem; height: 2rem" style="width: 2rem; height: 2rem"
@ -135,9 +160,7 @@ const promptDeleteProfile = async () => {
path path
.join(general.dataDir, `profile-${p!.game}-${p!.name}`) .join(general.dataDir, `profile-${p!.game}-${p!.name}`)
.then(async (path) => { .then(async (path) => {
if (await invoke('file_exists', { path })) { await invoke('open_file', { path });
await invoke('open_file', { path });
}
}) })
" "
/> />

View File

@ -107,7 +107,10 @@ export interface BepInExConfig {
export interface Mu3IniConfig { export interface Mu3IniConfig {
audio?: 'Shared' | 'Excl6Ch' | 'Excl2Ch'; audio?: 'Shared' | 'Excl6Ch' | 'Excl2Ch';
// blacklist?: [number, number]; sample_rate: number;
blacklist?: [number, number];
gp: number;
enable_bonus_tracks: boolean;
} }
export interface OngekiButtons { export interface OngekiButtons {