Skip to main content

compatdb_core/
builtin.rs

1//! The built-in rules, compiled into `compatdb.so`.
2//!
3//! This is static data the library owns, so it never travels through the
4//! environment. Only the caller's rules are serialized into `WINE_COMPATDB`;
5//! the library appends them to these, and every rule that matches a process
6//! applies.
7//!
8//! Each rule for one program ([`programs`]) names an executable and pins its
9//! version fingerprint, or names a Steam app id, so none of them can catch an
10//! unrelated program that happens to share a file name. The [`defaults`]
11//! name no executable and match every process on purpose: each is a default
12//! that a built-in rule for one program, or any `WINE_COMPATDB` rule that
13//! sets the same key, overrides. A rule for one program folds after a `*`
14//! rule from `WINE_COMPATDB`, so its fix survives launch-wide settings; a
15//! `WINE_COMPATDB` rule for that program changes it (see
16//! [`Table::resolve`]).
17
18use std::fmt;
19
20#[cfg(doc)]
21use crate::Table;
22use crate::{
23    Argument, D3d9, DllOverride, Dxgi, EnvEntry, Exe, Fingerprint, Key, OpenGl, Rule, Setting,
24};
25
26/// The rules the bundle ships with: the [`defaults`] for every process, then
27/// the rules for one program each ([`programs`]). [`Table::builtin`] is the
28/// table of them.
29#[must_use]
30pub fn rules() -> Vec<Rule> {
31    defaults().into_iter().chain(programs()).collect()
32}
33
34/// The rules that match every process, each turning on one behaviour that
35/// differs from upstream Wine.
36#[must_use]
37pub fn defaults() -> Vec<Rule> {
38    vec![
39        dxgi_dxmt(),
40        d3d9_mtld3d(),
41        dpi_aware(),
42        gcmouse(),
43        fex_reduced_x87_precision(),
44        opengl_egl(),
45        force_nx(),
46        no_mono_or_gecko(),
47        native_d3dx_and_d3dcompiler(),
48    ]
49}
50
51/// The rules for one program each, which beat the [`defaults`] for it.
52#[must_use]
53pub fn programs() -> Vec<Rule> {
54    vec![
55        rockstar_games_launcher(),
56        social_club_helper(),
57        steam_web_helper(),
58        gta_iv(),
59        nostale(),
60        age_of_empires_ii_hd(),
61        world_of_warcraft(),
62        half_life_2(),
63        team_fortress_2(),
64    ]
65}
66
67/// DXMT for D3D10, D3D11 and D3D12 in every process.
68///
69/// D3D10, D3D11 and D3D12 go through DXMT, which translates them to Metal
70/// directly, instead of through wined3d, upstream Wine's implementation,
71/// which draws through OpenGL or Vulkan and reaches a lower feature level and
72/// frame rate on a Mac. D3D12 is vkd3d-proton in the same tree, which presents
73/// through DXMT's dxgi.dll; the wined3d tree has no D3D12 at all. A rule for
74/// one program with `dxgi = wined3d` gives it wined3d, as the Rockstar
75/// launcher's does, and a `*` rule with it gives wined3d to every process
76/// that no built-in rule for one program pins to a tree.
77#[must_use]
78pub fn dxgi_dxmt() -> Rule {
79    Rule {
80        exe: Exe::Any,
81        dxgi: Setting::Set(Dxgi::Dxmt),
82        ..Rule::default()
83    }
84}
85
86/// mtld3d for D3D9 in every process.
87///
88/// D3D9 goes through mtld3d, which translates it to Metal directly, instead
89/// of through wined3d, for the same reason as [`dxgi_dxmt`]. A rule for one
90/// program with `d3d9 = wined3d` gives it wined3d, and a `*` rule with it
91/// gives every process wined3d.
92#[must_use]
93pub fn d3d9_mtld3d() -> Rule {
94    Rule {
95        exe: Exe::Any,
96        d3d9: Setting::Set(D3d9::Mtld3d),
97        ..Rule::default()
98    }
99}
100
101/// Every process is DPI-aware.
102///
103/// Programs under this Wine are treated as DPI-aware unless a rule says
104/// otherwise. An unaware program on a scaled desktop gets its window and
105/// mouse coordinates scaled while display modes are not, so a game that sizes
106/// itself from the mode list draws and reads the pointer in two different
107/// coordinate spaces.
108#[must_use]
109pub fn dpi_aware() -> Rule {
110    Rule {
111        exe: Exe::Any,
112        dpi_aware: Setting::Set(true),
113        ..Rule::default()
114    }
115}
116
117/// Mouse movement from GameController in every process.
118///
119/// The Mac driver takes the mouse movement of raw input (`WM_INPUT`) and
120/// DirectInput from GameController's `GCMouse`, which reports the device's
121/// own counts. Otherwise that movement is the cursor's, which macOS has
122/// already accelerated and, on a retina display, scaled, so mouselook in a
123/// game that reads raw input or DirectInput turns further the faster the
124/// hand moves. winemac.drv reads the value through ntdll when it reads its
125/// settings, and without one keeps upstream Wine's behaviour, the cursor's
126/// movement.
127///
128/// It matches every process, like the other [`defaults`]. A rule for one
129/// game with `gcmouse = false` gives it the cursor's movement again: for a
130/// game whose sensitivity was tuned to that, a mouse that GameController
131/// reports twice, or a regression. A `*` rule with it does the same for every
132/// process.
133#[must_use]
134pub fn gcmouse() -> Rule {
135    Rule {
136        exe: Exe::Any,
137        gcmouse: Setting::Set(true),
138        ..Rule::default()
139    }
140}
141
142/// FEX's reduced x87 precision in every process.
143///
144/// x86 code runs under FEX, which by default emulates x87 floating-point
145/// code, which games built for 32-bit x86 use heavily, at its full 80-bit
146/// precision, and such games run slowly that way. Its `X87ReducedPrecision`
147/// option computes with 64-bit doubles instead, which is fast at some cost in
148/// accuracy (FEX warns of possible rendering bugs). Our FEX still rounds to
149/// single precision when the x87 precision control asks for 24 bits, as D3D9
150/// sets it when it creates a device. FEX takes the option from
151/// `FEX_X87REDUCEDPRECISION`, parsed as an integer (so "1", not "true"), and
152/// reads it from the Windows environment block when its process init runs,
153/// after this library has written its env there.
154///
155/// It matches every process, native ARM64 ones included. FEX is not loaded in
156/// those, so the variable does nothing there, but a child they start inherits
157/// it, and that child may be x86. A rule for one game with
158/// `env=FEX_X87REDUCEDPRECISION=` removes it again, and the variable exported
159/// where the process starts wins over it.
160#[must_use]
161pub fn fex_reduced_x87_precision() -> Rule {
162    Rule {
163        exe: Exe::Any,
164        env: vec![EnvEntry::literal("FEX_X87REDUCEDPRECISION", "1")],
165        ..Rule::default()
166    }
167}
168
169/// Mesa's EGL for OpenGL in every process.
170///
171/// OpenGL goes through Mesa's EGL with Zink on KosmicKrisp, which reaches
172/// OpenGL 4.6 and the compatibility profile, instead of Apple's OpenGL,
173/// upstream Wine's, which stops at 4.1 core and 2.1 compatibility.
174/// winemac.drv reads the `opengl` setting through ntdll when a process first
175/// uses OpenGL, long after compatdb has handed it over; unset, it uses
176/// Apple's OpenGL. With egl it does not fall back: where Zink has no GPU, as
177/// on a Mac without a Metal 4 GPU, the process gets no OpenGL, and
178/// `opengl = cgl` gives it Apple's.
179///
180/// It matches every process, like the other [`defaults`]. A rule for one
181/// program with `opengl = cgl` gives that program Apple's OpenGL, and only
182/// that program: the value is the process's own, so the programs it starts
183/// keep this default. A `*` rule with it gives every process Apple's.
184#[must_use]
185pub fn opengl_egl() -> Rule {
186    Rule {
187        exe: Exe::Any,
188        opengl: Setting::Set(OpenGl::Egl),
189        ..Rule::default()
190    }
191}
192
193/// No-execute stays on in every 32-bit process.
194///
195/// i386 code runs under FEX. Wine turns no-execute off for a 32-bit process
196/// when its executable or a DLL it loads lacks the `NX_COMPAT` flag, as many
197/// older games do, and FEX then has to retranslate code whenever writable
198/// memory changes. With `force_nx`, our ntdll keeps no-execute permanently on
199/// instead; it reads the value the first time the process asks for or
200/// changes its execute flags, long after compatdb has handed it over. A
201/// 64-bit process always has no-execute on, so the setting does nothing
202/// there.
203///
204/// A program that runs code from memory it did not make executable crashes
205/// with it. That includes programs built with the ATL of Visual Studio 2003,
206/// such as game configurators and launchers, whose window thunks live on the
207/// heap: Windows emulates such thunks, but in a WoW64 process under FEX
208/// neither Wine nor FEX does, so the first window message faults.
209///
210/// It matches every process, like the other [`defaults`]. A rule for one
211/// program with `force_nx = false` gives that program Wine's behaviour
212/// again, and only that program: the value is the process's own, so the
213/// programs it starts keep this default. A `*` rule with it does that for
214/// every process.
215#[must_use]
216pub fn force_nx() -> Rule {
217    Rule {
218        exe: Exe::Any,
219        force_nx: Setting::Set(true),
220        ..Rule::default()
221    }
222}
223
224/// Keeps Wine from prompting to install Mono and Gecko, in wineboot and in
225/// any process that loads either.
226///
227/// The override disables both modules, not only the prompts. ntdll consults
228/// the overrides compatdb adds before the registry `DllOverrides` keys, so an
229/// installed wine-mono, Gecko or native .NET selected there stays disabled
230/// too. A prefix that needs one adds a later entry for the module, such as
231/// `mscoree=n,b`, or hands it back to the registry with `mscoree=default`, in
232/// a `*` rule or a rule for the program.
233#[must_use]
234pub fn no_mono_or_gecko() -> Rule {
235    Rule {
236        exe: Exe::Any,
237        dll_overrides: vec![DllOverride::literal("mscoree,mshtml=")],
238        ..Rule::default()
239    }
240}
241
242/// Native, then builtin, for the D3DX and D3DCompiler DLLs in every process.
243///
244/// Wine's builtins of these DLLs are incomplete. Its d3dx9 returns
245/// `E_NOTIMPL` from `ID3DXEffect::SetRawValue` for a vector with fewer than 4
246/// columns, so Need for Speed: ProStreet, which sets its sun and ambient
247/// colours that way, renders a black scene. Microsoft's copies, which a
248/// game's DirectX redistributable installs into the prefix or the game ships
249/// next to its executable, have what Wine's lack. Without an override Wine
250/// never loads them: it loads a DLL whose version resource names Microsoft
251/// as its company builtin first, wherever the file is.
252///
253/// The order is `n,b`, never `n`. Where no native copy exists, the prefix
254/// holds Wine's placeholder DLL, and ntdll loads the builtin for a placeholder
255/// under `n,b`; under `n` the DLL would fail to load. The entry names every
256/// D3DX and D3DCompiler DLL Wine builds, since `WINEDLLOVERRIDES` has no
257/// wildcards: `d3dx9_24` to `d3dx9_43`, `d3dx10_33` to `d3dx10_43`,
258/// `d3dx11_42` and `d3dx11_43`, `d3dcompiler_33` to `d3dcompiler_43`,
259/// `d3dcompiler_46` and `d3dcompiler_47`.
260///
261/// Steam's and the Social Club's web helpers ship `d3dcompiler_47` in their
262/// own folders, so they load that copy native too.
263///
264/// It matches every process, like the other [`defaults`]. ntdll keeps the
265/// last entry for a module, so a later entry for one of these DLLs, in a `*`
266/// rule or a rule for the program, such as `d3dx9_43=b`, replaces this one
267/// for that DLL. `d3dx9_43=default` hands it back to the registry and
268/// `WINEDLLOVERRIDES`, as for [`no_mono_or_gecko`]: otherwise this entry
269/// wins over their entries for the same name.
270#[must_use]
271pub fn native_d3dx_and_d3dcompiler() -> Rule {
272    let modules: Vec<String> = d3dx_and_d3dcompiler_modules().collect();
273    Rule {
274        exe: Exe::Any,
275        dll_overrides: vec![DllOverride::literal(&format!("{}=n,b", modules.join(",")))],
276        ..Rule::default()
277    }
278}
279
280/// The D3DX and D3DCompiler DLLs Wine builds a builtin of, without `.dll`.
281fn d3dx_and_d3dcompiler_modules() -> impl Iterator<Item = String> {
282    let d3dx9 = (24..=43).map(|v| format!("d3dx9_{v}"));
283    let d3dx10 = (33..=43).map(|v| format!("d3dx10_{v}"));
284    let d3dx11 = (42..=43).map(|v| format!("d3dx11_{v}"));
285    let d3dcompiler = (33..=43)
286        .chain([46, 47])
287        .map(|v| format!("d3dcompiler_{v}"));
288    d3dx9.chain(d3dx10).chain(d3dx11).chain(d3dcompiler)
289}
290
291/// wined3d for the DXGI family in the Rockstar Games Launcher.
292///
293/// The Rockstar Games Launcher needs a real D3D10.1 device, which DXMT, the
294/// default, does not provide; wined3d does. Matched by its version resource,
295/// not its path, so it works wherever it is installed and never hits another
296/// game's Launcher.exe.
297#[must_use]
298pub fn rockstar_games_launcher() -> Rule {
299    Rule {
300        exe: Exe::literal("Launcher.exe"),
301        company: Some(Fingerprint::literal("Rockstar Games")),
302        product: Some(Fingerprint::literal("Rockstar Games Launcher")),
303        dxgi: Setting::Set(Dxgi::Wined3d),
304        ..Rule::default()
305    }
306}
307
308/// An in-process GPU, DXMT and no Metal HUD for the Rockstar Games
309/// Launcher's Social Club helper.
310///
311/// The launcher's embedded browser (CEF): its GPU process cannot paint into a
312/// window owned by another process under winemac, so the sign-in page stays
313/// blank. Keeping the drawing in-process fixes it; the child --type=
314/// processes inherit the switches. It is pinned to DXMT to match Steam's CEF
315/// helper ([`steam_web_helper`]), which embeds the browser the same way.
316///
317/// The Metal HUD is for games and is noise on the launcher UI. It is turned
318/// off here and not on Launcher.exe because games started from the launcher
319/// would inherit the variables. `metal_hud=false` removes the `MTL_HUD_`
320/// variables, a launcher `*` rule's `metal_hud=true` included, so the
321/// resolution reports the HUD off; the `env` entry `MTL_HUD_ENABLED=0` after
322/// it then turns the HUD off explicitly rather than leaving the variable
323/// unset.
324#[must_use]
325pub fn social_club_helper() -> Rule {
326    Rule {
327        exe: Exe::literal("SocialClubHelper.exe"),
328        company: Some(Fingerprint::literal("Take-Two Interactive Software")),
329        product: Some(Fingerprint::literal("Social Club UI")),
330        dxgi: Setting::Set(Dxgi::Dxmt),
331        metal_hud: Setting::Set(false),
332        arguments: vec![Argument::literal("--in-process-gpu")],
333        env: vec![EnvEntry::literal("MTL_HUD_ENABLED", "0")],
334        ..Rule::default()
335    }
336}
337
338/// An in-process GPU, DXMT and no Metal HUD for Steam's web helper.
339///
340/// Steam's embedded browser (CEF): its GPU process cannot paint into the
341/// browser's window (blank UI), so keep the GPU in-process. GPU rendering
342/// stays on and goes through DXMT, which the Steam client runs well on; the
343/// dxgi pin keeps it there over the `*` rules, built-in or from
344/// `WINE_COMPATDB`, while a `WINE_COMPATDB` rule for the helper that
345/// selects another tree moves it.
346///
347/// The Metal HUD is off, through `metal_hud=false` and the `env` entry
348/// `MTL_HUD_ENABLED=0`, for the same reason and in the same way as on the
349/// Social Club helper ([`social_club_helper`]); steam.exe, which starts the
350/// games, has no rule.
351#[must_use]
352pub fn steam_web_helper() -> Rule {
353    Rule {
354        exe: Exe::literal("steamwebhelper.exe"),
355        company: Some(Fingerprint::literal("Valve Corporation")),
356        product: Some(Fingerprint::literal("Steam Client WebHelper")),
357        dxgi: Setting::Set(Dxgi::Dxmt),
358        metal_hud: Setting::Set(false),
359        arguments: vec![Argument::literal("--in-process-gpu")],
360        env: vec![EnvEntry::literal("MTL_HUD_ENABLED", "0")],
361        ..Rule::default()
362    }
363}
364
365/// The video memory GTA IV assumes.
366///
367/// GTA IV (Complete Edition). The switch overrides the game's own
368/// video-memory detection, which mis-reads the reported figure and falls back
369/// to 512 MB; 2048 unlocks every quality tier while staying inside the 32-bit
370/// address space. Nothing else is needed here: the D3D9 quirks this game
371/// wants are backend behaviour, so they live in mtld3d's own built-in profile
372/// for GTAIV.exe and apply whichever launcher started it.
373#[must_use]
374pub fn gta_iv() -> Rule {
375    Rule {
376        exe: Exe::literal("GTAIV.exe"),
377        company: Some(Fingerprint::literal("Rockstar Games")),
378        product: Some(Fingerprint::literal("Grand Theft Auto IV")),
379        arguments: vec![Argument::literal("-availablevidmem 2048.0")],
380        ..Rule::default()
381    }
382}
383
384/// The NosTale client is DPI-unaware.
385///
386/// NosTale rasterises its text with GDI into fixed-grid glyph atlases and
387/// fixed-size UI boxes laid out for 96 DPI. Marked DPI-aware at 192 DPI
388/// (retina), GDI hands it fonts twice as large, so wide glyphs overrun their
389/// atlas slots (clipped "m" and "w") and text overflows its boxes. Left
390/// DPI-unaware, Wine gives it 96 DPI fonts and scales its window, as Windows
391/// does at 200%. The client's version resource names only the company, so
392/// that is the whole fingerprint.
393#[must_use]
394pub fn nostale() -> Rule {
395    Rule {
396        exe: Exe::literal("NostaleClientX.exe"),
397        company: Some(Fingerprint::literal("Entwell")),
398        dpi_aware: Setting::Set(false),
399        ..Rule::default()
400    }
401}
402
403/// Age of Empires II HD is DPI-unaware.
404///
405/// Age of Empires II HD draws its UI at a fixed size in pixels. Marked
406/// DPI-aware at 192 DPI (retina), it runs at the display's full resolution
407/// and its UI comes out too small to use; left DPI-unaware, Wine scales its
408/// window, as Windows does at 200%, and the UI has its intended size. Its
409/// manifest does not declare DPI awareness, but the game looks up
410/// `SetProcessDPIAware` and likely calls it at run time. The rule still wins:
411/// win32u sets the process awareness from it when it initializes, and
412/// refuses every later change. The fingerprint is the company alone: the
413/// product name of the build this was checked with, `age2_x1`, is the
414/// original game's, and newer Steam builds may have changed it.
415#[must_use]
416pub fn age_of_empires_ii_hd() -> Rule {
417    Rule {
418        exe: Exe::literal("AoK HD.exe"),
419        company: Some(Fingerprint::literal("Microsoft Corporation")),
420        dpi_aware: Setting::Set(false),
421        ..Rule::default()
422    }
423}
424
425/// The 4 GB address space for 32-bit World of Warcraft clients.
426///
427/// Every 32-bit WoW client with these version strings gets the 4 GB address
428/// space. Under Wine the client shares its 2 GB with Wine itself and the
429/// Direct3D layers, so it runs out long before it would on Windows. The
430/// clients checked are 1.12.1 and 3.3.5a: both carry these three strings, and
431/// their unpatched builds lack the large-address-aware flag. The matcher
432/// cannot pin `FileVersion`, so other 32-bit clients with the same strings get
433/// it too. Patched private-server clients, such as Turtle WoW's, already
434/// have the flag and need no rule; a client that has it loses nothing. A
435/// 64-bit client is not affected.
436#[must_use]
437pub fn world_of_warcraft() -> Rule {
438    Rule {
439        exe: Exe::literal("WoW.exe"),
440        company: Some(Fingerprint::literal("Blizzard Entertainment")),
441        product: Some(Fingerprint::literal("World of Warcraft")),
442        original_filename: Some(Fingerprint::literal("WoW.exe")),
443        large_address_aware: Setting::Set(true),
444        ..Rule::default()
445    }
446}
447
448/// No assert dialog when Half-Life 2 (Steam app 220) quits.
449///
450/// Valve's `~CThread` checks once, while server.dll unloads, that the
451/// achievement-save thread has exited, and thread exit under Wine and FEX
452/// loses that race, so tier0 shows "Illegal termination of worker thread";
453/// with `-noassert`, tier0's `DoNewAssertDialog` returns without the dialog
454/// ([source-1-games#7391]). The rule matches the app id because `hl2.exe`
455/// has no version resource.
456///
457/// [source-1-games#7391]: https://github.com/ValveSoftware/source-1-games/issues/7391
458#[must_use]
459pub fn half_life_2() -> Rule {
460    source_noassert("steam:220")
461}
462
463/// No assert dialog when Team Fortress 2 (Steam app 440) quits.
464///
465/// The same race as in [`half_life_2`], with TF2's file-writer thread
466/// ([source-1-games#7391] reports it on Windows and Linux too). The rule
467/// matches the app id because `tf.exe` and `tf_win64.exe` have no version
468/// resource.
469///
470/// [source-1-games#7391]: https://github.com/ValveSoftware/source-1-games/issues/7391
471#[must_use]
472pub fn team_fortress_2() -> Rule {
473    source_noassert("steam:440")
474}
475
476/// The rule that appends `-noassert` to the command line of the processes
477/// with the Steam app id `app`, a `steam:<appid>` literal.
478fn source_noassert(app: &str) -> Rule {
479    Rule {
480        exe: Exe::literal(app),
481        arguments: vec![Argument::literal("-noassert")],
482        ..Rule::default()
483    }
484}
485
486/// A name a built-in rule had while each had one.
487///
488/// A launcher's settings written for those rules may still use one to
489/// extend a rule or turn it off. The rules have no names any more, so such
490/// a name reaches nothing; [`former`] gives what does the same now.
491///
492/// Its `Display` form says that, for example `"dxmt" was the name of a
493/// built-in rule, and built-in rules can no longer be extended or turned off
494/// by name`.
495#[derive(Clone, Copy, Debug, PartialEq, Eq)]
496pub struct Former {
497    /// The name, in lowercase.
498    name: &'static str,
499    /// What the rule of that name did, and so what does it now.
500    did: Did,
501}
502
503/// What a former built-in rule did.
504#[derive(Clone, Copy, Debug, PartialEq, Eq)]
505enum Did {
506    /// Set the scalar setting of this key in every process.
507    Setting(Key),
508    /// Anything else: what to do instead of extending it, if that differs
509    /// from what to do instead of turning it off, and that.
510    Other {
511        /// The sentence that says what to do instead of extending the rule.
512        extend: Option<&'static str>,
513        /// The sentence that says what to do instead of turning it off.
514        off: &'static str,
515    },
516}
517
518/// What changes what a former built-in rule for one program set.
519const PROGRAM_RULE: Did = Did::Other {
520    extend: Some(
521        "Add an `exe` with a `company`, `product` or `original_filename` to make it a rule for \
522         that program, or remove it",
523    ),
524    off: "To undo what it set, give the program's `exe` a rule that sets those keys to `default`",
525};
526
527/// The former names of the built-in rules.
528const FORMER: &[Former] = &[
529    Former {
530        name: "dxmt",
531        did: Did::Setting(Key::Dxgi),
532    },
533    Former {
534        name: "mtld3d",
535        did: Did::Setting(Key::D3d9),
536    },
537    Former {
538        name: "dpi-aware",
539        did: Did::Setting(Key::DpiAware),
540    },
541    Former {
542        name: "force-nx",
543        did: Did::Setting(Key::ForceNx),
544    },
545    Former {
546        name: "gcmouse",
547        did: Did::Setting(Key::Gcmouse),
548    },
549    Former {
550        name: "opengl-egl",
551        did: Did::Setting(Key::OpenGl),
552    },
553    Former {
554        name: "no-mono-gecko",
555        did: Did::Other {
556            extend: Some(
557                "Set `dll_overrides` in a rule for every process or for one program instead",
558            ),
559            off: "Set `dll_overrides` to `mscoree,mshtml=default` in a rule for every process to \
560                  take away the built-in overrides of Mono and Gecko",
561        },
562    },
563    Former {
564        name: "fex-x87-reduced-precision",
565        did: Did::Other {
566            extend: Some(
567                "Set `FEX_X87REDUCEDPRECISION` in the `env` of a rule for every process or for \
568                 one program instead",
569            ),
570            off: "For FEX's full x87 precision, start Wine with `FEX_X87REDUCEDPRECISION=0`, or \
571                  remove the variable in the `env` of a rule for every process",
572        },
573    },
574    Former {
575        name: "vkd3d-proton-min-latency",
576        did: Did::Other {
577            extend: None,
578            off: "That rule no longer exists; remove this one",
579        },
580    },
581    Former {
582        name: "large-address-aware",
583        did: Did::Other {
584            extend: None,
585            off: "The executable's large-address-aware flag now decides; set \
586                  `large_address_aware` to `true` for 4 GB, in a rule for every process or for \
587                  one program",
588        },
589    },
590    Former {
591        name: "rockstar-launcher",
592        did: PROGRAM_RULE,
593    },
594    Former {
595        name: "rockstar-social-club-ui",
596        did: PROGRAM_RULE,
597    },
598    Former {
599        name: "steam-web-helper",
600        did: PROGRAM_RULE,
601    },
602    Former {
603        name: "gta-iv",
604        did: PROGRAM_RULE,
605    },
606    Former {
607        name: "nostale",
608        did: PROGRAM_RULE,
609    },
610    Former {
611        name: "aoe2-hd",
612        did: PROGRAM_RULE,
613    },
614];
615
616/// The former built-in rule called `name`, compared
617/// ASCII-case-insensitively as `compatdb.so` compared the names, or `None`
618/// when no built-in rule had that name.
619#[must_use]
620pub fn former(name: &str) -> Option<Former> {
621    FORMER
622        .iter()
623        .find(|former| former.name.eq_ignore_ascii_case(name))
624        .copied()
625}
626
627/// Every former name of a built-in rule, in lowercase: the names for which
628/// [`former`] gives a [`Former`].
629pub fn former_names() -> impl Iterator<Item = &'static str> {
630    FORMER.iter().map(|former| former.name)
631}
632
633impl Former {
634    /// The name, in lowercase.
635    #[must_use]
636    pub const fn name(self) -> &'static str {
637        self.name
638    }
639
640    /// What to do instead of extending the rule by its name, a sentence
641    /// without the final period that a launcher can print after its own, for
642    /// example ``Set `dxgi` in a rule for every process or for one program
643    /// instead``.
644    #[must_use]
645    pub fn instead_of_extending(self) -> String {
646        match self.did {
647            Did::Setting(key) => {
648                format!("Set `{key}` in a rule for every process or for one program instead")
649            }
650            Did::Other {
651                extend: Some(extend),
652                ..
653            } => extend.to_owned(),
654            Did::Other { extend: None, off } => off.to_owned(),
655        }
656    }
657
658    /// What to do instead of turning the rule off by its name, a sentence
659    /// like [`Former::instead_of_extending`].
660    #[must_use]
661    pub fn instead_of_turning_off(self) -> String {
662        match self.did {
663            Did::Setting(key) => format!(
664                "Set `{key}` to `default` in a rule for every process to get upstream Wine's \
665                 behaviour, except where a built-in rule for one program sets it"
666            ),
667            Did::Other { off, .. } => off.to_owned(),
668        }
669    }
670}
671
672impl fmt::Display for Former {
673    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
674        write!(
675            f,
676            "{:?} was the name of a built-in rule, and built-in rules can no longer be extended \
677             or turned off by name",
678            self.name
679        )
680    }
681}
682
683#[cfg(test)]
684mod tests;