modde_core/installer/
fs.rs1use std::fs;
10use std::io;
11use std::path::{Path, PathBuf};
12
13use tracing::warn;
14
15use super::types::{InstallerError, InstallerResult};
16
17pub fn extract_archive(archive_path: &Path, dest: &Path) -> InstallerResult<()> {
20 let file = fs::File::open(archive_path)
21 .map_err(|e| InstallerError::Extract(format!("open {}: {e}", archive_path.display())))?;
22 let mut archive = zip::ZipArchive::new(file)
23 .map_err(|e| InstallerError::Extract(format!("read {}: {e}", archive_path.display())))?;
24
25 for i in 0..archive.len() {
26 let mut entry = archive
27 .by_index(i)
28 .map_err(|e| InstallerError::Extract(format!("entry {i}: {e}")))?;
29 let Some(name) = entry.enclosed_name() else {
30 warn!("skipping archive entry with unsafe path");
31 continue;
32 };
33 let out_path = dest.join(name);
34
35 if entry.is_dir() {
36 fs::create_dir_all(&out_path)?;
37 } else {
38 if let Some(parent) = out_path.parent() {
39 fs::create_dir_all(parent)?;
40 }
41 let mut out_file = fs::File::create(&out_path)?;
42 io::copy(&mut entry, &mut out_file)?;
43 }
44 }
45
46 Ok(())
47}
48
49#[must_use]
55pub fn find_fomod_config(mod_dir: &Path) -> Option<PathBuf> {
56 let canonical = mod_dir.join("fomod").join("ModuleConfig.xml");
57 if canonical.exists() {
58 return Some(canonical);
59 }
60
61 let entries = fs::read_dir(mod_dir).ok()?;
62 for entry in entries.flatten() {
63 if !entry.path().is_dir() {
64 continue;
65 }
66 if !entry.file_name().eq_ignore_ascii_case("fomod") {
67 continue;
68 }
69 let inner_entries = fs::read_dir(entry.path()).ok()?;
70 for inner in inner_entries.flatten() {
71 if inner.file_name().eq_ignore_ascii_case("moduleconfig.xml") {
72 return Some(inner.path());
73 }
74 }
75 }
76 None
77}
78
79pub(crate) fn walk_files(dir: &Path) -> InstallerResult<Vec<(PathBuf, PathBuf)>> {
82 let mut out = Vec::new();
83 walk_files_into(dir, dir, &mut out)?;
84 Ok(out)
85}
86
87fn walk_files_into(
88 base: &Path,
89 dir: &Path,
90 out: &mut Vec<(PathBuf, PathBuf)>,
91) -> InstallerResult<()> {
92 if !dir.exists() {
93 return Ok(());
94 }
95 for entry in fs::read_dir(dir)? {
96 let entry = entry?;
97 let path = entry.path();
98 if path.is_dir() {
99 walk_files_into(base, &path, out)?;
100 } else if path.is_file()
101 && let Ok(rel) = path.strip_prefix(base)
102 {
103 out.push((path.clone(), rel.to_path_buf()));
104 }
105 }
106 Ok(())
107}
108
109pub fn xxh64_file_hex(path: &Path) -> InstallerResult<String> {
112 use std::io::Read;
113 use xxhash_rust::xxh64::Xxh64;
114
115 let mut file = fs::File::open(path)?;
116 let mut hasher = Xxh64::new(0);
117 let mut buf = [0u8; 64 * 1024];
118 loop {
119 let n = file.read(&mut buf)?;
120 if n == 0 {
121 break;
122 }
123 hasher.update(&buf[..n]);
124 }
125 Ok(format!("{:016x}", hasher.digest()))
126}