aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src-tauri/Cargo.lock46
-rw-r--r--src-tauri/Cargo.toml2
-rw-r--r--src-tauri/src/mod_management/mod.rs207
3 files changed, 236 insertions, 19 deletions
diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock
index 968d523b..9c916f6f 100644
--- a/src-tauri/Cargo.lock
+++ b/src-tauri/Cargo.lock
@@ -45,6 +45,15 @@ dependencies = [
]
[[package]]
+name = "aho-corasick"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "43f6cb1bf222025340178f382c426f13757b2960e89779dfcb319c32542a5a41"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
name = "alloc-no-stdlib"
version = "2.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1454,7 +1463,7 @@ version = "0.4.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "029d74589adefde59de1a0c4f4732695c32805624aec7b68d91503d4dba79afc"
dependencies = [
- "aho-corasick",
+ "aho-corasick 0.7.20",
"bstr",
"fnv",
"log",
@@ -1984,12 +1993,13 @@ checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79"
[[package]]
name = "libthermite"
-version = "0.6.5"
+version = "0.7.0-alpha"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "27cd844bbc25676cd14fa9ad04cc40e0f3c4d5c66107ef3a99896db1f81324c0"
+checksum = "b3927ebe9945316ba6a70f4384045fba7da9261e23b44faa7fddf9238f8916fa"
dependencies = [
"flate2",
"json5",
+ "regex",
"serde",
"serde_json",
"tar",
@@ -2117,7 +2127,7 @@ version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558"
dependencies = [
- "regex-automata",
+ "regex-automata 0.1.10",
]
[[package]]
@@ -3058,13 +3068,14 @@ dependencies = [
[[package]]
name = "regex"
-version = "1.7.1"
+version = "1.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "48aaa5748ba571fb95cd2c85c09f629215d3a6ece942baa100950af03a34f733"
+checksum = "b2eae68fc220f7cf2532e4494aded17545fce192d59cd996e0fe7887f4ceb575"
dependencies = [
- "aho-corasick",
+ "aho-corasick 1.0.2",
"memchr",
- "regex-syntax",
+ "regex-automata 0.3.3",
+ "regex-syntax 0.7.4",
]
[[package]]
@@ -3073,7 +3084,18 @@ version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132"
dependencies = [
- "regex-syntax",
+ "regex-syntax 0.6.28",
+]
+
+[[package]]
+name = "regex-automata"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "39354c10dd07468c2e73926b23bb9c2caca74c5501e38a35da70406f1d923310"
+dependencies = [
+ "aho-corasick 1.0.2",
+ "memchr",
+ "regex-syntax 0.7.4",
]
[[package]]
@@ -3083,6 +3105,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848"
[[package]]
+name = "regex-syntax"
+version = "0.7.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e5ea92a5b6195c6ef2a0295ea818b312502c6fc94dde986c5553242e18fd4ce2"
+
+[[package]]
name = "reqwest"
version = "0.11.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml
index 63ff51f7..3369e142 100644
--- a/src-tauri/Cargo.toml
+++ b/src-tauri/Cargo.toml
@@ -33,7 +33,7 @@ steamlocate = "1.2"
# Error messages
anyhow = "1.0"
# libthermite for Northstar/mod install handling
-libthermite = { version = "0.6.5", features = ["proton"] }
+libthermite = { version = "0.7.0-alpha", features = ["proton"] }
# zip stuff
zip = "0.6.2"
# Regex
diff --git a/src-tauri/src/mod_management/mod.rs b/src-tauri/src/mod_management/mod.rs
index 825cba37..c9b7ae0c 100644
--- a/src-tauri/src/mod_management/mod.rs
+++ b/src-tauri/src/mod_management/mod.rs
@@ -4,15 +4,17 @@ use crate::constants::{BLACKLISTED_MODS, CORE_MODS};
use async_recursion::async_recursion;
use crate::NorthstarMod;
-use anyhow::Result;
+use anyhow::{anyhow, Result};
use serde::{Deserialize, Serialize};
+use std::str::FromStr;
+use std::string::ToString;
use std::{fs, path::PathBuf};
mod legacy;
use crate::GameInstall;
#[derive(Debug, Clone)]
-struct ParsedThunderstoreModString {
+pub struct ParsedThunderstoreModString {
author_name: String,
mod_name: String,
version: String,
@@ -182,6 +184,132 @@ pub fn set_mod_enabled_status(
Ok(())
}
+/// Resembles the bare minimum keys in Northstar `mods.json`
+#[derive(Serialize, Deserialize, Debug, Clone)]
+pub struct ModJson {
+ #[serde(rename = "Name")]
+ name: String,
+ #[serde(rename = "Version")]
+ version: Option<String>,
+}
+
+/// Parse `mods` folder for installed mods.
+pub fn parse_mods_in_package(
+ package_mods_path: PathBuf,
+ thunderstore_mod_string: ParsedThunderstoreModString,
+) -> Result<Vec<NorthstarMod>, anyhow::Error> {
+ let paths = match std::fs::read_dir(package_mods_path) {
+ Ok(paths) => paths,
+ Err(_err) => return Err(anyhow!("No mods folder found")),
+ };
+
+ let mut directories: Vec<PathBuf> = Vec::new();
+ let mut mods: Vec<NorthstarMod> = Vec::new();
+
+ // Get list of folders in `mods` directory
+ for path in paths {
+ let my_path = path.unwrap().path();
+ let md = std::fs::metadata(my_path.clone()).unwrap();
+ if md.is_dir() {
+ directories.push(my_path);
+ }
+ }
+
+ // Iterate over folders and check if they are Northstar mods
+ for directory in directories {
+ let directory_str = directory.to_str().unwrap().to_string();
+ // Check if mod.json exists
+ let mod_json_path = format!("{}/mod.json", directory_str);
+ if !std::path::Path::new(&mod_json_path).exists() {
+ continue;
+ }
+
+ // Read file into string and parse it
+ let data = std::fs::read_to_string(mod_json_path.clone())?;
+ let parsed_mod_json: ModJson = match json5::from_str(&data) {
+ Ok(parsed_json) => parsed_json,
+ Err(err) => {
+ log::warn!("Failed parsing {} with {}", mod_json_path, err.to_string());
+ continue;
+ }
+ };
+
+ // Get directory path
+ let mod_directory = directory.to_str().unwrap().to_string();
+
+ let ns_mod = NorthstarMod {
+ name: parsed_mod_json.name,
+ version: parsed_mod_json.version,
+ thunderstore_mod_string: Some(thunderstore_mod_string.to_string()),
+ enabled: false, // Placeholder
+ directory: mod_directory,
+ };
+
+ mods.push(ns_mod);
+ }
+
+ // Return found mod names
+ Ok(mods)
+}
+
+/// Parse `packages` folder for installed mods.
+pub fn parse_installed_package_mods(
+ game_install: &GameInstall,
+) -> Result<Vec<NorthstarMod>, anyhow::Error> {
+ let mut collected_mods: Vec<NorthstarMod> = Vec::new();
+
+ let packages_folder = format!("{}/R2Northstar/packages/", game_install.game_path);
+
+ let packages_dir = match fs::read_dir(packages_folder) {
+ Ok(res) => res,
+ Err(err) => {
+ // We couldn't read directory, probably cause it doesn't exist yet.
+ // In that case we just say no package mods installed.
+ log::warn!("{err}");
+ return Ok(vec![]);
+ }
+ };
+
+ // Iteratore over folders in `packages` dir
+ for entry in packages_dir {
+ let entry_path = entry?.path();
+ let entry_str = entry_path.file_name().unwrap().to_str().unwrap();
+
+ // Use the struct's from_str function to verify format
+ if entry_path.is_dir() {
+ let package_thunderstore_string = match ParsedThunderstoreModString::from_str(entry_str)
+ {
+ Ok(res) => res,
+ Err(err) => {
+ log::warn!(
+ "Not a Thunderstore mod string \"{}\" cause: {}",
+ entry_path.display(),
+ err
+ );
+ continue;
+ }
+ };
+ let manifest_path = entry_path.join("manifest.json");
+ let mods_path = entry_path.join("mods");
+
+ // Ensure `manifest.json` and `mods/` dir exist
+ if manifest_path.exists() && mods_path.is_dir() {
+ let mods =
+ match parse_mods_in_package(mods_path, package_thunderstore_string.clone()) {
+ Ok(res) => res,
+ Err(err) => {
+ log::warn!("Failed parsing cause: {err}");
+ continue;
+ }
+ };
+ collected_mods.extend(mods);
+ }
+ }
+ }
+
+ Ok(collected_mods)
+}
+
/// Gets list of installed mods and their properties
/// - name
/// - is enabled?
@@ -189,11 +317,19 @@ pub fn set_mod_enabled_status(
pub fn get_installed_mods_and_properties(
game_install: GameInstall,
) -> Result<Vec<NorthstarMod>, String> {
- // Get actually installed mods
- let found_installed_mods = match legacy::parse_installed_mods(&game_install) {
+ // Get installed mods from packages
+ let mut found_installed_mods = match parse_installed_package_mods(&game_install) {
Ok(res) => res,
Err(err) => return Err(err.to_string()),
};
+ // Get installed legacy mods
+ let found_installed_legacy_mods = match legacy::parse_installed_mods(&game_install) {
+ Ok(res) => res,
+ Err(err) => return Err(err.to_string()),
+ };
+
+ // Combine list of package and legacy mods
+ found_installed_mods.extend(found_installed_legacy_mods);
// Get enabled mods as JSON
let enabled_mods: serde_json::Value = match get_enabled_mods(&game_install) {
@@ -285,7 +421,6 @@ pub async fn fc_download_mod_and_install(
"{}/___flightcore-temp-download-dir/",
game_install.game_path
);
- let mods_directory = format!("{}/R2Northstar/mods/", game_install.game_path);
// Early return on empty string
if thunderstore_mod_string.is_empty() {
@@ -350,14 +485,16 @@ pub async fn fc_download_mod_and_install(
Err(err) => return Err(err.to_string()),
};
- // Get Thunderstore mod author
- let author = thunderstore_mod_string.split('-').next().unwrap();
+ // Get directory to install to made up of packages directory and Thunderstore mod string
+ let install_directory = format!(
+ "{}/R2Northstar/packages/{}",
+ game_install.game_path, thunderstore_mod_string
+ );
// Extract the mod to the mods directory
match thermite::core::manage::install_mod(
- author,
temp_file.file(),
- std::path::Path::new(&mods_directory),
+ std::path::Path::new(&install_directory),
) {
Ok(_) => (),
Err(err) => {
@@ -412,11 +549,63 @@ pub fn delete_northstar_mod(game_install: GameInstall, nsmod_name: String) -> Re
Err(format!("Mod {nsmod_name} not found to be installed"))
}
+/// Deletes a given Thunderstore package
+fn delete_package_folder(ts_package_directory: &str) -> Result<(), String> {
+ let ns_mod_dir_path = std::path::Path::new(&ts_package_directory);
+
+ // Safety check: Check whether `manifest.json` exists and exit early if not
+ // If it does not exist, we might not be dealing with a Thunderstore package
+ let mod_json_path = ns_mod_dir_path.join("manifest.json");
+ if !mod_json_path.exists() {
+ // If it doesn't exist, return an error
+ return Err(format!(
+ "manifest.json does not exist in {}",
+ ts_package_directory
+ ));
+ }
+
+ match std::fs::remove_dir_all(ts_package_directory) {
+ Ok(()) => Ok(()),
+ Err(err) => Err(format!("Failed deleting package: {err}")),
+ }
+}
+
/// Deletes all NorthstarMods related to a Thunderstore mod
#[tauri::command]
pub fn delete_thunderstore_mod(
game_install: GameInstall,
thunderstore_mod_string: String,
) -> Result<(), String> {
+ // Check packages
+ let packages_folder = format!("{}/R2Northstar/packages", game_install.game_path);
+ if std::path::Path::new(&packages_folder).exists() {
+ println!(
+ "Directory {} exists. Listing subdirectories...",
+ packages_folder
+ );
+ for entry in fs::read_dir(packages_folder).unwrap() {
+ let entry = entry.unwrap();
+
+ // Check if it's a folder and skip if otherwise
+ if !entry.file_type().unwrap().is_dir() {
+ log::warn!("Skipping \"{}\", not a file", entry.path().display());
+ continue;
+ }
+
+ let entry_path = entry.path();
+ let package_folder_ts_string = entry_path.file_name().unwrap().to_string_lossy();
+ dbg!(package_folder_ts_string.clone());
+
+ if package_folder_ts_string != thunderstore_mod_string {
+ // Not the mod folder we are looking for, try the next one\
+ continue;
+ }
+
+ // All checks passed, this is the matching mod
+ return delete_package_folder(&entry.path().display().to_string());
+ }
+ }
+
+ // Try legacy mod installs as fallback
legacy::delete_thunderstore_mod(game_install, thunderstore_mod_string)
}