diff options
Diffstat (limited to 'src-tauri/src')
-rw-r--r-- | src-tauri/src/constants.rs | 36 | ||||
-rw-r--r-- | src-tauri/src/development/mod.rs | 2 | ||||
-rw-r--r-- | src-tauri/src/github/mod.rs | 1 | ||||
-rw-r--r-- | src-tauri/src/github/pull_requests.rs | 22 | ||||
-rw-r--r-- | src-tauri/src/main.rs | 19 | ||||
-rw-r--r-- | src-tauri/src/mod_management/legacy.rs | 2 | ||||
-rw-r--r-- | src-tauri/src/mod_management/mod.rs | 48 | ||||
-rw-r--r-- | src-tauri/src/mod_management/plugins.rs | 26 | ||||
-rw-r--r-- | src-tauri/src/northstar/install.rs | 79 | ||||
-rw-r--r-- | src-tauri/src/northstar/mod.rs | 36 | ||||
-rw-r--r-- | src-tauri/src/northstar/profile.rs | 76 | ||||
-rw-r--r-- | src-tauri/src/repair_and_verify/mod.rs | 49 | ||||
-rw-r--r-- | src-tauri/src/util.rs | 62 |
13 files changed, 357 insertions, 101 deletions
diff --git a/src-tauri/src/constants.rs b/src-tauri/src/constants.rs index 9917a8c8..33aefac8 100644 --- a/src-tauri/src/constants.rs +++ b/src-tauri/src/constants.rs @@ -2,53 +2,63 @@ use const_format::concatcp; use std::time::Duration; -// FlightCore user agent for web requests +/// FlightCore user agent for web requests pub const APP_USER_AGENT: &str = concatcp!("FlightCore/", env!("CARGO_PKG_VERSION")); -// URL of the Northstar masterserver +/// URL of the Northstar masterserver pub const MASTER_SERVER_URL: &str = "https://northstar.tf"; -// server list endpoint +/// server list endpoint pub const SERVER_BROWSER_ENDPOINT: &str = "/client/servers"; -// List of core Northstar mods +/// List of core Northstar mods pub const CORE_MODS: [&str; 3] = [ "Northstar.Client", "Northstar.Custom", "Northstar.CustomServers", ]; -// List of Thunderstoremods that shouldn't be installable -// as they behave different than common Squirrel mods +/// List of Thunderstoremods that shouldn't be installable +/// as they behave different than common Squirrel mods pub const BLACKLISTED_MODS: [&str; 3] = [ "northstar-Northstar", "northstar-NorthstarReleaseCandidate", "ebkr-r2modman", ]; -// Titanfall2 Steam App ID +/// Titanfall2 Steam App ID pub const TITANFALL2_STEAM_ID: &str = "1237970"; -// Order in which the sections for release notes should be displayed +/// Order in which the sections for release notes should be displayed pub const SECTION_ORDER: [&str; 10] = [ "feat", "fix", "docs", "style", "refactor", "build", "test", "i18n", "chore", "other", ]; -// GitHub API endpoints for launcher/mods PRs +/// GitHub API endpoints for launcher/mods PRs pub const PULLS_API_ENDPOINT_LAUNCHER: &str = "https://api.github.com/repos/R2Northstar/NorthstarLauncher/pulls"; pub const PULLS_API_ENDPOINT_MODS: &str = "https://api.github.com/repos/R2Northstar/NorthstarMods/pulls"; -// Statistics (players and servers counts) refresh delay +/// Statistics (players and servers counts) refresh delay pub const REFRESH_DELAY: Duration = Duration::from_secs(5 * 60); -// Flightcore repo name and org name on GitHub +/// Flightcore repo name and org name on GitHub pub const FLIGHTCORE_REPO_NAME: &str = "R2NorthstarTools/FlightCore"; -// Northstar release repo name and org name on GitHub +/// Northstar release repo name and org name on GitHub pub const NORTHSTAR_RELEASE_REPO_NAME: &str = "R2Northstar/Northstar"; -// URL to launcher commits API URL +/// URL to launcher commits API URL pub const NS_LAUNCHER_COMMITS_API_URL: &str = "https://api.github.com/repos/R2Northstar/NorthstarLauncher/commits"; + +/// Filename of DLL that Northstar uses +pub const NORTHSTAR_DLL: &str = "Northstar.dll"; + +/// Profile that Northstar defaults to and ships with +pub const NORTHSTAR_DEFAULT_PROFILE: &str = "R2Northstar"; + +/// List of valid compatibility tools that Northstar can be launched with +pub const VALID_NORTHSTAR_PROTON_BUILDS: [&str; 3] = + ["NorthstarProton-8.1-1", "GE-Proton8-13", "GE-Proton8-11"]; diff --git a/src-tauri/src/development/mod.rs b/src-tauri/src/development/mod.rs index be02966d..7184904c 100644 --- a/src-tauri/src/development/mod.rs +++ b/src-tauri/src/development/mod.rs @@ -25,7 +25,7 @@ pub async fn install_git_main(game_install_path: &str) -> Result<String, String> }; let extract_directory = format!( - "{}/___flightcore-temp-download-dir/launcher-pr-{}", + "{}/___flightcore-temp/download-dir/launcher-pr-{}", game_install_path, latest_commit_sha ); match std::fs::create_dir_all(extract_directory.clone()) { diff --git a/src-tauri/src/github/mod.rs b/src-tauri/src/github/mod.rs index bc1ccfe8..f35b64a1 100644 --- a/src-tauri/src/github/mod.rs +++ b/src-tauri/src/github/mod.rs @@ -180,6 +180,7 @@ fn generate_flightcore_release_notes(commits: Vec<String>) -> String { } } + let release_notes = release_notes.trim_end_matches('\n').to_string(); release_notes } diff --git a/src-tauri/src/github/pull_requests.rs b/src-tauri/src/github/pull_requests.rs index 44b41638..ccb45dff 100644 --- a/src-tauri/src/github/pull_requests.rs +++ b/src-tauri/src/github/pull_requests.rs @@ -2,6 +2,7 @@ use crate::github::release_notes::fetch_github_releases_api; use crate::check_is_valid_game_path; use crate::constants::{APP_USER_AGENT, PULLS_API_ENDPOINT_LAUNCHER, PULLS_API_ENDPOINT_MODS}; +use crate::GameInstall; use anyhow::anyhow; use serde::{Deserialize, Serialize}; use std::fs::File; @@ -231,10 +232,10 @@ fn add_batch_file(game_install_path: &str) { #[tauri::command] pub async fn apply_launcher_pr( pull_request: PullsApiResponseElement, - game_install_path: &str, + game_install: GameInstall, ) -> Result<(), String> { // Exit early if wrong game path - check_is_valid_game_path(game_install_path)?; + check_is_valid_game_path(&game_install.game_path)?; // get download link let download_url = match get_launcher_download_link(pull_request.head.sha.clone()).await { @@ -253,8 +254,8 @@ pub async fn apply_launcher_pr( }; let extract_directory = format!( - "{}/___flightcore-temp-download-dir/launcher-pr-{}", - game_install_path, pull_request.number + "{}/___flightcore-temp/download-dir/launcher-pr-{}", + game_install.game_path, pull_request.number ); match std::fs::create_dir_all(extract_directory.clone()) { Ok(_) => (), @@ -281,7 +282,7 @@ pub async fn apply_launcher_pr( let files_to_copy = vec!["NorthstarLauncher.exe", "Northstar.dll"]; for file_name in files_to_copy { let source_file_path = format!("{}/{}", extract_directory, file_name); - let destination_file_path = format!("{}/{}", game_install_path, file_name); + let destination_file_path = format!("{}/{}", game_install.game_path, file_name); match std::fs::copy(source_file_path, destination_file_path) { Ok(_result) => (), Err(err) => { @@ -312,10 +313,10 @@ pub async fn apply_launcher_pr( #[tauri::command] pub async fn apply_mods_pr( pull_request: PullsApiResponseElement, - game_install_path: &str, + game_install: GameInstall, ) -> Result<(), String> { // Exit early if wrong game path - check_is_valid_game_path(game_install_path)?; + check_is_valid_game_path(&game_install.game_path)?; let download_url = match get_mods_download_link(pull_request) { Ok(url) => url, @@ -327,7 +328,10 @@ pub async fn apply_mods_pr( Err(err) => return Err(err.to_string()), }; - let profile_folder = format!("{}/R2Northstar-PR-test-managed-folder", game_install_path); + let profile_folder = format!( + "{}/R2Northstar-PR-test-managed-folder", + game_install.game_path + ); // Delete previously managed folder if std::fs::remove_dir_all(profile_folder.clone()).is_err() { @@ -352,7 +356,7 @@ pub async fn apply_mods_pr( } }; // Add batch file to launch right profile - add_batch_file(game_install_path); + add_batch_file(&game_install.game_path); log::info!("All done with installing mods PR"); Ok(()) diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index 1067f5d3..66bb98d2 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -142,6 +142,7 @@ fn main() { github::release_notes::get_newest_flightcore_version, mod_management::delete_northstar_mod, util::get_server_player_count, + util::kill_northstar, mod_management::delete_thunderstore_mod, install_northstar_proton_wrapper, uninstall_northstar_proton_wrapper, @@ -157,6 +158,8 @@ fn main() { close_application, development::install_git_main, get_available_northstar_versions, + northstar::profile::fetch_profiles, + northstar::profile::validate_profile, ]) .run(tauri::generate_context!()) { @@ -234,7 +237,7 @@ pub fn convert_release_candidate_number(version_number: String) -> String { /// true -> Northstar install is outdated #[tauri::command] async fn check_is_northstar_outdated( - game_path: String, + game_install: GameInstall, northstar_package_name: Option<String>, ) -> Result<bool, String> { let northstar_package_name = match northstar_package_name { @@ -258,7 +261,7 @@ async fn check_is_northstar_outdated( .expect("Couldn't find Northstar on thunderstore???"); // .ok_or_else(|| anyhow!("Couldn't find Northstar on thunderstore???"))?; - let version_number = match northstar::get_northstar_version_number(&game_path) { + let version_number = match northstar::get_northstar_version_number(game_install) { Ok(version_number) => version_number, Err(err) => { log::warn!("{}", err); @@ -295,7 +298,7 @@ async fn verify_install_location(game_path: String) -> bool { #[tauri::command] async fn install_northstar_caller( window: tauri::Window, - game_path: String, + game_install: GameInstall, northstar_package_name: Option<String>, version_number: Option<String>, ) -> Result<bool, String> { @@ -314,7 +317,7 @@ async fn install_northstar_caller( match northstar::install::install_northstar( window, - &game_path, + game_install, northstar_package_name, version_number, ) @@ -332,13 +335,13 @@ async fn install_northstar_caller( #[tauri::command] async fn update_northstar( window: tauri::Window, - game_path: String, + game_install: GameInstall, northstar_package_name: Option<String>, ) -> Result<bool, String> { log::info!("Updating Northstar"); // Simply re-run install with up-to-date version for upate - install_northstar_caller(window, game_path, northstar_package_name, None).await + install_northstar_caller(window, game_install, northstar_package_name, None).await } /// Installs the specified mod @@ -456,7 +459,8 @@ mod platform_specific; #[cfg(target_os = "linux")] use platform_specific::linux; -#[derive(Serialize, Deserialize, Debug, Clone)] +#[derive(Serialize, Deserialize, Debug, Clone, TS)] +#[ts(export)] pub enum InstallType { STEAM, ORIGIN, @@ -467,6 +471,7 @@ pub enum InstallType { #[derive(Serialize, Deserialize, Debug, Clone)] pub struct GameInstall { pub game_path: String, + pub profile: String, pub install_type: InstallType, } diff --git a/src-tauri/src/mod_management/legacy.rs b/src-tauri/src/mod_management/legacy.rs index 2a5228fc..2f4a7469 100644 --- a/src-tauri/src/mod_management/legacy.rs +++ b/src-tauri/src/mod_management/legacy.rs @@ -45,7 +45,7 @@ fn parse_for_thunderstore_mod_string(nsmod_path: &str) -> Result<String, anyhow: pub fn parse_installed_mods( game_install: &GameInstall, ) -> Result<Vec<NorthstarMod>, anyhow::Error> { - let ns_mods_folder = format!("{}/R2Northstar/mods/", game_install.game_path); + let ns_mods_folder = format!("{}/{}/mods/", game_install.game_path, game_install.profile); let paths = match std::fs::read_dir(ns_mods_folder) { Ok(paths) => paths, diff --git a/src-tauri/src/mod_management/mod.rs b/src-tauri/src/mod_management/mod.rs index 0d4edd87..a9826522 100644 --- a/src-tauri/src/mod_management/mod.rs +++ b/src-tauri/src/mod_management/mod.rs @@ -12,6 +12,7 @@ use std::string::ToString; use std::{fs, path::PathBuf}; mod legacy; +mod plugins; use crate::GameInstall; #[derive(Debug, Clone)] @@ -25,7 +26,7 @@ impl std::str::FromStr for ParsedThunderstoreModString { type Err = &'static str; // todo use an better error management fn from_str(s: &str) -> Result<Self, Self::Err> { - // Check whether Thunderstore string passse reges + // Check whether Thunderstore string passes regex let re = regex::Regex::new(r"^[a-zA-Z0-9_]+-[a-zA-Z0-9_]+-\d+\.\d+\.\d++$").unwrap(); if !re.is_match(s) { return Err("Incorrect format"); @@ -90,7 +91,10 @@ impl std::ops::Deref for TempFile { /// Returns a serde json object of the parsed `enabledmods.json` file pub fn get_enabled_mods(game_install: &GameInstall) -> Result<serde_json::value::Value, String> { - let enabledmods_json_path = format!("{}/R2Northstar/enabledmods.json", game_install.game_path); + let enabledmods_json_path = format!( + "{}/{}/enabledmods.json", + game_install.game_path, game_install.profile + ); // Check for JSON file if !std::path::Path::new(&enabledmods_json_path).exists() { @@ -115,7 +119,10 @@ pub fn get_enabled_mods(game_install: &GameInstall) -> Result<serde_json::value: /// Gets all currently installed and enabled/disabled mods to rebuild `enabledmods.json` pub fn rebuild_enabled_mods_json(game_install: &GameInstall) -> Result<(), String> { - let enabledmods_json_path = format!("{}/R2Northstar/enabledmods.json", game_install.game_path); + let enabledmods_json_path = format!( + "{}/{}/enabledmods.json", + game_install.game_path, game_install.profile + ); let mods_and_properties = get_installed_mods_and_properties(game_install.clone())?; // Create new mapping @@ -146,7 +153,10 @@ pub fn set_mod_enabled_status( mod_name: String, is_enabled: bool, ) -> Result<(), String> { - let enabledmods_json_path = format!("{}/R2Northstar/enabledmods.json", game_install.game_path); + let enabledmods_json_path = format!( + "{}/{}/enabledmods.json", + game_install.game_path, game_install.profile + ); // Parse JSON let mut res: serde_json::Value = match get_enabled_mods(&game_install) { @@ -259,7 +269,10 @@ pub fn parse_installed_package_mods( ) -> Result<Vec<NorthstarMod>, anyhow::Error> { let mut collected_mods: Vec<NorthstarMod> = Vec::new(); - let packages_folder = format!("{}/R2Northstar/packages/", game_install.game_path); + let packages_folder = format!( + "{}/{}/packages/", + game_install.game_path, game_install.profile + ); let packages_dir = match fs::read_dir(packages_folder) { Ok(res) => res, @@ -419,7 +432,10 @@ fn delete_older_versions( "Deleting other versions of {}", thunderstore_mod_string.to_string() ); - let packages_folder = format!("{}/R2Northstar/packages", game_install.game_path); + let packages_folder = format!( + "{}/{}/packages", + game_install.game_path, game_install.profile + ); // Get folders in packages dir let paths = match std::fs::read_dir(&packages_folder) { @@ -498,8 +514,10 @@ fn fc_sanity_check(input: &&fs::File) -> bool { if file_path.starts_with("plugins/") { if let Some(name) = file_path.file_name() { if name.to_str().unwrap().contains(".dll") { - log::warn!("Plugin detected, skipping"); - return false; // We disallow plugins for now + log::warn!("Plugin detected, prompting user"); + if !plugins::plugin_prompt() { + return false; // Plugin detected and user denied install + } } } } @@ -519,7 +537,7 @@ pub async fn fc_download_mod_and_install( log::info!("Attempting to install \"{thunderstore_mod_string}\" to {game_install:?}"); // Get mods and download directories let download_directory = format!( - "{}/___flightcore-temp-download-dir/", + "{}/___flightcore-temp/download-dir/", game_install.game_path ); @@ -566,7 +584,7 @@ pub async fn fc_download_mod_and_install( }; let path = format!( - "{}/___flightcore-temp-download-dir/{thunderstore_mod_string}.zip", + "{}/___flightcore-temp/download-dir/{thunderstore_mod_string}.zip", game_install.game_path ); @@ -587,7 +605,10 @@ pub async fn fc_download_mod_and_install( }; // Get directory to install to made up of packages directory and Thunderstore mod string - let install_directory = format!("{}/R2Northstar/packages/", game_install.game_path); + let install_directory = format!( + "{}/{}/packages/", + game_install.game_path, game_install.profile + ); // Extract the mod to the mods directory match thermite::core::manage::install_with_sanity( @@ -700,7 +721,10 @@ pub fn delete_thunderstore_mod( thunderstore_mod_string: String, ) -> Result<(), String> { // Check packages - let packages_folder = format!("{}/R2Northstar/packages", game_install.game_path); + let packages_folder = format!( + "{}/{}/packages", + game_install.game_path, game_install.profile + ); if std::path::Path::new(&packages_folder).exists() { for entry in fs::read_dir(packages_folder).unwrap() { let entry = entry.unwrap(); diff --git a/src-tauri/src/mod_management/plugins.rs b/src-tauri/src/mod_management/plugins.rs new file mode 100644 index 00000000..e2427a16 --- /dev/null +++ b/src-tauri/src/mod_management/plugins.rs @@ -0,0 +1,26 @@ +use tauri::api::dialog::blocking::MessageDialogBuilder; +use tauri::api::dialog::{MessageDialogButtons, MessageDialogKind}; + +/// Prompt on plugin +/// Returns: +/// - true: user accepted plugin install +/// - false: user denied plugin install +pub fn plugin_prompt() -> bool { + let dialog = MessageDialogBuilder::new( + "Plugin in package detected", + "This mod contains a plugin. Plugins have unrestricted access to your computer! + \nMake sure you trust the author! + \n + \nPress 'Ok' to continue or 'Cancel' to abort mod installation", + ) + .kind(MessageDialogKind::Warning) + .buttons(MessageDialogButtons::OkCancel); + + if dialog.show() { + log::info!("Accepted plugin install"); + true + } else { + log::warn!("Plugin install cancelled"); + false + } +} diff --git a/src-tauri/src/northstar/install.rs b/src-tauri/src/northstar/install.rs index c77fd538..757f6c68 100644 --- a/src-tauri/src/northstar/install.rs +++ b/src-tauri/src/northstar/install.rs @@ -4,8 +4,11 @@ use std::time::Duration; use std::{cell::RefCell, time::Instant}; use ts_rs::TS; -use crate::constants::TITANFALL2_STEAM_ID; -use crate::{util::extract, GameInstall, InstallType}; +use crate::constants::{NORTHSTAR_DEFAULT_PROFILE, NORTHSTAR_DLL, TITANFALL2_STEAM_ID}; +use crate::{ + util::{extract, move_dir_all}, + GameInstall, InstallType, +}; #[cfg(target_os = "windows")] use crate::platform_specific::windows; @@ -33,16 +36,16 @@ struct InstallProgress { async fn do_install( window: tauri::Window, nmod: &thermite::model::ModVersion, - game_path: &std::path::Path, + game_install: GameInstall, ) -> Result<()> { let filename = format!("northstar-{}.zip", nmod.version); - let download_directory = format!("{}/___flightcore-temp-download-dir/", game_path.display()); + let temp_dir = format!("{}/___flightcore-temp", game_install.game_path); + let download_directory = format!("{}/download-dir", temp_dir); + let extract_directory = format!("{}/extract-dir", temp_dir); - log::info!( - "Attempting to create temporary directory {}", - download_directory - ); + log::info!("Attempting to create temporary directory {}", temp_dir); std::fs::create_dir_all(download_directory.clone())?; + std::fs::create_dir_all(extract_directory.clone())?; let download_path = format!("{}/{}", download_directory, filename); log::info!("Download path: {download_path}"); @@ -91,11 +94,50 @@ async fn do_install( .unwrap(); log::info!("Extracting Northstar..."); - extract(nfile, game_path)?; + extract(nfile, std::path::Path::new(&extract_directory))?; + + // Prepare Northstar for Installation + log::info!("Preparing Northstar..."); + if game_install.profile != NORTHSTAR_DEFAULT_PROFILE { + // We are using a non standard Profile, we must: + // - move the DLL + // - rename the Profile + + // Move DLL into the default R2Northstar Profile + let old_dll_path = format!("{}/{}", extract_directory, NORTHSTAR_DLL); + let new_dll_path = format!( + "{}/{}/{}", + extract_directory, NORTHSTAR_DEFAULT_PROFILE, NORTHSTAR_DLL + ); + std::fs::rename(old_dll_path, new_dll_path)?; + + // rename default R2Northstar Profile to the profile we want to use + let old_profile_path = format!("{}/{}/", extract_directory, NORTHSTAR_DEFAULT_PROFILE); + let new_profile_path = format!("{}/{}/", extract_directory, game_install.profile); + std::fs::rename(old_profile_path, new_profile_path)?; + } + + log::info!("Installing Northstar..."); + + for entry in std::fs::read_dir(extract_directory).unwrap() { + let entry = entry.unwrap(); + let destination = format!( + "{}/{}", + game_install.game_path, + entry.path().file_name().unwrap().to_str().unwrap() + ); + + log::info!("Installing {}", entry.path().display()); + if !entry.file_type().unwrap().is_dir() { + std::fs::rename(entry.path(), destination)?; + } else { + move_dir_all(entry.path(), destination)?; + } + } // Delete old copy - log::info!("Delete temp folder again"); - std::fs::remove_dir_all(download_directory).unwrap(); + log::info!("Delete temporary directory"); + std::fs::remove_dir_all(temp_dir).unwrap(); log::info!("Done installing Northstar!"); window @@ -114,7 +156,7 @@ async fn do_install( pub async fn install_northstar( window: tauri::Window, - game_path: &str, + game_install: GameInstall, northstar_package_name: String, version_number: Option<String>, ) -> Result<String, String> { @@ -134,20 +176,15 @@ pub async fn install_northstar( // Use passed version or latest if no version was passed let version = version_number.as_ref().unwrap_or(&nmod.latest); + let game_path = game_install.game_path.clone(); log::info!("Install path \"{}\"", game_path); - match do_install( - window, - nmod.versions.get(version).unwrap(), - std::path::Path::new(game_path), - ) - .await - { + match do_install(window, nmod.versions.get(version).unwrap(), game_install).await { Ok(_) => (), Err(err) => { if game_path .to_lowercase() - .contains(&r#"C:\Program Files\"#.to_lowercase()) + .contains(&r"C:\Program Files\".to_lowercase()) // default is `C:\Program Files\EA Games\Titanfall2` { return Err( @@ -190,6 +227,7 @@ pub fn find_game_install_location() -> Result<GameInstall, String> { // println!("{:#?}", app); let game_install = GameInstall { game_path: app.path.to_str().unwrap().to_string(), + profile: "R2Northstar".to_string(), install_type: InstallType::STEAM, }; return Ok(game_install); @@ -206,6 +244,7 @@ pub fn find_game_install_location() -> Result<GameInstall, String> { Ok(game_path) => { let game_install = GameInstall { game_path, + profile: "R2Northstar".to_string(), install_type: InstallType::ORIGIN, }; return Ok(game_install); diff --git a/src-tauri/src/northstar/mod.rs b/src-tauri/src/northstar/mod.rs index bf55603b..5b0139f9 100644 --- a/src-tauri/src/northstar/mod.rs +++ b/src-tauri/src/northstar/mod.rs @@ -1,10 +1,11 @@ //! This module deals with handling things around Northstar such as //! - getting version number pub mod install; +pub mod profile; use crate::util::check_ea_app_or_origin_running; use crate::{ - constants::{CORE_MODS, TITANFALL2_STEAM_ID}, + constants::{CORE_MODS, TITANFALL2_STEAM_ID, VALID_NORTHSTAR_PROTON_BUILDS}, get_host_os, GameInstall, InstallType, }; use anyhow::anyhow; @@ -26,15 +27,14 @@ pub fn check_mod_version_number(path_to_mod_folder: &str) -> Result<String, anyh /// Returns the current Northstar version number as a string #[tauri::command] -pub fn get_northstar_version_number(game_path: &str) -> Result<String, String> { - log::info!("{}", game_path); +pub fn get_northstar_version_number(game_install: GameInstall) -> Result<String, String> { + log::info!("{}", game_install.game_path); // TODO: // Check if NorthstarLauncher.exe exists and check its version number - let profile_folder = "R2Northstar"; let initial_version_number = match check_mod_version_number(&format!( - "{game_path}/{profile_folder}/mods/{}", - CORE_MODS[0] + "{}/{}/mods/{}", + game_install.game_path, game_install.profile, CORE_MODS[0] )) { Ok(version_number) => version_number, Err(err) => return Err(err.to_string()), @@ -42,7 +42,8 @@ pub fn get_northstar_version_number(game_path: &str) -> Result<String, String> { for core_mod in CORE_MODS { let current_version_number = match check_mod_version_number(&format!( - "{game_path}/{profile_folder}/mods/{core_mod}", + "{}/{}/mods/{}", + game_install.game_path, game_install.profile, core_mod )) { Ok(version_number) => version_number, Err(err) => return Err(err.to_string()), @@ -85,7 +86,7 @@ pub fn launch_northstar( // Only check guards if bypassing checks is not enabled if !bypass_checks { // Some safety checks before, should have more in the future - if get_northstar_version_number(&game_install.game_path).is_err() { + if get_northstar_version_number(game_install.clone()).is_err() { return Err(anyhow!("Not all checks were met").to_string()); } @@ -112,8 +113,10 @@ pub fn launch_northstar( || matches!(game_install.install_type, InstallType::UNKNOWN)) { let ns_exe_path = format!("{}/NorthstarLauncher.exe", game_install.game_path); + let ns_profile_arg = format!("-profile={}", game_install.profile); + let _output = std::process::Command::new("C:\\Windows\\System32\\cmd.exe") - .args(["/C", "start", "", &ns_exe_path]) + .args(["/C", "start", "", &ns_exe_path, &ns_profile_arg]) .spawn() .expect("failed to execute process"); return Ok("Launched game".to_string()); @@ -142,15 +145,11 @@ pub fn launch_northstar_steam( let titanfall2_steamid: u32 = TITANFALL2_STEAM_ID.parse().unwrap(); match steamdir.compat_tool(&titanfall2_steamid) { Some(compat) => { - if !compat - .name - .clone() - .unwrap() - .to_ascii_lowercase() - .contains("northstarproton") + if !VALID_NORTHSTAR_PROTON_BUILDS + .contains(&compat.clone().name.unwrap().as_str()) { return Err( - "Titanfall2 was not configured to use NorthstarProton".to_string() + "Titanfall2 was not configured to use a valid version of NorthstarProton or GE-Proton".to_string(), ); } } @@ -173,7 +172,10 @@ pub fn launch_northstar_steam( return Err("Couldn't access Titanfall2 directory".to_string()); } - match open::that(format!("steam://run/{}//--northstar/", TITANFALL2_STEAM_ID)) { + match open::that(format!( + "steam://run/{}//-profile={} --northstar/", + TITANFALL2_STEAM_ID, game_install.profile + )) { Ok(()) => Ok("Started game".to_string()), Err(_err) => Err("Failed to launch Titanfall 2 via Steam".to_string()), } diff --git a/src-tauri/src/northstar/profile.rs b/src-tauri/src/northstar/profile.rs new file mode 100644 index 00000000..78e734d0 --- /dev/null +++ b/src-tauri/src/northstar/profile.rs @@ -0,0 +1,76 @@ +use crate::GameInstall; + +// These folders are part of Titanfall 2 and +// should NEVER be used as a Profile +const SKIP_PATHS: [&str; 8] = [ + "___flightcore-temp", + "__overlay", + "bin", + "Core", + "r2", + "vpk", + "platform", + "Support", +]; + +// A profile may have one of these to be detected +const MAY_CONTAIN: [&str; 10] = [ + "mods/", + "plugins/", + "packages/", + "logs/", + "runtime/", + "save_data/", + "Northstar.dll", + "enabledmods.json", + "placeholder.playerdata.pdata", + "LEGAL.txt", +]; + +/// Returns a list of Profile names +/// All the returned Profiles can be found relative to the game path +#[tauri::command] +pub fn fetch_profiles(game_install: GameInstall) -> Result<Vec<String>, String> { + let mut profiles: Vec<String> = Vec::new(); + + for content in MAY_CONTAIN { + let pattern = format!("{}/*/{}", game_install.game_path, content); + for e in glob::glob(&pattern).expect("Failed to read glob pattern") { + let path = e.unwrap(); + let mut ancestors = path.ancestors(); + + ancestors.next(); + + let profile_path = std::path::Path::new(ancestors.next().unwrap()); + let profile_name = profile_path + .file_name() + .unwrap() + .to_os_string() + .into_string() + .unwrap(); + + if !profiles.contains(&profile_name) { + profiles.push(profile_name); + } + } + } + + Ok(profiles) +} + +/// Validates if a given profile is actually a valid profile +#[tauri::command] +pub fn validate_profile(game_install: GameInstall, profile: String) -> bool { + // Game files are never a valid profile + // Prevent users with messed up installs from making it even worse + if SKIP_PATHS.contains(&profile.as_str()) { + return false; + } + + log::info!("Validating Profile {}", profile); + + let profile_path = format!("{}/{}", game_install.game_path, profile); + let profile_dir = std::path::Path::new(profile_path.as_str()); + + profile_dir.is_dir() +} diff --git a/src-tauri/src/repair_and_verify/mod.rs b/src-tauri/src/repair_and_verify/mod.rs index 92835a4e..70abc127 100644 --- a/src-tauri/src/repair_and_verify/mod.rs +++ b/src-tauri/src/repair_and_verify/mod.rs @@ -1,7 +1,6 @@ use crate::mod_management::{get_enabled_mods, rebuild_enabled_mods_json, set_mod_enabled_status}; /// Contains various functions to repair common issues and verifying installation use crate::{constants::CORE_MODS, GameInstall}; -use anyhow::anyhow; /// Verifies Titanfall2 game files #[tauri::command] @@ -40,33 +39,43 @@ pub fn clean_up_download_folder( game_install: &GameInstall, force: bool, ) -> Result<(), anyhow::Error> { - // Get download directory - let download_directory = format!( - "{}/___flightcore-temp-download-dir/", - game_install.game_path - ); - - // Check if files in folder - let download_dir_contents = std::fs::read_dir(download_directory.clone())?; - // dbg!(download_dir_contents); - - let mut count = 0; - download_dir_contents.for_each(|_| count += 1); + const TEMPORARY_DIRECTORIES: [&str; 4] = [ + "___flightcore-temp-download-dir", + "___flightcore-temp/download-dir", + "___flightcore-temp/extract-dir", + "___flightcore-temp", + ]; + + for directory in TEMPORARY_DIRECTORIES { + // Get download directory + let download_directory = format!("{}/{}/", game_install.game_path, directory); + + // Check if files in folder + let download_dir_contents = match std::fs::read_dir(download_directory.clone()) { + Ok(contents) => contents, + Err(_) => continue, + }; + // dbg!(download_dir_contents); + + let mut count = 0; + download_dir_contents.for_each(|_| count += 1); + + if count > 0 && !force { + // Skip folder if not empty + log::warn!("Folder not empty, not deleting: {directory}"); + continue; + } - if count > 0 && !force { - return Err(anyhow!("Folder not empty, not deleting")); + // Delete folder + std::fs::remove_dir_all(download_directory)?; } - - // Delete folder - std::fs::remove_dir_all(download_directory)?; - Ok(()) } /// Get list of Northstar logs #[tauri::command] pub fn get_log_list(game_install: GameInstall) -> Result<Vec<std::path::PathBuf>, String> { - let ns_log_folder = format!("{}/R2Northstar/logs", game_install.game_path); + let ns_log_folder = format!("{}/{}/logs", game_install.game_path, game_install.profile); // List files in logs folder let paths = match std::fs::read_dir(ns_log_folder) { diff --git a/src-tauri/src/util.rs b/src-tauri/src/util.rs index 0f32ecb5..b21b2208 100644 --- a/src-tauri/src/util.rs +++ b/src-tauri/src/util.rs @@ -2,7 +2,7 @@ use anyhow::{Context, Result}; use serde::{Deserialize, Serialize}; -use sysinfo::SystemExt; +use sysinfo::{ProcessExt, SystemExt}; use zip::ZipArchive; use crate::constants::{APP_USER_AGENT, MASTER_SERVER_URL, SERVER_BROWSER_ENDPOINT}; @@ -64,6 +64,27 @@ pub async fn get_server_player_count() -> Result<(i32, usize), String> { Ok((total_player_count, server_count)) } +#[tauri::command] +pub async fn kill_northstar() -> Result<(), String> { + if !check_northstar_running() { + return Err("Northstar is not running".to_string()); + } + + let s = sysinfo::System::new_all(); + + for process in s.processes_by_exact_name("Titanfall2.exe") { + log::info!("Killing Process {}", process.pid()); + process.kill(); + } + + for process in s.processes_by_exact_name("NorthstarLauncher.exe") { + log::info!("Killing Process {}", process.pid()); + process.kill(); + } + + Ok(()) +} + /// Copied from `papa` source code and modified ///Extract N* zip file to target game path // fn extract(ctx: &Ctx, zip_file: File, target: &Path) -> Result<()> { @@ -122,3 +143,42 @@ pub fn check_northstar_running() -> bool { || s.processes_by_name("Titanfall2.exe").next().is_some(); x } + +/// Copies a folder and all its contents to a new location +#[allow(dead_code)] +pub fn copy_dir_all( + src: impl AsRef<std::path::Path>, + dst: impl AsRef<std::path::Path>, +) -> std::io::Result<()> { + std::fs::create_dir_all(&dst)?; + for entry in std::fs::read_dir(src)? { + let entry = entry?; + let ty = entry.file_type()?; + if ty.is_dir() { + copy_dir_all(entry.path(), dst.as_ref().join(entry.file_name()))?; + } else { + std::fs::copy(entry.path(), dst.as_ref().join(entry.file_name()))?; + } + } + Ok(()) +} + +/// Moves a folders file structure to a new location +/// Old folders are not removed +pub fn move_dir_all( + src: impl AsRef<std::path::Path>, + dst: impl AsRef<std::path::Path>, +) -> std::io::Result<()> { + std::fs::create_dir_all(&dst)?; + for entry in std::fs::read_dir(src)? { + let entry = entry?; + let ty = entry.file_type()?; + if ty.is_dir() { + move_dir_all(entry.path(), dst.as_ref().join(entry.file_name()))?; + std::fs::remove_dir(entry.path())?; + } else { + std::fs::rename(entry.path(), dst.as_ref().join(entry.file_name()))?; + } + } + Ok(()) +} |