Skip to content

Epic games: launcher ownership, store identity, installer services, thread reaping and the registry font list - #515

Merged
125hz merged 8 commits into
willfaust:mainfrom
125hz:pr/launcher-fonts-1101
Nov 2, 2026
Merged

125hz merged 8 commits into
willfaust:mainfrom
125hz:pr/launcher-fonts-1101

Conversation

@125hz

@125hz 125hz commented Nov 1, 2026 •

Copy link
Copy Markdown
Collaborator

Fixes from testers running an Epic copy of a game whose publisher launcher sits between the store and the game (logs 2026-10-28 to 2026-11-01). The tester now reaches the game. Every change has a kill switch, a config catalog entry and a host test.

Depends on willfaust/wine#69. The wine pin (baaab873) is that PR's head: these three wine commits on top of the runtime fixes the swap tier PRs pin, directly on the current madeira-lgpl, so it resolves once #69 is merged as a fast-forward. (#69 supersedes wine#68, which held the same three commits on their own.)

Commit What Switch
Library: Epic games start under a process with the Epic client's name An Epic entry's x64 program starts as the child of C:\windows\system32\EpicGamesLauncher.exe. That is a 15 KB stub (build/epic-parent) that runs its command line, waits and returns the exit code. It is not the Epic client: no window, no network. The publisher's launcher looks for a parent with that name; without it, it treated the copy as not owned and showed its store page. env.MADEIRA_EPIC_LAUNCHER_PARENT=0
Library: games from other stores start without the fixed Steam identity SteamAppId/SteamGameId/SteamAppPath aren't published for an Epic entry. With them, its launcher took the Steam path and stopped at "Steam failed to initialize". env.MADEIRA_STORE_STEAM_IDENTITY=1
Guest log mirror: skip [DISPLAY] log-level tags, redact sign-in values wine side in #68. Keeps a launcher's info lines from using up the mirror's budget, and writes <redacted> for *password=/*token=/*secret= values (the store's one-use code is on the logged command line). —
Threads: check the previous exited thread before freeing it 32-bit processes started one after another can reuse the same WoW window and TEB addresses. The raw prev_teb hand-off then made a new thread free its own kernel stack at exit and hang holding virtual_mutex; a launcher installer waited on it forever. Brings in the checked hand-off (ios_ea_reap_exited_thread) and makes it the default. env.MADEIRA_THREAD_REAP_CHECKED=0
WoW64: refuse aarch64 programs in an ARM64EC session; recommended lists can start in the Wine desktop An aarch64 child (services.exe) in an x64-first session ran on the ARM64EC ntdll and faulted until the storm handler ended it, 20 s each time. It is now refused at once, as conhost.exe already was. GameRecommendation gains launchMode, and the outside-Steam list whose launcher runs that installer (version 3) starts in the Wine desktop. env.MADEIRA_EC_NATIVE_CHILD=1
sechost: start the service manager on demand wine side in #68, plus the rebuilt ARM64EC sechost.dll. A launcher installer's service registration no longer fails when the session didn't start services first. MADEIRA_SCM_ON_DEMAND=0
Fonts: keep the registry's Fonts values for the Windows directory's fonts wine side in #68. One session with an empty font list deleted Tahoma, Marlett, Symbol, Webdings and Wingdings from HKLM\...\Fonts through the template's Mac-generated External Fonts entries. DirectWrite then had no Tahoma: the launcher's CEF browser overflowed its stack in font fallback, and D2D dialogs drew no text. Already damaged prefixes are repaired at the next session start. env.MADEIRA_FONT_KEYS_REPAIR=0

Notes:

  • The commits come from the 125hz fork. Pieces that depended on unmerged fork work were ported minimally: the checked thread hand-off, and launchMode on GameRecommendation. The outside-Steam list is upstream's version 2 plus launchMode: "desktop", not the fork's version 10.
  • I added the config catalog lines directly rather than running gen-config-catalog.py, which needs the submodules checked out.

Tests (Linux, swiftc for the recommendations check): check-epic-parent, check-store-steam-identity, check-guest-log-mirror, check-thread-reap, check-ec-native-child, check-game-recommendations, check-scm-on-demand, and check-font-keys-repair (against the wine tree of these commits) all pass.

🤖 Generated with Claude Code

125hz and others added 7 commits November 1, 2026 15:55
On Windows the Epic client starts every Epic game. A publisher's launcher
started by such a game looked for that process, did not find it and showed
the game's store page as if the copy were not owned.

build/epic-parent builds EpicGamesLauncher.exe, an x86-64 GUI program that
starts the rest of its command line, waits for it and returns its exit
code (not the Epic client: no window, no network). It is in the
arm64ec-windows farm, so C:\windows\system32. An Epic entry's x64 program
now starts as its child, in the Wine desktop or directly; a batch or a
32-bit program starts as before. env.MADEIRA_EPIC_LAUNCHER_PARENT=0 starts
it directly.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Every launch outside Madeira Dock got one fixed Steam game's SteamAppId,
SteamGameId and SteamAppPath. A game from another store started a launcher
that saw SteamAppId, took its Steam path, failed in its Steam API start and
showed only an error. An Epic library entry now sets
MADEIRA_NO_STEAM_IDENTITY for its launch and the bridge publishes none of
the three; env.MADEIRA_STORE_STEAM_IDENTITY=1 restores the old behaviour.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
wine 8d0e83cf (willfaust/wine#68): a launcher tagging every info line [DISPLAY] used the mirror's
64-line budget during startup, before its error lines; the tag no longer
matches. Values after password=, token= and secret= are replaced with
<redacted> in mirrored lines. tests/host/check-guest-log-mirror.py compiles
both functions from the wine source.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
exit_thread hands the previous exited thread's TEB to the next thread to
exit. 32-bit processes started one after another can get the same WoW window
and TEB addresses, so the raw prev_teb pointer could name the thread that is
exiting: a launcher installer's second VC++ redistributable freed its worker
thread's own kernel stack at exit (unregister_view+4, the stack fault waiting
for the virtual_mutex it held) and the installer waited on it forever.

The checked hand-off, ios_ea_reap_exited_thread in virtual_ios.c (added
here), is the default: it frees the previous thread only after finding it,
by process, thread id and pthread, in the live TEB list, and joins it
outside virtual_mutex. env.MADEIRA_THREAD_REAP_CHECKED=0 restores the
upstream hand-off unless MADEIRA_EA_CEF=1.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ts can start in the Wine desktop

An aarch64 child started in a session whose first program is x64 runs on the
session's ARM64EC ntdll, which loads the emulator into it while its threads get
no CPU area: services.exe, started on demand by sechost for a launcher
installer's service helper, faulted at its first executable allocation and its
exception path faulted until the storm handler ended it, twice, 20 s each.
NtCreateUserProcess now refuses such a child (STATUS_ACCESS_DENIED), as it
already refused conhost.exe; env.MADEIRA_EC_NATIVE_CHILD=1 starts it anyway and
MADEIRA_EC_CONHOST=1 still lets conhost.exe through.

GameRecommendation gains launchMode (Game details › Launch › Start), applied
like the other entry switches and shown in the switch summary. The
outside-Steam list whose launcher runs that installer (version 3) starts in
the Wine desktop, an aarch64 session, so the installer's services.exe runs on
the session ntdll and sechost's on-demand start succeeds. No services batch:
it would write the store's one-use sign-in arguments to disk.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
wine 035e8407 (willfaust/wine#68): when a 64-bit program opens the service
manager and services.exe is not running, OpenSCManagerW starts it once per
process, waits up to 20 s for its pipe and asks again
(MADEIRA_SCM_ON_DEMAND=0 restores the old behaviour). Testers' Epic copy of a
game ran a launcher installer on its first start; registering the launcher's
service failed with \pipe\svcctl missing and the installer stopped. The
ARM64EC sechost.dll is rebuilt from that source.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…onts

A launcher's embedded browser crashed on every start and its dialogs drew no
text: DirectWrite lists only the fonts named in the registry's Fonts key, and
one session whose font list came up empty had deleted the values of Tahoma,
Marlett, Symbol, Webdings and Wingdings through the prefix template's External
Fonts entries. wine (win32u) now leaves the keys alone in such a session, drops
those stale external entries and writes missing values back, so a prefix that
already lost them is repaired at its next session start.

env.MADEIRA_FONT_KEYS_REPAIR=0 restores upstream behaviour. Host test:
tests/host/check-font-keys-repair.py.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
wine#69 holds the service manager, guest log and font commits of this pull
request on top of the runtime fixes it shares with the swap tier pull
requests, directly on madeira-lgpl, so one fast-forward keeps every pin
valid. The sources these commits need are the same as at the previous pin.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@125hz
125hz merged commit c327992 into willfaust:main Nov 2, 2026
12 of 13 checks passed
125hz added a commit that referenced this pull request Nov 2, 2026
…hread reaping and the registry font list (#515)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant