From 4324ad7ce2aad7394f472fe36dbf10469758c4e8 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 22 Jun 2026 13:26:11 +0000 Subject: [PATCH] docs: add SWBF2 2017 discovery writeup explaining why the fix works MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 Claude-Session: https://claude.ai/code/session_01D8ckUJQtj1pH8jtAddBDZs --- docs/SWBF2-2017-how-we-got-here.md | 289 +++++++++++++++++++++++++++++ 1 file changed, 289 insertions(+) create mode 100644 docs/SWBF2-2017-how-we-got-here.md diff --git a/docs/SWBF2-2017-how-we-got-here.md b/docs/SWBF2-2017-how-we-got-here.md new file mode 100644 index 0000000..ddc1670 --- /dev/null +++ b/docs/SWBF2-2017-how-we-got-here.md @@ -0,0 +1,289 @@ +# 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. + +```bash +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_MACHINE +- `pfx/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: + +```bash +#!/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//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.