Madeira Dock starts a Steam game that Steam's client has installed in the prefix, through Valve's genuine Windows Steam client, without the Steam desktop window, its Chromium web helper or its library UI.
It is a small headless Windows program (dockhost.exe). Inside Madeira's Wine
session it loads the client's own libraries, signs in with the user's own
Steam sign-in and asks the client to start the game with Valve's own
LaunchApp. Valve's client performs the online authentication and the licence
check. The game, its Steam API calls and any DRM run exactly as Valve ships
them.
- Not a Steam emulator, not a DRM bypass, no ticket forging, no game patches. Without Valve accepting the sign-in and confirming the licence, the game does not start, and there is no fallback that starts it anyway.
- Not a Valve SDK use. Dock drives undocumented internal interfaces of
Valve's client DLL (
steamclient64.dll). That is fragile by nature, so Dock only drives client builds it has been verified against: it hashes the DLL (SHA-256) and checks the exact method addresses it calls. Any other build fails closed before sign-in, with report code 30 and the client's fingerprint in the log. Two client builds are supported at the pinned commit; seemadeira-dock/docs/CLIENT_LAYOUTS.md. - No Valve binaries, game content, cached login or token is bundled or committed.
-
Source: the
madeira-docksubmodule (https://github.com/125hz/madeira-dock, pinned ate1bf62f), about 1,850 lines of C. Copyright 2026 125hz, GPL-3.0-or-later with the Madeira Converter Exception (the owner open-sourced it on 2026-09-27; it used to be a closed executable). -
Research references: OpenSteamworks (MIT) and Valve's public Steamworks documentation and headers were consulted. According to the Dock repository's
THIRD-PARTY-NOTICES.md, no code was copied from them. -
Build:
build/madeira-dock/build.shcross-compiles it with llvm-mingw (x86-64 PE, static runtime, stripped, reproducible: no PE timestamp) and stages two files in the bundle:app/Madeira/arm64ec-windows/dockhost.exeapp/Madeira/arm64ec-windows/dock-notices.txt(Dock's licence, the exception, the GPL text and the LLVM/MinGW-w64 runtime notices).
Both are build outputs and are gitignored;
tools/build-ipa.sh(and so the CI IPA) runs the script, since withoutdockhost.exethe app hides Dock and the Steam library.--checkalso runs Dock's own unit tests. Without a builtdockhost.exe, Madeira shows no Dock button.
Dock reads Steam's launch configuration before handing its sign-in to Valve's
client. It selects the installed Windows game entry (default first, excluding
DLC-only and tool entries) and preserves its original config.launch key, which
need not be 0. Old caches without keys are fetched again. With missing metadata or
no eligible installed entry (a launcher started through a .bat, say) it uses key
0, as every Dock start did before; the host then stops at once if Steam names that
entry missing, instead of waiting for configuration. The host receives that
key in MADEIRA_STEAM_HOST_LAUNCH_OPTION and uses it for every LaunchApp retry.
Its report exposes only numeric launch-option-index, launch-option-invalid
and launch-option-missing fields; a confirmed missing entry ends the config
wait. Rebuild the Dock host together with the app for this change.
The Steam/Dock Swift and C sanitizer regressions passed, and a tester confirmed startup of a previously failing game on an M2 iPad using a rebuilt Debug IPA. This verifies startup for that device test, not extended gameplay coverage.
The developer interface has a Madeira Dock button (when dockhost.exe
is built; env.MADEIRA_DOCK = 0 hides it). In the library the same sheet is
Settings › Steam › Madeira Dock, and first-run setup offers step 2 below
(docs/LIBRARY.md, "Steam setup"). A start from the library runs as a
library session. The sheet has:
-
Steam account. Sign in with Madeira's Steam sign-in (
docs/STEAM_SIGNIN.md). -
Steam client. "Download Valve's client components" fetches three pinned packages (about 73 MB) from Valve's update CDN (
client-update.akamai.steamstatic.com, HTTPS, redirects must stay on that host). Each package is checked against a pinned size and SHA-256 sum before it is read. The ZIP reader rejects traversal, symlinks, duplicates, encryption and ZIP64. The unpackedsteamclient64.dllmust be the pinned build the Dock host supports.The files go to
C:\Program Files (x86)\Steam, and Steam's discovery registry keys (install path, client DLL paths; nothing account-related) are written. This happens without starting Wine. Existing Steam files are kept; conflicting ones stop the setup. -
Installed games. Read from Steam's own
steamapps/appmanifest_<appid>.acffiles in the client's library and in the other C: libraries itslibraryfolders.vdflists. Only games Steam marks fully installed can be started.Dock does not install games. They must already be installed in the prefix by Steam's client, for example with the desktop client, or copied together with their app manifest. Valve's client may still update a game's content when Dock starts it, and Dock waits for that.
-
Smaller JIT pool (512 MB) for this launch. Opt-in. The toggle starts from
env.MADEIRA_DOCK_COMPACT_POOLand is off unless that is1. -
One-time installs. Listed for each installed game whose Steam install script has programs to run (see below): "Run at next start" or "Skip". A game starts at "Run at next start"; a start that runs its programs turns it to "Skip".
-
Steam starts. "Steam's choice", or a program in the game's folder. Madeira can start only one 32-bit program per session, so a game whose 32-bit launcher starts the game (Fallout: New Vegas) ends the session at the launcher's Play. With a program chosen, the next Dock start, while no session runs, keeps the program Steam's launch entry names beside it as
<name>.madeira-launcherand copies the chosen program in its place, so Valve's client still starts the game and checks ownership. "Steam's choice" puts the kept launcher back at the next start. Log lines:[dock-start-program].
Tap a game. Dock is started with Steam's default launch option only; custom arguments are not supported.
-
Madeira checks: JIT is enabled, no session has run in this app run,
dockhost.exeis bundled, the client DLL exists, the game is fully installed and its folder exists, and a sign-in is stored. Then the app's own Steam connection (the owned library,docs/STEAM_LIBRARY.md) logs off and closes its socket, and Madeira waits for that before going on: a second online sign-in of the same account would replace the client's session. It stays off until Dock's report or session is over. The checks run again. -
One-use sign-in transfer.
SteamSignIn.credentialsForDock()supplies the account name and refresh token from the Keychain. Madeira writes them, with the account's SteamID (taken from the token's subject claim to select the account, not trusted as identity) and the App ID, into a bounded file (MDOCK001: 8-byte magic, u64 SteamID, u32 App ID, u16 lengths, account, token). The file is:- in
Application Support/MadeiraDock/launch.auth, with complete file protection, mode 0600, excluded from backups; - created exclusively (
O_EXCL | O_NOFOLLOW); - reached by the guest only through its path, in
MADEIRA_DOCK_AUTH_FILEvia Wine's\\?\unixnamespace (the prefix has no guaranteed Z:).
Dock opens it exclusively with delete-on-close, clears its buffers and hands the token to the verified client's own sign-in method. Madeira removes any leftover when the result is reported, on sign-out and at the next app start. No token, account name or path is logged.
- in
-
The host's inputs go in its environment:
MADEIRA_STEAM_HOST_*gates, App ID, client folder, the expected install folder (Valve's client must resolve the game to exactly that folder) and the report path. The cached-account variables are always cleared. The launch also sets the engine's opt-in image-retire switch,MADEIRA_JIT_IMAGE_RETIRE=1, for this session only and logs[dock-launch] image-retire=1(see Known risks). Other sessions keep the engine default (off). It also sets Wine's opt-in image-map guard,MADEIRA_IMAGE_MAP_GUARD=1, and logs[dock-launch] image-map-guard=1: while the loader loads Valve's client and its imports, the emulator's image-map handler can commit a page of its own heap under its interval lock, and that commit's notification waits for the same lock. The host then parks for good with no report line afterload-client-begin. With the guard, the emulator's own memory calls during that notification are not notified again, as on the syscall path; ntdll logs[ldr-image] image-map guard on. Other sessions keep Wine's default (off). -
The session is the normal Wine session:
explorer.exe /desktop=madeira,<W>x<H> C:\windows\system32\dockhost.exe. The size comes fromdesktop-sizein madeira.cfg, else the device's own default size (the library's default Resolution: 1408x648 on a 19.5:9 iPhone, 1152x800 on an 11-inch iPad). When the game has one-time installs to run, it is... C:\windows\system32\cmd.exe /c call C:\madeira-dock-installers.cmd & C:\windows\system32\dockhost.exe: the installers first, then the host, in the same session.madeira-bcd: such a start runs only the installers (
... cmd.exe /c call C:\madeira-dock-installers.cmd,MADEIRA_DOCK_INSTALL_SEPARATE). In one session, the installers' copies in the JIT pool left Red Dead Redemption 2 too little room, and it stopped while loading ([jit-pool] EXHAUSTED). The session ends after the batch; the app runs one session per run, so Madeira then asks for a restart ("Restart Madeira" alert), and the game starts at the next start. If a program an installer started keeps the session open, the alert comes 45 s after the batch ended (the wineserver saves the registry every 30 s). That session runs without the game'senv.MADEIRA_SC_PA_POOLSandenv.MADEIRA_RDR2_VA_HOLDlines (MADEIRA_DOCK_INSTALL_LAYOUT): their holds take the 4 GB slots 32-bit programs run in, and the Visual C++ setups' second 32-bit process could not start (exit 0x8007054f).The session publishes no fixed Steam game identity. Every other launch gives its guests one title's
SteamAppId,SteamGameIdandSteamAppPath(a known compromise inWineProcessBridge.m); in a Dock session they reached Valve's client, which runs inside the host, and the game it starts. A Dock launch setsMADEIRA_DOCK_SESSION=1and the bridge leaves the three unset ([steam-env] Madeira Dock session: ...); Valve's client gives the game its own.env.MADEIRA_DOCK_CLEAR_STEAM_ID = 0keeps the fixed identity. -
The report. Dock writes numeric stages to
C:\madeira-dock.txt. Madeira reads only whitelisted numeric fields and the 64-hex client fingerprint (never other guest text), logs changes as[dock-report], and turns the finalprobe-resultinto a message. Covered: unsupported client build, no licence, a licence Valve's client did not confirm within the host's 90 s after sign-in (34), a bad transfer, Valve's own launch refusal codes, and per-user executable preparation errors. The game session's service manager and hold reportgame-scm-services,game-scmorgame-scm-timeout, andlaunch-keep-alivethenlaunch-keep-alive-held(roundml2020; the Switches table below has the three switches). The first 16 callback IDs Valve's client posts after sign-in are logged in order assession-callback-ids(numbers only), withsession-requested-app-entitledandsession-subscription-count, so a sign-in that never reaches the licence check can be told apart from one whose licences never arrive. -
Another session. If Valve's client refuses the launch with 35 (Steam still counts the account as playing in another session, which also happens for a session that ended without telling Steam until its old connection times out), Dock asks again every 15 s for up to three minutes (
launch-session-wait,launch-session-gave-up). The sheet shows the wait; only Valve's own later success starts the game, and a session that is really playing elsewhere still fails with its own message.
Valve's client can sign an account in without a connection, with an offline logon
ticket that Steam issues to it during an online sign-in and that the client stores
itself (desktop Steam's Offline Mode). The Dock uses that mechanism as it is
(madeira-dock/docs/OFFLINE.md); Madeira stores no ticket and decides nothing
about ownership.
- Online starts are unchanged. Once Valve's client has confirmed the game's
license, the host asks it whether the account can log on offline and reports the
answer (
session-offline-ready). When it is 1, the app notes that the ACCOUNT can sign in offline (DockOffline: one date for the account and the games started online since, in the app's preferences; removed with the sign-in). Steam's offline sign-in is per account: with it, Valve's client answers the license question for any installed game from its cached list, started online before or not. - A start with no network path (
NWPathMonitor) setsMADEIRA_DOCK_OFFLINE=1for the host, which hands over the sign-in as usual, asks Valve's client whether it can log on offline, and only then asks it to. The client answers the license question from what it cached and starts the game through the same launch call. A network that fails after the start falls back the same way 20 s in. - Game details shows a small line under the title for games the Dock starts: "Can be played offline" (green check) once the account is saved; "Start a game online to play offline" before that; and "Start once online to play offline" for a game with per-user executables that has not started online yet (Steam prepares those over the network on the first start). Tapping it explains. The mark is a note of the last online start; whether an offline start goes ahead is always Steam's decision at that moment, and Steam's offline sign-in expires on Steam's own schedule.
- A refusal ends the start with its own message: nothing saved to sign in offline (50), offline sign-in refused (51), game not in what Steam saved (52).
[dock-offline] logs the request and the save; [dock-report] session-offline-*
carries the host's numbers. Steam Cloud cannot be checked without a connection, so
the existing "could not be checked" question still appears before an offline start.
Device-run once (2026-10-04, iPhone 18,3 / iOS 27.0, no network path): the offline
request, Steam's offline sign-in, the license from its cache and the game's window
8 s after the host started; the refusals and the fallback have not been exercised.
A Dock start from the library (Settings › Steam › Madeira Dock, or Play on a
Steam game's Game details page) is a desktop session, and the library's
starting screen used to end on the desktop's first GDI frame: explorer's
windows and the host's console window, seconds before the game. The user saw
the Wine desktop instead of the game. Now (DockStartScreen.swift) the
starting screen of a Dock start shows the game's Steam cover and background
art (public store artwork by App ID), its title, a spinner, a status line
with the seconds so far, and one row of round glyph buttons (their words are
VoiceOver labels): Close session once Dock has stopped, Show live log,
and Show desktop while the desktop is held back. It stays until the
game's own window is shown.
The status line follows the furthest stage in the host's report
(DockStartStatus, read once a second):
| Report | Status line |
|---|---|
| nothing yet | "Starting Madeira Dock…" ("Still starting Madeira Dock…" after 30 s) |
| one-time installs running | the program running now, and any that failed |
one-time installs ended ([dock-installers] end) |
"One-time installs finished: N of M succeeded." and the failed ones, then "Starting Madeira Dock…" (late 30 s after the installs' end); madeira-bcd, in a session of installs only: "This session now closes…", then the restart alert |
probe-start-bits |
"Loading Steam…" |
session-native-token-submitted or session-logon-start-result |
"Signing in to Steam…" |
session-authenticated-online=1 |
"Signed in. Waiting for Steam to confirm this game's license…" |
session-requested-app-listed=1 |
"License confirmed. Steam is starting the game…" |
ceg-scm, ceg-request-busy or ceg-request without ceg-result |
"Steam is preparing this game's executable…" |
launch-client-error=0 |
"The game is starting. Waiting for its window…" |
Steam's own waits come first: content the game needs (launch-update-wait),
the game's configuration right after sign-in (launch-config-wait with 22 or
23), and Steam's other session still counted as playing (35). The host
reports only numbers; the words are the app's. When the host reports a
result while the starting screen is up, the screen says "Madeira Dock
stopped" with the report's words (the host waits for the game it started, so
a result before any game window means the game did not start).
Which window is the game's. Winios keeps a census of the desktop's
top-level windows while a Dock start's starting screen is up: per window,
whether it is shown, its size, whether it has put a frame on screen (a GDI
frame or a D3D swapchain of its own), whether it has a title bar
(WS_CAPTION), and the owning process with its executable path, read once
per process from the Wine server by process id.
The app classes the owner by where the program lives on drive C, never by
its name:
| Owner's executable | Class | Effect |
|---|---|---|
under C:\windows\ |
helper | never the game: Wine's shell, services and console hosts, msiexec, and the Dock host itself |
under the Steam client's folder, outside its steamapps library |
client | a shown, drawn dialog (240x120 or larger) may need the user |
| anywhere else | other | the game's (or its own launcher's) once shown, drawn and 160x120 or larger; a smaller shown, drawn window with a title bar (the game's own message box) may need the user |
| unreadable | unknown | the game's only when D3D frames reached the screen |
A window first shown before the host started (its first report field,
probe-start-bits) belongs to the one-time installs, which run before the
host: it is never the game's, and a dialog of it may need the user (a failed
installer can wait for OK). A game that fails early can do the same with its
own message box (Unity's "Failed to load il2cpp" is 179x114), smaller than a
game window and than a dialog: its title bar tells it from a splash screen or
a tool strip. A window that may need the user and stays up for
2 s reveals the desktop; 4 s after it is gone the starting screen returns, at
most six times. Show desktop reveals the desktop for good. The game's
window ends the starting screen in every case. [steam-launch-view] logs the
hold, scene changes (window size and owner class, never a path) and reveals.
A game window born minimized. Some games show their main window minimized
the first time (parked at -32000,-32000). On Windows the taskbar brings it
back; the desktop here has none, so the starting screen waited for a window
that never appeared. While the census runs, a top-level window whose first
show is minimized gets what a taskbar click sends, once: WM_SYSCOMMAND /
SC_RESTORE, posted, and its own thread then brings it to the front from its
event pump (a fullscreen game pauses without focus). A window that was shown
and minimized itself later is left alone. [born-minimized] logs both steps.
The starting screen often ends on a splash or launcher window, before the
game's main window is shown at all, so the restore keeps running after the
census ends (counting nothing else) until the session ends.
MADEIRA_RESTORE_BORN_MINIMIZED_LATE=0 ends it with the census, as before.
The census runs only for a Dock start's starting screen: with it off, each hook costs one atomic load, and nothing else changes for any other session, direct launch or desktop.
A game's Steam install script (installscript.vdf) lists programs Steam's
desktop client runs before a first start, typically runtime setups, each
recorded in the registry once it has run ("Run Process", HasRunKey,
MinimumHasRunValue). Valve's client runs them as part of its own launch
tasks, before the step Dock asks it for, so on the Dock route nothing ran them.
DockInstallers.swift does, right before a Dock start, while no session runs:
-
Find. Scripts in the game's folder (one level down) and in
common/Steamworks Shared/_CommonRedist(three levels down). Repeated "Run Process" sections all count. An entry without aHasRunKeyis recorded where Valve's client records it, a DWORD named after the entry underHKLM\Software\Valve\Steam\Apps\<appid>. HKLM keys are read and written in both registry views. -
Plan. Each program is
done(recorded at least at its minimum),missing(not on disk; paths resolve case-insensitively inside drive_c),unsupportedorpending; at most 8 run per start (limit: the next start).unsupporteddepends only on what the program is and what the bundle has: a Windows Installer package needsmsiexec.exein the bundle, a 32-bit executable (PE machine 0x14c) needsi386-windows. There is no list of program names; every runtime setup is treated alike. -
Choice. With "Skip", nothing runs. Otherwise the pending programs go into
C:\madeira-dock-installers.cmdand the choice turns to "Skip" (unless some programs wait for a later start). -
Batch. It first runs
dockhost.exe --start-services: installers expect a service manager, and a Dock session starts none before the host. That mode loads no Steam client, starts Wine'sservices.exeonly if no manager answers, gives up after 20 s and writes no report file. Then each program runs once (.msithroughmsiexec.exe /i). Each start and exit status goes toC:\madeira-dock-installers.result; the batch writes nothing to the registry. -
Record. At the next Dock start, when no session runs, Madeira reads the result file and records the programs that exited with 0, 3010 or 1641 in
system.reg/user.reg(a.madeira-bakcopy is kept once). Failed programs are not recorded; with the choice at "Skip" they do not run again at every start. -
Sync engine for that session. With madsync, Wine's
services.exenever answered its RPC clients in the fork's device runs, so the service step and then every installer waited; msiexec's custom actions use the same RPC server. A start that runs installers therefore setsMADEIRA_MADSYNC_SESSION=0before its session starts, andbuild/madsync/madsync.cturns madsync off for that session (the app runs one session per run). Every other session keeps the configured engine. -
fusion.dll. An installer with a managed step (DirectX setup's is one) loads
fusion.dllfromC:\windows\Microsoft.NET\Framework\v2.0.50727. Wine gets that file from its Mono package, which Madeira does not ship; in the fork's device runs such a setup ended with -9. Before a start that runs installers, Wine's own builtin 32-bitfusion.dll(i386-windows/fusion.dll) is copied there if the file is missing. A bundle without 32-bit Windows DLLs has nothing to copy (no-source). -
Unfinished programs. A program the batch started but never saw exit (an installer window that stays open after its work, a session ended early) is kept per game in
madeira-dock-installs.jsonuntil the game's next batch or request. Later starts log it (last-run=unfinished,unfinished=) and the Dock note names it. While a program runs, the status names the minutes after 10 minutes and onestill runningline is logged; nothing is stopped. madeira-bcd: the Rockstar Games Launcher installer was one of these. Its install page repaints its status line without end, and paint goes before timer, so the timer its status text and finish page run on never fired. The wineserver now lets an expired timer that has waited 100 ms go before such a paint (build/wineserver/queue_ios.c,env.MADEIRA_TIMER_FAIRNESS, log[timer-fair]). -
Record as done / Reset. Under each game's choice, in the Dock sheet and the game's Steam section. Each is confirmed, saved in
madeira-dock-installs.json, can be cancelled until the game's next Dock start, and is applied by that start before it plans, while no session runs. Both act only on the game's own programs (under its install folder); shared Steamworks redistributables are left as they are. Record as done writes their records as a finished run would. Reset removes them (log[dock-reset]) and sets the choice to "Run at next start", so they run again at that start; files the installers wrote stay in place.madeira-bcd (
DockReset.swift,MADEIRA_DOCK_RESET_CLEAN): Reset first takes out what a Rockstar game's one-time installs put into the prefix, as on a new device: the Rockstar Games Launcher and Social Club (folders under Program Files and ProgramData, registry keys, uninstall entries, the Rockstar Service), the Visual C++ 2015-2022 runtimes x86 and x64 (uninstall entries, Windows Installer registrations, Burn dependency keys, the Package Cache, the regular runtime files in system32 and syswow64; Madeira's own files there are links and stay) and the Vulkan runtime. The VC++ markers a new prefix has go back to the bundled template's values. "Reset and sign out of Rockstar" also takes the launcher's and Social Club's sign-in data. Nothing is deleted: it all moves intoDocuments/madeira-reset-backup/<time>-app<id>/drive_c/, next to the registry files as they were and a list (reset.txt). The game, its Steam files, its settings and saves stay. Another installed game whose own programs record under the same keys, or run the same installer, gets its records removed and "Run at next start" too.
The plan, each program's fate and the results are logged as
[dock-installers]; the Dock sheet's status and the starting screen show the
plan, the running program and, once the batch ended, how many succeeded.
madeira-dock-installs.json next to the registry files holds the
batch's program list and each game's choice.
If the install record lists per-user custom executables (CheckGuid), Dock
asks Valve's client to prepare them before launching. First it starts Valve's
registered Steam Client Service through the service manager and waits until
it runs (30 s bound, then a 2 s settle; ceg-service-start), because under
emulation the client's own demand start was still in progress when the
request failed with 55 (RemoteCallFailed). A request that still fails with 55
is sent again, up to 3 times 1.5 s apart (ceg-request-retry). When the
client answers that it started no job, because it already produced this
account's executables (every launch after the first, until a repair or
update replaces the files), the game starts at once (ceg-already-prepared=1,
ceg-result=1); after a retried request such an answer still stops the
launch with 49. env.MADEIRA_DOCK_CEG = 0 never asks. The preparation is
Valve's; Dock does not touch the files.
env.NAME = value in Documents/madeira.cfg.
| Switch | Default | Effect |
|---|---|---|
MADEIRA_DOCK |
on | 0 hides the Dock button |
MADEIRA_DOCK_COMPACT_POOL |
off | 1 starts the sheet's "Smaller JIT pool" toggle on |
MADEIRA_DOCK_CLEAR_STEAM_ID |
on | 0: a Dock session keeps the fixed Steam game identity every other launch publishes |
MADEIRA_DOCK_CEG |
on | 0: never ask the client to prepare per-user executables |
MADEIRA_DOCK_CEG_SERVICE_START |
on | 0: do not start Valve's client service before the request |
MADEIRA_DOCK_IMAGE_RETIRE |
on | 0: a Dock launch does not turn on the engine's MADEIRA_JIT_IMAGE_RETIRE (an explicit env.MADEIRA_JIT_IMAGE_RETIRE still wins) |
MADEIRA_DOCK_IMAGE_MAP_GUARD |
on | 0: a Dock launch does not turn on Wine's MADEIRA_IMAGE_MAP_GUARD |
MADEIRA_DOCK_CLIENT_202601 |
on | read by the host: 0 disables its January 2026 client adapter |
MADEIRA_DOCK_HANDOFF_DIAGNOSTICS |
on | read by the host: 0 drops its numeric transfer diagnostics |
MADEIRA_DOCK_SESSION_WAIT |
on | read by the host: 0 fails a launch refused with 35 (another session playing) at once instead of asking again for up to three minutes |
MADEIRA_DOCK_OFFLINE_LOGON |
on | 0: no offline starts. The app never asks for one and hides the Game details mark; read by the host too, which then does not look up Valve's offline-logon methods |
MADEIRA_DOCK_INSTALLERS |
on | 0: no one-time installs (nothing read, run or shown) |
MADEIRA_INSTALL_DEFAULT_KEY |
on | 0: install-script entries without a HasRunKey are ignored |
MADEIRA_DOCK_INSTALL_CHOICE |
on | 0: no per-game choice; every start runs whatever is pending |
MADEIRA_DOCK_INSTALL_SCM |
on | 0: the batch records "services off" instead of starting the service manager (also read by the host's --start-services) |
MADEIRA_DOCK_INSTALL_SERVER_SYNC |
on | 0: a start that runs installers keeps madsync and leaves the service step out |
MADEIRA_DOCK_INSTALL_REGISTRY |
on | Before each Dock start the app writes the "Registry" values of the install scripts in the game's folder, as Valve's client does when it installs a game (a launcher that reads its folder there offered to install the game). Missing values are added, a path that follows the install folder is corrected, any other value stays; per-language values follow the install's language. 0: only the programs' records are written, as before 2026-11-02 |
MADEIRA_DOCK_INSTALL_SEPARATE |
on | madeira-bcd. 0: the host follows the installers in the same session |
MADEIRA_DOCK_INSTALL_LAYOUT |
on | madeira-bcd. 0: a session of one-time installs keeps the game's env.MADEIRA_SC_PA_POOLS and env.MADEIRA_RDR2_VA_HOLD lines |
MADEIRA_TIMER_FAIRNESS |
on | madeira-bcd, read by the wineserver. 0: a WM_PAINT that is always pending keeps every timer of its thread waiting, as Windows' order says |
MADEIRA_DOCK_RESET_CLEAN |
on | madeira-bcd. 0: Reset only forgets the records; installed files stay |
MADEIRA_DOCK_VULKAN_STANDIN |
on | madeira-bcd. Before a Dock start, a Vulkan loader an installer wrote into system32 (RDR2's VulkanRT) moves into Documents/madeira-reset-backup, so the session links Madeira's vulkan-1.dll stand-in again, as a direct launch does. 0: it stays |
MADEIRA_DOCK_GAME_SCM |
auto | read by the app and the host: 1 starts Wine's service manager in every Dock game session, 0 never; unset, only when the prefix's system.reg registers an automatic-start Win32 service whose image is outside the Windows folder (a launcher's own service). The app logs [dock-game-scm] and exports the decision; the host reports game-scm-services, game-scm or game-scm-timeout |
MADEIRA_DOCK_GAME_SCM_SERVER_SYNC |
on | 0: a session that starts the service manager keeps madsync (under madsync Wine's services.exe never answered its RPC clients on device, so the default turns it off for that session, as a start that runs installers does) |
MADEIRA_DOCK_GAME_SCM_FASTSYNC |
on | 0: a session that gives madsync up for the service manager runs Wine's standard sync instead of fastsync (fastsync keeps every object's state with the server; without it a job-system game sent ~33,000 waits and wakes a second to the server). A game's own fastsync switch is kept |
MADEIRA_DOCK_KEEP_ALIVE |
auto | read by the host: 0 ends the session 3 s after Valve's client stops reporting the game as running, as before; unset, the host follows the game's own process tree and holds the session while a member still shows a window or is younger than 60 s (a launcher restarting itself, the game it then starts); a ;-separated list of image names adds programs outside the tree. Reports launch-keep-alive, then launch-keep-alive-held |
MADEIRA_WOW_HOLD_FOR_GAME |
on | read by the app. For a game whose folder has a 32-bit program, the guest window held from session start goes only to a 32-bit program from the game's folder (ntdll's MADEIRA_WOW_HOLD_FOR); a program from elsewhere, such as Valve's 32-bit client service, still gets any other free slot. 0: the first 32-bit program of the session takes it, as before 2026-11-02 |
MADEIRA_DOTNET_FUSION |
on | 0: never place fusion.dll in the .NET 2.0 folder |
MADEIRA_DOCK_HIDE_DESKTOP |
on | 0: a Dock start's starting screen ends on the desktop's first frame, as before (no census) |
MADEIRA_DOCK_AUTO_REVEAL |
on | 0: a window that may need the user never reveals the desktop by itself (Show desktop still does) |
MADEIRA_DOCK_INSTALLER_REVEAL |
on | 0: a one-time installer's dialog never reveals the desktop by itself |
MADEIRA_DOCK_STATUS |
on | 0: the starting screen does not watch the host's result |
MADEIRA_RESTORE_BORN_MINIMIZED |
on | 0: while the census runs, a window first shown minimized stays minimized |
MADEIRA_RESTORE_BORN_MINIMIZED_LATE |
on | 0: after the starting screen ends, a window first shown minimized stays minimized |
With no Dock launch, nothing changes. The JIT pool stays 896 MB (or
madeira.cfg pool); no engine, Wine, FEX or DXMT file is touched. The compact
pool applies only to a Dock launch whose toggle is on, only for that launch,
and an explicit madeira.cfg pool still wins. Dock's host is itself an x64
program. A Dock launch also sets MADEIRA_JIT_IMAGE_RETIRE=1, the engine's
opt-in image-retire switch (its own engine PR), for that session only; no
other session sets it, and an engine without the switch ignores it.
One-time installs change the engine for one session only: a Dock start that
runs a game's installers sets MADEIRA_MADSYNC_SESSION=0, and madsync is off
for that session, including the game Valve's client then starts in it. With
the variable unset, madsync_enabled() returns what madeira.cfg selects
(madsync only with inproc-sync = 1; fastsync is the default engine); no other
code sets the variable. The next start, with the choice at "Skip", uses the
configured engine again.
- Game installation and library integration. Dock itself neither installs
nor removes games. The owned library and downloads are a separate part
(
docs/STEAM_LIBRARY.md) that writes the install records Dock reads; a Steam game's Game details page starts it through Dock. The library also offers Dock's sheet from Settings › Steam (docs/LIBRARY.md). - One-time installs, fork extras. The fork also marked runtimes it
recognised by file name as done without running them, and recorded such a
runtime as done after a failed run. Those name rules are gone here (every
program is treated alike; the per-game choice keeps failures from running
at every start). The fork's reset on uninstall exists only in Madeira's own uninstall
(
docs/STEAM_LIBRARY.md), which sets the game's choice back to "Run at next start"; Dock neither installs nor removes games. The fork's "skip" button on its starting screen needs a session stop this branch does not have; choose "Skip" in the sheet before the start instead. - Automatic session end and the exit-status hook. These need the ntdll program start/exit hooks from the front-end PR. Here, the result comes from the report file, and the session ends as other developer-interface sessions do.
- Heap, D3D9-census and pool-pressure defaults for Dock sessions (fork engine policies).
- On the owner's devices (fork builds), Dock launches reached gameplay: the host reported an authenticated online session and the app in the account's licences. Clean-prefix component setup and broad title compatibility are less proven.
- This extraction has not been run on a device.
- The fork's device runs depended on an engine fix: retiring an unloaded
image's translations before its address is reused (fork ml1850). Without
it, one device run crashed while Valve's client loaded its DLLs, before any
sign-in: a DLL mapped at the address of a DLL unloaded moments before ran
the unloaded DLL's translated code. The fix is its own engine PR, off by
default, behind
MADEIRA_JIT_IMAGE_RETIRE=1. Dock launches set that switch for their session only. This app side builds and runs without the engine PR; the variable is then ignored and Dock may hit that crash. - Valve can change the client at any time. A new client build needs a newly
verified adapter in the Dock repository and new pins in
SteamRuntime.swift. Until then, Dock refuses the new build.
On a Linux host with swiftc, cc and python3; no Steam, Wine or
credentials:
check-dock-contract.py:- static rules: no program-name lists, no credential in a log line, compact
pool off by default and Dock-only, madeira.cfg
poolwins, the image-retire switch set only in the Dock launch environment, no built binary tracked, submodule pin; - compiled production Swift: pool policy, manifest and library discovery,
validation, the transfer envelope and subject, the host environment
(image retire on, and off with
MADEIRA_DOCK_IMAGE_RETIRE=0; the image-map guard on, and off withMADEIRA_DOCK_IMAGE_MAP_GUARD=0), and the one-launch request.
- static rules: no program-name lists, no credential in a log line, compact
pool off by default and Dock-only, madeira.cfg
check-dock-report.py: the report parser, its messages (including 34), the ordered callback IDs, rejection of private and malformed fields, and that every report round the pinned host writes is accepted.check-dock-path.py: the transfer path through Wine's own path resolver (WINE_FILE_Ccan point atdlls/ntdll/unix/file.cwhen the wine submodule is not checked out).check-dock-components.py: the component ZIP reader and registry writer on synthetic archives, and the pins (one HTTPS host, well-formed sums, a client hash the pinned host supports).check-dock-installers.py: install-script parsing, the plan and its note, the batch and result parser, the whole start in a synthetic prefix (choice, recording at the next start, madsync request, fusion.dll, every switch),madsync_enabled()with the productionmadeira_cfg.h, and, where Windowscmd.exeis reachable, the batch run for real with stand-in installers and the stageddockhost.exe --start-services.check-dock-start-program.py: "Steam starts" is applied before the session with Steam's launch entry, Game details offers it, the file is in the Xcode project, and (with Swift) the swap, the restore and refused paths outside the game's folder.check-dock-game-session.py: the game session's service-manager decision (DockGameSession.swiftcompiled on the host: the three-way switch, Wine'ssystem.regparsed for automatic-start services outside Windows, the export to the host, the log line, the madsync rule), the report allow-list and round for the host'sgame-scm*andlaunch-keep-alive*fields, and the pinned host's switches.build/madeira-dock/build.sh --check: Dock's own ASan/UBSan unit tests (transfer parsing, 2,000 malformed inputs, client selection, launch-result and callback bounds).check-dock-start-screen.py: the starting screen's rules compiled from production Swift (owner classes by folder, which window is the game's, the one-time-install windows, reveal and cover timing, Show desktop, the status texts and the stop rule), Winios's census extracted fromWinios.munder ASan/UBSan and ThreadSanitizer (top-level windows only, one path lookup per process, frames and swapchains, capacity, the born-minimized restore and its switch), the driver's path form, and the wiring and glyph row.
Game details passes the saved game arguments through
MADEIRA_STEAM_HOST_ARGUMENTS. Dock appends them to the selected Steam launch
option. Both the app and the host reject unbalanced unescaped quotes, control
characters other than tabs, more than 64 arguments, and commands of 4096 bytes
or more. Invalid arguments stop the host with code 54. Code 50 remains the
offline sign-in result. Argument values do not appear in the host report.
The host waits 500 ms after ownership setup by default. The developer override
MADEIRA_DOCK_LICENCE_WAIT_MS accepts decimal values from 0 to
30000. Invalid values keep the default. This changes timing only. Valve's
client still checks the account and the game license.
Rebuild the Dock host with these source changes. The source tests and the Windows cross-build do not establish Steam startup or gameplay on a device.