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