Skip to main content

modde_games/
optiscaler.rs

1//! Per-game `OptiScaler` compatibility profiles.
2//!
3//! These profiles are intentionally game-owned metadata. They represent
4//! community-tested configurations users may choose when enabling `OptiScaler`;
5//! the presence of a profile must not imply `OptiScaler` is enabled by default.
6
7pub(crate) mod loader;
8pub(crate) mod spec;
9
10use std::sync::OnceLock;
11
12use dashmap::DashMap;
13use tracing::warn;
14
15use crate::generic::leak::slice as leak_slice;
16
17pub use spec::{
18    OptiScalerImportToml, OptiScalerProfilesSpec, serialize as serialize_optiscaler_profiles_toml,
19};
20
21/// One `OptiScaler` INI override expressed as a dotted section/key path.
22#[derive(Debug, Clone, Copy, PartialEq, Eq)]
23pub struct OptiScalerIniOverride {
24    pub key: &'static str,
25    pub value: &'static str,
26}
27
28/// A community-tested `OptiScaler` configuration for a game.
29#[derive(Debug, Clone, Copy, PartialEq, Eq)]
30#[allow(clippy::struct_excessive_bools)]
31pub struct OptiScalerProfile {
32    pub id: &'static str,
33    pub name: &'static str,
34    pub source_url: &'static str,
35    pub tested_optiscaler_version: &'static str,
36    pub source_mode: Option<&'static str>,
37    pub goverlay_channel: Option<&'static str>,
38    pub proxy_dll: &'static str,
39    pub release_tag: Option<&'static str>,
40    pub release_asset: Option<&'static str>,
41    pub wine_dll_overrides: &'static [&'static str],
42    pub copy_companion_files: bool,
43    pub enable_optipatcher: bool,
44    pub fsr4_variant: Option<&'static str>,
45    pub emulate_fp8: bool,
46    pub spoof_dlss: bool,
47    pub ini_overrides: &'static [OptiScalerIniOverride],
48    pub notes: &'static str,
49}
50
51static MERGED_PROFILE_CACHE: OnceLock<DashMap<String, &'static [OptiScalerProfile]>> =
52    OnceLock::new();
53
54fn merged_profile_cache() -> &'static DashMap<String, &'static [OptiScalerProfile]> {
55    MERGED_PROFILE_CACHE.get_or_init(DashMap::new)
56}
57
58/// Resolve community-tested `OptiScaler` profiles for a supported game.
59#[must_use]
60pub fn resolve_optiscaler_profiles(game_id: &str) -> &'static [OptiScalerProfile] {
61    let built_in = crate::registry::resolve_game(game_id)
62        .map_or(&[] as &'static [OptiScalerProfile], |game| {
63            game.optiscaler_profiles
64        });
65    let user = loader::load_user_profiles(game_id);
66
67    if user.is_empty() {
68        return built_in;
69    }
70
71    if built_in.is_empty() {
72        return user;
73    }
74
75    if let Some(profiles) = merged_profile_cache().get(game_id) {
76        return *profiles;
77    }
78
79    let mut merged = built_in.to_vec();
80    for profile in user {
81        if let Some(existing_index) = merged.iter().position(|existing| existing.id == profile.id) {
82            warn!(
83                game_id,
84                profile_id = profile.id,
85                "user OptiScaler profile overrides built-in profile"
86            );
87            merged[existing_index] = *profile;
88        } else {
89            merged.push(*profile);
90        }
91    }
92
93    *merged_profile_cache()
94        .entry(game_id.to_string())
95        .or_insert_with(move || leak_slice(merged))
96}
97
98/// Return the default community-tested profile for a game, if any.
99#[must_use]
100pub fn default_optiscaler_profile(game_id: &str) -> Option<&'static OptiScalerProfile> {
101    resolve_optiscaler_profiles(game_id).first()
102}
103
104#[cfg(test)]
105mod tests {
106    use super::{default_optiscaler_profile, resolve_optiscaler_profiles};
107    use std::sync::OnceLock;
108
109    fn shared_data_dir() -> &'static std::path::PathBuf {
110        static DATA_DIR: OnceLock<std::path::PathBuf> = OnceLock::new();
111        DATA_DIR.get_or_init(|| {
112            let tempdir = tempfile::TempDir::new().expect("create tempdir");
113            let data_dir = tempdir.path().join("data");
114            std::fs::create_dir_all(data_dir.join("games")).expect("create games dir");
115            modde_core::paths::set_data_dir(data_dir.clone());
116            std::mem::forget(tempdir);
117            data_dir
118        })
119    }
120
121    #[test]
122    fn stellar_blade_resolves_community_optiscaler_profile() {
123        let _ = shared_data_dir();
124        let profiles = resolve_optiscaler_profiles("stellar-blade");
125
126        assert_eq!(profiles.len(), 1);
127        assert_eq!(profiles[0].id, "community-dxgi");
128        assert_eq!(profiles[0].proxy_dll, "dxgi.dll");
129        assert_eq!(profiles[0].tested_optiscaler_version, "0.9");
130        assert_eq!(profiles[0].source_mode, Some("github_release"));
131        assert_eq!(profiles[0].goverlay_channel, None);
132        assert!(profiles[0].enable_optipatcher);
133        assert_eq!(profiles[0].fsr4_variant, Some("latest_fp8"));
134        assert!(profiles[0].emulate_fp8);
135        assert!(!profiles[0].spoof_dlss);
136        assert_eq!(
137            default_optiscaler_profile("stellar-blade").map(|profile| profile.id),
138            Some("community-dxgi")
139        );
140    }
141
142    #[test]
143    fn unsupported_optiscaler_profiles_resolve_empty() {
144        let _ = shared_data_dir();
145        assert!(resolve_optiscaler_profiles("skyrim-se").is_empty());
146        assert!(default_optiscaler_profile("skyrim-se").is_none());
147    }
148
149    #[test]
150    fn missing_sidecar_returns_built_in_unchanged() {
151        let _ = shared_data_dir();
152        let first = resolve_optiscaler_profiles("stellar-blade");
153        let second = resolve_optiscaler_profiles("stellar-blade");
154
155        assert_eq!(first.len(), 1);
156        assert!(std::ptr::eq(first, second));
157    }
158}