Skip to main content

modde_games/tools/
reshade.rs

1//! ReShade — shader injection for Wine/Proton games.
2//!
3//! ReShade works by placing a proxy DLL (typically `dxgi.dll` or `d3d11.dll`)
4//! in the game directory. Wine needs `WINEDLLOVERRIDES` set to load the native
5//! version instead of its built-in stub.
6
7use std::path::{Path, PathBuf};
8
9use anyhow::{Context, Result};
10use smallvec::{SmallVec, smallvec};
11use tracing::info;
12
13use super::{
14    AppliedFiles, GameTool, ToolAvailability, ToolCategory, ToolConfig,
15};
16
17pub static RESHADE: ReShade = ReShade;
18
19pub struct ReShade;
20
21impl GameTool for ReShade {
22    fn tool_id(&self) -> &'static str {
23        "reshade"
24    }
25
26    fn display_name(&self) -> &'static str {
27        "ReShade"
28    }
29
30    fn category(&self) -> ToolCategory {
31        ToolCategory::PostProcess
32    }
33
34    fn detect_available(&self) -> ToolAvailability {
35        // ReShade DLLs must be provided by the user (they're Windows binaries).
36        // Check if a source path is configured.
37        ToolAvailability::Available {
38            version: Some("user-provided".into()),
39        }
40    }
41
42    fn env_vars(&self, _config: &ToolConfig) -> SmallVec<[(String, String); 4]> {
43        SmallVec::new()
44    }
45
46    fn wine_dll_overrides(&self, config: &ToolConfig) -> SmallVec<[String; 4]> {
47        let dll_name = config.get_str("dll_name").unwrap_or("dxgi");
48        smallvec![dll_name.to_string()]
49    }
50
51    fn apply(&self, game_dir: &Path, config: &ToolConfig) -> Result<AppliedFiles> {
52        let source_dir = config
53            .get_str("source_dir")
54            .map(PathBuf::from)
55            .context("reshade: 'source_dir' setting is required (path to ReShade DLLs)")?;
56
57        let dll_name = config.get_str("dll_name").unwrap_or("dxgi.dll");
58        let exe_subdir = config.get_str("exe_subdir").unwrap_or("");
59
60        let target_dir = if exe_subdir.is_empty() {
61            game_dir.to_path_buf()
62        } else {
63            game_dir.join(exe_subdir)
64        };
65
66        std::fs::create_dir_all(&target_dir)?;
67
68        let mut applied = AppliedFiles::default();
69
70        // Copy the main DLL
71        let src_dll = source_dir.join(dll_name);
72        if src_dll.exists() {
73            let dest = target_dir.join(dll_name);
74            std::fs::copy(&src_dll, &dest)
75                .with_context(|| format!("failed to copy ReShade DLL to {}", dest.display()))?;
76            let rel = dest.strip_prefix(game_dir).unwrap_or(&dest).to_path_buf();
77            applied.files.push(rel);
78            info!(dll = %dll_name, "applied ReShade DLL");
79        }
80
81        // Copy ReShade.ini if present
82        let ini = source_dir.join("ReShade.ini");
83        if ini.exists() {
84            let dest = target_dir.join("ReShade.ini");
85            std::fs::copy(&ini, &dest)?;
86            let rel = dest.strip_prefix(game_dir).unwrap_or(&dest).to_path_buf();
87            applied.files.push(rel);
88        }
89
90        // Copy shader directories if present
91        for subdir in &["reshade-shaders", "reshade-presets"] {
92            let src = source_dir.join(subdir);
93            if src.is_dir() {
94                let dest = target_dir.join(subdir);
95                copy_dir_recursive(&src, &dest, game_dir, &mut applied)?;
96            }
97        }
98
99        Ok(applied)
100    }
101
102    fn default_config(&self) -> ToolConfig {
103        let mut config = ToolConfig::new("reshade");
104        config.set("dll_name", serde_json::json!("dxgi.dll"));
105        config.set("exe_subdir", serde_json::json!(""));
106        config
107    }
108}
109
110/// Recursively copy a directory, recording all files in [`AppliedFiles`].
111fn copy_dir_recursive(
112    src: &Path,
113    dest: &Path,
114    game_dir: &Path,
115    applied: &mut AppliedFiles,
116) -> Result<()> {
117    std::fs::create_dir_all(dest)?;
118
119    for entry in std::fs::read_dir(src)?.flatten() {
120        let ty = entry.file_type()?;
121        let src_path = entry.path();
122        let dest_path = dest.join(entry.file_name());
123
124        if ty.is_dir() {
125            copy_dir_recursive(&src_path, &dest_path, game_dir, applied)?;
126        } else {
127            std::fs::copy(&src_path, &dest_path)?;
128            let rel = dest_path
129                .strip_prefix(game_dir)
130                .unwrap_or(&dest_path)
131                .to_path_buf();
132            applied.files.push(rel);
133        }
134    }
135
136    Ok(())
137}