Files
ubuntu-post-install/scripts/setup-kyber-linux.sh
T
Claude 303a4b4967 fix(kyber): use IFEO registry hook instead of system32 cmd.exe replacement
Proton's prefix-setup phase overwrites system32/cmd.exe on every game
launch, breaking the previous approach of placing a shim there. Switch to
Windows Image File Execution Options (IFEO): the shim is compiled to
drive_c/shim/kyber_cmd.exe (Proton never touches that directory) and the
IFEO Debugger key is written to system.reg so Wine intercepts cmd.exe at
CreateProcess time without touching the filesystem binary.

Also fix the wine64 path used for winetricks: correct path is
files/lib/wine/x86_64-unix/wine64 not files/bin/wine64.

The shim is simplified to scan all args for any http URL (IFEO passes the
debuggee path as an extra arg, so the /c start pattern match was fragile).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D8ckUJQtj1pH8jtAddBDZs
2026-06-24 00:52:42 +00:00

578 lines
24 KiB
Bash
Executable File

#!/bin/bash
# setup-kyber-linux.sh — Install the Kyber Launcher for SWBF2 (2017) on a
# native Linux Steam machine (headless or desktop) with Proton Experimental.
#
# 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).
#
# ── How Kyber's EA login actually works ────────────────────────────────────
#
# Kyber uses the "Maxima" OAuth PKCE flow:
# 1. Kyber starts a temporary HTTP server on 127.0.0.1 (dynamic port ~41413+)
# 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)
# when called as cmd /c start "" "<URL>". This blocks the login entirely.
#
# Fix: Windows Image File Execution Options (IFEO) lets us intercept any
# cmd.exe launch at the registry level without touching system32/cmd.exe
# (which Proton resets on every launch). We register a tiny shim that
# 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 ─────────────────────
#
# Wolf runs Docker + Proton's bwrap nested. The double-sandbox blocks
# CLONE_NEWUSER which breaks WebView2, AND the inner bwrap prevents
# xdg-open from reaching the host display. Kyber's loopback redirect to
# 127.0.0.1:PORT also fails inside the container network stack. Use native
# Linux Steam instead — it's the supported path.
#
# ── WebView2 ───────────────────────────────────────────────────────────────
#
# WebView2 is NOT required for the login flow (Maxima handles that). This
# script still installs the WebView2 Evergreen runtime because Kyber may use
# it for in-app content rendering. Installing it causes no harm and prevents
# any fallback-related error dialogs inside Kyber.
#
# ── Headless note ──────────────────────────────────────────────────────────
#
# On a headless GPU box use Steam Remote Play: install Steam, configure a
# virtual/dummy display so Steam has something to render to, add Kyber +
# SWBF2, then connect with the Steam Link app from any device.
# This script warns if no display is detected at setup time.
#
# ── Prerequisites ──────────────────────────────────────────────────────────
# a. Steam installed and launched at least once (native, not Flatpak ideally
# — Flatpak works but paths differ; this script handles both).
# b. SWBF2 (AppID 1237950) installed and working with Proton Experimental
# (run setup-swbf2-linux.sh first).
# c. KyberLauncher.exe downloaded from https://kyber.gg saved to
# ~/Downloads/KyberLauncher.exe (or pass the path as $1).
#
# ── Usage ──────────────────────────────────────────────────────────────────
# chmod +x setup-kyber-linux.sh
# ./setup-kyber-linux.sh [/path/to/KyberLauncher.exe]
set -euo pipefail
KYBER_INSTALLER="${1:-$HOME/Downloads/KyberLauncher.exe}"
KYBER_APPID="9900000001" # non-Steam shortcut appid
KYBER_COMPAT_ID="$KYBER_APPID" # prefix dir must match appid for Steam to find it
echo "=== Kyber Launcher — Native Linux Steam Setup ==="
echo ""
# ── Locate Steam home ──────────────────────────────────────────────────────
find_steam_home() {
for candidate in \
"$HOME/.steam/steam" \
"$HOME/.local/share/Steam" \
"$HOME/.var/app/com.valvesoftware.Steam/.steam/steam"; do
if [ -d "$candidate/steamapps" ]; then
echo "$candidate"
return 0
fi
done
return 1
}
STEAM_HOME=$(find_steam_home) || {
echo "ERROR: Steam home not found. Install Steam and launch it once."
exit 1
}
echo "Steam home: $STEAM_HOME"
# ── Verify / download Kyber installer ─────────────────────────────────────
if [ ! -f "$KYBER_INSTALLER" ]; then
echo ""
echo "Kyber installer not found at: $KYBER_INSTALLER"
echo "Downloading from kyber.gg API..."
mkdir -p "$(dirname "$KYBER_INSTALLER")"
KYBER_ZIP="/tmp/kyber-installer-$$.zip"
KYBER_DL_URL="https://api.prod.kyber.gg/download/kyber-installer-win64.zip"
if curl -L --progress-bar -o "$KYBER_ZIP" "$KYBER_DL_URL" && [ -s "$KYBER_ZIP" ]; then
# Extract the installer .exe from the zip
_exe=$(unzip -Z1 "$KYBER_ZIP" 2>/dev/null | grep -i '\.exe$' | head -1)
if [ -z "$_exe" ]; then
echo "ERROR: No .exe found inside the downloaded zip."
rm -f "$KYBER_ZIP"
exit 1
fi
unzip -p "$KYBER_ZIP" "$_exe" > "$KYBER_INSTALLER"
rm -f "$KYBER_ZIP"
echo "Extracted: $(basename "$_exe")$KYBER_INSTALLER"
else
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 ─────────────────────────────────────────────
# Kyber's WebView2 is best supported on Proton Experimental (newest Wine +
# 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 ─────────────────────────────────────────────────
# 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 ──────────────────────────
echo ""
echo "[1/7] Installing Kyber into Wine prefix..."
KYBER_PFX="$STEAM_HOME/steamapps/compatdata/$KYBER_COMPAT_ID"
OLD_PFX="$STEAM_HOME/steamapps/compatdata/kyber"
# 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 "[7/7] Done."
echo ""
echo "=== Kyber Setup Complete (native Linux) ==="
echo ""
echo "Next steps:"
echo " 1. RESTART Steam so it picks up the new shortcut and compat mapping."
echo " 2. The OAuth watcher is already running in the background."
echo " (Confirm: systemctl --user status kyber-oauth-watcher)"
echo " 3. Launch 'Kyber Launcher' from your Steam Library."
echo " 4. Click Login. Kyber calls cmd /c start with the EA auth URL."
echo " The cmd shim intercepts it and writes it to a file. The watcher"
echo " picks it up and opens it in your browser via xdg-open."
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 "Streaming from a headless box:"
echo " Use Steam Remote Play — install the Steam Link app on your client,"
echo " it discovers this host over your network."
echo ""
echo "Troubleshooting:"
echo " Browser never opens after clicking Login:"
echo " Check watcher is running: systemctl --user status kyber-oauth-watcher"
echo " If not: $HOME/.local/bin/kyber-oauth-watcher.sh &"
echo ""
echo " Browser opens but login loops back to EA sign-in page:"
echo " Kyber's loopback server timed out. Close Kyber, reopen it,"
echo " then click Login and log in quickly."
echo ""
echo " 'cmd shim' missing (mingw-w64 was not installed):"
echo " sudo apt install mingw-w64 && $0"