feat: segatools.ini loading
This commit is contained in:
@ -1,5 +1,7 @@
|
|||||||
|
use ini::Ini;
|
||||||
use log;
|
use log;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
use std::path::PathBuf;
|
||||||
use tokio::sync::Mutex;
|
use tokio::sync::Mutex;
|
||||||
use tokio::fs;
|
use tokio::fs;
|
||||||
use tauri::{AppHandle, Manager, State};
|
use tauri::{AppHandle, Manager, State};
|
||||||
@ -319,7 +321,7 @@ pub async fn get_current_profile(state: State<'_, Mutex<AppData>>) -> Result<Opt
|
|||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub async fn sync_current_profile(state: State<'_, Mutex<AppData>>, data: ProfileData) -> Result<(), String> {
|
pub async fn sync_current_profile(state: State<'_, Mutex<AppData>>, data: ProfileData) -> Result<(), String> {
|
||||||
log::debug!("invoke: sync_current_profile");
|
log::debug!("invoke: sync_current_profile {:?}", data);
|
||||||
|
|
||||||
let mut appd = state.lock().await;
|
let mut appd = state.lock().await;
|
||||||
if let Some(p) = &mut appd.profile {
|
if let Some(p) = &mut appd.profile {
|
||||||
@ -345,6 +347,27 @@ pub async fn save_current_profile(state: State<'_, Mutex<AppData>>) -> Result<()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
pub async fn load_segatools_ini(state: State<'_, Mutex<AppData>>, path: PathBuf) -> Result<(), String> {
|
||||||
|
log::debug!("invoke: load_segatools_ini({:?})", path);
|
||||||
|
|
||||||
|
let mut appd = state.lock().await;
|
||||||
|
if let Some(p) = &mut appd.profile {
|
||||||
|
let str = std::fs::read_to_string(path).map_err(|e| e.to_string())?;
|
||||||
|
// Stupid path escape hack for the ini reader
|
||||||
|
let str = str.replace("\\", "\\\\").replace("\\\\\\\\", "\\\\");
|
||||||
|
let ini = Ini::load_from_str(&str).map_err(|e| e.to_string())?;
|
||||||
|
p.data.sgt.load_from_ini(&ini);
|
||||||
|
p.data.network.load_from_ini(&ini).map_err(|e| e.to_string())?;
|
||||||
|
if let Some(kb) = &mut p.data.keyboard {
|
||||||
|
kb.load_from_ini(&ini).map_err(|e| e.to_string())?;
|
||||||
|
}
|
||||||
|
p.save().map_err(|e| e.to_string())?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub async fn list_platform_capabilities() -> Result<Vec<String>, ()> {
|
pub async fn list_platform_capabilities() -> Result<Vec<String>, ()> {
|
||||||
log::debug!("invoke: list_platform_capabilities");
|
log::debug!("invoke: list_platform_capabilities");
|
||||||
@ -414,3 +437,9 @@ pub async fn list_directories() -> Result<util::Dirs, ()> {
|
|||||||
|
|
||||||
Ok(util::all_dirs().clone())
|
Ok(util::all_dirs().clone())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Tauri fs api is useless
|
||||||
|
#[tauri::command]
|
||||||
|
pub async fn file_exists(path: String) -> Result<bool, ()> {
|
||||||
|
Ok(std::fs::exists(path).unwrap_or(false))
|
||||||
|
}
|
@ -71,8 +71,8 @@ pub async fn run(_args: Vec<String>) {
|
|||||||
} else {
|
} else {
|
||||||
tauri::WebviewWindowBuilder::new(app, "main", tauri::WebviewUrl::App("index.html".into()))
|
tauri::WebviewWindowBuilder::new(app, "main", tauri::WebviewUrl::App("index.html".into()))
|
||||||
.title("STARTLINER")
|
.title("STARTLINER")
|
||||||
.inner_size(760f64, 480f64)
|
.inner_size(900f64, 480f64)
|
||||||
.min_inner_size(760f64, 480f64)
|
.min_inner_size(900f64, 480f64)
|
||||||
.build()?;
|
.build()?;
|
||||||
start_immediately = false;
|
start_immediately = false;
|
||||||
}
|
}
|
||||||
@ -199,6 +199,7 @@ pub async fn run(_args: Vec<String>) {
|
|||||||
cmd::get_current_profile,
|
cmd::get_current_profile,
|
||||||
cmd::sync_current_profile,
|
cmd::sync_current_profile,
|
||||||
cmd::save_current_profile,
|
cmd::save_current_profile,
|
||||||
|
cmd::load_segatools_ini,
|
||||||
|
|
||||||
cmd::get_global_config,
|
cmd::get_global_config,
|
||||||
cmd::set_global_config,
|
cmd::set_global_config,
|
||||||
@ -206,6 +207,7 @@ pub async fn run(_args: Vec<String>) {
|
|||||||
cmd::list_displays,
|
cmd::list_displays,
|
||||||
cmd::list_platform_capabilities,
|
cmd::list_platform_capabilities,
|
||||||
cmd::list_directories,
|
cmd::list_directories,
|
||||||
|
cmd::file_exists,
|
||||||
])
|
])
|
||||||
.build(tauri::generate_context!())
|
.build(tauri::generate_context!())
|
||||||
.expect("error while building tauri application");
|
.expect("error while building tauri application");
|
||||||
|
@ -65,8 +65,8 @@ impl Game {
|
|||||||
|
|
||||||
pub fn has_module(&self, module: ProfileModule) -> bool {
|
pub fn has_module(&self, module: ProfileModule) -> bool {
|
||||||
match self {
|
match self {
|
||||||
Game::Ongeki => make_bitflags!(ProfileModule::{Segatools | Display | Network | BepInEx | Mu3Ini}),
|
Game::Ongeki => make_bitflags!(ProfileModule::{Segatools | Display | Network | BepInEx | Mu3Ini | Keyboard}),
|
||||||
Game::Chunithm => make_bitflags!(ProfileModule::{Segatools | Network}),
|
Game::Chunithm => make_bitflags!(ProfileModule::{Segatools | Network | Keyboard}),
|
||||||
}.contains(module)
|
}.contains(module)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -162,6 +162,75 @@ pub struct Mu3Ini {
|
|||||||
pub blacklist: Option<(i32, i32)>,
|
pub blacklist: Option<(i32, i32)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Serialize, Clone, Debug)]
|
||||||
|
pub struct OngekiKeyboard {
|
||||||
|
pub use_mouse: bool,
|
||||||
|
pub coin: i32,
|
||||||
|
pub svc: i32,
|
||||||
|
pub test: i32,
|
||||||
|
pub lmenu: i32,
|
||||||
|
pub rmenu: i32,
|
||||||
|
pub l1: i32,
|
||||||
|
pub l2: i32,
|
||||||
|
pub l3: i32,
|
||||||
|
pub r1: i32,
|
||||||
|
pub r2: i32,
|
||||||
|
pub r3: i32,
|
||||||
|
pub lwad: i32,
|
||||||
|
pub rwad: i32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for OngekiKeyboard {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
use_mouse: true,
|
||||||
|
test: 0x70,
|
||||||
|
svc: 0x71,
|
||||||
|
coin: 0x72,
|
||||||
|
lmenu: 0x55,
|
||||||
|
rmenu: 0x4F,
|
||||||
|
lwad: 0x01,
|
||||||
|
rwad: 0x02,
|
||||||
|
l1: 0x41,
|
||||||
|
l2: 0x53,
|
||||||
|
l3: 0x44,
|
||||||
|
r1: 0x4A,
|
||||||
|
r2: 0x4B,
|
||||||
|
r3: 0x4C
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Serialize, Clone, Debug)]
|
||||||
|
pub struct ChunithmKeyboard {
|
||||||
|
pub split_ir: bool,
|
||||||
|
pub coin: i32,
|
||||||
|
pub svc: i32,
|
||||||
|
pub test: i32,
|
||||||
|
pub cell: [i32; 32],
|
||||||
|
pub ir: [i32; 6],
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for ChunithmKeyboard {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
split_ir: false,
|
||||||
|
test: 0x70,
|
||||||
|
svc: 0x71,
|
||||||
|
coin: 0x72,
|
||||||
|
cell: Default::default(),
|
||||||
|
ir: Default::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Serialize, Clone, Debug)]
|
||||||
|
#[serde(tag = "game", content = "data")]
|
||||||
|
pub enum Keyboard {
|
||||||
|
Ongeki(OngekiKeyboard),
|
||||||
|
Chunithm(ChunithmKeyboard),
|
||||||
|
}
|
||||||
|
|
||||||
#[bitflags]
|
#[bitflags]
|
||||||
#[repr(u8)]
|
#[repr(u8)]
|
||||||
#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
|
#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||||
@ -170,5 +239,6 @@ pub enum ProfileModule {
|
|||||||
Network,
|
Network,
|
||||||
Display,
|
Display,
|
||||||
BepInEx,
|
BepInEx,
|
||||||
Mu3Ini
|
Mu3Ini,
|
||||||
|
Keyboard,
|
||||||
}
|
}
|
@ -38,49 +38,7 @@ cabLedOutputSerial=0
|
|||||||
; Output slider LED data to the named pipe
|
; Output slider LED data to the named pipe
|
||||||
controllerLedOutputPipe=1
|
controllerLedOutputPipe=1
|
||||||
; Output slider LED data to the serial port
|
; Output slider LED data to the serial port
|
||||||
controllerLedOutputSerial=0
|
controllerLedOutputSerial=0".to_owned(),
|
||||||
|
|
||||||
[io4]
|
|
||||||
; Test button virtual-key code. Default is the F1 key.
|
|
||||||
test=0x70
|
|
||||||
; Service button virtual-key code. Default is the F2 key.
|
|
||||||
service=0x71
|
|
||||||
; Keyboard button to increment coin counter. Default is the F3 key.
|
|
||||||
coin=0x72
|
|
||||||
|
|
||||||
; Set \"1\" to enable mouse lever emulation, \"0\" to use XInput
|
|
||||||
mouse=1
|
|
||||||
|
|
||||||
; XInput input bindings
|
|
||||||
;
|
|
||||||
; Left Stick Lever
|
|
||||||
; Left Trigger Lever (move to the left)
|
|
||||||
; Right Trigger Lever (move to the right)
|
|
||||||
; Left Left red button
|
|
||||||
; Up Left green button
|
|
||||||
; Right Left blue button
|
|
||||||
; Left Shoulder Left side button
|
|
||||||
; Right Shoulder Right side button
|
|
||||||
; X Right red button
|
|
||||||
; Y Right green button
|
|
||||||
; A Right blue button
|
|
||||||
; Back Left menu button
|
|
||||||
; Start Right menu button
|
|
||||||
|
|
||||||
; Keyboard input bindings
|
|
||||||
left1=0x41 ; A
|
|
||||||
left2=0x53 ; S
|
|
||||||
left3=0x44 ; D
|
|
||||||
|
|
||||||
leftSide=0x01 ; Mouse Left
|
|
||||||
rightSide=0x02 ; Mouse Right
|
|
||||||
|
|
||||||
right1=0x4A ; J
|
|
||||||
right2=0x4B ; K
|
|
||||||
right3=0x4C ; L
|
|
||||||
|
|
||||||
leftMenu=0x55 ; U
|
|
||||||
rightMenu=0x4F ; O".to_owned(),
|
|
||||||
Game::Chunithm => "
|
Game::Chunithm => "
|
||||||
[vfd]
|
[vfd]
|
||||||
; Enable VFD emulation. Disable to use a real VFD
|
; Enable VFD emulation. Disable to use a real VFD
|
||||||
@ -179,120 +137,6 @@ controllerLedOutputOpeNITHM=0
|
|||||||
; x86 chuniio to path32, x64 to path64. Both are necessary.
|
; x86 chuniio to path32, x64 to path64. Both are necessary.
|
||||||
;path32=
|
;path32=
|
||||||
;path64=
|
;path64=
|
||||||
|
|
||||||
; -----------------------------------------------------------------------------
|
|
||||||
; Input settings
|
|
||||||
; -----------------------------------------------------------------------------
|
|
||||||
|
|
||||||
; Keyboard bindings are specified as hexadecimal (prefixed with 0x) or decimal
|
|
||||||
; (not prefixed with 0x) virtual-key codes, a list of which can be found here:
|
|
||||||
;
|
|
||||||
; https://docs.microsoft.com/en-us/windows/win32/inputdev/virtual-key-codes
|
|
||||||
;
|
|
||||||
; This is, admittedly, not the most user-friendly configuration method in the
|
|
||||||
; world. An improved solution will be provided later.
|
|
||||||
|
|
||||||
[io3]
|
|
||||||
|
|
||||||
test=0x31
|
|
||||||
|
|
||||||
service=0x32
|
|
||||||
|
|
||||||
coin=0x33
|
|
||||||
|
|
||||||
ir=0x00
|
|
||||||
|
|
||||||
ir6=0x39
|
|
||||||
|
|
||||||
ir5=0x38
|
|
||||||
|
|
||||||
ir4=0x37
|
|
||||||
|
|
||||||
ir3=0x36
|
|
||||||
|
|
||||||
ir2=0x35
|
|
||||||
|
|
||||||
ir1=0x34
|
|
||||||
|
|
||||||
[ir]
|
|
||||||
|
|
||||||
ir6=0x39
|
|
||||||
|
|
||||||
ir5=0x38
|
|
||||||
|
|
||||||
ir4=0x37
|
|
||||||
|
|
||||||
ir3=0x36
|
|
||||||
|
|
||||||
ir2=0x35
|
|
||||||
|
|
||||||
ir1=0x34
|
|
||||||
|
|
||||||
[slider]
|
|
||||||
|
|
||||||
cell32=0x51
|
|
||||||
|
|
||||||
cell30=0x5A
|
|
||||||
|
|
||||||
cell28=0x53
|
|
||||||
|
|
||||||
cell26=0x45
|
|
||||||
|
|
||||||
cell24=0x43
|
|
||||||
|
|
||||||
cell22=0x46
|
|
||||||
|
|
||||||
cell20=0x54
|
|
||||||
|
|
||||||
cell18=0x42
|
|
||||||
|
|
||||||
cell16=0x48
|
|
||||||
|
|
||||||
cell14=0x55
|
|
||||||
|
|
||||||
cell12=0x4D
|
|
||||||
|
|
||||||
cell10=0x4B
|
|
||||||
|
|
||||||
cell8=0x4F
|
|
||||||
|
|
||||||
cell6=190
|
|
||||||
|
|
||||||
cell4=186
|
|
||||||
|
|
||||||
cell2=219
|
|
||||||
|
|
||||||
cell31=0x41
|
|
||||||
|
|
||||||
cell29=0x57
|
|
||||||
|
|
||||||
cell27=0x58
|
|
||||||
|
|
||||||
cell25=0x44
|
|
||||||
|
|
||||||
cell23=0x52
|
|
||||||
|
|
||||||
cell21=0x56
|
|
||||||
|
|
||||||
cell19=0x47
|
|
||||||
|
|
||||||
cell17=0x59
|
|
||||||
|
|
||||||
cell15=0x4E
|
|
||||||
|
|
||||||
cell13=0x4A
|
|
||||||
|
|
||||||
cell11=0x49
|
|
||||||
|
|
||||||
cell9=188
|
|
||||||
|
|
||||||
cell7=0x4C
|
|
||||||
|
|
||||||
cell5=0x50
|
|
||||||
|
|
||||||
cell3=191
|
|
||||||
|
|
||||||
cell1=222
|
|
||||||
".to_owned()
|
".to_owned()
|
||||||
}
|
}
|
||||||
}
|
}
|
120
rust/src/modules/keyboard.rs
Normal file
120
rust/src/modules/keyboard.rs
Normal file
@ -0,0 +1,120 @@
|
|||||||
|
use ini::Ini;
|
||||||
|
use anyhow::Result;
|
||||||
|
use crate::model::profile::Keyboard;
|
||||||
|
|
||||||
|
macro_rules! parse_int_field {
|
||||||
|
($section:expr,$sgt:expr,$sl:expr) => {
|
||||||
|
if let Some(field) = $section.get($sgt) {
|
||||||
|
let field = &field[0..field.chars().position(|c| c == ';').unwrap_or(field.len())].trim();
|
||||||
|
log::debug!("loading {}={}", $sgt, field);
|
||||||
|
|
||||||
|
let res = if field.starts_with("0x") {
|
||||||
|
i32::from_str_radix(&field.trim()[2..], 16)
|
||||||
|
} else {
|
||||||
|
field.trim().parse::<i32>()
|
||||||
|
};
|
||||||
|
|
||||||
|
match res {
|
||||||
|
Ok(v) => $sl = v,
|
||||||
|
Err(e) => log::warn!("unable to read a segatools.ini field key={} value={}: {:?}", $sgt, field.trim(), e)
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
log::debug!("unable to load {}", $sgt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Keyboard {
|
||||||
|
pub fn load_from_ini(&mut self, ini: &Ini) -> Result<()> {
|
||||||
|
log::debug!("loading kb");
|
||||||
|
match self {
|
||||||
|
Keyboard::Ongeki(kb) => {
|
||||||
|
if let Some(s) = ini.section(Some("io4")) {
|
||||||
|
parse_int_field!(s, "test", kb.test);
|
||||||
|
parse_int_field!(s, "service", kb.svc);
|
||||||
|
parse_int_field!(s, "coin", kb.coin);
|
||||||
|
parse_int_field!(s, "left1", kb.l1);
|
||||||
|
parse_int_field!(s, "left2", kb.l2);
|
||||||
|
parse_int_field!(s, "left3", kb.l3);
|
||||||
|
parse_int_field!(s, "right1", kb.r1);
|
||||||
|
parse_int_field!(s, "right2", kb.r2);
|
||||||
|
parse_int_field!(s, "right3", kb.r3);
|
||||||
|
parse_int_field!(s, "leftMenu", kb.lmenu);
|
||||||
|
parse_int_field!(s, "rightMenu", kb.rmenu);
|
||||||
|
parse_int_field!(s, "leftSide", kb.lwad);
|
||||||
|
parse_int_field!(s, "rightSide", kb.rwad);
|
||||||
|
|
||||||
|
let mut mouse: i32 = 1;
|
||||||
|
parse_int_field!(s, "mouse", mouse);
|
||||||
|
kb.use_mouse = if mouse == 1 { true } else { false };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Keyboard::Chunithm(kb) => {
|
||||||
|
if let Some(s) = ini.section(Some("io3")) {
|
||||||
|
parse_int_field!(s, "test", kb.test);
|
||||||
|
parse_int_field!(s, "service", kb.svc);
|
||||||
|
parse_int_field!(s, "coin", kb.coin);
|
||||||
|
|
||||||
|
let mut ir: i32 = 1;
|
||||||
|
parse_int_field!(s, "ir", ir);
|
||||||
|
kb.split_ir = if ir == 0 { true } else { false };
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(s) = ini.section(Some("slider")) {
|
||||||
|
for i in 0..kb.cell.len() {
|
||||||
|
parse_int_field!(s, format!("cell{}", i + 1), kb.cell[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(s) = ini.section(Some("ir")) {
|
||||||
|
for i in 0..kb.ir.len() {
|
||||||
|
parse_int_field!(s, format!("ir{}", i + 1), kb.ir[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is assumed to run in sync after the segatools module
|
||||||
|
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" });
|
||||||
|
}
|
||||||
|
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.split_ir {
|
||||||
|
for (i, ir) in kb.ir.iter().enumerate() {
|
||||||
|
ini.with_section(Some("ir")).set(format!("ir{}", i + 1), ir.to_string());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ini.with_section(Some("io3")).set("ir", kb.ir[0].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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
@ -3,6 +3,7 @@ pub mod segatools;
|
|||||||
pub mod network;
|
pub mod network;
|
||||||
pub mod bepinex;
|
pub mod bepinex;
|
||||||
pub mod mu3ini;
|
pub mod mu3ini;
|
||||||
|
pub mod keyboard;
|
||||||
|
|
||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_os = "windows")]
|
||||||
pub mod display_windows;
|
pub mod display_windows;
|
@ -5,6 +5,32 @@ use ini::Ini;
|
|||||||
use crate::model::profile::{Network, NetworkType};
|
use crate::model::profile::{Network, NetworkType};
|
||||||
|
|
||||||
impl Network {
|
impl Network {
|
||||||
|
pub fn load_from_ini(&mut self, ini: &Ini) -> Result<()> {
|
||||||
|
log::debug!("loading network");
|
||||||
|
if let Some(s) = ini.section(Some("dns")) {
|
||||||
|
if let Some(default) = s.get("default") {
|
||||||
|
if default.starts_with("192.") || default.starts_with("127.") {
|
||||||
|
self.network_type = NetworkType::Artemis;
|
||||||
|
} else {
|
||||||
|
self.network_type = NetworkType::Remote;
|
||||||
|
self.remote_address = default.to_owned();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(s) = ini.section(Some("netenv")) {
|
||||||
|
s.get("addrSuffix").map(|v|
|
||||||
|
self.suffix = v.parse::<i32>().ok()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(s) = ini.section(Some("keychip")) {
|
||||||
|
s.get("subnet").map(|v| self.subnet = v.to_owned());
|
||||||
|
s.get("id").map(|v| self.keychip = v.to_owned());
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
pub fn line_up(&self, ini: &mut Ini) -> Result<()> {
|
pub fn line_up(&self, ini: &mut Ini) -> Result<()> {
|
||||||
log::debug!("begin line-up: network");
|
log::debug!("begin line-up: network");
|
||||||
|
|
||||||
|
@ -31,6 +31,14 @@ impl Segatools {
|
|||||||
_ => {},
|
_ => {},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
pub fn load_from_ini(&mut self, ini: &Ini) {
|
||||||
|
log::debug!("loading sgt");
|
||||||
|
if let Some(s) = ini.section(Some("vfs")) {
|
||||||
|
s.get("amfs").map(|v| self.amfs = PathBuf::from(v));
|
||||||
|
s.get("appdata").map(|v| self.appdata = PathBuf::from(v));
|
||||||
|
s.get("option").map(|v| self.option = PathBuf::from(v));
|
||||||
|
}
|
||||||
|
}
|
||||||
pub async fn line_up(&self, p: &impl ProfilePaths, game: Game) -> Result<Ini> {
|
pub async fn line_up(&self, p: &impl ProfilePaths, game: Game) -> Result<Ini> {
|
||||||
log::debug!("begin line-up: segatools");
|
log::debug!("begin line-up: segatools");
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use tauri::AppHandle;
|
use tauri::AppHandle;
|
||||||
use std::{collections::BTreeSet, path::{Path, PathBuf}};
|
use std::{collections::BTreeSet, path::{Path, PathBuf}};
|
||||||
use crate::{model::{misc::Game, profile::{Aime, Mu3Ini, ProfileModule}}, modules::package::prepare_packages, pkg::PkgKey, pkg_store::PackageStore, util};
|
use crate::{model::{misc::Game, profile::{Aime, ChunithmKeyboard, Keyboard, Mu3Ini, OngekiKeyboard, ProfileModule}}, modules::package::prepare_packages, pkg::PkgKey, pkg_store::PackageStore, util};
|
||||||
use tauri::Emitter;
|
use tauri::Emitter;
|
||||||
use std::process::Stdio;
|
use std::process::Stdio;
|
||||||
use crate::model::profile::BepInEx;
|
use crate::model::profile::BepInEx;
|
||||||
@ -54,7 +54,10 @@ pub struct ProfileData {
|
|||||||
pub wine: crate::model::profile::Wine,
|
pub wine: crate::model::profile::Wine,
|
||||||
|
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub mu3_ini: Option<Mu3Ini>
|
pub mu3_ini: Option<Mu3Ini>,
|
||||||
|
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub keyboard: Option<Keyboard>
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Profile {
|
impl Profile {
|
||||||
@ -74,6 +77,12 @@ impl Profile {
|
|||||||
#[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 { audio: None, blacklist: None }) } else { None },
|
||||||
|
keyboard:
|
||||||
|
if meta.game == Game::Ongeki {
|
||||||
|
Some(Keyboard::Ongeki(OngekiKeyboard::default()))
|
||||||
|
} else {
|
||||||
|
Some(Keyboard::Chunithm(ChunithmKeyboard::default()))
|
||||||
|
},
|
||||||
},
|
},
|
||||||
meta: meta.clone()
|
meta: meta.clone()
|
||||||
};
|
};
|
||||||
@ -87,11 +96,18 @@ impl Profile {
|
|||||||
pub fn load(game: Game, name: String) -> Result<Self> {
|
pub fn load(game: Game, name: String) -> Result<Self> {
|
||||||
let path = util::profile_config_dir(game, &name).join("profile.json");
|
let path = util::profile_config_dir(game, &name).join("profile.json");
|
||||||
if let Ok(s) = std::fs::read_to_string(&path) {
|
if let Ok(s) = std::fs::read_to_string(&path) {
|
||||||
let data = serde_json::from_str::<ProfileData>(&s)
|
let mut data = serde_json::from_str::<ProfileData>(&s)
|
||||||
.map_err(|e| anyhow!("Unable to parse {:?}: {:?}", path, e))?;
|
.map_err(|e| anyhow!("Unable to parse {:?}: {:?}", path, e))?;
|
||||||
|
|
||||||
log::debug!("{:?}", data);
|
log::debug!("{:?}", data);
|
||||||
|
|
||||||
|
if game == Game::Ongeki && data.keyboard.is_none() {
|
||||||
|
data.keyboard = Some(Keyboard::Ongeki(OngekiKeyboard::default()));
|
||||||
|
}
|
||||||
|
if game == Game::Chunithm && data.keyboard.is_none() {
|
||||||
|
data.keyboard = Some(Keyboard::Chunithm(ChunithmKeyboard::default()));
|
||||||
|
}
|
||||||
|
|
||||||
Ok(Profile {
|
Ok(Profile {
|
||||||
meta: ProfileMeta {
|
meta: ProfileMeta {
|
||||||
game, name
|
game, name
|
||||||
@ -163,6 +179,10 @@ impl Profile {
|
|||||||
if self.meta.game.has_module(ProfileModule::Mu3Ini) && source.mu3_ini.is_some() {
|
if self.meta.game.has_module(ProfileModule::Mu3Ini) && source.mu3_ini.is_some() {
|
||||||
self.data.mu3_ini = source.mu3_ini;
|
self.data.mu3_ini = source.mu3_ini;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if self.meta.game.has_module(ProfileModule::Keyboard) && source.keyboard.is_some() {
|
||||||
|
self.data.keyboard = source.keyboard;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
pub async fn line_up(&self, pkg_hash: String, refresh: bool, _app: AppHandle) -> Result<()> {
|
pub async fn line_up(&self, pkg_hash: String, refresh: bool, _app: AppHandle) -> Result<()> {
|
||||||
let info = match &self.data.display {
|
let info = match &self.data.display {
|
||||||
@ -200,6 +220,10 @@ impl Profile {
|
|||||||
.map_err(|e| anyhow!("segatools configuration failed:\n{:?}", e))?;
|
.map_err(|e| anyhow!("segatools configuration failed:\n{:?}", e))?;
|
||||||
self.data.network.line_up(&mut ini)?;
|
self.data.network.line_up(&mut ini)?;
|
||||||
|
|
||||||
|
if let Some(keyboard) = &self.data.keyboard {
|
||||||
|
keyboard.line_up(&mut ini)?;
|
||||||
|
}
|
||||||
|
|
||||||
ini.write_to_file(self.data_dir().join("segatools.ini"))
|
ini.write_to_file(self.data_dir().join("segatools.ini"))
|
||||||
.map_err(|e| anyhow!("Error writing segatools.ini: {}", e))?;
|
.map_err(|e| anyhow!("Error writing segatools.ini: {}", e))?;
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://schema.tauri.app/config/2",
|
"$schema": "https://schema.tauri.app/config/2",
|
||||||
"productName": "STARTLINER",
|
"productName": "STARTLINER",
|
||||||
"version": "0.4.0",
|
"version": "0.5.0",
|
||||||
"identifier": "zip.patafour.startliner",
|
"identifier": "zip.patafour.startliner",
|
||||||
"build": {
|
"build": {
|
||||||
"beforeDevCommand": "bun run dev",
|
"beforeDevCommand": "bun run dev",
|
||||||
|
@ -1,9 +1,11 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { Ref, computed, onMounted, ref } from 'vue';
|
import { Ref, computed, onMounted, ref } from 'vue';
|
||||||
import Button from 'primevue/button';
|
import Button from 'primevue/button';
|
||||||
|
import ConfirmDialog from 'primevue/confirmdialog';
|
||||||
import Dialog from 'primevue/dialog';
|
import Dialog from 'primevue/dialog';
|
||||||
import InputIcon from 'primevue/inputicon';
|
import InputIcon from 'primevue/inputicon';
|
||||||
import InputText from 'primevue/inputtext';
|
import InputText from 'primevue/inputtext';
|
||||||
|
import ScrollPanel from 'primevue/scrollpanel';
|
||||||
import Tab from 'primevue/tab';
|
import Tab from 'primevue/tab';
|
||||||
import TabList from 'primevue/tablist';
|
import TabList from 'primevue/tablist';
|
||||||
import TabPanel from 'primevue/tabpanel';
|
import TabPanel from 'primevue/tabpanel';
|
||||||
@ -23,6 +25,7 @@ import {
|
|||||||
usePrfStore,
|
usePrfStore,
|
||||||
} from '../stores';
|
} from '../stores';
|
||||||
import { Dirs } from '../types';
|
import { Dirs } from '../types';
|
||||||
|
import { messageSplit } from '../util';
|
||||||
|
|
||||||
const pkg = usePkgStore();
|
const pkg = usePkgStore();
|
||||||
const prf = usePrfStore();
|
const prf = usePrfStore();
|
||||||
@ -86,6 +89,23 @@ listen<{ message: string; header: string }>('invoke-error', (event) => {
|
|||||||
: 'main-scale-xl'
|
: 'main-scale-xl'
|
||||||
"
|
"
|
||||||
>
|
>
|
||||||
|
<ConfirmDialog>
|
||||||
|
<template #message="{ message }">
|
||||||
|
<ScrollPanel
|
||||||
|
v-if="messageSplit(message).length > 5"
|
||||||
|
style="width: 100%; height: 40vh"
|
||||||
|
>
|
||||||
|
<p v-for="m in messageSplit(message)">
|
||||||
|
{{ m }}
|
||||||
|
</p></ScrollPanel
|
||||||
|
>
|
||||||
|
<div v-else>
|
||||||
|
<p v-for="m in messageSplit(message)">
|
||||||
|
{{ m }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</ConfirmDialog>
|
||||||
<Dialog
|
<Dialog
|
||||||
modal
|
modal
|
||||||
:visible="errorVisible"
|
:visible="errorVisible"
|
||||||
|
245
src/components/KeyboardKey.vue
Normal file
245
src/components/KeyboardKey.vue
Normal file
@ -0,0 +1,245 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { computed, ref } from 'vue';
|
||||||
|
import InputText from 'primevue/inputtext';
|
||||||
|
import { usePrfStore } from '../stores';
|
||||||
|
import { OngekiButtons } from '../types';
|
||||||
|
|
||||||
|
const prf = usePrfStore();
|
||||||
|
|
||||||
|
const hasClickedM1Once = ref(false);
|
||||||
|
|
||||||
|
const handleKey = (
|
||||||
|
button: string | undefined,
|
||||||
|
event: KeyboardEvent,
|
||||||
|
index?: number
|
||||||
|
) => {
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
const keycode = toKeycode(event.code);
|
||||||
|
if (keycode !== null && button !== undefined) {
|
||||||
|
const data = prf.current!.data.keyboard!.data as any;
|
||||||
|
if (index !== undefined) {
|
||||||
|
data[button][index] = keycode;
|
||||||
|
} else {
|
||||||
|
data[button] = keycode;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleMouse = (
|
||||||
|
button: string | undefined,
|
||||||
|
event: MouseEvent,
|
||||||
|
index?: number
|
||||||
|
) => {
|
||||||
|
if (button === undefined || button == 'use_mouse') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.button === 0) {
|
||||||
|
if (hasClickedM1Once.value === false) {
|
||||||
|
hasClickedM1Once.value = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
event.preventDefault();
|
||||||
|
}
|
||||||
|
|
||||||
|
let keycode;
|
||||||
|
switch (event.button) {
|
||||||
|
case 0:
|
||||||
|
keycode = 1;
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
keycode = 4;
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
keycode = 2;
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
keycode = 5;
|
||||||
|
break;
|
||||||
|
case 4:
|
||||||
|
keycode = 6;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (keycode !== undefined) {
|
||||||
|
const data = prf.current!.data.keyboard!.data as any;
|
||||||
|
|
||||||
|
if (index !== undefined) {
|
||||||
|
data[button][index] = keycode;
|
||||||
|
} else {
|
||||||
|
data[button] = keycode;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getKey = (key: keyof OngekiButtons, index?: number) =>
|
||||||
|
computed(() => {
|
||||||
|
const data = prf.current!.data.keyboard?.data as any;
|
||||||
|
const keycode =
|
||||||
|
index === undefined
|
||||||
|
? (data[key] as number | undefined)
|
||||||
|
: (data[key]?.[index] as number | undefined);
|
||||||
|
return keycode && fromKeycode(keycode) ? fromKeycode(keycode) : '–';
|
||||||
|
});
|
||||||
|
|
||||||
|
const KEY_MAP: { [key: number]: string } = {
|
||||||
|
1: 'M1',
|
||||||
|
2: 'M2',
|
||||||
|
4: 'M3',
|
||||||
|
5: 'M4',
|
||||||
|
6: 'M5',
|
||||||
|
8: 'Backspace',
|
||||||
|
9: 'Tab',
|
||||||
|
13: 'Enter',
|
||||||
|
19: 'Pause',
|
||||||
|
20: 'CapsLock',
|
||||||
|
27: 'Escape',
|
||||||
|
32: 'Space',
|
||||||
|
33: 'PageUp',
|
||||||
|
34: 'PageDown',
|
||||||
|
35: 'End',
|
||||||
|
36: 'Home',
|
||||||
|
37: 'ArrowLeft',
|
||||||
|
38: 'ArrowUp',
|
||||||
|
39: 'ArrowRight',
|
||||||
|
40: 'ArrowDown',
|
||||||
|
45: 'Insert',
|
||||||
|
46: 'Delete',
|
||||||
|
48: 'Digit0',
|
||||||
|
49: 'Digit1',
|
||||||
|
50: 'Digit2',
|
||||||
|
51: 'Digit3',
|
||||||
|
52: 'Digit4',
|
||||||
|
53: 'Digit5',
|
||||||
|
54: 'Digit6',
|
||||||
|
55: 'Digit7',
|
||||||
|
56: 'Digit8',
|
||||||
|
57: 'Digit9',
|
||||||
|
65: 'KeyA',
|
||||||
|
66: 'KeyB',
|
||||||
|
67: 'KeyC',
|
||||||
|
68: 'KeyD',
|
||||||
|
69: 'KeyE',
|
||||||
|
70: 'KeyF',
|
||||||
|
71: 'KeyG',
|
||||||
|
72: 'KeyH',
|
||||||
|
73: 'KeyI',
|
||||||
|
74: 'KeyJ',
|
||||||
|
75: 'KeyK',
|
||||||
|
76: 'KeyL',
|
||||||
|
77: 'KeyM',
|
||||||
|
78: 'KeyN',
|
||||||
|
79: 'KeyO',
|
||||||
|
80: 'KeyP',
|
||||||
|
81: 'KeyQ',
|
||||||
|
82: 'KeyR',
|
||||||
|
83: 'KeyS',
|
||||||
|
84: 'KeyT',
|
||||||
|
85: 'KeyU',
|
||||||
|
86: 'KeyV',
|
||||||
|
87: 'KeyW',
|
||||||
|
88: 'KeyX',
|
||||||
|
89: 'KeyY',
|
||||||
|
90: 'KeyZ',
|
||||||
|
91: 'MetaLeft',
|
||||||
|
92: 'MetaRight',
|
||||||
|
93: 'ContextMenu',
|
||||||
|
96: 'Numpad0',
|
||||||
|
97: 'Numpad1',
|
||||||
|
98: 'Numpad2',
|
||||||
|
99: 'Numpad3',
|
||||||
|
100: 'Numpad4',
|
||||||
|
101: 'Numpad5',
|
||||||
|
102: 'Numpad6',
|
||||||
|
103: 'Numpad7',
|
||||||
|
104: 'Numpad8',
|
||||||
|
105: 'Numpad9',
|
||||||
|
106: 'NumpadMultiply',
|
||||||
|
107: 'NumpadAdd',
|
||||||
|
109: 'NumpadSubtract',
|
||||||
|
110: 'NumpadDecimal',
|
||||||
|
111: 'NumpadDivide',
|
||||||
|
112: 'F1',
|
||||||
|
113: 'F2',
|
||||||
|
114: 'F3',
|
||||||
|
115: 'F4',
|
||||||
|
116: 'F5',
|
||||||
|
117: 'F6',
|
||||||
|
118: 'F7',
|
||||||
|
119: 'F8',
|
||||||
|
120: 'F9',
|
||||||
|
121: 'F10',
|
||||||
|
122: 'F11',
|
||||||
|
123: 'F12',
|
||||||
|
144: 'NumLock',
|
||||||
|
145: 'ScrollLock',
|
||||||
|
160: 'ShiftLeft',
|
||||||
|
161: 'ShiftRight',
|
||||||
|
162: 'ControlLeft',
|
||||||
|
163: 'ControlRight',
|
||||||
|
164: 'AltLeft',
|
||||||
|
165: 'AltRight',
|
||||||
|
186: 'Semicolon',
|
||||||
|
187: 'Equal',
|
||||||
|
188: 'Comma',
|
||||||
|
189: 'Minus',
|
||||||
|
190: 'Period',
|
||||||
|
191: 'Slash',
|
||||||
|
192: 'Backquote',
|
||||||
|
219: 'BracketLeft',
|
||||||
|
220: 'Backslash',
|
||||||
|
221: 'BracketRight',
|
||||||
|
222: 'Quote',
|
||||||
|
};
|
||||||
|
|
||||||
|
const fromKeycode = (keyCode: number): string | null => {
|
||||||
|
return KEY_MAP[keyCode] ?? null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const toKeycode = (key: string): number | null => {
|
||||||
|
const res = Object.entries(KEY_MAP).find(([_, v]) => v === key)?.[0];
|
||||||
|
return res ? parseInt(res) : null;
|
||||||
|
};
|
||||||
|
|
||||||
|
defineProps({
|
||||||
|
small: Boolean,
|
||||||
|
verySmall: Boolean,
|
||||||
|
tall: Boolean,
|
||||||
|
tooltip: String,
|
||||||
|
button: String,
|
||||||
|
color: String,
|
||||||
|
index: Number,
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<InputText
|
||||||
|
:style="{
|
||||||
|
width: small ? '3em' : '5em',
|
||||||
|
height: small ? '3em' : tall ? '10em' : '5em',
|
||||||
|
fontSize: small ? '0.9em' : '1em',
|
||||||
|
backgroundColor: color,
|
||||||
|
}"
|
||||||
|
unstyled
|
||||||
|
class="text-center buttoninputtext"
|
||||||
|
v-tooltip="tooltip"
|
||||||
|
@contextmenu.prevent="() => {}"
|
||||||
|
@keydown="(ev: KeyboardEvent) => handleKey(button, ev, index)"
|
||||||
|
@mousedown="
|
||||||
|
(ev: MouseEvent) =>
|
||||||
|
handleMouse(button as keyof OngekiButtons, ev, index)
|
||||||
|
"
|
||||||
|
@focusout="() => (hasClickedM1Once = false)"
|
||||||
|
:model-value="getKey(button as keyof OngekiButtons, index) as any"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped lang="css">
|
||||||
|
.buttoninputtext {
|
||||||
|
border-radius: 6px;
|
||||||
|
border: 1px solid rgba(200, 200, 200, 0.3);
|
||||||
|
}
|
||||||
|
</style>
|
@ -6,6 +6,7 @@ const general = useGeneralStore();
|
|||||||
|
|
||||||
defineProps({
|
defineProps({
|
||||||
title: String,
|
title: String,
|
||||||
|
collapsed: Boolean,
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@ -14,6 +15,7 @@ defineProps({
|
|||||||
:legend="title"
|
:legend="title"
|
||||||
:toggleable="true"
|
:toggleable="true"
|
||||||
v-show="general.cfgCategories.has(title ?? '')"
|
v-show="general.cfgCategories.has(title ?? '')"
|
||||||
|
:collapsed="collapsed"
|
||||||
>
|
>
|
||||||
<div class="flex w-full flex-col gap-1">
|
<div class="flex w-full flex-col gap-1">
|
||||||
<slot />
|
<slot />
|
||||||
|
@ -7,6 +7,7 @@ import OptionCategory from './OptionCategory.vue';
|
|||||||
import OptionRow from './OptionRow.vue';
|
import OptionRow from './OptionRow.vue';
|
||||||
import AimeOptions from './options/Aime.vue';
|
import AimeOptions from './options/Aime.vue';
|
||||||
import DisplayOptions from './options/Display.vue';
|
import DisplayOptions from './options/Display.vue';
|
||||||
|
import KeyboardOptions from './options/Keyboard.vue';
|
||||||
import MiscOptions from './options/Misc.vue';
|
import MiscOptions from './options/Misc.vue';
|
||||||
import NetworkOptions from './options/Network.vue';
|
import NetworkOptions from './options/Network.vue';
|
||||||
import SegatoolsOptions from './options/Segatools.vue';
|
import SegatoolsOptions from './options/Segatools.vue';
|
||||||
@ -129,6 +130,7 @@ prf.reload();
|
|||||||
v-model="blacklistMaxModel"
|
v-model="blacklistMaxModel"
|
||||||
/></OptionRow> -->
|
/></OptionRow> -->
|
||||||
</OptionCategory>
|
</OptionCategory>
|
||||||
|
<KeyboardOptions />
|
||||||
<StartlinerOptions />
|
<StartlinerOptions />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -1,9 +1,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { Ref, computed, ref } from 'vue';
|
import { Ref, computed, ref } from 'vue';
|
||||||
import Button from 'primevue/button';
|
import Button from 'primevue/button';
|
||||||
import ConfirmDialog from 'primevue/confirmdialog';
|
|
||||||
import ContextMenu from 'primevue/contextmenu';
|
import ContextMenu from 'primevue/contextmenu';
|
||||||
import ScrollPanel from 'primevue/scrollpanel';
|
|
||||||
import { useConfirm } from 'primevue/useconfirm';
|
import { useConfirm } from 'primevue/useconfirm';
|
||||||
import { listen } from '@tauri-apps/api/event';
|
import { listen } from '@tauri-apps/api/event';
|
||||||
import { getCurrentWindow } from '@tauri-apps/api/window';
|
import { getCurrentWindow } from '@tauri-apps/api/window';
|
||||||
@ -38,6 +36,8 @@ const startline = async (force: boolean, refresh: boolean) => {
|
|||||||
confirmDialog.require({
|
confirmDialog.require({
|
||||||
message: message.join('\n'),
|
message: message.join('\n'),
|
||||||
header: 'Start check failed',
|
header: 'Start check failed',
|
||||||
|
acceptLabel: 'Run anyway',
|
||||||
|
rejectLabel: 'Cancel',
|
||||||
accept: () => {
|
accept: () => {
|
||||||
startline(true, refresh);
|
startline(true, refresh);
|
||||||
},
|
},
|
||||||
@ -85,10 +85,6 @@ listen('launch-end', () => {
|
|||||||
getCurrentWindow().setFocus();
|
getCurrentWindow().setFocus();
|
||||||
});
|
});
|
||||||
|
|
||||||
const messageSplit = (message: any) => {
|
|
||||||
return message.message?.split('\n');
|
|
||||||
};
|
|
||||||
|
|
||||||
const menuItems = [
|
const menuItems = [
|
||||||
{
|
{
|
||||||
label: 'Refresh and start',
|
label: 'Refresh and start',
|
||||||
@ -111,45 +107,6 @@ const showContextMenu = (event: Event) => {
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<ContextMenu ref="menu" :model="menuItems" />
|
<ContextMenu ref="menu" :model="menuItems" />
|
||||||
<ConfirmDialog>
|
|
||||||
<template #container="{ message, acceptCallback, rejectCallback }">
|
|
||||||
<div
|
|
||||||
class="flex flex-col p-8 bg-surface-0 dark:bg-surface-900 rounded"
|
|
||||||
>
|
|
||||||
<span class="font-bold self-center text-2xl block mb-2 mt-2">{{
|
|
||||||
message.header
|
|
||||||
}}</span>
|
|
||||||
<ScrollPanel
|
|
||||||
v-if="messageSplit(message).length > 5"
|
|
||||||
style="width: 100%; height: 40vh"
|
|
||||||
>
|
|
||||||
<p v-for="m in messageSplit(message)">
|
|
||||||
{{ m }}
|
|
||||||
</p></ScrollPanel
|
|
||||||
>
|
|
||||||
<div v-else>
|
|
||||||
<p v-for="m in messageSplit(message)">
|
|
||||||
{{ m }}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div class="flex self-center items-center gap-2 mt-6">
|
|
||||||
<Button
|
|
||||||
label="Run anyway"
|
|
||||||
@click="acceptCallback"
|
|
||||||
size="small"
|
|
||||||
class="w-32"
|
|
||||||
></Button>
|
|
||||||
<Button
|
|
||||||
label="Cancel"
|
|
||||||
outlined
|
|
||||||
size="small"
|
|
||||||
@click="rejectCallback"
|
|
||||||
class="w-32"
|
|
||||||
></Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</ConfirmDialog>
|
|
||||||
<Button
|
<Button
|
||||||
v-if="startStatus === 'ready'"
|
v-if="startStatus === 'ready'"
|
||||||
v-tooltip="disabledTooltip"
|
v-tooltip="disabledTooltip"
|
||||||
|
177
src/components/options/Keyboard.vue
Normal file
177
src/components/options/Keyboard.vue
Normal file
@ -0,0 +1,177 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import SelectButton from 'primevue/selectbutton';
|
||||||
|
import ToggleSwitch from 'primevue/toggleswitch';
|
||||||
|
import KeyboardKey from '../KeyboardKey.vue';
|
||||||
|
import OptionCategory from '../OptionCategory.vue';
|
||||||
|
import OptionRow from '../OptionRow.vue';
|
||||||
|
import { usePrfStore } from '../../stores';
|
||||||
|
import { ChunithmButtons } from '@/types';
|
||||||
|
|
||||||
|
ToggleSwitch;
|
||||||
|
|
||||||
|
const prf = usePrfStore();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<OptionCategory title="Keyboard">
|
||||||
|
<OptionRow
|
||||||
|
title="Lever mode"
|
||||||
|
v-if="prf.current!.data.keyboard!.game === 'Ongeki'"
|
||||||
|
>
|
||||||
|
<SelectButton
|
||||||
|
v-model="prf.current!.data.keyboard!.data.use_mouse"
|
||||||
|
:options="[
|
||||||
|
{ title: 'XInput', value: false },
|
||||||
|
{ title: 'Mouse', value: true },
|
||||||
|
]"
|
||||||
|
:allow-empty="false"
|
||||||
|
option-label="title"
|
||||||
|
option-value="value"
|
||||||
|
/>
|
||||||
|
</OptionRow>
|
||||||
|
<OptionRow
|
||||||
|
title="Enable multiple IRs"
|
||||||
|
v-if="prf.current!.data.keyboard!.game === 'Chunithm'"
|
||||||
|
>
|
||||||
|
<ToggleSwitch v-model="prf.current!.data.keyboard!.data.split_ir" />
|
||||||
|
</OptionRow>
|
||||||
|
<div
|
||||||
|
:style="`position: relative; height: ${prf.current!.data.keyboard!.game === 'Ongeki' ? 400 : 250}px`"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="absolute left-1/6 top-1/10"
|
||||||
|
style="transform: translateX(-30%) translateY(-50%)"
|
||||||
|
>
|
||||||
|
<div class="flex flex-row gap-2 self-center w-full">
|
||||||
|
<KeyboardKey button="test" small tooltip="Test" />
|
||||||
|
<KeyboardKey button="svc" small tooltip="Service" />
|
||||||
|
<KeyboardKey button="coin" small tooltip="Coin" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-if="prf.current?.meta.game === 'ongeki'">
|
||||||
|
<div
|
||||||
|
class="absolute left-1/2 top-1/2"
|
||||||
|
style="transform: translateX(-540%) translateY(-200%)"
|
||||||
|
>
|
||||||
|
<KeyboardKey
|
||||||
|
button="lmenu"
|
||||||
|
small
|
||||||
|
color="rgba(255, 0, 0, 0.2)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="absolute right-1/2 top-1/2"
|
||||||
|
style="transform: translateX(540%) translateY(-200%)"
|
||||||
|
>
|
||||||
|
<KeyboardKey
|
||||||
|
button="rmenu"
|
||||||
|
small
|
||||||
|
color="rgba(255, 255, 0, 0.2)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="absolute left-1/2 top-1/2"
|
||||||
|
style="transform: translateX(-50%) translateY(-20%)"
|
||||||
|
>
|
||||||
|
<div class="flex flex-row gap-2 self-center w-full">
|
||||||
|
<KeyboardKey
|
||||||
|
button="lwad"
|
||||||
|
tall
|
||||||
|
color="rgba(180, 0, 255, 0.2)"
|
||||||
|
/>
|
||||||
|
<div style="width: 0.7em"></div>
|
||||||
|
<KeyboardKey button="l1" color="rgba(255, 0, 0, 0.2)" />
|
||||||
|
<KeyboardKey button="l2" color="rgba(0, 255, 0, 0.2)" />
|
||||||
|
<KeyboardKey button="l3" color="rgba(0, 0, 255, 0.2)" />
|
||||||
|
<div style="width: 0.7em"></div>
|
||||||
|
<KeyboardKey button="r1" color="rgba(255, 0, 0, 0.2)" />
|
||||||
|
<KeyboardKey button="r2" color="rgba(0, 255, 0, 0.2)" />
|
||||||
|
<KeyboardKey button="r3" color="rgba(0, 0, 255, 0.2)" />
|
||||||
|
<div style="width: 0.7em"></div>
|
||||||
|
<KeyboardKey
|
||||||
|
button="rwad"
|
||||||
|
tall
|
||||||
|
color="rgba(255, 0, 180, 0.2)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-if="prf.current?.meta.game === 'chunithm'">
|
||||||
|
<div class="absolute left-1/2 top-1/5">
|
||||||
|
<div
|
||||||
|
class="flex flex-row flex-nowrap gap-2 self-center w-full"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
v-if="
|
||||||
|
(
|
||||||
|
prf.current!.data.keyboard!
|
||||||
|
.data as ChunithmButtons
|
||||||
|
).split_ir
|
||||||
|
"
|
||||||
|
v-for="idx in Array(6)
|
||||||
|
.fill(0)
|
||||||
|
.map((_, i) => i + 1)"
|
||||||
|
>
|
||||||
|
<KeyboardKey
|
||||||
|
button="ir"
|
||||||
|
:index="idx - 1"
|
||||||
|
:tooltip="`ir${idx}`"
|
||||||
|
small
|
||||||
|
color="rgba(0, 255, 0, 0.2)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div v-else>
|
||||||
|
<KeyboardKey
|
||||||
|
button="ir"
|
||||||
|
:index="0"
|
||||||
|
:tooltip="`ir0`"
|
||||||
|
small
|
||||||
|
color="rgba(0, 255, 0, 0.2)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="absolute left-1/2 top-1/2"
|
||||||
|
style="transform: translateX(-50%) translateY(-5%)"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="flex flex-row flex-nowrap gap-2 self-center w-full"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
v-for="idx in Array(16)
|
||||||
|
.fill(0)
|
||||||
|
.map((_, i) => 16 - i)"
|
||||||
|
>
|
||||||
|
<KeyboardKey
|
||||||
|
button="cell"
|
||||||
|
:index="idx - 1"
|
||||||
|
:tooltip="`cell${idx}`"
|
||||||
|
small
|
||||||
|
color="rgba(255, 255, 0, 0.2)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div style="height: 0.6em"></div>
|
||||||
|
<div
|
||||||
|
class="flex flex-row flex-nowrap gap-2 self-center w-full"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
v-for="idx in Array(16)
|
||||||
|
.fill(0)
|
||||||
|
.map((_, i) => 32 - i)"
|
||||||
|
>
|
||||||
|
<KeyboardKey
|
||||||
|
button="cell"
|
||||||
|
:index="idx - 1"
|
||||||
|
:tooltip="`cell${idx}`"
|
||||||
|
small
|
||||||
|
color="rgba(255, 255, 0, 0.2)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</OptionCategory>
|
||||||
|
</template>
|
@ -17,6 +17,7 @@ const prf = usePrfStore();
|
|||||||
title="More segatools options"
|
title="More segatools options"
|
||||||
tooltip="Advanced options not covered by STARTLINER"
|
tooltip="Advanced options not covered by STARTLINER"
|
||||||
>
|
>
|
||||||
|
<!-- <Button icon="pi pi-refresh" size="small" /> -->
|
||||||
<FileEditor filename="segatools-base.ini" />
|
<FileEditor filename="segatools-base.ini" />
|
||||||
</OptionRow>
|
</OptionRow>
|
||||||
</OptionCategory>
|
</OptionCategory>
|
||||||
|
@ -1,15 +1,19 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed } from 'vue';
|
import { computed } from 'vue';
|
||||||
import Select from 'primevue/select';
|
import Select from 'primevue/select';
|
||||||
|
import { useConfirm } from 'primevue/useconfirm';
|
||||||
|
import * as path from '@tauri-apps/api/path';
|
||||||
import FilePicker from '../FilePicker.vue';
|
import FilePicker from '../FilePicker.vue';
|
||||||
import OptionCategory from '../OptionCategory.vue';
|
import OptionCategory from '../OptionCategory.vue';
|
||||||
import OptionRow from '../OptionRow.vue';
|
import OptionRow from '../OptionRow.vue';
|
||||||
|
import { invoke } from '../../invoke';
|
||||||
import { usePkgStore, usePrfStore } from '../../stores';
|
import { usePkgStore, usePrfStore } from '../../stores';
|
||||||
import { Feature } from '../../types';
|
import { Feature } from '../../types';
|
||||||
import { pkgKey } from '../../util';
|
import { pkgKey } from '../../util';
|
||||||
|
|
||||||
const prf = usePrfStore();
|
const prf = usePrfStore();
|
||||||
const pkgs = usePkgStore();
|
const pkgs = usePkgStore();
|
||||||
|
const confirmDialog = useConfirm();
|
||||||
|
|
||||||
const names = computed(() => {
|
const names = computed(() => {
|
||||||
switch (prf.current?.meta.game) {
|
switch (prf.current?.meta.game) {
|
||||||
@ -31,6 +35,20 @@ const names = computed(() => {
|
|||||||
throw new Error('Option tab without a profile');
|
throw new Error('Option tab without a profile');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const checkSegatoolsIni = async (target: string) => {
|
||||||
|
const iniPath = await path.join(target, '../segatools.ini');
|
||||||
|
if (await invoke('file_exists', { path: iniPath })) {
|
||||||
|
confirmDialog.require({
|
||||||
|
message: 'Would you like to load the existing configuration data?',
|
||||||
|
header: 'segatools.ini found',
|
||||||
|
accept: async () => {
|
||||||
|
await invoke('load_segatools_ini', { path: iniPath });
|
||||||
|
await prf.reload();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@ -45,7 +63,10 @@ const names = computed(() => {
|
|||||||
extension="exe"
|
extension="exe"
|
||||||
:value="prf.current!.data.sgt.target"
|
:value="prf.current!.data.sgt.target"
|
||||||
:callback="
|
:callback="
|
||||||
(value: string) => (prf.current!.data.sgt.target = value)
|
(value: string) => (
|
||||||
|
(prf.current!.data.sgt.target = value),
|
||||||
|
checkSegatoolsIni(value)
|
||||||
|
)
|
||||||
"
|
"
|
||||||
></FilePicker>
|
></FilePicker>
|
||||||
</OptionRow>
|
</OptionRow>
|
||||||
|
@ -347,7 +347,7 @@ export const useClientStore = defineStore('client', () => {
|
|||||||
scaleFactor.value = value;
|
scaleFactor.value = value;
|
||||||
|
|
||||||
const window = getCurrentWindow();
|
const window = getCurrentWindow();
|
||||||
const w = Math.floor(scaleValue(value) * 760);
|
const w = Math.floor(scaleValue(value) * 900);
|
||||||
const h = Math.floor(scaleValue(value) * 480);
|
const h = Math.floor(scaleValue(value) * 480);
|
||||||
|
|
||||||
let size = await window.innerSize();
|
let size = await window.innerSize();
|
||||||
|
37
src/types.ts
37
src/types.ts
@ -53,6 +53,7 @@ export interface ProfileData {
|
|||||||
network: NetworkConfig;
|
network: NetworkConfig;
|
||||||
bepinex: BepInExConfig;
|
bepinex: BepInExConfig;
|
||||||
mu3_ini: Mu3IniConfig | undefined;
|
mu3_ini: Mu3IniConfig | undefined;
|
||||||
|
keyboard: KeyboardConfig | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SegatoolsConfig {
|
export interface SegatoolsConfig {
|
||||||
@ -101,6 +102,42 @@ export interface Mu3IniConfig {
|
|||||||
// blacklist?: [number, number];
|
// blacklist?: [number, number];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface OngekiButtons {
|
||||||
|
use_mouse: boolean;
|
||||||
|
coin: number;
|
||||||
|
svc: number;
|
||||||
|
test: number;
|
||||||
|
lmenu: number;
|
||||||
|
rmenu: number;
|
||||||
|
l1: number;
|
||||||
|
l2: number;
|
||||||
|
l3: number;
|
||||||
|
r1: number;
|
||||||
|
r2: number;
|
||||||
|
r3: number;
|
||||||
|
lwad: number;
|
||||||
|
rwad: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ChunithmButtons {
|
||||||
|
split_ir: boolean;
|
||||||
|
coin: number;
|
||||||
|
svc: number;
|
||||||
|
test: number;
|
||||||
|
cell: number[];
|
||||||
|
ir: number[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export type KeyboardConfig =
|
||||||
|
| {
|
||||||
|
game: 'Ongeki';
|
||||||
|
data: OngekiButtons;
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
game: 'Chunithm';
|
||||||
|
data: ChunithmButtons;
|
||||||
|
};
|
||||||
|
|
||||||
export interface Profile {
|
export interface Profile {
|
||||||
meta: ProfileMeta;
|
meta: ProfileMeta;
|
||||||
data: ProfileData;
|
data: ProfileData;
|
||||||
|
@ -55,3 +55,7 @@ export const hasFeature = (pkg: Package | undefined, feature: Feature) => {
|
|||||||
pkg.loc.status.OK & feature
|
pkg.loc.status.OK & feature
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const messageSplit = (message: any) => {
|
||||||
|
return message.message?.split('\n');
|
||||||
|
};
|
||||||
|
Reference in New Issue
Block a user