- GetCurrentTokens + HasRetrievedUserItems now pass through to original:
spoofed tokens broke the credit-purchase modal; forced retrieved-flag with
empty userItems made flows treat items as definitively not-owned
- UserHasItem/PlayerHasItem keep returning true but LOG itemDef on every call;
IsCharacterUnlocked/HasPerk/HasOutfit log decoded string args
-> next play session reveals whether pet/perk/outfit UI consults these at all
(if silent while browsing locked pets, UI reads userItems list field directly)
- install.sh: replace fragile inline heredoc python with scripts/vdf_patch.py
(chr(92)-built bytes; the old heredoc produced \\ corruption that silently
killed LaunchOptions for account 919954036); warn when Steam is running
160 lines
8.9 KiB
Bash
Executable file
160 lines
8.9 KiB
Bash
Executable file
#!/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
|
|
|
|
# version.dll is sole unlocker (full hooks, less flagged than winhttp) — winhttp kept only as optional compat
|
|
if [[ -f "$TMP/version.dll" ]]; then cp -v "$TMP/version.dll" "$GAME_DIR/version.dll"; echo "[*] Installed version.dll (primary unlocker — all categories)"; fi
|
|
if [[ -f "$TMP/winhttp.dll" ]]; then cp -v "$TMP/winhttp.dll" "$GAME_DIR/winhttp.dll"; echo "[*] Installed winhttp.dll (optional compat, not needed)"; 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"
|
|
# Auto-set Steam launch options for Proton silently (so user never needs manual WINEDLLOVERRIDES)
|
|
# Uses scripts/vdf_patch.py — bash-escaped heredoc python caused \\\" corruption before.
|
|
if [[ "$OS" == "linux" ]]; then
|
|
if pgrep -x steam >/dev/null 2>&1; then
|
|
echo "[!] Steam is RUNNING — it will overwrite localconfig.vdf on exit and drop our LaunchOptions."
|
|
echo " Close Steam first: steam -shutdown then re-run install.sh"
|
|
fi
|
|
if [[ -f "$(dirname "$0")/vdf_patch.py" ]]; then
|
|
python3 "$(dirname "$0")/vdf_patch.py" 2>&1 | head -n 20 && patched_any=1
|
|
else
|
|
echo "[!] vdf_patch.py missing — cannot auto-set Launch Options"
|
|
fi
|
|
if [[ $patched_any -eq 0 ]]; then echo "[!] No localconfig.vdf with Apps->1274570 found — launch once from Steam, then re-run install.sh"; fi
|
|
echo "[*] Linux/Proton: version.dll is sole unlocker beside DEVOUR.exe — Steam Launch Options now auto-set, just press Play. If Steam was running during this install: steam -shutdown, re-run install.sh, reopen Steam."
|
|
echo "[*] Fallback: bash $(dirname "$0")/launch.sh (also auto-sets WINEDLLOVERRIDES)"
|
|
else
|
|
echo "[*] Windows: version.dll beside DEVOUR.exe is auto-loaded (UnityPlayer.dll imports VERSION.dll) — no launch option needed. Just launch Devour."
|
|
fi
|
|
echo "[*] version.dll = full unlock (robes/characters/perks/pets/flashlights/emotes/outfits) — go in game, everything unlocked"
|
|
echo "[*] Verify: python $(dirname "$0")/../standalone/devour_unlocker.py diagnose"
|
|
rm -rf "$TMP"
|