Merge pull request #135 from outis1one/claude/zealous-heisenberg-8ylloi

Claude/zealous heisenberg 8ylloi
This commit is contained in:
Outis
2026-06-25 12:06:34 -04:00
committed by GitHub
2 changed files with 323 additions and 542 deletions
+80
View File
@@ -137,3 +137,83 @@ then either:
Tested on **Ubuntu 24.04 LTS** and **26.04 LTS**. Tested on **Ubuntu 24.04 LTS** and **26.04 LTS**.
Works on any Ubuntu LTS ≥ 22.04; non-LTS releases also work. Works on any Ubuntu LTS ≥ 22.04; non-LTS releases also work.
The wizard shows the detected OS in the header and warns on unknown versions. The wizard shows the detected OS in the header and warns on unknown versions.
## Gaming scripts
Standalone scripts in `scripts/` for gaming setup — not part of the main
wizard, run separately.
### Star Wars Battlefront II (2017) + Kyber
**`scripts/setup-swbf2-linux.sh`** — Configure SWBF2 on native Linux Steam
(Proton, controller, performance tweaks).
**`scripts/setup-kyber-linux.sh`** — Install the native Linux Kyber launcher.
Kyber is the community multiplayer replacement for SWBF2 after EA shut down
official servers in 2022. It went open-source (GPL) in January 2026.
**The correct approach is a native Linux AppImage** — not Wine or Proton for
the launcher itself. The AppImage is maintained at:
https://github.com/simonlinuxcraft/kyber-linuxport-unofficial
```bash
chmod +x scripts/setup-kyber-linux.sh
./scripts/setup-kyber-linux.sh
```
The script downloads the latest AppImage, installs a desktop entry, and
creates a `kyber` command in `~/.local/bin`.
**Every time you want to play:**
1. Open **Steam** (must be running for library validation) — do NOT click Play on SWBF2
2. Launch **Kyber** (`kyber` or from the app menu)
3. In Kyber: join a server (HOME) or create one (HOST)
4. Kyber/Maxima launches SWBF2 via its own bundled GE-Proton — wait 1-3 minutes
5. If the SWBF2 window appears but won't focus: press **Alt+Tab** or click its
taskbar entry — this is normal when the game is launched by a wrapper process
Do NOT launch SWBF2 from Steam directly. If Steam's SWBF2 is already running
when Kyber starts, kill it first — Kyber cannot inject into a Steam-launched instance.
**If Kyber says "Game Not Found":**
Click **SET GAME FOLDER** and point it to the SWBF2 install directory.
Find it with:
```bash
find ~/.steam/steam/steamapps -name "starwarsbattlefrontii.exe" 2>/dev/null | head -1 | xargs dirname
```
Paste that path into the SET GAME FOLDER dialog.
**First run (one-time setup):**
1. Click **EA Account** → log in with your EA credentials in the browser
2. Click **Skip** on Nexus Mods (optional, only needed for mods)
3. EA login is cached — you stay logged in across sessions
**Hosting a private server with bots:**
- HOST → pick maps/modes → set a **name** and **PASSWORD** → Start Server
- Share the server name + password with friends; they search by name in HOME
- Bot count: in the HOST panel right side → **AUTOPLAYERS** section →
set **BOTS TEAM 1** and **BOTS TEAM 2** (e.g. 4 each) → click **UPDATE SERVER**
- Bot difficulty: the **BOT DIFFICULTY** slider (RECRUIT → OFFICER → KNIGHT → MASTER)
- After the game loads you can also update settings live and hit UPDATE SERVER again
**Requirements:**
- SWBF2 (Steam AppID 1237950) installed via Steam
(Kyber manages its own GE-Proton for launching the game)
- glibc 2.38+ — Ubuntu 24.04+, Fedora 38+, SteamOS 3.7+
- EA account (free) at ea.com
- Unprivileged user namespaces enabled (Ubuntu 24.04 restricts these by default):
```bash
sudo sysctl -w kernel.unprivileged_userns_clone=1
sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=0
```
The setup script applies this automatically when run with sudo and saves it
to `/etc/sysctl.d/99-userns.conf` to persist across reboots.
Without this fix Kyber fails with: `bwrap: setting up uid map: Permission denied`
**What does NOT work:**
- Running the Windows `kyber_launcher.exe` under Wine/Proton: EA's auth
callback uses the `eadesktop://` URI scheme which has no Linux handler,
and Wine's cmd.exe crashes on long OAuth URLs anyway
- Running Kyber inside Wolf/Games-on-Whales: the Docker double-sandbox
blocks the user namespace clone that Proton requires
+234 -533
View File
@@ -1,577 +1,278 @@
#!/bin/bash #!/bin/bash
# setup-kyber-linux.sh — Install the Kyber Launcher for SWBF2 (2017) on a # setup-kyber-linux.sh — Install the Kyber Launcher (native Linux port) for
# native Linux Steam machine (headless or desktop) with Proton Experimental. # SWBF2 (2017) on a native Linux Steam machine.
# #
# Kyber is a community multiplayer client for Star Wars Battlefront II (2017) # Kyber is a community multiplayer client for Star Wars Battlefront II (2017)
# after EA shut down the official servers. It is a Windows app (Flutter/Rust). # after EA shut down the official servers in 2022. Kyber went open-source
# under GPL in January 2026.
# #
# ── How Kyber's EA login actually works ──────────────────────────────────── # ── The RIGHT way: native Linux AppImage ───────────────────────────────────
# #
# Kyber uses the "Maxima" OAuth PKCE flow: # As of 2026 there is an unofficial native Linux port of Kyber:
# 1. Kyber starts a temporary HTTP server on 127.0.0.1 (dynamic port ~41413+) # https://github.com/simonlinuxcraft/kyber-linuxport-unofficial
# 2. It calls cmd /c start "" "<EA auth URL>" to open a browser
# 3. You log in on the EA page; EA redirects to 127.0.0.1:PORT/?code=...
# 4. Kyber's HTTP server catches the code and exchanges it for tokens
# #
# Problem: Wine's cmd.exe crashes with STATUS_ACCESS_VIOLATION (0xC0000005) # It ships as a self-contained AppImage (x86_64). No Wine, no Proton, no
# when called as cmd /c start "" "<URL>". This blocks the login entirely. # cmd.exe shims, no OAuth watcher daemons. EA login is handled natively by
# the bundled Maxima service (open-source EA Desktop replacement by the
# Armchair Developers team).
# #
# Fix: Windows Image File Execution Options (IFEO) lets us intercept any # Tested on: Ubuntu 24.04+, Fedora, SteamOS 3.7+ (requires glibc 2.38+)
# cmd.exe launch at the registry level without touching system32/cmd.exe # Recommended Proton for SWBF2 itself: GE-Proton 10.x or proton-cachyos 11.x
# (which Proton resets on every launch). We register a tiny shim that # (Kyber/Maxima downloads and manages its own GE-Proton automatically)
# captures the http URL, writes it to a known file, and exits 0. A Linux
# watcher picks up the file and calls xdg-open to open the URL in the
# system browser, completing the OAuth flow normally.
# #
# ── Why NOT to run Kyber inside Wolf / Games-on-Whales ───────────────────── # ── Login flow ─────────────────────────────────────────────────────────────
# #
# Wolf runs Docker + Proton's bwrap nested. The double-sandbox blocks # 1. Launch the AppImage.
# CLONE_NEWUSER which breaks WebView2, AND the inner bwrap prevents # 2. Click "EA Account" — a browser window opens to accounts.ea.com.
# xdg-open from reaching the host display. Kyber's loopback redirect to # 3. Log in with your EA account.
# 127.0.0.1:PORT also fails inside the container network stack. Use native # 4. Kyber completes authentication via Maxima (no redirect hacks needed).
# Linux Steam instead — it's the supported path. # 5. Click "Skip" on Nexus Mods if you don't use mods.
# #
# ── WebView2 ─────────────────────────────────────────────────────────────── # ── How to play ────────────────────────────────────────────────────────────
# #
# WebView2 is NOT required for the login flow (Maxima handles that). This # Kyber launches SWBF2 itself — do NOT launch SWBF2 from Steam first.
# script still installs the WebView2 Evergreen runtime because Kyber may use # If Steam's SWBF2 is running when Kyber starts, kill it.
# it for in-app content rendering. Installing it causes no harm and prevents
# any fallback-related error dialogs inside Kyber.
# #
# ── Headless note ────────────────────────────────────────────────────────── # 1. Open Steam (must be running for library access, but do NOT click Play).
# 2. Launch Kyber (AppImage or 'kyber' command).
# 3. In Kyber: join a server (HOME) or create one (HOST).
# 4. Kyber/Maxima launches SWBF2 via its own bundled GE-Proton.
# 5. Wait for the Frostbite/SWBF2 loading screen. This takes 1-3 minutes.
# 6. If the SWBF2 window appears but looks stuck: click its taskbar entry
# or press Alt+Tab to bring it into focus — this is normal behaviour
# when the game is launched by a wrapper process rather than directly.
# #
# On a headless GPU box use Steam Remote Play: install Steam, configure a # ── Hosting a private server ───────────────────────────────────────────────
# virtual/dummy display so Steam has something to render to, add Kyber + #
# SWBF2, then connect with the Steam Link app from any device. # 1. In Kyber, click HOST.
# This script warns if no display is detected at setup time. # 2. Select maps/modes for your rotation (drag into Active Rotation).
# 3. In the right panel: set a name, set a PASSWORD (keeps it private).
# 4. Click Settings to adjust max players etc.
# 5. Click START SERVER.
# 6. Share the server name + password with friends — they search by name
# in the HOME tab and enter the password to join.
#
# Bots: SWBF2 fills empty player slots with AI automatically. No separate
# bot count setting is needed — just start the server and join it.
#
# ── Why NOT Wine/Proton for the Kyber launcher ─────────────────────────────
#
# The Windows Kyber launcher (kyber_launcher.exe) has a fatal flaw on Linux:
# its EA OAuth flow calls cmd /c start "" "<URL>" to open a browser.
# Wine's cmd.exe crashes with STATUS_ACCESS_VIOLATION (0xC0000005) on long
# URLs. Fixing this requires replacing Proton's own cmd.exe binary with a
# shim — and the shim gets overwritten on every Proton update. Additionally,
# EA's auth callback uses the eadesktop:// URI scheme (not http://127.0.0.1
# as originally believed), which has no Linux handler. The native AppImage
# bypasses all of this entirely.
# #
# ── Prerequisites ────────────────────────────────────────────────────────── # ── Prerequisites ──────────────────────────────────────────────────────────
# a. Steam installed and launched at least once (native, not Flatpak ideally # a. SWBF2 (AppID 1237950) installed via Steam.
# — Flatpak works but paths differ; this script handles both). # Run setup-swbf2-linux.sh first if needed.
# b. SWBF2 (AppID 1237950) installed and working with Proton Experimental # NOTE: Kyber manages its own GE-Proton for launching the game — you
# (run setup-swbf2-linux.sh first). # do not need to configure a Proton version for SWBF2 in Steam.
# c. KyberLauncher.exe downloaded from https://kyber.gg saved to # b. Internet access to download the AppImage (~173 MB).
# ~/Downloads/KyberLauncher.exe (or pass the path as $1). # c. glibc 2.38+ (Ubuntu 24.04+, Fedora 38+, SteamOS 3.7+).
# On Ubuntu 22.04 the AppImage may not run — upgrade to 24.04.
# d. Unprivileged user namespaces enabled (required for Kyber's sandbox).
# This script checks and optionally fixes this for you (see below).
#
# ── Unprivileged user namespaces (bwrap) ───────────────────────────────────
#
# Kyber's AppImage uses bubblewrap (bwrap) for sandboxing. Ubuntu 24.04
# restricts unprivileged user namespaces by default, which causes the error:
# bwrap: setting up uid map: Permission denied
#
# Fix (this script can apply these automatically):
# sudo sysctl -w kernel.unprivileged_userns_clone=1
# sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=0
#
# To make permanent across reboots, write to /etc/sysctl.d/99-userns.conf:
# kernel.unprivileged_userns_clone = 1
# kernel.apparmor_restrict_unprivileged_userns = 0
#
# ── "Game Not Found" dialog ────────────────────────────────────────────────
#
# If Kyber shows "Game Not Found" after launching:
# 1. Click SET GAME FOLDER in Kyber.
# 2. Run this command to find your SWBF2 install path:
# find ~/.steam/steam/steamapps -name 'starwarsbattlefrontii.exe' 2>/dev/null | head -1 | xargs dirname
# 3. Paste that path into the dialog. Kyber remembers it after this.
# #
# ── Usage ────────────────────────────────────────────────────────────────── # ── Usage ──────────────────────────────────────────────────────────────────
# chmod +x setup-kyber-linux.sh # chmod +x setup-kyber-linux.sh
# ./setup-kyber-linux.sh [/path/to/KyberLauncher.exe] # ./setup-kyber-linux.sh
set -euo pipefail set -euo pipefail
KYBER_INSTALLER="${1:-$HOME/Downloads/KyberLauncher.exe}" KYBER_REPO="simonlinuxcraft/kyber-linuxport-unofficial"
KYBER_APPID="9900000001" # non-Steam shortcut appid INSTALL_DIR="$HOME/.local/share/kyber"
KYBER_COMPAT_ID="$KYBER_APPID" # prefix dir must match appid for Steam to find it DESKTOP_FILE="$HOME/.local/share/applications/kyber-launcher.desktop"
BIN_LINK="$HOME/.local/bin/kyber"
echo "=== Kyber Launcher — Native Linux Steam Setup ===" echo "=== Kyber Launcher — Native Linux Setup ==="
echo "" echo ""
# ── Locate Steam home ────────────────────────────────────────────────────── # ── Check glibc version ────────────────────────────────────────────────────
find_steam_home() { GLIBC=$(ldd --version 2>/dev/null | head -1 | grep -oP '\d+\.\d+$' || echo "0.0")
for candidate in \ GLIBC_MAJOR=$(echo "$GLIBC" | cut -d. -f1)
"$HOME/.steam/steam" \ GLIBC_MINOR=$(echo "$GLIBC" | cut -d. -f2)
"$HOME/.local/share/Steam" \ if [ "$GLIBC_MAJOR" -lt 2 ] || { [ "$GLIBC_MAJOR" -eq 2 ] && [ "$GLIBC_MINOR" -lt 38 ]; }; then
"$HOME/.var/app/com.valvesoftware.Steam/.steam/steam"; do echo "WARNING: glibc $GLIBC detected. The Kyber AppImage requires glibc 2.38+."
if [ -d "$candidate/steamapps" ]; then echo " Ubuntu 22.04 ships glibc 2.35 — upgrade to Ubuntu 24.04 or use"
echo "$candidate" echo " a newer distro. Continuing anyway in case your system has it..."
return 0 echo ""
fi fi
done
return 1
}
STEAM_HOME=$(find_steam_home) || { # ── Check/fix unprivileged user namespaces (bwrap requirement) ────────────
echo "ERROR: Steam home not found. Install Steam and launch it once." echo "[0/3] Checking unprivileged user namespace support (required for Kyber)..."
exit 1
}
echo "Steam home: $STEAM_HOME"
# ── Verify / download Kyber installer ───────────────────────────────────── USERNS_OK=true
if [ ! -f "$KYBER_INSTALLER" ]; then CLONE_VAL=$(sysctl -n kernel.unprivileged_userns_clone 2>/dev/null || echo "1")
APPARMOR_VAL=$(sysctl -n kernel.apparmor_restrict_unprivileged_userns 2>/dev/null || echo "0")
if [ "$CLONE_VAL" != "1" ] || [ "$APPARMOR_VAL" != "0" ]; then
USERNS_OK=false
echo " WARNING: Unprivileged user namespaces are restricted."
echo " Kyber uses bubblewrap (bwrap) which requires them."
echo " Without this fix you will see: bwrap: setting up uid map: Permission denied"
echo "" echo ""
echo "Kyber installer not found at: $KYBER_INSTALLER" if [ "$(id -u)" -eq 0 ]; then
echo "Downloading from kyber.gg API..." echo " Applying fix now (running as root)..."
mkdir -p "$(dirname "$KYBER_INSTALLER")" sysctl -w kernel.unprivileged_userns_clone=1
KYBER_ZIP="/tmp/kyber-installer-$$.zip" sysctl -w kernel.apparmor_restrict_unprivileged_userns=0
KYBER_DL_URL="https://api.prod.kyber.gg/download/kyber-installer-win64.zip" SYSCTL_FILE="/etc/sysctl.d/99-userns.conf"
if curl -L --progress-bar -o "$KYBER_ZIP" "$KYBER_DL_URL" && [ -s "$KYBER_ZIP" ]; then cat > "$SYSCTL_FILE" << 'SYSCTL'
# Extract the installer .exe from the zip # Required for Kyber AppImage (bubblewrap sandbox)
_exe=$(unzip -Z1 "$KYBER_ZIP" 2>/dev/null | grep -i '\.exe$' | head -1) kernel.unprivileged_userns_clone = 1
if [ -z "$_exe" ]; then kernel.apparmor_restrict_unprivileged_userns = 0
echo "ERROR: No .exe found inside the downloaded zip." SYSCTL
rm -f "$KYBER_ZIP" echo " Saved to $SYSCTL_FILE — will persist across reboots."
USERNS_OK=true
else
echo " To fix, run these commands (requires sudo):"
echo " sudo sysctl -w kernel.unprivileged_userns_clone=1"
echo " sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=0"
echo ""
echo " To make permanent, create /etc/sysctl.d/99-userns.conf:"
echo " echo 'kernel.unprivileged_userns_clone = 1' | sudo tee /etc/sysctl.d/99-userns.conf"
echo " echo 'kernel.apparmor_restrict_unprivileged_userns = 0' | sudo tee -a /etc/sysctl.d/99-userns.conf"
echo ""
echo " Re-run this script with sudo to apply automatically."
echo " Continuing with download regardless..."
fi
else
echo " OK — unprivileged user namespaces are enabled."
fi
echo ""
# ── Fetch latest release URL ───────────────────────────────────────────────
echo "[1/3] Fetching latest Kyber Linux release..."
API_URL="https://api.github.com/repos/${KYBER_REPO}/releases/latest"
APPIMAGE_URL=$(curl -fsSL "$API_URL" \
| python3 -c "
import json, sys
data = json.load(sys.stdin)
assets = data.get('assets', [])
for a in assets:
url = a['browser_download_url']
if url.endswith('.AppImage'):
print(url)
break
" 2>/dev/null)
if [ -z "$APPIMAGE_URL" ]; then
echo "ERROR: Could not fetch AppImage URL from GitHub."
echo " Check: https://github.com/${KYBER_REPO}/releases"
echo " Download the AppImage manually and run: chmod +x KyberLinuxPort*.AppImage && ./KyberLinuxPort*.AppImage"
exit 1 exit 1
fi fi
unzip -p "$KYBER_ZIP" "$_exe" > "$KYBER_INSTALLER"
rm -f "$KYBER_ZIP" VERSION=$(echo "$APPIMAGE_URL" | grep -oP 'v[\d.a-z-]+' | head -1)
echo "Extracted: $(basename "$_exe")$KYBER_INSTALLER" echo " Latest: $VERSION"
echo " URL: $APPIMAGE_URL"
echo ""
# ── Download ───────────────────────────────────────────────────────────────
mkdir -p "$INSTALL_DIR"
APPIMAGE_PATH="$INSTALL_DIR/KyberLinuxPort.AppImage"
CURRENT_VERSION=""
if [ -f "$APPIMAGE_PATH.version" ]; then
CURRENT_VERSION=$(cat "$APPIMAGE_PATH.version")
fi
if [ -f "$APPIMAGE_PATH" ] && [ "$CURRENT_VERSION" = "$VERSION" ]; then
echo "[2/3] Already up to date ($VERSION) — skipping download."
else else
echo "[2/3] Downloading Kyber Linux AppImage ($VERSION)..."
curl -L --progress-bar -o "$APPIMAGE_PATH" "$APPIMAGE_URL"
chmod +x "$APPIMAGE_PATH"
echo "$VERSION" > "$APPIMAGE_PATH.version"
echo " Saved to: $APPIMAGE_PATH"
fi
echo "" echo ""
echo "ERROR: Download failed."
echo "Download the zip manually from https://kyber.gg, extract the .exe, then:"
echo " $0 /path/to/KyberLauncher.exe"
rm -f "$KYBER_ZIP"
exit 1
fi
fi
echo "Kyber installer: $KYBER_INSTALLER"
# ── Locate Proton Experimental ───────────────────────────────────────────── # ── Desktop entry + bin symlink ────────────────────────────────────────────
# Kyber's WebView2 is best supported on Proton Experimental (newest Wine + echo "[3/3] Installing desktop entry and launcher..."
# the most complete WebView2/Edge compatibility shims). GE-Proton also works,
# but Experimental tends to have the freshest fixes for Chromium sandboxing.
PROTON_DIR=$(find "$STEAM_HOME/steamapps/common" -maxdepth 1 -type d \
-iname "Proton Experimental" 2>/dev/null | head -1)
if [ -z "$PROTON_DIR" ]; then
PROTON_DIR=$(find "$STEAM_HOME/steamapps/common" -maxdepth 1 -type d \
-iname "Proton*" 2>/dev/null | sort | tail -1)
fi
if [ -z "$PROTON_DIR" ] || [ ! -x "$PROTON_DIR/proton" ]; then
echo ""
echo "ERROR: Proton not found under $STEAM_HOME/steamapps/common."
echo " In Steam → Settings → Compatibility, install Proton Experimental,"
echo " then re-run this script."
exit 1
fi
echo "Proton: $PROTON_DIR"
# ── Headless display check ───────────────────────────────────────────────── mkdir -p "$(dirname "$DESKTOP_FILE")" "$HOME/.local/bin"
# WebView2 (and Kyber's Flutter UI) need a display to render to. On a headless
# box with no X/Wayland session, Kyber's window has nowhere to draw.
if [ -z "${DISPLAY:-}" ] && [ -z "${WAYLAND_DISPLAY:-}" ]; then
echo ""
echo "NOTE: No DISPLAY or WAYLAND_DISPLAY detected (headless box)."
echo " For Steam Remote Play you need a virtual display so Steam can"
echo " render. Options:"
echo " - Configure your GPU driver's dummy/virtual display (recommended"
echo " for hardware-encoded Remote Play), OR"
echo " - Run this whole setup under Xvfb for the install step only:"
echo " xvfb-run -a $0 $KYBER_INSTALLER"
echo ""
echo " Continuing the install (the prefix can be built headless), but you"
echo " must have a real or virtual display when you actually launch Kyber."
echo ""
fi
# ── Build Kyber Wine prefix and run the installer ────────────────────────── cat > "$DESKTOP_FILE" << EOF
echo "" [Desktop Entry]
echo "[1/7] Installing Kyber into Wine prefix..." Type=Application
Name=Kyber Launcher
Comment=Community multiplayer for Star Wars Battlefront II (2017)
Exec=${APPIMAGE_PATH}
Icon=kyber
Categories=Game;
StartupNotify=true
EOF
KYBER_PFX="$STEAM_HOME/steamapps/compatdata/$KYBER_COMPAT_ID" ln -sf "$APPIMAGE_PATH" "$BIN_LINK"
OLD_PFX="$STEAM_HOME/steamapps/compatdata/kyber" update-desktop-database "$HOME/.local/share/applications" 2>/dev/null || true
# Migrate old 'kyber' prefix to the numeric appid directory Steam expects
if [ -d "$OLD_PFX" ] && [ ! -d "$KYBER_PFX" ]; then
echo " Migrating prefix: compatdata/kyber → compatdata/$KYBER_COMPAT_ID"
mv "$OLD_PFX" "$KYBER_PFX"
elif [ -d "$OLD_PFX" ] && [ -d "$KYBER_PFX" ]; then
echo " Removing old compatdata/kyber (numeric prefix already exists)..."
rm -rf "$OLD_PFX"
fi
export STEAM_COMPAT_DATA_PATH="$KYBER_PFX"
export STEAM_COMPAT_CLIENT_INSTALL_PATH="$STEAM_HOME"
export PROTON_NO_ESYNC=1
# Check if Kyber is already installed — skip the installer if so.
KYBER_EXE_PATH=$(find "$KYBER_PFX/pfx" -iname "kyber_launcher.exe" 2>/dev/null | head -1)
if [ -n "$KYBER_EXE_PATH" ]; then
echo " Kyber.exe already present — skipping installer."
else
mkdir -p "$KYBER_PFX"
# Kill any leftover Wine/Proton processes from a previous attempt.
pkill -9 -f "compatdata/$KYBER_COMPAT_ID" 2>/dev/null || true
sleep 1
echo " Running installer (silent)..."
# /S = NSIS silent flag; if Kyber's installer ignores it a GUI appears.
"$PROTON_DIR/proton" run "$KYBER_INSTALLER" /S 2>/dev/null || \
"$PROTON_DIR/proton" run "$KYBER_INSTALLER" 2>/dev/null || true
sleep 5
KYBER_EXE_PATH=$(find "$KYBER_PFX/pfx" -iname "kyber_launcher.exe" 2>/dev/null | head -1)
if [ -z "$KYBER_EXE_PATH" ]; then
echo ""
echo "WARNING: Kyber.exe not found after installation."
echo " Default install path assumed; continuing."
fi
fi
# Resolve Windows-style paths for the shortcut
if [ -n "$KYBER_EXE_PATH" ]; then
rel=$(echo "$KYBER_EXE_PATH" | sed "s|.*/pfx/drive_c/||")
KYBER_EXE_WIN="C:\\$(echo "$rel" | sed 's|/|\\|g')"
dir_rel=$(dirname "$rel")
KYBER_START_DIR="C:\\$(echo "$dir_rel" | sed 's|/|\\|g')\\"
else
KYBER_EXE_WIN='C:\Program Files (x86)\KYBER Launcher\kyber_launcher.exe'
KYBER_START_DIR='C:\Program Files (x86)\KYBER Launcher\'
fi
echo " Windows path: $KYBER_EXE_WIN"
# ── Ensure WebView2 Evergreen runtime is installed ─────────────────────────
echo ""
echo "[2/7] Verifying WebView2 runtime in the Kyber prefix..."
# The Evergreen runtime lives here once installed.
WV2_FOUND=$(find "$KYBER_PFX/pfx/drive_c" -iname "msedgewebview2.exe" 2>/dev/null | head -1)
if [ -n "$WV2_FOUND" ]; then
echo " WebView2 runtime present:"
echo " $WV2_FOUND"
else
echo " WebView2 runtime NOT found — installing..."
# Prefer winetricks if available — it handles the prefix env automatically
# and uses a cached offline installer so no Wine-internal network call needed.
if command -v winetricks >/dev/null 2>&1; then
echo " Using winetricks to install webview2..."
WINEPREFIX="$KYBER_PFX/pfx" \
WINE="$PROTON_DIR/files/lib/wine/x86_64-unix/wine64" \
winetricks -q webview2 || true
else
# Download the Evergreen STANDALONE (offline) installer — linkid=2135547.
# The bootstrapper (linkid=2124703) requires a second download from inside
# Wine which reliably fails. The standalone is ~150 MB but self-contained.
echo " Downloading WebView2 standalone installer (~150 MB)..."
WV2_STANDALONE="/tmp/WebView2RuntimeInstaller_$$.exe"
WV2_URL="https://go.microsoft.com/fwlink/p/?LinkId=2135547"
if curl -L --progress-bar -o "$WV2_STANDALONE" "$WV2_URL" && [ -s "$WV2_STANDALONE" ]; then
"$PROTON_DIR/proton" run "$WV2_STANDALONE" /silent /install || true
rm -f "$WV2_STANDALONE"
else
echo " WARNING: Could not download WebView2 standalone installer."
rm -f "$WV2_STANDALONE"
fi
fi
sleep 5
WV2_FOUND=$(find "$KYBER_PFX/pfx/drive_c" -iname "msedgewebview2.exe" 2>/dev/null | head -1)
if [ -n "$WV2_FOUND" ]; then
echo " WebView2 runtime installed:"
echo " $WV2_FOUND"
else
echo ""
echo " WARNING: WebView2 runtime still not found after install attempt."
echo " Install winetricks and re-run, or install manually:"
echo " sudo apt install winetricks"
echo " $0"
fi
fi
# ── Install cmd shim + IFEO registry hook ─────────────────────────────────
# Wine's built-in cmd.exe crashes (exit 0xC0000005) when Kyber calls:
# cmd /c start "" "<EA auth URL>"
#
# FIX: Use Windows "Image File Execution Options" (IFEO) to intercept any
# cmd.exe launch at the registry level. IFEO survives Proton's prefix-setup
# phase (which overwrites system32/cmd.exe on every game launch), because it
# lives in the registry — not the filesystem.
#
# Mechanism:
# HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\
# Image File Execution Options\cmd.exe → Debugger = C:\shim\kyber_cmd.exe
#
# Wine reads this key at CreateProcess time and runs our shim instead of
# cmd.exe. The shim scans its arguments for an http URL, writes it to
# C:\kyber_oauth_url.txt, and exits 0. A Linux-side watcher reads the file
# and calls xdg-open to open the URL in the system browser.
#
# The shim is placed in drive_c/shim/ — a directory Proton never touches.
echo ""
echo "[3/7] Installing cmd shim + IFEO registry hook for OAuth login..."
SHIM_DIR="$KYBER_PFX/pfx/drive_c/shim"
SHIM_EXE_PATH="$SHIM_DIR/kyber_cmd.exe"
SYSTEM_REG="$KYBER_PFX/pfx/system.reg"
IFEO_ALREADY=0
if grep -q "Image File Execution Options\\\\cmd.exe" "$SYSTEM_REG" 2>/dev/null; then
IFEO_ALREADY=1
fi
if [ -f "$SHIM_EXE_PATH" ] && [ "$IFEO_ALREADY" = "1" ]; then
echo " cmd shim + IFEO already installed."
else
# Compile the shim if not present
if [ ! -f "$SHIM_EXE_PATH" ]; then
if ! command -v x86_64-w64-mingw32-gcc >/dev/null 2>&1; then
echo " Installing mingw-w64..."
sudo apt-get install -y mingw-w64 || {
echo " WARNING: mingw-w64 install failed. Install manually:"
echo " sudo apt install mingw-w64"
echo " Then re-run this script."
}
fi
if command -v x86_64-w64-mingw32-gcc >/dev/null 2>&1; then
SHIM_C="/tmp/kyber_cmd_shim_$$.c"
SHIM_EXE_TMP="/tmp/kyber_cmd_shim_$$.exe"
cat > "$SHIM_C" << 'CEOF'
#include <windows.h>
static void write_url(LPCWSTR url) {
HANDLE h = CreateFileW(L"C:\\kyber_oauth_url.txt",
GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
if (h == INVALID_HANDLE_VALUE) return;
int n = WideCharToMultiByte(CP_UTF8, 0, url, -1, NULL, 0, NULL, NULL);
char *buf = (char*)HeapAlloc(GetProcessHeap(), 0, n + 1);
WideCharToMultiByte(CP_UTF8, 0, url, -1, buf, n, NULL, NULL);
DWORD w; WriteFile(h, buf, n - 1, &w, NULL);
HeapFree(GetProcessHeap(), 0, buf);
CloseHandle(h);
}
int WINAPI mainCRTStartup(void) {
/* IFEO prepends our exe name before the real command line, so skip argv[0]
and argv[1] (the debuggee path Wine adds). Scan remaining args for URL. */
int argc;
LPWSTR *argv = CommandLineToArgvW(GetCommandLineW(), &argc);
for (int i = 1; i < argc; i++) {
LPCWSTR a = argv[i];
if ((a[0]=='h'||a[0]=='H') && (a[1]=='t'||a[1]=='T') &&
(a[2]=='t'||a[2]=='T') && (a[3]=='p'||a[3]=='P')) {
write_url(a);
LocalFree(argv);
ExitProcess(0);
}
}
LocalFree(argv);
ExitProcess(0);
}
CEOF
mkdir -p "$SHIM_DIR"
if x86_64-w64-mingw32-gcc -O2 -ffreestanding -nostdlib \
-mno-stack-arg-probe \
-e mainCRTStartup -o "$SHIM_EXE_TMP" "$SHIM_C" \
-lkernel32 -lshell32; then
cp "$SHIM_EXE_TMP" "$SHIM_EXE_PATH"
echo " Shim compiled and placed: $SHIM_EXE_PATH ($(stat -c%s "$SHIM_EXE_PATH") bytes)"
else
echo " WARNING: cmd shim compile failed — login may not work."
fi
rm -f "$SHIM_C" "$SHIM_EXE_TMP"
else
echo " WARNING: mingw-w64 not found. Install it and re-run:"
echo " sudo apt install mingw-w64"
fi
else
echo " Shim already compiled."
fi
# Write IFEO registry key to system.reg
# system.reg uses \\ for path separators in key name brackets.
if [ "$IFEO_ALREADY" = "0" ] && [ -f "$SYSTEM_REG" ]; then
echo "" >> "$SYSTEM_REG"
echo "[Software\\\\Microsoft\\\\Windows NT\\\\CurrentVersion\\\\Image File Execution Options\\\\cmd.exe]" >> "$SYSTEM_REG"
echo '"Debugger"="C:\\\\shim\\\\kyber_cmd.exe"' >> "$SYSTEM_REG"
echo " IFEO registry key written to system.reg."
elif [ ! -f "$SYSTEM_REG" ]; then
echo " WARNING: system.reg not found — IFEO key not written."
echo " The prefix may not exist yet. Run the installer first (step 1)."
else
echo " IFEO registry key already present."
fi
fi
# ── OAuth watcher ──────────────────────────────────────────────────────────
echo ""
echo "[4/7] Installing OAuth watcher (opens EA login in your browser)..."
URL_FILE="$KYBER_PFX/pfx/drive_c/kyber_oauth_url.txt"
WATCHER="$HOME/.local/bin/kyber-oauth-watcher.sh"
mkdir -p "$HOME/.local/bin"
cat > "$WATCHER" << WEOF
#!/bin/bash
# Watches for Kyber's OAuth URL and opens it in the system browser.
URL_FILE="$URL_FILE"
echo "[kyber-watcher] Started. Watching \$URL_FILE"
rm -f "\$URL_FILE"
while true; do
if [ -f "\$URL_FILE" ]; then
URL=\$(cat "\$URL_FILE")
rm -f "\$URL_FILE"
if [[ "\$URL" == http* ]]; then
echo "[kyber-watcher] Opening: \$URL"
xdg-open "\$URL"
fi
fi
sleep 0.5
done
WEOF
chmod +x "$WATCHER"
# Install as a systemd user service so it is always ready when Kyber runs
SVCDIR="$HOME/.config/systemd/user"
mkdir -p "$SVCDIR"
cat > "$SVCDIR/kyber-oauth-watcher.service" << SEOF
[Unit]
Description=Kyber EA OAuth URL watcher
After=graphical-session.target
[Service]
ExecStart=$WATCHER
Restart=always
RestartSec=2
[Install]
WantedBy=default.target
SEOF
if systemctl --user daemon-reload 2>/dev/null && \
systemctl --user enable --now kyber-oauth-watcher.service 2>/dev/null; then
echo " Watcher service enabled and started."
echo " (It will auto-start on login from now on.)"
else
echo " NOTE: systemd user service could not be enabled."
echo " Start the watcher manually before clicking Login in Kyber:"
echo " $WATCHER &"
fi
# ── Add Kyber as a non-Steam shortcut ──────────────────────────────────────
echo ""
echo "[5/7] Adding Kyber as a non-Steam shortcut..."
STEAM_UID=$(ls "$STEAM_HOME/userdata/" 2>/dev/null | grep -E '^[0-9]+$' | head -1)
if [ -z "$STEAM_UID" ]; then
echo " WARNING: No Steam userdata found — sign in to Steam once, then re-run."
echo " Skipping shortcut + compat mapping."
SKIP_STEAM_CFG=1
else
SHORTCUTS_DIR="$STEAM_HOME/userdata/$STEAM_UID/config"
SHORTCUTS_FILE="$SHORTCUTS_DIR/shortcuts.vdf"
mkdir -p "$SHORTCUTS_DIR"
if [ -f "$SHORTCUTS_FILE" ] && [ ! -w "$SHORTCUTS_FILE" ]; then
echo " Fixing permissions on shortcuts.vdf..."
chmod 644 "$SHORTCUTS_FILE" || {
echo " WARNING: Cannot write $SHORTCUTS_FILE — run:"
echo " chmod 644 $SHORTCUTS_FILE"
echo " then re-run this script."
SKIP_STEAM_CFG=1
}
fi
[ -f "$SHORTCUTS_FILE" ] && cp "$SHORTCUTS_FILE" "$SHORTCUTS_FILE.bak"
python3 - "$SHORTCUTS_FILE" "$KYBER_APPID" "$KYBER_EXE_WIN" "$KYBER_START_DIR" << 'PYEOF'
import sys, struct, os
def s(key, value): # VDF string field
return b'\x01' + key.encode() + b'\x00' + value.encode() + b'\x00'
def i(key, value): # VDF int field
return b'\x02' + key.encode() + b'\x00' + struct.pack('<I', value)
shortcuts_file, appid_s, exe_win, start_dir = sys.argv[1:5]
appid = int(appid_s)
existing = b''
idx = 0
if os.path.exists(shortcuts_file):
try:
with open(shortcuts_file, 'rb') as f:
raw = f.read()
head = b'\x00shortcuts\x00'
if raw.startswith(head) and raw.endswith(b'\x08\x08'):
existing = raw[len(head):-2]
# Skip if Kyber Launcher entry already present
if b'Kyber Launcher' in existing:
print(" Kyber Launcher already in shortcuts.vdf — skipping.")
sys.exit(0)
idx = existing.count(b'\x01appid\x00')
except Exception:
existing = b''
idx = 0
entry = b'\x00' + str(idx).encode() + b'\x00'
entry += s('appid', str(appid))
entry += s('AppName', 'Kyber Launcher')
entry += s('Exe', f'"{exe_win}"')
entry += s('StartDir', f'"{start_dir}"')
entry += s('icon', '')
entry += s('ShortcutPath', '')
entry += s('LaunchOptions', '')
entry += i('IsHidden', 0)
entry += i('AllowDesktopConfig', 1)
entry += i('AllowOverlay', 1)
entry += i('OpenVR', 0)
entry += i('Devkit', 0)
entry += s('DevkitGameID', '')
entry += i('LastPlayTime', 0)
entry += b'\x08\x08'
data = b'\x00shortcuts\x00' + existing + entry + b'\x08'
with open(shortcuts_file, 'wb') as f:
f.write(data)
print(f" shortcuts.vdf written ({len(data)} bytes, entry index {idx})")
PYEOF
fi
# ── CompatToolMapping — force Proton Experimental for the Kyber shortcut ────
echo ""
echo "[6/7] Forcing Proton Experimental for the Kyber shortcut..."
if [ "${SKIP_STEAM_CFG:-0}" != "1" ]; then
CFG_FILE="$STEAM_HOME/config/config.vdf"
if [ -f "$CFG_FILE" ]; then
cp "$CFG_FILE" "$CFG_FILE.bak"
python3 - "$CFG_FILE" "$KYBER_APPID" << 'PYEOF'
import sys, re
cfg_file, appid = sys.argv[1], sys.argv[2]
with open(cfg_file) as f:
content = f.read()
if f'"{appid}"' in content:
print(f" CompatToolMapping for {appid} already present — skipping.")
sys.exit(0)
entry = (
f'\t\t\t\t"{appid}"\n\t\t\t\t{{\n'
f'\t\t\t\t\t"name"\t\t"proton_experimental"\n'
f'\t\t\t\t\t"config"\t\t""\n'
f'\t\t\t\t\t"Priority"\t\t"250"\n'
f'\t\t\t\t}}\n'
)
new = re.sub(r'("CompatToolMapping"\s*\n\s*\{)', r'\1\n' + entry, content)
if new == content:
print(" WARNING: CompatToolMapping block not found.")
print(" Set Proton Experimental for Kyber manually: right-click the")
print(" Kyber shortcut in Steam → Properties → Compatibility.")
sys.exit(0)
with open(cfg_file, 'w') as f:
f.write(new)
print(f" CompatToolMapping → proton_experimental for appid {appid}")
PYEOF
else
echo " WARNING: config.vdf not found. Set Proton Experimental manually"
echo " in the Kyber shortcut's Properties → Compatibility."
fi
fi
echo "" echo ""
echo "[7/7] Done." echo "=== Kyber Setup Complete ==="
echo "" echo ""
echo "=== Kyber Setup Complete (native Linux) ===" echo "Launch Kyber:"
echo " From terminal: kyber"
echo " From app menu: search 'Kyber Launcher'"
echo " Direct: $APPIMAGE_PATH"
echo "" echo ""
echo "Next steps:" echo "Every time you want to play:"
echo " 1. RESTART Steam so it picks up the new shortcut and compat mapping." echo " 1. Open Steam (must be running for library validation)."
echo " 2. The OAuth watcher is already running in the background." echo " Do NOT click Play on SWBF2 in Steam — Kyber launches it."
echo " (Confirm: systemctl --user status kyber-oauth-watcher)" echo " 2. Launch Kyber and join or host a server."
echo " 3. Launch 'Kyber Launcher' from your Steam Library." echo " 3. Kyber/Maxima will launch SWBF2 using its own bundled GE-Proton."
echo " 4. Click Login. Kyber calls cmd /c start with the EA auth URL." echo " 4. Wait 1-3 minutes for the Frostbite loading screen."
echo " The cmd shim intercepts it and writes it to a file. The watcher" echo " 5. If SWBF2 appears but you can't click into it: press Alt+Tab or"
echo " picks it up and opens it in your browser via xdg-open." echo " click its taskbar entry to bring it into focus."
echo " 5. Log in on the EA page (email / Steam / 2FA as usual)."
echo " 6. EA redirects to 127.0.0.1:<port>/?code=... — Kyber's loopback"
echo " server catches the auth code and login completes."
echo " The browser tab will show 'OK' or connection-refused — both normal."
echo "" echo ""
echo "Streaming from a headless box:" echo "First run (one-time):"
echo " Use Steam Remote Play — install the Steam Link app on your client," echo " 1. Click 'EA Account' and log in with your EA credentials."
echo " it discovers this host over your network." echo " The browser opens to accounts.ea.com — log in there."
echo " 2. Click 'Skip' on the Nexus Mods step (only needed for mods)."
echo " 3. EA login is cached — you stay logged in across sessions."
echo "" echo ""
echo "Troubleshooting:" echo "If Kyber says 'Game Not Found':"
echo " Browser never opens after clicking Login:" echo " Click SET GAME FOLDER and run this to find the path:"
echo " Check watcher is running: systemctl --user status kyber-oauth-watcher" echo " find ~/.steam/steam/steamapps -name 'starwarsbattlefrontii.exe' 2>/dev/null | head -1 | xargs dirname"
echo " If not: $HOME/.local/bin/kyber-oauth-watcher.sh &" echo " Paste that path into the SET GAME FOLDER dialog. Kyber remembers it."
echo "" echo ""
echo " Browser opens but login loops back to EA sign-in page:" echo "Hosting a private server with bots:"
echo " Kyber's loopback server timed out. Close Kyber, reopen it," echo " HOST → pick maps/modes → set a name and PASSWORD → Start Server."
echo " then click Login and log in quickly." echo " Share the server name + password with friends (they search by name in HOME)."
echo " Bot count: HOST panel → AUTOPLAYERS → set BOTS TEAM 1 and BOTS TEAM 2"
echo " (e.g. 4 each) → click UPDATE SERVER."
echo " Bot difficulty: use the BOT DIFFICULTY slider (RECRUIT / OFFICER / KNIGHT / MASTER)."
echo "" echo ""
echo " 'cmd shim' missing (mingw-w64 was not installed):" if [ "$USERNS_OK" = "false" ]; then
echo " sudo apt install mingw-w64 && $0" echo "IMPORTANT: Fix unprivileged user namespaces before launching Kyber:"
echo " sudo sysctl -w kernel.unprivileged_userns_clone=1"
echo " sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=0"
echo " Or re-run this script with sudo to apply automatically."
echo ""
fi
echo "SWBF2 must be installed via Steam (AppID 1237950)."
echo "To update Kyber later, re-run this script."