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!("{}/R2Northstar/mods/", game_install.game_path);
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(())
}
|