Merge pull request #109 from outis1one/claude/zealous-heisenberg-8ylloi
Claude/zealous heisenberg 8ylloi
This commit is contained in:
+244
-179
@@ -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 -- <sniper> --verb=waitforexitandrun -- <proton> waitforexitandrun <game>
|
||||
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/<uid>/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/<uid>/config/localconfig.vdf` | Steam launch options |
|
||||
| `/home/retro/ea_install.sh` | Launch wrapper |
|
||||
| `/tmp/ea_install.log` | Wrapper log (resets on container restart) |
|
||||
|
||||
+261
-1
@@ -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: <extracted>/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/<session-id>/<App>
|
||||
# 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)"
|
||||
|
||||
Reference in New Issue
Block a user