modde_games/gamebryo/
scanner.rs1use std::path::Path;
4
5use anyhow::{Context, Result};
6
7use crate::traits::{DiscoveredFile, DiscoveredMod, ModScanner, ModSource, ScanContext};
8
9pub struct GamebryoScanner {
11 pub game_id: &'static str,
12}
13
14pub static FALLOUT_NEW_VEGAS_SCANNER: GamebryoScanner = GamebryoScanner {
15 game_id: "fallout-new-vegas",
16};
17
18pub static OBLIVION_SCANNER: GamebryoScanner = GamebryoScanner {
19 game_id: "oblivion",
20};
21
22const PLUGIN_EXTENSIONS: &[&str] = &["esp", "esm"];
23const ARCHIVE_EXTENSIONS: &[&str] = &["bsa"];
24
25impl ModScanner for GamebryoScanner {
26 fn scan_directories(&self) -> &[&str] {
27 &["Data"]
28 }
29
30 fn scan_filesystem(&self, ctx: &ScanContext<'_>) -> Result<Vec<DiscoveredMod>> {
31 let data_dir = ctx.install_dir.join("Data");
32 if !data_dir.is_dir() {
33 return Ok(Vec::new());
34 }
35
36 let mut mods = Vec::new();
37 for entry in std::fs::read_dir(&data_dir)
38 .with_context(|| format!("failed to read directory: {}", data_dir.display()))?
39 .flatten()
40 {
41 let path = entry.path();
42 if path.is_dir() {
43 continue;
44 }
45 let ext = path
46 .extension()
47 .and_then(|ext| ext.to_str())
48 .unwrap_or("")
49 .to_lowercase();
50 if !PLUGIN_EXTENSIONS.contains(&ext.as_str()) {
51 continue;
52 }
53
54 let stem = path.file_stem().and_then(|s| s.to_str()).unwrap_or("");
55 let mut files = vec![make_data_file(ctx.install_dir, &path)];
56 for archive_ext in ARCHIVE_EXTENSIONS {
57 let archive_path = data_dir.join(format!("{stem}.{archive_ext}"));
58 if archive_path.exists() {
59 files.push(make_data_file(ctx.install_dir, &archive_path));
60 }
61 }
62
63 let filename = path
64 .file_name()
65 .and_then(|name| name.to_str())
66 .unwrap_or(stem)
67 .to_string();
68 mods.push(DiscoveredMod {
69 mod_id: format!("plugin/{filename}"),
70 display_name: filename,
71 version: None,
72 files,
73 source: ModSource::Filesystem {
74 location: "Data".into(),
75 },
76 confidence: 0.85,
77 });
78 }
79
80 Ok(mods)
81 }
82
83 fn mod_id_footprint(&self, mod_id: &str) -> Option<modde_core::scanner::ModFootprint> {
84 let filename = mod_id.strip_prefix("plugin/")?.to_lowercase();
85 Some(modde_core::scanner::ModFootprint::File(filename))
86 }
87}
88
89fn make_data_file(install_root: &Path, file_path: &Path) -> DiscoveredFile {
90 let rel = file_path
91 .strip_prefix(install_root)
92 .unwrap_or(file_path)
93 .to_string_lossy()
94 .replace('\\', "/");
95 let size = file_path.metadata().map_or(0, |m| m.len());
96 DiscoveredFile {
97 rel_path: rel,
98 size,
99 }
100}