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;