pub mod gamemode;
pub mod mangohud;
pub mod optiscaler;
pub mod reshade;
pub mod vkbasalt;
use std::path::{Path, PathBuf};
use anyhow::Result;
use serde::{Deserialize, Serialize};
use smallvec::SmallVec;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ToolCategory {
Overlay,
PostProcess,
Performance,
Upscaler,
}
impl std::fmt::Display for ToolCategory {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Overlay => write!(f, "Overlay"),
Self::PostProcess => write!(f, "Post-Processing"),
Self::Performance => write!(f, "Performance"),
Self::Upscaler => write!(f, "Upscaler"),
}
}
}
#[derive(Debug, Clone)]
pub enum ToolAvailability {
Available { version: Option<String> },
NotInstalled { install_hint: String },
}
impl ToolAvailability {
pub fn is_available(&self) -> bool {
matches!(self, Self::Available { .. })
}
}
#[derive(Debug, Clone)]
pub struct WrapperEntry {
pub exe: String,
pub args: String,
}
#[derive(Debug, Clone, Default)]
pub struct AppliedFiles {
pub files: Vec<PathBuf>,
}
#[derive(Debug, Clone)]
pub struct GeneratedConfig {
pub path: PathBuf,
pub content: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ToolConfig {
pub tool_id: String,
pub enabled: bool,
pub settings: serde_json::Value,
}
impl ToolConfig {
pub fn new(tool_id: impl Into<String>) -> Self {
Self {
tool_id: tool_id.into(),
enabled: false,
settings: serde_json::Value::Object(serde_json::Map::new()),
}
}
pub fn get_str(&self, key: &str) -> Option<&str> {
self.settings.get(key).and_then(|v| v.as_str())
}
pub fn get_bool(&self, key: &str) -> bool {
self.settings
.get(key)
.and_then(|v| v.as_bool())
.unwrap_or(false)
}
pub fn get_i64(&self, key: &str) -> Option<i64> {
self.settings.get(key).and_then(|v| v.as_i64())
}
pub fn set(&mut self, key: impl Into<String>, value: serde_json::Value) {
if let serde_json::Value::Object(ref mut map) = self.settings {
map.insert(key.into(), value);
}
}
}
pub trait GameTool: Send + Sync {
fn tool_id(&self) -> &'static str;
fn display_name(&self) -> &'static str;
fn category(&self) -> ToolCategory;
fn detect_available(&self) -> ToolAvailability;
fn env_vars(&self, config: &ToolConfig) -> SmallVec<[(String, String); 4]>;
fn wrapper_command(&self, _config: &ToolConfig) -> Option<WrapperEntry> {
None
}
fn wine_dll_overrides(&self, _config: &ToolConfig) -> SmallVec<[String; 4]> {
SmallVec::new()
}
fn apply(&self, _game_dir: &Path, _config: &ToolConfig) -> Result<AppliedFiles> {
Ok(AppliedFiles::default())
}
fn revert(&self, game_dir: &Path, applied: &AppliedFiles) -> Result<()> {
for rel in &applied.files {
let path = game_dir.join(rel);
if path.exists() {
std::fs::remove_file(&path)?;
}
}
Ok(())
}
fn generate_config(&self, _config: &ToolConfig) -> Option<GeneratedConfig> {
None
}
fn default_config(&self) -> ToolConfig;
}
static ALL_TOOLS: [&dyn GameTool; 5] = [
&mangohud::MANGOHUD,
&vkbasalt::VKBASALT,
&gamemode::GAMEMODE,
&reshade::RESHADE,
&optiscaler::OPTISCALER,
];
pub fn all_tools() -> &'static [&'static dyn GameTool] {
&ALL_TOOLS
}
pub fn resolve_tool(tool_id: &str) -> Option<&'static dyn GameTool> {
all_tools().iter().find(|t| t.tool_id() == tool_id).copied()
}
pub fn tool_config_dir(game_id: &str) -> PathBuf {
modde_core::paths::modde_data_dir().join("tools").join(game_id)
}
pub(crate) fn which(binary: &str) -> Option<PathBuf> {
which::which(binary).ok()
}