#!/usr/bin/env bash set -euo pipefail # DevourUnlocker — one-click per-platform installer (detects OS, pulls correct files, supports uninstall/update/repair) # Primary proxy is version.dll (less flagged than winhttp.dll) — winhttp kept for compat # Usage: bash install.sh [--install] [--uninstall] [--update|--repair] [--tag v2.1.0] [--game-dir /path] [--repo ad3laid3/DevourUnlocker] REPO="${REPO:-ad3laid3/DevourUnlocker}" TAG="${TAG:-latest}" GAME_DIR="" MODE="install" detect_os() { case "$(uname -s)" in Linux*) echo "linux" ;; Darwin*) echo "macos" ;; MINGW*|MSYS*|CYGWIN*) echo "windows" ;; *) echo "linux" ;; esac } detect_game_dir() { local cands=( "$HOME/.local/share/Steam/steamapps/common/Devour" "$HOME/.steam/steam/steamapps/common/Devour" "/mnt/*/SteamLibrary/steamapps/common/Devour" "C:/Program Files (x86)/Steam/steamapps/common/Devour" ) for c in "${cands[@]}"; do for e in $c; do [[ -f "$e/DEVOUR.exe" ]] && { echo "$e"; return 0; }; done; done local vdf="$HOME/.local/share/Steam/steamapps/libraryfolders.vdf" if [[ -f "$vdf" ]]; then grep -o '"path"[[:space:]]*"[^"]*"' "$vdf" | cut -d'"' -f4 | while read -r p; do [[ -f "$p/steamapps/common/Devour/DEVOUR.exe" ]] && { echo "$p/steamapps/common/Devour"; return 0; }; done; fi return 1 } while [[ $# -gt 0 ]]; do case "$1" in --install) MODE="install"; shift;; --uninstall) MODE="uninstall"; shift;; --update|--repair) MODE="update"; shift;; --tag) TAG="$2"; shift 2;; --game-dir) GAME_DIR="$2"; shift 2;; --repo) REPO="$2"; shift 2;; *) echo "Unknown arg $1 (try --install|--uninstall|--update)"; exit 1;; esac done if [[ -z "$GAME_DIR" ]]; then GAME_DIR="$(detect_game_dir || true)"; fi if [[ -z "$GAME_DIR" || ! -f "$GAME_DIR/DEVOUR.exe" ]]; then echo "[!] Could not find Devour install. Use --game-dir /path/to/Devour"; exit 1; fi OS=$(detect_os) echo "[*] OS: $OS Game dir: $GAME_DIR Mode: $MODE Repo: $REPO Tag: $TAG" # --- UNINSTALL --- if [[ "$MODE" == "uninstall" ]]; then echo "[*] Uninstalling (removing version.dll/winhttp.dll + Cloud patch)..." rm -f "$GAME_DIR/version.dll" "$GAME_DIR/winhttp.dll" "$GAME_DIR/DevourUnlocker.dll" "$GAME_DIR/DevourUnlocker.json" "$GAME_DIR/DevourUnlocker.log" [[ -f "$GAME_DIR/version.dll.bak" ]] && mv "$GAME_DIR/version.dll.bak" "$GAME_DIR/version.dll" && echo "[*] Restored version.dll.bak" [[ -f "$GAME_DIR/winhttp.dll.bak" ]] && mv "$GAME_DIR/winhttp.dll.bak" "$GAME_DIR/winhttp.dll" && echo "[*] Restored winhttp.dll.bak" # Revert collectables.json to Steam Cloud (remove our superset) python3 -c " import pathlib, json for p in pathlib.Path.home().glob('.local/share/Steam/userdata/*/1274570/remote/collectables.json'): try: j=json.loads(p.read_text()) # clear to minimal (will be re-fetched from cloud on next launch) p.write_text('{}') print(f'[+] Cleared {p}') except: pass " echo "[*] Uninstalled. Remove from Steam → Devour → Properties → Launch Options: WINEDLLOVERRIDES if you added it." exit 0 fi # --- UPDATE / REPAIR: redownload + verify GameAssembly hasn't shifted --- if [[ "$MODE" == "update" ]]; then echo "[*] Repair: checking GameAssembly hash + metadata offsets..." GA="$GAME_DIR/GameAssembly.dll" if [[ -f "$GA" ]]; then sha=$(sha1sum "$GA" | cut -d' ' -f1) echo "[*] GameAssembly sha1 $sha" # Compare to last installed sha stored in DevourUnlocker.json if present if [[ -f "$GAME_DIR/DevourUnlocker.json" ]]; then old=$(python3 -c "import json,pathlib; print(json.loads(open('$GAME_DIR/DevourUnlocker.json').read()).get('gameAssemblySha',''))" 2>/dev/null || echo "") if [[ -n "$old" && "$old" != "$sha" ]]; then echo "[!] GameAssembly changed since last install (game updated) — will re-resolve offsets" fi fi fi # Fall through to install (redownload) fi # --- INSTALL --- if [[ "$TAG" == "latest" ]]; then # Forgejo (git.mithraic.cloud) uses /api/v1/repos/:owner/:repo/releases/latest API="https://git.mithraic.cloud/api/v1/repos/$REPO/releases/latest" if curl -s "$API" | grep -q "tag_name"; then TAG=$(curl -s "$API" | grep -o '"tag_name"[[:space:]]*:[[:space:]]*"[^"]*"' | cut -d'"' -f4) else API="https://api.github.com/repos/$REPO/releases/latest" TAG=$(curl -s "$API" | grep -o '"tag_name"[[:space:]]*:[[:space:]]*"[^"]*"' | cut -d'"' -f4) fi [[ -z "$TAG" ]] && { echo "[!] Could not resolve latest tag from $REPO"; exit 1; } fi echo "[*] Installing $REPO $TAG for $OS" # Per-platform artifact names if [[ "$OS" == "windows" ]]; then ZIP="DevourUnlocker_version_${TAG}.zip"; URL="https://git.mithraic.cloud/$REPO/releases/download/$TAG/$ZIP"; ALT_URL="https://github.com/$REPO/releases/download/$TAG/$ZIP" elif [[ "$OS" == "macos" ]]; then echo "[!] macOS no native GameAssembly.dylib available — will install Linux/Windows version for Proton/Wine. Use --game-dir to override."; ZIP="DevourUnlocker_winhttp_${TAG}.zip"; URL="https://git.mithraic.cloud/$REPO/releases/download/$TAG/$ZIP"; ALT_URL="https://github.com/$REPO/releases/download/$TAG/$ZIP" else ZIP="DevourUnlocker_version_${TAG}.zip"; URL="https://git.mithraic.cloud/$REPO/releases/download/$TAG/$ZIP"; ALT_URL="https://github.com/$REPO/releases/download/$TAG/$ZIP"; fi TMP="$(mktemp -d)" echo "[*] Downloading $URL (fallback $ALT_URL)" if ! curl -L -o "$TMP/pkg.zip" "$URL"; then curl -L -o "$TMP/pkg.zip" "$ALT_URL" || { echo "[!] Download failed — check REPO/TAG"; echo " Fallback: build locally: cmake -B build && cmake --build build"; exit 1; }; fi unzip -o "$TMP/pkg.zip" -d "$TMP" >/dev/null # Backup existing version/winhttp if not ours (check for DevourUnlocker string) for dll in version.dll winhttp.dll; do if [[ -f "$GAME_DIR/$dll" ]]; then if ! grep -q "DevourUnlocker" "$GAME_DIR/$dll" 2>/dev/null; then cp "$GAME_DIR/$dll" "$GAME_DIR/$dll.bak" && echo "[*] Backed up $dll → $dll.bak" fi fi done # Prefer version.dll (less flagged) — copy both for compat if [[ -f "$TMP/version.dll" ]]; then cp -v "$TMP/version.dll" "$GAME_DIR/version.dll"; echo "[*] Installed version.dll (primary, less detectable)"; fi if [[ -f "$TMP/winhttp.dll" ]]; then cp -v "$TMP/winhttp.dll" "$GAME_DIR/winhttp.dll"; echo "[*] Installed winhttp.dll (compat)"; fi cp -v "$TMP/DevourUnlocker.dll" "$GAME_DIR/DevourUnlocker.dll" 2>/dev/null || true # Record GameAssembly hash for future update checks GA_SHA=$(sha1sum "$GAME_DIR/GameAssembly.dll" 2>/dev/null | cut -d' ' -f1 || echo "") if [[ -f "$TMP/DevourUnlocker.json" ]]; then python3 -c "import json; j=json.load(open('$TMP/DevourUnlocker.json')); j['gameAssemblySha']='$GA_SHA'; open('$GAME_DIR/DevourUnlocker.json','w').write(json.dumps(j,indent=2))" else python3 -c "import json; open('$GAME_DIR/DevourUnlocker.json','w').write(json.dumps({'unlockRobes':True,'unlockCharacters':True,'unlockPerks':True,'unlockPets':True,'unlockFlashlights':True,'unlockEmotes':True,'unlockOutfits':True,'gameAssemblySha':'$GA_SHA'},indent=2))" fi # Patch Steam Cloud collectables.json via python helper (uses proper game calls: HasCollectedAll* checks) echo "[*] Patching Steam Cloud collectables.json (proper game call: HasCollectedAllBarbedWires etc)..." python3 "$(dirname "$0")/../standalone/devour_unlocker.py" unlock --all 2>&1 | tail -n 20 || echo "[!] Could not patch collectables.json — will still unlock via runtime hook" echo "[*] Installed to $GAME_DIR" if [[ "$OS" == "linux" ]]; then echo "[*] Linux/Proton: version.dll is auto-loaded beside DEVOUR.exe (no WINEDLLOVERRIDES needed on native Windows, but on Proton set Steam → Devour → Properties → Launch Options: WINEDLLOVERRIDES=\"version=n,b\" %command% or just use scripts/launch.sh)" echo "[*] Or run: bash $(dirname "$0")/launch.sh (auto sets WINEDLLOVERRIDES and launches via Proton)" else echo "[*] Windows: version.dll beside DEVOUR.exe is auto-loaded — no launch option needed. Just launch Devour." fi echo "[*] Verify: python $(dirname "$0")/../standalone/devour_unlocker.py diagnose" rm -rf "$TMP"