aboutsummaryrefslogtreecommitdiff
path: root/src-tauri/src/mod_management/legacy.rs
blob: 1e9f90f55c6c60af6b295c8d308411c73c51aa42 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
use crate::constants::BLACKLISTED_MODS;
use crate::mod_management::{
    delete_mod_folder, get_installed_mods_and_properties, ParsedThunderstoreModString,
};
use crate::GameInstall;
use crate::NorthstarMod;
use anyhow::{anyhow, Result};
use serde::{Deserialize, Serialize};
use std::{io::Read, path::PathBuf};

#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct ModJson {
    #[serde(rename = "Name")]
    name: String,
    #[serde(rename = "ThunderstoreModString")]
    thunderstore_mod_string: Option<String>,
    #[serde(rename = "Version")]
    version: Option<String>,
}

/// Parses `manifest.json` for Thunderstore mod string
fn parse_for_thunderstore_mod_string(nsmod_path: &str) -> Result<String, anyhow::Error> {
    let manifest_json_path = format!("{}/manifest.json", nsmod_path);
    let ts_author_txt_path = format!("{}/thunderstore_author.txt", nsmod_path);

    // Check if `manifest.json` exists and parse
    let data = std::fs::read_to_string(manifest_json_path)?;
    let thunderstore_manifest: super::ThunderstoreManifest = json5::from_str(&data)?;

    // Check if `thunderstore_author.txt` exists and parse
    let mut file = std::fs::File::open(ts_author_txt_path)?;
    let mut thunderstore_author = String::new();
    file.read_to_string(&mut thunderstore_author)?;

    // Build mod string
    let thunderstore_mod_string = format!(
        "{}-{}-{}",
        thunderstore_author, thunderstore_manifest.name, thunderstore_manifest.version_number
    );

    Ok(thunderstore_mod_string)
}

/// Parse `mods` folder for installed mods.
pub fn parse_installed_mods(
    game_install: &GameInstall,
) -> Result<Vec<NorthstarMod>, anyhow::Error> {
    let ns_mods_folder = format!("{}/{}/mods/", game_install.game_path, game_install.profile);

    let paths = match std::fs::read_dir(ns_mods_folder) {
        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 {
        log::info!("{path:?}");
        let my_path = path.unwrap().path();
        log::info!("{my_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;
        }

        // Parse mod.json and get mod name

        // 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 Thunderstore mod string if it exists
        let thunderstore_mod_string = match parsed_mod_json.thunderstore_mod_string {
            // 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) {
                Ok(thunderstore_mod_string) => Some(thunderstore_mod_string),
                Err(_err) => None,
            },
        };
        // 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,
            enabled: false, // Placeholder
            directory: mod_directory,
        };

        mods.push(ns_mod);
    }

    // Return found mod names
    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,
    thunderstore_mod_string: String,
) -> Result<(), String> {
    // Prevent deleting core mod
    for core_ts_mod in BLACKLISTED_MODS {
        if thunderstore_mod_string == core_ts_mod {
            return Err(format!("Cannot remove core mod {thunderstore_mod_string}"));
        }
    }

    let parsed_ts_mod_string: ParsedThunderstoreModString =
        thunderstore_mod_string.parse().unwrap();

    // Get installed mods
    let installed_ns_mods = get_installed_mods_and_properties(game_install)?;

    // List of mod folders to remove
    let mut mod_folders_to_remove: Vec<String> = Vec::new();

    // Get folder name based on Thundestore mod string
    for installed_ns_mod in installed_ns_mods {
        if installed_ns_mod.thunderstore_mod_string.is_none() {
            // Not a Thunderstore mod
            continue;
        }

        let installed_ns_mod_ts_string: ParsedThunderstoreModString = installed_ns_mod
            .thunderstore_mod_string
            .unwrap()
            .parse()
            .unwrap();

        // Installed mod matches specified Thunderstore mod string
        if parsed_ts_mod_string.author_name == installed_ns_mod_ts_string.author_name
            && parsed_ts_mod_string.mod_name == installed_ns_mod_ts_string.mod_name
        {
            // Add folder to list of folder to remove
            mod_folders_to_remove.push(installed_ns_mod.directory);
        }
    }

    if mod_folders_to_remove.is_empty() {
        return Err(format!(
            "No mods removed as no Northstar mods matching {thunderstore_mod_string} were found to be installed."
        ));
    }

    // Delete given folders
    for mod_folder in mod_folders_to_remove {
        delete_mod_folder(&mod_folder)?;
    }

    Ok(())
}