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
use serde::{Deserialize, Serialize};
use serde_json::{self, Value};
use std::{
collections::{hash_map::DefaultHasher, BTreeMap},
hash::{Hash, Hasher},
};
use std::{
fs,
path::{Path, PathBuf},
};
use crate::error::ThermiteError;
#[derive(Serialize, Deserialize, Debug, Clone)]
#[serde(rename_all = "PascalCase")]
pub struct ModJSON {
name: String,
description: String,
version: String,
load_priotity: i32,
con_vars: Vec<Value>,
scripts: Vec<Value>,
localisation: Vec<String>,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct Mod {
pub name: String,
pub latest: String,
#[serde(default)]
pub installed: bool,
#[serde(default)]
pub upgradable: bool,
#[serde(default)]
pub global: bool,
pub versions: BTreeMap<String, ModVersion>,
}
impl Mod {
pub fn get_latest(&self) -> Option<&ModVersion> {
self.versions.get(&self.latest)
}
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct ModVersion {
pub name: String,
pub version: String,
pub url: String,
pub desc: String,
pub deps: Vec<String>,
pub installed: bool,
pub global: bool,
pub file_size: u64,
}
impl ModVersion {
pub fn file_size_string(&self) -> String {
if self.file_size / 1_000_000 >= 1 {
let size = self.file_size as f64 / 1_048_576f64;
format!("{:.2} MB", size)
} else {
let size = self.file_size as f64 / 1024f64;
format!("{:.2} KB", size)
}
}
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct Manifest {
pub name: String,
pub version_number: String,
pub website_url: String,
pub description: String,
pub dependencies: Vec<String>,
}
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct EnabledMods {
#[serde(rename = "Northstar.Client")]
pub client: bool,
#[serde(rename = "Northstar.Custom")]
pub custom: bool,
#[serde(rename = "Northstar.CustomServers")]
pub servers: bool,
#[serde(flatten)]
pub mods: BTreeMap<String, bool>,
#[serde(skip)]
hash: u64,
#[serde(skip)]
path: Option<PathBuf>,
}
impl Hash for EnabledMods {
fn hash<H: Hasher>(&self, state: &mut H) {
self.client.hash(state);
self.custom.hash(state);
self.servers.hash(state);
self.mods.hash(state);
}
}
impl Default for EnabledMods {
fn default() -> Self {
Self {
client: true,
custom: true,
servers: true,
mods: BTreeMap::new(),
hash: 0,
path: None,
}
}
}
impl Drop for EnabledMods {
fn drop(&mut self) {
if self.path.is_some() {
let hash = {
let mut hasher = DefaultHasher::new();
self.hash(&mut hasher);
hasher.finish()
};
if hash != self.hash {
self.save().unwrap()
}
}
}
}
impl EnabledMods {
pub fn save(&self) -> Result<(), ThermiteError> {
let parsed = serde_json::to_string_pretty(self)?;
if let Some(path) = &self.path {
if let Some(p) = path.parent() {
fs::create_dir_all(p)?;
}
fs::write(path, &parsed)?;
}
Ok(())
}
pub fn save_with_path(&mut self, path: impl AsRef<Path>) -> Result<(), ThermiteError> {
self.path = Some(path.as_ref().to_owned());
self.save()
}
}