aboutsummaryrefslogtreecommitdiff
path: root/src-tauri/src/lib.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src-tauri/src/lib.rs')
-rw-r--r--src-tauri/src/lib.rs276
1 files changed, 214 insertions, 62 deletions
diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs
index 12c839fe..8ec7e4b1 100644
--- a/src-tauri/src/lib.rs
+++ b/src-tauri/src/lib.rs
@@ -1,7 +1,6 @@
-use std::env;
+use std::{cell::RefCell, env, fs, path::Path, time::Duration, time::Instant};
use anyhow::{anyhow, Context, Result};
-use sentry::{add_breadcrumb, Breadcrumb, Level};
mod northstar;
@@ -15,11 +14,14 @@ use platform_specific::linux;
use serde::{Deserialize, Serialize};
use sysinfo::SystemExt;
+use tokio::time::sleep;
use ts_rs::TS;
use zip::ZipArchive;
use northstar::get_northstar_version_number;
+use crate::constants::TITANFALL2_STEAM_ID;
+
#[derive(Serialize, Deserialize, Debug, Clone)]
pub enum InstallType {
STEAM,
@@ -50,10 +52,26 @@ pub struct NorthstarServer {
pub player_count: i32,
}
+#[derive(Serialize, Deserialize, Debug, Clone, TS)]
+#[ts(export)]
+pub enum InstallState {
+ DOWNLOADING,
+ EXTRACTING,
+ DONE,
+}
+
+#[derive(Serialize, Deserialize, Debug, Clone, TS)]
+#[ts(export)]
+struct InstallProgress {
+ current_downloaded: u64,
+ total_size: u64,
+ state: InstallState,
+}
+
/// Check version number of a mod
-pub fn check_mod_version_number(path_to_mod_folder: String) -> Result<String, anyhow::Error> {
+pub fn check_mod_version_number(path_to_mod_folder: &str) -> Result<String, anyhow::Error> {
// println!("{}", format!("{}/mod.json", path_to_mod_folder));
- let data = std::fs::read_to_string(format!("{}/mod.json", path_to_mod_folder))?;
+ let data = std::fs::read_to_string(format!("{path_to_mod_folder}/mod.json"))?;
let parsed_json: serde_json::Value = serde_json::from_str(&data)?;
// println!("{}", parsed_json);
let mod_version_number = match parsed_json.get("Version").and_then(|value| value.as_str()) {
@@ -61,7 +79,7 @@ pub fn check_mod_version_number(path_to_mod_folder: String) -> Result<String, an
None => return Err(anyhow!("No version number found")),
};
- println!("{}", mod_version_number);
+ log::info!("{}", mod_version_number);
Ok(mod_version_number.to_string())
}
@@ -81,8 +99,7 @@ pub fn linux_checks_librs() -> Result<(), String> {
return Err(format!(
"GLIBC is not version {} or greater",
min_required_ldd_version
- )
- .to_string());
+ ));
};
// All checks passed
@@ -94,7 +111,7 @@ pub fn find_game_install_location() -> Result<GameInstall, String> {
// Attempt parsing Steam library directly
match steamlocate::SteamDir::locate() {
Some(mut steamdir) => {
- let titanfall2_steamid = 1237970;
+ let titanfall2_steamid = TITANFALL2_STEAM_ID.parse().unwrap();
match steamdir.app(&titanfall2_steamid) {
Some(app) => {
// println!("{:#?}", app);
@@ -104,10 +121,10 @@ pub fn find_game_install_location() -> Result<GameInstall, String> {
};
return Ok(game_install);
}
- None => println!("Couldn't locate Titanfall2"),
+ None => log::info!("Couldn't locate Titanfall2 Steam instal"),
}
}
- None => println!("Couldn't locate Steam on this computer!"),
+ None => log::info!("Couldn't locate Steam on this computer!"),
}
// (On Windows only) try parsing Windows registry for Origin install path
@@ -115,13 +132,13 @@ pub fn find_game_install_location() -> Result<GameInstall, String> {
match windows::origin_install_location_detection() {
Ok(game_path) => {
let game_install = GameInstall {
- game_path: game_path,
+ game_path,
install_type: InstallType::ORIGIN,
};
return Ok(game_install);
}
Err(err) => {
- println!("{}", err);
+ log::info!("{}", err);
}
};
@@ -130,13 +147,13 @@ pub fn find_game_install_location() -> Result<GameInstall, String> {
/// Checks whether the provided path is a valid Titanfall2 gamepath by checking against a certain set of criteria
pub fn check_is_valid_game_path(game_install_path: &str) -> Result<(), String> {
- let path_to_titanfall2_exe = format!("{}/Titanfall2.exe", game_install_path);
+ let path_to_titanfall2_exe = format!("{game_install_path}/Titanfall2.exe");
let is_correct_game_path = std::path::Path::new(&path_to_titanfall2_exe).exists();
- println!("Titanfall2.exe exists in path? {}", is_correct_game_path);
+ log::info!("Titanfall2.exe exists in path? {}", is_correct_game_path);
// Exit early if wrong game path
if !is_correct_game_path {
- return Err(format!("Incorrect game path \"{}\"", game_install_path)); // Return error cause wrong game path
+ return Err(format!("Incorrect game path \"{game_install_path}\"")); // Return error cause wrong game path
}
Ok(())
}
@@ -159,12 +176,12 @@ fn extract(zip_file: std::fs::File, target: &std::path::Path) -> Result<()> {
);
if (*f.name()).ends_with('/') {
- println!("Create directory {}", f.name());
+ log::info!("Create directory {}", f.name());
std::fs::create_dir_all(target.join(f.name()))
.context("Unable to create directory")?;
continue;
} else if let Some(p) = out.parent() {
- std::fs::create_dir_all(&p).context("Unable to create directory")?;
+ std::fs::create_dir_all(p).context("Unable to create directory")?;
}
let mut outfile = std::fs::OpenOptions::new()
@@ -173,7 +190,7 @@ fn extract(zip_file: std::fs::File, target: &std::path::Path) -> Result<()> {
.truncate(true)
.open(&out)?;
- println!("Write file {}", out.display());
+ log::info!("Write file {}", out.display());
std::io::copy(&mut f, &mut outfile).context("Unable to write to file")?;
}
@@ -186,33 +203,83 @@ fn extract(zip_file: std::fs::File, target: &std::path::Path) -> Result<()> {
///Install N* from the provided mod
///
///Checks cache, else downloads the latest version
-async fn do_install(nmod: &thermite::model::ModVersion, game_path: &std::path::Path) -> Result<()> {
+async fn do_install(
+ window: tauri::Window,
+ nmod: &thermite::model::ModVersion,
+ game_path: &std::path::Path,
+) -> Result<()> {
let filename = format!("northstar-{}.zip", nmod.version);
let download_directory = format!("{}/___flightcore-temp-download-dir/", game_path.display());
std::fs::create_dir_all(download_directory.clone())?;
- let download_path = format!("{}/{}", download_directory.clone(), filename);
- println!("{}", download_path);
-
- let nfile = match thermite::core::manage::download_file(&nmod.url, download_path) {
+ let download_path = format!("{}/{}", download_directory, filename);
+ log::info!("Download path: {download_path}");
+
+ let last_emit = RefCell::new(Instant::now()); // Keep track of the last time a signal was emitted
+ let nfile = match thermite::core::manage::download_file_with_progress(
+ &nmod.url,
+ download_path,
+ |delta, current, total| {
+ if delta != 0 {
+ // Only emit a signal once every 100ms
+ // This way we don't bombard the frontend with events on fast download speeds
+ let time_since_last_emit = Instant::now().duration_since(*last_emit.borrow());
+ if time_since_last_emit >= Duration::from_millis(100) {
+ window
+ .emit(
+ "northstar-install-download-progress",
+ InstallProgress {
+ current_downloaded: current,
+ total_size: total,
+ state: InstallState::DOWNLOADING,
+ },
+ )
+ .unwrap();
+ *last_emit.borrow_mut() = Instant::now();
+ }
+ }
+ },
+ ) {
Ok(res) => res,
Err(err) => return Err(anyhow!("Failed downloading Northstar {}", err)),
};
- println!("Extracting Northstar...");
+ window
+ .emit(
+ "northstar-install-download-progress",
+ InstallProgress {
+ current_downloaded: 0,
+ total_size: 0,
+ state: InstallState::EXTRACTING,
+ },
+ )
+ .unwrap();
+
+ log::info!("Extracting Northstar...");
extract(nfile, game_path)?;
// Delete old copy
- println!("Delete temp folder again");
+ log::info!("Delete temp folder again");
std::fs::remove_dir_all(download_directory).unwrap();
- println!("Done!");
+ log::info!("Done installing Northstar!");
+ window
+ .emit(
+ "northstar-install-download-progress",
+ InstallProgress {
+ current_downloaded: 0,
+ total_size: 0,
+ state: InstallState::DONE,
+ },
+ )
+ .unwrap();
Ok(())
}
pub async fn install_northstar(
+ window: tauri::Window,
game_path: &str,
northstar_package_name: Option<String>,
) -> Result<String, String> {
@@ -234,20 +301,30 @@ pub async fn install_northstar(
.ok_or_else(|| panic!("Couldn't find Northstar on thunderstore???"))
.unwrap();
- // Breadcrumb for sentry to debug crash
- add_breadcrumb(Breadcrumb {
- // category: Some("auth".into()),
- message: Some(format!("Install path \"{}\"", game_path)),
- level: Level::Info,
- ..Default::default()
- });
+ log::info!("Install path \"{}\"", game_path);
- do_install(
+ match do_install(
+ window,
nmod.versions.get(&nmod.latest).unwrap(),
std::path::Path::new(game_path),
)
.await
- .unwrap();
+ {
+ Ok(_) => (),
+ Err(err) => {
+ if game_path
+ .to_lowercase()
+ .contains(&r#"C:\Program Files\"#.to_lowercase())
+ // default is `C:\Program Files\EA Games\Titanfall2`
+ {
+ return Err(
+ "Cannot install to default EA App install path, please move Titanfall2 to a different install location.".to_string(),
+ );
+ } else {
+ return Err(err.to_string());
+ }
+ }
+ }
Ok(nmod.latest.clone())
}
@@ -258,7 +335,7 @@ pub fn get_host_os() -> String {
}
pub fn launch_northstar(
- game_install: GameInstall,
+ game_install: &GameInstall,
bypass_checks: Option<bool>,
) -> Result<String, String> {
dbg!(game_install.clone());
@@ -278,15 +355,12 @@ pub fn launch_northstar(
));
}
- let bypass_checks = match bypass_checks {
- Some(bypass_checks) => bypass_checks,
- None => false,
- };
+ let bypass_checks = bypass_checks.unwrap_or(false);
// 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.clone()).is_err() {
+ if get_northstar_version_number(&game_install.game_path).is_err() {
return Err(anyhow!("Not all checks were met").to_string());
}
@@ -314,7 +388,7 @@ pub fn launch_northstar(
{
let ns_exe_path = format!("{}/NorthstarLauncher.exe", game_install.game_path);
let _output = std::process::Command::new("C:\\Windows\\System32\\cmd.exe")
- .args(&["/C", "start", "", &ns_exe_path])
+ .args(["/C", "start", "", &ns_exe_path])
.spawn()
.expect("failed to execute process");
return Ok("Launched game".to_string());
@@ -327,31 +401,109 @@ pub fn launch_northstar(
))
}
-pub fn check_origin_running() -> bool {
- let s = sysinfo::System::new_all();
- for _process in s.processes_by_name("Origin.exe") {
- // check here if this is your process
- // dbg!(process);
- // There's at least one Origin process, so we can launch
- return true;
+/// Prepare Northstar and Launch through Steam using the Browser Protocol
+pub fn launch_northstar_steam(
+ game_install: &GameInstall,
+ _bypass_checks: Option<bool>,
+) -> Result<String, String> {
+ if !matches!(game_install.install_type, InstallType::STEAM) {
+ return Err("Titanfall2 was not installed via Steam".to_string());
}
- // Alternatively, check for EA Desktop
- for _process in s.processes_by_name("EADesktop.exe") {
- // There's at least one EADesktop process, so we can launch
- return true;
+
+ match steamlocate::SteamDir::locate() {
+ Some(mut steamdir) => {
+ if get_host_os() != "windows" {
+ 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")
+ {
+ return Err(
+ "Titanfall2 was not configured to use NorthstarProton".to_string()
+ );
+ }
+ }
+ None => {
+ return Err(
+ "Titanfall2 was not configured to use a compatibility tool".to_string()
+ );
+ }
+ }
+ }
+ }
+ None => {
+ return Err("Couldn't access Titanfall2 directory".to_string());
+ }
}
- false
+
+ // Switch to Titanfall2 directory to set everything up
+ if std::env::set_current_dir(game_install.game_path.clone()).is_err() {
+ // We failed to get to Titanfall2 directory
+ return Err("Couldn't access Titanfall2 directory".to_string());
+ }
+
+ let run_northstar = "run_northstar.txt";
+ let run_northstar_bak = "run_northstar.txt.bak";
+
+ if Path::new(run_northstar).exists() {
+ // rename should ovewrite existing files
+ fs::rename(run_northstar, run_northstar_bak).unwrap();
+ }
+
+ // Passing arguments gives users a prompt, so we use run_northstar.txt
+ fs::write(run_northstar, b"1").unwrap();
+
+ let retval = match open::that(format!("steam://run/{}/", TITANFALL2_STEAM_ID)) {
+ Ok(()) => Ok("Started game".to_string()),
+ Err(_err) => Err("Failed to launch Titanfall 2 via Steam".to_string()),
+ };
+
+ let is_err = retval.is_err();
+
+ // Handle the rest in the backround
+ tauri::async_runtime::spawn(async move {
+ // Starting the EA app and Titanfall might take a good minute or three
+ let mut wait_countdown = 60 * 3;
+ while wait_countdown > 0 && !check_northstar_running() && !is_err {
+ sleep(Duration::from_millis(1000)).await;
+ wait_countdown -= 1;
+ }
+
+ // Northstar may be running, but it may not have loaded the file yet
+ sleep(Duration::from_millis(2000)).await;
+
+ // intentionally ignore Result
+ let _ = fs::remove_file(run_northstar);
+
+ if Path::new(run_northstar_bak).exists() {
+ fs::rename(run_northstar_bak, run_northstar).unwrap();
+ }
+ });
+
+ retval
+}
+
+pub fn check_origin_running() -> bool {
+ let s = sysinfo::System::new_all();
+ let x = s.processes_by_name("Origin.exe").next().is_some()
+ || s.processes_by_name("EADesktop.exe").next().is_some();
+ x
}
/// Checks if Northstar process is running
pub fn check_northstar_running() -> bool {
let s = sysinfo::System::new_all();
- for _process in s.processes_by_name("NorthstarLauncher.exe") {
- // check here if this is your process
- // dbg!(process);
- return true;
- }
- false
+ let x = s
+ .processes_by_name("NorthstarLauncher.exe")
+ .next()
+ .is_some()
+ || s.processes_by_name("Titanfall2.exe").next().is_some();
+ x
}
/// Helps with converting release candidate numbers which are different on Thunderstore
@@ -364,7 +516,7 @@ pub fn convert_release_candidate_number(version_number: String) -> String {
}
/// 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> {
+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);
// Check for JSON file
@@ -381,7 +533,7 @@ pub fn get_enabled_mods(game_install: GameInstall) -> Result<serde_json::value::
// Parse JSON
let res: serde_json::Value = match serde_json::from_str(&data) {
Ok(result) => result,
- Err(err) => return Err(format!("Failed to read JSON due to: {}", err.to_string())),
+ Err(err) => return Err(format!("Failed to read JSON due to: {}", err)),
};
// Return parsed data