aboutsummaryrefslogtreecommitdiff
path: root/src-tauri
diff options
context:
space:
mode:
authorGeckoEidechse <40122905+GeckoEidechse@users.noreply.github.com>2023-07-21 12:43:11 +0200
committerGitHub <noreply@github.com>2023-07-21 12:43:11 +0200
commit672ee8e0df804caa4c746dbc9ce7efc5c3674647 (patch)
tree6f83846289f0d8653237e8f3584f4cfdef80395c /src-tauri
parent99920002511676f55b8a45e51b5c735f50db4bd6 (diff)
downloadFlightCore-672ee8e0df804caa4c746dbc9ce7efc5c3674647.tar.gz
FlightCore-672ee8e0df804caa4c746dbc9ce7efc5c3674647.zip
feat: Support installing Thunderstore packages (#426)
Support for installing mods from Thunderstore directly into the `packages` folder. Adds some basic sanity check during installation. After successful installation, the old version of the mod is removed. This includes both from `packages` and from `mods`.
Diffstat (limited to 'src-tauri')
-rw-r--r--src-tauri/Cargo.lock47
-rw-r--r--src-tauri/Cargo.toml2
-rw-r--r--src-tauri/src/mod_management/legacy.rs40
-rw-r--r--src-tauri/src/mod_management/mod.rs129
4 files changed, 201 insertions, 17 deletions
diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock
index cd380a27..f01d299b 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,14 @@ checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79"
[[package]]
name = "libthermite"
-version = "0.6.5"
+version = "0.7.0-beta"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "27cd844bbc25676cd14fa9ad04cc40e0f3c4d5c66107ef3a99896db1f81324c0"
+checksum = "07d01e44ad3cd57ad026987a2d69a71d7a6d49e5ecdbbe64c2f940477b2a3a2d"
dependencies = [
"flate2",
"json5",
+ "lazy_static",
+ "regex",
"serde",
"serde_json",
"tar",
@@ -2117,7 +2128,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 +3069,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 +3085,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 +3106,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 03eb8e9e..bba82afc 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-beta", features = ["proton"] }
# zip stuff
zip = "0.6.2"
# Regex
diff --git a/src-tauri/src/mod_management/legacy.rs b/src-tauri/src/mod_management/legacy.rs
index f24f44b6..91463250 100644
--- a/src-tauri/src/mod_management/legacy.rs
+++ b/src-tauri/src/mod_management/legacy.rs
@@ -115,6 +115,46 @@ pub fn parse_installed_mods(
Ok(mods)
}
+/// Deletes all legacy packages that match in author and mod name
+/// regardless of version
+///
+/// "legacy package" refers to a Thunderstore package installed into the `mods` folder
+/// by extracting Northstar mods contained inside and then adding `manifest.json` and `thunderstore_author.txt`
+/// to indicate which Thunderstore package they are part of
+pub fn delete_legacy_package_install(
+ thunderstore_mod_string: &str,
+ game_install: &GameInstall,
+) -> Result<(), String> {
+ let thunderstore_mod_string: ParsedThunderstoreModString =
+ thunderstore_mod_string.parse().unwrap();
+ let found_installed_legacy_mods = match parse_installed_mods(game_install) {
+ Ok(res) => res,
+ Err(err) => return Err(err.to_string()),
+ };
+
+ for legacy_mod in found_installed_legacy_mods {
+ if legacy_mod.thunderstore_mod_string.is_none() {
+ continue; // Not a thunderstore mod
+ }
+
+ let current_mod_ts_string: ParsedThunderstoreModString = legacy_mod
+ .clone()
+ .thunderstore_mod_string
+ .unwrap()
+ .parse()
+ .unwrap();
+
+ if thunderstore_mod_string.author_name == current_mod_ts_string.author_name
+ && thunderstore_mod_string.mod_name == current_mod_ts_string.mod_name
+ {
+ // They match, delete
+ delete_mod_folder(&legacy_mod.directory)?;
+ }
+ }
+
+ Ok(())
+}
+
/// Deletes all NorthstarMods related to a Thunderstore mod
pub fn delete_thunderstore_mod(
game_install: GameInstall,
diff --git a/src-tauri/src/mod_management/mod.rs b/src-tauri/src/mod_management/mod.rs
index b28671c8..c049fe07 100644
--- a/src-tauri/src/mod_management/mod.rs
+++ b/src-tauri/src/mod_management/mod.rs
@@ -2,6 +2,7 @@
use crate::constants::{BLACKLISTED_MODS, CORE_MODS};
use async_recursion::async_recursion;
+use thermite::prelude::ThermiteError;
use crate::NorthstarMod;
use anyhow::{anyhow, Result};
@@ -407,6 +408,97 @@ async fn get_mod_dependencies(thunderstore_mod_string: &str) -> Result<Vec<Strin
Ok(Vec::<String>::new())
}
+/// Deletes all versions of Thunderstore package except the specified one
+fn delete_older_versions(
+ thunderstore_mod_string: &str,
+ game_install: &GameInstall,
+) -> Result<(), String> {
+ let thunderstore_mod_string: ParsedThunderstoreModString =
+ thunderstore_mod_string.parse().unwrap();
+ log::info!(
+ "Deleting other versions of {}",
+ thunderstore_mod_string.to_string()
+ );
+ let packages_folder = format!("{}/R2Northstar/packages", game_install.game_path);
+
+ // Get folders in packages dir
+ let paths = match std::fs::read_dir(&packages_folder) {
+ Ok(paths) => paths,
+ Err(_err) => return Err(format!("Failed to read directory {}", &packages_folder)),
+ };
+
+ let mut directories: Vec<PathBuf> = 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);
+ }
+ }
+
+ for directory in directories {
+ let folder_name = directory.file_name().unwrap().to_str().unwrap();
+ let ts_mod_string_from_folder: ParsedThunderstoreModString = match folder_name.parse() {
+ Ok(res) => res,
+ Err(err) => {
+ // Failed parsing folder name as Thunderstore mod string
+ // This means it doesn't follow the `AUTHOR-MOD-VERSION` naming structure
+ // This folder could've been manually created by the user or another application
+ // As parsing failed we cannot determine the Thunderstore package it is part of hence we skip it
+ log::warn!("{err}");
+ continue;
+ }
+ };
+ // Check which match `AUTHOR-MOD` and do NOT match `AUTHOR-MOD-VERSION`
+ if ts_mod_string_from_folder.author_name == thunderstore_mod_string.author_name
+ && ts_mod_string_from_folder.mod_name == thunderstore_mod_string.mod_name
+ && ts_mod_string_from_folder.version != thunderstore_mod_string.version
+ {
+ delete_package_folder(&directory.display().to_string())?;
+ }
+ }
+
+ Ok(())
+}
+
+/// Checks whether some mod is correctly formatted
+/// Currently checks whether
+/// - Some `mod.json` exists under `mods/*/mod.json`
+fn fc_sanity_check(input: &&fs::File) -> bool {
+ let mut archive = match zip::read::ZipArchive::new(*input) {
+ Ok(archive) => archive,
+ Err(_) => return false,
+ };
+
+ let mut has_mods = false;
+ let mut mod_json_exists = false;
+
+ // Checks for `mods/*/mod.json`
+ for i in 0..archive.len() {
+ let file = match archive.by_index(i) {
+ Ok(file) => file,
+ Err(_) => continue,
+ };
+ let file_path = file.mangled_name();
+ if file_path.starts_with("mods/") {
+ has_mods = true;
+ if let Some(name) = file_path.file_name() {
+ if name == "mod.json" {
+ let parent_path = file_path.parent().unwrap();
+ if parent_path.parent().unwrap().to_str().unwrap() == "mods" {
+ mod_json_exists = true;
+ }
+ }
+ }
+ }
+ }
+
+ has_mods && mod_json_exists
+}
+
// Copied from `libtermite` source code and modified
// Should be replaced with a library call to libthermite in the future
/// Download and install mod to the specified target.
@@ -421,7 +513,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() {
@@ -486,19 +577,43 @@ 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);
// Extract the mod to the mods directory
- match thermite::core::manage::install_mod(
- author,
+ match thermite::core::manage::install_with_sanity(
+ thunderstore_mod_string,
temp_file.file(),
- std::path::Path::new(&mods_directory),
+ std::path::Path::new(&install_directory),
+ fc_sanity_check,
) {
Ok(_) => (),
Err(err) => {
log::warn!("libthermite couldn't install mod {thunderstore_mod_string} due to {err:?}",);
- return Err(err.to_string());
+ return match err {
+ ThermiteError::SanityError => Err(
+ "Mod failed sanity check during install. It's probably not correctly formatted"
+ .to_string(),
+ ),
+ _ => Err(err.to_string()),
+ };
+ }
+ };
+
+ // Successful package install
+ match legacy::delete_legacy_package_install(thunderstore_mod_string, game_install) {
+ Ok(()) => (),
+ Err(err) => {
+ // Catch error but ignore
+ log::warn!("Failed deleting legacy versions due to: {}", err);
+ }
+ };
+
+ match delete_older_versions(thunderstore_mod_string, game_install) {
+ Ok(()) => (),
+ Err(err) => {
+ // Catch error but ignore
+ log::warn!("Failed deleting older versions due to: {}", err);
}
};