Long-form document tracing the actual discovery process: the wrong turns, the aha moments, and the reasoning behind each step. Covers: - Why EA is involved in a Steam game at all (link2ea:// architecture) - JunoConfigureRegistry failure and why DISABLEROLLBACK was a dead end - msiextract bypass as the actual solution - Why direct system.reg edits don't survive reboots (wineserver ownership) - The launch wrapper approach for registry persistence - RPC_S_SERVER_UNAVAILABLE and EALocalHostSvc - Wolf-specific bwrap capabilities mismatch - localconfig.vdf atomic rename problem and chmod 555 fix Written for sharing — no identifying information, explains the "why" for each fix so readers can adapt if EA changes their MSI structure. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01D8ckUJQtj1pH8jtAddBDZs
13 KiB
Star Wars Battlefront II (2017) on Linux — How We Got Here
This document explains why the fix works, not just what to run. It traces the actual discovery process: the wrong turns, the aha moments, and the reasoning behind each step. If you want to understand what the setup scripts are doing and why, read this first.
Does it work out of the box?
No — not on any Linux Steam setup as of 2025. The game itself runs fine under GE-Proton. The blocker is EA's launcher infrastructure, specifically the step where Steam tries to install the EA App (formerly EA Desktop) into your Wine prefix before the first launch. That installation silently fails, and the game never gets past it.
This is true whether you're running native Linux Steam, or streaming through Wolf/Games-on-Whales, or any other Proton setup. The root cause is in Wine, not in the streaming layer.
The architecture: why EA is involved at all
SWBF2 (2017) is a Steam game that also requires EA's launcher infrastructure. This surprises people because it's a Steam purchase. The dependency exists because EA's backend handles identity and entitlement checks separately from Steam's payment and file delivery.
The game binary does not launch directly. Instead, Steam passes a link2ea://
URL as the "executable" to GE-Proton:
link2ea://launchgame/1237950?platform=steam&theme=swbfii
GE-Proton's internal steam.exe intercepts this URL, looks up the link2ea://
protocol handler in the Wine registry, finds Link2EA.exe, runs it, and
Link2EA.exe authenticates with EA's servers before handing off to the game.
If anything in that chain is missing — no protocol handler, no Link2EA.exe, no EA services — the game exits silently and you're back at the Play button.
The first question was: why is the protocol handler missing?
The installer: why it fails
Steam bundles ea_app.msi inside the game files. On first launch it runs this
MSI inside Wine to install EA Desktop. The installer runs, appears to do
something, and then... nothing. The game exits. No error message.
Red herring: the wrong Proton version
The first instinct was to try different Proton versions. Vanilla Proton, various GE-Proton releases. Some got further than others, but none completed the EA App installation. GE-Proton is required — it contains patches specifically for EA's launcher — but even with it the MSI fails.
Finding the actual failure
The Wine logs showed the MSI starting, running custom actions, and then rolling back. The key line buried in several hundred lines of output:
err:msi:HANDLE_CustomType1 ... JunoConfigureRegistry ... returned 0
The custom action returned 0 (success) but triggered a rollback anyway. That
seems contradictory. The reason: JunoConfigureRegistry is a .NET custom
action that calls SetSecurityDescriptorSddlForm to set ACLs on registry keys.
Wine's .NET implementation doesn't support that call. It returns 0 silently
(Wine's default for unimplemented functions) but the registry keys end up in
an invalid state, which the MSI detects and treats as a failure, rolling back
the entire installation.
The MSI was designed for Windows. On Windows, SetSecurityDescriptorSddlForm
works. On Wine, it silently no-ops, leaving the installation in a broken state
that triggers rollback.
DISABLEROLLBACK was a dead end
The obvious next attempt: run the MSI with DISABLEROLLBACK=1 so it can't
roll back even if something fails. This produced a 3-line log instead of
the usual hundreds of lines. The MSI thought the product was already registered
from the previous failed attempt and exited immediately without doing anything.
The partial state from the first failed run confused it.
The actual fix: don't run the installer at all
MSI files are archives. They contain files, a database of what goes where, and custom actions (executable code). The custom actions are what fail. If you skip the installer entirely and just extract the files directly, you get everything you need without running any of the broken .NET code.
msiextract (from the msitools package) extracts an MSI's file payload on
Linux without executing any custom actions. Run it on the host (outside Wine),
and it produces the full EA Desktop directory — Link2EA.exe, EADesktop.exe,
EALocalHostSvc.exe, all of it — without touching Wine at all.
msiextract -C /tmp/ea_app_extracted /tmp/ea_app.msi
This was the first major aha moment. The installer was never necessary. The files were always there.
The registry: why direct edits don't work
With EA Desktop files in place, the next problem: the link2ea:// protocol
handler still wasn't registered in Wine's registry. Without it, GE-Proton's
steam.exe can't find Link2EA.exe and the URL goes nowhere.
The Wine registry for a prefix lives in plain text files:
pfx/system.reg— HKEY_LOCAL_MACHINEpfx/user.reg— HKEY_CURRENT_USER
The obvious approach: edit system.reg directly, add the protocol handler
entries, done. This worked — once. On the next launch the entries were gone.
Why: wineserver owns the registry
When Wine (or Proton) starts, wineserver loads the registry files into memory.
Any changes you make to the files on disk while wineserver is running are
ignored — wineserver's in-memory copy is authoritative. When wineserver shuts
down, it flushes its in-memory state back to disk, overwriting whatever you
wrote.
So: edit the file while wineserver is stopped → entries appear → Steam launches the game → wineserver starts → your entries are still there → wineserver stops at the end → your entries are overwritten with the original state.
One launch works. The second launch doesn't.
The fix: go through the launch chain
The correct way to write registry entries that persist is to run regedit
through Proton's own wine binary, which writes into wineserver's live memory.
When wineserver then flushes to disk on shutdown, it writes the entries you
added — because they came from inside wineserver's own session.
The Steam launch option %command% expands to the full Proton/pressure-vessel
launch invocation. A wrapper script can intercept this, run regedit using the
same Proton binary before the game starts, then pass control through to the
game:
#!/bin/bash
# $1 through ${11} is the Proton launch wrapper
# running regedit with the same binary registers into the live wineserver session
"$1" "$2" "$3" ... "${11}" regedit /S "C:\\link2ea_fix.reg"
"$1" "$2" "$3" ... "${11}" regedit /S "C:\\ea_services.reg"
exec "$@" # now launch the actual game
The .reg files live in drive_c (the Wine prefix's C: drive) so they survive
prefix wipes and container restarts. On every launch, the wrapper imports them
fresh. The entries are always current, always in wineserver's memory.
This was the second major aha moment. The registry isn't a file to edit — it's a live service to talk to.
The missing service: RPC_S_SERVER_UNAVAILABLE
With the protocol handler registered, link2ea:// now fired correctly.
Link2EA.exe launched. Then it immediately crashed with:
RPC_S_SERVER_UNAVAILABLE (0x800706ba)
Link2EA.exe uses local RPC (inter-process communication) to talk to
EALocalHostSvc — a Windows service that runs in the background and provides
the local IPC socket Link2EA.exe expects to find. Without that service
registered, Link2EA.exe looks for the socket, finds nothing, and exits.
The fix was the same as the protocol handler: a .reg file imported on every
launch that registers EALocalHostSvc and EABackgroundService as Windows
services. Wine's service layer is a pale imitation of Windows Services, but it
provides enough of the IPC interface that Link2EA.exe is satisfied.
The Wolf-specific problem: capabilities and bwrap
(Skip this section if you're on native Linux Steam — it doesn't apply.)
Wolf/Games-on-Whales runs Steam inside a Docker container. The obvious thing to
try was running Wine or Proton directly from outside the container using
docker exec. This failed consistently with:
setting up uid map: Permission denied
The reason is a Linux capabilities mismatch. Processes launched via docker exec
inherit elevated capabilities from the Docker daemon (roughly CapEff ≈ 0xa80c35fb).
Steam processes inside the container run with CapEff=0 — no elevated
capabilities at all.
When a high-capability process tries to run bwrap (the bubblewrap sandbox that
Steam's pressure-vessel/sniper runtime uses), bwrap attempts a privileged uid
namespace map — and that path requires either no capabilities or full root, not
the partial set that docker exec provides. It fails.
The consequence: only Steam itself can launch programs through the sniper+GE-Proton runtime. Any attempt to invoke Proton from outside Steam's own process tree hits this wall. Every approach that tried to "just run wine" from a terminal or docker exec hit this problem and was a dead end.
This is why the wrapper script approach works where direct invocation does not.
The wrapper is called by Steam as part of its own launch chain, so it inherits
Steam's CapEff=0 context and bwrap succeeds.
The localconfig.vdf problem
Steam stores per-game launch options in
userdata/<uid>/config/localconfig.vdf. The wrapper needs to be set as the
launch option for SWBF2, and that setting needs to survive Steam restarts.
The naive fix — chmod 444 on the file — doesn't work. Steam writes
localconfig.vdf using an atomic rename: it writes to a temporary file in the
same directory, then renames the temp file over the original. chmod 444 on
the destination file doesn't prevent a rename-over.
The fix is chmod 555 on the directory. A rename-into requires write
permission on the destination directory. Lock the directory and Steam can't
complete the atomic rename — it fails silently and the original file is
preserved.
This is Linux filesystem semantics: file permissions control reads and writes to the file's contents; directory permissions control the ability to create, delete, or rename entries within the directory.
What EA actually requires
After all of this, a reasonable question: why does EA need to authenticate at all for a game I bought on Steam?
EA's position is that their games require EA account authentication regardless of purchase platform. SWBF2 (2017) is in a middle state: it was originally an Origin-only title, moved to Steam, but EA kept their authentication layer in place. The game phones home to EA's servers on first launch and periodically thereafter. The EA App caches credentials locally, so subsequent launches work offline until the token expires (weeks to months).
The authentication cannot be bypassed without modifying game binaries — which would be both a TOS violation and unnecessary, since the fix above gets the legitimate authentication flow working correctly. The game still calls home to EA. You still need a valid EA account linked to your Steam account. The scripts here fix the installation of the authentication layer, not the authentication itself.
If EA's servers shut down permanently: the community SWBF2 server project (Kyber) would likely replace the authentication infrastructure, as has happened with other abandoned EA titles. SWBF2 (2005, AppID 6060) has no EA dependency and remains fully playable with no account required.
Summary: why each piece is necessary
| Problem | Root cause | Fix |
|---|---|---|
| EA App install rolls back | JunoConfigureRegistry .NET call unsupported in Wine |
msiextract on host, bypass installer entirely |
link2ea:// URL goes nowhere |
Protocol handler not in Wine registry | link2ea_fix.reg imported via launch wrapper |
| Link2EA.exe crashes immediately | EALocalHostSvc IPC service not registered |
ea_services.reg imported via launch wrapper |
| Registry fixes disappear after one launch | wineserver owns the registry; file edits are overwritten on shutdown | Import .reg files through Proton's own wine binary inside the launch chain |
| Launch wrapper setting gets overwritten | Steam uses atomic rename to write localconfig.vdf |
chmod 555 on the config directory blocks the rename |
(Wolf only) Can't run Proton via docker exec |
Capabilities mismatch breaks bwrap uid namespace mapping | Use Steam's own launch chain; only Steam can invoke the sniper runtime |
None of these problems are obvious. None of them produce useful error messages by default. Each one looks like "the game just exits" until you find the right log, add the right verbosity flag, or run the right diagnostic. The solutions are individually small — a reg file here, a chmod there — but finding each one required understanding a separate piece of Linux internals, Wine internals, or Docker internals.
The scripts exist so no one else has to find them again.