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