compatdb_core/resolve.rs
1//! Folding the rules that match a process into the settings it gets, with
2//! where each value came from.
3
4use std::collections::BTreeMap;
5
6#[cfg(doc)]
7use crate::runtime::{basename_of, steam_app};
8use crate::{
9 Argument, D3d9, DebugChannel, DllOverride, Dxgi, ENV_VAR, EnvEntry, FILE_ENV_VAR, OpenGl,
10 PROF_SERVICE_VAR, Rule, Scalar, ScalarValue, Setting, Source, Specificity, Table, VersionInfo,
11 Wined3d, metal_hud_env, runtime,
12};
13
14/// FEX's option for the extended debug info of [`Resolution::fold_profiler_env`].
15const FEX_DEBUG_INFO_VAR: &str = "FEX_EXTENDEDDEBUGINFO";
16
17/// The settings that apply to one process.
18///
19/// Every matching rule folded in the order [`Table::resolve`] describes, so a
20/// scalar holds the value of the last rule that set it and the lists
21/// accumulate. Also says where each value came from, for the log.
22#[derive(Clone, Default, Debug, PartialEq, Eq)]
23pub struct Resolution {
24 /// The state of each scalar setting a rule set or reset; one that is
25 /// missing is unset.
26 scalars: BTreeMap<Scalar, ScalarState>,
27 /// The `dll_overrides` entries of every matching rule in fold order,
28 /// without the `default` entries, which only take modules out of the
29 /// entries before them.
30 pub dll_overrides: Vec<DllOverride>,
31 /// Where each entry of [`Resolution::dll_overrides`] came from, at the
32 /// same index.
33 pub dll_overrides_from: Vec<Origin>,
34 /// Each module a `<module>=default` entry named, in fold order, with
35 /// what that did to the entries before it.
36 pub dll_overrides_reset: Vec<DllOverrideReset>,
37 /// The texts to append to the command line, in fold order, after any
38 /// `arguments_override` replaced what came before it.
39 pub arguments: Vec<Argument>,
40 /// Where each entry of [`Resolution::arguments`] came from, at the same
41 /// index.
42 pub arguments_from: Vec<Origin>,
43 /// Each `arguments_override` that dropped entries from the argument
44 /// list, in fold order.
45 pub arguments_dropped: Vec<ArgumentsDropped>,
46 /// The rule of each `arguments_override` that was ignored, in fold order:
47 /// that of a rule for a Steam app id (see [`Rule::arguments_override`]).
48 pub arguments_override_ignored: Vec<Origin>,
49 /// The `debug_channels` entries of every matching rule in fold order,
50 /// which `compatdb.so` only logs (see [`Rule::debug_channels`]).
51 pub debug_channels: Vec<DebugChannel>,
52 /// Where each entry of [`Resolution::debug_channels`] came from, at the
53 /// same index.
54 pub debug_channels_from: Vec<Origin>,
55 /// The entry of [`Resolution::fold_profiler_env`], when it ran, then the
56 /// `env` entries of every matching rule in fold order, followed by those
57 /// [`Resolution::fold_derived_env`] adds.
58 /// [`Resolution::env_steps`] says which of them apply in a process.
59 pub env: Vec<EnvEntry>,
60 /// Where each entry of [`Resolution::env`] came from, at the same index.
61 pub env_from: Vec<EnvFrom>,
62 /// Whether [`Resolution::fold_derived_env`] has run.
63 derived_env: bool,
64 /// Where in [`Resolution::env`] the rule that set `metal_hud` started
65 /// its entries, which is where [`Resolution::fold_derived_env`] puts
66 /// the entries of the setting.
67 metal_hud_at: Option<usize>,
68}
69
70/// Where a scalar setting of a [`Resolution`] ended up.
71#[non_exhaustive]
72#[derive(Clone, Default, Debug, PartialEq, Eq)]
73pub struct ScalarState {
74 /// The value of the last rule folded that set it, or `None` when no rule
75 /// set it or a later rule reset it.
76 pub value: Option<ScalarValue>,
77 /// The rule of that value; `Some` exactly when `value` is.
78 pub set_by: Option<Origin>,
79 /// The rule that reset the setting to `default` when no rule folded
80 /// after it set it again; never `Some` together with `value`.
81 pub reset_by: Option<Origin>,
82}
83
84/// The state of a setting no rule set or reset.
85static UNSET: ScalarState = ScalarState {
86 value: None,
87 set_by: None,
88 reset_by: None,
89};
90
91/// What a `<module>=default` entry of `dll_overrides` did.
92#[derive(Clone, Debug, PartialEq, Eq)]
93pub struct DllOverrideReset {
94 /// The module it names.
95 pub module: String,
96 /// The rule of the entry.
97 pub by: Origin,
98 /// Where each entry it took the module out of came from; empty when no
99 /// rule folded before it had given the module a load order.
100 pub removed: Vec<Origin>,
101}
102
103/// Where a value of a [`Resolution`] came from: the rule that set it, by its
104/// [`Source`] and, for a built-in rule that does not match every process,
105/// its matchers, or a profiler.
106#[derive(Clone, Debug, PartialEq, Eq)]
107pub enum Origin {
108 /// A built-in rule that matches every process.
109 BuiltIn,
110 /// A built-in rule for the processes its matchers (see
111 /// [`Rule::matchers`]) describe.
112 BuiltInFor(String),
113 /// Line `n` of [`ENV_VAR`].
114 Line(usize),
115 /// Line `n` of the file [`FILE_ENV_VAR`] names.
116 FileLine(usize),
117 /// [`PROF_SERVICE_VAR`], see [`Resolution::fold_profiler_env`].
118 Profiler,
119}
120
121impl Origin {
122 /// Where the values of `rule` come from, a rule of a table with the
123 /// source `source`.
124 #[must_use]
125 pub fn of(source: Source, rule: &Rule) -> Self {
126 match source {
127 Source::Line(n) => Self::Line(n),
128 Source::FileLine(n) => Self::FileLine(n),
129 Source::BuiltIn if rule.specificity() == Specificity::Any => Self::BuiltIn,
130 Source::BuiltIn => Self::BuiltInFor(rule.matchers()),
131 }
132 }
133
134 /// Whether the `env` entries of a rule from here are defaults, which
135 /// leave a variable the process already has alone: those of a built-in
136 /// rule that matches every process (see [`Rule::env`]).
137 #[must_use]
138 pub const fn env_is_default(&self) -> bool {
139 matches!(self, Self::BuiltIn)
140 }
141
142 /// How the log names it: `built-in`, `built-in for <matchers>`,
143 /// `WINE_COMPATDB line <n>`, `WINE_COMPATDB_FILE line <n>` or
144 /// `WINE_PROF_SERVICE`.
145 #[must_use]
146 pub fn label(&self) -> String {
147 match self {
148 Self::BuiltIn => String::from("built-in"),
149 Self::BuiltInFor(matchers) => format!("built-in for {matchers}"),
150 Self::Line(n) => format!("{ENV_VAR} line {n}"),
151 Self::FileLine(n) => format!("{FILE_ENV_VAR} line {n}"),
152 Self::Profiler => String::from(PROF_SERVICE_VAR),
153 }
154 }
155}
156
157/// An `arguments_override` that dropped the arguments the rules folded
158/// before it had accumulated.
159#[derive(Clone, Debug, PartialEq, Eq)]
160pub struct ArgumentsDropped {
161 /// The rule whose `arguments_override` it is.
162 pub by: Origin,
163 /// The arguments it dropped, each with where it came from.
164 pub entries: Vec<(Argument, Origin)>,
165}
166
167/// Where an env entry of a [`Resolution`] came from.
168#[derive(Clone, Debug, PartialEq, Eq)]
169pub struct EnvFrom {
170 /// The rule that set it, or [`Origin::Profiler`].
171 pub origin: Origin,
172 /// Whether it is a default (see [`Origin::env_is_default`]), which leaves
173 /// a variable the process already has alone.
174 pub default: bool,
175}
176
177/// What one env entry of a [`Resolution`] does in a process.
178#[derive(Clone, Debug, PartialEq, Eq)]
179pub struct EnvStep {
180 /// The entry: the variable and the value it sets, or none to remove it.
181 pub entry: EnvEntry,
182 /// The rule that set it, or [`Origin::Profiler`].
183 pub origin: Origin,
184 /// False for a default whose variable the process already had, which it
185 /// leaves alone.
186 pub applied: bool,
187 /// The origin of a later entry for the same variable that applies, which
188 /// replaces what this one did: the variable ends up with that entry's
189 /// value.
190 pub replaced_by: Option<Origin>,
191 /// For an entry that removes the variable, the origin of the earlier
192 /// entry for it that applied and set a value, which the removal undoes.
193 pub removes: Option<Origin>,
194}
195
196impl Resolution {
197 /// Whether there is nothing to apply or log: no value of a scalar
198 /// setting and no entry of a list.
199 #[must_use]
200 pub fn is_empty(&self) -> bool {
201 self.scalars.values().all(|state| state.value.is_none())
202 && self.dll_overrides.is_empty()
203 && self.arguments.is_empty()
204 && self.debug_channels.is_empty()
205 && self.env.is_empty()
206 }
207
208 /// Where the scalar setting `scalar` ended up: its value and the rule
209 /// that set it, or the rule that reset it.
210 #[must_use]
211 pub fn scalar(&self, scalar: Scalar) -> &ScalarState {
212 self.scalars.get(&scalar).unwrap_or(&UNSET)
213 }
214
215 /// The DXGI-family implementation (see [`Rule::dxgi`]), `None` when no
216 /// rule sets it.
217 #[must_use]
218 pub fn dxgi(&self) -> Option<Dxgi> {
219 match self.scalar(Scalar::Dxgi).value {
220 Some(ScalarValue::Dxgi(dxgi)) => Some(dxgi),
221 _ => None,
222 }
223 }
224
225 /// The D3D9 implementation (see [`Rule::d3d9`]), `None` when no rule
226 /// sets it.
227 #[must_use]
228 pub fn d3d9(&self) -> Option<D3d9> {
229 match self.scalar(Scalar::D3d9).value {
230 Some(ScalarValue::D3d9(d3d9)) => Some(d3d9),
231 _ => None,
232 }
233 }
234
235 /// The renderer of wined3d (see [`Rule::wined3d`]), `None` when no rule
236 /// sets it.
237 #[must_use]
238 pub fn wined3d(&self) -> Option<Wined3d> {
239 match self.scalar(Scalar::Wined3d).value {
240 Some(ScalarValue::Wined3d(wined3d)) => Some(wined3d),
241 _ => None,
242 }
243 }
244
245 /// Which OpenGL the Mac driver gives (see [`Rule::opengl`]), `None` when
246 /// no rule sets it.
247 #[must_use]
248 pub fn opengl(&self) -> Option<OpenGl> {
249 match self.scalar(Scalar::OpenGl).value {
250 Some(ScalarValue::OpenGl(opengl)) => Some(opengl),
251 _ => None,
252 }
253 }
254
255 /// The DPI awareness (see [`Rule::dpi_aware`]), `None` when no rule sets
256 /// it.
257 #[must_use]
258 pub fn dpi_aware(&self) -> Option<bool> {
259 self.switch(Scalar::DpiAware)
260 }
261
262 /// Whether large address awareness is forced (see
263 /// [`Rule::large_address_aware`]), `None` when no rule sets it.
264 #[must_use]
265 pub fn large_address_aware(&self) -> Option<bool> {
266 self.switch(Scalar::LargeAddressAware)
267 }
268
269 /// Whether mouse movement comes from `GCMouse` (see [`Rule::gcmouse`]),
270 /// `None` when no rule sets it.
271 #[must_use]
272 pub fn gcmouse(&self) -> Option<bool> {
273 self.switch(Scalar::Gcmouse)
274 }
275
276 /// Whether no-execute stays on (see [`Rule::force_nx`]), `None` when no
277 /// rule sets it.
278 #[must_use]
279 pub fn force_nx(&self) -> Option<bool> {
280 self.switch(Scalar::ForceNx)
281 }
282
283 /// Whether the Metal performance HUD shows (see [`Rule::metal_hud`]),
284 /// `None` when no rule sets it.
285 #[must_use]
286 pub fn metal_hud(&self) -> Option<bool> {
287 self.switch(Scalar::MetalHud)
288 }
289
290 /// The value of the true-or-false setting `scalar`.
291 fn switch(&self, scalar: Scalar) -> Option<bool> {
292 match self.scalar(scalar).value {
293 Some(ScalarValue::Bool(on)) => Some(on),
294 _ => None,
295 }
296 }
297
298 /// The value the rules' own `env` entries leave `WINE_D3D_CONFIG` at:
299 /// the last entry for it, `Some("")` when that one removes it, and `None`
300 /// when no rule sets it.
301 #[must_use]
302 pub fn d3d_config(&self) -> Option<&str> {
303 self.env
304 .iter()
305 .rev()
306 .find(|entry| is_d3d_config(entry))
307 .map(|entry| entry.value().unwrap_or_default())
308 }
309
310 /// Fold the settings that `compatdb.so` applies as environment entries
311 /// into [`Resolution::env`], each with the rule that set the setting as
312 /// its origin and none of them a default:
313 ///
314 /// - [`Resolution::wined3d`], when a rule sets it: the `WINE_D3D_CONFIG`
315 /// that names the renderer, after the rules' own entries. It is the
316 /// value the variable ends up with, and it keeps the other options a
317 /// rule's `WINE_D3D_CONFIG` set (see [`runtime::wined3d_config`]).
318 /// Unset, the process keeps whatever `WINE_D3D_CONFIG` it has, from a
319 /// rule's `env` or inherited, and without one wined3d uses OpenGL.
320 /// - [`Resolution::metal_hud`], when a rule sets it: the entries of
321 /// [`metal_hud_env`], which set or remove the `MTL_HUD_` variables.
322 /// They go where the rule that set it folded, before that rule's own
323 /// `env` entries, so an `env` entry for one of the variables from that
324 /// rule or from any rule folded after it wins. Unset, the process keeps
325 /// the ones it was started with.
326 ///
327 /// Call it after [`Table::resolve`], which records where each setting
328 /// came from; a setting without that record adds nothing. A second call
329 /// changes nothing.
330 pub fn fold_derived_env(&mut self) {
331 if std::mem::replace(&mut self.derived_env, true) {
332 return;
333 }
334 if let (Some(wined3d), Some(origin)) =
335 (self.wined3d(), self.scalar(Scalar::Wined3d).set_by.clone())
336 {
337 let config = self.d3d_config().filter(|value| !value.is_empty());
338 let value = runtime::wined3d_config(wined3d, config);
339 // The value is the renderer and the options of a checked one.
340 self.push_derived(EnvEntry::literal(Wined3d::CONFIG_VAR, &value), origin);
341 }
342 if let (Some(on), Some(origin)) = (
343 self.metal_hud(),
344 self.scalar(Scalar::MetalHud).set_by.clone(),
345 ) {
346 let at = self
347 .metal_hud_at
348 .map_or(self.env.len(), |at| at.min(self.env.len()));
349 let entries = metal_hud_env(on);
350 let from: Vec<EnvFrom> = entries
351 .iter()
352 .map(|_| EnvFrom {
353 origin: origin.clone(),
354 default: false,
355 })
356 .collect();
357 self.env.splice(at..at, entries);
358 self.env_from.splice(at..at, from);
359 }
360 }
361
362 /// Add the env entry a process with [`PROF_SERVICE_VAR`] gets: FEX's
363 /// extended debug info, which a profiler needs to map host code to every
364 /// guest instruction. A second call changes nothing.
365 ///
366 /// FEX reads its options from `block`, the process's environment block,
367 /// and not from the unix environment, which `unix` reads. So only a value
368 /// in `block` makes the entry a default that leaves the variable alone. A
369 /// value only `unix` has is what a child started with a block of its own
370 /// inherits, and it goes into `block`: without that the log would name a
371 /// value FEX does not get. It comes ahead of every other entry, so a
372 /// rule's entry for the variable wins. A FEX without the option ignores
373 /// the variable.
374 pub fn fold_profiler_env(&mut self, block: &[u16], unix: impl Fn(&str) -> Option<String>) {
375 self.fold_derived_env();
376 if self
377 .env_from
378 .iter()
379 .any(|from| from.origin == Origin::Profiler)
380 {
381 return;
382 }
383 let inherited = unix(FEX_DEBUG_INFO_VAR).filter(|value| !value.is_empty());
384 let value = inherited.as_deref().unwrap_or("1");
385 self.env
386 .insert(0, EnvEntry::literal(FEX_DEBUG_INFO_VAR, value));
387 self.env_from.insert(
388 0,
389 EnvFrom {
390 origin: Origin::Profiler,
391 default: runtime::env_value(block, FEX_DEBUG_INFO_VAR).is_some(),
392 },
393 );
394 }
395
396 /// Add `entry`, which a setting from `origin` stands for, after the
397 /// other env entries.
398 fn push_derived(&mut self, entry: EnvEntry, origin: Origin) {
399 self.env.push(entry);
400 self.env_from.push(EnvFrom {
401 origin,
402 default: false,
403 });
404 }
405
406 /// Where the env entry that leaves `WINE_D3D_CONFIG` at
407 /// [`Resolution::d3d_config`] came from, or `None` when no rule sets it.
408 #[must_use]
409 pub fn d3d_config_origin(&self) -> Option<&Origin> {
410 let i = self.env.iter().rposition(is_d3d_config)?;
411 self.env_from.get(i).map(|from| &from.origin)
412 }
413
414 /// Fold what a rule from `origin` says about the scalar setting
415 /// `scalar`: a value replaces the value or reset of a rule folded before,
416 /// and a reset unsets the setting from here on.
417 pub(crate) fn fold_scalar(
418 &mut self,
419 scalar: Scalar,
420 setting: Setting<ScalarValue>,
421 origin: &Origin,
422 ) {
423 let state = match setting {
424 Setting::Unset => return,
425 Setting::Set(value) => ScalarState {
426 value: Some(value),
427 set_by: Some(origin.clone()),
428 reset_by: None,
429 },
430 Setting::Reset => ScalarState {
431 value: None,
432 set_by: None,
433 reset_by: Some(origin.clone()),
434 },
435 };
436 self.scalars.insert(scalar, state);
437 }
438
439 /// Fold the `dll_overrides` entries of `rule` into the list: an entry
440 /// whose order is `default` takes its modules out of the entries before
441 /// it, dropping an entry left without one, and every other entry is
442 /// appended.
443 fn fold_dll_overrides(&mut self, rule: &Rule, origin: &Origin) {
444 for entry in &rule.dll_overrides {
445 if !entry.is_default() {
446 self.dll_overrides.push(entry.clone());
447 self.dll_overrides_from.push(origin.clone());
448 continue;
449 }
450 for module in entry.modules() {
451 let mut removed = Vec::new();
452 let mut kept = Vec::new();
453 let entries = std::mem::take(&mut self.dll_overrides)
454 .into_iter()
455 .zip(std::mem::take(&mut self.dll_overrides_from));
456 for (earlier, from) in entries {
457 match without_module(&earlier, module) {
458 Without::Absent => kept.push((earlier, from)),
459 Without::Emptied => removed.push(from),
460 Without::Rest(rest) => {
461 removed.push(from.clone());
462 kept.push((rest, from));
463 }
464 }
465 }
466 let (entries, from): (Vec<_>, Vec<_>) = kept.into_iter().unzip();
467 self.dll_overrides = entries;
468 self.dll_overrides_from = from;
469 self.dll_overrides_reset.push(DllOverrideReset {
470 module: module.to_string(),
471 by: origin.clone(),
472 removed,
473 });
474 }
475 }
476 }
477
478 /// Fold the arguments of `rule` into the list: its `arguments_override`,
479 /// when set, replaces what the rules folded before it accumulated, unless
480 /// `rule` is for a Steam app id, and its `arguments` append.
481 fn fold_arguments(&mut self, rule: &Rule, origin: &Origin) {
482 match &rule.arguments_override {
483 Some(_) if rule.exe.is_steam_app() => {
484 self.arguments_override_ignored.push(origin.clone());
485 }
486 Some(over) => {
487 let entries: Vec<(Argument, Origin)> = std::mem::take(&mut self.arguments)
488 .into_iter()
489 .zip(std::mem::take(&mut self.arguments_from))
490 .collect();
491 if !entries.is_empty() {
492 self.arguments_dropped.push(ArgumentsDropped {
493 by: origin.clone(),
494 entries,
495 });
496 }
497 self.arguments.extend(over.iter().cloned());
498 self.arguments_from
499 .extend(over.iter().map(|_| origin.clone()));
500 }
501 None => {}
502 }
503 self.arguments.extend(rule.arguments.iter().cloned());
504 self.arguments_from
505 .extend(rule.arguments.iter().map(|_| origin.clone()));
506 }
507
508 /// The env entries in order, each with the rule that set it and whether
509 /// it applies in a process where `has` says which variables the process
510 /// already has. A default leaves such a variable alone, so a value the
511 /// caller exported wins over a built-in `*` rule; every other entry
512 /// applies, replacing the value or, when empty, removing the variable.
513 /// An entry that a later applied entry for the same variable replaces
514 /// says where that entry came from.
515 ///
516 /// [`Resolution::env_from`] has one entry per env entry, as
517 /// [`Table::resolve`] and [`Resolution::fold_derived_env`] keep it.
518 #[must_use]
519 pub fn env_steps(&self, has: impl Fn(&str) -> bool) -> Vec<EnvStep> {
520 let mut steps: Vec<EnvStep> = self
521 .env
522 .iter()
523 .zip(&self.env_from)
524 .map(|(entry, from)| EnvStep {
525 entry: entry.clone(),
526 origin: from.origin.clone(),
527 applied: !(from.default && has(entry.name().as_str())),
528 replaced_by: None,
529 removes: None,
530 })
531 .collect();
532 let same = |a: &EnvStep, b: &EnvStep| {
533 a.applied
534 && a.entry
535 .name()
536 .as_str()
537 .eq_ignore_ascii_case(b.entry.name().as_str())
538 };
539 let links: Vec<(Option<Origin>, Option<Origin>)> = steps
540 .iter()
541 .enumerate()
542 .map(|(i, step)| {
543 let replaced_by = steps
544 .iter()
545 .skip(i.saturating_add(1))
546 .rev()
547 .find(|later| same(later, step))
548 .map(|later| later.origin.clone());
549 let removes = steps
550 .iter()
551 .take(i)
552 .rev()
553 .find(|earlier| same(earlier, step))
554 .filter(|earlier| {
555 step.entry.value().is_none() && earlier.entry.value().is_some()
556 })
557 .map(|earlier| earlier.origin.clone());
558 (replaced_by, removes)
559 })
560 .collect();
561 for (step, (replaced_by, removes)) in steps.iter_mut().zip(links) {
562 step.replaced_by = replaced_by;
563 step.removes = removes;
564 }
565 steps
566 }
567}
568
569impl Table {
570 /// Fold every matching rule into one [`Resolution`]. `exe` is the image
571 /// basename (see [`basename_of`]); `version` is the image's version
572 /// resource, for the optional fingerprint constraints; `steam_app` is the
573 /// process's Steam app id, if it has one (see [`steam_app`]), for the
574 /// rules that name one.
575 ///
576 /// The rules fold in this order, each group in table order, which is
577 /// line order for the rules of [`ENV_VAR`] and then line order for those
578 /// of the file [`FILE_ENV_VAR`] names:
579 ///
580 /// 1. built-in `*` rules, those without a fingerprint first;
581 /// 2. the caller's `*` rules, those without a fingerprint first;
582 /// 3. built-in rules naming an executable, those without a fingerprint
583 /// first;
584 /// 4. the caller's rules naming an executable, those without a
585 /// fingerprint first.
586 ///
587 /// So a scalar field ends up holding the value of the last rule that
588 /// sets it. Whether a rule names an executable decides first, then where
589 /// it comes from, then whether it pins a fingerprint (see
590 /// [`Rule::specificity`]). The variable and the file are one place in
591 /// that order, so only table order tells them apart: a rule of the file
592 /// beats a rule of the variable of equal specificity, because it is
593 /// declared later, and a `*` rule of the file does not beat a rule of
594 /// the variable that names an executable. A launch-wide `*` rule of the
595 /// caller replaces a built-in default but not what a built-in rule for
596 /// one program sets, so that fix survives launch-wide settings, and a
597 /// rule of the caller for the program beats it, with or without a
598 /// fingerprint. The lists accumulate across every rule that matched, in
599 /// the same order. The exceptions are a
600 /// rule's `arguments_override`, which replaces the arguments the rules
601 /// folded before it accumulated (the `arguments` of the rule itself and
602 /// of the rules folded after it append to it), a scalar set to
603 /// `default` (see [`Setting::Reset`]), and a `dll_overrides` entry with
604 /// the order `default`, which takes its modules out of the entries
605 /// before it.
606 ///
607 /// The sort has to stay stable: rules of equal place in that order fold
608 /// in table order.
609 ///
610 /// A rule whose executable matches but whose fingerprint does not simply
611 /// does not apply; nothing falls back to it, and the rules that do match
612 /// still apply.
613 ///
614 /// A rule for a Steam app id counts as naming an executable: it folds in
615 /// the same groups, in table order with the rules for a basename. Its
616 /// `arguments_override` is ignored (see [`Rule::arguments_override`]).
617 #[must_use]
618 pub fn resolve(&self, exe: &str, version: &VersionInfo, steam_app: Option<u32>) -> Resolution {
619 let mut matching: Vec<(Source, &Rule)> = self
620 .entries()
621 .filter(|(_, rule)| rule.matches(exe, version, steam_app))
622 .collect();
623 matching.sort_by_key(|(source, rule)| {
624 let specificity = rule.specificity();
625 let names_exe = matches!(
626 specificity,
627 Specificity::Exe | Specificity::ExeWithFingerprint
628 );
629 // The two sources of the caller's rules compare equal here.
630 (names_exe, *source != Source::BuiltIn, specificity)
631 });
632 let mut r = Resolution::default();
633 for (source, rule) in matching {
634 let origin = Origin::of(source, rule);
635 for &scalar in Scalar::ALL {
636 r.fold_scalar(scalar, rule.scalar(scalar), &origin);
637 }
638 r.fold_dll_overrides(rule, &origin);
639 r.fold_arguments(rule, &origin);
640 r.debug_channels.extend(rule.debug_channels.iter().cloned());
641 r.debug_channels_from
642 .extend(rule.debug_channels.iter().map(|_| origin.clone()));
643 match rule.metal_hud {
644 Setting::Set(_) => r.metal_hud_at = Some(r.env.len()),
645 Setting::Reset => r.metal_hud_at = None,
646 Setting::Unset => {}
647 }
648 let default = origin.env_is_default();
649 r.env.extend(rule.env.iter().cloned());
650 r.env_from.extend(rule.env.iter().map(|_| EnvFrom {
651 origin: origin.clone(),
652 default,
653 }));
654 }
655 r
656 }
657}
658
659/// Whether `entry` is for the variable wined3d reads its settings from,
660/// matched ASCII-case-insensitively as in a process environment.
661fn is_d3d_config(entry: &EnvEntry) -> bool {
662 entry
663 .name()
664 .as_str()
665 .eq_ignore_ascii_case(Wined3d::CONFIG_VAR)
666}
667
668/// Whether two module names of `dll_overrides` entries name the same module:
669/// compared case-insensitively, with or without `.dll`.
670fn same_module(a: &str, b: &str) -> bool {
671 let (a, b) = (a.to_ascii_lowercase(), b.to_ascii_lowercase());
672 a.strip_suffix(".dll").unwrap_or(&a) == b.strip_suffix(".dll").unwrap_or(&b)
673}
674
675/// A `dll_overrides` entry with one module taken out (see
676/// [`without_module`]).
677#[derive(Debug, PartialEq, Eq)]
678enum Without {
679 /// The entry does not name the module.
680 Absent,
681 /// The module was the entry's only one, so nothing is left.
682 Emptied,
683 /// The entry with its other modules and the same order.
684 Rest(DllOverride),
685}
686
687/// The `dll_overrides` entry `entry` without `module`.
688fn without_module(entry: &DllOverride, module: &str) -> Without {
689 let all: Vec<&str> = entry.modules().collect();
690 let rest: Vec<&str> = all
691 .iter()
692 .copied()
693 .filter(|name| !same_module(name, module))
694 .collect();
695 if rest.len() == all.len() {
696 Without::Absent
697 } else if rest.is_empty() {
698 Without::Emptied
699 } else {
700 // Made of the parts of a checked entry: its `=` is there.
701 Without::Rest(DllOverride::literal(&format!(
702 "{}={}",
703 rest.join(","),
704 entry.order()
705 )))
706 }
707}
708
709#[cfg(test)]
710mod tests;