supreme_dependencies/
lib.rs1use anyhow::Result;
2use sha2::{Digest, Sha256};
3use std::fs;
4use std::io::{self, Cursor, Read};
5use std::path::{Path, PathBuf};
6use std::sync::mpsc::{Sender, channel};
7use std::thread;
8use supreme_fs::GameStorage;
9use zip::ZipArchive;
10
11#[derive(Debug, Clone)]
12pub enum DependencyEvent {
13 Log(String),
14 SetStatus(String),
15 InitProgress(usize),
16 UpdateProgress(usize),
17 Finished(Option<String>), }
19
20#[must_use]
22pub fn check_dependencies_exist(storage: &GameStorage) -> bool {
23 let game_path = storage.game_path();
24 let injector_path = game_path.join("Display_Config_Resources/Injector.exe");
25 let helper_path = game_path.join("Display_Config_Resources/Display_Config_Helper.dll");
26
27 if !injector_path.exists() || !helper_path.exists() {
28 return false;
29 }
30
31 let injector_hash = calculate_sha256(&injector_path).unwrap_or_default();
33 let helper_hash = calculate_sha256(&helper_path).unwrap_or_default();
34
35 let expected_injector_hash = "ad8d3abe13e2af0084fa62a8bf6d0474dee2b266e8f7ac1a9964c773cdef3d2f";
37 let expected_helper_hash = "0df99e44d6a1f8f637c6836cb4347b9b3c2329c21137faa047e9907494b54671";
38
39 injector_hash == expected_injector_hash && helper_hash == expected_helper_hash
40}
41
42fn calculate_sha256(path: &PathBuf) -> Result<String> {
43 let mut file = fs::File::open(path)?;
44 let mut hasher = Sha256::new();
45 let mut buffer = Vec::new();
46 file.read_to_end(&mut buffer)?;
47 hasher.update(&buffer);
48 let hash = hasher.finalize();
49 Ok(hex::encode(hash))
50}
51
52#[must_use]
53pub fn check_hd_fonts_needed(storage: &GameStorage) -> bool {
54 let frnk_path = storage.game_path().join("Data/Fonts/Frnk/FRNK0033.tga");
55
56 if !frnk_path.exists() {
57 return false;
58 }
59
60 if let Ok(img) = image::open(&frnk_path) {
61 use image::GenericImageView;
62 let (width, height) = img.dimensions();
63 return width == 32 && height == 32;
66 }
67
68 false
69}
70
71fn download_and_extract_zip<F>(
73 url: &str,
74 target_dir: &Path,
75 filter: F,
76 tx: Sender<DependencyEvent>,
77) -> Result<usize>
78where
79 F: Fn(&str) -> bool,
80{
81 let _ = tx.send(DependencyEvent::Log(format!("Downloading from {url}")));
82 let _ = tx.send(DependencyEvent::SetStatus("Downloading...".to_string()));
83
84 let response = ureq::get(url).call().map_err(|e| {
85 let err_msg = format!("Failed to download file: {e}");
86 let _ = tx.send(DependencyEvent::Log(err_msg.clone()));
87 anyhow::anyhow!(err_msg)
88 })?;
89
90 let total_size = response
92 .headers()
93 .get("Content-Length")
94 .and_then(|v| v.to_str().ok())
95 .and_then(|s| s.parse::<u64>().ok())
96 .unwrap_or(0);
97 let _ = tx.send(DependencyEvent::Log(format!(
98 "Downloading {total_size} bytes...",
99 )));
100 let _ = tx.send(DependencyEvent::InitProgress(100));
101
102 let mut bytes = Vec::new();
103 let mut downloaded = 0;
104
105 let mut reader = response.into_body().into_reader();
107 let mut buffer = [0; 8192]; while let Ok(bytes_read) = reader.read(&mut buffer) {
109 if bytes_read == 0 {
110 break;
111 }
112
113 bytes.extend_from_slice(&buffer[..bytes_read]);
114 downloaded += bytes_read as u64;
115
116 if total_size > 0 {
117 let progress = (downloaded as f32 / total_size as f32 * 100.0) as usize;
118 let _ = tx.send(DependencyEvent::UpdateProgress(progress));
119 let _ = tx.send(DependencyEvent::SetStatus(format!(
120 "Downloading... {progress}%",
121 )));
122 }
123 }
124
125 let _ = tx.send(DependencyEvent::Log("Download completed!".to_string()));
126 let _ = tx.send(DependencyEvent::SetStatus(
127 "Extracting files...".to_string(),
128 ));
129
130 let cursor = Cursor::new(bytes);
132 let mut archive = ZipArchive::new(cursor).map_err(|e| {
133 let err_msg = format!("Failed to open zip archive: {e}");
134 let _ = tx.send(DependencyEvent::Log(err_msg.clone()));
135 anyhow::anyhow!(err_msg)
136 })?;
137
138 let _ = tx.send(DependencyEvent::InitProgress(archive.len()));
139
140 let mut extracted_count = 0;
142 for i in 0..archive.len() {
143 let mut file = archive.by_index(i).map_err(|e| {
144 let err_msg = format!("Failed to read file from archive: {e}");
145 let _ = tx.send(DependencyEvent::Log(err_msg.clone()));
146 anyhow::anyhow!(err_msg)
147 })?;
148
149 let file_name = file.name().to_string();
150
151 if file.is_dir() {
152 continue;
153 }
154
155 if filter(&file_name) {
157 let out_path = target_dir.join(&file_name);
158
159 if let Some(parent) = out_path.parent()
160 && let Err(e) = fs::create_dir_all(parent)
161 {
162 let err_msg = format!("Failed to create parent directory: {e}");
163 let _ = tx.send(DependencyEvent::Log(err_msg.clone()));
164 anyhow::bail!(err_msg);
165 }
166
167 let mut out_file = fs::File::create(&out_path).map_err(|e| {
168 let err_msg = format!("Failed to create file: {e}");
169 let _ = tx.send(DependencyEvent::Log(err_msg.clone()));
170 anyhow::anyhow!(err_msg)
171 })?;
172
173 if let Err(e) = io::copy(&mut file, &mut out_file) {
174 let err_msg = format!("Failed to extract file: {e}");
175 let _ = tx.send(DependencyEvent::Log(err_msg.clone()));
176 anyhow::bail!(err_msg);
177 }
178
179 let extracted_file_name = file.name().to_string();
180 let _ = tx.send(DependencyEvent::Log(format!(
181 "Extracted {extracted_file_name}",
182 )));
183 extracted_count += 1;
184 }
185
186 let _ = tx.send(DependencyEvent::UpdateProgress(i + 1));
187 }
188
189 Ok(extracted_count)
190}
191
192pub fn install_dependencies(storage: &GameStorage, tx: Sender<DependencyEvent>) -> Result<()> {
193 let game_path = storage.game_path();
194
195 let resources_dir = game_path.join("Display_Config_Resources");
197 if let Err(e) = fs::create_dir_all(&resources_dir) {
198 let err_msg = format!("Failed to create directory: {e}");
199 let _ = tx.send(DependencyEvent::Log(err_msg.clone()));
200 anyhow::bail!(err_msg);
201 }
202
203 let target_dir = game_path;
205 let url =
206 "https://github.com/domsleee/SS-Dat-Info/releases/download/0.4.5/Display_Config_0.4.5.zip";
207
208 let extracted_count = download_and_extract_zip(
209 url,
210 target_dir,
211 |file_name| file_name.starts_with("Display_Config_Resources/"),
212 tx.clone(),
213 )?;
214
215 let _ = tx.send(DependencyEvent::Log(format!(
216 "Extracted {extracted_count} required files",
217 )));
218
219 Ok(())
220}
221
222pub fn install_hd_fonts(storage: &GameStorage, tx: Sender<DependencyEvent>) -> Result<()> {
223 let game_path = storage.game_path();
224 let target_dir = game_path;
227 let url = "https://github.com/domsleee/SS-Dat-Info/releases/download/0.3.2/4xFonts.zip";
228
229 let extracted_count = download_and_extract_zip(
230 url,
231 target_dir,
232 |file_name| {
233 let path = Path::new(file_name);
234 let is_tga = path.extension().is_some_and(|e| e == "TGA");
235 let is_frnk = path
236 .file_stem()
237 .is_some_and(|n| n.as_encoded_bytes().starts_with(b"FRNK"));
238 is_tga && is_frnk
239 },
240 tx.clone(),
241 )?;
242
243 let _ = tx.send(DependencyEvent::Log(format!(
244 "Extracted {extracted_count} HD font files",
245 )));
246
247 Ok(())
248}
249
250#[must_use]
252pub fn start_install_dependencies(
253 storage: &GameStorage,
254) -> std::sync::mpsc::Receiver<DependencyEvent> {
255 let (tx, rx) = channel();
256
257 let storage_clone = storage.clone();
258 thread::spawn(move || {
259 let result = install_dependencies(&storage_clone, tx.clone());
260 let error_msg = result.err().map(|e| e.to_string());
261 let _ = tx.send(DependencyEvent::Finished(error_msg));
262 });
263
264 rx
265}
266
267#[must_use]
269pub fn start_install_hd_fonts(storage: &GameStorage) -> std::sync::mpsc::Receiver<DependencyEvent> {
270 let (tx, rx) = channel();
271
272 let storage_clone = storage.clone();
273 thread::spawn(move || {
274 let result = install_hd_fonts(&storage_clone, tx.clone());
275 let error_msg = result.err().map(|e| e.to_string());
276 let _ = tx.send(DependencyEvent::Finished(error_msg));
277 });
278
279 rx
280}