aboutsummaryrefslogtreecommitdiff
path: root/src-tauri/src
diff options
context:
space:
mode:
Diffstat (limited to 'src-tauri/src')
-rw-r--r--src-tauri/src/constants.rs36
-rw-r--r--src-tauri/src/development/mod.rs2
-rw-r--r--src-tauri/src/github/mod.rs1
-rw-r--r--src-tauri/src/github/pull_requests.rs22
-rw-r--r--src-tauri/src/main.rs19
-rw-r--r--src-tauri/src/mod_management/legacy.rs2
-rw-r--r--src-tauri/src/mod_management/mod.rs48
-rw-r--r--src-tauri/src/mod_management/plugins.rs26
-rw-r--r--src-tauri/src/northstar/install.rs79
-rw-r--r--src-tauri/src/northstar/mod.rs36
-rw-r--r--src-tauri/src/northstar/profile.rs76
-rw-r--r--src-tauri/src/repair_and_verify/mod.rs49
-rw-r--r--src-tauri/src/util.rs62
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(())
+}