compatdb_core/lib.rs
1//! The per-process rules of this Wine's `compatdb.so`, and the wire format
2//! a launcher hands them over in.
3//!
4//! `compatdb.so` is the unix library Wine's ntdll loads into every process
5//! of the bundle before any PE code runs. It reads the process's image
6//! name and version resource, takes the built-in rules of [`builtin`],
7//! appends the rules a launcher passed in the environment variable
8//! [`ENV_VAR`] (`WINE_COMPATDB`), folds every rule that matches the process
9//! into one [`Resolution`] and applies it: the Direct3D trees to load, the
10//! renderer of wined3d, which OpenGL the Mac driver gives, DLL load-order
11//! overrides, the DPI awareness, large address awareness, whether the Mac
12//! driver reads raw mouse input from `GameController`, whether no-execute
13//! stays on, whether the Metal performance HUD shows, command-line
14//! arguments and environment entries. It logs every value with the rule it
15//! came from: the lines on Wine's stderr that start with `compatdb:`, which
16//! [docs/compatdb.md](https://github.com/athei/mach9/blob/main/docs/compatdb.md#logging-and-verification)
17//! describes, show a launcher author what a process got and why.
18//!
19//! This crate is the only place that knows the rules and their format. A
20//! launcher uses it to build its rules ([`Rule`], [`Table`]), to write the
21//! value of [`ENV_VAR`] (`Table`'s `Display`) and to show what a program
22//! would get ([`Table::preview`]). `compatdb.so` uses the same crate to
23//! read the value ([`Table::parse_lenient`]) and resolve it
24//! ([`Table::resolve`]); the [`runtime`] module holds what only it needs.
25//!
26//! Everything here is pure: it works on `&str`, `&[u16]` (UTF-16 as Wine
27//! hands it over) and owned values, with no unsafe and no I/O, so the
28//! decision logic is unit-tested on the host and the `.so` itself only has
29//! to move bytes in and out of the process.
30//!
31//! # Quick start
32//!
33//! A launcher builds one rule for every process from its launch-wide
34//! settings and one per program it knows something about, writes the
35//! table and starts Wine with it in [`ENV_VAR`]. The whole process tree
36//! inherits the variable, so a rule for a program the game starts reaches
37//! it too.
38//!
39//! The settings layer fills a rule from text without the launcher knowing
40//! any key: [`Key::from_setting`] names the key, [`Key::kind`] says whether
41//! it holds one value, a list or environment entries, and [`Rule::set`],
42//! [`Rule::set_list`] or [`Rule::set_env`] take the value. Every error is a
43//! [`FieldError`] to print as it is, and [`FieldError::severity`] says
44//! whether to warn and go on or to refuse the settings.
45//!
46//! ```
47//! use std::process::Command;
48//!
49//! use compatdb_core::{
50//! Argument, Dxgi, ENV_VAR, Exe, Fingerprint, Key, Rule, Severity, Table, VersionInfo,
51//! };
52//!
53//! // The launch-wide settings, as the launcher's own config holds them.
54//! let mut every = Rule::new(Exe::Any);
55//! for (name, value) in [("dpi_aware", "false"), ("GCMouse", "Default")] {
56//! every.set(Key::from_setting(name)?, value)?;
57//! }
58//!
59//! // A rule for one game, from typed parts.
60//! let game = Rule::new(Exe::new("Game.exe")?)
61//! .with_company(Fingerprint::new("Some Vendor")?)
62//! .with_dxgi(Dxgi::Wined3d)
63//! .with_argument(Argument::new("-windowed")?);
64//!
65//! // A retired value is a warning: the rule stays as it was.
66//! let mut old = Rule::new(Exe::new("Old.exe")?);
67//! let error = old.set(Key::Dxgi, "gptk").err().ok_or("gptk was taken")?;
68//! assert_eq!(error.severity(), Severity::Warning);
69//! assert!(old.sets_nothing());
70//!
71//! let mut table = Table::new();
72//! table.push(every);
73//! table.push(game);
74//! let value = table.to_string();
75//! assert_eq!(
76//! value,
77//! "exe=*;dpi_aware=false;gcmouse=default\n\
78//! exe=Game.exe;company=Some Vendor;dxgi=wined3d;arguments=-windowed"
79//! );
80//! let mut wine = Command::new("bin/wine");
81//! wine.env(ENV_VAR, &value);
82//!
83//! // What that game would get, the built-in rules included.
84//! let version = VersionInfo {
85//! company: "Some Vendor Ltd.".into(),
86//! ..VersionInfo::default()
87//! };
88//! let resolution = table.preview("Game.exe", &version);
89//! assert_eq!(resolution.dxgi(), Some(Dxgi::Wined3d));
90//! assert_eq!(resolution.dpi_aware(), Some(false));
91//! // `default` hands gcmouse back to upstream Wine.
92//! assert_eq!(resolution.gcmouse(), None);
93//! assert_eq!(resolution.arguments, ["-windowed"]);
94//! # Ok::<(), Box<dyn std::error::Error>>(())
95//! ```
96//!
97//! # Format
98//!
99//! The value of [`ENV_VAR`] is one record per line, each a rule:
100//!
101//! ```text
102//! exe=Game.exe;company=Some Vendor;d3d9=wined3d;dpi_aware=false;env=VKD3D_CONFIG=a%3Bb
103//! exe=OldGame.exe;large_address_aware=true
104//! exe=*;gcmouse=false
105//! ```
106//!
107//! - A line ends at LF, and a CR before the LF is dropped. A line of
108//! whitespace alone is skipped. Lines are counted from 1, blank lines
109//! included; that number is the rule's [`Source::Line`], and the line
110//! of each [`Diagnostic`].
111//! - A record is fields joined by `;`. An empty field is skipped.
112//! - A field is `key=value`, and the key ends at the first `=`. The keys
113//! are those of [`Key`].
114//! - Every record needs an `exe`. The other matchers, `company`, `product`
115//! and `original_filename`, are optional (see [Matching](#matching)).
116//! A matcher or a scalar setting holds one value, and a list takes one
117//! entry per field, in order.
118//! - A scalar setting takes one of the spellings of [`Key::values`]: one
119//! of its words, or `default`, which resets it ([`Setting::Reset`]).
120//! - `arguments_override` with an empty value sets the override without
121//! adding an entry, so `arguments_override=` alone clears the arguments.
122//! An empty entry of `arguments` or `debug_channels` is refused.
123//! - An `env` field is `env=NAME=value`. The name ends at the first `=`,
124//! so a name cannot hold one ([`EnvName`]); the value may. An empty
125//! value, or none, removes the variable.
126//! - What each list entry and matcher may hold is the type of its part:
127//! [`Exe`], [`Fingerprint`], [`DllOverride`] (needs a `=`), [`Argument`],
128//! [`DebugChannel`] (no `,`) and [`EnvName`]. None of them holds a NUL.
129//!
130//! Escaping. In a value, `Display` writes `%`, `;` and every ASCII control
131//! character (U+0000 to U+001F and U+007F, CR and LF among them) as `%XX`,
132//! the uppercase hex of the byte. Everything else, `=`, `,`, spaces and
133//! non-ASCII included, is written as it is, in UTF-8. Reading decodes every
134//! `%XX`, in either case, to its byte, and the bytes of a value must be
135//! UTF-8 without a NUL: `%C3%A9` is `é`, and `%FF` or `%00` drops the
136//! field. A `%` that two hex digits do not follow is kept as it is, with a
137//! diagnostic.
138//!
139//! The case rule. Every word the format defines is read
140//! ASCII-case-insensitively and written in lowercase: the keys, the words
141//! of the scalar settings, `true`, `false`, `default`, and the `v` of a
142//! legacy header. User data is kept byte for byte: the `exe`, the
143//! fingerprint values, list entries and env names and values. So
144//! `DXGI=WineD3D` reads as `dxgi=wined3d`, while `exe=Game.EXE` keeps its
145//! case (matching ignores it, see [Matching](#matching)).
146//!
147//! There is no format version. A first line of `v=` and ASCII digits, the
148//! header launchers wrote for an older format, is skipped without a
149//! diagnostic; it still counts as a line. Compatibility is per field
150//! instead: `compatdb.so` ignores a field whose key it does not know or
151//! whose value its key does not take, with a warning, and applies the rest
152//! of the record. A key added later is therefore only ever a setting: the
153//! matcher keys are a closed set, since a library that ignored an unknown
154//! matcher would apply the rule to more processes than it names. The
155//! `name` and `enabled` keys of the older format, where a line changed or
156//! dropped a built-in rule by its name, are unknown keys like any other,
157//! and a `compatdb.so` from before that change drops every line without
158//! `name`, so a table in this format does nothing on an older bundle.
159//!
160//! `Display` writes the canonical text: one line per rule and no line
161//! break after the last, the fields in the order of [`Key::ALL`], every
162//! word in lowercase, and none of the built-in rules, which `compatdb.so`
163//! has compiled in.
164//!
165//! ## Strict and lenient parsing
166//!
167//! [`Table::parse_lenient`] reads the value the way `compatdb.so` does,
168//! which must never abort a process. Each problem is a [`Diagnostic`],
169//! whose `Display` is the warning the library logs:
170//!
171//! - A field with an unknown key, a value its key or its part does not
172//! take, no `=`, escapes that are not UTF-8, or a NUL is left out, and
173//! the rest of its line still applies.
174//! - A line without an `exe` is left out as a whole, since it matches no
175//! process.
176//! - A second field for a matcher or a scalar setting replaces the first.
177//! - A `%` that two hex digits do not follow is kept as it is.
178//!
179//! [`Table::parse_strict`], which is also `Table`'s `FromStr`, refuses
180//! every problem lenient parsing reports, with a [`ParseError`] that lists
181//! them. A launcher that writes the value with `Display` never needs to
182//! read it; strict parsing is for text a person wrote.
183//!
184//! Two guarantees hold, and the [round-trip suite] checks them on
185//! generated tables: a table built with [`Table::new`] and [`Table::push`]
186//! reads back from its text as itself, and canonical text writes back byte
187//! for byte.
188//!
189//! [round-trip suite]: https://github.com/athei/mach9/blob/main/compatdb/core/tests/roundtrip.rs
190//!
191//! ```
192//! use compatdb_core::Table;
193//!
194//! let table: Table = "EXE=Game.exe;DPI_Aware=False;env=VKD3D_CONFIG=a%3Bb".parse()?;
195//! let text = table.to_string();
196//! assert_eq!(text, "exe=Game.exe;dpi_aware=false;env=VKD3D_CONFIG=a%3Bb");
197//! assert_eq!(Table::parse_strict(&text)?, table);
198//! assert_eq!(Table::parse_strict(&text)?.to_string(), text);
199//!
200//! // Lenient parsing keeps what it can read and says what it left out.
201//! let (table, diagnostics) = Table::parse_lenient("exe=Game.exe;bogus=1;gcmouse=yes");
202//! assert_eq!(table.rules().len(), 1);
203//! let warnings: Vec<String> = diagnostics.iter().map(ToString::to_string).collect();
204//! assert_eq!(
205//! warnings,
206//! [
207//! "WINE_COMPATDB line 1: ignoring unknown key \"bogus\"",
208//! "WINE_COMPATDB line 1: ignoring unknown gcmouse value \"yes\"",
209//! ]
210//! );
211//! assert!(Table::parse_strict("exe=Game.exe;bogus=1").is_err());
212//! # Ok::<(), Box<dyn std::error::Error>>(())
213//! ```
214//!
215//! # Matching
216//!
217//! A rule has no name: it is its matchers and its settings
218//! ([`Rule::matches`]).
219//!
220//! - `exe` matches the image basename ASCII-case-insensitively, or every
221//! process when it is `*` ([`Exe::Any`]).
222//! - `company`, `product` and `original_filename` are optional
223//! ASCII-case-insensitive substrings of the image's version strings
224//! `CompanyName`, `ProductName` and `OriginalFilename` ([`VersionInfo`]).
225//! A string the image lacks reads as empty, so a rule that pins a value
226//! for it does not match.
227//! - A rule whose `exe` matches but whose fingerprint does not does not
228//! apply; nothing falls back to it, and the other rules that match still
229//! apply.
230//! - [`Table::duplicate_matchers`] finds rules that match exactly the same
231//! processes, which in a hand-written table is a mistake.
232//!
233//! # Folding
234//!
235//! Every rule that matches a process applies; no rule removes or replaces
236//! another. [`Table::resolve`] folds them in this order, and within each
237//! group a rule without a fingerprint before one with, otherwise in table
238//! order (line order for [`ENV_VAR`]):
239//!
240//! 1. the built-in `*` rules;
241//! 2. the `*` rules of [`ENV_VAR`];
242//! 3. the built-in rules that name an executable;
243//! 4. the rules of [`ENV_VAR`] that name an executable.
244//!
245//! So a launch-wide `*` rule replaces a built-in default but not what a
246//! built-in rule for one program sets: that fix survives launch-wide
247//! settings, and a rule of the launcher for that program changes it, with
248//! or without a fingerprint. Within [`ENV_VAR`], a rule for an executable
249//! beats a `*` rule whatever their line order.
250//!
251//! - A scalar setting ends up with the value of the last rule that sets it.
252//! `default` resets it: from that rule on it is unset, as if no rule had
253//! set it, so upstream Wine decides unless a rule folded later sets it
254//! again. For most settings that is the same as one of the values, but
255//! not for `dpi_aware`: `false` makes a program unaware, `default` leaves
256//! it to the registry and the manifest.
257//! - The lists accumulate in fold order.
258//! - `arguments_override` replaces the arguments the rules folded before it
259//! gathered, the built-in ones included, with its own entries; the
260//! rule's own `arguments` and those of the rules folded after it still
261//! append.
262//! - A `dll_overrides` entry with the order `default`, such as
263//! `mscoree,mshtml=default`, takes its modules out of the entries the
264//! rules folded before it gave them and adds none, so the registry and
265//! `WINEDLLOVERRIDES` decide for them again.
266//! - The `env` entries of a built-in `*` rule without a fingerprint are
267//! defaults: one sets its variable only where the process does not have
268//! it, so a value whoever started the process exported wins, an empty
269//! one included.
270//! Every other entry, a `*` rule's of [`ENV_VAR`] included, replaces the
271//! variable, and an entry without a value removes it.
272//! [`Resolution::env_steps`] says what each entry does in a process.
273//! - `wined3d` and `metal_hud` become env entries too
274//! ([`Resolution::fold_derived_env`]): `wined3d` the `WINE_D3D_CONFIG`
275//! after every rule's own entries, `metal_hud` the `MTL_HUD_` variables
276//! where the rule that set it folds, before its own entries, so an `env`
277//! entry for one of them from that rule or a later one wins.
278//! - A program inherits the environment of the process that started it,
279//! the values `compatdb.so` set there included. A value a rule for one
280//! program sets for a variable with a `*` default therefore carries into
281//! the programs it starts, whose default finds the variable set. A
282//! value it removes does not: the default sets it again in the child.
283//! The settings ntdll stores for the process (`dpi_aware`,
284//! `large_address_aware`, `gcmouse`, `force_nx`, `opengl`) are its own:
285//! a child gets the value of its own rules.
286//!
287//! [`Resolution`] keeps where each value came from ([`Origin`],
288//! [`ScalarState`]), which `compatdb.so` logs.
289//!
290//! ```
291//! use compatdb_core::{Dxgi, Exe, Origin, Rule, Scalar, Table, VersionInfo};
292//!
293//! let mut table = Table::new();
294//! table.push(Rule::new(Exe::Any).with_dxgi(Dxgi::Wined3d));
295//!
296//! // A game gets the launch-wide value over the built-in default.
297//! let game = table.preview("Game.exe", &VersionInfo::default());
298//! assert_eq!(game.dxgi(), Some(Dxgi::Wined3d));
299//! assert_eq!(game.scalar(Scalar::Dxgi).set_by, Some(Origin::Line(1)));
300//!
301//! // Steam's web helper keeps the DXMT its built-in rule pins.
302//! let helper = VersionInfo {
303//! company: "Valve Corporation".into(),
304//! product: "Steam Client WebHelper".into(),
305//! ..VersionInfo::default()
306//! };
307//! let steam = table.preview("steamwebhelper.exe", &helper);
308//! assert_eq!(steam.dxgi(), Some(Dxgi::Dxmt));
309//! assert!(matches!(
310//! steam.scalar(Scalar::Dxgi).set_by,
311//! Some(Origin::BuiltInFor(_))
312//! ));
313//!
314//! // A rule of the launcher for the helper changes it.
315//! table.push(Rule::new(Exe::new("steamwebhelper.exe")?).with_dxgi(Dxgi::Wined3d));
316//! let steam = table.preview("steamwebhelper.exe", &helper);
317//! assert_eq!(steam.dxgi(), Some(Dxgi::Wined3d));
318//! # Ok::<(), Box<dyn std::error::Error>>(())
319//! ```
320//!
321//! # Keys
322//!
323//! [`Key`] lists every field. Each variant says what it does and what a
324//! process gets without it, and links to the getter of [`Rule`] with the
325//! details: when it applies, which part of Wine reads it and which
326//! built-in rule sets it.
327//!
328//! - Matchers: [`Key::Exe`], [`Key::Company`], [`Key::Product`],
329//! [`Key::OriginalFilename`].
330//! - Scalar settings, each a [`Scalar`] with a [`Setting`]:
331//! [`Key::Dxgi`], [`Key::D3d9`], [`Key::Wined3d`], [`Key::OpenGl`],
332//! [`Key::DpiAware`], [`Key::LargeAddressAware`], [`Key::Gcmouse`],
333//! [`Key::ForceNx`], [`Key::MetalHud`].
334//! - Lists: [`Key::DllOverrides`], [`Key::ArgumentsOverride`],
335//! [`Key::Arguments`], [`Key::DebugChannels`].
336//! - Environment entries: [`Key::Env`].
337//!
338//! [`Key::kind`] says which of these a key is, [`Key::values`] gives the
339//! spellings of a scalar setting, `default` included, [`Key::label`] its
340//! name for a person, and [`Key::upstream`] what upstream Wine does
341//! without it, as `compatdb.so` logs it. For a launcher's templates and
342//! help, [`Key::summary`] says in one line what a key does,
343//! [`Key::example`] gives a value to show and [`Key::docs_url`] links to
344//! its doc; [`DocSection::url`] links to a section of this page.
345//!
346//! # Built-in rules
347//!
348//! [`builtin`] holds the rules compiled into `compatdb.so`, one function
349//! each, whose doc says why the rule exists. [`builtin::defaults`] match
350//! every process: a behaviour of this Wine that differs from upstream Wine
351//! and that a rule can switch is off in the code, and one of these turns
352//! it on, so the log of a process shows its whole configuration with the
353//! source of every value, and a launcher rule that sets the same key
354//! changes it. [`builtin::programs`] each name an executable and pin its
355//! version resource, so none of them catches an unrelated program of the
356//! same name; a test enforces this.
357//!
358//! # Recipes
359//!
360//! Each is one line of [`ENV_VAR`]; `Game.exe` stands for the game's
361//! executable. [`Rule::set`] with the same keys and values builds them.
362//!
363//! | Goal | Rule |
364//! |------|------|
365//! | Re-enable Wine's Mono for every process | `exe=*;dll_overrides=mscoree=b` |
366//! | Prefer a native .NET | `exe=*;dll_overrides=mscoree=n,b` |
367//! | Hand Mono and Gecko back to the registry and `WINEDLLOVERRIDES` | `exe=*;dll_overrides=mscoree,mshtml=default` |
368//! | Make one game DPI-unaware | `exe=Game.exe;dpi_aware=false` |
369//! | Let the registry and the manifest decide DPI awareness | `exe=*;dpi_aware=default` |
370//! | Give one 32-bit game 4 GB | `exe=Game.exe;large_address_aware=true` |
371//! | Give one game the cursor's mouse movement | `exe=Game.exe;gcmouse=false` |
372//! | Give every process wined3d for D3D10 and D3D11, and no D3D12 | `exe=*;dxgi=wined3d` |
373//! | Give every process wined3d for D3D9 | `exe=*;d3d9=wined3d` |
374//! | Hide D3D12 from one game, keeping DXMT for D3D10 and D3D11 | `exe=Game.exe;dll_overrides=d3d12,d3d12core=` |
375//! | Change a vkd3d-proton setting for one game | `exe=Game.exe;env=VKD3D_CONFIG=<options>` |
376//! | Give one game Wine's no-execute behaviour | `exe=Game.exe;force_nx=false` |
377//! | Give one game FEX's full x87 precision | `exe=Game.exe;env=FEX_X87REDUCEDPRECISION=` |
378//! | Give one game Apple's OpenGL | `exe=Game.exe;opengl=cgl` |
379//! | Put wined3d on Vulkan for one game | `exe=Game.exe;wined3d=vulkan` |
380//! | Show the Metal HUD in one game | `exe=Game.exe;metal_hud=true` |
381//! | Leave one game to upstream Wine for a setting | `exe=Game.exe;<key>=default` |
382//! | Replace the arguments the rules give one game | `exe=Game.exe;arguments_override=-windowed` |
383//! | Give one game no arguments from any rule | `exe=Game.exe;arguments_override=` |
384//! | Change what a built-in rule sets for one program | `exe=Launcher.exe;dxgi=dxmt` |
385//!
386//! Most of them with `exe=*` do the same for every process, except where a
387//! built-in rule for one program sets the key. [`Rule::set`] refuses
388//! `arguments_override` in a `*` rule: for every process it would take away
389//! the arguments the built-in rules give single programs.
390//!
391//! Hiding D3D12 disables both of vkd3d-proton's modules, so `d3d12.dll`
392//! fails to load and a game that can use D3D11 falls back to it on DXMT; a
393//! game that only has D3D12 does not start with it, nor with
394//! `dxgi=wined3d`.
395//!
396//! # Launcher responsibilities
397//!
398//! - Write the value with `Table`'s `Display` and nothing else. Never
399//! write the format by hand, add a version header or send the built-in
400//! rules: `Display` leaves them out, since `compatdb.so` has them.
401//! - Remove the variables of [`inherited_conflicts`] from the environment
402//! Wine starts with when the launcher inherited them, so a launch does
403//! not depend on where the launcher was started from.
404//! - Build `WINEDEBUG` itself: Wine reads it before `compatdb.so` loads.
405//! Its own entries come first, then [`Table::winedebug`], joined with
406//! `,`.
407//! - Ask the crate instead of naming keys: [`Key::replaces_on_merge`] when
408//! it lays one layer of its configuration over another, and
409//! [`Key::allowed_on_any`] for the settings it offers for every process.
410//! - Refuse an environment entry for a variable a setting replaced
411//! ([`retired_env`]); [`Rule::set_env`] does, and [`check_env_name`]
412//! checks a name on its own. That includes
413//! `WINEDLLOVERRIDES`: an override `compatdb.so` adds replaces its entry
414//! for the same module, so it cannot undo one, while a rule's
415//! `<module>=default` can.
416//! - Show or hide the Metal HUD with [`Key::MetalHud`], not with its own
417//! `MTL_HUD_` entries in `env`.
418//! - Put `env` entries and `metal_hud` on the rule of the program that
419//! needs them, not on a program that starts games, such as a store
420//! client: the games it starts inherit them.
421//! - Name a former built-in rule with [`builtin::former`], which says what
422//! to do instead; such a name reaches nothing any more.
423//! - Expect a changed table to reach the processes that outlive one
424//! launch, such as `explorer.exe` and `services.exe`, only in the next
425//! wineserver session: they keep the rules of the launch that started
426//! it.
427//!
428//! ```
429//! use compatdb_core::{Exe, FieldError, Key, Rule, Table, inherited_conflicts, retired_env};
430//!
431//! let mut game = Rule::new(Exe::new("Game.exe")?);
432//! game.set(Key::DebugChannels, "+relay,-seh")?;
433//! let mut table = Table::new();
434//! table.push(game);
435//! let mut winedebug = vec![String::from("-all")];
436//! winedebug.extend(table.winedebug());
437//! assert_eq!(winedebug.join(","), "-all,Game.exe:+relay,Game.exe:-seh");
438//!
439//! let mut every = Rule::new(Exe::Any);
440//! let error = every.set_env("WINEDLLOVERRIDES", "mscoree=b").err();
441//! assert!(matches!(
442//! error,
443//! Some(FieldError::RetiredEnv {
444//! key: Key::DllOverrides,
445//! ..
446//! })
447//! ));
448//! assert_eq!(retired_env("WineDebug"), Some(Key::DebugChannels));
449//! assert!(inherited_conflicts().contains(&"WINEDLLOVERRIDES"));
450//! # Ok::<(), Box<dyn std::error::Error>>(())
451//! ```
452#![forbid(unsafe_code)]
453#![deny(missing_docs)]
454#![deny(
455 clippy::unwrap_used,
456 clippy::expect_used,
457 clippy::panic,
458 clippy::indexing_slicing,
459 clippy::arithmetic_side_effects
460)]
461
462/// The text of [`DOCS_URL`] as a literal, so `concat!` can build the links
463/// of [`Key::docs_url`] and [`DocSection::url`] from it.
464macro_rules! docs_url {
465 () => {
466 "https://docs.mach9.app/compatdb_core/"
467 };
468}
469
470pub mod builtin;
471mod docs;
472mod error;
473mod key;
474mod part;
475mod resolve;
476mod rule;
477pub mod runtime;
478mod settings;
479mod table;
480mod value;
481mod version;
482mod wire;
483
484pub use crate::{
485 docs::DocSection,
486 error::{Diagnostic, FieldError, ParseError, Severity},
487 key::{Key, KeyKind, Scalar, ScalarValue, ValueKind},
488 part::{
489 Argument, DebugChannel, DllOverride, EnvEntry, EnvName, Exe, ExeName, Fingerprint,
490 Specificity,
491 },
492 resolve::{
493 ArgumentsDropped, DllOverrideReset, EnvFrom, EnvStep, Origin, Resolution, ScalarState,
494 },
495 rule::{DEFAULT_VALUE, Rule, Setting},
496 settings::{
497 check_env_name, inherited_conflicts, retired_env, retired_envs, retired_keys,
498 retired_values,
499 },
500 table::{Source, Table},
501 value::{D3d9, Dxgi, METAL_HUD_ENV, OpenGl, Wined3d, metal_hud_env},
502 version::VersionInfo,
503};
504
505/// The environment variable `compatdb.so` reads extra rules from. Wine itself
506/// never looks at it; it is only the channel from whatever started the process
507/// tree to the library.
508pub const ENV_VAR: &str = "WINE_COMPATDB";
509
510/// Where this crate's rustdoc is published. [`Key::docs_url`] and
511/// [`DocSection::url`] link into it.
512pub const DOCS_URL: &str = docs_url!();