diff --git a/rust/Cargo.lock b/rust/Cargo.lock index 9f7c0dc..18b0eeb 100644 --- a/rust/Cargo.lock +++ b/rust/Cargo.lock @@ -4567,6 +4567,7 @@ dependencies = [ "derive_more 2.0.1", "directories", "displayz", + "enumflags2", "flate2", "futures", "junction", diff --git a/rust/Cargo.toml b/rust/Cargo.toml index 69720f1..d53f2a3 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -41,6 +41,7 @@ derive_more = { version = "2.0.1", features = ["display"] } junction = "1.2.0" tauri-plugin-fs = "2" yaml-rust2 = "0.10.0" +enumflags2 = { version = "0.7.11", features = ["serde"] } [target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies] tauri-plugin-cli = "2" diff --git a/rust/src/model/profile.rs b/rust/src/model/profile.rs index 66cb7e1..aa77f87 100644 --- a/rust/src/model/profile.rs +++ b/rust/src/model/profile.rs @@ -2,16 +2,38 @@ use std::path::PathBuf; use serde::{Deserialize, Serialize}; use crate::pkg::PkgKey; +#[derive(Deserialize, Serialize, Clone)] +pub enum Aime { + BuiltIn, + AMNet(PkgKey), + Other(PkgKey), +} + +#[derive(Deserialize, Serialize, Clone)] +pub struct AMNet { + pub name: String, + pub addr: String, + pub physical: bool, +} + +impl Default for AMNet { + fn default() -> Self { + Self { name: Default::default(), addr: "http://+:6070".to_string(), physical: false } + } +} + #[derive(Deserialize, Serialize, Clone)] pub struct Segatools { pub target: PathBuf, pub hook: Option, pub io: Option, + pub aime: Option, pub amfs: PathBuf, pub option: PathBuf, pub appdata: PathBuf, - pub enable_aime: bool, pub intel: bool, + #[serde(default)] + pub amnet: AMNet, } impl Default for Segatools { @@ -23,8 +45,9 @@ impl Default for Segatools { amfs: PathBuf::default(), option: PathBuf::default(), appdata: PathBuf::from("appdata"), - enable_aime: false, - intel: false + aime: Some(Aime::BuiltIn), + intel: false, + amnet: AMNet::default(), } } } diff --git a/rust/src/modules/segatools.rs b/rust/src/modules/segatools.rs index b939cbe..d86178f 100644 --- a/rust/src/modules/segatools.rs +++ b/rust/src/modules/segatools.rs @@ -2,7 +2,7 @@ use std::path::PathBuf; use anyhow::{anyhow, Result}; use ini::Ini; -use crate::{model::{profile::Segatools, segatools_base::segatools_base}, profiles::ProfilePaths, util::{self, PathStr}}; +use crate::{model::{profile::{Aime, Segatools}, segatools_base::segatools_base}, profiles::ProfilePaths, util::{self, PathStr}}; impl Segatools { pub async fn line_up(&self, p: &impl ProfilePaths) -> Result { @@ -50,10 +50,30 @@ impl Segatools { pfx_dir.join("BepInEx").join("core").join("BepInEx.Preloader.dll").stringify()? ); - if self.enable_aime { + if let Some(aime) = &self.aime { ini_out.with_section(Some("aime")) .set("enable", "1") .set("aimePath", p.config_dir().join("aime.txt").stringify()?); + if let Aime::AMNet(key) = aime { + let mut aimeio = ini_out.with_section(Some("aimeio")); + aimeio + .set("path", util::pkg_dir().join(key.to_string()).join("segatools").join("aimeio.dll").stringify()?) + .set("gameId", "SDDT") + .set("serverAddress", &self.amnet.addr) + .set("useAimeDBForPhysicalCards", if self.amnet.physical { "1" } else { "0" }) + .set("enableKeyboardMode", "0"); + + if let Ok(keyboard_code) = std::fs::read_to_string(p.config_dir().join("aime.txt")) { + log::debug!("{} {}", keyboard_code, keyboard_code.len()); + if keyboard_code.len() == 20 { + aimeio.set("enableKeyboardMode", "1"); + } + } + + if self.amnet.name.len() > 0 { + aimeio.set("serverName", &self.amnet.name); + } + } } else { ini_out.with_section(Some("aime")) .set("enable", "0"); diff --git a/rust/src/pkg.rs b/rust/src/pkg.rs index 3f8b6ea..a0f923d 100644 --- a/rust/src/pkg.rs +++ b/rust/src/pkg.rs @@ -3,6 +3,7 @@ use derive_more::Display; use serde::{Deserialize, Serialize}; use std::{collections::BTreeSet, path::{Path, PathBuf}}; use tokio::fs; +use enumflags2::{bitflags, BitFlags}; use crate::{model::{local::{self, PackageManifest}, rainy}, util}; // {namespace}-{name} @@ -24,25 +25,33 @@ pub struct Package { pub rmt: Option } -#[derive(Clone, Default, PartialEq, Serialize, Deserialize)] -pub enum Kind { +#[derive(Clone, PartialEq, Serialize, Deserialize)] +pub enum Status { Unchecked, Unsupported, - #[default] Mod, - Hook, - IO, + OK(BitFlags) } -#[derive(Clone, Default, Serialize, Deserialize)] +#[bitflags] +#[repr(u8)] +#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)] +pub enum Feature { + Hook, + GameIO, + Aime, + AMNet, +} + +#[derive(Clone, Serialize, Deserialize)] #[allow(dead_code)] pub struct Local { pub version: String, pub path: PathBuf, pub dependencies: BTreeSet, - pub kind: Kind + pub status: Status, } -#[derive(Clone, Default, Serialize, Deserialize)] +#[derive(Clone, Serialize, Deserialize)] #[allow(dead_code)] pub struct Remote { pub version: String, @@ -90,7 +99,7 @@ impl Package { .unwrap() .to_owned(); - let kind = Self::parse_kind(&mft); + let status = Self::parse_status(&mft); let dependencies = Self::sanitize_deps(mft.dependencies); Ok(Package { @@ -101,7 +110,7 @@ impl Package { loc: Some(Local { version: mft.version_number, path: dir.to_owned(), - kind, + status, dependencies }), rmt: None @@ -174,24 +183,31 @@ impl Package { res } - fn parse_kind(mft: &PackageManifest) -> Kind { + fn parse_status(mft: &PackageManifest) -> Status { if mft.installers.len() == 0 { - return Kind::Mod;//Unchecked + return Status::OK(BitFlags::default());//Unchecked } else if mft.installers.len() == 1 { if let Some(serde_json::Value::String(id)) = &mft.installers[0].get("identifier") { if id == "rainycolor" { - return Kind::Mod + return Status::OK(BitFlags::default()); } else if id == "segatools" { + // Multiple features in the same dll (yubideck etc.) should be supported at some point + let mut flags = BitFlags::default(); if let Some(serde_json::Value::String(module)) = mft.installers[0].get("module") { if module.ends_with("hook") { - return Kind::Hook; + flags |= Feature::Hook; + } else if module == "amnet" { + flags |= Feature::AMNet | Feature::Aime; + } else if module == "aimeio" { + flags |= Feature::Aime; } else if module.ends_with("io") { - return Kind::IO; + flags |= Feature::GameIO; } } + return Status::OK(flags); } } } - return Kind::Unsupported + Status::Unsupported } } \ No newline at end of file diff --git a/src/components/ModTitlecard.vue b/src/components/ModTitlecard.vue index 7b58713..1f2bc12 100644 --- a/src/components/ModTitlecard.vue +++ b/src/components/ModTitlecard.vue @@ -58,11 +58,19 @@ const iconSrc = () => { > + + { +
+ + + + + + + + + +
+ + + + + diff --git a/src/stores.ts b/src/stores.ts index 946d4d7..e292fc0 100644 --- a/src/stores.ts +++ b/src/stores.ts @@ -3,8 +3,8 @@ import { defineStore } from 'pinia'; import { listen } from '@tauri-apps/api/event'; import * as path from '@tauri-apps/api/path'; import { invoke, invoke_nopopup } from './invoke'; -import { Dirs, Game, Package, Profile, ProfileMeta } from './types'; -import { changePrimaryColor, pkgKey } from './util'; +import { Dirs, Feature, Game, Package, Profile, ProfileMeta } from './types'; +import { changePrimaryColor, hasFeature, pkgKey } from './util'; type InstallStatus = { pkg: string; @@ -101,9 +101,13 @@ export const usePkgStore = defineStore('pkg', { )) ), hooks: (state) => - Object.values(state.pkg).filter((p) => p.loc?.kind === 'Hook'), - ios: (state) => - Object.values(state.pkg).filter((p) => p.loc?.kind === 'IO'), + Object.values(state.pkg).filter((p) => hasFeature(p, Feature.Hook)), + gameIOs: (state) => + Object.values(state.pkg).filter((p) => + hasFeature(p, Feature.GameIO) + ), + aimes: (state) => + Object.values(state.pkg).filter((p) => hasFeature(p, Feature.Aime)), }, actions: { setupListeners() { diff --git a/src/types.ts b/src/types.ts index 0edb85c..3f08e67 100644 --- a/src/types.ts +++ b/src/types.ts @@ -7,7 +7,7 @@ export interface Package { version: string; path: string; dependencies: string[]; - kind: 'Unchecked' | 'Unsupported' | 'Mod' | 'Hook' | 'IO'; + status: Status; } | null; rmt: { version: string; @@ -22,6 +22,20 @@ export interface Package { }; } +export enum Feature { + Hook = 0b00001, + GameIO = 0b00010, + Aime = 0b00100, + AMNet = 0b01000, +} + +export type Status = + | 'Unchecked' + | 'Unsupported' + | { + OK: Feature; + }; + export type Game = 'ongeki' | 'chunithm'; export interface ProfileMeta { @@ -36,8 +50,13 @@ export interface SegatoolsConfig { amfs: string; option: string; appdata: string; - enable_aime: boolean; + aime: { AMNet: string } | { Other: string } | null; intel: boolean; + amnet: { + name: string; + addr: string; + physical: boolean; + }; } export interface DisplayConfig { diff --git a/src/util.ts b/src/util.ts index bd66d29..19b56f1 100644 --- a/src/util.ts +++ b/src/util.ts @@ -1,5 +1,5 @@ import { updatePrimaryPalette } from '@primevue/themes'; -import { Game, Package } from './types'; +import { Feature, Game, Package } from './types'; export const changePrimaryColor = (game: Game | null) => { const color = @@ -45,3 +45,11 @@ export const needsUpdate = (pkg: Package | undefined) => { } return l1 < r1; }; + +export const hasFeature = (pkg: Package, feature: Feature) => { + return ( + pkg.loc !== null && + typeof pkg.loc?.status !== 'string' && + pkg.loc.status.OK & feature + ); +};