forked from akanyan/STARTLINER
feat: chusanApp.exe patching
This commit is contained in:
@ -2,7 +2,7 @@ use std::hash::{DefaultHasher, Hash, Hasher};
|
|||||||
use crate::model::config::GlobalConfig;
|
use crate::model::config::GlobalConfig;
|
||||||
use crate::model::patch::PatchFileVec;
|
use crate::model::patch::PatchFileVec;
|
||||||
use crate::pkg::{Feature, Status};
|
use crate::pkg::{Feature, Status};
|
||||||
use crate::profiles::Profile;
|
use crate::profiles::types::Profile;
|
||||||
use crate::{model::misc::Game, pkg::PkgKey};
|
use crate::{model::misc::Game, pkg::PkgKey};
|
||||||
use crate::pkg_store::PackageStore;
|
use crate::pkg_store::PackageStore;
|
||||||
use crate::util;
|
use crate::util;
|
||||||
@ -18,7 +18,7 @@ pub struct AppData {
|
|||||||
pub pkgs: PackageStore,
|
pub pkgs: PackageStore,
|
||||||
pub cfg: GlobalConfig,
|
pub cfg: GlobalConfig,
|
||||||
pub state: GlobalState,
|
pub state: GlobalState,
|
||||||
pub patch_set: PatchFileVec,
|
pub patch_vec: PatchFileVec,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(PartialEq, Debug, Copy, Clone)]
|
#[derive(PartialEq, Debug, Copy, Clone)]
|
||||||
@ -39,7 +39,7 @@ impl AppData {
|
|||||||
None => None
|
None => None
|
||||||
};
|
};
|
||||||
|
|
||||||
let patch_set = PatchFileVec::new(util::config_dir())
|
let patch_vec = PatchFileVec::new(util::config_dir())
|
||||||
.map_err(|e| log::error!("unable to load patch set: {e}"))
|
.map_err(|e| log::error!("unable to load patch set: {e}"))
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
|
|
||||||
@ -50,7 +50,7 @@ impl AppData {
|
|||||||
pkgs: PackageStore::new(apph.clone()),
|
pkgs: PackageStore::new(apph.clone()),
|
||||||
cfg,
|
cfg,
|
||||||
state: GlobalState { remain_open: true },
|
state: GlobalState { remain_open: true },
|
||||||
patch_set
|
patch_vec
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -85,8 +85,7 @@ impl AppData {
|
|||||||
.clone()
|
.clone()
|
||||||
.ok_or_else(|| anyhow!("Attempted to enable a non-existent package"))?;
|
.ok_or_else(|| anyhow!("Attempted to enable a non-existent package"))?;
|
||||||
|
|
||||||
if let Status::OK(feature_set) = loc.status {
|
if let Status::OK(feature_set, _) = loc.status {
|
||||||
log::debug!("{:?}", feature_set);
|
|
||||||
if feature_set.contains(Feature::Mod) {
|
if feature_set.contains(Feature::Mod) {
|
||||||
profile.mod_pkgs_mut().insert(key);
|
profile.mod_pkgs_mut().insert(key);
|
||||||
}
|
}
|
||||||
|
@ -8,9 +8,10 @@ use tauri::{AppHandle, Manager, State};
|
|||||||
use crate::model::config::GlobalConfigField;
|
use crate::model::config::GlobalConfigField;
|
||||||
use crate::model::misc::Game;
|
use crate::model::misc::Game;
|
||||||
use crate::model::patch::Patch;
|
use crate::model::patch::Patch;
|
||||||
|
use crate::modules::package::prepare_dlls;
|
||||||
use crate::pkg::{Package, PkgKey};
|
use crate::pkg::{Package, PkgKey};
|
||||||
use crate::pkg_store::{InstallResult, PackageStore};
|
use crate::pkg_store::{InstallResult, PackageStore};
|
||||||
use crate::profiles::{self, Profile, ProfileData, ProfileMeta, ProfilePaths};
|
use crate::profiles::{self, Profile, ProfileData, ProfileMeta, ProfilePaths, StartPayload};
|
||||||
use crate::appdata::{AppData, ToggleAction};
|
use crate::appdata::{AppData, ToggleAction};
|
||||||
use crate::model::misc::StartCheckError;
|
use crate::model::misc::StartCheckError;
|
||||||
use crate::util;
|
use crate::util;
|
||||||
@ -59,18 +60,40 @@ pub async fn startline(app: AppHandle, refresh: bool) -> Result<(), String> {
|
|||||||
let state = app.state::<Mutex<AppData>>();
|
let state = app.state::<Mutex<AppData>>();
|
||||||
let mut hash = "".to_owned();
|
let mut hash = "".to_owned();
|
||||||
|
|
||||||
let mut appd = state.lock().await;
|
let appd = state.lock().await;
|
||||||
|
let mut game_dlls = Vec::new();
|
||||||
|
let mut amd_dlls = Vec::new();
|
||||||
if let Some(p) = &appd.profile {
|
if let Some(p) = &appd.profile {
|
||||||
hash = appd.sum_packages(p);
|
hash = appd.sum_packages(p);
|
||||||
|
(game_dlls, amd_dlls) = prepare_dlls(p.mod_pkgs(), &appd.pkgs).map_err(|e| e.to_string())?
|
||||||
}
|
}
|
||||||
if let Some(p) = &mut appd.profile {
|
if let Some(p) = &appd.profile {
|
||||||
log::debug!("{}", hash);
|
log::debug!("{}", hash);
|
||||||
p.line_up(hash, refresh, app.clone()).await
|
let info = p.prepare_display()
|
||||||
.map_err(|e| e.to_string())?;
|
.map_err(|e| e.to_string())?;
|
||||||
|
let lineup_res = p.line_up(hash, refresh, &appd.patch_vec).await
|
||||||
|
.map_err(|e| e.to_string());
|
||||||
|
|
||||||
|
#[cfg(target_os = "windows")]
|
||||||
|
if let Some(info) = info {
|
||||||
|
use crate::model::profile::Display;
|
||||||
|
if lineup_res.is_ok() {
|
||||||
|
Display::wait_for_exit(app.clone(), info);
|
||||||
|
} else {
|
||||||
|
Display::clean_up(&info).map_err(|e| e.to_string())?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lineup_res?;
|
||||||
|
|
||||||
let app_clone = app.clone();
|
let app_clone = app.clone();
|
||||||
let p_clone = p.clone();
|
let p_clone = p.clone();
|
||||||
tauri::async_runtime::spawn(async move {
|
tauri::async_runtime::spawn(async move {
|
||||||
if let Err(e) = p_clone.start(app_clone).await {
|
if let Err(e) = p_clone.start(StartPayload {
|
||||||
|
app: app_clone,
|
||||||
|
game_dlls,
|
||||||
|
amd_dlls
|
||||||
|
}).await {
|
||||||
log::error!("Startup failed:\n{}", e);
|
log::error!("Startup failed:\n{}", e);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -152,6 +175,10 @@ pub async fn get_all_packages(state: State<'_, Mutex<AppData>>) -> Result<HashMa
|
|||||||
|
|
||||||
let appd = state.lock().await;
|
let appd = state.lock().await;
|
||||||
|
|
||||||
|
let pkgs_all = appd.pkgs.get_all();
|
||||||
|
|
||||||
|
log::debug!("pkgs_all: {:?}", pkgs_all);
|
||||||
|
|
||||||
Ok(appd.pkgs.get_all())
|
Ok(appd.pkgs.get_all())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -466,7 +493,7 @@ pub async fn list_patches(state: State<'_, Mutex<AppData>>, target: String) -> R
|
|||||||
|
|
||||||
let mut appd = state.lock().await;
|
let mut appd = state.lock().await;
|
||||||
appd.fix();
|
appd.fix();
|
||||||
let list = appd.patch_set.find_patches(target).map_err(|e| e.to_string())?;
|
let list = appd.patch_vec.find_patches(target).map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
Ok(list)
|
Ok(list)
|
||||||
}
|
}
|
@ -8,6 +8,7 @@ use anyhow::Result;
|
|||||||
pub struct PatchSelection(pub BTreeMap<String, PatchSelectionData>);
|
pub struct PatchSelection(pub BTreeMap<String, PatchSelectionData>);
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize, Clone, Debug)]
|
#[derive(Deserialize, Serialize, Clone, Debug)]
|
||||||
|
#[serde(rename_all = "snake_case")]
|
||||||
pub enum PatchSelectionData {
|
pub enum PatchSelectionData {
|
||||||
Enabled,
|
Enabled,
|
||||||
Number(i8),
|
Number(i8),
|
||||||
@ -108,10 +109,10 @@ impl<'de> serde::Deserialize<'de> for Patch {
|
|||||||
.and_then(Value::as_i64)
|
.and_then(Value::as_i64)
|
||||||
.ok_or_else(|| de::Error::missing_field("min"))?
|
.ok_or_else(|| de::Error::missing_field("min"))?
|
||||||
).map_err(|_| de::Error::missing_field("min"))?,
|
).map_err(|_| de::Error::missing_field("min"))?,
|
||||||
max: i32::try_from(value.get("min")
|
max: i32::try_from(value.get("max")
|
||||||
.and_then(Value::as_i64)
|
.and_then(Value::as_i64)
|
||||||
.ok_or_else(|| de::Error::missing_field("min"))?
|
.ok_or_else(|| de::Error::missing_field("max"))?
|
||||||
).map_err(|_| de::Error::missing_field("min"))?
|
).map_err(|_| de::Error::missing_field("max"))?
|
||||||
}),
|
}),
|
||||||
None => {
|
None => {
|
||||||
let mut patches = vec![];
|
let mut patches = vec![];
|
||||||
|
@ -164,8 +164,11 @@ pub struct Mu3Ini {
|
|||||||
pub blacklist: Option<(i32, i32)>,
|
pub blacklist: Option<(i32, i32)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn default_true() -> bool { true }
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize, Clone, Debug)]
|
#[derive(Deserialize, Serialize, Clone, Debug)]
|
||||||
pub struct OngekiKeyboard {
|
pub struct OngekiKeyboard {
|
||||||
|
#[serde(default = "default_true")] pub enabled: bool,
|
||||||
pub use_mouse: bool,
|
pub use_mouse: bool,
|
||||||
pub coin: i32,
|
pub coin: i32,
|
||||||
pub svc: i32,
|
pub svc: i32,
|
||||||
@ -185,6 +188,7 @@ pub struct OngekiKeyboard {
|
|||||||
impl Default for OngekiKeyboard {
|
impl Default for OngekiKeyboard {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
enabled: true,
|
||||||
use_mouse: true,
|
use_mouse: true,
|
||||||
test: 0x70,
|
test: 0x70,
|
||||||
svc: 0x71,
|
svc: 0x71,
|
||||||
@ -205,6 +209,7 @@ impl Default for OngekiKeyboard {
|
|||||||
|
|
||||||
#[derive(Deserialize, Serialize, Clone, Debug)]
|
#[derive(Deserialize, Serialize, Clone, Debug)]
|
||||||
pub struct ChunithmKeyboard {
|
pub struct ChunithmKeyboard {
|
||||||
|
#[serde(default = "default_true")] pub enabled: bool,
|
||||||
pub coin: i32,
|
pub coin: i32,
|
||||||
pub svc: i32,
|
pub svc: i32,
|
||||||
pub test: i32,
|
pub test: i32,
|
||||||
@ -215,6 +220,7 @@ pub struct ChunithmKeyboard {
|
|||||||
impl Default for ChunithmKeyboard {
|
impl Default for ChunithmKeyboard {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
enabled: true,
|
||||||
test: 0x70,
|
test: 0x70,
|
||||||
svc: 0x71,
|
svc: 0x71,
|
||||||
coin: 0x72,
|
coin: 0x72,
|
||||||
|
@ -77,34 +77,46 @@ impl Keyboard {
|
|||||||
pub fn line_up(&self, ini: &mut Ini) -> Result<()> {
|
pub fn line_up(&self, ini: &mut Ini) -> Result<()> {
|
||||||
match self {
|
match self {
|
||||||
Keyboard::Ongeki(kb) => {
|
Keyboard::Ongeki(kb) => {
|
||||||
ini.with_section(Some("io4"))
|
if kb.enabled {
|
||||||
.set("test", kb.test.to_string())
|
ini.with_section(Some("io4"))
|
||||||
.set("service", kb.svc.to_string())
|
.set("test", kb.test.to_string())
|
||||||
.set("coin", kb.coin.to_string())
|
.set("service", kb.svc.to_string())
|
||||||
.set("left1", kb.l1.to_string())
|
.set("coin", kb.coin.to_string())
|
||||||
.set("left2", kb.l2.to_string())
|
.set("left1", kb.l1.to_string())
|
||||||
.set("left3", kb.l3.to_string())
|
.set("left2", kb.l2.to_string())
|
||||||
.set("right1", kb.r1.to_string())
|
.set("left3", kb.l3.to_string())
|
||||||
.set("right2", kb.r2.to_string())
|
.set("right1", kb.r1.to_string())
|
||||||
.set("right3", kb.r3.to_string())
|
.set("right2", kb.r2.to_string())
|
||||||
.set("leftSide", kb.lwad.to_string())
|
.set("right3", kb.r3.to_string())
|
||||||
.set("rightSide", kb.rwad.to_string())
|
.set("leftSide", kb.lwad.to_string())
|
||||||
.set("leftMenu", kb.lmenu.to_string())
|
.set("rightSide", kb.rwad.to_string())
|
||||||
.set("rightMenu", kb.rmenu.to_string())
|
.set("leftMenu", kb.lmenu.to_string())
|
||||||
.set("mouse", if kb.use_mouse { "1" } else { "0" });
|
.set("rightMenu", kb.rmenu.to_string())
|
||||||
|
.set("mouse", if kb.use_mouse { "1" } else { "0" });
|
||||||
|
} else {
|
||||||
|
ini.with_section(Some("io4"))
|
||||||
|
.set("enable", "0");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Keyboard::Chunithm(kb) => {
|
Keyboard::Chunithm(kb) => {
|
||||||
for (i, cell) in kb.cell.iter().enumerate() {
|
if kb.enabled {
|
||||||
ini.with_section(Some("slider")).set(format!("cell{}", i + 1), cell.to_string());
|
for (i, cell) in kb.cell.iter().enumerate() {
|
||||||
|
ini.with_section(Some("slider")).set(format!("cell{}", i + 1), cell.to_string());
|
||||||
|
}
|
||||||
|
for (i, ir) in kb.ir.iter().enumerate() {
|
||||||
|
ini.with_section(Some("ir")).set(format!("ir{}", i + 1), ir.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())
|
||||||
|
.set("ir", "0");
|
||||||
|
} else {
|
||||||
|
ini.with_section(Some("io4"))
|
||||||
|
.set("enable", "0");
|
||||||
|
ini.with_section(Some("slider"))
|
||||||
|
.set("enable", "0");
|
||||||
}
|
}
|
||||||
for (i, ir) in kb.ir.iter().enumerate() {
|
|
||||||
ini.with_section(Some("ir")).set(format!("ir{}", i + 1), ir.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())
|
|
||||||
.set("ir", "0");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,59 @@
|
|||||||
|
use std::path::Path;
|
||||||
|
use anyhow::Result;
|
||||||
|
use crate::model::patch::{Patch, PatchData, PatchFileVec, PatchSelection, PatchSelectionData};
|
||||||
|
|
||||||
|
impl PatchSelection {
|
||||||
|
pub async fn render_to_file(
|
||||||
|
&self,
|
||||||
|
filename: &str,
|
||||||
|
patches: &PatchFileVec,
|
||||||
|
path: impl AsRef<Path>
|
||||||
|
) -> Result<()> {
|
||||||
|
let mut res = "".to_owned();
|
||||||
|
|
||||||
|
for file in &patches.0 {
|
||||||
|
for list in &file.0 {
|
||||||
|
if list.filename != filename {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
for patch in &list.patches {
|
||||||
|
if let Some(selection) = self.0.get(&patch.id) {
|
||||||
|
res += &Self::render(filename, patch, selection);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tokio::fs::write(path, res).await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render(filename: &str, patch: &Patch, sel: &PatchSelectionData) -> String {
|
||||||
|
let mut res = "".to_owned();
|
||||||
|
match &patch.data {
|
||||||
|
PatchData::Normal(data) => {
|
||||||
|
for p in &data.patches {
|
||||||
|
res += &format!("{} F+{:X} ", filename, p.offset);
|
||||||
|
for on in &p.on {
|
||||||
|
res += &format!("{:02X}", on);
|
||||||
|
}
|
||||||
|
res += " ";
|
||||||
|
for off in &p.off {
|
||||||
|
res += &format!("{:02X}", off);
|
||||||
|
}
|
||||||
|
res += "\n";
|
||||||
|
}
|
||||||
|
},
|
||||||
|
PatchData::Number(data) => {
|
||||||
|
if let PatchSelectionData::Number(val) = sel {
|
||||||
|
let width = (data.size as usize) * 2usize;
|
||||||
|
res += &format!("{} F+{:X} {:0width$X} {:0width$X}", filename, data.offset, val, data.default, width = width);
|
||||||
|
} else {
|
||||||
|
log::error!("invalid number patch {:?}", patch);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
format!("{}\n", res)
|
||||||
|
}
|
||||||
|
}
|
@ -1,8 +1,10 @@
|
|||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use std::collections::BTreeSet;
|
use std::collections::BTreeSet;
|
||||||
use crate::pkg::PkgKey;
|
use std::path::PathBuf;
|
||||||
|
use crate::pkg::{PkgKey, Status};
|
||||||
|
use crate::pkg_store::PackageStore;
|
||||||
use crate::util;
|
use crate::util;
|
||||||
use crate::profiles::ProfilePaths;
|
use crate::profiles::types::ProfilePaths;
|
||||||
|
|
||||||
pub async fn prepare_packages<'a>(p: &'a impl ProfilePaths, pkgs: &BTreeSet<PkgKey>, redo_bepinex: bool) -> Result<()> {
|
pub async fn prepare_packages<'a>(p: &'a impl ProfilePaths, pkgs: &BTreeSet<PkgKey>, redo_bepinex: bool) -> Result<()> {
|
||||||
log::debug!("begin prepare packages");
|
log::debug!("begin prepare packages");
|
||||||
@ -22,10 +24,10 @@ pub async fn prepare_packages<'a>(p: &'a impl ProfilePaths, pkgs: &BTreeSet<PkgK
|
|||||||
|
|
||||||
for m in pkgs {
|
for m in pkgs {
|
||||||
log::debug!("preparing {}", m);
|
log::debug!("preparing {}", m);
|
||||||
let (namespace, name) = m.0.split_at(m.0.find("-").expect("Invalid mod definition"));
|
let (namespace, name) = m.split()?;
|
||||||
|
|
||||||
if redo_bepinex {
|
if redo_bepinex {
|
||||||
let bpx_dir = util::pkg_dir_of(namespace, &name[1..]) // cut the hyphen
|
let bpx_dir = util::pkg_dir_of(&namespace, &name)
|
||||||
.join("app")
|
.join("app")
|
||||||
.join("BepInEx");
|
.join("BepInEx");
|
||||||
if bpx_dir.exists() {
|
if bpx_dir.exists() {
|
||||||
@ -33,7 +35,7 @@ pub async fn prepare_packages<'a>(p: &'a impl ProfilePaths, pkgs: &BTreeSet<PkgK
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let opt_dir = util::pkg_dir_of(namespace, &name[1..]).join("option");
|
let opt_dir = util::pkg_dir_of(&namespace, &name).join("option");
|
||||||
if opt_dir.exists() {
|
if opt_dir.exists() {
|
||||||
let x = opt_dir.read_dir().unwrap().next().unwrap()?;
|
let x = opt_dir.read_dir().unwrap().next().unwrap()?;
|
||||||
if x.metadata()?.is_dir() {
|
if x.metadata()?.is_dir() {
|
||||||
@ -45,4 +47,27 @@ pub async fn prepare_packages<'a>(p: &'a impl ProfilePaths, pkgs: &BTreeSet<PkgK
|
|||||||
log::debug!("end prepare packages");
|
log::debug!("end prepare packages");
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn prepare_dlls(
|
||||||
|
enabled_pkgs: &BTreeSet<PkgKey>,
|
||||||
|
store: &PackageStore,
|
||||||
|
) -> Result<(Vec<PathBuf>, Vec<PathBuf>)> {
|
||||||
|
let mut res_game = Vec::new();
|
||||||
|
let mut res_amd = Vec::new();
|
||||||
|
for pkg in enabled_pkgs {
|
||||||
|
if let Ok(pkg) = store.get(&pkg) {
|
||||||
|
if let Some(loc) = &pkg.loc {
|
||||||
|
if let Status::OK(_, dlls) = &loc.status {
|
||||||
|
if let Some(game_dll) = &dlls.game {
|
||||||
|
res_game.push(pkg.path().join(game_dll.clone()));
|
||||||
|
}
|
||||||
|
if let Some(amd_dll) = &dlls.amd {
|
||||||
|
res_amd.push(pkg.path().join(amd_dll.clone()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok((res_game, res_amd))
|
||||||
}
|
}
|
@ -20,7 +20,7 @@ pub enum PackageSource {
|
|||||||
Local(Game)
|
Local(Game)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Default, Serialize, Deserialize)]
|
#[derive(Clone, Default, Serialize, Deserialize, Debug)]
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub struct Package {
|
pub struct Package {
|
||||||
pub namespace: String,
|
pub namespace: String,
|
||||||
@ -31,11 +31,17 @@ pub struct Package {
|
|||||||
pub source: PackageSource,
|
pub source: PackageSource,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, PartialEq, Serialize, Deserialize)]
|
#[derive(Clone, PartialEq, Serialize, Deserialize, Debug)]
|
||||||
pub enum Status {
|
pub enum Status {
|
||||||
Unchecked,
|
Unchecked,
|
||||||
Unsupported,
|
Unsupported,
|
||||||
OK(BitFlags<Feature>)
|
OK(BitFlags<Feature>, DLLs),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, PartialEq, Serialize, Deserialize, Debug)]
|
||||||
|
pub struct DLLs {
|
||||||
|
pub game: Option<String>,
|
||||||
|
pub amd: Option<String>
|
||||||
}
|
}
|
||||||
|
|
||||||
#[bitflags]
|
#[bitflags]
|
||||||
@ -54,7 +60,7 @@ pub enum Feature {
|
|||||||
AmdDLL
|
AmdDLL
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Serialize, Deserialize)]
|
#[derive(Clone, Serialize, Deserialize, Debug)]
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub struct Local {
|
pub struct Local {
|
||||||
pub version: String,
|
pub version: String,
|
||||||
@ -64,7 +70,7 @@ pub struct Local {
|
|||||||
pub icon: String,
|
pub icon: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Serialize, Deserialize)]
|
#[derive(Clone, Serialize, Deserialize, Debug)]
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub struct Remote {
|
pub struct Remote {
|
||||||
pub version: String,
|
pub version: String,
|
||||||
@ -77,6 +83,14 @@ pub struct Remote {
|
|||||||
pub dependencies: BTreeSet<PkgKey>,
|
pub dependencies: BTreeSet<PkgKey>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl PkgKey {
|
||||||
|
pub fn split(&self) -> Result<(String, String)> {
|
||||||
|
let (namespace, name) = self.0
|
||||||
|
.split_at(self.0.find("-").ok_or_else(|| anyhow!("Invalid package key"))?);
|
||||||
|
Ok((namespace.to_owned(), name[1..].to_owned())) // cut the hyphen
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Package {
|
impl Package {
|
||||||
pub fn from_rainy(mut p: rainy::V1Package) -> Option<Package> {
|
pub fn from_rainy(mut p: rainy::V1Package) -> Option<Package> {
|
||||||
if p.versions.len() == 0 {
|
if p.versions.len() == 0 {
|
||||||
@ -209,37 +223,50 @@ impl Package {
|
|||||||
|
|
||||||
fn parse_status(mft: &PackageManifest) -> Status {
|
fn parse_status(mft: &PackageManifest) -> Status {
|
||||||
if mft.installers.len() == 0 {
|
if mft.installers.len() == 0 {
|
||||||
return Status::OK(make_bitflags!(Feature::Mod));//Unchecked
|
return Status::OK(make_bitflags!(Feature::Mod), DLLs { game: None, amd: None }); //Unchecked
|
||||||
} else if mft.installers.len() == 1 {
|
} else {
|
||||||
if let Some(serde_json::Value::String(id)) = &mft.installers[0].get("identifier") {
|
let mut flags = BitFlags::default();
|
||||||
if id == "rainycolor" {
|
let mut game_dll = None;
|
||||||
return Status::OK(make_bitflags!(Feature::Mod));
|
let mut amd_dll = None;
|
||||||
} else if id == "segatools" {
|
for installer in &mft.installers {
|
||||||
// Multiple features in the same dll (yubideck etc.) should be supported at some point
|
if let Some(serde_json::Value::String(id)) = installer.get("identifier") {
|
||||||
let mut flags = BitFlags::default();
|
if id == "rainycolor" {
|
||||||
if let Some(serde_json::Value::String(module)) = mft.installers[0].get("module") {
|
flags |= Feature::Mod;
|
||||||
if module == "mu3hook" {
|
} else if id == "segatools" {
|
||||||
flags |= Feature::Mu3Hook;
|
// Multiple features in the same dll (yubideck etc.) should be supported at some point
|
||||||
} else if module == "chusanhook" {
|
if let Some(serde_json::Value::String(module)) = installer.get("module") {
|
||||||
flags |= Feature::ChusanHook;
|
if module == "mu3hook" {
|
||||||
} else if module == "amnet" {
|
flags |= Feature::Mu3Hook;
|
||||||
flags |= Feature::AMNet | Feature::Aime;
|
} else if module == "chusanhook" {
|
||||||
} else if module == "aimeio" {
|
flags |= Feature::ChusanHook;
|
||||||
flags |= Feature::Aime;
|
} else if module == "amnet" {
|
||||||
} else if module == "mu3io" {
|
flags |= Feature::AMNet | Feature::Aime;
|
||||||
flags |= Feature::Mu3IO;
|
} else if module == "aimeio" {
|
||||||
} else if module == "chuniio" {
|
flags |= Feature::Aime;
|
||||||
flags |= Feature::ChuniIO;
|
} else if module == "mu3io" {
|
||||||
} else if module == "mempatcher" {
|
flags |= Feature::Mu3IO;
|
||||||
flags |= Feature::Mempatcher;
|
} else if module == "chuniio" {
|
||||||
} else if module == "game-dll" {
|
flags |= Feature::ChuniIO;
|
||||||
flags |= Feature::GameDLL;
|
}
|
||||||
}
|
}
|
||||||
|
} else if id == "native-mod" {
|
||||||
|
if let Some(serde_json::Value::String(path)) = installer.get("dll-game") {
|
||||||
|
flags |= Feature::GameDLL;
|
||||||
|
flags |= Feature::Mod;
|
||||||
|
game_dll = Some(path.to_owned());
|
||||||
|
}
|
||||||
|
if let Some(serde_json::Value::String(path)) = installer.get("dll-amdaemon") {
|
||||||
|
flags |= Feature::AmdDLL;
|
||||||
|
flags |= Feature::Mod;
|
||||||
|
amd_dll = Some(path.to_owned());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return Status::Unsupported;
|
||||||
}
|
}
|
||||||
return Status::OK(flags);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
log::debug!("{} parse result: {:?} {:?} {:?}", mft.name, flags, game_dll, amd_dll);
|
||||||
|
Status::OK(flags, DLLs { game: game_dll, amd: amd_dll })
|
||||||
}
|
}
|
||||||
Status::Unsupported
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -8,7 +8,7 @@ use tokio::task::JoinSet;
|
|||||||
use crate::model::local::{PackageList, PackageListEntry};
|
use crate::model::local::{PackageList, PackageListEntry};
|
||||||
use crate::model::misc::Game;
|
use crate::model::misc::Game;
|
||||||
use crate::model::rainy;
|
use crate::model::rainy;
|
||||||
use crate::pkg::{Package, PackageSource, PkgKey, Remote, Status};
|
use crate::pkg::{Feature, Package, PackageSource, PkgKey, Remote, Status};
|
||||||
use crate::util;
|
use crate::util;
|
||||||
use crate::download_handler::DownloadHandler;
|
use crate::download_handler::DownloadHandler;
|
||||||
|
|
||||||
@ -67,6 +67,21 @@ impl PackageStore {
|
|||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub fn get_by_feature(&self, feature: Feature) -> Vec<PkgKey> {
|
||||||
|
self.store.iter()
|
||||||
|
.filter(|(_, v)| {
|
||||||
|
if let Some(loc) = &v.loc {
|
||||||
|
if let Status::OK(flags, _) = loc.status {
|
||||||
|
return flags.contains(feature);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
})
|
||||||
|
.map(|(k, _)| k.clone())
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn reload_package(&mut self, key: PkgKey) {
|
pub async fn reload_package(&mut self, key: PkgKey) {
|
||||||
let dir = util::pkg_dir().join(&key.0);
|
let dir = util::pkg_dir().join(&key.0);
|
||||||
if let Ok(pkg) = Package::from_dir(dir, PackageSource::Rainy).await {
|
if let Ok(pkg) = Package::from_dir(dir, PackageSource::Rainy).await {
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
use serde::{Deserialize, Serialize};
|
pub use types::{Profile, ProfileData, ProfileMeta, ProfilePaths, StartPayload};
|
||||||
use tauri::AppHandle;
|
|
||||||
use std::{collections::{BTreeMap, BTreeSet}, path::{Path, PathBuf}};
|
use std::{collections::{BTreeMap, BTreeSet}, path::{Path, PathBuf}};
|
||||||
use crate::{model::{misc::Game, patch::PatchSelection, profile::{Aime, ChunithmKeyboard, Keyboard, Mu3Ini, OngekiKeyboard, ProfileModule}}, modules::package::prepare_packages, pkg::PkgKey, pkg_store::PackageStore, util};
|
use crate::{model::{misc::Game, patch::{PatchFileVec, PatchSelection}, profile::{Aime, ChunithmKeyboard, Keyboard, Mu3Ini, OngekiKeyboard, ProfileModule}}, modules::{display_windows::DisplayInfo, 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;
|
||||||
@ -11,57 +10,7 @@ use std::fs::File;
|
|||||||
use tokio::process::Command;
|
use tokio::process::Command;
|
||||||
use tokio::task::JoinSet;
|
use tokio::task::JoinSet;
|
||||||
|
|
||||||
pub trait ProfilePaths {
|
pub mod types;
|
||||||
fn config_dir(&self) -> PathBuf;
|
|
||||||
fn data_dir(&self) -> PathBuf;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize, Clone, Debug, PartialEq)]
|
|
||||||
pub struct ProfileMeta {
|
|
||||||
pub game: Game,
|
|
||||||
pub name: String
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ProfilePaths for ProfileMeta {
|
|
||||||
fn config_dir(&self) -> PathBuf {
|
|
||||||
util::profile_config_dir(self.game, &self.name)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn data_dir(&self) -> PathBuf {
|
|
||||||
util::data_dir().join(format!("profile-{}-{}", &self.game, &self.name))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize, Clone)]
|
|
||||||
pub struct Profile {
|
|
||||||
pub meta: ProfileMeta,
|
|
||||||
pub data: ProfileData,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize, Clone, Debug)]
|
|
||||||
pub struct ProfileData {
|
|
||||||
pub mods: BTreeSet<PkgKey>,
|
|
||||||
pub sgt: Segatools,
|
|
||||||
pub network: Network,
|
|
||||||
|
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
|
||||||
pub display: Option<Display>,
|
|
||||||
|
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
|
||||||
pub bepinex: Option<BepInEx>,
|
|
||||||
|
|
||||||
#[cfg(not(target_os = "windows"))]
|
|
||||||
pub wine: crate::model::profile::Wine,
|
|
||||||
|
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
|
||||||
pub mu3_ini: Option<Mu3Ini>,
|
|
||||||
|
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
|
||||||
pub keyboard: Option<Keyboard>,
|
|
||||||
|
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
|
||||||
pub patches: Option<PatchSelection>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Profile {
|
impl Profile {
|
||||||
pub fn new(mut meta: ProfileMeta) -> Result<Self> {
|
pub fn new(mut meta: ProfileMeta) -> Result<Self> {
|
||||||
@ -205,27 +154,15 @@ impl Profile {
|
|||||||
self.data.patches = source.patches;
|
self.data.patches = source.patches;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub async fn line_up(&self, pkg_hash: String, refresh: bool, _app: AppHandle) -> Result<()> {
|
pub fn prepare_display(&self) -> Result<Option<DisplayInfo>> {
|
||||||
let info = match &self.data.display {
|
let info = match &self.data.display {
|
||||||
None => None,
|
None => None,
|
||||||
Some(display) => display.prepare()?
|
Some(display) => display.prepare()?
|
||||||
};
|
};
|
||||||
|
|
||||||
let res = self.line_up_the_rest(pkg_hash, refresh).await;
|
Ok(info)
|
||||||
|
|
||||||
#[cfg(target_os = "windows")]
|
|
||||||
if let Some(info) = info {
|
|
||||||
use crate::model::profile::Display;
|
|
||||||
if res.is_ok() {
|
|
||||||
Display::wait_for_exit(_app, info);
|
|
||||||
} else {
|
|
||||||
Display::clean_up(&info)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
res
|
|
||||||
}
|
}
|
||||||
async fn line_up_the_rest(&self, pkg_hash: String, refresh: bool) -> Result<()> {
|
pub async fn line_up(&self, pkg_hash: String, refresh: bool, patch_files: &PatchFileVec) -> Result<()> {
|
||||||
if !self.data_dir().exists() {
|
if !self.data_dir().exists() {
|
||||||
tokio::fs::create_dir(self.data_dir()).await?;
|
tokio::fs::create_dir(self.data_dir()).await?;
|
||||||
}
|
}
|
||||||
@ -235,10 +172,13 @@ impl Profile {
|
|||||||
util::clean_up_opts(self.data_dir().join("option"))?;
|
util::clean_up_opts(self.data_dir().join("option"))?;
|
||||||
|
|
||||||
let hash_check = Self::hash_check(&hash_path, &pkg_hash).await? || refresh;
|
let hash_check = Self::hash_check(&hash_path, &pkg_hash).await? || refresh;
|
||||||
|
|
||||||
prepare_packages(&self.meta, &self.data.mods, hash_check).await
|
prepare_packages(&self.meta, &self.data.mods, hash_check).await
|
||||||
.map_err(|e| anyhow!("package configuration failed:\n{:?}", e))?;
|
.map_err(|e| anyhow!("package configuration failed:\n{:?}", e))?;
|
||||||
|
|
||||||
let mut ini = self.data.sgt.line_up(&self.meta, self.meta.game).await
|
let mut ini = self.data.sgt.line_up(&self.meta, self.meta.game).await
|
||||||
.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(display) = &self.data.display {
|
if let Some(display) = &self.data.display {
|
||||||
@ -260,10 +200,18 @@ impl Profile {
|
|||||||
mu3ini.line_up(&self.data.sgt.target.parent().unwrap())?;
|
mu3ini.line_up(&self.data.sgt.target.parent().unwrap())?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let Some(patches) = &self.data.patches {
|
||||||
|
futures::try_join!(
|
||||||
|
patches.render_to_file("amdaemon.exe", patch_files, self.data_dir().join("patch-amd.mph")),
|
||||||
|
patches.render_to_file("chusanApp.exe", patch_files, self.data_dir().join("patch-game.mph"))
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn start(&self, app: AppHandle) -> Result<()> {
|
pub async fn start(&self, payload: StartPayload) -> Result<()> {
|
||||||
let ini_path = self.data_dir().join("segatools.ini");
|
let ini_path = self.data_dir().join("segatools.ini");
|
||||||
|
|
||||||
log::debug!("With path {:?}", ini_path);
|
log::debug!("With path {:?}", ini_path);
|
||||||
@ -294,10 +242,23 @@ impl Profile {
|
|||||||
&ini_path,
|
&ini_path,
|
||||||
)
|
)
|
||||||
.current_dir(&exe_dir)
|
.current_dir(&exe_dir)
|
||||||
.arg("/C")
|
.raw_arg("/C")
|
||||||
.arg(&sgt_dir.join(self.meta.game.inject_amd()))
|
.arg(&sgt_dir.join(self.meta.game.inject_amd()))
|
||||||
.args(["-d", "-k"])
|
.raw_arg("-d")
|
||||||
.arg(sgt_dir.join(self.meta.game.hook_amd()))
|
.raw_arg("-k")
|
||||||
|
.arg(sgt_dir.join(self.meta.game.hook_amd()));
|
||||||
|
|
||||||
|
// for dll in payload.amd_dlls {
|
||||||
|
// amd_builder.arg("-k");
|
||||||
|
// amd_builder.arg(dll);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// if self.meta.game.has_module(ProfileModule::Mempatcher) {
|
||||||
|
// amd_builder.arg("--mempatch");
|
||||||
|
// amd_builder.arg(self.data_dir().join("patch-amd.mph"));
|
||||||
|
// }
|
||||||
|
|
||||||
|
amd_builder
|
||||||
.arg("amdaemon.exe")
|
.arg("amdaemon.exe")
|
||||||
.args(self.meta.game.amd_args());
|
.args(self.meta.game.amd_args());
|
||||||
|
|
||||||
@ -317,9 +278,16 @@ impl Profile {
|
|||||||
self.config_dir().join("saekawa.toml"),
|
self.config_dir().join("saekawa.toml"),
|
||||||
)
|
)
|
||||||
.current_dir(&exe_dir)
|
.current_dir(&exe_dir)
|
||||||
.args(["-d", "-k"])
|
.raw_arg("-d")
|
||||||
.arg(sgt_dir.join(self.meta.game.hook_exe()))
|
.raw_arg("-k")
|
||||||
.arg(self.meta.game.exe());
|
.arg(sgt_dir.join(self.meta.game.hook_exe()));
|
||||||
|
|
||||||
|
for dll in payload.game_dlls {
|
||||||
|
game_builder.raw_arg("-k");
|
||||||
|
game_builder.arg(dll);
|
||||||
|
}
|
||||||
|
|
||||||
|
game_builder.arg(self.meta.game.exe());
|
||||||
|
|
||||||
if self.meta.game.has_module(ProfileModule::BepInEx) {
|
if self.meta.game.has_module(ProfileModule::BepInEx) {
|
||||||
if let Some(display) = &self.data.display {
|
if let Some(display) = &self.data.display {
|
||||||
@ -339,6 +307,11 @@ impl Profile {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if self.meta.game.has_module(ProfileModule::Mempatcher) {
|
||||||
|
game_builder.arg("--mempatch");
|
||||||
|
game_builder.arg(self.data_dir().join("patch-game.mph"));
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
{
|
{
|
||||||
amd_builder.env("WINEPREFIX", &self.wine.prefix);
|
amd_builder.env("WINEPREFIX", &self.wine.prefix);
|
||||||
@ -383,7 +356,7 @@ impl Profile {
|
|||||||
(game.wait().await.expect("game failed to run"), "game")
|
(game.wait().await.expect("game failed to run"), "game")
|
||||||
});
|
});
|
||||||
|
|
||||||
if let Err(e) = app.emit("launch-start", "") {
|
if let Err(e) = payload.app.emit("launch-start", "") {
|
||||||
log::warn!("Unable to emit launch-start: {}", e);
|
log::warn!("Unable to emit launch-start: {}", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -401,7 +374,7 @@ impl Profile {
|
|||||||
|
|
||||||
log::debug!("Fin");
|
log::debug!("Fin");
|
||||||
|
|
||||||
if let Err(e) = app.emit("launch-end", "") {
|
if let Err(e) = payload.app.emit("launch-end", "") {
|
||||||
log::warn!("Unable to emit launch-end: {}", e);
|
log::warn!("Unable to emit launch-end: {}", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
64
rust/src/profiles/types.rs
Normal file
64
rust/src/profiles/types.rs
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use tauri::AppHandle;
|
||||||
|
use std::{collections::BTreeSet, path::PathBuf};
|
||||||
|
use crate::{model::{misc::Game, patch::PatchSelection, profile::{Keyboard, Mu3Ini}}, pkg::PkgKey, util};
|
||||||
|
use crate::model::profile::BepInEx;
|
||||||
|
use crate::model::profile::{Display, Network, Segatools};
|
||||||
|
|
||||||
|
pub trait ProfilePaths {
|
||||||
|
fn config_dir(&self) -> PathBuf;
|
||||||
|
fn data_dir(&self) -> PathBuf;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Serialize, Clone, Debug, PartialEq)]
|
||||||
|
pub struct ProfileMeta {
|
||||||
|
pub game: Game,
|
||||||
|
pub name: String
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ProfilePaths for ProfileMeta {
|
||||||
|
fn config_dir(&self) -> PathBuf {
|
||||||
|
util::profile_config_dir(self.game, &self.name)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn data_dir(&self) -> PathBuf {
|
||||||
|
util::data_dir().join(format!("profile-{}-{}", &self.game, &self.name))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Serialize, Clone)]
|
||||||
|
pub struct Profile {
|
||||||
|
pub meta: ProfileMeta,
|
||||||
|
pub data: ProfileData,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Serialize, Clone, Debug)]
|
||||||
|
pub struct ProfileData {
|
||||||
|
pub mods: BTreeSet<PkgKey>,
|
||||||
|
pub sgt: Segatools,
|
||||||
|
pub network: Network,
|
||||||
|
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub display: Option<Display>,
|
||||||
|
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub bepinex: Option<BepInEx>,
|
||||||
|
|
||||||
|
#[cfg(not(target_os = "windows"))]
|
||||||
|
pub wine: crate::model::profile::Wine,
|
||||||
|
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub mu3_ini: Option<Mu3Ini>,
|
||||||
|
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub keyboard: Option<Keyboard>,
|
||||||
|
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub patches: Option<PatchSelection>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct StartPayload {
|
||||||
|
pub app: AppHandle,
|
||||||
|
pub game_dlls: Vec<PathBuf>,
|
||||||
|
pub amd_dlls: Vec<PathBuf>,
|
||||||
|
}
|
@ -90,8 +90,8 @@
|
|||||||
type: 'number',
|
type: 'number',
|
||||||
default: 3,
|
default: 3,
|
||||||
offset: 3768513,
|
offset: 3768513,
|
||||||
size: 4,
|
size: 1,
|
||||||
min: 3,
|
min: 1,
|
||||||
max: 12,
|
max: 12,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -78,7 +78,6 @@ const iconSrc = computed(() => {
|
|||||||
</span>
|
</span>
|
||||||
<span
|
<span
|
||||||
v-if="
|
v-if="
|
||||||
hasFeature(pkg, Feature.Mempatcher) ||
|
|
||||||
hasFeature(pkg, Feature.GameDLL) ||
|
hasFeature(pkg, Feature.GameDLL) ||
|
||||||
hasFeature(pkg, Feature.AmdDLL)
|
hasFeature(pkg, Feature.AmdDLL)
|
||||||
"
|
"
|
||||||
|
@ -3,37 +3,53 @@ import InputNumber from 'primevue/inputnumber';
|
|||||||
import ToggleSwitch from 'primevue/toggleswitch';
|
import ToggleSwitch from 'primevue/toggleswitch';
|
||||||
import OptionRow from './OptionRow.vue';
|
import OptionRow from './OptionRow.vue';
|
||||||
import { usePrfStore } from '../stores';
|
import { usePrfStore } from '../stores';
|
||||||
|
import { Patch } from '@/types';
|
||||||
|
|
||||||
const prf = usePrfStore();
|
const prf = usePrfStore();
|
||||||
|
|
||||||
const toggleUnary = (key: string, val: boolean) => {
|
const toggleUnary = (key: string, val: boolean) => {
|
||||||
if (val) {
|
if (val) {
|
||||||
prf.current!.data.patches[key] = 'Enabled';
|
prf.current!.data.patches[key] = 'enabled';
|
||||||
|
} else {
|
||||||
|
delete prf.current!.data.patches[key];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const setNumber = (key: string, val: number) => {
|
||||||
|
if (val) {
|
||||||
|
prf.current!.data.patches[key] = { number: val };
|
||||||
} else {
|
} else {
|
||||||
delete prf.current!.data.patches[key];
|
delete prf.current!.data.patches[key];
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
defineProps({
|
defineProps({
|
||||||
id: String,
|
patch: Object as () => Patch,
|
||||||
name: String,
|
|
||||||
tooltip: String,
|
|
||||||
type: String,
|
|
||||||
defaultValue: Number,
|
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<OptionRow :title="name" :tooltip="tooltip" :greytext="id">
|
<OptionRow
|
||||||
|
:title="patch?.name"
|
||||||
|
:tooltip="patch?.tooltip"
|
||||||
|
:greytext="patch?.id"
|
||||||
|
>
|
||||||
<ToggleSwitch
|
<ToggleSwitch
|
||||||
v-if="type === undefined"
|
v-if="patch?.type === undefined"
|
||||||
:model-value="prf.current!.data.patches[id!] !== undefined"
|
:model-value="prf.current!.data.patches[patch!.id!] !== undefined"
|
||||||
@update:model-value="(v: boolean) => toggleUnary(id!, v)"
|
@update:model-value="(v: boolean) => toggleUnary(patch!.id!, v)"
|
||||||
/>
|
/>
|
||||||
<InputNumber
|
<InputNumber
|
||||||
v-else
|
v-else-if="patch?.type === 'number'"
|
||||||
class="number-input"
|
class="number-input"
|
||||||
:placeholder="(defaultValue ?? 0).toString()"
|
:model-value="
|
||||||
|
(prf.current!.data.patches[patch!.id!] as { number: number })
|
||||||
|
?.number
|
||||||
|
"
|
||||||
|
@update:model-value="(v: number) => setNumber(patch!.id!, v)"
|
||||||
|
:min="patch?.min"
|
||||||
|
:max="patch?.max"
|
||||||
|
:placeholder="(patch?.default ?? 0).toString()"
|
||||||
/>
|
/>
|
||||||
</OptionRow>
|
</OptionRow>
|
||||||
</template>
|
</template>
|
||||||
|
@ -39,11 +39,7 @@ const errorMessage =
|
|||||||
<PatchEntry
|
<PatchEntry
|
||||||
v-if="gamePatches !== null"
|
v-if="gamePatches !== null"
|
||||||
v-for="p in gamePatches"
|
v-for="p in gamePatches"
|
||||||
:id="p.id"
|
:patch="p"
|
||||||
:title="p.name"
|
|
||||||
:tooltip="p.tooltip"
|
|
||||||
:type="p.type"
|
|
||||||
:default-value="p.default"
|
|
||||||
/>
|
/>
|
||||||
<div v-if="gamePatches === null">Loading...</div>
|
<div v-if="gamePatches === null">Loading...</div>
|
||||||
<div v-if="gamePatches !== null && gamePatches.length === 0">
|
<div v-if="gamePatches !== null && gamePatches.length === 0">
|
||||||
@ -54,11 +50,7 @@ const errorMessage =
|
|||||||
<PatchEntry
|
<PatchEntry
|
||||||
v-if="amdPatches !== null"
|
v-if="amdPatches !== null"
|
||||||
v-for="p in amdPatches"
|
v-for="p in amdPatches"
|
||||||
:id="p.id"
|
:patch="p"
|
||||||
:title="p.name"
|
|
||||||
:tooltip="p.tooltip"
|
|
||||||
:type="p.type"
|
|
||||||
:default-value="p.default"
|
|
||||||
/>
|
/>
|
||||||
<div v-if="gamePatches === null">Loading...</div>
|
<div v-if="gamePatches === null">Loading...</div>
|
||||||
<div v-if="amdPatches !== null && amdPatches.length === 0">
|
<div v-if="amdPatches !== null && amdPatches.length === 0">
|
||||||
|
@ -6,13 +6,14 @@ import OptionCategory from '../OptionCategory.vue';
|
|||||||
import OptionRow from '../OptionRow.vue';
|
import OptionRow from '../OptionRow.vue';
|
||||||
import { usePrfStore } from '../../stores';
|
import { usePrfStore } from '../../stores';
|
||||||
|
|
||||||
ToggleSwitch;
|
|
||||||
|
|
||||||
const prf = usePrfStore();
|
const prf = usePrfStore();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<OptionCategory title="Keyboard">
|
<OptionCategory title="Keyboard">
|
||||||
|
<OptionRow title="Enable">
|
||||||
|
<ToggleSwitch v-model="prf.current!.data.keyboard!.data.enabled" />
|
||||||
|
</OptionRow>
|
||||||
<OptionRow
|
<OptionRow
|
||||||
title="Lever mode"
|
title="Lever mode"
|
||||||
v-if="prf.current!.data.keyboard!.game === 'Ongeki'"
|
v-if="prf.current!.data.keyboard!.game === 'Ongeki'"
|
||||||
@ -28,7 +29,6 @@ const prf = usePrfStore();
|
|||||||
option-value="value"
|
option-value="value"
|
||||||
/>
|
/>
|
||||||
</OptionRow>
|
</OptionRow>
|
||||||
<OptionRow v-if="prf.current!.data.keyboard!.game === 'Chunithm'" />
|
|
||||||
<div
|
<div
|
||||||
:style="`position: relative; height: ${prf.current!.data.keyboard!.game === 'Ongeki' ? 400 : 250}px`"
|
:style="`position: relative; height: ${prf.current!.data.keyboard!.game === 'Ongeki' ? 400 : 250}px`"
|
||||||
>
|
>
|
||||||
|
@ -40,7 +40,7 @@ export type Status =
|
|||||||
| 'Unchecked'
|
| 'Unchecked'
|
||||||
| 'Unsupported'
|
| 'Unsupported'
|
||||||
| {
|
| {
|
||||||
OK: Feature;
|
OK: [Feature, String, String];
|
||||||
};
|
};
|
||||||
|
|
||||||
export type Game = 'ongeki' | 'chunithm';
|
export type Game = 'ongeki' | 'chunithm';
|
||||||
@ -59,7 +59,7 @@ export interface ProfileData {
|
|||||||
mu3_ini: Mu3IniConfig | undefined;
|
mu3_ini: Mu3IniConfig | undefined;
|
||||||
keyboard: KeyboardConfig | undefined;
|
keyboard: KeyboardConfig | undefined;
|
||||||
patches: {
|
patches: {
|
||||||
[key: string]: 'Enabled' | { Number: number } | { Hex: Int8Array };
|
[key: string]: 'enabled' | { number: number } | { hex: Int8Array };
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -112,6 +112,7 @@ export interface Mu3IniConfig {
|
|||||||
|
|
||||||
export interface OngekiButtons {
|
export interface OngekiButtons {
|
||||||
use_mouse: boolean;
|
use_mouse: boolean;
|
||||||
|
enabled: boolean;
|
||||||
coin: number;
|
coin: number;
|
||||||
svc: number;
|
svc: number;
|
||||||
test: number;
|
test: number;
|
||||||
@ -128,6 +129,7 @@ export interface OngekiButtons {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface ChunithmButtons {
|
export interface ChunithmButtons {
|
||||||
|
enabled: boolean;
|
||||||
coin: number;
|
coin: number;
|
||||||
svc: number;
|
svc: number;
|
||||||
test: number;
|
test: number;
|
||||||
@ -164,4 +166,6 @@ export interface Patch {
|
|||||||
tooltip: string;
|
tooltip: string;
|
||||||
type: undefined | 'number';
|
type: undefined | 'number';
|
||||||
default: number;
|
default: number;
|
||||||
|
min: number;
|
||||||
|
max: number;
|
||||||
}
|
}
|
||||||
|
@ -52,7 +52,7 @@ export const hasFeature = (pkg: Package | undefined, feature: Feature) => {
|
|||||||
pkg.loc !== null &&
|
pkg.loc !== null &&
|
||||||
pkg.loc !== undefined &&
|
pkg.loc !== undefined &&
|
||||||
typeof pkg.loc?.status !== 'string' &&
|
typeof pkg.loc?.status !== 'string' &&
|
||||||
pkg.loc.status.OK & feature
|
pkg.loc.status.OK[0] & feature
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user