modde_games/cyberpunk/
scanner.rs1use std::path::Path;
4
5use anyhow::{Context, Result};
6
7use super::manifest::RedModManifest;
8use crate::scanner_patterns::{DirectoryModRule, SingleFileModRule};
9use crate::traits::{DiscoveredMod, ModScanner, ModSource, ScanContext, walk_files_relative};
10
11pub struct CyberpunkScanner;
13
14pub static CYBERPUNK_SCANNER: CyberpunkScanner = CyberpunkScanner;
15
16const SCAN_DIRS: &[&str] = &[
18 "bin/x64/plugins/cyber_engine_tweaks/mods",
19 "r6/scripts",
20 "r6/tweaks",
21 "archive/pc/mod",
22 "mods",
23];
24
25const CET_RULE: DirectoryModRule = DirectoryModRule {
26 rel_dir: "bin/x64/plugins/cyber_engine_tweaks/mods",
27 mod_id_prefix: "cet",
28 source_location: "cet",
29 confidence: 0.7,
30 marker_file: Some("init.lua"),
31 marker_confidence: Some(0.95),
32};
33
34const REDSCRIPT_RULE: DirectoryModRule = DirectoryModRule {
35 rel_dir: "r6/scripts",
36 mod_id_prefix: "reds",
37 source_location: "r6/scripts",
38 confidence: 0.9,
39 marker_file: None,
40 marker_confidence: None,
41};
42
43const TWEAKXL_RULE: DirectoryModRule = DirectoryModRule {
44 rel_dir: "r6/tweaks",
45 mod_id_prefix: "tweak",
46 source_location: "r6/tweaks",
47 confidence: 0.9,
48 marker_file: None,
49 marker_confidence: None,
50};
51
52const ARCHIVE_RULE: SingleFileModRule = SingleFileModRule {
53 rel_dir: "archive/pc/mod",
54 extension: "archive",
55 ignored_prefixes: &[],
56 mod_id_prefix: "archive",
57 source_location: "archive/pc/mod",
58 confidence: 0.85,
59};
60
61impl ModScanner for CyberpunkScanner {
62 fn scan_directories(&self) -> &[&str] {
63 SCAN_DIRS
64 }
65
66 fn scan_filesystem(&self, ctx: &ScanContext<'_>) -> Result<Vec<DiscoveredMod>> {
67 let install = ctx.install_dir;
68 let mut mods = Vec::new();
69
70 CET_RULE.scan(install, &mut mods)?;
71 REDSCRIPT_RULE.scan(install, &mut mods)?;
72 TWEAKXL_RULE.scan(install, &mut mods)?;
73 ARCHIVE_RULE.scan(install, &mut mods)?;
74 scan_redmod_mods(install, &mut mods)?;
75
76 Ok(mods)
77 }
78
79 fn mod_id_footprint(&self, mod_id: &str) -> Option<modde_core::scanner::ModFootprint> {
88 use modde_core::scanner::ModFootprint;
89 if let Some(name) = mod_id.strip_prefix("cet/") {
90 Some(ModFootprint::Directory(format!(
91 "bin/x64/plugins/cyber_engine_tweaks/mods/{}/",
92 name.to_lowercase()
93 )))
94 } else if let Some(name) = mod_id.strip_prefix("reds/") {
95 Some(ModFootprint::Directory(format!(
96 "r6/scripts/{}/",
97 name.to_lowercase()
98 )))
99 } else if let Some(name) = mod_id.strip_prefix("tweak/") {
100 Some(ModFootprint::Directory(format!(
101 "r6/tweaks/{}/",
102 name.to_lowercase()
103 )))
104 } else if let Some(name) = mod_id.strip_prefix("redmod/") {
105 Some(ModFootprint::Directory(format!(
106 "mods/{}/",
107 name.to_lowercase()
108 )))
109 } else {
110 mod_id.strip_prefix("archive/").map(|stem| {
111 ModFootprint::File(format!("archive/pc/mod/{}.archive", stem.to_lowercase()))
112 })
113 }
114 }
115}
116
117fn scan_redmod_mods(install: &Path, out: &mut Vec<DiscoveredMod>) -> Result<()> {
119 let mods_dir = install.join("mods");
120 if !mods_dir.is_dir() {
121 return Ok(());
122 }
123
124 for entry in std::fs::read_dir(&mods_dir)
125 .with_context(|| format!("failed to read directory: {}", mods_dir.display()))?
126 .flatten()
127 {
128 if !entry.path().is_dir() {
129 continue;
130 }
131
132 let dir_name = entry.file_name().to_string_lossy().to_string();
133 let info_json = entry.path().join("info.json");
134
135 let (name, version) = if info_json.exists() {
136 match std::fs::read_to_string(&info_json)
137 .ok()
138 .and_then(|s| RedModManifest::parse(&s).ok())
139 {
140 Some(manifest) => (manifest.name, manifest.version),
141 None => (dir_name.clone(), None),
142 }
143 } else {
144 (dir_name.clone(), None)
145 };
146
147 let files = walk_files_relative(install, &entry.path());
148 if files.is_empty() {
149 continue;
150 }
151
152 out.push(DiscoveredMod {
153 mod_id: format!("redmod/{dir_name}"),
154 display_name: name,
155 version,
156 files,
157 source: ModSource::Filesystem {
158 location: "mods".into(),
159 },
160 confidence: if info_json.exists() { 0.95 } else { 0.8 },
161 });
162 }
163 Ok(())
164}