1pub mod gamemode;
8pub mod mangohud;
9pub mod optiscaler;
10pub mod reshade;
11pub mod vkbasalt;
12
13use std::path::{Path, PathBuf};
14
15use anyhow::Result;
16use serde::{Deserialize, Serialize};
17use smallvec::SmallVec;
18
19#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
23#[serde(rename_all = "snake_case")]
24pub enum ToolCategory {
25 Overlay,
27 PostProcess,
29 Performance,
31 Upscaler,
33}
34
35impl std::fmt::Display for ToolCategory {
36 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
37 match self {
38 Self::Overlay => write!(f, "Overlay"),
39 Self::PostProcess => write!(f, "Post-Processing"),
40 Self::Performance => write!(f, "Performance"),
41 Self::Upscaler => write!(f, "Upscaler"),
42 }
43 }
44}
45
46#[derive(Debug, Clone)]
48pub enum ToolAvailability {
49 Available { version: Option<String> },
50 NotInstalled { install_hint: String },
51}
52
53impl ToolAvailability {
54 pub fn is_available(&self) -> bool {
55 matches!(self, Self::Available { .. })
56 }
57}
58
59#[derive(Debug, Clone)]
61pub struct WrapperEntry {
62 pub exe: String,
63 pub args: String,
64}
65
66#[derive(Debug, Clone, Default)]
68pub struct AppliedFiles {
69 pub files: Vec<PathBuf>,
71}
72
73#[derive(Debug, Clone)]
75pub struct GeneratedConfig {
76 pub path: PathBuf,
78 pub content: String,
80}
81
82#[derive(Debug, Clone, Serialize, Deserialize)]
84pub struct ToolConfig {
85 pub tool_id: String,
86 pub enabled: bool,
87 pub settings: serde_json::Value,
88}
89
90impl ToolConfig {
91 pub fn new(tool_id: impl Into<String>) -> Self {
92 Self {
93 tool_id: tool_id.into(),
94 enabled: false,
95 settings: serde_json::Value::Object(serde_json::Map::new()),
96 }
97 }
98
99 pub fn get_str(&self, key: &str) -> Option<&str> {
101 self.settings.get(key).and_then(|v| v.as_str())
102 }
103
104 pub fn get_bool(&self, key: &str) -> bool {
106 self.settings
107 .get(key)
108 .and_then(|v| v.as_bool())
109 .unwrap_or(false)
110 }
111
112 pub fn get_i64(&self, key: &str) -> Option<i64> {
114 self.settings.get(key).and_then(|v| v.as_i64())
115 }
116
117 pub fn set(&mut self, key: impl Into<String>, value: serde_json::Value) {
119 if let serde_json::Value::Object(ref mut map) = self.settings {
120 map.insert(key.into(), value);
121 }
122 }
123}
124
125pub trait GameTool: Send + Sync {
129 fn tool_id(&self) -> &'static str;
131
132 fn display_name(&self) -> &'static str;
134
135 fn category(&self) -> ToolCategory;
137
138 fn detect_available(&self) -> ToolAvailability;
140
141 fn env_vars(&self, config: &ToolConfig) -> SmallVec<[(String, String); 4]>;
143
144 fn wrapper_command(&self, _config: &ToolConfig) -> Option<WrapperEntry> {
146 None
147 }
148
149 fn wine_dll_overrides(&self, _config: &ToolConfig) -> SmallVec<[String; 4]> {
151 SmallVec::new()
152 }
153
154 fn apply(&self, _game_dir: &Path, _config: &ToolConfig) -> Result<AppliedFiles> {
157 Ok(AppliedFiles::default())
158 }
159
160 fn revert(&self, game_dir: &Path, applied: &AppliedFiles) -> Result<()> {
162 for rel in &applied.files {
163 let path = game_dir.join(rel);
164 if path.exists() {
165 std::fs::remove_file(&path)?;
166 }
167 }
168 Ok(())
169 }
170
171 fn generate_config(&self, _config: &ToolConfig) -> Option<GeneratedConfig> {
173 None
174 }
175
176 fn default_config(&self) -> ToolConfig;
178}
179
180static ALL_TOOLS: [&dyn GameTool; 5] = [
184 &mangohud::MANGOHUD,
185 &vkbasalt::VKBASALT,
186 &gamemode::GAMEMODE,
187 &reshade::RESHADE,
188 &optiscaler::OPTISCALER,
189];
190
191pub fn all_tools() -> &'static [&'static dyn GameTool] {
192 &ALL_TOOLS
193}
194
195pub fn resolve_tool(tool_id: &str) -> Option<&'static dyn GameTool> {
197 all_tools().iter().find(|t| t.tool_id() == tool_id).copied()
198}
199
200pub fn tool_config_dir(game_id: &str) -> PathBuf {
204 modde_core::paths::modde_data_dir().join("tools").join(game_id)
205}
206
207pub(crate) fn which(binary: &str) -> Option<PathBuf> {
212 which::which(binary).ok()
213}