modified: README.md

new file:   battlenet-diagnose.sh
	modified:   battlenet-umu-setup.sh
	new file:   battlenet-update-proton.sh
This commit is contained in:
funman300
2026-04-16 12:59:47 -07:00
parent 28b1f0f824
commit 6f36e503fd
4 changed files with 877 additions and 58 deletions
+197 -12
View File
@@ -9,7 +9,7 @@ A guide for installing the Battle.net launcher on Arch Linux (2026).
## TL;DR — Which method should I use? ## TL;DR — Which method should I use?
| Method | Best for | Effort | | Method | Best for | Effort |
|---|---|---| | ---------------------------------------------- | ------------------------------------------------------ | ---------- |
| **umu-launcher (standalone Proton, no Steam)** | Scripting, minimal installs, homelab. **Recommended.** | Low | | **umu-launcher (standalone Proton, no Steam)** | Scripting, minimal installs, homelab. **Recommended.** | Low |
| **Steam + Proton-GE (non-Steam shortcut)** | People who already run Steam. | Low | | **Steam + Proton-GE (non-Steam shortcut)** | People who already run Steam. | Low |
| **Lutris (official installer script)** | People who want a GUI game manager. | LowMedium | | **Lutris (official installer script)** | People who want a GUI game manager. | LowMedium |
@@ -20,6 +20,43 @@ If you just want it to work: use **Method 1 (umu)**. It gives you Proton-grade c
--- ---
## Quick start — automated script
The fastest path is the included setup script, which handles everything in Method 1 automatically:
```bash
# Download and run
curl -LO https://raw.githubusercontent.com/your-repo/battlenet-umu-setup.sh
chmod +x battlenet-umu-setup.sh
./battlenet-umu-setup.sh
```
Or if you already have this repo cloned:
```bash
./battlenet-umu-setup.sh
```
The script is idempotent — safe to re-run. Common flags:
```bash
./battlenet-umu-setup.sh --yes # non-interactive / CI
./battlenet-umu-setup.sh --reinstall # wipe prefix and start over
./battlenet-umu-setup.sh --proton-version=GE-Proton9-20 # pin a specific Proton version
./battlenet-umu-setup.sh --gpu=nvidia # override GPU auto-detection
./battlenet-umu-setup.sh --prefix-dir=~/Games/bnet # custom prefix location
./battlenet-umu-setup.sh --skip-packages # skip the package check
```
Companion scripts also included:
| Script | Purpose |
| ----------------------------- | ------------------------------------------------------------- |
| `battlenet-update-proton.sh` | Download a new GE-Proton release and update the launch script |
| `battlenet-diagnose.sh` | Check setup health and report anything broken |
---
## Prerequisites ## Prerequisites
Enable `[multilib]` in `/etc/pacman.conf` if you haven't already. Uncomment these two lines: Enable `[multilib]` in `/etc/pacman.conf` if you haven't already. Uncomment these two lines:
@@ -66,7 +103,17 @@ sudo pacman -S --needed \
cups samba cups samba
``` ```
For an NVIDIA GPU, also install `lib32-nvidia-utils`. For AMD/Intel, `lib32-mesa` and `lib32-vulkan-radeon` or `lib32-vulkan-intel`. ### GPU-specific packages
Install the right set for your GPU — getting this wrong is the most common reason a successful install fails to render:
| GPU | Extra packages to install |
| ------------ | ---------------------------------------------------------------- |
| **NVIDIA** | `lib32-nvidia-utils nvidia-utils` |
| **AMD** | `lib32-mesa lib32-vulkan-radeon vulkan-radeon mesa` |
| **Intel** | `lib32-mesa lib32-vulkan-intel vulkan-intel mesa` |
The `battlenet-umu-setup.sh` script auto-detects your GPU and installs the correct set. Override with `--gpu=nvidia|amd|intel` if detection is wrong.
--- ---
@@ -85,13 +132,18 @@ For an NVIDIA GPU, also install `lib32-nvidia-utils`. For AMD/Intel, `lib32-mesa
[`umu-launcher`](https://github.com/Open-Wine-Components/umu-launcher) is the Open Wine Components project (from the GE-Proton maintainers) that makes Proton usable outside Steam. It manages Proton versions, runs games inside the Steam Runtime container, and automatically applies **protonfixes** — including the upstream fix for Battle.net that handles the `WINE_SIMULATE_WRITECOPY=1` workaround for you. [`umu-launcher`](https://github.com/Open-Wine-Components/umu-launcher) is the Open Wine Components project (from the GE-Proton maintainers) that makes Proton usable outside Steam. It manages Proton versions, runs games inside the Steam Runtime container, and automatically applies **protonfixes** — including the upstream fix for Battle.net that handles the `WINE_SIMULATE_WRITECOPY=1` workaround for you.
### 1. Install umu-launcher ### 1. Install prerequisites and umu-launcher
Install the prerequisite packages from [Prerequisites](#prerequisites) above, then:
```bash ```bash
sudo pacman -S umu-launcher sudo pacman -S umu-launcher
``` ```
Alternatively, `battlenet-umu-setup.sh` handles both in one step.
Verify: Verify:
```bash ```bash
umu-run --help umu-run --help
``` ```
@@ -114,6 +166,7 @@ umu-run ~/Downloads/Battle.net-Setup.exe
``` ```
umu will: umu will:
- Download the latest GE-Proton into `~/.local/share/Steam/compatibilitytools.d/` if it isn't there. - Download the latest GE-Proton into `~/.local/share/Steam/compatibilitytools.d/` if it isn't there.
- Create the prefix at `~/Games/battlenet-umu`. - Create the prefix at `~/Games/battlenet-umu`.
- Look up `umu-battlenet` in the protonfixes database and pre-apply the needed env vars and tweaks. - Look up `umu-battlenet` in the protonfixes database and pre-apply the needed env vars and tweaks.
@@ -123,6 +176,7 @@ Complete the installer at the default path.
### 4. Create a launch script ### 4. Create a launch script
`~/.local/bin/battlenet`: `~/.local/bin/battlenet`:
```bash ```bash
#!/bin/sh #!/bin/sh
export WINEPREFIX="$HOME/Games/battlenet-umu" export WINEPREFIX="$HOME/Games/battlenet-umu"
@@ -135,18 +189,20 @@ exec umu-run "$WINEPREFIX/drive_c/Program Files (x86)/Battle.net/Battle.net Laun
chmod +x ~/.local/bin/battlenet chmod +x ~/.local/bin/battlenet
``` ```
Make sure `~/.local/bin` is on your `PATH` (it is by default on most Arch shells). Now `battlenet` in any terminal starts the launcher. Make sure `~/.local/bin` is on your `PATH`. See [Adding ~/.local/bin to PATH](#adding-localbin-to-path) below if it isn't. Once it is, `battlenet` in any terminal starts the launcher.
### 5. (Optional) Desktop entry ### 5. (Optional) Desktop entry
`~/.local/share/applications/battlenet.desktop`: `~/.local/share/applications/battlenet.desktop`:
```ini ```ini
[Desktop Entry] [Desktop Entry]
Name=Battle.net Name=Battle.net
Exec=/home/YOUR_USERNAME/.local/bin/battlenet Exec=$HOME/.local/bin/battlenet
Icon=battlenet Icon=battlenet
Type=Application Type=Application
Categories=Game; Categories=Game;
StartupWMClass=battle.net.exe
``` ```
### 6. (Optional) Pin a specific Proton version ### 6. (Optional) Pin a specific Proton version
@@ -157,7 +213,7 @@ If a GE-Proton release ever regresses Battle.net, pin a known-good one instead o
export PROTONPATH="GE-Proton9-20" export PROTONPATH="GE-Proton9-20"
``` ```
It needs to exist in `~/.local/share/Steam/compatibilitytools.d/`. It needs to exist in `~/.local/share/Steam/compatibilitytools.d/`. With the setup script, use `--proton-version=GE-Proton9-20`.
--- ---
@@ -175,7 +231,7 @@ yay -S protonup-qt # or use paru, or grab the Flatpak
Log into Steam at least once. Open ProtonUp-Qt, point it at your Steam install, and install the latest **GE-Proton**. Restart Steam. Log into Steam at least once. Open ProtonUp-Qt, point it at your Steam install, and install the latest **GE-Proton**. Restart Steam.
### 2. Download the Battle.net installer ### 2. Get the Battle.net installer
Get `Battle.net-Setup.exe` from <https://download.battle.net/?product=bnetdesk>. Get `Battle.net-Setup.exe` from <https://download.battle.net/?product=bnetdesk>.
@@ -184,9 +240,11 @@ Get `Battle.net-Setup.exe` from <https://download.battle.net/?product=bnetdesk>.
1. In Steam: **Games → Add a Non-Steam Game to My Library → Browse** and select `Battle.net-Setup.exe`. 1. In Steam: **Games → Add a Non-Steam Game to My Library → Browse** and select `Battle.net-Setup.exe`.
2. Right-click the new entry → **Properties → Compatibility** → tick **Force the use of a specific Steam Play compatibility tool** → select the latest **GE-Proton**. 2. Right-click the new entry → **Properties → Compatibility** → tick **Force the use of a specific Steam Play compatibility tool** → select the latest **GE-Proton**.
3. Under **Launch Options**, paste: 3. Under **Launch Options**, paste:
```
```text
WINE_SIMULATE_WRITECOPY=1 %command% WINE_SIMULATE_WRITECOPY=1 %command%
``` ```
This works around a CEF/login-screen issue that Blizzard periodically reintroduces with launcher updates. Cheap insurance. This works around a CEF/login-screen issue that Blizzard periodically reintroduces with launcher updates. Cheap insurance.
4. Launch it from Steam and complete the installer at the default path. 4. Launch it from Steam and complete the installer at the default path.
@@ -227,7 +285,7 @@ sudo pacman -S lutris
Right-click the Battle.net entry → **Configure → System options → Environment variables**: Right-click the Battle.net entry → **Configure → System options → Environment variables**:
| Key | Value | | Key | Value |
|---|---| | -------------------------- | ----- |
| `WINE_SIMULATE_WRITECOPY` | `1` | | `WINE_SIMULATE_WRITECOPY` | `1` |
--- ---
@@ -288,6 +346,7 @@ WINE_SIMULATE_WRITECOPY=1 \
### 4. Launch scripts ### 4. Launch scripts
`~/.local/bin/battlenet`: `~/.local/bin/battlenet`:
```bash ```bash
#!/bin/sh #!/bin/sh
export WINEPREFIX="$HOME/Games/battlenet-wine-prefix" export WINEPREFIX="$HOME/Games/battlenet-wine-prefix"
@@ -296,6 +355,7 @@ exec wine "$WINEPREFIX/drive_c/Program Files (x86)/Battle.net/Battle.net Launche
``` ```
`~/.local/bin/battlenetkill`: `~/.local/bin/battlenetkill`:
```bash ```bash
#!/bin/sh #!/bin/sh
export WINEPREFIX="$HOME/Games/battlenet-wine-prefix" export WINEPREFIX="$HOME/Games/battlenet-wine-prefix"
@@ -308,6 +368,55 @@ chmod +x ~/.local/bin/battlenet ~/.local/bin/battlenetkill
--- ---
## Adding ~/.local/bin to PATH
Most Arch shell setups include `~/.local/bin` automatically, but if yours doesn't:
**bash** — add to `~/.bashrc` or `~/.bash_profile`:
```bash
export PATH="$HOME/.local/bin:$PATH"
```
**zsh** — add to `~/.zshrc`:
```zsh
export PATH="$HOME/.local/bin:$PATH"
```
**fish** — run once; it persists across sessions:
```fish
fish_add_path $HOME/.local/bin
```
---
## Game-specific notes
### World of Warcraft
Runs well out of the box on GE-Proton. A few tuning points:
- **D3D12 vs D3D11:** WoW defaults to D3D12 on recent clients. If you see crashes at launch, switch to D3D11 in the WoW graphics settings or add `PROTON_USE_WINED3D=0` and try D3D11 explicitly.
- **High CPU usage at login screen:** Known issue with some GE-Proton builds. Pinning a slightly older release (e.g. `GE-Proton9-15`) often resolves it until the next fix lands.
### Overwatch 2
- Uses the **BattlEye** anti-cheat. BattlEye has official Linux support via the runtime bundled with Proton-GE; you do **not** need to disable it.
- Add `PROTON_ENABLE_NVAPI=1` if you have NVIDIA and see the game refuse to launch with a GPU compatibility error.
### Diablo IV
- Runs well on GE-Proton. Uses **Warden** (no dedicated Linux anti-cheat binary) — no special config needed.
- If you see corruption in cut-scenes, try `DXVK_ASYNC=1` in your launch script (async shader compilation).
### Hearthstone
Historically the most problematic title on Wine. With umu + protonfixes it works, but the login screen is particularly sensitive to `WINE_SIMULATE_WRITECOPY=1`. Make sure it's set (umu sets it automatically via protonfixes).
---
## Troubleshooting ## Troubleshooting
### "Battle.net Update Agent went to sleep. Attempting to wake it up… BLZBNTBNA00000005" ### "Battle.net Update Agent went to sleep. Attempting to wake it up… BLZBNTBNA00000005"
@@ -316,11 +425,13 @@ The most common 20252026 failure mode. In order:
1. Confirm `WINE_SIMULATE_WRITECOPY=1` is set (or that you're using umu/Lutris-via-umu, which sets it). 1. Confirm `WINE_SIMULATE_WRITECOPY=1` is set (or that you're using umu/Lutris-via-umu, which sets it).
2. Kill everything and wipe the agent cache: 2. Kill everything and wipe the agent cache:
```bash ```bash
pkill -9 Battle.net; pkill -9 Agent; pkill -9 Blizzard pkill -9 Battle.net; pkill -9 Agent; pkill -9 Blizzard
rm -rf "$WINEPREFIX/drive_c/ProgramData/Battle.net/Agent" rm -rf "$WINEPREFIX/drive_c/ProgramData/Battle.net/Agent"
rm -rf "$WINEPREFIX/drive_c/ProgramData/Blizzard Entertainment" rm -rf "$WINEPREFIX/drive_c/ProgramData/Blizzard Entertainment"
``` ```
Restart the launcher; you'll have to log in again. Restart the launcher; you'll have to log in again.
3. Update GE-Proton. Fixes for Battle.net regressions usually land in a new GE-Proton release within days. 3. Update GE-Proton. Fixes for Battle.net regressions usually land in a new GE-Proton release within days.
@@ -330,13 +441,33 @@ The most common 20252026 failure mode. In order:
### Blank window / broken input under Wayland ### Blank window / broken input under Wayland
Wine's native Wayland driver is still rough for the Battle.net launcher in 2026. Either: Wine's native Wayland driver is still rough for the Battle.net launcher in 2026. The fix is to force XWayland explicitly:
- Log into an X11 session (SDDM/GDM login screen → session picker), or
- Make sure you're going through XWayland — Steam/Proton does this automatically. ```bash
DISPLAY=:0 SDL_VIDEODRIVER=x11 battlenet
```
Or add the overrides permanently to your launch script (`~/.local/bin/battlenet`):
```sh
export DISPLAY="${DISPLAY:-:0}"
export SDL_VIDEODRIVER=x11
```
If `DISPLAY=:0` doesn't work, find the correct display number:
```bash
ls /tmp/.X11-unix/
```
Each `X<N>` file corresponds to `DISPLAY=:<N>`. Use whichever exists.
As a fallback, log into a plain **X11 session** from your display manager's session picker (SDDM/GDM). Steam/Proton routes through XWayland automatically, which is why Method 2 works without this tweak.
### "X Error of failed request: BadWindow" or X11 root-access errors ### "X Error of failed request: BadWindow" or X11 root-access errors
Caused by running Wine as root (what the old guide told you to do). **Don't.** If you're stuck with a root-owned prefix, remove it and start over as your normal user: Caused by running Wine as root (what the old guide told you to do). **Don't.** If you're stuck with a root-owned prefix, remove it and start over as your normal user:
```bash ```bash
sudo rm -rf /home/<user>/Games/battlenet-wine-prefix sudo rm -rf /home/<user>/Games/battlenet-wine-prefix
``` ```
@@ -344,12 +475,57 @@ sudo rm -rf /home/<user>/Games/battlenet-wine-prefix
### Something else is broken ### Something else is broken
Delete the prefix and start over — this really does fix most weird states. Delete the prefix and start over — this really does fix most weird states.
```bash ```bash
rm -rf "$HOME/Games/battlenet-umu" # or battlenet-wine-prefix rm -rf "$HOME/Games/battlenet-umu" # or battlenet-wine-prefix
``` ```
--- ---
## systemd user service (optional)
If you want to start and stop Battle.net with `systemctl` rather than a terminal command, create a user service unit:
`~/.config/systemd/user/battlenet.service`:
```ini
[Unit]
Description=Battle.net launcher (umu/Proton)
After=graphical-session.target
[Service]
Type=simple
Environment=DISPLAY=:0
ExecStart=%h/.local/bin/battlenet
ExecStop=%h/.local/bin/battlenetkill
Restart=no
[Install]
WantedBy=default.target
```
Enable and control it:
```bash
systemctl --user daemon-reload
# Start
systemctl --user start battlenet
# Stop
systemctl --user stop battlenet
# Start automatically at login
systemctl --user enable battlenet
# View logs
journalctl --user -u battlenet -f
```
> **Note:** The `DISPLAY=:0` line assumes a single-user X11 or XWayland session. If your display number differs (check `ls /tmp/.X11-unix/`), update it. Under a proper XDG session you can also use `%I` socket activation, but for a game launcher `Type=simple` with a fixed display is simpler and more reliable.
---
## A note on bans ## A note on bans
Blizzard does not ban for running via Wine/Proton/Linux. Their Warden anti-cheat targets cheat software inside the game process, not the compatibility layer. This has been confirmed by Blizzard support as recently as 2025. Blizzard does not ban for running via Wine/Proton/Linux. Their Warden anti-cheat targets cheat software inside the game process, not the compatibility layer. This has been confirmed by Blizzard support as recently as 2025.
@@ -370,3 +546,12 @@ Blizzard does not ban for running via Wine/Proton/Linux. Their Warden anti-cheat
- Replaced `/usr/bin/` install of user launch scripts with `~/.local/bin/`. - Replaced `/usr/bin/` install of user launch scripts with `~/.local/bin/`.
- Set default Wine Windows version to Windows 10 rather than Windows 7. - Set default Wine Windows version to Windows 10 rather than Windows 7.
- Added Wayland/XWayland notes and the Agent-cache wipe fix for `BLZBNTBNA00000005`. - Added Wayland/XWayland notes and the Agent-cache wipe fix for `BLZBNTBNA00000005`.
- Added GPU-specific package table to Prerequisites.
- Added shell-specific PATH setup instructions (bash/zsh/fish).
- Fixed desktop entry to use `$HOME` instead of hardcoded username.
- Added game-specific notes for WoW, Overwatch 2, Diablo IV, and Hearthstone.
- Added Quick start section with script flags and companion script table.
- Added explicit XWayland fix (`DISPLAY=:0 SDL_VIDEODRIVER=x11`) to Wayland troubleshooting.
- Added systemd user service unit for start/stop via `systemctl`.
- Added `battlenet-update-proton.sh` — GE-Proton version manager.
- Added `battlenet-diagnose.sh` — setup health checker.
+268
View File
@@ -0,0 +1,268 @@
#!/usr/bin/env bash
#
# battlenet-diagnose.sh
#
# Checks the health of your Battle.net / umu-launcher setup and reports
# anything that looks wrong. Safe to run at any time — read-only, no changes.
#
# Usage:
# ./battlenet-diagnose.sh
# ./battlenet-diagnose.sh --prefix-dir=PATH # check a non-default prefix
set -uo pipefail
# ---------- config ----------
PREFIX_DIR="${BATTLENET_PREFIX:-$HOME/Games/battlenet-umu}"
LAUNCH_SCRIPT="${HOME}/.local/bin/battlenet"
PROTON_COMPAT_DIR="${HOME}/.local/share/Steam/compatibilitytools.d"
LAUNCHER_EXE_REL="drive_c/Program Files (x86)/Battle.net/Battle.net Launcher.exe"
for arg in "$@"; do
case "$arg" in
--prefix-dir=*) PREFIX_DIR="${arg#*=}" ;;
-h|--help) grep '^#' "$0" | grep -v '^#!/' | sed 's/^# \{0,1\}//'; exit 0 ;;
*) echo "Unknown argument: $arg" >&2; exit 1 ;;
esac
done
# ---------- helpers ----------
c_reset=$'\033[0m'
c_green=$'\033[1;32m'; c_yellow=$'\033[1;33m'; c_red=$'\033[1;31m'
c_blue=$'\033[1;34m'; c_dim=$'\033[2m'; c_bold=$'\033[1m'
pass() { printf ' %s✓%s %s\n' "$c_green" "$c_reset" "$*"; }
fail() { printf ' %s✗%s %s\n' "$c_red" "$c_reset" "$*"; ISSUES=$(( ISSUES + 1 )); }
warn() { printf ' %s!%s %s\n' "$c_yellow" "$c_reset" "$*"; WARNINGS=$(( WARNINGS + 1 )); }
info() { printf ' %s·%s %s\n' "$c_dim" "$c_reset" "$*"; }
section() { printf '\n%s%s%s\n' "$c_bold$c_blue" "$*" "$c_reset"; }
ISSUES=0
WARNINGS=0
# ---------- system ----------
section "System"
info "Kernel: $(uname -r)"
info "Date: $(date)"
if [[ $EUID -eq 0 ]]; then
fail "Running as root — Wine/Proton must run as a regular user"
else
pass "Not running as root"
fi
if pacman -Sl multilib >/dev/null 2>&1; then
pass "[multilib] enabled"
else
fail "[multilib] not enabled — required for 32-bit libraries"
fi
# ---------- GPU & drivers ----------
section "GPU & Drivers"
GPU_LINE=""
if command -v lspci >/dev/null 2>&1; then
GPU_LINE=$(lspci | grep -i "VGA\|3D\|Display" | head -1 || true)
if [[ -n "$GPU_LINE" ]]; then
info "GPU: $GPU_LINE"
else
warn "No VGA/3D/Display device found via lspci"
fi
else
warn "lspci not found (install pciutils) — GPU detection skipped"
fi
# Vulkan
if command -v vulkaninfo >/dev/null 2>&1; then
if vulkaninfo 2>/dev/null | grep -q "apiVersion"; then
VK_VER=$(vulkaninfo 2>/dev/null | grep "apiVersion" | head -1 | sed 's/.*= *//')
pass "Vulkan available (apiVersion: $VK_VER)"
else
fail "vulkaninfo ran but reported no Vulkan devices"
fi
else
warn "vulkaninfo not found — install vulkan-tools to verify Vulkan support"
fi
# 32-bit Vulkan ICD loader
if pacman -Qi lib32-vulkan-icd-loader >/dev/null 2>&1; then
pass "lib32-vulkan-icd-loader installed"
else
fail "lib32-vulkan-icd-loader missing — install with: sudo pacman -S lib32-vulkan-icd-loader"
fi
# ---------- umu-launcher ----------
section "umu-launcher"
if command -v umu-run >/dev/null 2>&1; then
UMU_PATH=$(command -v umu-run)
UMU_VER=$(umu-run --version 2>/dev/null | head -1 || echo "unknown")
pass "umu-run found: $UMU_PATH ($UMU_VER)"
else
fail "umu-run not on PATH — install with: sudo pacman -S umu-launcher"
fi
# ---------- GE-Proton ----------
section "GE-Proton"
if [[ -d "$PROTON_COMPAT_DIR" ]]; then
mapfile -t PROTON_VERSIONS < <(find "$PROTON_COMPAT_DIR" -maxdepth 1 -type d -name "GE-Proton*" | sort -rV)
if [[ ${#PROTON_VERSIONS[@]} -gt 0 ]]; then
pass "${#PROTON_VERSIONS[@]} GE-Proton version(s) installed:"
for v in "${PROTON_VERSIONS[@]}"; do
info " $(basename "$v")"
done
else
warn "No GE-Proton versions found in $PROTON_COMPAT_DIR"
warn "umu-run will download GE-Proton automatically on first launch"
fi
else
warn "Proton compat dir not found: $PROTON_COMPAT_DIR"
warn "It will be created when umu-run downloads GE-Proton"
fi
# Check what the launch script is pinned to
if [[ -f "$LAUNCH_SCRIPT" ]]; then
PINNED=$(grep '^export PROTONPATH=' "$LAUNCH_SCRIPT" 2>/dev/null | head -1 \
| sed 's/.*="\?\([^"]*\)"\?.*/\1/' || echo "")
if [[ -n "$PINNED" ]]; then
info "Launch script pinned to: $PINNED"
if [[ "$PINNED" == "GE-Proton" ]]; then
info "(tracking latest — will auto-download if not present)"
elif [[ -d "$PROTON_COMPAT_DIR/$PINNED" ]]; then
pass "Pinned version is installed"
else
fail "Pinned version '$PINNED' not found in $PROTON_COMPAT_DIR"
fail "Run: ./battlenet-update-proton.sh --version=$PINNED"
fi
fi
fi
# ---------- prefix ----------
section "Wine Prefix ($PREFIX_DIR)"
if [[ -d "$PREFIX_DIR" ]]; then
pass "Prefix directory exists"
LAUNCHER_PATH="$PREFIX_DIR/$LAUNCHER_EXE_REL"
if [[ -f "$LAUNCHER_PATH" ]]; then
pass "Battle.net Launcher.exe present"
else
fail "Battle.net Launcher.exe NOT found at expected path"
fail " Expected: $LAUNCHER_PATH"
fail "Re-run battlenet-umu-setup.sh (or use --reinstall)"
fi
# Check for common stuck-agent cache
AGENT_LOCK="$PREFIX_DIR/drive_c/ProgramData/Battle.net/Agent/agent.lock"
if [[ -f "$AGENT_LOCK" ]]; then
LOCK_AGE=$(( $(date +%s) - $(stat -c%Y "$AGENT_LOCK") ))
if (( LOCK_AGE > 300 )); then
warn "Stale agent.lock found (${LOCK_AGE}s old) — may cause BLZBNTBNA00000005"
warn "Fix: battlenetkill && rm -f \"$AGENT_LOCK\""
else
info "agent.lock exists but is fresh (${LOCK_AGE}s) — launcher may be running"
fi
fi
# Check prefix ownership
if [[ -O "$PREFIX_DIR" ]]; then
pass "Prefix is owned by current user"
else
fail "Prefix is NOT owned by current user — Wine will misbehave"
fail "Fix: sudo chown -R $(whoami): \"$PREFIX_DIR\""
fi
else
fail "Prefix directory does not exist: $PREFIX_DIR"
fail "Run battlenet-umu-setup.sh to create it"
fi
# ---------- launch scripts ----------
section "Launch Scripts"
if [[ -f "$LAUNCH_SCRIPT" ]]; then
pass "Launch script exists: $LAUNCH_SCRIPT"
if [[ -x "$LAUNCH_SCRIPT" ]]; then
pass "Launch script is executable"
else
fail "Launch script is NOT executable — run: chmod +x $LAUNCH_SCRIPT"
fi
# Verify env vars are present
for var in WINEPREFIX GAMEID PROTONPATH; do
if grep -q "^export $var=" "$LAUNCH_SCRIPT"; then
VAL=$(grep "^export $var=" "$LAUNCH_SCRIPT" | head -1 | sed 's/.*="\?\([^"]*\)"\?.*/\1/')
info "$var=$VAL"
else
fail "Launch script missing: export $var=..."
fi
done
else
fail "Launch script not found: $LAUNCH_SCRIPT"
fail "Run battlenet-umu-setup.sh to create it"
fi
KILL_SCRIPT="$HOME/.local/bin/battlenetkill"
if [[ -f "$KILL_SCRIPT" && -x "$KILL_SCRIPT" ]]; then
pass "Kill script exists and is executable: $KILL_SCRIPT"
else
warn "Kill script missing or not executable: $KILL_SCRIPT"
fi
# ---------- display / Wayland ----------
section "Display"
if [[ -n "${WAYLAND_DISPLAY:-}" ]]; then
info "Wayland session detected (WAYLAND_DISPLAY=$WAYLAND_DISPLAY)"
if [[ -n "${DISPLAY:-}" ]]; then
pass "XWayland is available (DISPLAY=$DISPLAY)"
info "Battle.net will run via XWayland — this is correct"
else
fail "No DISPLAY set — XWayland does not appear to be running"
fail "Battle.net requires XWayland under Wayland"
fail "Make sure your compositor starts XWayland (most do by default)"
fi
elif [[ -n "${DISPLAY:-}" ]]; then
pass "X11 session (DISPLAY=$DISPLAY)"
else
warn "Neither DISPLAY nor WAYLAND_DISPLAY is set — running headless?"
fi
# ---------- running processes ----------
section "Running Processes"
BN_PROCS=$(pgrep -fl 'Battle\.net\|battle\.net\|bnet' 2>/dev/null || true)
AGENT_PROCS=$(pgrep -fl 'Agent\.exe' 2>/dev/null || true)
WINE_PROCS=$(pgrep -fl 'wine\|wineserver\|proton' 2>/dev/null | grep -v 'grep' || true)
if [[ -n "$BN_PROCS" ]]; then
info "Battle.net process(es) running:"
while IFS= read -r line; do info " $line"; done <<< "$BN_PROCS"
else
info "No Battle.net processes running"
fi
if [[ -n "$AGENT_PROCS" ]]; then
info "Agent process(es) running:"
while IFS= read -r line; do info " $line"; done <<< "$AGENT_PROCS"
fi
if [[ -n "$WINE_PROCS" ]]; then
info "Wine/Proton process(es) running:"
while IFS= read -r line; do info " $line"; done <<< "$WINE_PROCS"
fi
# ---------- summary ----------
echo
printf '%s%s%s\n' "$c_bold$c_blue" "=== Summary ===" "$c_reset"
if [[ $ISSUES -eq 0 && $WARNINGS -eq 0 ]]; then
printf '%s✓ Everything looks good.%s\n' "$c_green" "$c_reset"
elif [[ $ISSUES -eq 0 ]]; then
printf '%s! %d warning(s). Battle.net should work, but check the items above.%s\n' \
"$c_yellow" "$WARNINGS" "$c_reset"
else
printf '%s✗ %d issue(s), %d warning(s). See items marked ✗ above.%s\n' \
"$c_red" "$ISSUES" "$WARNINGS" "$c_reset"
fi
echo
+223 -23
View File
@@ -9,6 +9,11 @@
# ./battlenet-umu-setup.sh # interactive # ./battlenet-umu-setup.sh # interactive
# ./battlenet-umu-setup.sh --yes # non-interactive, assume yes # ./battlenet-umu-setup.sh --yes # non-interactive, assume yes
# ./battlenet-umu-setup.sh --reinstall # wipe prefix and reinstall # ./battlenet-umu-setup.sh --reinstall # wipe prefix and reinstall
# ./battlenet-umu-setup.sh --proton-version=GE-Proton9-20 # pin a specific Proton version
# ./battlenet-umu-setup.sh --gpu=nvidia|amd|intel # override GPU detection
# ./battlenet-umu-setup.sh --prefix-dir=PATH # override prefix location
# ./battlenet-umu-setup.sh --skip-packages # skip prerequisite package check
# END_HELP
# #
# Does NOT run itself as root. It will call sudo only for pacman. # Does NOT run itself as root. It will call sudo only for pacman.
@@ -17,18 +22,24 @@ set -euo pipefail
# ---------- config ---------- # ---------- config ----------
PREFIX_DIR="${BATTLENET_PREFIX:-$HOME/Games/battlenet-umu}" PREFIX_DIR="${BATTLENET_PREFIX:-$HOME/Games/battlenet-umu}"
GAMEID="umu-battlenet" GAMEID="umu-battlenet"
PROTONPATH="${PROTONPATH:-GE-Proton}" PROTON_VERSION="GE-Proton" # default; overridden by --proton-version or PROTONPATH env var
INSTALLER_URL="https://downloader.battle.net/download/getInstaller?os=win&installer=Battle.net-Setup.exe" INSTALLER_URL="https://downloader.battle.net/download/getInstaller?os=win&installer=Battle.net-Setup.exe"
INSTALLER_PATH="$HOME/Downloads/Battle.net-Setup.exe" INSTALLER_PATH="$HOME/Downloads/Battle.net-Setup.exe"
LAUNCHER_EXE_REL="drive_c/Program Files (x86)/Battle.net/Battle.net Launcher.exe" LAUNCHER_EXE_REL="drive_c/Program Files (x86)/Battle.net/Battle.net Launcher.exe"
BIN_DIR="$HOME/.local/bin" BIN_DIR="$HOME/.local/bin"
DESKTOP_DIR="$HOME/.local/share/applications" DESKTOP_DIR="$HOME/.local/share/applications"
ICON_DIR="$HOME/.local/share/icons/hicolor/256x256/apps"
LAUNCH_SCRIPT="$BIN_DIR/battlenet" LAUNCH_SCRIPT="$BIN_DIR/battlenet"
KILL_SCRIPT="$BIN_DIR/battlenetkill" KILL_SCRIPT="$BIN_DIR/battlenetkill"
DESKTOP_FILE="$DESKTOP_DIR/battlenet.desktop" DESKTOP_FILE="$DESKTOP_DIR/battlenet.desktop"
ASSUME_YES=0 ASSUME_YES=0
REINSTALL=0 REINSTALL=0
SKIP_PACKAGES=0
GPU_TYPE="" # auto-detected; override with --gpu=nvidia|amd|intel
# Honour PROTONPATH env var as a default, but let --proton-version override it
[[ -n "${PROTONPATH:-}" ]] && PROTON_VERSION="$PROTONPATH"
# ---------- helpers ---------- # ---------- helpers ----------
c_reset=$'\033[0m'; c_blue=$'\033[1;34m'; c_green=$'\033[1;32m' c_reset=$'\033[0m'; c_blue=$'\033[1;34m'; c_green=$'\033[1;32m'
@@ -56,21 +67,30 @@ for arg in "$@"; do
case "$arg" in case "$arg" in
-y|--yes) ASSUME_YES=1 ;; -y|--yes) ASSUME_YES=1 ;;
--reinstall) REINSTALL=1 ;; --reinstall) REINSTALL=1 ;;
--skip-packages) SKIP_PACKAGES=1 ;;
--proton-version=*) PROTON_VERSION="${arg#*=}" ;;
--prefix-dir=*) PREFIX_DIR="${arg#*=}" ;;
--gpu=nvidia|--gpu=amd|--gpu=intel)
GPU_TYPE="${arg#*=}" ;;
-h|--help) -h|--help)
sed -n '2,12p' "$0" | sed 's/^# \{0,1\}//' sed -n '/^# Usage:/,/^# END_HELP/p' "$0" | sed 's/^# \{0,1\}//'
exit 0 exit 0
;; ;;
*) die "Unknown argument: $arg" ;; *) die "Unknown argument: $arg" ;;
esac esac
done done
# Expose PROTONPATH for umu-run
PROTONPATH="$PROTON_VERSION"
export PROTONPATH
# ---------- preflight ---------- # ---------- preflight ----------
log "Preflight checks" log "Preflight checks"
[[ $EUID -ne 0 ]] || die "Do not run this script as root. It will sudo when it needs to." [[ $EUID -ne 0 ]] || die "Do not run this script as root. It will sudo when it needs to."
command -v pacman >/dev/null || die "pacman not found — this script is for Arch Linux." command -v pacman >/dev/null || die "pacman not found — this script is for Arch Linux."
command -v curl >/dev/null || die "curl not installed. sudo pacman -S curl" command -v curl >/dev/null || die "curl not installed. Run: sudo pacman -S curl"
# multilib check # multilib check
if ! pacman -Sl multilib >/dev/null 2>&1; then if ! pacman -Sl multilib >/dev/null 2>&1; then
@@ -81,6 +101,81 @@ if ! pacman -Sl multilib >/dev/null 2>&1; then
fi fi
ok "[multilib] is enabled" ok "[multilib] is enabled"
# ---------- detect GPU ----------
log "Detecting GPU"
if [[ -z "$GPU_TYPE" ]]; then
if command -v lspci >/dev/null 2>&1; then
if lspci | grep -qi "NVIDIA"; then
GPU_TYPE="nvidia"
elif lspci | grep -qi "AMD\|Radeon"; then
GPU_TYPE="amd"
else
GPU_TYPE="intel"
fi
else
warn "lspci not found (install pciutils for auto-detection). Defaulting to intel."
warn "Override with --gpu=nvidia|amd|intel if that is wrong."
GPU_TYPE="intel"
fi
fi
ok "GPU type: $GPU_TYPE (override with --gpu=nvidia|amd|intel)"
# ---------- install prerequisites ----------
if [[ $SKIP_PACKAGES -eq 1 ]]; then
skip "Package check skipped (--skip-packages)"
else
log "Checking 32-bit prerequisite libraries"
COMMON_PKGS=(
giflib lib32-giflib
libpng lib32-libpng
libldap lib32-libldap
gnutls lib32-gnutls
mpg123 lib32-mpg123
openal lib32-openal
v4l-utils lib32-v4l-utils
libpulse lib32-libpulse
alsa-plugins lib32-alsa-plugins
alsa-lib lib32-alsa-lib
libjpeg-turbo lib32-libjpeg-turbo
sqlite lib32-sqlite
libxcomposite lib32-libxcomposite
libxinerama lib32-libxinerama
ncurses lib32-ncurses
opencl-icd-loader lib32-opencl-icd-loader
libxslt lib32-libxslt
libva lib32-libva
gtk3 lib32-gtk3
gst-plugins-base-libs lib32-gst-plugins-base-libs
vulkan-icd-loader lib32-vulkan-icd-loader
cups samba
)
GPU_PKGS=()
case "$GPU_TYPE" in
nvidia) GPU_PKGS=(lib32-nvidia-utils nvidia-utils) ;;
amd) GPU_PKGS=(lib32-mesa lib32-vulkan-radeon vulkan-radeon mesa) ;;
intel) GPU_PKGS=(lib32-mesa lib32-vulkan-intel vulkan-intel mesa) ;;
esac
ALL_PKGS=("${COMMON_PKGS[@]}" "${GPU_PKGS[@]}")
MISSING_PKGS=()
for pkg in "${ALL_PKGS[@]}"; do
pacman -Qi "$pkg" >/dev/null 2>&1 || MISSING_PKGS+=("$pkg")
done
if [[ ${#MISSING_PKGS[@]} -eq 0 ]]; then
skip "All prerequisite packages already installed"
else
warn "Missing packages: ${MISSING_PKGS[*]}"
confirm "Install missing prerequisite packages?" || die "Aborted."
sudo pacman -S --needed --noconfirm "${MISSING_PKGS[@]}"
ok "Prerequisite packages installed"
fi
fi
# ---------- install umu-launcher ---------- # ---------- install umu-launcher ----------
log "Installing umu-launcher" log "Installing umu-launcher"
@@ -98,14 +193,34 @@ command -v umu-run >/dev/null || die "umu-run not on PATH after install — some
log "Fetching Battle.net installer" log "Fetching Battle.net installer"
mkdir -p "$(dirname "$INSTALLER_PATH")" mkdir -p "$(dirname "$INSTALLER_PATH")"
_download_installer() {
curl -L --fail --progress-bar -o "$INSTALLER_PATH" "$INSTALLER_URL"
# Sanity check: Battle.net-Setup.exe is always several MB; <100 KB means a redirect page or error.
local size
size=$(stat -c%s "$INSTALLER_PATH" 2>/dev/null || echo 0)
if (( size < 102400 )); then
rm -f "$INSTALLER_PATH"
die "Downloaded file is suspiciously small (${size} bytes) — download may have failed."
fi
}
if [[ -f "$INSTALLER_PATH" ]]; then if [[ -f "$INSTALLER_PATH" ]]; then
skip "Installer already at $INSTALLER_PATH" local_size=$(stat -c%s "$INSTALLER_PATH" 2>/dev/null || echo 0)
if confirm "Re-download to get the latest?"; then if (( local_size < 102400 )); then
curl -L --fail -o "$INSTALLER_PATH" "$INSTALLER_URL" warn "Existing installer looks corrupt (${local_size} bytes). Re-downloading."
_download_installer
ok "Installer downloaded to $INSTALLER_PATH"
else
skip "Installer already at $INSTALLER_PATH ($(( local_size / 1024 / 1024 )) MB)"
# When running non-interactively (--yes), keep the existing file; only re-download on explicit prompt.
if [[ $ASSUME_YES -eq 0 ]] && confirm "Re-download to get the latest?"; then
_download_installer
ok "Installer re-downloaded" ok "Installer re-downloaded"
fi fi
fi
else else
curl -L --fail -o "$INSTALLER_PATH" "$INSTALLER_URL" _download_installer
ok "Installer saved to $INSTALLER_PATH" ok "Installer saved to $INSTALLER_PATH"
fi fi
@@ -146,9 +261,13 @@ else
warn "umu-run exited non-zero. That's often normal for the Battle.net installer." warn "umu-run exited non-zero. That's often normal for the Battle.net installer."
} }
# The installer sometimes drops the launcher even if it exits weirdly. # The installer sometimes exits before the launcher EXE is flushed to disk.
# Give it a moment, then check. # Poll for up to 30 s rather than assuming a fixed delay is enough.
sleep 2 i=0
while [[ ! -f "$LAUNCHER_PATH" && $i -lt 30 ]]; do
sleep 1
(( i++ )) || true
done
if [[ ! -f "$LAUNCHER_PATH" ]]; then if [[ ! -f "$LAUNCHER_PATH" ]]; then
err "Battle.net Launcher.exe not found at expected path:" err "Battle.net Launcher.exe not found at expected path:"
@@ -159,6 +278,52 @@ else
ok "Battle.net installed into prefix" ok "Battle.net installed into prefix"
fi fi
# ---------- icon ----------
log "Installing icon"
ICON_SRC="$PREFIX_DIR/drive_c/Program Files (x86)/Battle.net/Battle.net Launcher.exe"
ICON_PNG="$ICON_DIR/battlenet.png"
_install_icon() {
local tmpdir
tmpdir=$(mktemp -d)
trap 'rm -rf "$tmpdir"' RETURN
# Extract the largest icon group from the EXE, then convert to PNG.
# wrestool and icotool come from the 'icoutils' package.
if command -v wrestool >/dev/null && command -v icotool >/dev/null; then
if wrestool -x -t 14 "$ICON_SRC" -o "$tmpdir" 2>/dev/null; then
local ico
ico=$(find "$tmpdir" -name "*.ico" | head -1)
if [[ -n "$ico" ]]; then
mkdir -p "$ICON_DIR"
icotool -x --index=1 -o "$ICON_DIR" "$ico" 2>/dev/null \
&& mv "$ICON_DIR/"*_1_*.png "$ICON_PNG" 2>/dev/null \
&& ok "Icon installed to $ICON_PNG" \
&& return 0
fi
fi
warn "Icon extraction failed — Battle.net may not be installed yet, or the EXE layout changed."
else
warn "icoutils (wrestool/icotool) not installed. Skipping icon extraction."
warn "Install with: sudo pacman -S icoutils"
fi
return 1
}
if [[ -f "$ICON_PNG" ]]; then
skip "Icon already at $ICON_PNG"
elif [[ -f "$ICON_SRC" ]]; then
_install_icon || true
else
skip "Launcher EXE not found; skipping icon (will retry on next run)"
fi
# Refresh icon cache best-effort
if command -v gtk-update-icon-cache >/dev/null; then
gtk-update-icon-cache -f -t "$HOME/.local/share/icons/hicolor" 2>/dev/null || true
fi
# ---------- launch scripts ---------- # ---------- launch scripts ----------
log "Installing launch scripts to $BIN_DIR" log "Installing launch scripts to $BIN_DIR"
@@ -167,54 +332,88 @@ mkdir -p "$BIN_DIR"
cat > "$LAUNCH_SCRIPT" <<EOF cat > "$LAUNCH_SCRIPT" <<EOF
#!/bin/sh #!/bin/sh
# Auto-generated by battlenet-umu-setup.sh # Auto-generated by battlenet-umu-setup.sh
# Proton version: $PROTON_VERSION
export WINEPREFIX="$PREFIX_DIR" export WINEPREFIX="$PREFIX_DIR"
export GAMEID="$GAMEID" export GAMEID="$GAMEID"
export PROTONPATH="$PROTONPATH" export PROTONPATH="$PROTON_VERSION"
exec umu-run "\$WINEPREFIX/$LAUNCHER_EXE_REL" "\$@" exec umu-run "\$WINEPREFIX/$LAUNCHER_EXE_REL" "\$@"
EOF EOF
chmod +x "$LAUNCH_SCRIPT" chmod +x "$LAUNCH_SCRIPT"
ok "Wrote $LAUNCH_SCRIPT" ok "Wrote $LAUNCH_SCRIPT"
cat > "$KILL_SCRIPT" <<EOF cat > "$KILL_SCRIPT" <<'KILL_EOF'
#!/bin/sh #!/bin/sh
# Auto-generated by battlenet-umu-setup.sh # Auto-generated by battlenet-umu-setup.sh
# Kills the Battle.net launcher and all Wine/Proton processes for this prefix. # Gracefully stops Battle.net, then force-kills after a timeout.
export WINEPREFIX="$PREFIX_DIR"
pkill -9 -f 'Battle\\.net' 2>/dev/null || true _graceful_kill() {
pkill -9 -f 'Agent\\.exe' 2>/dev/null || true local pattern="$1"
pkill -9 -f 'Blizzard' 2>/dev/null || true # Try SIGTERM first
pkill -15 -f "$pattern" 2>/dev/null || return 0
local i=0
while pkill -0 -f "$pattern" 2>/dev/null && [ $i -lt 5 ]; do
sleep 1
i=$(( i + 1 ))
done
# Force-kill anything still alive
pkill -9 -f "$pattern" 2>/dev/null || true
}
_graceful_kill 'Battle\.net'
_graceful_kill 'Agent\.exe'
_graceful_kill 'Blizzard'
# wineserver may not be on PATH in umu environments; try both. # wineserver may not be on PATH in umu environments; try both.
if command -v wineserver >/dev/null; then if command -v wineserver >/dev/null; then
wineserver -k 2>/dev/null || true wineserver -k 2>/dev/null || true
fi fi
EOF KILL_EOF
chmod +x "$KILL_SCRIPT" chmod +x "$KILL_SCRIPT"
ok "Wrote $KILL_SCRIPT" ok "Wrote $KILL_SCRIPT"
# PATH check # PATH check — show shell-appropriate instructions
case ":$PATH:" in case ":$PATH:" in
*":$BIN_DIR:"*) ok "$BIN_DIR is on PATH" ;; *":$BIN_DIR:"*) ok "$BIN_DIR is on PATH" ;;
*) warn "$BIN_DIR is not on your PATH. Add this to your shell rc:" *)
warn " export PATH=\"\$HOME/.local/bin:\$PATH\"" ;; warn "$BIN_DIR is not on your PATH."
_shell_name="$(basename "${SHELL:-bash}")"
case "$_shell_name" in
fish)
warn "Add it permanently with:"
warn " fish_add_path \$HOME/.local/bin"
;;
zsh)
warn "Add this to ~/.zshrc:"
warn " export PATH=\"\$HOME/.local/bin:\$PATH\""
;;
*)
warn "Add this to ~/.bashrc (or ~/.profile for login shells):"
warn " export PATH=\"\$HOME/.local/bin:\$PATH\""
;;
esac
;;
esac esac
# ---------- desktop entry ---------- # ---------- desktop entry ----------
log "Installing desktop entry" log "Installing desktop entry"
# Use extracted icon if available, fall back to generic game icon
ICON_NAME="battlenet"
[[ -f "$ICON_PNG" ]] || ICON_NAME="applications-games"
mkdir -p "$DESKTOP_DIR" mkdir -p "$DESKTOP_DIR"
cat > "$DESKTOP_FILE" <<EOF cat > "$DESKTOP_FILE" <<EOF
[Desktop Entry] [Desktop Entry]
Name=Battle.net Name=Battle.net
Comment=Blizzard launcher (umu/Proton) Comment=Blizzard launcher (umu/Proton)
Exec=$LAUNCH_SCRIPT Exec=$LAUNCH_SCRIPT
Icon=battlenet Icon=$ICON_NAME
Type=Application Type=Application
Categories=Game; Categories=Game;
StartupWMClass=battle.net.exe StartupWMClass=battle.net.exe
EOF EOF
ok "Wrote $DESKTOP_FILE" ok "Wrote $DESKTOP_FILE"
# Refresh the desktop database if the tool is around — best-effort only.
if command -v update-desktop-database >/dev/null; then if command -v update-desktop-database >/dev/null; then
update-desktop-database "$DESKTOP_DIR" 2>/dev/null || true update-desktop-database "$DESKTOP_DIR" 2>/dev/null || true
fi fi
@@ -224,6 +423,7 @@ echo
ok "Setup complete." ok "Setup complete."
echo echo
echo " Prefix: $PREFIX_DIR" echo " Prefix: $PREFIX_DIR"
echo " Proton: $PROTON_VERSION"
echo " Launcher: $LAUNCH_SCRIPT" echo " Launcher: $LAUNCH_SCRIPT"
echo " Kill: $KILL_SCRIPT" echo " Kill: $KILL_SCRIPT"
echo " Desktop: $DESKTOP_FILE" echo " Desktop: $DESKTOP_FILE"
+166
View File
@@ -0,0 +1,166 @@
#!/usr/bin/env bash
#
# battlenet-update-proton.sh
#
# Lists available GE-Proton releases from GitHub, downloads the chosen version
# into ~/.local/share/Steam/compatibilitytools.d/, and updates the battlenet
# launch script to point at it.
#
# Usage:
# ./battlenet-update-proton.sh # interactive picker
# ./battlenet-update-proton.sh --latest # auto-install the latest release
# ./battlenet-update-proton.sh --list # list available releases and exit
# ./battlenet-update-proton.sh --version=GE-Proton9-20 # install a specific release
# END_HELP
set -euo pipefail
# ---------- config ----------
PROTON_DIR="${PROTON_INSTALL_DIR:-$HOME/.local/share/Steam/compatibilitytools.d}"
LAUNCH_SCRIPT="${BATTLENET_LAUNCH_SCRIPT:-$HOME/.local/bin/battlenet}"
GITHUB_API="https://api.github.com/repos/GloriousEggroll/proton-ge-custom/releases"
LIST_COUNT=10 # how many recent releases to show in the picker
ASSUME_LATEST=0
LIST_ONLY=0
PINNED_VERSION=""
# ---------- helpers ----------
c_reset=$'\033[0m'; c_blue=$'\033[1;34m'; c_green=$'\033[1;32m'
c_yellow=$'\033[1;33m'; c_red=$'\033[1;31m'; c_dim=$'\033[2m'
log() { printf '%s==>%s %s\n' "$c_blue" "$c_reset" "$*"; }
ok() { printf '%s✓%s %s\n' "$c_green" "$c_reset" "$*"; }
warn() { printf '%s!%s %s\n' "$c_yellow" "$c_reset" "$*"; }
err() { printf '%s✗%s %s\n' "$c_red" "$c_reset" "$*" >&2; }
skip() { printf ' %s(skip) %s%s\n' "$c_dim" "$*" "$c_reset"; }
die() { err "$*"; exit 1; }
# ---------- arg parsing ----------
for arg in "$@"; do
case "$arg" in
--latest) ASSUME_LATEST=1 ;;
--list) LIST_ONLY=1 ;;
--version=*) PINNED_VERSION="${arg#*=}" ;;
-h|--help)
sed -n '/^# Usage:/,/^# END_HELP/p' "$0" | sed 's/^# \{0,1\}//'
exit 0
;;
*) die "Unknown argument: $arg" ;;
esac
done
# ---------- preflight ----------
command -v curl >/dev/null || die "curl not installed. Run: sudo pacman -S curl"
command -v tar >/dev/null || die "tar not installed."
# ---------- fetch release list ----------
log "Fetching GE-Proton release list from GitHub"
RELEASES_JSON=$(curl -fsSL "$GITHUB_API?per_page=$LIST_COUNT")
# Extract tag names (GE-ProtonX-Y format)
mapfile -t RELEASE_TAGS < <(
printf '%s' "$RELEASES_JSON" \
| grep '"tag_name"' \
| sed 's/.*"tag_name": *"\([^"]*\)".*/\1/'
)
[[ ${#RELEASE_TAGS[@]} -gt 0 ]] || die "No releases found — GitHub API may be rate-limited. Try again shortly."
if [[ $LIST_ONLY -eq 1 ]]; then
echo "Recent GE-Proton releases:"
for tag in "${RELEASE_TAGS[@]}"; do
installed=""
[[ -d "$PROTON_DIR/$tag" ]] && installed=" ${c_green}(installed)${c_reset}"
printf " %s%s\n" "$tag" "$installed"
done
exit 0
fi
# ---------- choose version ----------
if [[ -n "$PINNED_VERSION" ]]; then
TARGET="$PINNED_VERSION"
elif [[ $ASSUME_LATEST -eq 1 ]]; then
TARGET="${RELEASE_TAGS[0]}"
log "Latest release: $TARGET"
else
echo
echo "Recent GE-Proton releases (${c_green}green = already installed${c_reset}):"
i=1
for tag in "${RELEASE_TAGS[@]}"; do
installed=""
[[ -d "$PROTON_DIR/$tag" ]] && installed=" ${c_green}${c_reset}"
printf " %2d) %s%s\n" "$i" "$tag" "$installed"
(( i++ ))
done
echo
printf "Enter number (1-%d) or a version tag [default: 1 = %s]: " "${#RELEASE_TAGS[@]}" "${RELEASE_TAGS[0]}"
read -r choice
if [[ -z "$choice" ]]; then
TARGET="${RELEASE_TAGS[0]}"
elif [[ "$choice" =~ ^[0-9]+$ ]] && (( choice >= 1 && choice <= ${#RELEASE_TAGS[@]} )); then
TARGET="${RELEASE_TAGS[$(( choice - 1 ))]}"
else
TARGET="$choice"
fi
fi
log "Target: $TARGET"
# ---------- download ----------
INSTALL_PATH="$PROTON_DIR/$TARGET"
if [[ -d "$INSTALL_PATH" ]]; then
skip "$TARGET already installed at $INSTALL_PATH"
else
# Find the .tar.gz asset URL for this release
log "Resolving download URL for $TARGET"
ASSET_URL=$(
curl -fsSL "$GITHUB_API/tags/$TARGET" \
| grep '"browser_download_url"' \
| grep '\.tar\.gz"' \
| head -1 \
| sed 's/.*"browser_download_url": *"\([^"]*\)".*/\1/'
)
[[ -n "$ASSET_URL" ]] || die "Could not find a .tar.gz asset for $TARGET. Check the version name."
log "Downloading $TARGET"
TMPFILE=$(mktemp --suffix=.tar.gz)
trap 'rm -f "$TMPFILE"' EXIT
curl -L --fail --progress-bar -o "$TMPFILE" "$ASSET_URL"
log "Extracting to $PROTON_DIR"
mkdir -p "$PROTON_DIR"
tar -xzf "$TMPFILE" -C "$PROTON_DIR"
ok "$TARGET installed to $INSTALL_PATH"
fi
# ---------- update launch script ----------
if [[ ! -f "$LAUNCH_SCRIPT" ]]; then
warn "Launch script not found at $LAUNCH_SCRIPT — skipping update."
warn "Run battlenet-umu-setup.sh first, or set BATTLENET_LAUNCH_SCRIPT."
else
CURRENT=$(grep '^export PROTONPATH=' "$LAUNCH_SCRIPT" 2>/dev/null | head -1 | sed 's/.*="\?\([^"]*\)"\?.*/\1/' || echo "(not set)")
if [[ "$CURRENT" == "$TARGET" ]]; then
skip "Launch script already points to $TARGET"
else
log "Updating launch script: $CURRENT$TARGET"
sed -i \
-e "s|^# Proton version:.*|# Proton version: $TARGET|" \
-e "s|^export PROTONPATH=.*|export PROTONPATH=\"$TARGET\"|" \
"$LAUNCH_SCRIPT"
ok "Launch script updated"
fi
fi
# ---------- done ----------
echo
ok "Done. Active Proton: $TARGET"
echo
echo " Installed at: $INSTALL_PATH"
echo " Launch script: $LAUNCH_SCRIPT"
echo
echo "Restart Battle.net to use the new Proton version."
echo "If anything regresses, re-run with --version=<older-tag> to roll back."