aboutsummaryrefslogtreecommitdiff
path: root/src-tauri/src/mod_management/mod.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src-tauri/src/mod_management/mod.rs')
-rw-r--r--src-tauri/src/mod_management/mod.rs797
1 files changed, 0 insertions, 797 deletions
diff --git a/src-tauri/src/mod_management/mod.rs b/src-tauri/src/mod_management/mod.rs
deleted file mode 100644
index 52ef1180..00000000
--- a/src-tauri/src/mod_management/mod.rs
+++ /dev/null
@@ -1,797 +0,0 @@
-// This file contains various mod management functions
-
-use crate::constants::{BLACKLISTED_MODS, CORE_MODS, MODS_WITH_SPECIAL_REQUIREMENTS};
-use async_recursion::async_recursion;
-use thermite::prelude::ThermiteError;
-
-use crate::NorthstarMod;
-use anyhow::{anyhow, Result};
-use serde::{Deserialize, Serialize};
-use std::error::Error;
-use std::str::FromStr;
-use std::string::ToString;
-use std::{fs, path::PathBuf};
-
-mod legacy;
-mod plugins;
-use crate::GameInstall;
-
-#[derive(Debug, Clone)]
-pub struct ParsedThunderstoreModString {
- author_name: String,
- mod_name: String,
- version: String,
-}
-
-impl std::str::FromStr for ParsedThunderstoreModString {
- type Err = &'static str; // todo use an better error management
-
- fn from_str(s: &str) -> Result<Self, Self::Err> {
- // Check whether Thunderstore string passes regex
- let re = regex::Regex::new(r"^[a-zA-Z0-9_]+-[a-zA-Z0-9_]+-\d+\.\d+\.\d++$").unwrap();
- if !re.is_match(s) {
- return Err("Incorrect format");
- }
-
- let mut parts = s.split('-');
-
- let author_name = parts.next().ok_or("None value on author_name")?.to_string();
- let mod_name = parts.next().ok_or("None value on mod_name")?.to_string();
- let version = parts.next().ok_or("None value on version")?.to_string();
-
- Ok(ParsedThunderstoreModString {
- author_name,
- mod_name,
- version,
- })
- }
-}
-
-impl std::fmt::Display for ParsedThunderstoreModString {
- fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
- write!(f, "{}-{}-{}", self.author_name, self.mod_name, self.version)
- }
-}
-
-#[derive(Serialize, Deserialize, Debug, Clone)]
-pub struct ThunderstoreManifest {
- name: String,
- version_number: String,
-}
-
-/// A wrapper around a temporary file handle and its path.
-///
-/// This struct is designed to be used for temporary files that should be automatically deleted
-/// when the `TempFile` instance goes out of scope.
-#[derive(Debug)]
-pub struct TempFile(fs::File, PathBuf);
-
-impl TempFile {
- pub fn new(file: fs::File, path: PathBuf) -> Self {
- Self(file, path)
- }
-
- pub fn file(&self) -> &fs::File {
- &self.0
- }
-}
-
-impl Drop for TempFile {
- fn drop(&mut self) {
- _ = fs::remove_file(&self.1)
- }
-}
-
-impl std::ops::Deref for TempFile {
- type Target = fs::File;
-
- fn deref(&self) -> &Self::Target {
- &self.0
- }
-}
-
-/// Installs the specified mod
-#[tauri::command]
-pub async fn install_mod_wrapper(
- game_install: GameInstall,
- thunderstore_mod_string: String,
-) -> Result<(), String> {
- match fc_download_mod_and_install(&game_install, &thunderstore_mod_string).await {
- Ok(()) => (),
- Err(err) => {
- log::warn!("{err}");
- return Err(err);
- }
- };
- match crate::repair_and_verify::clean_up_download_folder(&game_install, false) {
- Ok(()) => Ok(()),
- Err(err) => {
- log::info!("Failed to delete download folder due to {}", err);
- // Failure to delete download folder is not an error in mod install
- // As such ignore. User can still force delete if need be
- Ok(())
- }
- }
-}
-
-/// Returns a serde json object of the parsed `enabledmods.json` file
-pub fn get_enabled_mods(game_install: &GameInstall) -> Result<serde_json::value::Value, String> {
- let enabledmods_json_path = format!(
- "{}/{}/enabledmods.json",
- game_install.game_path, game_install.profile
- );
-
- // Check for JSON file
- if !std::path::Path::new(&enabledmods_json_path).exists() {
- return Err("enabledmods.json not found".to_string());
- }
-
- // Read file
- let data = match std::fs::read_to_string(enabledmods_json_path) {
- Ok(data) => data,
- Err(err) => return Err(err.to_string()),
- };
-
- // Parse JSON
- let res: serde_json::Value = match serde_json::from_str(&data) {
- Ok(result) => result,
- Err(err) => return Err(format!("Failed to read JSON due to: {}", err)),
- };
-
- // Return parsed data
- Ok(res)
-}
-
-/// Gets all currently installed and enabled/disabled mods to rebuild `enabledmods.json`
-pub fn rebuild_enabled_mods_json(game_install: &GameInstall) -> Result<(), String> {
- let enabledmods_json_path = format!(
- "{}/{}/enabledmods.json",
- game_install.game_path, game_install.profile
- );
- let mods_and_properties = get_installed_mods_and_properties(game_install.clone())?;
-
- // Create new mapping
- let mut my_map = serde_json::Map::new();
-
- // Build mapping
- for ns_mod in mods_and_properties.into_iter() {
- my_map.insert(ns_mod.name, serde_json::Value::Bool(ns_mod.enabled));
- }
-
- // Turn into serde object
- let obj = serde_json::Value::Object(my_map);
-
- // Write to file
- std::fs::write(
- enabledmods_json_path,
- serde_json::to_string_pretty(&obj).unwrap(),
- )
- .unwrap();
-
- Ok(())
-}
-
-/// Set the status of a passed mod to enabled/disabled
-#[tauri::command]
-pub fn set_mod_enabled_status(
- game_install: GameInstall,
- mod_name: String,
- is_enabled: bool,
-) -> Result<(), String> {
- let enabledmods_json_path = format!(
- "{}/{}/enabledmods.json",
- game_install.game_path, game_install.profile
- );
-
- // Parse JSON
- let mut res: serde_json::Value = match get_enabled_mods(&game_install) {
- Ok(res) => res,
- Err(err) => {
- log::warn!("Couldn't parse `enabledmod.json`: {}", err);
- log::warn!("Rebuilding file.");
-
- rebuild_enabled_mods_json(&game_install)?;
-
- // Then try again
- get_enabled_mods(&game_install)?
- }
- };
-
- // Check if key exists
- 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)?;
-
- // Then try again
- res = get_enabled_mods(&game_install)?;
- }
-
- // Update value
- res[mod_name] = serde_json::Value::Bool(is_enabled);
-
- // Save the JSON structure into the output file
- std::fs::write(
- enabledmods_json_path,
- serde_json::to_string_pretty(&res).unwrap(),
- )
- .unwrap();
-
- 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?.path();
- let md = std::fs::metadata(my_path.clone())?;
- 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!(
- "{}/{}/packages/",
- game_install.game_path, game_install.profile
- );
-
- 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?
-#[tauri::command]
-pub fn get_installed_mods_and_properties(
- game_install: GameInstall,
-) -> Result<Vec<NorthstarMod>, String> {
- // 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) {
- Ok(enabled_mods) => enabled_mods,
- Err(_) => serde_json::from_str("{}").unwrap(), // `enabledmods.json` not found, create empty object
- };
-
- let mut installed_mods = Vec::new();
- let binding = serde_json::Map::new(); // Empty map in case treating as object fails
- let mapping = enabled_mods.as_object().unwrap_or(&binding);
-
- // Use list of installed mods and set enabled based on `enabledmods.json`
- for mut current_mod in found_installed_mods {
- let current_mod_enabled = match mapping.get(&current_mod.name) {
- Some(enabled) => enabled.as_bool().unwrap(),
- None => true, // Northstar considers mods not in mapping as enabled.
- };
- current_mod.enabled = current_mod_enabled;
- installed_mods.push(current_mod);
- }
-
- Ok(installed_mods)
-}
-
-async fn get_ns_mod_download_url(thunderstore_mod_string: &str) -> Result<String, 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();
-
- // Parse mod string
- let parsed_ts_mod_string: ParsedThunderstoreModString = match thunderstore_mod_string.parse() {
- Ok(res) => res,
- Err(_) => return Err("Failed to parse mod string".to_string()),
- };
-
- // Encode as URL
- let ts_mod_string_url = format!(
- "{}/{}/{}",
- parsed_ts_mod_string.author_name,
- parsed_ts_mod_string.mod_name,
- parsed_ts_mod_string.version
- );
-
- for ns_mod in index {
- // Iterate over all versions of a given mod
- for ns_mod in ns_mod.versions.values() {
- if ns_mod.url.contains(&ts_mod_string_url) {
- return Ok(ns_mod.url.clone());
- }
- }
- }
-
- Err("Could not find mod on Thunderstore".to_string())
-}
-
-/// Returns a vector of modstrings containing the dependencies of a given mod
-async fn get_mod_dependencies(thunderstore_mod_string: &str) -> Result<Vec<String>, anyhow::Error> {
- log::info!("Attempting to get dependencies for: {thunderstore_mod_string}");
-
- let index = thermite::api::get_package_index()?.to_vec();
-
- // String replace works but more care should be taken in the future
- let ts_mod_string_url = thunderstore_mod_string.replace('-', "/");
-
- // Iterate over index
- for ns_mod in index {
- // Iterate over all versions of a given mod
- for ns_mod in ns_mod.versions.values() {
- if ns_mod.url.contains(&ts_mod_string_url) {
- return Ok(ns_mod.deps.clone());
- }
- }
- }
- 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!(
- "{}/{}/packages",
- game_install.game_path, game_install.profile
- );
-
- // 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) -> Result<(), Box<dyn Error + Send + Sync + 'static>> {
- let mut archive = match zip::read::ZipArchive::new(*input) {
- Ok(archive) => archive,
- Err(_) => {
- return Err(Box::new(ThermiteError::UnknownError(
- "Failed reading zip file".into(),
- )))
- }
- };
-
- 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;
- }
- }
- }
- }
-
- if file_path.starts_with("plugins/") {
- if let Some(name) = file_path.file_name() {
- if name.to_str().unwrap().contains(".dll") {
- log::warn!("Plugin detected, prompting user");
- if !plugins::plugin_prompt() {
- return Err(Box::new(ThermiteError::UnknownError(
- "Plugin detected and install denied".into(),
- )));
- }
- }
- }
- }
- }
-
- if has_mods && mod_json_exists {
- Ok(())
- } else {
- Err(Box::new(ThermiteError::UnknownError(
- "Mod not correctly formatted".into(),
- )))
- }
-}
-
-// 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.
-#[async_recursion]
-pub async fn fc_download_mod_and_install(
- game_install: &GameInstall,
- thunderstore_mod_string: &str,
-) -> Result<(), String> {
- log::info!("Attempting to install \"{thunderstore_mod_string}\" to {game_install:?}");
- // Get mods and download directories
- let download_directory = format!(
- "{}/___flightcore-temp/download-dir/",
- game_install.game_path
- );
-
- // Early return on empty string
- if thunderstore_mod_string.is_empty() {
- return Err("Passed empty string".to_string());
- }
-
- let deps = match get_mod_dependencies(thunderstore_mod_string).await {
- Ok(deps) => deps,
- Err(err) => return Err(err.to_string()),
- };
- log::info!("Mod dependencies: {deps:?}");
-
- // Recursively install dependencies
- for dep in deps {
- match fc_download_mod_and_install(game_install, &dep).await {
- Ok(()) => (),
- Err(err) => {
- if err == "Cannot install Northstar as a mod!" {
- continue; // For Northstar as a dependency, we just skip it
- } else {
- return Err(err);
- }
- }
- };
- }
-
- // Prevent installing Northstar as a mod
- // While it would fail during install anyway, having explicit error message is nicer
- for blacklisted_mod in BLACKLISTED_MODS {
- if thunderstore_mod_string.contains(blacklisted_mod) {
- return Err("Cannot install Northstar as a mod!".to_string());
- }
- }
-
- // Prevent installing mods that have specific install requirements
- for special_mod in MODS_WITH_SPECIAL_REQUIREMENTS {
- if thunderstore_mod_string.contains(special_mod) {
- return Err(format!(
- "{} has special install requirements and cannot be installed with FlightCore",
- thunderstore_mod_string
- ));
- }
- }
-
- // Get download URL for the specified mod
- let download_url = get_ns_mod_download_url(thunderstore_mod_string).await?;
-
- // Create download directory
- match std::fs::create_dir_all(download_directory.clone()) {
- Ok(()) => (),
- Err(err) => return Err(err.to_string()),
- };
-
- let path = format!(
- "{}/___flightcore-temp/download-dir/{thunderstore_mod_string}.zip",
- game_install.game_path
- );
-
- // Download the mod
- let temp_file = TempFile::new(
- std::fs::File::options()
- .read(true)
- .write(true)
- .truncate(true)
- .create(true)
- .open(&path)
- .map_err(|e| e.to_string())?,
- (&path).into(),
- );
- match thermite::core::manage::download(temp_file.file(), download_url) {
- Ok(_written_bytes) => (),
- Err(err) => return Err(err.to_string()),
- };
-
- // Get directory to install to made up of packages directory and Thunderstore mod string
- let install_directory = format!(
- "{}/{}/packages/",
- game_install.game_path, game_install.profile
- );
-
- // Extract the mod to the mods directory
- match thermite::core::manage::install_with_sanity(
- thunderstore_mod_string,
- temp_file.file(),
- 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 match err {
- ThermiteError::SanityError(e) => Err(
- format!("Mod failed sanity check during install. It's probably not correctly formatted. {}", e)
- ),
- _ => 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);
- }
- };
-
- Ok(())
-}
-
-/// Deletes a given Northstar mod folder
-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
- // If it does not exist, we might not be dealing with a Northstar mod
- let mod_json_path = ns_mod_dir_path.join("mod.json");
- if !mod_json_path.exists() {
- // If it doesn't exist, return an error
- return Err(format!("mod.json does not exist in {}", ns_mod_directory));
- }
-
- match std::fs::remove_dir_all(ns_mod_directory) {
- Ok(()) => Ok(()),
- Err(err) => Err(format!("Failed deleting mod: {err}")),
- }
-}
-
-/// Deletes a Northstar mod based on its name
-#[tauri::command]
-pub fn delete_northstar_mod(game_install: GameInstall, nsmod_name: String) -> Result<(), String> {
- // Prevent deleting core mod
- for core_mod in CORE_MODS {
- if nsmod_name == core_mod {
- return Err(format!("Cannot remove core mod {nsmod_name}"));
- }
- }
-
- // Get installed mods
- let installed_ns_mods = get_installed_mods_and_properties(game_install)?;
-
- // Get folder name based on northstarmods
- for installed_ns_mod in installed_ns_mods {
- // Installed mod matches specified mod
- if installed_ns_mod.name == nsmod_name {
- // Delete folder
- return delete_mod_folder(&installed_ns_mod.directory);
- }
- }
-
- 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!(
- "{}/{}/packages",
- game_install.game_path, game_install.profile
- );
- if std::path::Path::new(&packages_folder).exists() {
- for entry in fs::read_dir(packages_folder).unwrap() {
- let entry = entry.unwrap();
-
- // 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();
-
- 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)
-}