Skip to main content

compatdb_core/
key.rs

1//! The keys of a rule's fields and the values of its scalar settings.
2//!
3//! The case rule: every word the format itself defines (a key, a value of
4//! `dxgi`, `d3d9`, `wined3d` or `opengl`, `true`, `false` and `default`) is
5//! matched ASCII-case-insensitively and always written in lowercase. User
6//! data (the `exe`, the fingerprint values, env names and values, arguments
7//! and the text of a DLL override) is kept byte for byte.
8
9use std::{fmt, str::FromStr};
10
11use crate::{D3d9, DEFAULT_VALUE, Dxgi, FieldError, OpenGl, Wined3d};
12#[cfg(doc)]
13use crate::{Exe, Rule, Setting, Table, metal_hud_env};
14
15/// The one-line summary of each key in plain text: the first paragraph of
16/// the key's variant doc, and what [`Key::summary`] returns.
17macro_rules! summary {
18    (Exe) => {
19        "The image basename a rule matches, in any case, * for every process, or steam:440 for \
20         that Steam app."
21    };
22    (Company) => {
23        "A text the company name of the image's version resource must contain."
24    };
25    (Product) => {
26        "A text the product name of the image's version resource must contain."
27    };
28    (OriginalFilename) => {
29        "A text the original file name of the image's version resource must contain."
30    };
31    (Dxgi) => {
32        "The implementation of D3D10 to D3D12 the process loads."
33    };
34    (D3d9) => {
35        "The D3D9 implementation the process loads."
36    };
37    (Wined3d) => {
38        "The renderer wined3d draws with in the process."
39    };
40    (OpenGl) => {
41        "Which OpenGL the Mac driver gives the process."
42    };
43    (DpiAware) => {
44        "Whether the process is DPI-aware."
45    };
46    (LargeAddressAware) => {
47        "Whether a 32-bit process gets the 4 GB address space whatever its executable's flag \
48         says."
49    };
50    (Gcmouse) => {
51        "Whether raw mouse movement comes from GameController instead of the cursor."
52    };
53    (ForceNx) => {
54        "Whether no-execute stays on in a 32-bit process."
55    };
56    (MetalHud) => {
57        "Whether the Metal performance HUD shows."
58    };
59    (DllOverrides) => {
60        "DLL load-order overrides, one WINEDLLOVERRIDES element each."
61    };
62    (ArgumentsOverride) => {
63        "Arguments that replace those the rules folded before give the command line."
64    };
65    (Arguments) => {
66        "Text appended to the command line, one argument string each."
67    };
68    (DebugChannels) => {
69        "WINEDEBUG channels for the process, one entry each."
70    };
71    (Env) => {
72        "Environment variables the process gets; an empty value removes the variable."
73    };
74}
75
76/// The key of a field of a rule, which is also the name of the field on the
77/// wire.
78///
79/// Each variant says what the field does and what a process gets without
80/// it; the getter of [`Rule`] it links to has the details. The text
81/// `compatdb.so` logs for an unset setting is [`Key::upstream`].
82#[non_exhaustive]
83#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
84pub enum Key {
85    #[doc = summary!(Exe)]
86    ///
87    /// `exe`, see [`Exe`]. Every rule has one.
88    Exe,
89    #[doc = summary!(Company)]
90    ///
91    /// `company`, a substring of the version string `CompanyName`, compared
92    /// ASCII-case-insensitively (see [`Rule::company`]). Without it, any
93    /// company matches.
94    Company,
95    #[doc = summary!(Product)]
96    ///
97    /// `product`, a substring of the version string `ProductName`, compared
98    /// ASCII-case-insensitively (see [`Rule::product`]). Without it, any
99    /// product matches.
100    Product,
101    #[doc = summary!(OriginalFilename)]
102    ///
103    /// `original_filename`, a substring of the version string
104    /// `OriginalFilename`, compared ASCII-case-insensitively (see
105    /// [`Rule::original_filename`]). Without it, any original filename
106    /// matches.
107    OriginalFilename,
108    #[doc = summary!(Dxgi)]
109    ///
110    /// `dxgi`, a [`Dxgi`] (see [`Rule::dxgi`]). Unset: wined3d, as in
111    /// upstream Wine.
112    Dxgi,
113    #[doc = summary!(D3d9)]
114    ///
115    /// `d3d9`, a [`D3d9`] (see [`Rule::d3d9`]). Unset: wined3d, as in
116    /// upstream Wine.
117    D3d9,
118    #[doc = summary!(Wined3d)]
119    ///
120    /// `wined3d`, a [`Wined3d`] for the whole process (see
121    /// [`Rule::wined3d`]). Unset: `WINE_D3D_CONFIG` or wined3d's registry
122    /// settings decide, and without them OpenGL.
123    Wined3d,
124    #[doc = summary!(OpenGl)]
125    ///
126    /// `opengl`, an [`OpenGl`] (see [`Rule::opengl`]). Unset: Apple's
127    /// OpenGL, as in upstream Wine.
128    OpenGl,
129    #[doc = summary!(DpiAware)]
130    ///
131    /// `dpi_aware` (see [`Rule::dpi_aware`]). Unset: the registry and the
132    /// manifest decide, and without them the process is unaware.
133    DpiAware,
134    #[doc = summary!(LargeAddressAware)]
135    ///
136    /// `large_address_aware` (see [`Rule::large_address_aware`]). Unset:
137    /// the flag decides.
138    LargeAddressAware,
139    #[doc = summary!(Gcmouse)]
140    ///
141    /// `gcmouse` (see [`Rule::gcmouse`]). Unset: from the cursor, as in
142    /// upstream Wine.
143    Gcmouse,
144    #[doc = summary!(ForceNx)]
145    ///
146    /// `force_nx` (see [`Rule::force_nx`]). Unset: Wine turns it off for an
147    /// image without `NX_COMPAT`.
148    ForceNx,
149    #[doc = summary!(MetalHud)]
150    ///
151    /// `metal_hud`, through the variables of [`metal_hud_env`] (see
152    /// [`Rule::metal_hud`]). Unset: the process keeps the variables it was
153    /// started with.
154    MetalHud,
155    #[doc = summary!(DllOverrides)]
156    ///
157    /// `dll_overrides`, repeatable (see [`Rule::dll_overrides`]). None:
158    /// `WINEDLLOVERRIDES` and the registry decide.
159    DllOverrides,
160    #[doc = summary!(ArgumentsOverride)]
161    ///
162    /// `arguments_override`, repeatable (see [`Rule::arguments_override`]).
163    /// None: the arguments of every rule append.
164    ArgumentsOverride,
165    #[doc = summary!(Arguments)]
166    ///
167    /// `arguments`, repeatable (see [`Rule::arguments`]). None: the command
168    /// line stays as it was.
169    Arguments,
170    #[doc = summary!(DebugChannels)]
171    ///
172    /// `debug_channels`, repeatable (see [`Rule::debug_channels`]). Wine
173    /// reads `WINEDEBUG` before `compatdb.so` loads, so the library only
174    /// logs them; a launcher puts them into the variable (see
175    /// [`Table::winedebug`]). None: the `WINEDEBUG` the process was started
176    /// with.
177    DebugChannels,
178    #[doc = summary!(Env)]
179    ///
180    /// `env`, one `NAME=value` entry each, repeatable (see [`Rule::env`]).
181    /// None: the environment the process was started with.
182    Env,
183}
184
185/// What kind of field a [`Key`] names.
186#[non_exhaustive]
187#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
188pub enum KeyKind {
189    /// A matcher: which processes the rule applies to.
190    Matcher,
191    /// A setting that holds one value of the given kind, or `default`.
192    Scalar(ValueKind),
193    /// A setting that holds a list of texts, one per field.
194    List,
195    /// The environment entries, `NAME=value` each.
196    Env,
197}
198
199/// The values a scalar setting takes.
200#[non_exhaustive]
201#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
202pub enum ValueKind {
203    /// One word of a fixed set, which [`Key::values`] lists.
204    Word,
205    /// `true` or `false`.
206    Bool,
207}
208
209impl Key {
210    /// Every key, in the order a record writes its fields.
211    pub const ALL: &'static [Self] = &[
212        Self::Exe,
213        Self::Company,
214        Self::Product,
215        Self::OriginalFilename,
216        Self::Dxgi,
217        Self::D3d9,
218        Self::Wined3d,
219        Self::OpenGl,
220        Self::DpiAware,
221        Self::LargeAddressAware,
222        Self::Gcmouse,
223        Self::ForceNx,
224        Self::MetalHud,
225        Self::DllOverrides,
226        Self::ArgumentsOverride,
227        Self::Arguments,
228        Self::DebugChannels,
229        Self::Env,
230    ];
231
232    /// The key as the wire writes it, in lowercase.
233    #[must_use]
234    pub const fn as_str(self) -> &'static str {
235        match self {
236            Self::Exe => "exe",
237            Self::Company => "company",
238            Self::Product => "product",
239            Self::OriginalFilename => "original_filename",
240            Self::Dxgi => "dxgi",
241            Self::D3d9 => "d3d9",
242            Self::Wined3d => "wined3d",
243            Self::OpenGl => "opengl",
244            Self::DpiAware => "dpi_aware",
245            Self::LargeAddressAware => "large_address_aware",
246            Self::Gcmouse => "gcmouse",
247            Self::ForceNx => "force_nx",
248            Self::MetalHud => "metal_hud",
249            Self::DllOverrides => "dll_overrides",
250            Self::ArgumentsOverride => "arguments_override",
251            Self::Arguments => "arguments",
252            Self::DebugChannels => "debug_channels",
253            Self::Env => "env",
254        }
255    }
256
257    /// The scalar setting this key names, or `None` for a matcher or a
258    /// list.
259    #[must_use]
260    pub const fn scalar(self) -> Option<Scalar> {
261        match self {
262            Self::Dxgi => Some(Scalar::Dxgi),
263            Self::D3d9 => Some(Scalar::D3d9),
264            Self::Wined3d => Some(Scalar::Wined3d),
265            Self::OpenGl => Some(Scalar::OpenGl),
266            Self::DpiAware => Some(Scalar::DpiAware),
267            Self::LargeAddressAware => Some(Scalar::LargeAddressAware),
268            Self::Gcmouse => Some(Scalar::Gcmouse),
269            Self::ForceNx => Some(Scalar::ForceNx),
270            Self::MetalHud => Some(Scalar::MetalHud),
271            Self::Exe
272            | Self::Company
273            | Self::Product
274            | Self::OriginalFilename
275            | Self::DllOverrides
276            | Self::ArgumentsOverride
277            | Self::Arguments
278            | Self::DebugChannels
279            | Self::Env => None,
280        }
281    }
282
283    /// What kind of field this key names.
284    #[must_use]
285    pub const fn kind(self) -> KeyKind {
286        if let Some(scalar) = self.scalar() {
287            return KeyKind::Scalar(scalar.value_kind());
288        }
289        match self {
290            Self::DllOverrides
291            | Self::ArgumentsOverride
292            | Self::Arguments
293            | Self::DebugChannels => KeyKind::List,
294            Self::Env => KeyKind::Env,
295            _ => KeyKind::Matcher,
296        }
297    }
298
299    /// How a launcher names the field to a person, for example
300    /// `DPI-aware`.
301    #[must_use]
302    pub const fn label(self) -> &'static str {
303        match self {
304            Self::Exe => "Executable",
305            Self::Company => "Company",
306            Self::Product => "Product",
307            Self::OriginalFilename => "Original filename",
308            Self::Dxgi => "DXGI",
309            Self::D3d9 => "D3D9",
310            Self::Wined3d => "wined3d renderer",
311            Self::OpenGl => "OpenGL",
312            Self::DpiAware => "DPI-aware",
313            Self::LargeAddressAware => "Large-address-aware",
314            Self::Gcmouse => "GameController mouse",
315            Self::ForceNx => "Force no-execute",
316            Self::MetalHud => "Metal HUD",
317            Self::DllOverrides => "DLL overrides",
318            Self::ArgumentsOverride => "Arguments override",
319            Self::Arguments => "Arguments",
320            Self::DebugChannels => "Debug channels",
321            Self::Env => "Environment",
322        }
323    }
324
325    /// What upstream Wine does for this setting when no rule sets it, as the
326    /// log of `compatdb.so` prints it, or `None` for a matcher.
327    /// `arguments_override` is a way of setting the arguments, so it shares
328    /// their text.
329    #[must_use]
330    pub const fn upstream(self) -> Option<&'static str> {
331        if let Some(scalar) = self.scalar() {
332            return Some(scalar.upstream());
333        }
334        match self {
335            Self::DllOverrides => Some("WINEDLLOVERRIDES and the registry's DllOverrides decide"),
336            Self::ArgumentsOverride | Self::Arguments => {
337                Some("the command line the process was started with")
338            }
339            Self::DebugChannels => Some("the WINEDEBUG the process was started with"),
340            Self::Env => Some("the environment the process was started with"),
341            _ => None,
342        }
343    }
344
345    /// The spellings the value of a scalar setting can have, in lowercase,
346    /// `default` included; empty for any other key, whose values are user
347    /// data.
348    #[must_use]
349    pub const fn values(self) -> &'static [&'static str] {
350        match self {
351            Self::Dxgi => &["dxmt", "wined3d", DEFAULT_VALUE],
352            Self::D3d9 => &["mtld3d", "wined3d", DEFAULT_VALUE],
353            Self::Wined3d => &["gl", "vulkan", DEFAULT_VALUE],
354            Self::OpenGl => &["egl", "cgl", DEFAULT_VALUE],
355            Self::DpiAware
356            | Self::LargeAddressAware
357            | Self::Gcmouse
358            | Self::ForceNx
359            | Self::MetalHud => &["true", "false", DEFAULT_VALUE],
360            Self::Exe
361            | Self::Company
362            | Self::Product
363            | Self::OriginalFilename
364            | Self::DllOverrides
365            | Self::ArgumentsOverride
366            | Self::Arguments
367            | Self::DebugChannels
368            | Self::Env => &[],
369        }
370    }
371
372    /// What the field does, in one line of plain text without markup, such
373    /// as a launcher's template puts above the setting. It is the first
374    /// paragraph of the key's doc, which [`Key::docs_url`] links to.
375    #[must_use]
376    pub const fn summary(self) -> &'static str {
377        match self {
378            Self::Exe => summary!(Exe),
379            Self::Company => summary!(Company),
380            Self::Product => summary!(Product),
381            Self::OriginalFilename => summary!(OriginalFilename),
382            Self::Dxgi => summary!(Dxgi),
383            Self::D3d9 => summary!(D3d9),
384            Self::Wined3d => summary!(Wined3d),
385            Self::OpenGl => summary!(OpenGl),
386            Self::DpiAware => summary!(DpiAware),
387            Self::LargeAddressAware => summary!(LargeAddressAware),
388            Self::Gcmouse => summary!(Gcmouse),
389            Self::ForceNx => summary!(ForceNx),
390            Self::MetalHud => summary!(MetalHud),
391            Self::DllOverrides => summary!(DllOverrides),
392            Self::ArgumentsOverride => summary!(ArgumentsOverride),
393            Self::Arguments => summary!(Arguments),
394            Self::DebugChannels => summary!(DebugChannels),
395            Self::Env => summary!(Env),
396        }
397    }
398
399    /// A value a launcher's template can show for the field, as
400    /// [`Rule::set`] takes it: a matcher's value, one of [`Key::values`]
401    /// other than `default` for a scalar setting, one entry for a list, and
402    /// `NAME=value` for `env`. A launcher that writes `env` as a table of
403    /// names splits it at the first `=`.
404    ///
405    /// The `exe` is a program's, not `*`, since `arguments_override` needs
406    /// one; every example is a value a rule for that program takes.
407    #[must_use]
408    pub const fn example(self) -> &'static str {
409        match self {
410            Self::Exe | Self::OriginalFilename => "Game.exe",
411            Self::Company => "Some Vendor",
412            Self::Product => "Some Game",
413            Self::Dxgi | Self::D3d9 => "wined3d",
414            Self::Wined3d => "vulkan",
415            Self::OpenGl => "cgl",
416            Self::DpiAware | Self::Gcmouse | Self::ForceNx => "false",
417            Self::LargeAddressAware | Self::MetalHud => "true",
418            Self::DllOverrides => "d3dcompiler_47=n",
419            Self::ArgumentsOverride | Self::Arguments => "-windowed",
420            Self::DebugChannels => "+relay",
421            Self::Env => "DXMT_CONFIG=d3d11.preferredMaxFrameRate=60",
422        }
423    }
424}
425
426impl FromStr for Key {
427    type Err = FieldError;
428
429    /// The key spelled `s`, compared ASCII-case-insensitively.
430    fn from_str(s: &str) -> Result<Self, Self::Err> {
431        Self::ALL
432            .iter()
433            .copied()
434            .find(|key| key.as_str().eq_ignore_ascii_case(s))
435            .ok_or_else(|| FieldError::UnknownKey(s.to_owned()))
436    }
437}
438
439impl fmt::Display for Key {
440    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
441        f.write_str(self.as_str())
442    }
443}
444
445/// A setting that holds one value (see [`KeyKind::Scalar`]), or is reset
446/// with `default` (see [`Setting::Reset`]).
447///
448/// Each variant is the setting of its [`Key`], whose doc says what it does
449/// and what a process gets without it.
450#[non_exhaustive]
451#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
452pub enum Scalar {
453    /// [`Key::Dxgi`]: the implementation of D3D10 to D3D12. Unset:
454    /// wined3d.
455    Dxgi,
456    /// [`Key::D3d9`]: the D3D9 implementation. Unset: wined3d.
457    D3d9,
458    /// [`Key::Wined3d`]: the renderer of wined3d. Unset: what
459    /// `WINE_D3D_CONFIG` or the registry says, otherwise OpenGL.
460    Wined3d,
461    /// [`Key::OpenGl`]: the Mac driver's OpenGL. Unset: Apple's.
462    OpenGl,
463    /// [`Key::DpiAware`]: DPI awareness. Unset: the registry and the
464    /// manifest decide.
465    DpiAware,
466    /// [`Key::LargeAddressAware`]: 4 GB for a 32-bit process. Unset: the
467    /// executable's flag decides.
468    LargeAddressAware,
469    /// [`Key::Gcmouse`]: raw mouse movement from GameController. Unset:
470    /// from the cursor.
471    Gcmouse,
472    /// [`Key::ForceNx`]: no-execute kept on in a 32-bit process. Unset:
473    /// Wine's behaviour.
474    ForceNx,
475    /// [`Key::MetalHud`]: the Metal performance HUD. Unset: the inherited
476    /// variables decide.
477    MetalHud,
478}
479
480impl Scalar {
481    /// Every scalar setting, in the order a record writes them.
482    pub const ALL: &'static [Self] = &[
483        Self::Dxgi,
484        Self::D3d9,
485        Self::Wined3d,
486        Self::OpenGl,
487        Self::DpiAware,
488        Self::LargeAddressAware,
489        Self::Gcmouse,
490        Self::ForceNx,
491        Self::MetalHud,
492    ];
493
494    /// The key of this setting.
495    #[must_use]
496    pub const fn key(self) -> Key {
497        match self {
498            Self::Dxgi => Key::Dxgi,
499            Self::D3d9 => Key::D3d9,
500            Self::Wined3d => Key::Wined3d,
501            Self::OpenGl => Key::OpenGl,
502            Self::DpiAware => Key::DpiAware,
503            Self::LargeAddressAware => Key::LargeAddressAware,
504            Self::Gcmouse => Key::Gcmouse,
505            Self::ForceNx => Key::ForceNx,
506            Self::MetalHud => Key::MetalHud,
507        }
508    }
509
510    /// The key of this setting as the wire writes it.
511    #[must_use]
512    pub const fn as_str(self) -> &'static str {
513        self.key().as_str()
514    }
515
516    /// The kind of value this setting holds.
517    #[must_use]
518    pub const fn value_kind(self) -> ValueKind {
519        match self {
520            Self::Dxgi | Self::D3d9 | Self::Wined3d | Self::OpenGl => ValueKind::Word,
521            Self::DpiAware
522            | Self::LargeAddressAware
523            | Self::Gcmouse
524            | Self::ForceNx
525            | Self::MetalHud => ValueKind::Bool,
526        }
527    }
528
529    /// What upstream Wine does for this setting when no rule sets it (see
530    /// [`Key::upstream`]).
531    #[must_use]
532    pub const fn upstream(self) -> &'static str {
533        match self {
534            Self::Dxgi | Self::D3d9 => "wined3d",
535            Self::Wined3d => {
536                "the OpenGL renderer, unless WINE_D3D_CONFIG or wined3d's registry settings name \
537                 another"
538            }
539            Self::OpenGl => "Apple's OpenGL",
540            Self::DpiAware => "the AppCompatFlags layer, then the manifest, otherwise unaware",
541            Self::LargeAddressAware => "the executable's large-address-aware flag",
542            Self::Gcmouse => "off, the cursor's movement",
543            Self::ForceNx => "no-execute off when the executable or a DLL lacks the NX_COMPAT flag",
544            Self::MetalHud => {
545                "the MTL_HUD_ variables of the environment the process was started with"
546            }
547        }
548    }
549
550    /// Read `text` as a value of this setting, ASCII-case-insensitively.
551    /// `default` is not a value: it resets the setting (see
552    /// [`Setting::Reset`]).
553    ///
554    /// # Errors
555    ///
556    /// [`FieldError::BadValue`] when the setting does not take `text`. Its
557    /// `expected` is [`Key::values`], `default` included, since it lists
558    /// what a field of the key takes.
559    pub fn parse_value(self, text: &str) -> Result<ScalarValue, FieldError> {
560        let value = match self {
561            Self::Dxgi => text.parse().ok().map(ScalarValue::Dxgi),
562            Self::D3d9 => text.parse().ok().map(ScalarValue::D3d9),
563            Self::Wined3d => text.parse().ok().map(ScalarValue::Wined3d),
564            Self::OpenGl => text.parse().ok().map(ScalarValue::OpenGl),
565            Self::DpiAware
566            | Self::LargeAddressAware
567            | Self::Gcmouse
568            | Self::ForceNx
569            | Self::MetalHud => parse_bool(text).map(ScalarValue::Bool),
570        };
571        value.ok_or_else(|| FieldError::BadValue {
572            key: self.key(),
573            value: text.to_owned(),
574            expected: self.key().values(),
575        })
576    }
577}
578
579impl fmt::Display for Scalar {
580    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
581        f.write_str(self.as_str())
582    }
583}
584
585/// The value of a scalar setting.
586#[non_exhaustive]
587#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
588pub enum ScalarValue {
589    /// A value of [`Scalar::Dxgi`].
590    Dxgi(Dxgi),
591    /// A value of [`Scalar::D3d9`].
592    D3d9(D3d9),
593    /// A value of [`Scalar::Wined3d`].
594    Wined3d(Wined3d),
595    /// A value of [`Scalar::OpenGl`].
596    OpenGl(OpenGl),
597    /// A value of one of the true-or-false settings.
598    Bool(bool),
599}
600
601impl ScalarValue {
602    /// The value as the wire writes it, in lowercase.
603    #[must_use]
604    pub const fn as_str(self) -> &'static str {
605        match self {
606            Self::Dxgi(value) => value.as_str(),
607            Self::D3d9(value) => value.as_str(),
608            Self::Wined3d(value) => value.as_str(),
609            Self::OpenGl(value) => value.as_str(),
610            Self::Bool(true) => "true",
611            Self::Bool(false) => "false",
612        }
613    }
614}
615
616impl fmt::Display for ScalarValue {
617    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
618        f.write_str(self.as_str())
619    }
620}
621
622/// `true` or `false`, ASCII-case-insensitively.
623const fn parse_bool(text: &str) -> Option<bool> {
624    if text.eq_ignore_ascii_case("true") {
625        Some(true)
626    } else if text.eq_ignore_ascii_case("false") {
627        Some(false)
628    } else {
629        None
630    }
631}
632
633#[cfg(test)]
634mod tests;