diff --git a/docs/SWBF2-2017-wolf-setup.md b/docs/SWBF2-2017-wolf-setup.md index 8f9ca6b..2d80684 100644 --- a/docs/SWBF2-2017-wolf-setup.md +++ b/docs/SWBF2-2017-wolf-setup.md @@ -1,248 +1,311 @@ # Star Wars Battlefront II (2017) — Wolf/Moonlight Setup **AppID:** 1237950 -**Proton:** GE-Proton10-34 (required — stock Proton does not work with EA App) +**Proton:** GE-Proton10-34 (required) **Platform:** Wolf (Games-on-Whales) — WolfSteam Docker container --- -## Why this is hard +## Prerequisites -SWBF2 (2017) is an EA game. EA mandated their **EA App** launcher for all EA Steam titles. -On launch, SWBF2 calls `ShellExecuteW("link2ea://launchgame/1237950?platform=steam&theme=swbfii")`. -GE-Proton's `steam.exe` intercepts that URL, finds `Link2EA.exe` from the EA Desktop install, and -hands off to the EA App which then launches the actual game. - -Without EA Desktop installed in the Wine prefix, the game exits immediately. +- SWBF2 installed via Steam in the WolfSteam session +- GE-Proton10-34 installed (Steam → Settings → Compatibility → Enable Steam Play) +- EA account created at ea.com and linked to your Steam account +- `msitools` on the Docker host: `sudo apt-get install -y msitools` --- -## The bwrap problem (why you can't just docker exec) +## The EA dependency problem + +SWBF2 (2017) requires EA's launcher infrastructure — even for single-player offline: + +- **Steam** handles payment, library, and game files +- **EA App** handles identity and authentication + +First launch requires internet + EA account. After that, EA App caches credentials +locally and subsequent launches work offline — until the token expires or EA's servers +shut down permanently. + +**If/when EA shuts down:** +- Community private server projects (like Kyber) may replace EA's infrastructure +- SWBF2 (2005, AppID 6060) is the zero-dependency alternative — no accounts, no + launchers, active community servers, runs perfectly through Proton + +**Migrating to another machine:** +```bash +# Back up the Wine prefix — EA credentials travel with it +tar -czf swbf2-prefix-backup.tar.gz \ + /home/retro/.steam/steam/steamapps/compatdata/1237950/ +``` +Tokens last weeks to months before requiring re-login. + +--- + +## Why docker exec can't run Wine/Proton -The WolfSteam container runs Steam as uid 1000 (`retro` inside container). `docker exec` carries high Linux capabilities (`CapEff ≈ 0xa8ac35fb`). -Steam processes have `CapEff=0`. - -When a high-cap shell tries to run bwrap (used by Steam's sniper/pressure-vessel runtime), -bwrap attempts a privileged uid-map path that fails: +Steam processes run with `CapEff=0`. When a high-cap process tries to run bwrap +(Steam's sniper/pressure-vessel sandbox), bwrap tries a privileged uid-map path: ``` setting up uid map: Permission denied ``` -This means you **cannot** run Proton or wine64 via `docker exec` — bwrap always fails. -Only Steam itself can properly launch programs through sniper+GE-Proton. +**Only Steam itself can launch programs through sniper+GE-Proton.** All Wine +operations must go through Steam's launch chain — hence the wrapper approach. --- -## Solution: Steam launch wrapper +## How it works -Use SWBF2's **Steam launch options** to intercept the launch, run a helper script, -then pass control back to Steam's real launch chain. +Steam passes a `link2ea://` URL as the "game executable" to GE-Proton. The wrapper +intercepts this, imports registry fixes, then passes the original URL through. +GE-Proton's `steam.exe` handles `link2ea://`, finds `Link2EA.exe` via the registry, +runs it, and EA Desktop authenticates the session before launching the game. -Steam passes all its args to the wrapper as `$@`: ``` -steam-launch-wrapper -- reaper SteamLaunch AppId=1237950 -- --verb=waitforexitandrun -- waitforexitandrun +Steam → wrapper → regedit (registry fixes) → exec original args + → GE-Proton steam.exe → link2ea:// → Link2EA.exe → EA Desktop → SWBF2 ``` -The wrapper runs msiexec (or anything else) inside the full sniper+GE-Proton environment -that Steam set up, then `exec "$@"` to hand off to the game. - --- ## Step-by-step -### 1. Lock localconfig.vdf against Steam overwriting it +### 1. Trigger Wine prefix creation -Steam uses atomic rename (write temp + rename) when saving config. -`chmod 444` on the file is bypassed. Lock the **directory** instead: +SWBF2 must be launched at least once so Steam creates the Wine prefix and drops +`ea_app.msi` into it. Launch SWBF2 from Moonlight, wait ~10 seconds, then close it +(the "Origin is not installed" error is expected at this point). + +Verify the MSI appeared: +```bash +WOLF_CONTAINER=$(docker ps --format '{{.Names}}' | grep Wolf) +docker exec "$WOLF_CONTAINER" ls -lh \ + /home/retro/.steam/steam/steamapps/compatdata/1237950/pfx/drive_c/ea_app.msi +``` +Expected: ~227 MB file. + +### 2. Extract EA Desktop files on the host + +The MSI's `JunoConfigureRegistry` .NET custom action fails in Wine (it uses +`SetSecurityDescriptorSddlForm` for registry ACLs — unsupported in Wine). This +causes the entire install to roll back. Extract files on the host instead: ```bash -WOLF_CONTAINER="WolfSteam_XXXXXXXXXXXXXXXX" # docker ps to find exact name -CONFIG_DIR="/home/retro/.steam/steam/userdata/XXXXXXXXX/config" +WOLF_CONTAINER=$(docker ps --format '{{.Names}}' | grep Wolf) -# Find container name -docker ps --format '{{.Names}}' | grep Wolf +docker cp "$WOLF_CONTAINER:/home/retro/.steam/steam/steamapps/compatdata/1237950/pfx/drive_c/ea_app.msi" \ + /tmp/ea_app.msi -# Find Steam user ID -docker exec "$WOLF_CONTAINER" ls /home/retro/.steam/steam/userdata/ - -docker exec "$WOLF_CONTAINER" chmod 555 "$CONFIG_DIR" +sudo apt-get install -y msitools +mkdir -p /tmp/ea_app_extracted +msiextract -C /tmp/ea_app_extracted /tmp/ea_app.msi ``` -### 2. Set SWBF2 launch options +### 3. Copy EA Desktop files into the Wine prefix -Edit localconfig.vdf **while Steam is not running** (or kill Steam first): +The MSI installs EA Desktop under a versioned directory with a symlink. +The version (e.g. `14.2.0.3345`) is whatever the extracted MSI contains: ```bash +EA_VERSION=$(ls /tmp/ea_app_extracted/Electronic\ Arts/EA\ Desktop/ | head -1) +# Usually "EA Desktop" — this is the versioned dir, renamed during real install +# Use the version from the failed-install symlink if present: +SYMLINK_TARGET=$(docker exec "$WOLF_CONTAINER" readlink \ + "/home/retro/.steam/steam/steamapps/compatdata/1237950/pfx/drive_c/Program Files/Electronic Arts/EA Desktop/EA Desktop" \ + 2>/dev/null || echo "14.2.0.3345") + +PFXBASE="/home/retro/.steam/steam/steamapps/compatdata/1237950/pfx/drive_c/Program Files/Electronic Arts/EA Desktop" + +docker exec "$WOLF_CONTAINER" bash -c " + rm -f '$PFXBASE/EA Desktop' + mkdir -p '$PFXBASE/$SYMLINK_TARGET' + ln -s '$SYMLINK_TARGET' '$PFXBASE/EA Desktop' +" + +docker cp "/tmp/ea_app_extracted/Electronic Arts/EA Desktop/EA Desktop/." \ + "$WOLF_CONTAINER:$PFXBASE/$SYMLINK_TARGET/" + +# Verify +docker exec "$WOLF_CONTAINER" find "$PFXBASE" -name "Link2EA.exe" +``` + +### 4. Write registry fix files to drive_c + +These are imported into the Wine registry on each launch via the wrapper. +Writing to drive_c ensures they survive wineserver restarts (unlike direct +system.reg edits which get overwritten). + +```bash +PFXC="$WOLF_CONTAINER:/home/retro/.steam/steam/steamapps/compatdata/1237950/pfx/drive_c" + +# link2ea:// protocol handler — tells Wine how to run Link2EA.exe +cat > /tmp/link2ea_fix.reg << 'EOF' +Windows Registry Editor Version 5.00 + +[HKEY_LOCAL_MACHINE\SOFTWARE\Classes\link2ea] +@="URL:link2ea Protocol" +"URL Protocol"="" + +[HKEY_LOCAL_MACHINE\SOFTWARE\Classes\link2ea\shell\open\command] +@="\"C:\\Program Files\\Electronic Arts\\EA Desktop\\EA Desktop\\Link2EA.exe\" \"%1\"" +EOF + +# EA Desktop Windows services — EALocalHostSvc provides local IPC that +# Link2EA.exe needs; without it launch fails with RPC_S_SERVER_UNAVAILABLE +cat > /tmp/ea_services.reg << 'EOF' +Windows Registry Editor Version 5.00 + +[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\EALocalHostSvc] +"Type"=dword:00000010 +"Start"=dword:00000002 +"ErrorControl"=dword:00000001 +"ImagePath"="C:\\Program Files\\Electronic Arts\\EA Desktop\\EA Desktop\\EALocalHostSvc.exe" +"DisplayName"="EA Local Host Service" +"ObjectName"="LocalSystem" + +[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\EABackgroundService] +"Type"=dword:00000010 +"Start"=dword:00000002 +"ErrorControl"=dword:00000001 +"ImagePath"="C:\\Program Files\\Electronic Arts\\EA Desktop\\EA Desktop\\EABackgroundService.exe" +"DisplayName"="EA Background Service" +"ObjectName"="LocalSystem" +EOF + +docker cp /tmp/link2ea_fix.reg "$PFXC/link2ea_fix.reg" +docker cp /tmp/ea_services.reg "$PFXC/ea_services.reg" +``` + +### 5. Lock localconfig.vdf and set launch options + +Steam overwrites localconfig.vdf on shutdown via atomic rename (temp file + +rename). `chmod 444` on the file is bypassed. Lock the **directory** instead: + +```bash +STEAM_UID=$(docker exec "$WOLF_CONTAINER" ls /home/retro/.steam/steam/userdata/) +CONFIG_DIR="/home/retro/.steam/steam/userdata/$STEAM_UID/config" CONFIG_FILE="$CONFIG_DIR/localconfig.vdf" -# Kill Steam so it flushes and stops watching +# Stop Steam so it flushes before we edit docker exec "$WOLF_CONTAINER" pkill -f steam.sh || true -sleep 3 +sleep 5 -# Edit — find the LaunchOptions line for AppID 1237950 -# Add before it if missing: -docker exec "$WOLF_CONTAINER" bash -c " - sed -i '/\"1237950\"/,/}/ s/\"LaunchOptions\"\s*\"[^\"]*\"/\"LaunchOptions\"\t\t\"STEAM_UNIX_SOCKET=\/tmp\/steam.sock \/home\/retro\/ea_install.sh %command%\"/' '$CONFIG_FILE' -" -# Or if LaunchOptions key doesn't exist yet, add it manually. -# Verify: -docker exec "$WOLF_CONTAINER" grep -A5 '"1237950"' "$CONFIG_FILE" | grep LaunchOptions +# Lock directory +docker exec "$WOLF_CONTAINER" chmod 555 "$CONFIG_DIR" + +# Add launch options for AppID 1237950 +# Find the 1237950 block and insert/replace LaunchOptions +docker exec "$WOLF_CONTAINER" python3 - "$CONFIG_FILE" << 'PYEOF' +import sys, re +path = sys.argv[1] +with open(path, 'r') as f: + content = f.read() + +launch_opt = 'STEAM_UNIX_SOCKET=/tmp/steam.sock /home/retro/ea_install.sh %command%' + +# Replace existing LaunchOptions in 1237950 block if present +new = re.sub( + r'("1237950".*?"LaunchOptions"\s*)"[^"]*"', + rf'\1"{launch_opt}"', + content, flags=re.DOTALL +) + +if new == content: + # LaunchOptions key missing — add it after the "1237950" line + new = re.sub( + r'("1237950"\s*\n\s*\{)', + rf'\1\n\t\t\t\t"LaunchOptions"\t\t"{launch_opt}"', + content + ) + +with open(path, 'w') as f: + f.write(new) +print("Done") +PYEOF + +# Verify +docker exec "$WOLF_CONTAINER" grep -A3 '"1237950"' "$CONFIG_FILE" | grep -i launch ``` -### 3. Write the EA install wrapper - -The wrapper runs `msiexec /a` (administrative install — no custom actions) to extract -EA Desktop files, then execs the real game: +### 6. Write the wrapper script ```bash cat > /tmp/ea_install.sh << 'EOF' #!/bin/bash -echo "=== ea_install.sh called $(date) ===" >> /tmp/ea_install.log -echo "Args: $@" >> /tmp/ea_install.log -"$1" "$2" "$3" "$4" "$5" "$6" "$7" "$8" "$9" "${10}" "${11}" \ - msiexec /i "C:\\ea_app.msi" /qn \ - TARGETDIR="C:\\Program Files\\Electronic Arts" \ - /l*v "C:\\msi_admin.log" \ - >> /tmp/ea_install.log 2>&1 -echo "MSI exit: $?" >> /tmp/ea_install.log +echo "=== launch $(date) ===" >> /tmp/ea_install.log +# Import registry fixes on every launch (survives wineserver restarts) +"$1" "$2" "$3" "$4" "$5" "$6" "$7" "$8" "$9" "${10}" "${11}" regedit /S "C:\\link2ea_fix.reg" +"$1" "$2" "$3" "$4" "$5" "$6" "$7" "$8" "$9" "${10}" "${11}" regedit /S "C:\\ea_services.reg" +# Pass original args through — Steam's link2ea:// URL goes to GE-Proton → Link2EA.exe exec "$@" EOF chmod +x /tmp/ea_install.sh docker cp /tmp/ea_install.sh "$WOLF_CONTAINER":/home/retro/ea_install.sh ``` -### 4. Get the EA App MSI +### 7. First launch -The MSI ships inside SWBF2's game files. Copy it into the Wine prefix: +1. Reconnect to Moonlight and launch SWBF2 +2. **Let Vulkan shaders compile** — do not skip, takes several minutes, only happens once +3. EA App authenticates via your linked Steam/EA account (no manual login if accounts are linked) +4. Game loads -```bash -MSI_SRC="/home/retro/.steam/steam/steamapps/common/STAR WARS Battlefront II/__Installer/Origin/redist/internal/EAappInstaller.exe" -# EAappInstaller.exe is a WiX Burn bootstrapper — the real MSI is buried inside. -# Easier: use the MSI that gets extracted to the Wine prefix during a first launch attempt: -MSI_IN_PREFIX="/home/retro/.steam/steam/steamapps/compatdata/1237950/pfx/drive_c/ea_app.msi" -``` +--- -The MSI appears in the Wine prefix (`drive_c/ea_app.msi`, ~227 MB) after SWBF2 is launched -once — GE-Proton's protonfixes or steam.exe drops it there. +## Is this fragile? Will it work on a fresh install? -### 5. Extract EA Desktop files on the host (bypassing Wine custom actions) +**Honest assessment:** -The MSI's `JunoConfigureRegistry` custom action fails in Wine (it tries to set registry -ACLs via `SetSecurityDescriptorSddlForm` which Wine doesn't support). This causes -MSI rollback and removes all installed files. Bypass it by extracting on Linux: +| Component | Fresh install safe? | Notes | +|-----------|-------------------|-------| +| EA Desktop files in Wine prefix | ✅ | Extracted from MSI in game files | +| Registry fixes (.reg files) | ✅ | Imported on every launch via wrapper | +| Launch options in localconfig.vdf | ✅ | Set by script | +| Config dir lock (chmod 555) | ✅ | Set by script | +| ea_app.msi in drive_c | ⚠️ | Requires one failed launch first to appear | +| EA account link | ⚠️ | One-time manual step | +| Vulkan shader cache | ⚠️ | Recompiles on fresh prefix — takes minutes | +| EA token in Wine prefix | ⚠️ | Requires login if prefix deleted or token expired | -```bash -# Copy MSI out of container -docker cp "$WOLF_CONTAINER:/home/retro/.steam/steam/steamapps/compatdata/1237950/pfx/drive_c/ea_app.msi" \ - /tmp/ea_app.msi +**The catch:** The Wine prefix (`compatdata/1237950/`) is recreated from scratch if deleted. +All file copies and registry entries live inside it. A fresh install needs all steps run again +— but the automation handles that. -# Extract (msitools package) -sudo apt-get install -y msitools -mkdir -p /tmp/ea_app_extracted -msiextract -C /tmp/ea_app_extracted /tmp/ea_app.msi -``` - -### 6. Copy EA Desktop into the Wine prefix - -The MSI installs to `C:\Program Files\Electronic Arts\EA Desktop\` with a versioned -subdirectory (`14.2.0.3345`) and a symlink `EA Desktop -> 14.2.0.3345`. - -The normal MSI install creates that symlink via `JunoSetupSymlinkMode`. After a -failed install + rollback, the symlink may remain but the versioned dir is gone. - -```bash -PFXBASE="/home/retro/.steam/steam/steamapps/compatdata/1237950/pfx/drive_c/Program Files/Electronic Arts/EA Desktop" - -# Remove any broken symlink -docker exec "$WOLF_CONTAINER" bash -c " - [ -L '$PFXBASE/EA Desktop' ] && rm '$PFXBASE/EA Desktop' - mkdir -p '$PFXBASE/14.2.0.3345' - ln -s 14.2.0.3345 '$PFXBASE/EA Desktop' -" - -# Copy extracted files into versioned directory -docker cp "/tmp/ea_app_extracted/Electronic Arts/EA Desktop/EA Desktop/." \ - "$WOLF_CONTAINER:$PFXBASE/14.2.0.3345/" - -# Verify -docker exec "$WOLF_CONTAINER" find "$PFXBASE" -name "Link2EA.exe" -``` - -Expected output: -``` -/home/retro/.steam/steam/steamapps/compatdata/1237950/pfx/drive_c/Program Files/Electronic Arts/EA Desktop/14.2.0.3345/Link2EA.exe -``` - -### 7. Switch wrapper to pass-through and launch - -Once EA Desktop files are in place, update the wrapper to just exec the game: - -```bash -cat > /tmp/ea_passthrough.sh << 'EOF' -#!/bin/bash -echo "=== launch $(date) ===" >> /tmp/ea_install.log -exec "$@" -EOF -chmod +x /tmp/ea_passthrough.sh -docker cp /tmp/ea_passthrough.sh "$WOLF_CONTAINER":/home/retro/ea_install.sh -``` - -Launch SWBF2 in Moonlight. Steam → GE-Proton → SWBF2 → link2ea:// → steam.exe -finds Link2EA.exe → EA App launches → SWBF2 loads. - -### 8. EA account login (one-time) - -On first launch, EA Desktop will show a login screen. Log in with your EA account. -Credentials are cached; subsequent launches skip login. - -### 9. Restore clean state - -After EA Desktop is confirmed working, remove the launch wrapper: - -```bash -# Restore directory to writable -docker exec "$WOLF_CONTAINER" chmod 755 "$CONFIG_DIR" - -# Remove LaunchOptions from localconfig.vdf -# (edit the file while Steam is stopped, remove the LaunchOptions line for 1237950) -``` +**The one irreducible manual step:** Linking your EA account to Steam (done once at ea.com, +persists across reinstalls via EA's servers). --- ## Troubleshooting -### "link2ea not found" / game exits in ~6 seconds -EA Desktop files are missing or in wrong path. Check: +### "Origin is not installed" popup +The game binary checks for Origin before calling link2ea://. This happens when: +- The wrapper didn't run (check launch options are set) +- The wrapper ran but regedit failed (check `tail /tmp/ea_install.log`) + +### `link2ea://` fails with ret 31 (SE_ERR_NOASSOC) +`link2ea_fix.reg` wasn't imported. Check drive_c has the file and wrapper ran both regedits. + +### RPC_S_SERVER_UNAVAILABLE (0x800706ba) in wine log +`ea_services.reg` wasn't imported or EALocalHostSvc failed to register. Check wrapper log. + +### Game exits immediately, no popup, no log +Wrapper isn't being called. Config dir may not be chmod 555 or launch options missing. ```bash -docker exec "$WOLF_CONTAINER" find \ - "/home/retro/.steam/steam/steamapps/compatdata/1237950/pfx/drive_c/Program Files/Electronic Arts" \ - -name "Link2EA.exe" +docker exec "$WOLF_CONTAINER" stat "$CONFIG_DIR" | grep Access +docker exec "$WOLF_CONTAINER" grep -A5 '"1237950"' "$CONFIG_FILE" | grep -i launch ``` -### Wrapper never runs (Steam ignores launch options) -Steam wrote config before chmod 555 was applied. Kill Steam, chmod 555 the directory, -then edit localconfig.vdf. Steam reads config fresh on startup. - -### INST-14-1603 error in EA App installer -`JunoConfigureRegistry` .NET custom action returns 0 in Wine. This is a Wine limitation -with registry ACL operations. Use the `msiextract` bypass (Step 5–6) instead of -running the MSI through Wine. - -### msiexec DISABLEROLLBACK=1 produces empty log -MSI detected a stale product registration from a previous failed install. -Check: `grep -i C2622085 /home/retro/.steam/steam/steamapps/compatdata/1237950/pfx/system.reg` -If present, delete those registry keys then retry. - -### wine64 segfaults outside Steam (exit 139) -GE-Proton10's wine64 requires the sniper runtime. Never call it directly from -`docker exec` — always use Steam's launch chain via the wrapper approach. - -### localconfig.vdf gets overwritten on Steam shutdown -Steam uses atomic rename (temp file + rename into place). `chmod 444` on the file -is bypassed by the rename. Solution: `chmod 555` on the **directory** containing -localconfig.vdf — this prevents new files being renamed into the directory. +### Collecting logs after a failure +```bash +docker exec "$WOLF_CONTAINER" bash -c " + tail -20 /tmp/ea_install.log + echo '---' + grep -E '(err:|link2ea|origin|RPC)' /home/retro/steam-1237950.log | tail -20 +" +``` --- @@ -251,9 +314,11 @@ localconfig.vdf — this prevents new files being renamed into the directory. | Path | Description | |------|-------------| | `/home/retro/.steam/steam/steamapps/compatdata/1237950/pfx/` | SWBF2 Wine prefix | +| `/home/retro/.steam/steam/steamapps/compatdata/1237950/pfx/system.reg` | Wine HKLM registry | | `/home/retro/.steam/steam/steamapps/compatdata/1237950/pfx/drive_c/ea_app.msi` | EA App MSI (~227 MB) | -| `/home/retro/.steam/steam/steamapps/compatdata/1237950/pfx/drive_c/Program Files/Electronic Arts/EA Desktop/14.2.0.3345/` | EA Desktop files | -| `/home/retro/.steam/steam/steamapps/compatdata/1237950/pfx/drive_c/Program Files/Electronic Arts/EA Desktop/EA Desktop` | Symlink → `14.2.0.3345` | -| `/home/retro/.steam/steam/userdata//config/localconfig.vdf` | Steam per-user config (launch options) | -| `/home/retro/ea_install.sh` | Launch wrapper script | -| `/tmp/ea_install.log` | Wrapper log (inside container) | +| `/home/retro/.steam/steam/steamapps/compatdata/1237950/pfx/drive_c/link2ea_fix.reg` | link2ea protocol handler reg | +| `/home/retro/.steam/steam/steamapps/compatdata/1237950/pfx/drive_c/ea_services.reg` | EA service registrations | +| `/home/retro/.steam/steam/steamapps/common/STAR WARS Battlefront II/` | Game files | +| `/home/retro/.steam/steam/userdata//config/localconfig.vdf` | Steam launch options | +| `/home/retro/ea_install.sh` | Launch wrapper | +| `/tmp/ea_install.log` | Wrapper log (resets on container restart) | diff --git a/services/wolf.sh b/services/wolf.sh index 3218b0f..1e50282 100644 --- a/services/wolf.sh +++ b/services/wolf.sh @@ -2065,6 +2065,265 @@ PYEOF echo "Expected path: $_dx_log" fi ;; + setup-swbf2) + # Full automated setup for Star Wars Battlefront II (2017, AppID 1237950) + # on Wolf/Moonlight with GE-Proton. Implements the msiextract bypass for + # the JunoConfigureRegistry Wine incompatibility, installs EA Desktop files + # into the Wine prefix, and wires up a launch wrapper so registry fixes + # survive wineserver restarts. + # + # Prerequisites (do these first, then run this command): + # 1. Wolf is running and Steam is open in Moonlight + # 2. GE-Proton10-34+ is installed (./manage.sh ge-proton) + # 3. SWBF2 (AppID 1237950) is set to use GE-Proton in Steam → Compatibility + # 4. msitools is installed on the host (sudo apt-get install -y msitools) + # 5. Your EA account is linked to your Steam account at ea.com (one-time manual step) + # 6. Launch SWBF2 once from Moonlight, wait ~10s for the "Origin is not installed" + # error, then close it — this triggers Wine prefix creation and drops ea_app.msi + # + # After this command completes: launch SWBF2 from Moonlight. Let Vulkan + # shaders compile on first run (takes several minutes, only once). EA App + # authenticates via your linked Steam/EA account and the game launches. + # + # Usage: ./manage.sh setup-swbf2 + _sw_appid="1237950" + _sw_sh=$(_steam_home) + if [ -z "$_sw_sh" ]; then + echo "Steam home not found. Start Wolf (./manage.sh start) and open Steam in Moonlight first." + exit 1 + fi + _sw_container=$(docker ps --format '{{.Names}}' | grep -i WolfSteam | head -1) + if [ -z "$_sw_container" ]; then + echo "WolfSteam container is not running. Start Wolf: ./manage.sh start" + exit 1 + fi + _sw_pfxc="$_sw_sh/.steam/steam/steamapps/compatdata/$_sw_appid/pfx/drive_c" + _sw_msi="$_sw_pfxc/ea_app.msi" + + echo "=== SWBF2 (2017) Wolf/Moonlight Setup ===" + echo "" + + # ── Step 1: verify Wine prefix exists ────────────────────────────────── + if [ ! -d "$_sw_pfxc" ]; then + echo "ERROR: Wine prefix not found at $_sw_pfxc" + echo "" + echo "Required setup before running this command:" + echo " 1. In Steam (via Moonlight): right-click SWBF2 → Properties → Compatibility" + echo " → Force GE-Proton10-34 (or later)" + echo " 2. Click Play on SWBF2 — wait ~10 seconds for 'Origin is not installed' error" + echo " 3. Close the error and return here" + exit 1 + fi + echo "[1/6] Wine prefix found: OK" + + # ── Step 2: check ea_app.msi exists ──────────────────────────────────── + if [ ! -f "$_sw_msi" ]; then + echo "" + echo "ERROR: ea_app.msi not found at $_sw_msi" + echo "" + echo "SWBF2 must be launched once so Steam drops ea_app.msi into the Wine prefix." + echo " 1. Open Steam in Moonlight" + echo " 2. Click Play on SWBF2" + echo " 3. Wait ~10 seconds — you will see an 'Origin is not installed' error" + echo " 4. Close the error popup" + echo " 5. Re-run: ./manage.sh setup-swbf2" + echo "" + echo "Expected file size: ~227 MB" + exit 1 + fi + _sw_msi_size=$(stat -c%s "$_sw_msi" 2>/dev/null || echo 0) + if [ "$_sw_msi_size" -lt 50000000 ]; then + echo "WARNING: ea_app.msi looks too small ($_sw_msi_size bytes, expected ~227 MB)." + echo "It may still be copying. Try again in a moment." + exit 1 + fi + echo "[2/6] ea_app.msi found ($(( _sw_msi_size / 1048576 )) MB): OK" + + # ── Step 3: extract MSI on the host (bypasses JunoConfigureRegistry) ── + echo "[3/6] Extracting EA Desktop files from MSI (this takes ~30s)..." + if ! command -v msiextract >/dev/null 2>&1; then + echo "ERROR: msitools not installed. Run: sudo apt-get install -y msitools" + exit 1 + fi + _sw_extract_dir="/tmp/ea_app_extracted_$$" + rm -rf "$_sw_extract_dir" + mkdir -p "$_sw_extract_dir" + if ! msiextract -C "$_sw_extract_dir" "$_sw_msi" >/dev/null 2>&1; then + echo "ERROR: msiextract failed. Check that msitools is properly installed." + rm -rf "$_sw_extract_dir" + exit 1 + fi + _sw_ea_src=$(find "$_sw_extract_dir" -maxdepth 4 \ + -path "*/Electronic Arts/EA Desktop/EA Desktop" -type d 2>/dev/null | head -1) + if [ -z "$_sw_ea_src" ] || [ ! -f "$_sw_ea_src/Link2EA.exe" ]; then + echo "ERROR: Link2EA.exe not found after extraction. MSI structure may have changed." + echo "Expected: /Electronic Arts/EA Desktop/EA Desktop/Link2EA.exe" + ls -la "$_sw_extract_dir/Electronic Arts/EA Desktop/" 2>/dev/null || true + rm -rf "$_sw_extract_dir" + exit 1 + fi + echo " Extracted to: $_sw_ea_src" + echo " $(ls "$_sw_ea_src" | wc -l) files found including Link2EA.exe" + + # ── Step 4: copy EA Desktop files into Wine prefix ───────────────────── + echo "[4/6] Installing EA Desktop files into Wine prefix..." + _sw_ea_dest_base="$_sw_pfxc/Program Files/Electronic Arts/EA Desktop" + # Determine the version directory name from the extracted content. + # The MSI extracts to "EA Desktop" but the real install uses a versioned dir + # with a symlink. Use the same version string if a broken symlink exists from + # a failed install attempt; otherwise default to 14.2.0.3345. + _sw_ea_version=$( \ + docker exec "$_sw_container" \ + readlink "/home/retro/.steam/steam/steamapps/compatdata/$_sw_appid/pfx/drive_c/Program Files/Electronic Arts/EA Desktop/EA Desktop" \ + 2>/dev/null || echo "14.2.0.3345") + _sw_ea_dest="$_sw_ea_dest_base/$_sw_ea_version" + sudo mkdir -p "$_sw_ea_dest" + sudo cp -r "$_sw_ea_src/." "$_sw_ea_dest/" + sudo chown -R 1000:1000 "$_sw_ea_dest_base" + # Remove any broken symlink, then create a clean one + sudo rm -f "$_sw_ea_dest_base/EA Desktop" + docker exec "$_sw_container" bash -c \ + "ln -sf '$_sw_ea_version' '/home/retro/.steam/steam/steamapps/compatdata/$_sw_appid/pfx/drive_c/Program Files/Electronic Arts/EA Desktop/EA Desktop'" \ + 2>/dev/null || sudo ln -sf "$_sw_ea_version" "$_sw_ea_dest_base/EA Desktop" + if [ ! -f "$_sw_ea_dest/Link2EA.exe" ]; then + echo "ERROR: Copy failed — Link2EA.exe not found at $_sw_ea_dest/Link2EA.exe" + rm -rf "$_sw_extract_dir" + exit 1 + fi + echo " EA Desktop installed to: $( echo "$_sw_ea_dest" | sed "s|$_sw_sh||" )" + rm -rf "$_sw_extract_dir" + + # ── Step 5: write .reg files and wrapper script into Wine prefix ──────── + echo "[5/6] Writing registry fix files and launch wrapper..." + _sw_drive_c="$_sw_pfxc" + + # link2ea:// protocol handler — points Wine at Link2EA.exe + sudo tee "$_sw_drive_c/link2ea_fix.reg" > /dev/null << 'REGEOF' +Windows Registry Editor Version 5.00 + +[HKEY_LOCAL_MACHINE\SOFTWARE\Classes\link2ea] +@="URL:link2ea Protocol" +"URL Protocol"="" + +[HKEY_LOCAL_MACHINE\SOFTWARE\Classes\link2ea\shell\open\command] +@="\"C:\\Program Files\\Electronic Arts\\EA Desktop\\EA Desktop\\Link2EA.exe\" \"%1\"" +REGEOF + + # EA Desktop Windows services — EALocalHostSvc provides local IPC that + # Link2EA.exe requires; without it launch fails with RPC_S_SERVER_UNAVAILABLE + sudo tee "$_sw_drive_c/ea_services.reg" > /dev/null << 'REGEOF' +Windows Registry Editor Version 5.00 + +[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\EALocalHostSvc] +"Type"=dword:00000010 +"Start"=dword:00000002 +"ErrorControl"=dword:00000001 +"ImagePath"="C:\\Program Files\\Electronic Arts\\EA Desktop\\EA Desktop\\EALocalHostSvc.exe" +"DisplayName"="EA Local Host Service" +"ObjectName"="LocalSystem" + +[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\EABackgroundService] +"Type"=dword:00000010 +"Start"=dword:00000002 +"ErrorControl"=dword:00000001 +"ImagePath"="C:\\Program Files\\Electronic Arts\\EA Desktop\\EA Desktop\\EABackgroundService.exe" +"DisplayName"="EA Background Service" +"ObjectName"="LocalSystem" +REGEOF + sudo chown 1000:1000 "$_sw_drive_c/link2ea_fix.reg" "$_sw_drive_c/ea_services.reg" + + # Launch wrapper — runs regedit via Steam's launch chain on every launch + # so registry entries survive wineserver restarts. Direct edits to + # system.reg are overwritten by wineserver when it flushes to disk. + sudo tee /tmp/ea_install_wrapper.sh > /dev/null << 'WRAPEOF' +#!/bin/bash +echo "=== launch $(date) ===" >> /tmp/ea_install.log +"$1" "$2" "$3" "$4" "$5" "$6" "$7" "$8" "$9" "${10}" "${11}" regedit /S "C:\\link2ea_fix.reg" +"$1" "$2" "$3" "$4" "$5" "$6" "$7" "$8" "$9" "${10}" "${11}" regedit /S "C:\\ea_services.reg" +exec "$@" +WRAPEOF + chmod +x /tmp/ea_install_wrapper.sh + docker cp /tmp/ea_install_wrapper.sh "$_sw_container":/home/retro/ea_install.sh + docker exec "$_sw_container" chmod +x /home/retro/ea_install.sh + rm -f /tmp/ea_install_wrapper.sh + echo " .reg files and wrapper script installed." + + # ── Step 6: set LaunchOptions in localconfig.vdf and lock config dir ─── + echo "[6/6] Setting Steam launch options and locking localconfig.vdf..." + _sw_steam_uid=$(sudo ls "$_sw_sh/.steam/steam/userdata/" 2>/dev/null | head -1) + if [ -z "$_sw_steam_uid" ]; then + echo "WARNING: No Steam userdata found. You may need to have logged into Steam first." + echo " Open Steam in Moonlight, sign in, then re-run this command." + else + _sw_cfg_dir="$_sw_sh/.steam/steam/userdata/$_sw_steam_uid/config" + _sw_cfg_file="$_sw_cfg_dir/localconfig.vdf" + if [ ! -f "$_sw_cfg_file" ]; then + echo "WARNING: localconfig.vdf not found at $_sw_cfg_file" + echo " Open Steam in Moonlight first, then re-run." + else + # Stop Steam so it flushes current state before we edit + docker exec "$_sw_container" pkill -f steam.sh 2>/dev/null || true + sleep 5 + + # Make sure config dir is writable long enough to edit the file + sudo chmod 755 "$_sw_cfg_dir" 2>/dev/null || true + + _sw_launch_opt='STEAM_UNIX_SOCKET=/tmp/steam.sock /home/retro/ea_install.sh %command%' + sudo python3 - "$_sw_cfg_file" "$_sw_launch_opt" << 'PYEOF' +import sys, re +path, launch_opt = sys.argv[1], sys.argv[2] +with open(path, 'r') as f: + content = f.read() +new = re.sub( + r'("1237950".*?"LaunchOptions"\s*)"[^"]*"', + rf'\1"{launch_opt}"', + content, flags=re.DOTALL +) +if new == content: + new = re.sub( + r'("1237950"\s*\n\s*\{)', + rf'\1\n\t\t\t\t"LaunchOptions"\t\t"{launch_opt}"', + content + ) +if new == content: + print("WARNING: 1237950 block not found in localconfig.vdf — launch options not set.") + print("In Steam: right-click SWBF2 → Properties → Launch Options, enter:") + print(" STEAM_UNIX_SOCKET=/tmp/steam.sock /home/retro/ea_install.sh %command%") +else: + with open(path, 'w') as f: + f.write(new) + print(" LaunchOptions set for AppID 1237950.") +PYEOF + sudo chown 1000:1000 "$_sw_cfg_file" + + # Lock the config directory so Steam can't overwrite localconfig.vdf + # via atomic rename (temp file + rename). chmod 444 on the file is + # bypassed; locking the directory blocks the rename-into. + sudo chmod 555 "$_sw_cfg_dir" + echo " Config directory locked (chmod 555) to preserve launch options." + fi + fi + + echo "" + echo "=== Setup complete ===" + echo "" + echo "Next steps:" + echo " 1. In Steam (Moonlight): right-click SWBF2 → Properties → Compatibility" + echo " → Force a specific Steam Play compatibility tool → GE-Proton10-34 (or later)" + echo " 2. Click Play on SWBF2" + echo " 3. Let Vulkan shaders compile — do NOT skip, takes several minutes, only happens once" + echo " 4. EA App will authenticate via your linked Steam/EA account (no manual login needed" + echo " if accounts are linked at ea.com)" + echo " 5. Game should launch" + echo "" + echo "Troubleshooting:" + echo " ./manage.sh diagnose-ea $_sw_appid - check registry state" + echo " docker exec \$container tail /tmp/ea_install.log - check wrapper log" + echo "" + echo "Note: If the config dir was not writable (Steam not stopped cleanly), set launch" + echo "options manually in Steam → SWBF2 → Properties → Launch Options:" + echo " STEAM_UNIX_SOCKET=/tmp/steam.sock /home/retro/ea_install.sh %command%" + ;; fix-perms) # Wolf creates each app's home dir under WOLF_STATE_DIR// # and mounts it as /home/retro inside the app container. If anything in @@ -2099,7 +2358,7 @@ PYEOF _manage_wolf_complete() { local cur="${COMP_WORDS[COMP_CWORD]}" local commands="start stop restart logs status pin update apps reorder - add-web ge-proton games fix-ea-game wait-ea-app + add-web ge-proton games setup-swbf2 fix-ea-game wait-ea-app install-ea-app diagnose-ea fix-perms install-completion backup" COMPREPLY=( $(compgen -W "$commands" -- "$cur") ) } @@ -2156,6 +2415,7 @@ COMPEOF echo " ./manage.sh add-web [name] [url] - Add a URL shortcut tile (Firefox kiosk)" echo " ./manage.sh ge-proton [version] - Install GE-Proton (latest, or pin a version)" echo " ./manage.sh games - List games; pick one to apply the EA fix" + echo " ./manage.sh setup-swbf2 - Full SWBF2 2017 setup (EA Desktop + wrapper, see docs/SWBF2-2017-wolf-setup.md)" echo " ./manage.sh fix-ea-game [appid] - Apply EA fix directly (default: SWBF2 1237950)" echo " ./manage.sh wait-ea-app [appid] - Watch for EA App to install, then register link2ea:// handler" echo " ./manage.sh install-ea-app [appid] - Run EA App installer inside container (GUI appears in Moonlight)"