From e36d6dfdff71d70e38f6febc5e3c2a1c3e7c4bee Mon Sep 17 00:00:00 2001 From: cat_or_not <41955154+catornot@users.noreply.github.com> Date: Sat, 15 Apr 2023 12:34:53 -0400 Subject: [PATCH 01/18] use pass by reference where possible --- src-tauri/src/github/mod.rs | 2 +- src-tauri/src/github/pull_requests.rs | 2 +- src-tauri/src/lib.rs | 23 ++++++----- src-tauri/src/main.rs | 14 +++---- src-tauri/src/mod_management/mod.rs | 55 ++++++++++++-------------- src-tauri/src/northstar/mod.rs | 13 +++--- src-tauri/src/repair_and_verify/mod.rs | 6 +-- 7 files changed, 57 insertions(+), 58 deletions(-) diff --git a/src-tauri/src/github/mod.rs b/src-tauri/src/github/mod.rs index c46d7c609..b336ab5c4 100644 --- a/src-tauri/src/github/mod.rs +++ b/src-tauri/src/github/mod.rs @@ -86,7 +86,7 @@ pub fn compare_tags(first_tag: Tag, second_tag: Tag) -> Result { repo, first_tag.name, second_tag.name ); - let comparison: Comparison = client.get(&comparison_url).send().unwrap().json().unwrap(); + let comparison: Comparison = client.get(comparison_url).send().unwrap().json().unwrap(); let commits = comparison.commits; // Display the list of commits. diff --git a/src-tauri/src/github/pull_requests.rs b/src-tauri/src/github/pull_requests.rs index a7ac1fd23..96ac623fe 100644 --- a/src-tauri/src/github/pull_requests.rs +++ b/src-tauri/src/github/pull_requests.rs @@ -259,7 +259,7 @@ pub async fn apply_launcher_pr( 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); - match std::fs::copy(&source_file_path, &destination_file_path) { + match std::fs::copy(source_file_path, destination_file_path) { Ok(_result) => (), Err(err) => { return Err(format!( diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 56bc590cd..aa3802bd0 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -69,7 +69,7 @@ struct InstallProgress { } /// Check version number of a mod -pub fn check_mod_version_number(path_to_mod_folder: String) -> Result { +pub fn check_mod_version_number(path_to_mod_folder: &str) -> Result { // println!("{}", 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)?; @@ -333,7 +333,7 @@ pub fn get_host_os() -> String { } pub fn launch_northstar( - game_install: GameInstall, + game_install: &GameInstall, bypass_checks: Option, ) -> Result { dbg!(game_install.clone()); @@ -358,7 +358,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.clone()).is_err() { + if get_northstar_version_number(&game_install.game_path).is_err() { return Err(anyhow!("Not all checks were met").to_string()); } @@ -401,7 +401,7 @@ pub fn launch_northstar( /// Prepare Northstar and Launch through Steam using the Browser Protocol pub fn launch_northstar_steam( - game_install: GameInstall, + game_install: &GameInstall, _bypass_checks: Option, ) -> Result { if !matches!(game_install.install_type, InstallType::STEAM) { @@ -440,7 +440,7 @@ pub fn launch_northstar_steam( } // Switch to Titanfall2 directory to set everything up - if std::env::set_current_dir(game_install.game_path).is_err() { + 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()); } @@ -488,17 +488,20 @@ pub fn launch_northstar_steam( pub fn check_origin_running() -> bool { let s = sysinfo::System::new_all(); - s.processes_by_name("Origin.exe").next().is_some() - || s.processes_by_name("EADesktop.exe").next().is_some() + 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(); - s.processes_by_name("NorthstarLauncher.exe") + let x = s + .processes_by_name("NorthstarLauncher.exe") .next() .is_some() - || s.processes_by_name("Titanfall2.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 @@ -511,7 +514,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 { +pub fn get_enabled_mods(game_install: &GameInstall) -> Result { let enabledmods_json_path = format!("{}/R2Northstar/enabledmods.json", game_install.game_path); // Check for JSON file diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index 76e8833eb..3aa174599 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -202,7 +202,7 @@ async fn get_flightcore_version_number() -> String { #[tauri::command] async fn get_northstar_version_number_caller(game_path: String) -> Result { - match get_northstar_version_number(game_path) { + match get_northstar_version_number(&game_path) { Ok(version_number) => Ok(version_number), Err(err) => Err(err.to_string()), } @@ -234,7 +234,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 get_northstar_version_number(game_path) { + let version_number = match get_northstar_version_number(&game_path) { Ok(version_number) => version_number, Err(err) => { log::warn!("{}", err); @@ -323,7 +323,7 @@ async fn launch_northstar_caller( game_install: GameInstall, bypass_checks: Option, ) -> Result { - launch_northstar(game_install, bypass_checks) + launch_northstar(&game_install, bypass_checks) } #[tauri::command] @@ -332,7 +332,7 @@ async fn launch_northstar_steam_caller( game_install: GameInstall, bypass_checks: Option, ) -> Result { - launch_northstar_steam(game_install, bypass_checks) + launch_northstar_steam(&game_install, bypass_checks) } #[tauri::command] @@ -341,8 +341,8 @@ async fn install_mod_caller( game_install: GameInstall, thunderstore_mod_string: String, ) -> Result<(), String> { - fc_download_mod_and_install(game_install.clone(), thunderstore_mod_string).await?; - match clean_up_download_folder(game_install, false) { + fc_download_mod_and_install(&game_install, &thunderstore_mod_string).await?; + match clean_up_download_folder(&game_install, false) { Ok(()) => Ok(()), Err(err) => { log::info!("Failed to delete download folder due to {}", err); @@ -359,7 +359,7 @@ async fn clean_up_download_folder_caller( game_install: GameInstall, force: bool, ) -> Result<(), String> { - match clean_up_download_folder(game_install, force) { + match clean_up_download_folder(&game_install, force) { Ok(()) => Ok(()), Err(err) => Err(err.to_string()), } diff --git a/src-tauri/src/mod_management/mod.rs b/src-tauri/src/mod_management/mod.rs index 7261619ea..5d3d812d7 100644 --- a/src-tauri/src/mod_management/mod.rs +++ b/src-tauri/src/mod_management/mod.rs @@ -54,9 +54,9 @@ pub struct ModJson { } /// Gets all currently installed and enabled/disabled mods to rebuild `enabledmods.json` -pub fn rebuild_enabled_mods_json(game_install: GameInstall) -> Result<(), String> { +pub fn rebuild_enabled_mods_json(game_install: &GameInstall) -> Result<(), String> { let enabledmods_json_path = format!("{}/R2Northstar/enabledmods.json", game_install.game_path); - let mods_and_properties = get_installed_mods_and_properties(game_install)?; + let mods_and_properties = get_installed_mods_and_properties(game_install.clone())?; // Create new mapping let mut my_map = serde_json::Map::new(); @@ -89,16 +89,16 @@ pub fn set_mod_enabled_status( let enabledmods_json_path = format!("{}/R2Northstar/enabledmods.json", game_install.game_path); // Parse JSON - let mut res: serde_json::Value = match get_enabled_mods(game_install.clone()) { + let mut res: serde_json::Value = match get_enabled_mods(&game_install) { Ok(res) => res, Err(err) => { log::warn!("Couldn't parse `enabledmod.json`: {}", err); log::warn!("Rebuilding file."); - rebuild_enabled_mods_json(game_install.clone())?; + rebuild_enabled_mods_json(&game_install)?; // Then try again - get_enabled_mods(game_install.clone())? + get_enabled_mods(&game_install)? } }; @@ -106,10 +106,10 @@ pub fn set_mod_enabled_status( if res.get(mod_name.clone()).is_none() { // If it doesn't exist, rebuild `enabledmod.json` log::info!("Value not found in `enabledmod.json`. Rebuilding file"); - rebuild_enabled_mods_json(game_install.clone())?; + rebuild_enabled_mods_json(&game_install)?; // Then try again - res = get_enabled_mods(game_install)?; + res = get_enabled_mods(&game_install)?; } // Update value @@ -126,7 +126,7 @@ pub fn set_mod_enabled_status( } /// Parses `manifest.json` for Thunderstore mod string -fn parse_for_thunderstore_mod_string(nsmod_path: String) -> Result { +fn parse_for_thunderstore_mod_string(nsmod_path: &str) -> Result { let manifest_json_path = format!("{}/manifest.json", nsmod_path); let ts_author_txt_path = format!("{}/thunderstore_author.txt", nsmod_path); @@ -149,7 +149,7 @@ fn parse_for_thunderstore_mod_string(nsmod_path: String) -> Result Result, anyhow::Error> { +fn parse_installed_mods(game_install: &GameInstall) -> Result, anyhow::Error> { let ns_mods_folder = format!("{}/R2Northstar/mods/", game_install.game_path); let paths = match std::fs::read_dir(ns_mods_folder) { @@ -195,7 +195,7 @@ fn parse_installed_mods(game_install: GameInstall) -> Result, // Attempt legacy method for getting Thunderstore string first Some(ts_mod_string) => Some(ts_mod_string), // Legacy method failed - None => match parse_for_thunderstore_mod_string(directory_str) { + None => match parse_for_thunderstore_mod_string(&directory_str) { Ok(thunderstore_mod_string) => Some(thunderstore_mod_string), Err(_err) => None, }, @@ -226,13 +226,13 @@ pub fn get_installed_mods_and_properties( game_install: GameInstall, ) -> Result, String> { // Get actually installed mods - let found_installed_mods = match parse_installed_mods(game_install.clone()) { + let found_installed_mods = match parse_installed_mods(&game_install) { Ok(res) => res, Err(err) => return Err(err.to_string()), }; // Get enabled mods as JSON - let enabled_mods: serde_json::Value = match get_enabled_mods(game_install) { + let enabled_mods: serde_json::Value = match get_enabled_mods(&game_install) { Ok(enabled_mods) => enabled_mods, Err(_) => serde_json::from_str("{}").unwrap(), // `enabledmods.json` not found, create empty object }; @@ -253,7 +253,7 @@ pub fn get_installed_mods_and_properties( Ok(installed_mods) } -async fn get_ns_mod_download_url(thunderstore_mod_string: String) -> Result { +async fn get_ns_mod_download_url(thunderstore_mod_string: &str) -> Result { // TODO: This will crash the thread if not internet connection exist. `match` should be used instead let index = thermite::api::get_package_index().unwrap().to_vec(); @@ -285,10 +285,8 @@ async fn get_ns_mod_download_url(thunderstore_mod_string: String) -> Result Result, anyhow::Error> { - dbg!(thunderstore_mod_string.clone()); +async fn get_mod_dependencies(thunderstore_mod_string: &str) -> Result, anyhow::Error> { + dbg!(thunderstore_mod_string); // TODO: This will crash the thread if not internet connection exist. `match` should be used instead let index = thermite::api::get_package_index().unwrap().to_vec(); @@ -314,8 +312,8 @@ async fn get_mod_dependencies( /// Download and install mod to the specified target. #[async_recursion] pub async fn fc_download_mod_and_install( - game_install: GameInstall, - thunderstore_mod_string: String, + game_install: &GameInstall, + thunderstore_mod_string: &str, ) -> Result<(), String> { // Get mods and download directories let download_directory = format!( @@ -329,7 +327,7 @@ pub async fn fc_download_mod_and_install( return Err("Passed empty string".to_string()); } - let deps = match get_mod_dependencies(thunderstore_mod_string.clone()).await { + let deps = match get_mod_dependencies(thunderstore_mod_string).await { Ok(deps) => deps, Err(err) => return Err(err.to_string()), }; @@ -337,7 +335,7 @@ pub async fn fc_download_mod_and_install( // Recursively install dependencies for dep in deps { - match fc_download_mod_and_install(game_install.clone(), dep).await { + match fc_download_mod_and_install(game_install, &dep).await { Ok(()) => (), Err(err) => { if err == "Cannot install Northstar as a mod!" { @@ -358,7 +356,7 @@ pub async fn fc_download_mod_and_install( } // Get download URL for the specified mod - let download_url = get_ns_mod_download_url(thunderstore_mod_string.clone()).await?; + let download_url = get_ns_mod_download_url(thunderstore_mod_string).await?; // Create download directory match std::fs::create_dir_all(download_directory.clone()) { @@ -366,10 +364,9 @@ pub async fn fc_download_mod_and_install( Err(err) => return Err(err.to_string()), }; - let name = thunderstore_mod_string.clone(); let path = format!( - "{}/___flightcore-temp-download-dir/{}.zip", - game_install.game_path, name + "{}/___flightcore-temp-download-dir/{thunderstore_mod_string}.zip", + game_install.game_path ); // Download the mod @@ -394,7 +391,7 @@ pub async fn fc_download_mod_and_install( } /// Deletes a given Northstar mod folder -fn delete_mod_folder(ns_mod_directory: String) -> Result<(), String> { +fn delete_mod_folder(ns_mod_directory: &str) -> Result<(), String> { let ns_mod_dir_path = std::path::Path::new(&ns_mod_directory); // Safety check: Check whether `mod.json` exists and exit early if not @@ -405,7 +402,7 @@ fn delete_mod_folder(ns_mod_directory: String) -> Result<(), String> { return Err(format!("mod.json does not exist in {}", ns_mod_directory)); } - match std::fs::remove_dir_all(&ns_mod_directory) { + match std::fs::remove_dir_all(ns_mod_directory) { Ok(()) => Ok(()), Err(err) => Err(format!("Failed deleting mod: {err}")), } @@ -429,7 +426,7 @@ pub fn delete_northstar_mod(game_install: GameInstall, nsmod_name: String) -> Re // Installed mod matches specified mod if installed_ns_mod.name == nsmod_name { // Delete folder - return delete_mod_folder(installed_ns_mod.directory); + return delete_mod_folder(&installed_ns_mod.directory); } } @@ -488,7 +485,7 @@ pub fn delete_thunderstore_mod( // Delete given folders for mod_folder in mod_folders_to_remove { - delete_mod_folder(mod_folder)?; + delete_mod_folder(&mod_folder)?; } Ok(()) diff --git a/src-tauri/src/northstar/mod.rs b/src-tauri/src/northstar/mod.rs index f3f8cde31..7bd0b0a3b 100644 --- a/src-tauri/src/northstar/mod.rs +++ b/src-tauri/src/northstar/mod.rs @@ -5,24 +5,23 @@ use crate::{check_mod_version_number, constants::CORE_MODS}; use anyhow::anyhow; /// Returns the current Northstar version number as a string -pub fn get_northstar_version_number(game_path: String) -> Result { +pub fn get_northstar_version_number(game_path: &str) -> Result { log::info!("{}", 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!( - "{}/{}/mods/{}", - game_path, profile_folder, CORE_MODS[0] + let initial_version_number = match check_mod_version_number(&format!( + "{game_path}/{profile_folder}/mods/{}", + CORE_MODS[0] )) { Ok(version_number) => version_number, Err(err) => return Err(err), }; for core_mod in CORE_MODS { - let current_version_number = match check_mod_version_number(format!( - "{}/{}/mods/{}", - game_path, profile_folder, core_mod + let current_version_number = match check_mod_version_number(&format!( + "{game_path}/{profile_folder}/mods/{core_mod}", )) { Ok(version_number) => version_number, Err(err) => return Err(err), diff --git a/src-tauri/src/repair_and_verify/mod.rs b/src-tauri/src/repair_and_verify/mod.rs index b3dbe3b27..ecf5e1ec3 100644 --- a/src-tauri/src/repair_and_verify/mod.rs +++ b/src-tauri/src/repair_and_verify/mod.rs @@ -15,9 +15,9 @@ pub fn verify_game_files(game_install: GameInstall) -> Result { #[tauri::command] pub fn disable_all_but_core(game_install: GameInstall) -> Result<(), String> { // Rebuild `enabledmods.json` first to ensure all mods are added - rebuild_enabled_mods_json(game_install.clone())?; + rebuild_enabled_mods_json(&game_install)?; - let current_mods = get_enabled_mods(game_install.clone())?; + let current_mods = get_enabled_mods(&game_install)?; // Disable all mods, set core mods to enabled for (key, _value) in current_mods.as_object().unwrap() { @@ -37,7 +37,7 @@ pub fn disable_all_but_core(game_install: GameInstall) -> Result<(), String> { /// If `force` is FALSE, bails on non-empty folder /// If `force` is TRUE, deletes folder even if non-empty pub fn clean_up_download_folder( - game_install: GameInstall, + game_install: &GameInstall, force: bool, ) -> Result<(), anyhow::Error> { // Get download directory From c0ff70e03e5ea7ce05f4b27002379b82826212bc Mon Sep 17 00:00:00 2001 From: cat_or_not <41955154+catornot@users.noreply.github.com> Date: Sun, 16 Apr 2023 01:30:05 -0400 Subject: [PATCH 02/18] installing plugins --- src-tauri/Cargo.lock | 1 + src-tauri/Cargo.toml | 2 + src-tauri/src/main.rs | 4 ++ src-tauri/src/mod_management/mod.rs | 35 +++++++++--- src-tauri/src/plugin_management/mod.rs | 75 ++++++++++++++++++++++++++ src-vue/src/views/DeveloperView.vue | 15 ++++++ 6 files changed, 124 insertions(+), 8 deletions(-) create mode 100644 src-tauri/src/plugin_management/mod.rs diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index 95476b66d..0f597a8ea 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -97,6 +97,7 @@ dependencies = [ "json5", "libthermite", "log", + "once_cell", "open", "pretty_env_logger", "regex", diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 693853901..a3d7063ca 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -63,6 +63,8 @@ log = "0.4.17" zip-extract = "0.1.2" # open urls open = "3.2.0" +# for statics +once_cell = "1.17.1" [features] # by default Tauri runs in production mode diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index 3aa174599..6422684d0 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -34,6 +34,9 @@ use mod_management::{ get_installed_mods_and_properties, set_mod_enabled_status, }; +mod plugin_management; +use plugin_management::toggle_plugin_install; + mod northstar; use northstar::get_northstar_version_number; @@ -146,6 +149,7 @@ fn main() { apply_mods_pr, get_launcher_download_link, close_application, + toggle_plugin_install, ]) .run(tauri::generate_context!()) .expect("error while running tauri application"); diff --git a/src-tauri/src/mod_management/mod.rs b/src-tauri/src/mod_management/mod.rs index 5d3d812d7..3b088e3d2 100644 --- a/src-tauri/src/mod_management/mod.rs +++ b/src-tauri/src/mod_management/mod.rs @@ -8,10 +8,13 @@ use app::NorthstarMod; use serde::{Deserialize, Serialize}; use std::io::Read; use std::path::PathBuf; +use thermite::prelude::ThermiteError; use app::get_enabled_mods; use app::GameInstall; +use crate::plugin_management::install_plugin; + #[derive(Debug, Clone)] struct ParsedThunderstoreModString { author_name: String, @@ -370,24 +373,40 @@ pub async fn fc_download_mod_and_install( ); // Download the mod - let f = match thermite::core::manage::download_file(download_url, path.clone()) { - Ok(f) => f, - Err(e) => return Err(e.to_string()), - }; + let f = thermite::core::manage::download_file(download_url, path.clone()) + .map_err(|err| err.to_string())?; // Get Thunderstore mod author let author = thunderstore_mod_string.split('-').next().unwrap(); // Extract the mod to the mods directory - match thermite::core::manage::install_mod(author, &f, std::path::Path::new(&mods_directory)) { - Ok(()) => (), - Err(err) => return Err(err.to_string()), + let result_mod = match thermite::core::manage::install_mod( + author, + &f, + std::path::Path::new(&mods_directory), + ) { + Ok(()) => Ok(()), + err if matches!(err, Err(ThermiteError::PrefixError(_))) => err, // probably happens when there is not mod folder found + Err(err) => Err(err.to_string())?, + }; + + // Injected plugin install + let result_plugin = match install_plugin(game_install, &f).await { + err if matches!(err, Err(ThermiteError::MissingFile(_))) => err, + Err(err) => Err(err.to_string())?, + r => r, }; // Delete downloaded zip file std::fs::remove_file(path).unwrap(); - Ok(()) + // Because of the match expression only errors that can indicate missing mod/plugins folder + // we can check say that it worked if the plugin install worked + if result_plugin.is_ok() { + Ok(()) + } else { + result_mod.map_err(|e| e.to_string()) + } } /// Deletes a given Northstar mod folder diff --git a/src-tauri/src/plugin_management/mod.rs b/src-tauri/src/plugin_management/mod.rs new file mode 100644 index 000000000..099847bca --- /dev/null +++ b/src-tauri/src/plugin_management/mod.rs @@ -0,0 +1,75 @@ +use app::GameInstall; +use once_cell::sync::Lazy; +use std::{ + fs::{self, File, OpenOptions}, + io, + path::PathBuf, +}; +use tauri::async_runtime::Mutex; +use thermite::{core::utils::TempDir, prelude::ThermiteError}; +use zip::ZipArchive; + +static CAN_INSTALL_PLUGINS: Lazy> = Lazy::new(|| Mutex::new(false)); + +/// Tries to install plugins from a thunderstore zip +pub async fn install_plugin( + game_install: &GameInstall, + zip_file: &File, +) -> Result<(), ThermiteError> { + if !*CAN_INSTALL_PLUGINS.lock().await { + return Err(ThermiteError::MiscError("Plugin installing disabled! If you know what you are doing plugins can be enabled in dev settings.".to_string())); + } + + let plugins_directory = PathBuf::new() + .join(&game_install.game_path) + .join("R2Northstar") + .join("plugins"); + let temp_dir = TempDir::create(plugins_directory.join("___flightcore-temp-plugin-dir"))?; + let mut archive = ZipArchive::new(zip_file)?; + + for i in 0..archive.len() { + let mut file = archive.by_index(i)?; + + if file.enclosed_name().is_none() || file.enclosed_name().unwrap().starts_with(".") { + continue; + } + + let out = temp_dir.join(file.enclosed_name().unwrap()); + + if (*file.name()).ends_with('/') { + fs::create_dir_all(&out)?; + continue; + } else if let Some(p) = out.parent() { + fs::create_dir_all(p)?; + } + let mut outfile = OpenOptions::new() + .create(true) + .write(true) + .truncate(true) + .open(&out)?; + io::copy(&mut file, &mut outfile)?; + } + + for file in temp_dir + .join("plugins") + .read_dir() + .map_err(|_| ThermiteError::MissingFile(Box::new(temp_dir.join("plugins"))))? + .filter_map(|f| f.ok()) // ignore any errors + .filter(|f| f.path().extension().map(|e| e == "dll").unwrap_or(false)) // check for dll extension + .inspect(|f| { + _ = fs::remove_file(plugins_directory.join(f.file_name().to_string_lossy().to_string())) + // try remove plugins to update + }) + { + fs::copy(file.path(), plugins_directory.join(file.file_name()))?; + } + + Ok(()) +} + +#[tauri::command] +pub async fn toggle_plugin_install() -> bool { + let mut lock = CAN_INSTALL_PLUGINS.lock().await; + *lock = !*lock; + *lock +} diff --git a/src-vue/src/views/DeveloperView.vue b/src-vue/src/views/DeveloperView.vue index d9e268e32..6d8431805 100644 --- a/src-vue/src/views/DeveloperView.vue +++ b/src-vue/src/views/DeveloperView.vue @@ -47,6 +47,14 @@ Panic button +

Installing Plugins:

+ + + Plugin Install Toggle + + + plugin_install_state +

Linux:

@@ -104,6 +112,7 @@ export default defineComponent({ first_tag: { label: '', value: {name: ''} }, second_tag: { label: '', value: {name: ''} }, ns_release_tags: [] as TagWrapper[], + plugin_install_state : "Disabled", } }, computed: { @@ -132,6 +141,12 @@ export default defineComponent({ await invoke("force_panic"); showErrorNotification("Never should have been able to get here!"); }, + async togglePluginsInstall() { + await invoke("toggle_plugin_install") + .then((state) => { + this.plugin_install_state = state ? "Enabled" : "Disabled"; + }) + }, async checkLinuxCompatibility() { await invoke("linux_checks") .then(() => { From 3ef92f77411b65ee2136702c834e64abadaa42f6 Mon Sep 17 00:00:00 2001 From: cat_or_not <41955154+catornot@users.noreply.github.com> Date: Wed, 19 Apr 2023 17:51:25 -0400 Subject: [PATCH 03/18] move plugins install state to frontend --- src-tauri/src/main.rs | 7 ++--- src-tauri/src/mod_management/mod.rs | 26 +++++++++++++------ src-tauri/src/plugin_management/mod.rs | 17 +----------- .../src/components/ThunderstoreModCard.vue | 2 +- src-vue/src/plugins/store.ts | 4 +++ src-vue/src/views/DeveloperView.vue | 22 +++++++++------- 6 files changed, 41 insertions(+), 37 deletions(-) diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index 6422684d0..37b0e24ae 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -35,7 +35,6 @@ use mod_management::{ }; mod plugin_management; -use plugin_management::toggle_plugin_install; mod northstar; use northstar::get_northstar_version_number; @@ -149,7 +148,6 @@ fn main() { apply_mods_pr, get_launcher_download_link, close_application, - toggle_plugin_install, ]) .run(tauri::generate_context!()) .expect("error while running tauri application"); @@ -344,8 +342,11 @@ async fn launch_northstar_steam_caller( async fn install_mod_caller( game_install: GameInstall, thunderstore_mod_string: String, + can_install_plugins: bool, ) -> Result<(), String> { - fc_download_mod_and_install(&game_install, &thunderstore_mod_string).await?; + log::info!("can_install_plugins {can_install_plugins}"); + fc_download_mod_and_install(&game_install, &thunderstore_mod_string, can_install_plugins) + .await?; match clean_up_download_folder(&game_install, false) { Ok(()) => Ok(()), Err(err) => { diff --git a/src-tauri/src/mod_management/mod.rs b/src-tauri/src/mod_management/mod.rs index d06c7bc1e..69568a89d 100644 --- a/src-tauri/src/mod_management/mod.rs +++ b/src-tauri/src/mod_management/mod.rs @@ -317,6 +317,7 @@ async fn get_mod_dependencies(thunderstore_mod_string: &str) -> Result Result<(), String> { // Get mods and download directories let download_directory = format!( @@ -338,7 +339,7 @@ pub async fn fc_download_mod_and_install( // Recursively install dependencies for dep in deps { - match fc_download_mod_and_install(game_install, &dep).await { + match fc_download_mod_and_install(game_install, &dep, can_install_plugins).await { Ok(()) => (), Err(err) => { if err == "Cannot install Northstar as a mod!" { @@ -373,8 +374,10 @@ pub async fn fc_download_mod_and_install( ); // Download the mod - let f = thermite::core::manage::download_file(download_url, path.clone()) - .map_err(|err| err.to_string())?; + let f = match thermite::core::manage::download_file(download_url, path.clone()) { + Ok(f) => f, + Err(e) => return Err(e.to_string()), + }; // Get Thunderstore mod author let author = thunderstore_mod_string.split('-').next().unwrap(); @@ -390,18 +393,25 @@ pub async fn fc_download_mod_and_install( Err(err) => Err(err.to_string())?, }; + println!("can_install_plugins {can_install_plugins}"); + // Injected plugin install - let result_plugin = match install_plugin(game_install, &f).await { - err if matches!(err, Err(ThermiteError::MissingFile(_))) => err, - Err(err) => Err(err.to_string())?, - r => r, + let result_plugin = if can_install_plugins { + match install_plugin(game_install, &f).await { + err if matches!(err, Err(ThermiteError::MissingFile(_))) => err, + Err(err) => Err(err.to_string())?, + r => r, + } + } else { + // this would never up + Err(ThermiteError::MiscError(String::new())) }; // Delete downloaded zip file std::fs::remove_file(path).unwrap(); // Because of the match expression only errors that can indicate missing mod/plugins folder - // we can check say that it worked if the plugin install worked + // we can say that it worked if the plugin install worked if result_plugin.is_ok() { Ok(()) } else { diff --git a/src-tauri/src/plugin_management/mod.rs b/src-tauri/src/plugin_management/mod.rs index 099847bca..f3ad2d9b3 100644 --- a/src-tauri/src/plugin_management/mod.rs +++ b/src-tauri/src/plugin_management/mod.rs @@ -1,25 +1,17 @@ use app::GameInstall; -use once_cell::sync::Lazy; use std::{ fs::{self, File, OpenOptions}, io, path::PathBuf, }; -use tauri::async_runtime::Mutex; use thermite::{core::utils::TempDir, prelude::ThermiteError}; use zip::ZipArchive; -static CAN_INSTALL_PLUGINS: Lazy> = Lazy::new(|| Mutex::new(false)); - /// Tries to install plugins from a thunderstore zip pub async fn install_plugin( game_install: &GameInstall, zip_file: &File, ) -> Result<(), ThermiteError> { - if !*CAN_INSTALL_PLUGINS.lock().await { - return Err(ThermiteError::MiscError("Plugin installing disabled! If you know what you are doing plugins can be enabled in dev settings.".to_string())); - } - let plugins_directory = PathBuf::new() .join(&game_install.game_path) .join("R2Northstar") @@ -65,11 +57,4 @@ pub async fn install_plugin( } Ok(()) -} - -#[tauri::command] -pub async fn toggle_plugin_install() -> bool { - let mut lock = CAN_INSTALL_PLUGINS.lock().await; - *lock = !*lock; - *lock -} +} \ No newline at end of file diff --git a/src-vue/src/components/ThunderstoreModCard.vue b/src-vue/src/components/ThunderstoreModCard.vue index fec95f147..bcfac77e6 100644 --- a/src-vue/src/components/ThunderstoreModCard.vue +++ b/src-vue/src/components/ThunderstoreModCard.vue @@ -243,7 +243,7 @@ export default defineComponent({ this.isBeingInstalled = true; } - await invoke("install_mod_caller", { gameInstall: game_install, thunderstoreModString: this.latestVersion.full_name }).then((message) => { + await invoke("install_mod_caller", { gameInstall: game_install, thunderstoreModString: this.latestVersion.full_name, can_install_plugins: $store.state.can_install_plugins }).then((message) => { showNotification(this.$t('mods.card.install_success', {modName: mod.name}), message); }) .catch((error) => { diff --git a/src-vue/src/plugins/store.ts b/src-vue/src/plugins/store.ts index 00b8f35a1..089647766 100644 --- a/src-vue/src/plugins/store.ts +++ b/src-vue/src/plugins/store.ts @@ -48,6 +48,8 @@ export interface FlightCoreStore { // user custom settings mods_per_page: number, + + can_install_plugins: boolean, } let notification_handle: NotificationHandle; @@ -83,6 +85,8 @@ export const store = createStore({ server_count: -1, mods_per_page: 20, + + can_install_plugins: false, } }, mutations: { diff --git a/src-vue/src/views/DeveloperView.vue b/src-vue/src/views/DeveloperView.vue index 6d8431805..426dd9b90 100644 --- a/src-vue/src/views/DeveloperView.vue +++ b/src-vue/src/views/DeveloperView.vue @@ -49,11 +49,11 @@

Installing Plugins:

- + Plugin Install Toggle - plugin_install_state +

Linux:

@@ -112,7 +112,7 @@ export default defineComponent({ first_tag: { label: '', value: {name: ''} }, second_tag: { label: '', value: {name: ''} }, ns_release_tags: [] as TagWrapper[], - plugin_install_state : "Disabled", + can_install_plugins_state : "Disabled", } }, computed: { @@ -141,11 +141,15 @@ export default defineComponent({ await invoke("force_panic"); showErrorNotification("Never should have been able to get here!"); }, - async togglePluginsInstall() { - await invoke("toggle_plugin_install") - .then((state) => { - this.plugin_install_state = state ? "Enabled" : "Disabled"; - }) + togglePluginsInstalling() { + let new_state = !$store.state.can_install_plugins; + + console.error(new_state); + + $store.state.can_install_plugins = new_state; + this.can_install_plugins_state = new_state ? "Enabled" : "Disabled"; + + showNotification('Plugins Install', this.can_install_plugins_state); // for testing }, async checkLinuxCompatibility() { await invoke("linux_checks") @@ -186,7 +190,7 @@ export default defineComponent({ install_type: this.$store.state.install_type } as GameInstall; let mod_to_install = this.mod_to_install_field_string; - await invoke("install_mod_caller", { gameInstall: game_install, thunderstoreModString: mod_to_install }).then((message) => { + await invoke("install_mod_caller", { gameInstall: game_install, thunderstoreModString: mod_to_install, can_install_plugins : $store.state.can_install_plugins }).then((message) => { // Show user notification if mod install completed. showNotification(`Installed ${mod_to_install}`, message); }) From a707a38c093ad5f34c659d323b7298a99e931a4f Mon Sep 17 00:00:00 2001 From: cat_or_not <41955154+catornot@users.noreply.github.com> Date: Wed, 19 Apr 2023 18:33:32 -0400 Subject: [PATCH 04/18] fix silly mistake --- src-tauri/src/main.rs | 1 - src-tauri/src/mod_management/mod.rs | 2 -- src-vue/src/components/ThunderstoreModCard.vue | 2 +- src-vue/src/views/DeveloperView.vue | 11 +++-------- 4 files changed, 4 insertions(+), 12 deletions(-) diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index 37b0e24ae..021ee3447 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -344,7 +344,6 @@ async fn install_mod_caller( thunderstore_mod_string: String, can_install_plugins: bool, ) -> Result<(), String> { - log::info!("can_install_plugins {can_install_plugins}"); fc_download_mod_and_install(&game_install, &thunderstore_mod_string, can_install_plugins) .await?; match clean_up_download_folder(&game_install, false) { diff --git a/src-tauri/src/mod_management/mod.rs b/src-tauri/src/mod_management/mod.rs index 69568a89d..098de7910 100644 --- a/src-tauri/src/mod_management/mod.rs +++ b/src-tauri/src/mod_management/mod.rs @@ -393,8 +393,6 @@ pub async fn fc_download_mod_and_install( Err(err) => Err(err.to_string())?, }; - println!("can_install_plugins {can_install_plugins}"); - // Injected plugin install let result_plugin = if can_install_plugins { match install_plugin(game_install, &f).await { diff --git a/src-vue/src/components/ThunderstoreModCard.vue b/src-vue/src/components/ThunderstoreModCard.vue index bcfac77e6..1fc5cdbfc 100644 --- a/src-vue/src/components/ThunderstoreModCard.vue +++ b/src-vue/src/components/ThunderstoreModCard.vue @@ -243,7 +243,7 @@ export default defineComponent({ this.isBeingInstalled = true; } - await invoke("install_mod_caller", { gameInstall: game_install, thunderstoreModString: this.latestVersion.full_name, can_install_plugins: $store.state.can_install_plugins }).then((message) => { + await invoke("install_mod_caller", { gameInstall: game_install, thunderstoreModString: this.latestVersion.full_name, canInstallPlugins: this.$store.state.can_install_plugins }).then((message) => { showNotification(this.$t('mods.card.install_success', {modName: mod.name}), message); }) .catch((error) => { diff --git a/src-vue/src/views/DeveloperView.vue b/src-vue/src/views/DeveloperView.vue index 426dd9b90..acad15563 100644 --- a/src-vue/src/views/DeveloperView.vue +++ b/src-vue/src/views/DeveloperView.vue @@ -142,14 +142,9 @@ export default defineComponent({ showErrorNotification("Never should have been able to get here!"); }, togglePluginsInstalling() { - let new_state = !$store.state.can_install_plugins; - - console.error(new_state); - - $store.state.can_install_plugins = new_state; + let new_state = !this.$store.state.can_install_plugins; + this.$store.state.can_install_plugins = new_state; this.can_install_plugins_state = new_state ? "Enabled" : "Disabled"; - - showNotification('Plugins Install', this.can_install_plugins_state); // for testing }, async checkLinuxCompatibility() { await invoke("linux_checks") @@ -190,7 +185,7 @@ export default defineComponent({ install_type: this.$store.state.install_type } as GameInstall; let mod_to_install = this.mod_to_install_field_string; - await invoke("install_mod_caller", { gameInstall: game_install, thunderstoreModString: mod_to_install, can_install_plugins : $store.state.can_install_plugins }).then((message) => { + await invoke("install_mod_caller", { gameInstall: game_install, thunderstoreModString: mod_to_install, canInstallPlugins : $store.state.can_install_plugins }).then((message) => { // Show user notification if mod install completed. showNotification(`Installed ${mod_to_install}`, message); }) From 5812a55a06a660588d9da4ec4a27722bf6e498e2 Mon Sep 17 00:00:00 2001 From: cat_or_not <41955154+catornot@users.noreply.github.com> Date: Wed, 19 Apr 2023 18:42:38 -0400 Subject: [PATCH 05/18] huh --- src-tauri/src/mod_management/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src-tauri/src/mod_management/mod.rs b/src-tauri/src/mod_management/mod.rs index 098de7910..c32935bf9 100644 --- a/src-tauri/src/mod_management/mod.rs +++ b/src-tauri/src/mod_management/mod.rs @@ -401,7 +401,7 @@ pub async fn fc_download_mod_and_install( r => r, } } else { - // this would never up + // this would never up show Err(ThermiteError::MiscError(String::new())) }; From 73ccb923055841fd877546cc93ac5c26da0d9c6c Mon Sep 17 00:00:00 2001 From: cat_or_not <41955154+catornot@users.noreply.github.com> Date: Wed, 19 Apr 2023 18:44:56 -0400 Subject: [PATCH 06/18] no more mistakes in frontend --- src-tauri/src/plugin_management/mod.rs | 2 +- src-vue/src/views/DeveloperView.vue | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src-tauri/src/plugin_management/mod.rs b/src-tauri/src/plugin_management/mod.rs index f3ad2d9b3..e26e3bc4b 100644 --- a/src-tauri/src/plugin_management/mod.rs +++ b/src-tauri/src/plugin_management/mod.rs @@ -57,4 +57,4 @@ pub async fn install_plugin( } Ok(()) -} \ No newline at end of file +} diff --git a/src-vue/src/views/DeveloperView.vue b/src-vue/src/views/DeveloperView.vue index acad15563..c1b0ffccb 100644 --- a/src-vue/src/views/DeveloperView.vue +++ b/src-vue/src/views/DeveloperView.vue @@ -185,7 +185,7 @@ export default defineComponent({ install_type: this.$store.state.install_type } as GameInstall; let mod_to_install = this.mod_to_install_field_string; - await invoke("install_mod_caller", { gameInstall: game_install, thunderstoreModString: mod_to_install, canInstallPlugins : $store.state.can_install_plugins }).then((message) => { + await invoke("install_mod_caller", { gameInstall: game_install, thunderstoreModString: mod_to_install, canInstallPlugins : this.$store.state.can_install_plugins }).then((message) => { // Show user notification if mod install completed. showNotification(`Installed ${mod_to_install}`, message); }) From 822202fa8d4f226203405743791e1b883b7f3170 Mon Sep 17 00:00:00 2001 From: cat_or_not <41955154+catornot@users.noreply.github.com> Date: Wed, 19 Apr 2023 20:31:03 -0400 Subject: [PATCH 07/18] add a popup warning to plugin installing --- src-tauri/src/main.rs | 13 ++++- src-tauri/src/plugin_management/mod.rs | 66 +++++++++++++++++++++++--- src-vue/src/plugins/store.ts | 20 ++++++++ 3 files changed, 92 insertions(+), 7 deletions(-) diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index 021ee3447..e6dec9038 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -3,6 +3,8 @@ windows_subsystem = "windows" )] +use once_cell::sync::OnceCell; + use std::{ env, sync::{Arc, Mutex}, @@ -35,6 +37,7 @@ use mod_management::{ }; mod plugin_management; +use crate::plugin_management::{receive_install_status, InstallStatusSender}; mod northstar; use northstar::get_northstar_version_number; @@ -42,12 +45,14 @@ use northstar::get_northstar_version_number; mod thunderstore; use thunderstore::query_thunderstore_packages_api; -use tauri::{Manager, Runtime}; +use tauri::{AppHandle, Manager, Runtime}; use tokio::time::sleep; #[derive(Default)] struct Counter(Arc>); +pub static APP_HANDLE: OnceCell = OnceCell::new(); + fn main() { // Setup logger let mut log_builder = pretty_env_logger::formatted_builder(); @@ -109,9 +114,14 @@ fn main() { } }); + APP_HANDLE + .set(app.app_handle()) + .expect("failed to set a app handle"); + Ok(()) }) .manage(Counter(Default::default())) + .manage(InstallStatusSender::new()) .invoke_handler(tauri::generate_handler![ force_panic, find_game_install_location_caller, @@ -148,6 +158,7 @@ fn main() { apply_mods_pr, get_launcher_download_link, close_application, + receive_install_status, ]) .run(tauri::generate_context!()) .expect("error while running tauri application"); diff --git a/src-tauri/src/plugin_management/mod.rs b/src-tauri/src/plugin_management/mod.rs index e26e3bc4b..e468d9bdb 100644 --- a/src-tauri/src/plugin_management/mod.rs +++ b/src-tauri/src/plugin_management/mod.rs @@ -1,12 +1,35 @@ use app::GameInstall; +use once_cell::sync::OnceCell; use std::{ fs::{self, File, OpenOptions}, io, path::PathBuf, }; +use tauri::{ + async_runtime::{block_on, channel, Mutex, Receiver, Sender}, + Manager, State, +}; use thermite::{core::utils::TempDir, prelude::ThermiteError}; use zip::ZipArchive; +use crate::APP_HANDLE; + +static INSTALL_STATUS_RECV: OnceCell>> = OnceCell::new(); + +pub struct InstallStatusSender(Mutex>); + +impl InstallStatusSender { + pub fn new() -> Self { + let (send, recv) = channel(1); + + INSTALL_STATUS_RECV + .set(Mutex::new(recv)) + .expect("failed to set INSTALL_STATUS_RECV"); + + Self(Mutex::new(send)) + } +} + /// Tries to install plugins from a thunderstore zip pub async fn install_plugin( game_install: &GameInstall, @@ -42,19 +65,50 @@ pub async fn install_plugin( io::copy(&mut file, &mut outfile)?; } - for file in temp_dir + let plugins: Vec = temp_dir .join("plugins") .read_dir() .map_err(|_| ThermiteError::MissingFile(Box::new(temp_dir.join("plugins"))))? .filter_map(|f| f.ok()) // ignore any errors .filter(|f| f.path().extension().map(|e| e == "dll").unwrap_or(false)) // check for dll extension - .inspect(|f| { - _ = fs::remove_file(plugins_directory.join(f.file_name().to_string_lossy().to_string())) - // try remove plugins to update - }) - { + .collect(); + + // warn user + if !plugins.is_empty() { + APP_HANDLE + .wait() + .emit_all("display-plugin-warning", ()) + .map_err(|err| ThermiteError::MiscError(err.to_string()))?; + + if !INSTALL_STATUS_RECV + .wait() + .lock() + .await + .recv() + .await + .unwrap_or(false) + { + Err(ThermiteError::MiscError( + "user denided plugin installing".to_string(), + ))? + } + } + + for file in plugins.iter().inspect(|f| { + _ = fs::remove_file(plugins_directory.join(f.file_name().to_string_lossy().to_string())) + // try remove plugins to update + }) { fs::copy(file.path(), plugins_directory.join(file.file_name()))?; } Ok(()) } + +#[tauri::command] +pub fn receive_install_status( + sender: State<'_, InstallStatusSender>, + comfirmed_install: bool, +) -> Result<(), String> { + block_on(async { sender.0.lock().await.send(comfirmed_install).await }) + .map_err(|err| err.to_string()) +} diff --git a/src-vue/src/plugins/store.ts b/src-vue/src/plugins/store.ts index 089647766..cb365fce4 100644 --- a/src-vue/src/plugins/store.ts +++ b/src-vue/src/plugins/store.ts @@ -19,6 +19,7 @@ import { searchModule } from './modules/search'; import { i18n } from '../main'; import { pullRequestModule } from './modules/pull_requests'; import { showErrorNotification, showNotification } from '../utils/ui'; +import { ElMessageBox } from "element-plus"; const persistentStore = new Store('flight-core-settings.json'); @@ -478,6 +479,10 @@ function _initializeListeners(state: any) { state.player_count = evt.payload.Ok[0]; state.server_count = evt.payload.Ok[1]; }); + + listen("display-plugin-warning", async function (evt: TauriEvent) { + await display_plugin_warning() // could also disaplay the names of the plugins + }); } /** @@ -506,3 +511,18 @@ async function _get_northstar_version_number(state: any) { state.northstar_state = NorthstarState.INSTALL; }) } + +async function display_plugin_warning() { + ElMessageBox.alert("This mod contains a plugin. Plugins CAN BE REALLY DANGEROURS since they have to access to your system!", "Warning"); + + // wait 5 seconds + await new Promise((resolve) => setTimeout(resolve, 5000)); + + await ElMessageBox.confirm("Do you still want to install this mod with a plugin?", "Warning", { confirmButtonText: "YES", cancelButtonText: "Cancel", type: "warning", }) + .then(() => { + invoke("receive_install_status", { comfirmedInstall: true }) + }) + .catch(() => { + invoke("receive_install_status", { comfirmedInstall: false }) + }) +} \ No newline at end of file From 190a3904b1eb4796e077726d2ddbb6f7b103f117 Mon Sep 17 00:00:00 2001 From: cat_or_not <41955154+catornot@users.noreply.github.com> Date: Thu, 20 Apr 2023 16:54:02 -0400 Subject: [PATCH 08/18] fix issues with frontend --- src-tauri/src/mod_management/mod.rs | 14 +++++--------- src-tauri/src/plugin_management/mod.rs | 13 +++++++++++++ src-vue/src/plugins/store.ts | 13 +++++++------ src-vue/src/views/DeveloperView.vue | 2 +- 4 files changed, 26 insertions(+), 16 deletions(-) diff --git a/src-tauri/src/mod_management/mod.rs b/src-tauri/src/mod_management/mod.rs index c32935bf9..df8ec745f 100644 --- a/src-tauri/src/mod_management/mod.rs +++ b/src-tauri/src/mod_management/mod.rs @@ -394,15 +394,11 @@ pub async fn fc_download_mod_and_install( }; // Injected plugin install - let result_plugin = if can_install_plugins { - match install_plugin(game_install, &f).await { - err if matches!(err, Err(ThermiteError::MissingFile(_))) => err, - Err(err) => Err(err.to_string())?, - r => r, - } - } else { - // this would never up show - Err(ThermiteError::MiscError(String::new())) + + let result_plugin = match install_plugin(game_install, &f, can_install_plugins).await { + err if matches!(err, Err(ThermiteError::MissingFile(_))) => err, + Err(err) => Err(err.to_string())?, + r => r, }; // Delete downloaded zip file diff --git a/src-tauri/src/plugin_management/mod.rs b/src-tauri/src/plugin_management/mod.rs index e468d9bdb..afd529513 100644 --- a/src-tauri/src/plugin_management/mod.rs +++ b/src-tauri/src/plugin_management/mod.rs @@ -34,6 +34,7 @@ impl InstallStatusSender { pub async fn install_plugin( game_install: &GameInstall, zip_file: &File, + can_install_plugins: bool, ) -> Result<(), ThermiteError> { let plugins_directory = PathBuf::new() .join(&game_install.game_path) @@ -75,6 +76,14 @@ pub async fn install_plugin( // warn user if !plugins.is_empty() { + + // check here instead if we can install plugins so people don't get broken mods without plugins + if !can_install_plugins { + Err(ThermiteError::MiscError( + "plugin installing is disabled; this mod contains a plugin; plugin can be enabled in the dev menu".to_string(), + ))? + } + APP_HANDLE .wait() .emit_all("display-plugin-warning", ()) @@ -92,6 +101,10 @@ pub async fn install_plugin( "user denided plugin installing".to_string(), ))? } + } else { + Err(ThermiteError::MissingFile(Box::new( + temp_dir.join("plugins/anyplugins.dll"), + )))?; } for file in plugins.iter().inspect(|f| { diff --git a/src-vue/src/plugins/store.ts b/src-vue/src/plugins/store.ts index cb365fce4..c44220b20 100644 --- a/src-vue/src/plugins/store.ts +++ b/src-vue/src/plugins/store.ts @@ -513,16 +513,17 @@ async function _get_northstar_version_number(state: any) { } async function display_plugin_warning() { - ElMessageBox.alert("This mod contains a plugin. Plugins CAN BE REALLY DANGEROURS since they have to access to your system!", "Warning"); + // just wait for the end user to click ok button + await ElMessageBox.alert("This mod contains a plugin. Plugins CAN BE REALLY DANGEROURS since they have to access to your system!", "Warning"); - // wait 5 seconds - await new Promise((resolve) => setTimeout(resolve, 5000)); - await ElMessageBox.confirm("Do you still want to install this mod with a plugin?", "Warning", { confirmButtonText: "YES", cancelButtonText: "Cancel", type: "warning", }) + // I switched comfirm and cancel button so end users don't spam throught the popups + // without reading and install a plugin by accident + await ElMessageBox.confirm("Do you still want to install this mod with a plugin?", "Warning", { confirmButtonText: "No", cancelButtonText: "Yes", type: "warning", }) .then(() => { - invoke("receive_install_status", { comfirmedInstall: true }) + invoke("receive_install_status", { comfirmedInstall: false }) }) .catch(() => { - invoke("receive_install_status", { comfirmedInstall: false }) + invoke("receive_install_status", { comfirmedInstall: true }) }) } \ No newline at end of file diff --git a/src-vue/src/views/DeveloperView.vue b/src-vue/src/views/DeveloperView.vue index c1b0ffccb..eb4a69420 100644 --- a/src-vue/src/views/DeveloperView.vue +++ b/src-vue/src/views/DeveloperView.vue @@ -112,7 +112,7 @@ export default defineComponent({ first_tag: { label: '', value: {name: ''} }, second_tag: { label: '', value: {name: ''} }, ns_release_tags: [] as TagWrapper[], - can_install_plugins_state : "Disabled", + can_install_plugins_state : this.$store.state.can_install_plugins ? "Enabled" : "Disabled", } }, computed: { From 5fb21077e40daca339ae917d7efa9cd1c19a08ec Mon Sep 17 00:00:00 2001 From: cat_or_not <41955154+catornot@users.noreply.github.com> Date: Thu, 20 Apr 2023 16:55:54 -0400 Subject: [PATCH 09/18] formating --- src-tauri/src/mod_management/mod.rs | 2 +- src-tauri/src/plugin_management/mod.rs | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src-tauri/src/mod_management/mod.rs b/src-tauri/src/mod_management/mod.rs index df8ec745f..bbdcb68e5 100644 --- a/src-tauri/src/mod_management/mod.rs +++ b/src-tauri/src/mod_management/mod.rs @@ -394,7 +394,7 @@ pub async fn fc_download_mod_and_install( }; // Injected plugin install - + let result_plugin = match install_plugin(game_install, &f, can_install_plugins).await { err if matches!(err, Err(ThermiteError::MissingFile(_))) => err, Err(err) => Err(err.to_string())?, diff --git a/src-tauri/src/plugin_management/mod.rs b/src-tauri/src/plugin_management/mod.rs index afd529513..9bcb9ecc6 100644 --- a/src-tauri/src/plugin_management/mod.rs +++ b/src-tauri/src/plugin_management/mod.rs @@ -76,7 +76,6 @@ pub async fn install_plugin( // warn user if !plugins.is_empty() { - // check here instead if we can install plugins so people don't get broken mods without plugins if !can_install_plugins { Err(ThermiteError::MiscError( From 3ba35f4c4fe6f762c16dabc8790257f319dc9687 Mon Sep 17 00:00:00 2001 From: cat_or_not <41955154+catornot@users.noreply.github.com> Date: Sat, 22 Apr 2023 17:04:24 -0400 Subject: [PATCH 10/18] localization lol --- src-vue/src/i18n/lang/en.json | 10 +++++++++- src-vue/src/plugins/store.ts | 16 +++++++++++++--- 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/src-vue/src/i18n/lang/en.json b/src-vue/src/i18n/lang/en.json index 407b69d1b..18957182f 100644 --- a/src-vue/src/i18n/lang/en.json +++ b/src-vue/src/i18n/lang/en.json @@ -16,7 +16,8 @@ "downloading": "Downloading", "extracting": "Extracting", "done": "Done", - "success": "Success" + "success": "Success", + "warning": "Warning" }, "play": { @@ -160,5 +161,12 @@ "Northstar": "Northstar", "NorthstarReleaseCandidate": "Northstar release candidate" } + }, + + "plugins": { + "warning_dialog": { + "warning_text": "This mod contains a plugin. Plugins CAN BE REALLY DANGEROURS since they have to access to your system!", + "comfirm_text": "Do you still want to install this mod with a plugin?" + } } } diff --git a/src-vue/src/plugins/store.ts b/src-vue/src/plugins/store.ts index c44220b20..de558e37d 100644 --- a/src-vue/src/plugins/store.ts +++ b/src-vue/src/plugins/store.ts @@ -481,7 +481,7 @@ function _initializeListeners(state: any) { }); listen("display-plugin-warning", async function (evt: TauriEvent) { - await display_plugin_warning() // could also disaplay the names of the plugins + await display_plugin_warning() // could also display the names of the plugins }); } @@ -513,13 +513,23 @@ async function _get_northstar_version_number(state: any) { } async function display_plugin_warning() { + let warning_title = i18n.global.tc('generic.warning'); + // just wait for the end user to click ok button - await ElMessageBox.alert("This mod contains a plugin. Plugins CAN BE REALLY DANGEROURS since they have to access to your system!", "Warning"); + await ElMessageBox.alert(i18n.global.tc('plugins.warning_dialog.warning_text'), warning_title); // I switched comfirm and cancel button so end users don't spam throught the popups // without reading and install a plugin by accident - await ElMessageBox.confirm("Do you still want to install this mod with a plugin?", "Warning", { confirmButtonText: "No", cancelButtonText: "Yes", type: "warning", }) + await ElMessageBox.confirm( + i18n.global.tc('plugins.warning_dialog.comfirm_text'), + warning_title, + { + confirmButtonText: i18n.global.tc('generic.no'), + cancelButtonText: i18n.global.tc('generic.yes'), + type: "warning", + } + ) .then(() => { invoke("receive_install_status", { comfirmedInstall: false }) }) From 7ecc5705dd6baa2cd47ea378b914bcd93feecbf0 Mon Sep 17 00:00:00 2001 From: cat_or_not <41955154+catornot@users.noreply.github.com> Date: Tue, 25 Apr 2023 19:11:02 -0400 Subject: [PATCH 11/18] use subdirs for installed plugins --- src-tauri/Cargo.lock | 8 +++---- src-tauri/src/mod_management/mod.rs | 4 ++-- src-tauri/src/plugin_management/mod.rs | 29 +++++++++++++++++++++----- 3 files changed, 30 insertions(+), 11 deletions(-) diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index 89f89b015..edf798e8f 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -2789,9 +2789,9 @@ checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068" [[package]] name = "proc-macro2" -version = "1.0.51" +version = "1.0.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d727cae5b39d21da60fa540906919ad737832fe0b1c165da3a34d6548c849d6" +checksum = "2b63bdb0cd06f1f4dedf69b254734f9b45af66e4a031e42a7480257d9898b435" dependencies = [ "unicode-ident", ] @@ -2877,9 +2877,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.23" +version = "1.0.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b" +checksum = "4424af4bf778aae2051a77b60283332f386554255d722233d09fbfc7e30da2fc" dependencies = [ "proc-macro2", ] diff --git a/src-tauri/src/mod_management/mod.rs b/src-tauri/src/mod_management/mod.rs index bbdcb68e5..c7922853b 100644 --- a/src-tauri/src/mod_management/mod.rs +++ b/src-tauri/src/mod_management/mod.rs @@ -42,8 +42,8 @@ impl std::str::FromStr for ParsedThunderstoreModString { #[derive(Serialize, Deserialize, Debug, Clone)] pub struct ThunderstoreManifest { - name: String, - version_number: String, + pub name: String, + pub version_number: String, } #[derive(Serialize, Deserialize, Debug, Clone)] diff --git a/src-tauri/src/plugin_management/mod.rs b/src-tauri/src/plugin_management/mod.rs index 9bcb9ecc6..0649f7088 100644 --- a/src-tauri/src/plugin_management/mod.rs +++ b/src-tauri/src/plugin_management/mod.rs @@ -12,7 +12,7 @@ use tauri::{ use thermite::{core::utils::TempDir, prelude::ThermiteError}; use zip::ZipArchive; -use crate::APP_HANDLE; +use crate::{mod_management::ThunderstoreManifest, APP_HANDLE}; static INSTALL_STATUS_RECV: OnceCell>> = OnceCell::new(); @@ -41,6 +41,7 @@ pub async fn install_plugin( .join("R2Northstar") .join("plugins"); let temp_dir = TempDir::create(plugins_directory.join("___flightcore-temp-plugin-dir"))?; + let manifest_path = temp_dir.join("manifest.json"); let mut archive = ZipArchive::new(zip_file)?; for i in 0..archive.len() { @@ -66,7 +67,25 @@ pub async fn install_plugin( io::copy(&mut file, &mut outfile)?; } - let plugins: Vec = temp_dir + let this_plugin_dir = { + let data = std::fs::read_to_string(&manifest_path)?; + let manifest: ThunderstoreManifest = + json5::from_str(&data).map_err(|err| ThermiteError::MiscError(err.to_string()))?; + + plugins_directory.join(manifest.name) + }; + + // create the plugin subdir + if !this_plugin_dir.exists() { + fs::create_dir(&this_plugin_dir)?; + } + + fs::copy( + &manifest_path, + this_plugin_dir.join(manifest_path.file_name().unwrap_or_default()), + )?; + + let plugins: Vec = temp_dir .join("plugins") .read_dir() .map_err(|_| ThermiteError::MissingFile(Box::new(temp_dir.join("plugins"))))? @@ -79,7 +98,7 @@ pub async fn install_plugin( // check here instead if we can install plugins so people don't get broken mods without plugins if !can_install_plugins { Err(ThermiteError::MiscError( - "plugin installing is disabled; this mod contains a plugin; plugin can be enabled in the dev menu".to_string(), + "plugin installing is disabled; this mod contains a plugin; plugins can be enabled in the dev menu".to_string(), ))? } @@ -107,10 +126,10 @@ pub async fn install_plugin( } for file in plugins.iter().inspect(|f| { - _ = fs::remove_file(plugins_directory.join(f.file_name().to_string_lossy().to_string())) + _ = fs::remove_file(this_plugin_dir.join(f.file_name().to_string_lossy().to_string())) // try remove plugins to update }) { - fs::copy(file.path(), plugins_directory.join(file.file_name()))?; + fs::copy(file.path(), this_plugin_dir.join(file.file_name()))?; } Ok(()) From b02913c6eccae7e99ebf3f0800244ba6d995aba4 Mon Sep 17 00:00:00 2001 From: cat_or_not <41955154+catornot@users.noreply.github.com> Date: Thu, 27 Apr 2023 20:16:42 -0400 Subject: [PATCH 12/18] move plugin download its own file --- src-tauri/src/plugin_management/download.rs | 145 +++++++++++++++++++ src-tauri/src/plugin_management/mod.rs | 147 +------------------- 2 files changed, 148 insertions(+), 144 deletions(-) create mode 100644 src-tauri/src/plugin_management/download.rs diff --git a/src-tauri/src/plugin_management/download.rs b/src-tauri/src/plugin_management/download.rs new file mode 100644 index 000000000..d00e838a2 --- /dev/null +++ b/src-tauri/src/plugin_management/download.rs @@ -0,0 +1,145 @@ +use app::GameInstall; +use once_cell::sync::OnceCell; +use std::{ + fs::{self, File, OpenOptions}, + io, + path::PathBuf, +}; +use tauri::{ + async_runtime::{block_on, channel, Mutex, Receiver, Sender}, + Manager, State, +}; +use thermite::{core::utils::TempDir, prelude::ThermiteError}; +use zip::ZipArchive; + +use crate::{mod_management::ThunderstoreManifest, APP_HANDLE}; + +static INSTALL_STATUS_RECV: OnceCell>> = OnceCell::new(); + +pub struct InstallStatusSender(Mutex>); + +impl InstallStatusSender { + pub fn new() -> Self { + let (send, recv) = channel(1); + + INSTALL_STATUS_RECV + .set(Mutex::new(recv)) + .expect("failed to set INSTALL_STATUS_RECV"); + + Self(Mutex::new(send)) + } +} + +/// Tries to install plugins from a thunderstore zip +pub async fn install_plugin( + game_install: &GameInstall, + zip_file: &File, + can_install_plugins: bool, +) -> Result<(), ThermiteError> { + let plugins_directory = PathBuf::new() + .join(&game_install.game_path) + .join("R2Northstar") + .join("plugins"); + let temp_dir = TempDir::create(plugins_directory.join("___flightcore-temp-plugin-dir"))?; + let manifest_path = temp_dir.join("manifest.json"); + let mut archive = ZipArchive::new(zip_file)?; + + for i in 0..archive.len() { + let mut file = archive.by_index(i)?; + + if file.enclosed_name().is_none() || file.enclosed_name().unwrap().starts_with(".") { + continue; + } + + let out = temp_dir.join(file.enclosed_name().unwrap()); + + if (*file.name()).ends_with('/') { + fs::create_dir_all(&out)?; + continue; + } else if let Some(p) = out.parent() { + fs::create_dir_all(p)?; + } + let mut outfile = OpenOptions::new() + .create(true) + .write(true) + .truncate(true) + .open(&out)?; + io::copy(&mut file, &mut outfile)?; + } + + let this_plugin_dir = { + let data = std::fs::read_to_string(&manifest_path)?; + let manifest: ThunderstoreManifest = + json5::from_str(&data).map_err(|err| ThermiteError::MiscError(err.to_string()))?; + + plugins_directory.join(manifest.name) + }; + + // create the plugin subdir + if !this_plugin_dir.exists() { + fs::create_dir(&this_plugin_dir)?; + } + + fs::copy( + &manifest_path, + this_plugin_dir.join(manifest_path.file_name().unwrap_or_default()), + )?; + + let plugins: Vec = temp_dir + .join("plugins") + .read_dir() + .map_err(|_| ThermiteError::MissingFile(Box::new(temp_dir.join("plugins"))))? + .filter_map(|f| f.ok()) // ignore any errors + .filter(|f| f.path().extension().map(|e| e == "dll").unwrap_or(false)) // check for dll extension + .collect(); + + // warn user + if !plugins.is_empty() { + // check here instead if we can install plugins so people don't get broken mods without plugins + if !can_install_plugins { + Err(ThermiteError::MiscError( + "plugin installing is disabled; this mod contains a plugin; plugins can be enabled in the dev menu".to_string(), + ))? + } + + APP_HANDLE + .wait() + .emit_all("display-plugin-warning", ()) + .map_err(|err| ThermiteError::MiscError(err.to_string()))?; + + if !INSTALL_STATUS_RECV + .wait() + .lock() + .await + .recv() + .await + .unwrap_or(false) + { + Err(ThermiteError::MiscError( + "user denided plugin installing".to_string(), + ))? + } + } else { + Err(ThermiteError::MissingFile(Box::new( + temp_dir.join("plugins/anyplugins.dll"), + )))?; + } + + for file in plugins.iter().inspect(|f| { + _ = fs::remove_file(this_plugin_dir.join(f.file_name().to_string_lossy().to_string())) + // try remove plugins to update + }) { + fs::copy(file.path(), this_plugin_dir.join(file.file_name()))?; + } + + Ok(()) +} + +#[tauri::command] +pub fn receive_install_status( + sender: State<'_, InstallStatusSender>, + comfirmed_install: bool, +) -> Result<(), String> { + block_on(async { sender.0.lock().await.send(comfirmed_install).await }) + .map_err(|err| err.to_string()) +} \ No newline at end of file diff --git a/src-tauri/src/plugin_management/mod.rs b/src-tauri/src/plugin_management/mod.rs index 0649f7088..192367645 100644 --- a/src-tauri/src/plugin_management/mod.rs +++ b/src-tauri/src/plugin_management/mod.rs @@ -1,145 +1,4 @@ -use app::GameInstall; -use once_cell::sync::OnceCell; -use std::{ - fs::{self, File, OpenOptions}, - io, - path::PathBuf, -}; -use tauri::{ - async_runtime::{block_on, channel, Mutex, Receiver, Sender}, - Manager, State, -}; -use thermite::{core::utils::TempDir, prelude::ThermiteError}; -use zip::ZipArchive; +mod download; -use crate::{mod_management::ThunderstoreManifest, APP_HANDLE}; - -static INSTALL_STATUS_RECV: OnceCell>> = OnceCell::new(); - -pub struct InstallStatusSender(Mutex>); - -impl InstallStatusSender { - pub fn new() -> Self { - let (send, recv) = channel(1); - - INSTALL_STATUS_RECV - .set(Mutex::new(recv)) - .expect("failed to set INSTALL_STATUS_RECV"); - - Self(Mutex::new(send)) - } -} - -/// Tries to install plugins from a thunderstore zip -pub async fn install_plugin( - game_install: &GameInstall, - zip_file: &File, - can_install_plugins: bool, -) -> Result<(), ThermiteError> { - let plugins_directory = PathBuf::new() - .join(&game_install.game_path) - .join("R2Northstar") - .join("plugins"); - let temp_dir = TempDir::create(plugins_directory.join("___flightcore-temp-plugin-dir"))?; - let manifest_path = temp_dir.join("manifest.json"); - let mut archive = ZipArchive::new(zip_file)?; - - for i in 0..archive.len() { - let mut file = archive.by_index(i)?; - - if file.enclosed_name().is_none() || file.enclosed_name().unwrap().starts_with(".") { - continue; - } - - let out = temp_dir.join(file.enclosed_name().unwrap()); - - if (*file.name()).ends_with('/') { - fs::create_dir_all(&out)?; - continue; - } else if let Some(p) = out.parent() { - fs::create_dir_all(p)?; - } - let mut outfile = OpenOptions::new() - .create(true) - .write(true) - .truncate(true) - .open(&out)?; - io::copy(&mut file, &mut outfile)?; - } - - let this_plugin_dir = { - let data = std::fs::read_to_string(&manifest_path)?; - let manifest: ThunderstoreManifest = - json5::from_str(&data).map_err(|err| ThermiteError::MiscError(err.to_string()))?; - - plugins_directory.join(manifest.name) - }; - - // create the plugin subdir - if !this_plugin_dir.exists() { - fs::create_dir(&this_plugin_dir)?; - } - - fs::copy( - &manifest_path, - this_plugin_dir.join(manifest_path.file_name().unwrap_or_default()), - )?; - - let plugins: Vec = temp_dir - .join("plugins") - .read_dir() - .map_err(|_| ThermiteError::MissingFile(Box::new(temp_dir.join("plugins"))))? - .filter_map(|f| f.ok()) // ignore any errors - .filter(|f| f.path().extension().map(|e| e == "dll").unwrap_or(false)) // check for dll extension - .collect(); - - // warn user - if !plugins.is_empty() { - // check here instead if we can install plugins so people don't get broken mods without plugins - if !can_install_plugins { - Err(ThermiteError::MiscError( - "plugin installing is disabled; this mod contains a plugin; plugins can be enabled in the dev menu".to_string(), - ))? - } - - APP_HANDLE - .wait() - .emit_all("display-plugin-warning", ()) - .map_err(|err| ThermiteError::MiscError(err.to_string()))?; - - if !INSTALL_STATUS_RECV - .wait() - .lock() - .await - .recv() - .await - .unwrap_or(false) - { - Err(ThermiteError::MiscError( - "user denided plugin installing".to_string(), - ))? - } - } else { - Err(ThermiteError::MissingFile(Box::new( - temp_dir.join("plugins/anyplugins.dll"), - )))?; - } - - for file in plugins.iter().inspect(|f| { - _ = fs::remove_file(this_plugin_dir.join(f.file_name().to_string_lossy().to_string())) - // try remove plugins to update - }) { - fs::copy(file.path(), this_plugin_dir.join(file.file_name()))?; - } - - Ok(()) -} - -#[tauri::command] -pub fn receive_install_status( - sender: State<'_, InstallStatusSender>, - comfirmed_install: bool, -) -> Result<(), String> { - block_on(async { sender.0.lock().await.send(comfirmed_install).await }) - .map_err(|err| err.to_string()) -} +// should be refactored later +pub use download::*; \ No newline at end of file From a62c5f4adfacdefceb6353e5a4371f7f4d479296 Mon Sep 17 00:00:00 2001 From: cat_or_not <41955154+catornot@users.noreply.github.com> Date: Thu, 27 Apr 2023 21:19:50 -0400 Subject: [PATCH 13/18] display downloaded plugins --- src-tauri/src/mod_management/mod.rs | 8 +++ src-tauri/src/plugin_management/detection.rs | 55 ++++++++++++++++++++ src-tauri/src/plugin_management/mod.rs | 1 + 3 files changed, 64 insertions(+) create mode 100644 src-tauri/src/plugin_management/detection.rs diff --git a/src-tauri/src/mod_management/mod.rs b/src-tauri/src/mod_management/mod.rs index c7922853b..de1ea12ad 100644 --- a/src-tauri/src/mod_management/mod.rs +++ b/src-tauri/src/mod_management/mod.rs @@ -13,6 +13,8 @@ use thermite::prelude::ThermiteError; use app::get_enabled_mods; use app::GameInstall; +use crate::plugin_management::detection::find_installed_plugins; +use crate::plugin_management::detection::installed_plugins_to_mod; use crate::plugin_management::install_plugin; #[derive(Debug, Clone)] @@ -253,6 +255,12 @@ pub fn get_installed_mods_and_properties( installed_mods.push(current_mod); } + // push plugins into this list + // plugins should probably have there own tab but I hate frontend + installed_mods.extend(installed_plugins_to_mod( + &find_installed_plugins(&game_install).map_err(|err| err.to_string())?, + )); + Ok(installed_mods) } diff --git a/src-tauri/src/plugin_management/detection.rs b/src-tauri/src/plugin_management/detection.rs new file mode 100644 index 000000000..62eebc391 --- /dev/null +++ b/src-tauri/src/plugin_management/detection.rs @@ -0,0 +1,55 @@ +use crate::{mod_management::ThunderstoreManifest, GameInstall, NorthstarMod}; +use std::{ffi::OsStr, fs, path::PathBuf}; +use thermite::prelude::ThermiteError; + +pub fn installed_plugins_to_mod( + manifests: &[(ThunderstoreManifest, PathBuf)], +) -> Vec { + manifests + .iter() + .map(|(m, path)| NorthstarMod { + name: m.name.clone(), + version: None, // assume None + thunderstore_mod_string: Some(m.name.clone()), + enabled: true, // assume it is enabled + directory: path.display().to_string(), + }) + .collect() +} + +pub fn find_installed_plugins( + game_install: &GameInstall, +) -> Result, ThermiteError> { + let plugins_directory = PathBuf::new() + .join(&game_install.game_path) + .join("R2Northstar") + .join("plugins"); + + Ok(plugins_directory + .read_dir() + .map_err(|_| ThermiteError::MissingFile(Box::new(plugins_directory)))? + .filter_map(|f| f.ok()) + .filter(|e| e.path().is_dir()) + .filter_map(find_manifest) + .collect()) +} + +// this can't be async :( +fn find_manifest(dir: fs::DirEntry) -> Option<(ThunderstoreManifest, PathBuf)> { + pasre_manifest_path( + dir.path() + .read_dir() + .ok()? + .filter_map(|e| e.ok()) + .map(|e| e.path()) + .filter(|path| path.file_name() == Some(OsStr::new("manifest.json"))) + .last()?, + ) +} + +fn pasre_manifest_path(path: PathBuf) -> Option<(ThunderstoreManifest, PathBuf)> { + Some(( + json5::from_str(&std::fs::read_to_string(&path).ok()?).ok()?, + path, + )) +} diff --git a/src-tauri/src/plugin_management/mod.rs b/src-tauri/src/plugin_management/mod.rs index 192367645..84fc76d53 100644 --- a/src-tauri/src/plugin_management/mod.rs +++ b/src-tauri/src/plugin_management/mod.rs @@ -1,4 +1,5 @@ mod download; +pub mod detection; // should be refactored later pub use download::*; \ No newline at end of file From da7d80947dc2964f832c0f74dc0aab4d9a5aff09 Mon Sep 17 00:00:00 2001 From: cat_or_not <41955154+catornot@users.noreply.github.com> Date: Thu, 27 Apr 2023 21:29:31 -0400 Subject: [PATCH 14/18] fmt and refactor thingy --- src-tauri/src/main.rs | 2 +- src-tauri/src/mod_management/mod.rs | 2 +- src-tauri/src/plugin_management/download.rs | 2 +- src-tauri/src/plugin_management/mod.rs | 5 +---- src-vue/src/plugins/store.ts | 2 +- 5 files changed, 5 insertions(+), 8 deletions(-) diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index e6dec9038..a0eefe7d1 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -37,7 +37,7 @@ use mod_management::{ }; mod plugin_management; -use crate::plugin_management::{receive_install_status, InstallStatusSender}; +use crate::plugin_management::download::{receive_install_status, InstallStatusSender}; mod northstar; use northstar::get_northstar_version_number; diff --git a/src-tauri/src/mod_management/mod.rs b/src-tauri/src/mod_management/mod.rs index c7922853b..e9feed5c1 100644 --- a/src-tauri/src/mod_management/mod.rs +++ b/src-tauri/src/mod_management/mod.rs @@ -13,7 +13,7 @@ use thermite::prelude::ThermiteError; use app::get_enabled_mods; use app::GameInstall; -use crate::plugin_management::install_plugin; +use crate::plugin_management::download::install_plugin; #[derive(Debug, Clone)] struct ParsedThunderstoreModString { diff --git a/src-tauri/src/plugin_management/download.rs b/src-tauri/src/plugin_management/download.rs index d00e838a2..0649f7088 100644 --- a/src-tauri/src/plugin_management/download.rs +++ b/src-tauri/src/plugin_management/download.rs @@ -142,4 +142,4 @@ pub fn receive_install_status( ) -> Result<(), String> { block_on(async { sender.0.lock().await.send(comfirmed_install).await }) .map_err(|err| err.to_string()) -} \ No newline at end of file +} diff --git a/src-tauri/src/plugin_management/mod.rs b/src-tauri/src/plugin_management/mod.rs index 192367645..674b799ed 100644 --- a/src-tauri/src/plugin_management/mod.rs +++ b/src-tauri/src/plugin_management/mod.rs @@ -1,4 +1 @@ -mod download; - -// should be refactored later -pub use download::*; \ No newline at end of file +pub mod download; diff --git a/src-vue/src/plugins/store.ts b/src-vue/src/plugins/store.ts index de558e37d..9794c2176 100644 --- a/src-vue/src/plugins/store.ts +++ b/src-vue/src/plugins/store.ts @@ -536,4 +536,4 @@ async function display_plugin_warning() { .catch(() => { invoke("receive_install_status", { comfirmedInstall: true }) }) -} \ No newline at end of file +} From 942a5e85b0dd0e78171168a2c1a3d1eefb0c72b8 Mon Sep 17 00:00:00 2001 From: cat_or_not <41955154+catornot@users.noreply.github.com> Date: Thu, 27 Apr 2023 21:50:00 -0400 Subject: [PATCH 15/18] use mod string for folder name --- src-tauri/src/mod_management/mod.rs | 17 ++++++--- src-tauri/src/plugin_management/download.rs | 39 +++++++++++---------- 2 files changed, 33 insertions(+), 23 deletions(-) diff --git a/src-tauri/src/mod_management/mod.rs b/src-tauri/src/mod_management/mod.rs index e9feed5c1..e7f54a8ad 100644 --- a/src-tauri/src/mod_management/mod.rs +++ b/src-tauri/src/mod_management/mod.rs @@ -16,10 +16,10 @@ use app::GameInstall; use crate::plugin_management::download::install_plugin; #[derive(Debug, Clone)] -struct ParsedThunderstoreModString { - author_name: String, - mod_name: String, - version: Option, +pub struct ParsedThunderstoreModString { + pub author_name: String, + pub mod_name: String, + pub version: Option, } impl std::str::FromStr for ParsedThunderstoreModString { @@ -395,7 +395,14 @@ pub async fn fc_download_mod_and_install( // Injected plugin install - let result_plugin = match install_plugin(game_install, &f, can_install_plugins).await { + let result_plugin = match install_plugin( + game_install, + &f, + thunderstore_mod_string, + can_install_plugins, + ) + .await + { err if matches!(err, Err(ThermiteError::MissingFile(_))) => err, Err(err) => Err(err.to_string())?, r => r, diff --git a/src-tauri/src/plugin_management/download.rs b/src-tauri/src/plugin_management/download.rs index 0649f7088..330127f3a 100644 --- a/src-tauri/src/plugin_management/download.rs +++ b/src-tauri/src/plugin_management/download.rs @@ -4,6 +4,7 @@ use std::{ fs::{self, File, OpenOptions}, io, path::PathBuf, + str::FromStr, }; use tauri::{ async_runtime::{block_on, channel, Mutex, Receiver, Sender}, @@ -12,7 +13,7 @@ use tauri::{ use thermite::{core::utils::TempDir, prelude::ThermiteError}; use zip::ZipArchive; -use crate::{mod_management::ThunderstoreManifest, APP_HANDLE}; +use crate::{mod_management::ParsedThunderstoreModString, APP_HANDLE}; static INSTALL_STATUS_RECV: OnceCell>> = OnceCell::new(); @@ -34,6 +35,7 @@ impl InstallStatusSender { pub async fn install_plugin( game_install: &GameInstall, zip_file: &File, + thunderstore_mod_string: &str, can_install_plugins: bool, ) -> Result<(), ThermiteError> { let plugins_directory = PathBuf::new() @@ -44,6 +46,13 @@ pub async fn install_plugin( let manifest_path = temp_dir.join("manifest.json"); let mut archive = ZipArchive::new(zip_file)?; + let parsed_mod_string = ParsedThunderstoreModString::from_str(thunderstore_mod_string).map_err(|_|ThermiteError::MiscError("Gecko why is this returning nothing? anyway the error is failed to parse thunderstore string lmao".into()))?; + let folder_name_string = format!( + "{}-{}", + parsed_mod_string.author_name, parsed_mod_string.mod_name + ); // version omited because it would be a pain to replace/update + // manifest is in the folder so the version is not lost + for i in 0..archive.len() { let mut file = archive.by_index(i)?; @@ -67,23 +76,7 @@ pub async fn install_plugin( io::copy(&mut file, &mut outfile)?; } - let this_plugin_dir = { - let data = std::fs::read_to_string(&manifest_path)?; - let manifest: ThunderstoreManifest = - json5::from_str(&data).map_err(|err| ThermiteError::MiscError(err.to_string()))?; - - plugins_directory.join(manifest.name) - }; - - // create the plugin subdir - if !this_plugin_dir.exists() { - fs::create_dir(&this_plugin_dir)?; - } - - fs::copy( - &manifest_path, - this_plugin_dir.join(manifest_path.file_name().unwrap_or_default()), - )?; + let this_plugin_dir = plugins_directory.join(folder_name_string); let plugins: Vec = temp_dir .join("plugins") @@ -125,6 +118,16 @@ pub async fn install_plugin( )))?; } + // create the plugin subdir + if !this_plugin_dir.exists() { + fs::create_dir(&this_plugin_dir)?; + } + + fs::copy( + &manifest_path, + this_plugin_dir.join(manifest_path.file_name().unwrap_or_default()), + )?; + for file in plugins.iter().inspect(|f| { _ = fs::remove_file(this_plugin_dir.join(f.file_name().to_string_lossy().to_string())) // try remove plugins to update From 4bb7a1fe80fd5eaf41ab419621aecaaee98382cb Mon Sep 17 00:00:00 2001 From: cat_or_not <41955154+catornot@users.noreply.github.com> Date: Fri, 28 Apr 2023 21:31:12 -0400 Subject: [PATCH 16/18] nuke previous folders when installing --- src-tauri/src/plugin_management/download.rs | 34 +++++++++++++++------ 1 file changed, 24 insertions(+), 10 deletions(-) diff --git a/src-tauri/src/plugin_management/download.rs b/src-tauri/src/plugin_management/download.rs index 330127f3a..a0ff8ecf8 100644 --- a/src-tauri/src/plugin_management/download.rs +++ b/src-tauri/src/plugin_management/download.rs @@ -47,11 +47,13 @@ pub async fn install_plugin( let mut archive = ZipArchive::new(zip_file)?; let parsed_mod_string = ParsedThunderstoreModString::from_str(thunderstore_mod_string).map_err(|_|ThermiteError::MiscError("Gecko why is this returning nothing? anyway the error is failed to parse thunderstore string lmao".into()))?; - let folder_name_string = format!( - "{}-{}", - parsed_mod_string.author_name, parsed_mod_string.mod_name - ); // version omited because it would be a pain to replace/update - // manifest is in the folder so the version is not lost + let package_name = parsed_mod_string.mod_name.to_owned(); + let folder_name = format!( + "{}-{}-{}", + parsed_mod_string.author_name, + package_name, + parsed_mod_string.version.unwrap_or_else(|| "0.0.0".into()) + ); for i in 0..archive.len() { let mut file = archive.by_index(i)?; @@ -76,7 +78,7 @@ pub async fn install_plugin( io::copy(&mut file, &mut outfile)?; } - let this_plugin_dir = plugins_directory.join(folder_name_string); + let this_plugin_dir = plugins_directory.join(folder_name); let plugins: Vec = temp_dir .join("plugins") @@ -118,6 +120,21 @@ pub async fn install_plugin( )))?; } + // nuke previous version if it exists + for (_, path) in plugins_directory + .read_dir() + .map_err(|_| ThermiteError::MissingFile(Box::new(temp_dir.join("plugins"))))? + .filter_map(|f| f.ok()) // ignore any errors + .map(|e| e.path()) + .filter(|path| path.is_dir()) + .filter_map(|path| Some((path.clone().file_name()?.to_str()?.to_owned(), path))) + .filter_map(|(name, path)| Some((name.parse::().ok()?, path))) + .filter(|(p, _)| p.mod_name == package_name) + .inspect(|(_, path)| println!("removing {}", path.display())) + { + fs::remove_dir_all(path)? + } + // create the plugin subdir if !this_plugin_dir.exists() { fs::create_dir(&this_plugin_dir)?; @@ -128,10 +145,7 @@ pub async fn install_plugin( this_plugin_dir.join(manifest_path.file_name().unwrap_or_default()), )?; - for file in plugins.iter().inspect(|f| { - _ = fs::remove_file(this_plugin_dir.join(f.file_name().to_string_lossy().to_string())) - // try remove plugins to update - }) { + for file in plugins { fs::copy(file.path(), this_plugin_dir.join(file.file_name()))?; } From 2712b63d8207f22ea201f3e2951f07cd7bae4364 Mon Sep 17 00:00:00 2001 From: cat_or_not <41955154+catornot@users.noreply.github.com> Date: Sat, 29 Apr 2023 00:21:21 -0400 Subject: [PATCH 17/18] also list plugins in plugins folder --- src-tauri/src/plugin_management/detection.rs | 25 +++++++++++++++----- 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/src-tauri/src/plugin_management/detection.rs b/src-tauri/src/plugin_management/detection.rs index 62eebc391..8e029b9a9 100644 --- a/src-tauri/src/plugin_management/detection.rs +++ b/src-tauri/src/plugin_management/detection.rs @@ -1,5 +1,5 @@ use crate::{mod_management::ThunderstoreManifest, GameInstall, NorthstarMod}; -use std::{ffi::OsStr, fs, path::PathBuf}; +use std::{ffi::OsStr, path::{PathBuf, Path}}; use thermite::prelude::ThermiteError; pub fn installed_plugins_to_mod( @@ -29,16 +29,29 @@ pub fn find_installed_plugins( .read_dir() .map_err(|_| ThermiteError::MissingFile(Box::new(plugins_directory)))? .filter_map(|f| f.ok()) - .filter(|e| e.path().is_dir()) - .filter_map(find_manifest) + .map(|e| e.path()) + .filter_map(|p| find_manifest(p.as_path()).or_else(|| find_plugin_in_root(p.as_path()))) .collect()) } +fn find_plugin_in_root(file: &Path) -> Option<(ThunderstoreManifest, PathBuf)> { + if file.extension()? == ".dll" { + Some(( + ThunderstoreManifest { + name: file.file_name()?.to_str()?.to_string(), + version_number: "0.0.0".to_string(), // TODO: peak the dll to find it's version + }, + file.to_owned(), + )) + } else { + None + } +} + // this can't be async :( -fn find_manifest(dir: fs::DirEntry) -> Option<(ThunderstoreManifest, PathBuf)> { +fn find_manifest(dir: &Path) -> Option<(ThunderstoreManifest, PathBuf)> { pasre_manifest_path( - dir.path() - .read_dir() + dir.read_dir() .ok()? .filter_map(|e| e.ok()) .map(|e| e.path()) From c14b36905167012e7b2e575a047dd0ee6288dea8 Mon Sep 17 00:00:00 2001 From: cat_or_not <41955154+catornot@users.noreply.github.com> Date: Sat, 29 Apr 2023 00:48:12 -0400 Subject: [PATCH 18/18] oh --- src-tauri/src/plugin_management/detection.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src-tauri/src/plugin_management/detection.rs b/src-tauri/src/plugin_management/detection.rs index 8e029b9a9..3b241d00a 100644 --- a/src-tauri/src/plugin_management/detection.rs +++ b/src-tauri/src/plugin_management/detection.rs @@ -35,7 +35,7 @@ pub fn find_installed_plugins( } fn find_plugin_in_root(file: &Path) -> Option<(ThunderstoreManifest, PathBuf)> { - if file.extension()? == ".dll" { + if file.extension()? == "dll" { Some(( ThunderstoreManifest { name: file.file_name()?.to_str()?.to_string(),