Skip to main content

compatdb_core/
rule.rs

1//! A rule: the processes it matches and the settings it applies to them.
2
3use crate::{
4    Argument, D3d9, DebugChannel, DllOverride, Dxgi, EnvEntry, Exe, Fingerprint, OpenGl, Scalar,
5    ScalarValue, Specificity, VersionInfo, Wined3d,
6};
7#[cfg(doc)]
8use crate::{Origin, Resolution, Source, Table, builtin, metal_hud_env};
9
10/// One rule of the table: what it matches, and the settings it applies to a
11/// process that matches. A rule has no name; `exe` is its only required
12/// matcher, and everything else is optional.
13///
14/// [`Rule::new`] makes one for an `exe`, and the `with_` methods add what it
15/// matches on and sets. A rule is its content alone: where it comes from is
16/// the [`Source`] the [`Table`] keeps beside it.
17#[derive(Clone, Debug, PartialEq, Eq)]
18pub struct Rule {
19    /// The executable it matches.
20    pub(crate) exe: Exe,
21    /// Substring the image's version `CompanyName` must contain.
22    pub(crate) company: Option<Fingerprint>,
23    /// Substring the image's version `ProductName` must contain.
24    pub(crate) product: Option<Fingerprint>,
25    /// Substring the image's version `OriginalFilename` must contain.
26    pub(crate) original_filename: Option<Fingerprint>,
27    /// See [`Rule::dxgi`].
28    pub(crate) dxgi: Setting<Dxgi>,
29    /// See [`Rule::d3d9`].
30    pub(crate) d3d9: Setting<D3d9>,
31    /// See [`Rule::wined3d`].
32    pub(crate) wined3d: Setting<Wined3d>,
33    /// See [`Rule::opengl`].
34    pub(crate) opengl: Setting<OpenGl>,
35    /// See [`Rule::dpi_aware`].
36    pub(crate) dpi_aware: Setting<bool>,
37    /// See [`Rule::large_address_aware`].
38    pub(crate) large_address_aware: Setting<bool>,
39    /// See [`Rule::gcmouse`].
40    pub(crate) gcmouse: Setting<bool>,
41    /// See [`Rule::force_nx`].
42    pub(crate) force_nx: Setting<bool>,
43    /// See [`Rule::metal_hud`].
44    pub(crate) metal_hud: Setting<bool>,
45    /// See [`Rule::dll_overrides`].
46    pub(crate) dll_overrides: Vec<DllOverride>,
47    /// See [`Rule::arguments`].
48    pub(crate) arguments: Vec<Argument>,
49    /// See [`Rule::arguments_override`].
50    pub(crate) arguments_override: Option<Vec<Argument>>,
51    /// See [`Rule::debug_channels`].
52    pub(crate) debug_channels: Vec<DebugChannel>,
53    /// See [`Rule::env`].
54    pub(crate) env: Vec<EnvEntry>,
55}
56
57/// The value of a scalar setting that resets it (see [`Setting::Reset`]),
58/// and of a `dll_overrides` entry that removes the overrides of its modules.
59///
60/// Like every word of the format it is matched ASCII-case-insensitively and
61/// written in lowercase.
62pub const DEFAULT_VALUE: &str = "default";
63
64/// What a rule says about a setting that holds one value.
65#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
66pub enum Setting<T> {
67    /// Nothing: a rule folded earlier decides, and with none upstream Wine.
68    #[default]
69    Unset,
70    /// This value, which beats that of every rule folded earlier.
71    Set(T),
72    /// `default`: from this rule on the setting is unset again, so upstream
73    /// Wine decides unless a rule folded later sets it.
74    Reset,
75}
76
77impl<T> Setting<T> {
78    /// The value it sets, or `None` when it is unset or reset.
79    #[must_use]
80    pub fn value(self) -> Option<T> {
81        match self {
82            Self::Set(value) => Some(value),
83            Self::Unset | Self::Reset => None,
84        }
85    }
86
87    /// Whether it says nothing.
88    #[must_use]
89    pub const fn is_unset(&self) -> bool {
90        matches!(self, Self::Unset)
91    }
92
93    /// The same setting with the value, if any, passed through `f`.
94    #[must_use]
95    pub fn map<U>(self, f: impl FnOnce(T) -> U) -> Setting<U> {
96        match self {
97            Self::Unset => Setting::Unset,
98            Self::Set(value) => Setting::Set(f(value)),
99            Self::Reset => Setting::Reset,
100        }
101    }
102}
103
104impl<T> From<T> for Setting<T> {
105    /// The setting that sets `value`.
106    fn from(value: T) -> Self {
107        Self::Set(value)
108    }
109}
110
111impl Default for Rule {
112    /// The rule for every process that sets nothing, [`Rule::new`] of
113    /// [`Exe::Any`].
114    fn default() -> Self {
115        Self::new(Exe::Any)
116    }
117}
118
119impl Rule {
120    /// The rule for the processes `exe` matches, with no fingerprint and no
121    /// setting.
122    #[must_use]
123    pub const fn new(exe: Exe) -> Self {
124        Self {
125            exe,
126            company: None,
127            product: None,
128            original_filename: None,
129            dxgi: Setting::Unset,
130            d3d9: Setting::Unset,
131            wined3d: Setting::Unset,
132            opengl: Setting::Unset,
133            dpi_aware: Setting::Unset,
134            large_address_aware: Setting::Unset,
135            gcmouse: Setting::Unset,
136            force_nx: Setting::Unset,
137            metal_hud: Setting::Unset,
138            dll_overrides: Vec::new(),
139            arguments: Vec::new(),
140            arguments_override: None,
141            debug_channels: Vec::new(),
142            env: Vec::new(),
143        }
144    }
145
146    /// The rule that also pins `company` on the image's `CompanyName`.
147    #[must_use]
148    pub fn with_company(mut self, company: Fingerprint) -> Self {
149        self.company = Some(company);
150        self
151    }
152
153    /// The rule that also pins `product` on the image's `ProductName`.
154    #[must_use]
155    pub fn with_product(mut self, product: Fingerprint) -> Self {
156        self.product = Some(product);
157        self
158    }
159
160    /// The rule that also pins `original_filename` on the image's
161    /// `OriginalFilename`.
162    #[must_use]
163    pub fn with_original_filename(mut self, original_filename: Fingerprint) -> Self {
164        self.original_filename = Some(original_filename);
165        self
166    }
167
168    /// The rule with `dxgi` for [`Rule::dxgi`].
169    #[must_use]
170    pub fn with_dxgi(mut self, dxgi: impl Into<Setting<Dxgi>>) -> Self {
171        self.dxgi = dxgi.into();
172        self
173    }
174
175    /// The rule with `d3d9` for [`Rule::d3d9`].
176    #[must_use]
177    pub fn with_d3d9(mut self, d3d9: impl Into<Setting<D3d9>>) -> Self {
178        self.d3d9 = d3d9.into();
179        self
180    }
181
182    /// The rule with `wined3d` for [`Rule::wined3d`].
183    #[must_use]
184    pub fn with_wined3d(mut self, wined3d: impl Into<Setting<Wined3d>>) -> Self {
185        self.wined3d = wined3d.into();
186        self
187    }
188
189    /// The rule with `opengl` for [`Rule::opengl`].
190    #[must_use]
191    pub fn with_opengl(mut self, opengl: impl Into<Setting<OpenGl>>) -> Self {
192        self.opengl = opengl.into();
193        self
194    }
195
196    /// The rule with `aware` for [`Rule::dpi_aware`].
197    #[must_use]
198    pub fn with_dpi_aware(mut self, aware: impl Into<Setting<bool>>) -> Self {
199        self.dpi_aware = aware.into();
200        self
201    }
202
203    /// The rule with `laa` for [`Rule::large_address_aware`].
204    #[must_use]
205    pub fn with_large_address_aware(mut self, laa: impl Into<Setting<bool>>) -> Self {
206        self.large_address_aware = laa.into();
207        self
208    }
209
210    /// The rule with `on` for [`Rule::gcmouse`].
211    #[must_use]
212    pub fn with_gcmouse(mut self, on: impl Into<Setting<bool>>) -> Self {
213        self.gcmouse = on.into();
214        self
215    }
216
217    /// The rule with `on` for [`Rule::force_nx`].
218    #[must_use]
219    pub fn with_force_nx(mut self, on: impl Into<Setting<bool>>) -> Self {
220        self.force_nx = on.into();
221        self
222    }
223
224    /// The rule with `on` for [`Rule::metal_hud`].
225    #[must_use]
226    pub fn with_metal_hud(mut self, on: impl Into<Setting<bool>>) -> Self {
227        self.metal_hud = on.into();
228        self
229    }
230
231    /// The rule with `entry` after its other [`Rule::debug_channels`].
232    #[must_use]
233    pub fn with_debug_channel(mut self, entry: DebugChannel) -> Self {
234        self.debug_channels.push(entry);
235        self
236    }
237
238    /// The rule with `entry` after its other [`Rule::dll_overrides`].
239    #[must_use]
240    pub fn with_dll_override(mut self, entry: DllOverride) -> Self {
241        self.dll_overrides.push(entry);
242        self
243    }
244
245    /// The rule with `argument` after its other [`Rule::arguments`].
246    #[must_use]
247    pub fn with_argument(mut self, argument: Argument) -> Self {
248        self.arguments.push(argument);
249        self
250    }
251
252    /// The rule whose [`Rule::arguments_override`] is `arguments`, which
253    /// may be empty to clear the arguments.
254    #[must_use]
255    pub fn with_arguments_override(
256        mut self,
257        arguments: impl IntoIterator<Item = Argument>,
258    ) -> Self {
259        self.arguments_override = Some(arguments.into_iter().collect());
260        self
261    }
262
263    /// The rule with `entry` after its other [`Rule::env`] entries.
264    #[must_use]
265    pub fn with_env(mut self, entry: EnvEntry) -> Self {
266        self.env.push(entry);
267        self
268    }
269
270    /// The executable the rule matches.
271    #[must_use]
272    pub const fn exe(&self) -> &Exe {
273        &self.exe
274    }
275
276    /// The substring the image's version `CompanyName` must contain, if the
277    /// rule pins one.
278    #[must_use]
279    pub const fn company(&self) -> Option<&Fingerprint> {
280        self.company.as_ref()
281    }
282
283    /// The substring the image's version `ProductName` must contain, if the
284    /// rule pins one.
285    #[must_use]
286    pub const fn product(&self) -> Option<&Fingerprint> {
287        self.product.as_ref()
288    }
289
290    /// The substring the image's version `OriginalFilename` must contain, if
291    /// the rule pins one.
292    #[must_use]
293    pub const fn original_filename(&self) -> Option<&Fingerprint> {
294        self.original_filename.as_ref()
295    }
296
297    /// The DXGI-family implementation to load, for D3D10, D3D10.1, D3D11
298    /// and D3D12 (see [`Dxgi`]): `compatdb.so` prepends its tree,
299    /// `lib/wine/dxgi/<name>`, to ntdll's builtin DLL search path when the
300    /// process loads. Only [`Dxgi::Dxmt`] has D3D12 (vkd3d-proton): under
301    /// [`Dxgi::Wined3d`] `d3d12.dll` does not load, so a game that only has
302    /// D3D12 does not start.
303    ///
304    /// With no rule setting it, the process gets wined3d, as in upstream
305    /// Wine. [`builtin::dxgi_dxmt`] sets it for every process.
306    #[must_use]
307    pub const fn dxgi(&self) -> Setting<Dxgi> {
308        self.dxgi
309    }
310
311    /// The D3D9 implementation to load (see [`D3d9`]): `compatdb.so`
312    /// prepends its tree, `lib/wine/d3d9/<name>`, to ntdll's builtin DLL
313    /// search path when the process loads.
314    ///
315    /// With no rule setting it, the process gets wined3d, as in upstream
316    /// Wine. [`builtin::d3d9_mtld3d`] sets it for every process.
317    #[must_use]
318    pub const fn d3d9(&self) -> Setting<D3d9> {
319        self.d3d9
320    }
321
322    /// The renderer of wined3d, for every wined3d user in the process (see
323    /// [`Wined3d`]): the wined3d trees of [`Rule::dxgi`] and [`Rule::d3d9`],
324    /// D3D8 and `DirectDraw` share one wined3d, which reads its settings
325    /// once. It chooses no tree.
326    ///
327    /// Set, `compatdb.so` names the renderer in `WINE_D3D_CONFIG`, after
328    /// the rules' own `env` entries: the options a rule's `env` gives the
329    /// variable are kept and its `renderer` is replaced (see
330    /// [`Resolution::fold_derived_env`]), and a value the process was
331    /// started with is replaced as by any `env` entry. wined3d reads the
332    /// variable from the environment block, and it takes precedence over
333    /// wined3d's registry settings.
334    ///
335    /// With no rule setting it, `compatdb.so` sets nothing: a
336    /// `WINE_D3D_CONFIG` from a rule's `env` or inherited from the process
337    /// that started this one applies as it is, and without one wined3d's
338    /// registry settings or its default, OpenGL.
339    #[must_use]
340    pub const fn wined3d(&self) -> Setting<Wined3d> {
341        self.wined3d
342    }
343
344    /// Which OpenGL the Mac driver gives the process (see [`OpenGl`]).
345    /// winemac.drv reads the value through ntdll when the process first
346    /// uses OpenGL. [`OpenGl::Egl`] does not fall back: where Zink has no
347    /// GPU, as on a Mac without a Metal 4 GPU, the process gets no OpenGL.
348    ///
349    /// With no rule setting it, the Mac driver gives Apple's OpenGL, as
350    /// upstream Wine does. [`builtin::opengl_egl`] sets it for every
351    /// process. The value is the process's own: the programs it starts get
352    /// the value of their own rules.
353    #[must_use]
354    pub const fn opengl(&self) -> Setting<OpenGl> {
355        self.opengl
356    }
357
358    /// Whether the process is DPI-aware (`true`, system-aware) or unaware
359    /// (`false`). win32u applies the value before the `AppCompatFlags`
360    /// `Layers` registry value and the manifest, and the first setting
361    /// wins, so the rule beats both. `true` also caps a program that asks
362    /// for per-monitor awareness at system-aware.
363    ///
364    /// With no rule setting it, Wine decides: the registry value, then the
365    /// manifest, and otherwise unaware. So `default` differs from `false`
366    /// here. [`builtin::dpi_aware`] sets it for every process.
367    #[must_use]
368    pub const fn dpi_aware(&self) -> Setting<bool> {
369        self.dpi_aware
370    }
371
372    /// Whether a 32-bit process gets the 4 GB address space whatever its
373    /// executable's `IMAGE_FILE_LARGE_ADDRESS_AWARE` flag says. ntdll
374    /// applies it after `compatdb.so` returns. `false` does not take the
375    /// space away from an executable that has the flag; it only stops
376    /// forcing it, so it is the same as unset. Has no effect on a 64-bit
377    /// process.
378    ///
379    /// With no rule setting it, the executable's flag decides, as in
380    /// upstream Wine.
381    #[must_use]
382    pub const fn large_address_aware(&self) -> Setting<bool> {
383        self.large_address_aware
384    }
385
386    /// Whether the Mac driver takes the mouse movement of raw input
387    /// (`WM_INPUT`) and `DirectInput` from GameController's `GCMouse`, in the
388    /// device's own counts, instead of from the cursor's movement, which macOS
389    /// accelerates and scales for retina. winemac.drv reads the value
390    /// through ntdll when it reads its settings.
391    ///
392    /// With no rule setting it, the Mac driver keeps upstream Wine's
393    /// behaviour, the cursor's movement, which `false` gives too.
394    /// [`builtin::gcmouse`] sets it for every process.
395    #[must_use]
396    pub const fn gcmouse(&self) -> Setting<bool> {
397        self.gcmouse
398    }
399
400    /// Whether no-execute stays permanently on in a 32-bit process, even when
401    /// its executable or a DLL lacks the `NX_COMPAT` flag, for which Wine
402    /// turns it off, and whether the process cannot turn it off. ntdll reads
403    /// the value the first time the process asks for or changes its execute
404    /// flags. Has no effect on a 64-bit process, which always has no-execute
405    /// on.
406    ///
407    /// `false` is the same as unset: Wine's behaviour. [`builtin::force_nx`]
408    /// sets it for every process. Unlike an `env` entry, the value is the
409    /// process's own: the processes it starts get their own rules' value.
410    #[must_use]
411    pub const fn force_nx(&self) -> Setting<bool> {
412        self.force_nx
413    }
414
415    /// Whether the Metal performance HUD shows in the process. `compatdb.so`
416    /// turns the value into the environment entries of [`metal_hud_env`],
417    /// before this rule's own `env` entries (see
418    /// [`Resolution::fold_derived_env`]), so an `env` entry for one of the
419    /// variables wins: `true` sets the variables that show the HUD, `false`
420    /// removes them. Like [`Rule::wined3d`] and unlike the other scalar
421    /// settings it reaches the programs the process starts, which inherit
422    /// the variables. With no rule setting it, the process keeps the
423    /// variables it was started with.
424    #[must_use]
425    pub const fn metal_hud(&self) -> Setting<bool> {
426        self.metal_hud
427    }
428
429    /// `WINEDEBUG` entries for the processes the rule names. Wine reads
430    /// `WINEDEBUG` before `compatdb.so` loads, so the library only logs
431    /// them; a launcher puts them into the variable it starts Wine with
432    /// (see [`Table::winedebug`]).
433    #[must_use]
434    pub fn debug_channels(&self) -> &[DebugChannel] {
435        &self.debug_channels
436    }
437
438    /// `WINEDLLOVERRIDES` elements, which `compatdb.so` adds through ntdll's
439    /// load-order hook. ntdll consults them before the registry
440    /// `DllOverrides` keys, and one for a module replaces the
441    /// `WINEDLLOVERRIDES` entry for it, so neither can undo them.
442    ///
443    /// An entry whose order is `default` sets no load order: it removes the
444    /// entries that the rules folded before it gave its modules, so the
445    /// registry and `WINEDLLOVERRIDES` decide for them again (see
446    /// [`DllOverride::is_default`]). Module names match
447    /// ASCII-case-insensitively, with or without `.dll`.
448    ///
449    /// With no entry, `WINEDLLOVERRIDES` and the registry decide, as in
450    /// upstream Wine. [`builtin::no_mono_or_gecko`] adds entries for every
451    /// process.
452    #[must_use]
453    pub fn dll_overrides(&self) -> &[DllOverride] {
454        &self.dll_overrides
455    }
456
457    /// Text appended to the command line when the process loads, each
458    /// entry unless the command line already holds it. The test is a
459    /// case-sensitive substring test, which leaves a Chromium `--type=`
460    /// child that inherited its parent's switches alone.
461    #[must_use]
462    pub fn arguments(&self) -> &[Argument] {
463        &self.arguments
464    }
465
466    /// When set, the arguments that replace the list the rules folded before
467    /// this one accumulated (see [`Table::resolve`]); `Some` with no entries
468    /// clears it. This rule's own [`Rule::arguments`] and those of the rules
469    /// folded after it still append.
470    #[must_use]
471    pub fn arguments_override(&self) -> Option<&[Argument]> {
472        self.arguments_override.as_deref()
473    }
474
475    /// Environment entries. The library sets them in the process's
476    /// environment block and unix environment when it loads, before any PE
477    /// code runs, and the programs the process starts inherit them. The
478    /// entries of a built-in rule that matches every process
479    /// (`exe` `*`, no fingerprint) are defaults: such an entry sets its
480    /// variable only where the process does not have it, so a value the
481    /// caller exported wins, an empty one included (see
482    /// [`Origin::env_is_default`]). Every other entry replaces the
483    /// variable, or removes it when it has no value.
484    #[must_use]
485    pub fn env(&self) -> &[EnvEntry] {
486        &self.env
487    }
488
489    /// Whether the rule sets nothing beyond its matchers: no scalar setting
490    /// is set or reset and every list is empty, `arguments_override`
491    /// included. Such a rule changes nothing in any process, so a launcher
492    /// can leave it out of the table, for example the rule for every process
493    /// that its launch-wide settings make when they set nothing.
494    #[must_use]
495    pub fn sets_nothing(&self) -> bool {
496        Scalar::ALL
497            .iter()
498            .all(|&scalar| self.scalar(scalar).is_unset())
499            && self.dll_overrides.is_empty()
500            && self.arguments.is_empty()
501            && self.arguments_override.is_none()
502            && self.debug_channels.is_empty()
503            && self.env.is_empty()
504    }
505
506    /// What this rule says about the scalar setting `scalar`.
507    #[must_use]
508    pub fn scalar(&self, scalar: Scalar) -> Setting<ScalarValue> {
509        match scalar {
510            Scalar::Dxgi => self.dxgi.map(ScalarValue::Dxgi),
511            Scalar::D3d9 => self.d3d9.map(ScalarValue::D3d9),
512            Scalar::Wined3d => self.wined3d.map(ScalarValue::Wined3d),
513            Scalar::OpenGl => self.opengl.map(ScalarValue::OpenGl),
514            Scalar::DpiAware => self.dpi_aware.map(ScalarValue::Bool),
515            Scalar::LargeAddressAware => self.large_address_aware.map(ScalarValue::Bool),
516            Scalar::Gcmouse => self.gcmouse.map(ScalarValue::Bool),
517            Scalar::ForceNx => self.force_nx.map(ScalarValue::Bool),
518            Scalar::MetalHud => self.metal_hud.map(ScalarValue::Bool),
519        }
520    }
521
522    /// Make this rule say `setting` about the scalar setting `scalar`. A
523    /// value of another setting leaves the rule as it is.
524    pub(crate) fn put_scalar(&mut self, scalar: Scalar, setting: Setting<ScalarValue>) {
525        /// Put `setting` into `slot`, its value taken out by `pick`, which
526        /// gives `None` for a value of another setting.
527        fn put<T>(
528            slot: &mut Setting<T>,
529            setting: Setting<ScalarValue>,
530            pick: impl FnOnce(ScalarValue) -> Option<T>,
531        ) {
532            *slot = match setting {
533                Setting::Unset => Setting::Unset,
534                Setting::Reset => Setting::Reset,
535                Setting::Set(value) => match pick(value) {
536                    Some(value) => Setting::Set(value),
537                    None => return,
538                },
539            };
540        }
541        let on = |value| match value {
542            ScalarValue::Bool(on) => Some(on),
543            _ => None,
544        };
545        match scalar {
546            Scalar::Dxgi => put(&mut self.dxgi, setting, |value| match value {
547                ScalarValue::Dxgi(dxgi) => Some(dxgi),
548                _ => None,
549            }),
550            Scalar::D3d9 => put(&mut self.d3d9, setting, |value| match value {
551                ScalarValue::D3d9(d3d9) => Some(d3d9),
552                _ => None,
553            }),
554            Scalar::Wined3d => put(&mut self.wined3d, setting, |value| match value {
555                ScalarValue::Wined3d(wined3d) => Some(wined3d),
556                _ => None,
557            }),
558            Scalar::OpenGl => put(&mut self.opengl, setting, |value| match value {
559                ScalarValue::OpenGl(opengl) => Some(opengl),
560                _ => None,
561            }),
562            Scalar::DpiAware => put(&mut self.dpi_aware, setting, on),
563            Scalar::LargeAddressAware => put(&mut self.large_address_aware, setting, on),
564            Scalar::Gcmouse => put(&mut self.gcmouse, setting, on),
565            Scalar::ForceNx => put(&mut self.force_nx, setting, on),
566            Scalar::MetalHud => put(&mut self.metal_hud, setting, on),
567        }
568    }
569
570    /// Whether this rule applies to a process with the given image basename
571    /// and version resource. `exe` matches the basename case-insensitively, or
572    /// every basename when it is [`Exe::Any`]; a fingerprint field, when
573    /// present, is an additional case-insensitive substring constraint on the
574    /// corresponding version string (so a rule with none matches on the
575    /// basename alone).
576    #[must_use]
577    pub fn matches(&self, exe: &str, version: &VersionInfo) -> bool {
578        let pin = |pin: Option<&Fingerprint>, actual: &str| pin.is_none_or(|p| p.matches(actual));
579        self.exe.matches(exe)
580            && pin(self.company.as_ref(), &version.company)
581            && pin(self.product.as_ref(), &version.product)
582            && pin(self.original_filename.as_ref(), &version.original_filename)
583    }
584
585    /// How specific this rule is: a wildcard rule (a launch-wide default),
586    /// one naming an executable, each with or without a version
587    /// fingerprint. [`Table::resolve`] folds a wildcard rule before one
588    /// naming an executable whatever their source, and within each the
589    /// built-in rules before the others, a rule without a fingerprint before
590    /// one with.
591    #[must_use]
592    pub const fn specificity(&self) -> Specificity {
593        let fingerprinted =
594            self.company.is_some() || self.product.is_some() || self.original_filename.is_some();
595        match (self.exe.is_any(), fingerprinted) {
596            (true, false) => Specificity::Any,
597            (true, true) => Specificity::AnyWithFingerprint,
598            (false, false) => Specificity::Exe,
599            (false, true) => Specificity::ExeWithFingerprint,
600        }
601    }
602
603    /// Whether this rule matches on its executable alone. Such a rule catches
604    /// every program with that file name, which is worth a warning for all but
605    /// the few basenames that are unique in practice.
606    #[must_use]
607    pub const fn is_unfingerprinted(&self) -> bool {
608        matches!(self.specificity(), Specificity::Exe)
609    }
610
611    /// Whether two rules would match exactly the same processes, which can only
612    /// be a mistake in a hand-written table.
613    #[must_use]
614    pub fn matches_the_same_as(&self, other: &Self) -> bool {
615        self.exe.as_str().eq_ignore_ascii_case(other.exe.as_str())
616            && self.company == other.company
617            && self.product == other.product
618            && self.original_filename == other.original_filename
619    }
620
621    /// The matchers of this rule as the log prints them: the `exe`, then the
622    /// fingerprint values it pins in parentheses, for example
623    /// `WoW.exe (Blizzard Entertainment, World of Warcraft, WoW.exe)`.
624    #[must_use]
625    pub fn matchers(&self) -> String {
626        let pins: Vec<&str> = [&self.company, &self.product, &self.original_filename]
627            .into_iter()
628            .filter_map(|pin| pin.as_ref().map(Fingerprint::as_str))
629            .collect();
630        if pins.is_empty() {
631            self.exe.to_string()
632        } else {
633            format!("{} ({})", self.exe, pins.join(", "))
634        }
635    }
636}
637
638#[cfg(test)]
639mod tests;