Skip to main content

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;