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;