forked from akanyan/STARTLINER
554 lines
16 KiB
Rust
554 lines
16 KiB
Rust
use ini::Ini;
|
|
use log;
|
|
use std::collections::{BTreeMap, HashMap};
|
|
use std::path::PathBuf;
|
|
use tokio::sync::Mutex;
|
|
use tokio::fs;
|
|
use tauri::{AppHandle, Manager, State};
|
|
use crate::model::config::GlobalConfigField;
|
|
use crate::model::misc::Game;
|
|
use crate::model::patch::Patch;
|
|
use crate::modules::package::prepare_dlls;
|
|
use crate::pkg::{Package, PkgKey};
|
|
use crate::pkg_store::{InstallResult, PackageStore};
|
|
use crate::profiles::{self, Profile, ProfileData, ProfileMeta, ProfilePaths, StartPayload};
|
|
use crate::appdata::{AppData, ToggleAction};
|
|
use crate::model::misc::StartCheckError;
|
|
use crate::util;
|
|
|
|
#[tauri::command]
|
|
pub async fn start_check(state: State<'_, Mutex<AppData>>) -> Result<Vec<StartCheckError>, String> {
|
|
log::debug!("invoke: start_check");
|
|
|
|
let appd = state.lock().await;
|
|
let prf = appd.profile.as_ref().ok_or_else(|| format!("No profile to check"))?;
|
|
let pkgs = appd.pkgs.get_all();
|
|
|
|
let mut res = Vec::new();
|
|
for key in prf.mod_pkgs() {
|
|
if let Some(pkg) = pkgs.get(key) {
|
|
if let Some(loc) = &pkg.loc {
|
|
for dep in &loc.dependencies {
|
|
if !prf.mod_pkgs().contains(dep) {
|
|
res.push(StartCheckError::MissingDependency(key.clone(), dep.clone()));
|
|
}
|
|
}
|
|
} else {
|
|
res.push(StartCheckError::MissingLocalPackage(key.clone()));
|
|
}
|
|
} else {
|
|
res.push(StartCheckError::MissingRemotePackage(key.clone()));
|
|
}
|
|
}
|
|
for key in prf.special_pkgs() {
|
|
log::debug!("special: {}", key);
|
|
if let Some(pkg) = pkgs.get(&key) {
|
|
if pkg.loc.is_some() {
|
|
continue;
|
|
}
|
|
}
|
|
res.push(StartCheckError::MissingTool(key));
|
|
}
|
|
|
|
Ok(res)
|
|
}
|
|
|
|
#[tauri::command]
|
|
pub async fn startline(app: AppHandle, refresh: bool) -> Result<(), String> {
|
|
log::debug!("invoke: startline");
|
|
|
|
let state = app.state::<Mutex<AppData>>();
|
|
let mut hash = "".to_owned();
|
|
|
|
let appd = state.lock().await;
|
|
let mut game_dlls = Vec::new();
|
|
let mut amd_dlls = Vec::new();
|
|
if let Some(p) = &appd.profile {
|
|
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) = &appd.profile {
|
|
log::debug!("{}", hash);
|
|
|
|
let patches_enabled = appd.patches_enabled(
|
|
&p.data.sgt.target,
|
|
&p.data.sgt.target.parent().unwrap().join("amdaemon.exe")
|
|
).map_err(|e| e.to_string())?;
|
|
|
|
let info = p.prepare_display()
|
|
.map_err(|e| e.to_string())?;
|
|
let lineup_res = p.line_up(hash, refresh, patches_enabled).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 p_clone = p.clone();
|
|
tauri::async_runtime::spawn(async move {
|
|
if let Err(e) = p_clone.start(StartPayload {
|
|
app: app_clone,
|
|
game_dlls,
|
|
amd_dlls
|
|
}).await {
|
|
log::error!("Startup failed:\n{}", e);
|
|
}
|
|
});
|
|
|
|
Ok(())
|
|
} else {
|
|
Err("No profile".to_owned())
|
|
}
|
|
|
|
}
|
|
|
|
#[tauri::command]
|
|
pub async fn kill() -> Result<(), String> {
|
|
util::pkill("amdaemon.exe").await;
|
|
// The start routine will kill the other process
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[tauri::command]
|
|
pub async fn install_package(
|
|
state: State<'_, tokio::sync::Mutex<AppData>>,
|
|
key: PkgKey,
|
|
force: bool
|
|
) -> Result<InstallResult, String> {
|
|
log::debug!("invoke: install_package({})", key);
|
|
|
|
let mut appd = state.lock().await;
|
|
appd.pkgs.install_package(&key, force, true)
|
|
.await
|
|
.map_err(|e| e.to_string())
|
|
}
|
|
|
|
#[tauri::command]
|
|
pub async fn delete_package(state: State<'_, tokio::sync::Mutex<AppData>>, key: PkgKey) -> Result<(), String> {
|
|
log::debug!("invoke: delete_package({})", key);
|
|
|
|
let mut appd = state.lock().await;
|
|
appd.pkgs.delete_package(&key, true)
|
|
.await
|
|
.map_err(|e| e.to_string())?;
|
|
|
|
appd.toggle_package(key, ToggleAction::Disable)
|
|
.map_err(|e| e.to_string())
|
|
}
|
|
|
|
#[tauri::command]
|
|
pub async fn get_package(state: State<'_, tokio::sync::Mutex<AppData>>, key: PkgKey) -> Result<Package, String> {
|
|
log::debug!("invoke: get_package({})", key);
|
|
|
|
let appd = state.lock().await;
|
|
appd.pkgs.get(&key)
|
|
.map_err(|e| e.to_string())
|
|
.cloned()
|
|
}
|
|
|
|
#[tauri::command]
|
|
pub async fn toggle_package(state: State<'_, tokio::sync::Mutex<AppData>>, key: PkgKey, enable: bool) -> Result<(), String> {
|
|
log::debug!("invoke: toggle_package({}, {})", key, enable);
|
|
|
|
let mut appd = state.lock().await;
|
|
appd.toggle_package(key, if enable { ToggleAction::EnableRecursive } else { ToggleAction::Disable })
|
|
.map_err(|e| e.to_string())
|
|
}
|
|
|
|
#[tauri::command]
|
|
pub async fn reload_all_packages(state: State<'_, tokio::sync::Mutex<AppData>>) -> Result<(), String> {
|
|
log::debug!("invoke: reload_all_packages");
|
|
|
|
let mut appd = state.lock().await;
|
|
appd.pkgs.reload_all()
|
|
.await
|
|
.map_err(|e| e.to_string())
|
|
}
|
|
|
|
#[tauri::command]
|
|
pub async fn get_all_packages(state: State<'_, Mutex<AppData>>) -> Result<HashMap<PkgKey, Package>, ()> {
|
|
log::debug!("invoke: get_all_packages");
|
|
|
|
let appd = state.lock().await;
|
|
|
|
let pkgs_all = appd.pkgs.get_all();
|
|
|
|
log::debug!("pkgs_all: {:?}", pkgs_all);
|
|
|
|
Ok(appd.pkgs.get_all())
|
|
}
|
|
|
|
|
|
#[tauri::command]
|
|
pub async fn get_game_packages(state: State<'_, Mutex<AppData>>, game: Game) -> Result<Vec<PkgKey>, ()> {
|
|
log::debug!("invoke: get_game_packages {game}");
|
|
|
|
let appd = state.lock().await;
|
|
|
|
Ok(appd.pkgs.get_game_list(game))
|
|
}
|
|
|
|
#[tauri::command]
|
|
pub async fn fetch_listings(state: State<'_, Mutex<AppData>>) -> Result<(), String> {
|
|
log::debug!("invoke: fetch_listings");
|
|
|
|
// let game;
|
|
{
|
|
let appd = state.lock().await;
|
|
if !appd.pkgs.is_offline() {
|
|
log::warn!("fetch_listings: already done");
|
|
return Ok(());
|
|
}
|
|
if appd.cfg.offline_mode {
|
|
log::info!("fetch_listings: skipped");
|
|
return Err("offline mode".to_owned());
|
|
}
|
|
// if let Some(profile) = &appd.profile {
|
|
// game = profile.meta.game;
|
|
// } else {
|
|
// return Err("No profile".to_owned());
|
|
// }
|
|
}
|
|
|
|
// Can be this lazy for now as there are only two short lists
|
|
let listings1 = PackageStore::fetch_listings(Game::Ongeki).await
|
|
.map_err(|e| e.to_string())?;
|
|
|
|
let listings2 = PackageStore::fetch_listings(Game::Chunithm).await
|
|
.map_err(|e| e.to_string())?;
|
|
|
|
let mut appd = state.lock().await;
|
|
appd.pkgs.process_fetched_listings(listings1, Game::Ongeki);
|
|
appd.pkgs.process_fetched_listings(listings2, Game::Chunithm);
|
|
|
|
appd.pkgs.save().await
|
|
.map_err(|e| e.to_string())?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[tauri::command]
|
|
pub async fn list_profiles() -> Result<Vec<ProfileMeta>, String> {
|
|
log::debug!("invoke: list_profiles");
|
|
|
|
let list = crate::profiles::list_profiles().await.map_err(|e| e.to_string())?;
|
|
Ok(list)
|
|
}
|
|
|
|
#[tauri::command]
|
|
pub async fn init_profile(
|
|
state: State<'_, Mutex<AppData>>,
|
|
game: Game,
|
|
name: String
|
|
) -> Result<(), String> {
|
|
log::debug!("invoke: init_profile({}, {})", game, name);
|
|
|
|
let mut appd = state.lock().await;
|
|
let new_profile = Profile::new(ProfileMeta { game, name })
|
|
.map_err(|e| format!("Unable to create profile: {}", e))?;
|
|
|
|
appd.profile = Some(new_profile);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[tauri::command]
|
|
pub async fn load_profile(state: State<'_, Mutex<AppData>>, game: Game, name: String) -> Result<(), String> {
|
|
log::debug!("invoke: load_profile({} {:?})", game, name);
|
|
|
|
let mut appd = state.lock().await;
|
|
appd.switch_profile(game, name).map_err(|e| e.to_string())?;
|
|
appd.fix();
|
|
Ok(())
|
|
}
|
|
|
|
#[tauri::command]
|
|
pub async fn rename_profile(
|
|
state: State<'_, Mutex<AppData>>,
|
|
profile: ProfileMeta,
|
|
name: String
|
|
) -> Result<(), String> {
|
|
log::debug!("invoke: rename_profile({:?} {:?})", profile, name);
|
|
|
|
let new_meta = ProfileMeta {
|
|
game: profile.game.clone(),
|
|
name: profiles::fixed_name(&ProfileMeta { game: profile.game.clone(), name }, false)
|
|
};
|
|
|
|
if new_meta.name == profile.name {
|
|
return Ok(());
|
|
}
|
|
|
|
if new_meta.config_dir().exists() {
|
|
return Err(format!("Profile {} already exists", &new_meta.name));
|
|
}
|
|
|
|
fs::rename(profile.config_dir(), new_meta.config_dir()).await
|
|
.map_err(|e| format!("Unable to rename: {}", e))?;
|
|
|
|
if let Err(e) = fs::rename(profile.data_dir(), new_meta.data_dir()).await {
|
|
log::warn!("Unable to move data dir {}->{}: {}", &profile.name, &new_meta.name, e);
|
|
}
|
|
|
|
let mut appd = state.lock().await;
|
|
if let Some(current) = &mut appd.profile {
|
|
if current.meta == profile {
|
|
current.rename(new_meta.name);
|
|
}
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[tauri::command]
|
|
pub async fn duplicate_profile(profile: ProfileMeta) -> Result<(), String> {
|
|
log::debug!("invoke: duplicate_profile({:?})", profile);
|
|
|
|
let new_meta = ProfileMeta {
|
|
game: profile.game.clone(),
|
|
name: profiles::fixed_name(&profile, true)
|
|
};
|
|
|
|
util::copy_directory(profile.config_dir(), new_meta.config_dir(), false)
|
|
.map_err(|e| format!("Unable to duplicate: {}", e))?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[tauri::command]
|
|
pub async fn delete_profile(state: State<'_, Mutex<AppData>>, profile: ProfileMeta) -> Result<(), String> {
|
|
log::debug!("invoke: delete_profile({:?})", profile);
|
|
|
|
util::remove_dir_all(profile.config_dir())
|
|
.await
|
|
.map_err(|e| format!("Unable to delete {:?}: {}", profile.config_dir(), e))?;
|
|
if let Err(e) = util::remove_dir_all(profile.data_dir()).await {
|
|
log::warn!("Unable to delete: {:?} {}", profile.data_dir(), e);
|
|
}
|
|
|
|
let mut appd = state.lock().await;
|
|
if let Some(current) = &mut appd.profile {
|
|
if current.meta == profile {
|
|
appd.profile = None;
|
|
}
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[tauri::command]
|
|
pub async fn get_current_profile(state: State<'_, Mutex<AppData>>) -> Result<Option<Profile>, ()> {
|
|
log::debug!("invoke: get_current_profile");
|
|
|
|
let appd = state.lock().await;
|
|
Ok(appd.profile.clone())
|
|
}
|
|
|
|
#[tauri::command]
|
|
pub async fn sync_current_profile(state: State<'_, Mutex<AppData>>, data: ProfileData) -> Result<(), String> {
|
|
log::debug!("invoke: sync_current_profile {:?}", data);
|
|
|
|
let mut appd = state.lock().await;
|
|
if let Some(p) = &mut appd.profile {
|
|
p.sync(data);
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[tauri::command]
|
|
pub async fn save_current_profile(state: State<'_, Mutex<AppData>>) -> Result<(), String> {
|
|
log::debug!("invoke: save_current_profile");
|
|
|
|
let mut appd = state.lock().await;
|
|
appd.fix();
|
|
match &mut appd.profile {
|
|
Some(p) => {
|
|
p.save().map_err(|e| e.to_string())
|
|
},
|
|
None => {
|
|
Err("no profile to save".to_owned())
|
|
}
|
|
}
|
|
}
|
|
|
|
#[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.config_dir()).map_err(|e| e.to_string())?;
|
|
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]
|
|
pub async fn create_shortcut(app: AppHandle, profile_meta: ProfileMeta) -> Result<(), String> {
|
|
log::debug!("invoke: create_shortcut({:?})", profile_meta);
|
|
|
|
util::create_shortcut(app, &profile_meta).map_err(|e| e.to_string())
|
|
}
|
|
|
|
#[tauri::command]
|
|
pub async fn export_profile(state: State<'_, Mutex<AppData>>, export_keychip: bool, files: Vec<String>) -> Result<(), String> {
|
|
log::debug!("invoke: export_profile({:?}, {:?} files)", export_keychip, files.len());
|
|
|
|
let appd = state.lock().await;
|
|
match &appd.profile {
|
|
Some(p) => {
|
|
p.export(export_keychip, files)
|
|
.map_err(|e| e.to_string())?;
|
|
}
|
|
None => {
|
|
let err = "export_profile: no profile".to_owned();
|
|
log::error!("{}", err);
|
|
return Err(err);
|
|
}
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[tauri::command]
|
|
pub async fn import_profile(path: PathBuf) -> Result<(), String> {
|
|
log::debug!("invoke: import_profile({:?})", path);
|
|
|
|
Profile::import(path).map_err(|e| e.to_string())
|
|
}
|
|
|
|
#[tauri::command]
|
|
pub async fn list_platform_capabilities() -> Result<Vec<String>, ()> {
|
|
log::debug!("invoke: list_platform_capabilities");
|
|
|
|
#[cfg(target_os = "windows")]
|
|
return Ok(vec!["display".to_owned()]);
|
|
|
|
#[cfg(target_os = "linux")]
|
|
return Ok(vec!["wine".to_owned()]);
|
|
}
|
|
|
|
#[tauri::command]
|
|
pub async fn get_global_config(state: State<'_, Mutex<AppData>>, field: GlobalConfigField) -> Result<bool, ()> {
|
|
log::debug!("invoke: get_global_config({field:?})");
|
|
|
|
let appd = state.lock().await;
|
|
match field {
|
|
GlobalConfigField::OfflineMode => Ok(appd.cfg.offline_mode),
|
|
GlobalConfigField::EnableAutoupdates => Ok(appd.cfg.enable_autoupdates),
|
|
GlobalConfigField::Verbose => Ok(appd.cfg.verbose)
|
|
}
|
|
}
|
|
|
|
#[tauri::command]
|
|
pub async fn set_global_config(state: State<'_, Mutex<AppData>>, field: GlobalConfigField, value: bool) -> Result<(), String> {
|
|
log::debug!("invoke: set_global_config({field:?}, {value})");
|
|
|
|
let mut appd = state.lock().await;
|
|
match field {
|
|
GlobalConfigField::OfflineMode => appd.cfg.offline_mode = value,
|
|
GlobalConfigField::EnableAutoupdates => appd.cfg.enable_autoupdates = value,
|
|
GlobalConfigField::Verbose => appd.cfg.verbose = value,
|
|
};
|
|
appd.write().map_err(|e| e.to_string())
|
|
}
|
|
|
|
#[tauri::command]
|
|
#[cfg(target_os = "windows")]
|
|
pub async fn list_displays() -> Result<Vec<(String, String)>, String> {
|
|
use winsafe::prelude::NativeBitflag;
|
|
|
|
log::debug!("invoke: list_displays");
|
|
|
|
let mut res = Vec::new();
|
|
for displ_dev in winsafe::EnumDisplayDevices(None, None) {
|
|
if let Ok(displ_dev) = displ_dev {
|
|
if displ_dev.StateFlags.has(winsafe::co::DISPLAY_DEVICE::ATTACHED_TO_DESKTOP) {
|
|
res.push((displ_dev.DeviceName(), displ_dev.DeviceString()));
|
|
}
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
|
|
Ok(res)
|
|
}
|
|
|
|
#[tauri::command]
|
|
#[cfg(not(target_os = "windows"))]
|
|
pub async fn list_displays() -> Result<Vec<String>, ()> {
|
|
log::debug!("invoke: list_displays");
|
|
|
|
Ok(Vec::new())
|
|
}
|
|
|
|
#[tauri::command]
|
|
pub async fn list_directories() -> Result<util::Dirs, ()> {
|
|
log::debug!("invoke: list_directores");
|
|
|
|
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))
|
|
}
|
|
|
|
// Easier than trying to get the barely-documented tauri permissions system to work
|
|
#[tauri::command]
|
|
pub async fn open_file(path: String) -> Result<(), String> {
|
|
open::that(path).map_err(|e| e.to_string())?;
|
|
Ok(())
|
|
}
|
|
|
|
#[tauri::command]
|
|
pub fn get_changelog() -> Result<String, ()> {
|
|
Ok(include_str!("../../CHANGELOG.md").to_owned())
|
|
}
|
|
|
|
#[tauri::command]
|
|
pub async fn list_com_ports() -> Result<BTreeMap<String, i32>, String> {
|
|
let ports = serialport::available_ports().unwrap_or(Vec::new());
|
|
let mut res = BTreeMap::new();
|
|
for p in ports {
|
|
log::debug!("port {}", p.port_name);
|
|
if p.port_name.starts_with("COM") {
|
|
if let Ok(parsed) = (p.port_name[3..]).parse() {
|
|
res.insert(p.port_name, parsed);
|
|
}
|
|
}
|
|
}
|
|
Ok(res)
|
|
}
|
|
|
|
#[tauri::command]
|
|
pub async fn list_patches(state: State<'_, Mutex<AppData>>, target: String) -> Result<Vec<Patch>, String> {
|
|
log::debug!("invoke: list_patches({})", target);
|
|
|
|
let mut appd = state.lock().await;
|
|
appd.fix();
|
|
let list = appd.patch_vec.find_patches(target).map_err(|e| e.to_string())?;
|
|
|
|
Ok(list)
|
|
} |