#!/usr/bin/env bash # Installs Project Reclaimer (Halo 3 via your own MCC install) on Bazzite or SteamOS # and adds it to Steam as a non-Steam game running under Proton Experimental. # # curl -fsSL halo.jaxon.dev | bash # curl -fsSL halo.jaxon.dev | bash -s -- --ui-window # # --ui-window sets RECLAIMER_UI_COMPOSITION=window, for missing or black menus/chat # RECLAIMER_DIR= installs somewhere other than ~/Games/ProjectReclaimer # # Re-running it updates the client to the latest release and repoints the shortcut. set -euo pipefail REPO=ProjectReclaimer/project-reclaimer-releases APP_NAME="Project Reclaimer" MCC_APPID=976730 say() { printf '\033[1;32m==>\033[0m %s\n' "$*"; } warn() { printf '\033[1;33m!!\033[0m %s\n' "$*" >&2; } die() { printf '\033[1;31mxx\033[0m %s\n' "$*" >&2; exit 1; } # Binary shortcuts.vdf and text config.vdf editing, plus Steam library lookup. read -r -d '' PY <<'PY' || true import os, re, shutil, struct, sys, time, zlib def bvdf_read(b, i): d = {} while True: t = b[i]; i += 1 if t == 8: return d, i j = b.index(b'\0', i); k = b[i:j].decode('utf-8', 'surrogateescape'); i = j + 1 if t == 0: v, i = bvdf_read(b, i) elif t == 1: j = b.index(b'\0', i); v = b[i:j].decode('utf-8', 'surrogateescape'); i = j + 1 elif t == 2: v = struct.unpack_from('/dev/null || pgrep -x steamwebhelper >/dev/null; } main() { local launch_opts=- while (($#)); do case $1 in --ui-window) launch_opts='RECLAIMER_UI_COMPOSITION=window %command%' ;; *) die "Unknown option: $1" ;; esac shift done [[ ${XDG_CURRENT_DESKTOP:-} == gamescope ]] && die "Switch to Desktop Mode first (Steam menu → Power → Switch to Desktop)." for bin in curl python3 sha256sum; do command -v "$bin" >/dev/null || die "Missing $bin." done local steam_root=${STEAM_ROOT:-} if [[ -z $steam_root ]]; then for d in "$HOME/.local/share/Steam" "$HOME/.steam/steam" "$HOME/.steam/root"; do [[ -d $d/steamapps ]] && { steam_root=$(realpath "$d"); break; } done fi if [[ -z $steam_root ]]; then [[ -d $HOME/.var/app/com.valvesoftware.Steam ]] && die "Only Flatpak Steam was found. This script supports the regular Steam that ships with Bazzite and SteamOS." die "Couldn't find Steam. Install it, log in, and run this again." fi say "Steam: $steam_root" # 1. MCC with Halo 3 local mcc if ! mcc=$(py find-app "$steam_root" "$MCC_APPID" "Halo The Master Chief Collection"); then warn "Halo: The Master Chief Collection isn't installed." warn "Opening the Steam install dialog. In MCC → Properties → DLC, keep Halo 3 Campaign" warn "and Halo 3 Multiplayer ticked (untick the rest to save space). Run this again once it's downloaded." steam "steam://install/$MCC_APPID" >/dev/null 2>&1 & exit 1 fi if [[ ! -f $mcc/halo3/halo3.dll ]]; then die "MCC is installed but Halo 3 isn't (or is still downloading). In Steam, right-click MCC → Properties → DLC, tick Halo 3 Campaign and Halo 3 Multiplayer, let it finish, then run this again." fi say "Halo 3: $mcc/halo3" # 2. Latest client local dir=${RECLAIMER_DIR:-$HOME/Games/ProjectReclaimer} mkdir -p "$dir" local tag tag=$(curl -fsSL "https://api.github.com/repos/$REPO/releases/latest" | python3 -I -c 'import json,sys; print(json.load(sys.stdin)["tag_name"])') || die "Couldn't reach GitHub to find the latest release." local exe_name="project-reclaimer-client-$tag.exe" base="https://github.com/$REPO/releases/download/$tag" local exe=$dir/$exe_name sum sum=$(curl -fsSL "$base/SHA256SUMS.txt" | grep -F " $exe_name") || die "No checksum for $exe_name." if [[ -f $exe ]] && (cd "$dir" && sha256sum -c --status <<<"$sum"); then say "Client $tag already downloaded" else say "Downloading client $tag" curl -fL --progress-bar -o "$exe.part" "$base/$exe_name" (cd "$dir" && sha256sum -c --status <<<"${sum/$exe_name/$exe_name.part}") || { rm -f "$exe.part"; die "Checksum mismatch on the download. Try again."; } mv "$exe.part" "$exe" fi find "$dir" -maxdepth 1 -name 'project-reclaimer-client-v*.exe' ! -name "$exe_name" -delete # 3. Steam shortcut. Steam rewrites shortcuts.vdf and config.vdf on exit, so it has to be closed. local was_running=0 if steam_running; then was_running=1 say "Closing Steam so the shortcut sticks" steam -shutdown >/dev/null 2>&1 || true for _ in $(seq 60); do steam_running || break; sleep 1; done steam_running && die "Steam didn't close. Quit it from the tray icon and run this again." fi local appid appid=$(py install "$steam_root" "$exe" "$APP_NAME" "$launch_opts") say "Added \"$APP_NAME\" to Steam (Proton Experimental, app ID $appid)" if ((was_running)); then say "Starting Steam" setsid -f steam >/dev/null 2>&1