forked from akanyan/STARTLINER
feat: 0.12 update
This commit is contained in:
@ -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
|
||||||
|
@ -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.
|
||||||
|
@ -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>,
|
||||||
}
|
}
|
@ -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)]
|
||||||
|
@ -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(())
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
@ -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 ],
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -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 {
|
||||||
|
@ -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
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
|
@ -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',
|
||||||
|
@ -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",
|
||||||
|
@ -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 />
|
||||||
|
@ -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 });
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
"
|
"
|
||||||
/>
|
/>
|
||||||
|
@ -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 {
|
||||||
|
Reference in New Issue
Block a user