Skip to main content

compatdb_core/
settings.rs

1//! The settings layer: how a launcher fills a [`Rule`] from its own
2//! configuration, key by key and value by value as text, without knowing
3//! any key itself.
4//!
5//! A launcher reads the name of each setting with [`Key::from_setting`],
6//! branches on [`Key::kind`] and hands the value to [`Rule::set`] (one value
7//! of a matcher or a scalar, or one entry of a list), [`Rule::set_list`] (a
8//! whole list) or [`Rule::set_env`] (one environment entry). Every error is
9//! a [`FieldError`] whose `Display` is complete enough to print as it is,
10//! and whose [`FieldError::severity`] says whether the launcher warns and
11//! goes on or refuses the settings.
12//!
13//! The policy for settings that older launchers wrote lives here too: the
14//! retired keys, values and environment variables, and the refusal of
15//! `arguments_override` in a rule for every process
16//! ([`Key::allowed_on_any`]). So does what a launcher needs to check its
17//! configuration before it builds a rule: which keys a later layer of it
18//! replaces ([`Key::replaces_on_merge`]) and which environment names a rule
19//! refuses ([`check_env_name`]).
20
21use crate::{
22    Argument, DEFAULT_VALUE, DebugChannel, DllOverride, EnvEntry, EnvName, Exe, FieldError,
23    Fingerprint, Key, KeyKind, Rule, Scalar, ScalarValue, Setting, Severity,
24};
25
26/// The keys that are no longer read, each with why and what to do instead
27/// (the clause that follows the key in the message) and how a launcher
28/// treats it.
29const RETIRED_KEYS: &[(&str, &str, Severity)] = &[
30    (
31        "x87_sidecar",
32        "is no longer read: this Wine runs x87 code in FEX and has no x87 sidecar; remove it",
33        Severity::Warning,
34    ),
35    (
36        "cmdline_append",
37        "is no longer read; use arguments",
38        Severity::Error,
39    ),
40];
41
42/// The values that are no longer read, each with its key, why and what to
43/// do instead (the clause that follows the value in the message) and how a
44/// launcher treats it.
45const RETIRED_VALUES: &[(Key, &str, &str, Severity)] = &[(
46    Key::Dxgi,
47    "gptk",
48    "asks for Apple's D3DMetal, which this Wine does not have, so it is ignored as if it were \
49     not there: without another dxgi value the built-in rules decide, which give DXMT unless \
50     they pin the program to another tree; remove it",
51    Severity::Warning,
52)];
53
54/// The environment variables that a setting replaced, each with the key of
55/// that setting.
56const RETIRED_ENV: &[(&str, Key)] = &[
57    ("WINEDEBUG", Key::DebugChannels),
58    ("WINEDLLOVERRIDES", Key::DllOverrides),
59    ("WINE_CMDLINE_APPEND", Key::Arguments),
60    ("WINE_MAC_OPENGL", Key::OpenGl),
61    ("WINE_FORCE_NX", Key::ForceNx),
62];
63
64/// The setting names that are no longer read, each with how
65/// [`Key::from_setting`] treats it: the names it refuses with
66/// [`FieldError::RetiredKey`] and that severity.
67pub fn retired_keys() -> impl Iterator<Item = (&'static str, Severity)> {
68    RETIRED_KEYS
69        .iter()
70        .map(|&(name, _, severity)| (name, severity))
71}
72
73/// The values that are no longer read, each with its key and how
74/// [`Rule::set`] treats it: the values it refuses with
75/// [`FieldError::RetiredValue`] and that severity.
76pub fn retired_values() -> impl Iterator<Item = (Key, &'static str, Severity)> {
77    RETIRED_VALUES
78        .iter()
79        .map(|&(key, value, _, severity)| (key, value, severity))
80}
81
82/// The environment variables that a setting replaced, each with the key of
83/// that setting: the names for which [`retired_env`] gives that key and
84/// [`check_env_name`] refuses.
85pub fn retired_envs() -> impl Iterator<Item = (&'static str, Key)> {
86    RETIRED_ENV.iter().copied()
87}
88
89/// The setting that replaced the environment variable `name`, if one did.
90///
91/// The name is compared ASCII-case-insensitively: `compatdb.so` merges a
92/// rule's env entries into the Windows environment block by name without
93/// regard to case, so an entry for `WineDllOverrides` would replace the
94/// `WINEDLLOVERRIDES` there. [`Rule::set_env`] refuses such a variable; a
95/// launcher can check the environment it sets up itself the same way.
96///
97/// `WINEDEBUG` is among them: Wine reads it before `compatdb.so` loads, so a
98/// rule cannot set it, and `debug_channels` reaches it through
99/// [`Table::winedebug`](crate::Table::winedebug).
100#[must_use]
101pub fn retired_env(name: &str) -> Option<Key> {
102    RETIRED_ENV
103        .iter()
104        .find(|(retired, _)| retired.eq_ignore_ascii_case(name))
105        .map(|&(_, key)| key)
106}
107
108/// Check `name` as the name of a rule's environment entry, with the errors
109/// [`Rule::set_env`] gives for it.
110///
111/// A launcher runs it on the names of its own configuration, such as an
112/// `[env]` table, before it builds a rule from them, or on the environment
113/// it starts Wine with.
114///
115/// # Errors
116///
117/// [`FieldError::RetiredEnv`] for a variable that a setting replaced (see
118/// [`retired_env`]), and the errors of [`EnvName::new`]:
119/// [`FieldError::InvalidEnvName`] for an empty name or one with a `=`,
120/// [`FieldError::Nul`] for one with a NUL.
121pub fn check_env_name(name: &str) -> Result<(), FieldError> {
122    env_name(name)?;
123    Ok(())
124}
125
126/// `name` as the name of a rule's environment entry, or the error of
127/// [`check_env_name`].
128fn env_name(name: &str) -> Result<EnvName, FieldError> {
129    if let Some(key) = retired_env(name) {
130        return Err(FieldError::RetiredEnv {
131            name: name.to_owned(),
132            key,
133        });
134    }
135    EnvName::new(name)
136}
137
138/// The variables a launcher removes from the environment it starts Wine
139/// with, when it inherited them, so that the launch does not depend on
140/// where the launcher itself was started from.
141///
142/// - `WINEDLLOVERRIDES`: ntdll reads it for every module, so an inherited
143///   value would change the load order of the modules the rules'
144///   `dll_overrides` leave alone, and a rule's `mscoree,mshtml=default`
145///   would hand Mono and Gecko to it.
146/// - `WINE_LARGE_ADDRESS_AWARE`: it gives every 32-bit process the 4 GB
147///   address space, which `large_address_aware` decides per process.
148///
149/// `WINE_LARGE_ADDRESS_AWARE` is not retired, so a launcher may still set
150/// it for the whole launch on purpose. `WINEDLLOVERRIDES` is (see
151/// [`retired_env`]): a rule's `dll_overrides` does its job.
152#[must_use]
153pub const fn inherited_conflicts() -> &'static [&'static str] {
154    &["WINEDLLOVERRIDES", "WINE_LARGE_ADDRESS_AWARE"]
155}
156
157impl Key {
158    /// The key a launcher's setting called `name` names, compared
159    /// ASCII-case-insensitively as [`str::parse`] compares it, which this
160    /// is but for the keys that are no longer read.
161    ///
162    /// `dxgi` and `DXGI` therefore name the same key: a launcher that hands
163    /// both to [`Rule::set`] gets the value of the last one for a matcher or
164    /// a scalar, and the entries of both for a list.
165    ///
166    /// # Errors
167    ///
168    /// [`FieldError::RetiredKey`] for a key that is no longer read, with
169    /// its severity: `x87_sidecar` is a warning, since it asks for nothing
170    /// this Wine could still do, and `cmdline_append`, which `arguments`
171    /// replaced, an error. [`FieldError::UnknownKey`] for any other name
172    /// that is no key.
173    pub fn from_setting(name: &str) -> Result<Self, FieldError> {
174        if let Some(&(_, reason, severity)) = RETIRED_KEYS
175            .iter()
176            .find(|(retired, ..)| retired.eq_ignore_ascii_case(name))
177        {
178            return Err(FieldError::RetiredKey {
179                key: name.to_owned(),
180                reason,
181                severity,
182            });
183        }
184        name.parse()
185    }
186
187    /// Whether a rule for every process, one whose `exe` is `*`
188    /// ([`Exe::Any`]) with or without a fingerprint, may hold this setting.
189    ///
190    /// It may hold every scalar setting, every list and `env`, but not
191    /// `arguments_override`: for every process that would take away the
192    /// arguments the built-in rules give single programs. A matcher is not a
193    /// setting, so this is `false` for one: `exe` decides whether the rule
194    /// is for every process at all, and a fingerprint narrows the processes
195    /// it matches, so neither belongs among the settings a launcher offers
196    /// for every process.
197    ///
198    /// [`Rule::set`] and [`Rule::set_list`] refuse a setting for which this
199    /// is `false` in a `*` rule, and an `exe` of `*` in a rule that holds
200    /// one, with [`FieldError::ArgumentsOverrideOnAny`].
201    #[must_use]
202    pub const fn allowed_on_any(self) -> bool {
203        match self.kind() {
204            KeyKind::Matcher => false,
205            KeyKind::Scalar(_) | KeyKind::List | KeyKind::Env => {
206                !matches!(self, Self::ArgumentsOverride)
207            }
208        }
209    }
210
211    /// Whether a later layer's value for this key takes the place of an
212    /// earlier layer's, rather than adding to it, when a launcher builds one
213    /// rule from several layers of its configuration, such as a file of
214    /// defaults and a file per game laid over it.
215    ///
216    /// - A matcher or a scalar setting holds one value, so the later layer's
217    ///   replaces it: `true`.
218    /// - A list adds the later layer's entries after the earlier layer's:
219    ///   `false`.
220    /// - `arguments_override` is the exception among the lists: `true`. A
221    ///   rule has one override, the whole list that replaces the arguments
222    ///   of the rules folded before it (see [`Rule::arguments_override`]).
223    ///   The entries of two layers added together would be an override
224    ///   neither layer wrote, and an empty list in the later layer has to
225    ///   clear an earlier layer's override, as `arguments_override=` alone
226    ///   clears the arguments on the wire.
227    /// - `env` adds the later layer's entries: `false`. The entries fold in
228    ///   order, so a later entry for the same variable wins.
229    ///
230    /// This is only about layers of one rule. Between rules nothing
231    /// replaces: every rule that matches a process applies, in the order
232    /// [`Table::resolve`](crate::Table::resolve) folds them.
233    #[must_use]
234    pub const fn replaces_on_merge(self) -> bool {
235        match self.kind() {
236            KeyKind::Matcher | KeyKind::Scalar(_) => true,
237            KeyKind::List => matches!(self, Self::ArgumentsOverride),
238            KeyKind::Env => false,
239        }
240    }
241}
242
243/// Whether a `*` rule refuses the field `key`: a setting that
244/// [`Key::allowed_on_any`] does not allow. A matcher is not a setting, and a
245/// `*` rule takes the fingerprints.
246const fn refused_on_any(key: Key) -> bool {
247    !matches!(key.kind(), KeyKind::Matcher) && !key.allowed_on_any()
248}
249
250impl Rule {
251    /// Set the field `key` from `value`, as a launcher's setting gives it:
252    /// the value of a matcher or a scalar setting, or one more entry of a
253    /// list. For a scalar, `default` in any case resets the setting (see
254    /// [`Setting::Reset`]); for `env`, `value` is `NAME=value`, as for
255    /// [`Rule::set_env`]. A `debug_channels` value is split at each `,` into
256    /// entries of their own, as `WINEDEBUG` reads it, so that
257    /// [`Table::winedebug`](crate::Table::winedebug) scopes each of them to
258    /// the rule's `exe`; an empty entry, `+relay,` included, is refused.
259    ///
260    /// On an error the rule is left as it was.
261    ///
262    /// # Errors
263    ///
264    /// The [`FieldError`] of a value the key does not take: a word the
265    /// setting does not know, a part that is empty or holds a NUL, and the
266    /// errors of [`Rule::set_env`]. A retired value is
267    /// [`FieldError::RetiredValue`]: `dxgi` `gptk`, a warning, since the
268    /// built-in rules then decide as if there were no value. A setting that
269    /// [`Key::allowed_on_any`] does not allow, `arguments_override`, in a
270    /// rule whose `exe` is `*`, or an `exe` of `*` in a rule that holds one,
271    /// is [`FieldError::ArgumentsOverrideOnAny`]: for every process it would
272    /// take away the arguments the built-in rules give single programs.
273    /// Make the rule with its `exe` first.
274    pub fn set(&mut self, key: Key, value: &str) -> Result<(), FieldError> {
275        self.refuse_on_any(key)?;
276        if let Some(scalar) = key.scalar() {
277            let setting = scalar_setting(scalar, value)?;
278            self.put_scalar(scalar, setting);
279            return Ok(());
280        }
281        match key {
282            Key::Exe => {
283                let exe = Exe::new(value)?;
284                if exe.is_any()
285                    && Key::ALL
286                        .iter()
287                        .any(|&key| refused_on_any(key) && self.holds(key))
288                {
289                    return Err(FieldError::ArgumentsOverrideOnAny);
290                }
291                self.exe = exe;
292            }
293            Key::Company => self.company = Some(Fingerprint::new(value)?),
294            Key::Product => self.product = Some(Fingerprint::new(value)?),
295            Key::OriginalFilename => self.original_filename = Some(Fingerprint::new(value)?),
296            Key::DllOverrides => self.dll_overrides.push(DllOverride::new(value)?),
297            Key::Arguments => self.arguments.push(Argument::new(value)?),
298            Key::ArgumentsOverride => {
299                let argument = Argument::new(value)?;
300                self.arguments_override
301                    .get_or_insert_default()
302                    .push(argument);
303            }
304            Key::DebugChannels => {
305                let channels = value
306                    .split(',')
307                    .map(DebugChannel::new)
308                    .collect::<Result<Vec<_>, _>>()?;
309                self.debug_channels.extend(channels);
310            }
311            Key::Env => {
312                let (name, value) = value.split_once('=').unwrap_or((value, ""));
313                self.set_env(name, value)?;
314            }
315            // The scalar settings returned above.
316            Key::Dxgi
317            | Key::D3d9
318            | Key::Wined3d
319            | Key::OpenGl
320            | Key::DpiAware
321            | Key::LargeAddressAware
322            | Key::Gcmouse
323            | Key::ForceNx
324            | Key::MetalHud => {}
325        }
326        Ok(())
327    }
328
329    /// Make the list `key` hold `values`, each read as [`Rule::set`] reads
330    /// one entry, in place of the entries it had. For `arguments_override`
331    /// that sets the override, and no values clear the arguments of the
332    /// rules folded before (see [`Rule::arguments_override`]). For `env`
333    /// each value is `NAME=value`.
334    ///
335    /// On an error the rule is left as it was.
336    ///
337    /// # Errors
338    ///
339    /// [`FieldError::NotAList`] for a key that holds one value,
340    /// [`FieldError::ArgumentsOverrideOnAny`] as for [`Rule::set`], even
341    /// with no values, and the first error [`Rule::set`] gives for one of
342    /// the values.
343    pub fn set_list<T: AsRef<str>>(
344        &mut self,
345        key: Key,
346        values: impl IntoIterator<Item = T>,
347    ) -> Result<(), FieldError> {
348        let mut rule = self.clone();
349        match key {
350            Key::DllOverrides => rule.dll_overrides.clear(),
351            Key::Arguments => rule.arguments.clear(),
352            Key::DebugChannels => rule.debug_channels.clear(),
353            Key::Env => rule.env.clear(),
354            Key::ArgumentsOverride => rule.arguments_override = Some(Vec::new()),
355            _ => return Err(FieldError::NotAList(key)),
356        }
357        self.refuse_on_any(key)?;
358        for value in values {
359            rule.set(key, value.as_ref())?;
360        }
361        *self = rule;
362        Ok(())
363    }
364
365    /// Add the environment entry that sets `name` to `value` after the
366    /// rule's other entries; an empty `value` removes the variable.
367    ///
368    /// # Errors
369    ///
370    /// The errors of [`check_env_name`] for `name`, and of [`EnvEntry::new`]
371    /// for `value`.
372    pub fn set_env(&mut self, name: &str, value: &str) -> Result<(), FieldError> {
373        self.refuse_on_any(Key::Env)?;
374        let entry = EnvEntry::new(env_name(name)?, value)?;
375        self.env.push(entry);
376        Ok(())
377    }
378
379    /// [`FieldError::ArgumentsOverrideOnAny`] when the rule matches every
380    /// process by its `exe` and refuses the setting `key` there (see
381    /// [`Key::allowed_on_any`]).
382    const fn refuse_on_any(&self, key: Key) -> Result<(), FieldError> {
383        if self.exe.is_any() && refused_on_any(key) {
384            Err(FieldError::ArgumentsOverrideOnAny)
385        } else {
386            Ok(())
387        }
388    }
389
390    /// Whether the rule holds the setting `key`: a scalar setting set or
391    /// reset, a list or `env` with an entry, an `arguments_override` with or
392    /// without entries. `false` for a matcher, which is not a setting.
393    fn holds(&self, key: Key) -> bool {
394        match key {
395            Key::DllOverrides => !self.dll_overrides.is_empty(),
396            Key::ArgumentsOverride => self.arguments_override.is_some(),
397            Key::Arguments => !self.arguments.is_empty(),
398            Key::DebugChannels => !self.debug_channels.is_empty(),
399            Key::Env => !self.env.is_empty(),
400            Key::Exe | Key::Company | Key::Product | Key::OriginalFilename => false,
401            Key::Dxgi
402            | Key::D3d9
403            | Key::Wined3d
404            | Key::OpenGl
405            | Key::DpiAware
406            | Key::LargeAddressAware
407            | Key::Gcmouse
408            | Key::ForceNx
409            | Key::MetalHud => key
410                .scalar()
411                .is_some_and(|scalar| !self.scalar(scalar).is_unset()),
412        }
413    }
414}
415
416/// What `text` says about the scalar setting `scalar`: `default`, in any
417/// case, resets it, and anything else is a value of it, or a retired one.
418fn scalar_setting(scalar: Scalar, text: &str) -> Result<Setting<ScalarValue>, FieldError> {
419    if text.eq_ignore_ascii_case(DEFAULT_VALUE) {
420        return Ok(Setting::Reset);
421    }
422    if let Some(&(key, _, reason, severity)) = RETIRED_VALUES
423        .iter()
424        .find(|(key, value, ..)| *key == scalar.key() && value.eq_ignore_ascii_case(text))
425    {
426        return Err(FieldError::RetiredValue {
427            key,
428            value: text.to_owned(),
429            reason,
430            severity,
431        });
432    }
433    scalar.parse_value(text).map(Setting::Set)
434}
435
436#[cfg(test)]
437mod tests;