feat(games): Added multi-argument game launch and setup support

This commit is contained in:
quexeky
2025-01-20 20:03:44 +11:00
parent 9831d96300
commit 48cbd1a5ed
4 changed files with 35 additions and 64 deletions
+12 -2
View File
@@ -57,13 +57,23 @@ pub enum ApplicationTransientStatus {
#[derive(Serialize, Deserialize, Clone, Debug)] #[derive(Serialize, Deserialize, Clone, Debug)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct GameVersion { pub struct GameVersion {
pub version_index: usize, pub game_id: String,
pub version_name: String, pub version_name: String,
pub platform: Platform,
pub launch_command: String, pub launch_command: String,
pub launch_args: Vec<String>, pub launch_args: Vec<String>,
pub setup_command: String, pub setup_command: String,
pub setup_args: Vec<String>, pub setup_args: Vec<String>,
pub platform: Platform,
pub only_setup: bool,
pub version_index: usize,
pub delta: bool,
pub umu_id_override: Option<String>,
} }
#[serde_as] #[serde_as]
+3 -5
View File
@@ -3,15 +3,13 @@ use std::sync::Mutex;
use tauri::AppHandle; use tauri::AppHandle;
use crate::{ use crate::{
error::{library_error::LibraryError, remote_access_error::RemoteAccessError}, database::db::GameVersion, error::{library_error::LibraryError, remote_access_error::RemoteAccessError}, games::library::{get_current_meta, uninstall_game_logic}, AppState
games::library::{get_current_meta, uninstall_game_logic},
AppState,
}; };
use super::{ use super::{
library::{ library::{
fetch_game_logic, fetch_game_verion_options_logic, fetch_library_logic, FetchGameStruct, fetch_game_logic, fetch_game_verion_options_logic, fetch_library_logic, FetchGameStruct,
Game, GameVersionOption, Game,
}, },
state::{GameStatusManager, GameStatusWithTransient}, state::{GameStatusManager, GameStatusWithTransient},
}; };
@@ -50,6 +48,6 @@ pub fn uninstall_game(game_id: String, app_handle: AppHandle) -> Result<(), Libr
pub fn fetch_game_verion_options( pub fn fetch_game_verion_options(
game_id: String, game_id: String,
state: tauri::State<'_, Mutex<AppState>>, state: tauri::State<'_, Mutex<AppState>>,
) -> Result<Vec<GameVersionOption>, RemoteAccessError> { ) -> Result<Vec<GameVersion>, RemoteAccessError> {
fetch_game_verion_options_logic(game_id, state) fetch_game_verion_options_logic(game_id, state)
} }
+5 -31
View File
@@ -6,18 +6,16 @@ use log::{debug, error, warn};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use tauri::Emitter; use tauri::Emitter;
use tauri::{AppHandle, Manager}; use tauri::{AppHandle, Manager};
use urlencoding::encode;
use crate::database::db::{borrow_db_checked, borrow_db_mut_checked, save_db, GameVersion}; use crate::database::db::{borrow_db_checked, borrow_db_mut_checked, save_db, GameVersion};
use crate::database::db::{ApplicationTransientStatus, DatabaseImpls, GameDownloadStatus}; use crate::database::db::{ApplicationTransientStatus, GameDownloadStatus};
use crate::download_manager::download_manager::DownloadStatus; use crate::download_manager::download_manager::DownloadStatus;
use crate::download_manager::downloadable_metadata::DownloadableMetadata; use crate::download_manager::downloadable_metadata::DownloadableMetadata;
use crate::error::remote_access_error::RemoteAccessError; use crate::error::remote_access_error::RemoteAccessError;
use crate::games::state::{GameStatusManager, GameStatusWithTransient}; use crate::games::state::{GameStatusManager, GameStatusWithTransient};
use crate::process::process_manager::Platform;
use crate::remote::auth::generate_authorization_header; use crate::remote::auth::generate_authorization_header;
use crate::remote::requests::make_request; use crate::remote::requests::make_request;
use crate::{AppState, DB}; use crate::AppState;
#[derive(serde::Serialize)] #[derive(serde::Serialize)]
pub struct FetchGameStruct { pub struct FetchGameStruct {
@@ -68,30 +66,6 @@ pub struct StatsUpdateEvent {
pub time: usize, pub time: usize,
} }
// Game version with some fields missing and size information
#[derive(serde::Deserialize, serde::Serialize, Debug)]
#[serde(rename_all = "camelCase")]
pub struct GameVersionOption {
game_id: String,
version_name: String,
platform: Platform,
launch_command: String,
launch_args: Vec<String>,
setup_command: String,
setup_args: Vec<String>,
only_setup: bool,
version_index: usize,
delta: bool,
umu_id_override: Option<String>,
// total_size: usize,
}
pub fn fetch_library_logic(app: AppHandle) -> Result<Vec<Game>, RemoteAccessError> { pub fn fetch_library_logic(app: AppHandle) -> Result<Vec<Game>, RemoteAccessError> {
let header = generate_authorization_header(); let header = generate_authorization_header();
@@ -187,7 +161,7 @@ pub fn fetch_game_logic(
pub fn fetch_game_verion_options_logic( pub fn fetch_game_verion_options_logic(
game_id: String, game_id: String,
state: tauri::State<'_, Mutex<AppState>>, state: tauri::State<'_, Mutex<AppState>>,
) -> Result<Vec<GameVersionOption>, RemoteAccessError> { ) -> Result<Vec<GameVersion>, RemoteAccessError> {
let client = reqwest::blocking::Client::new(); let client = reqwest::blocking::Client::new();
let response = make_request( let response = make_request(
@@ -204,11 +178,11 @@ pub fn fetch_game_verion_options_logic(
return Err(RemoteAccessError::InvalidResponse(err)); return Err(RemoteAccessError::InvalidResponse(err));
} }
let data: Vec<GameVersionOption> = response.json()?; let data: Vec<GameVersion> = response.json()?;
let state_lock = state.lock().unwrap(); let state_lock = state.lock().unwrap();
let process_manager_lock = state_lock.process_manager.lock().unwrap(); let process_manager_lock = state_lock.process_manager.lock().unwrap();
let data: Vec<GameVersionOption> = data let data: Vec<GameVersion> = data
.into_iter() .into_iter()
.filter(|v| process_manager_lock.valid_platform(&v.platform).unwrap()) .filter(|v| process_manager_lock.valid_platform(&v.platform).unwrap())
.collect(); .collect();
@@ -16,7 +16,7 @@ use umu_wrapper_lib::command_builder::UmuCommandBuilder;
use crate::{ use crate::{
database::db::{ database::db::{
borrow_db_mut_checked, ApplicationTransientStatus, GameDownloadStatus, DATA_ROOT_DIR, borrow_db_mut_checked, ApplicationTransientStatus, GameDownloadStatus, GameVersion, DATA_ROOT_DIR
}, },
download_manager::downloadable_metadata::{DownloadType, DownloadableMetadata}, download_manager::downloadable_metadata::{DownloadType, DownloadableMetadata},
error::process_error::ProcessError, error::process_error::ProcessError,
@@ -66,10 +66,6 @@ impl ProcessManager<'_> {
} }
} }
// There's no easy way to distinguish between an executable name with
// spaces and it's arguments.
// I think if we just join the install_dir to whatever the user provides us, we'll be alright
// In future, we should have a separate field for executable name and it's arguments
fn process_command(&self, install_dir: &String, command: Vec<String>) -> (PathBuf, Vec<String>) { fn process_command(&self, install_dir: &String, command: Vec<String>) -> (PathBuf, Vec<String>) {
let root = &command[0]; let root = &command[0];
@@ -194,11 +190,7 @@ impl ProcessManager<'_> {
GameDownloadStatus::Installed { GameDownloadStatus::Installed {
version_name, version_name,
install_dir, install_dir,
<<<<<<< Updated upstream
} => Some((version_name, install_dir)),
=======
} => (version_name, install_dir), } => (version_name, install_dir),
>>>>>>> Stashed changes
GameDownloadStatus::SetupRequired { GameDownloadStatus::SetupRequired {
version_name, version_name,
install_dir, install_dir,
@@ -222,18 +214,19 @@ impl ProcessManager<'_> {
version_name: _, version_name: _,
install_dir: _, install_dir: _,
} => { } => {
command.extend_one(game_version.launch_command); command.extend([game_version.launch_command.clone()]);
command.extend(game_version.launch_args); command.extend(game_version.launch_args.clone());
}, },
GameDownloadStatus::SetupRequired { GameDownloadStatus::SetupRequired {
version_name: _, version_name: _,
install_dir: _, install_dir: _,
} => { } => {
command.extend_one(game_version.setup_command); command.extend([game_version.setup_command.clone()]);
command.extend(game_version.setup_args); command.extend(game_version.setup_args.clone());
}, },
_ => panic!("unreachable code"), _ => panic!("unreachable code"),
}; };
info!("Command: {:?}", &command);
let (command, args) = self.process_command(install_dir, command); let (command, args) = self.process_command(install_dir, command);
@@ -283,8 +276,7 @@ impl ProcessManager<'_> {
let launch_process = game_launcher let launch_process = game_launcher
.launch_process( .launch_process(
&meta, &meta,
command.to_str().unwrap().to_owned(), game_version,
args,
target_current_dir, target_current_dir,
log_file, log_file,
error_file, error_file,
@@ -339,8 +331,7 @@ pub trait ProcessHandler: Send + 'static {
fn launch_process( fn launch_process(
&self, &self,
meta: &DownloadableMetadata, meta: &DownloadableMetadata,
command: String, game_version: &GameVersion,
args: Vec<String>,
current_dir: &str, current_dir: &str,
log_file: File, log_file: File,
error_file: File, error_file: File,
@@ -352,17 +343,16 @@ impl ProcessHandler for NativeGameLauncher {
fn launch_process( fn launch_process(
&self, &self,
_meta: &DownloadableMetadata, _meta: &DownloadableMetadata,
command: String, game_version: &GameVersion,
args: Vec<String>,
current_dir: &str, current_dir: &str,
log_file: File, log_file: File,
error_file: File, error_file: File,
) -> Result<Child, Error> { ) -> Result<Child, Error> {
Command::new(command) Command::new(game_version.launch_command.clone())
.current_dir(current_dir) .current_dir(current_dir)
.stdout(log_file) .stdout(log_file)
.stderr(error_file) .stderr(error_file)
.args(args) .args(game_version.launch_args.clone())
.spawn() .spawn()
} }
} }
@@ -373,15 +363,14 @@ impl ProcessHandler for UMULauncher {
fn launch_process( fn launch_process(
&self, &self,
_meta: &DownloadableMetadata, _meta: &DownloadableMetadata,
command: String, game_version: &GameVersion,
args: Vec<String>,
_current_dir: &str, _current_dir: &str,
_log_file: File, _log_file: File,
_error_file: File, _error_file: File,
) -> Result<Child, Error> { ) -> Result<Child, Error> {
UmuCommandBuilder::new(UMU_LAUNCHER_EXECUTABLE, command) UmuCommandBuilder::new(UMU_LAUNCHER_EXECUTABLE, game_version.launch_command.clone())
.game_id(String::from("0")) .game_id(String::from(game_version.umu_id_override.clone().unwrap_or(game_version.game_id.clone())))
.launch_args(args) .launch_args(game_version.launch_args.clone())
.build() .build()
.spawn() .spawn()
} }