add version.dll (less flagged) + per-platform installers with redownload/shift check
Some checks are pending
Build DevourUnlocker / windows-cross (push) Waiting to run
Build DevourUnlocker / linux (push) Waiting to run
Build DevourUnlocker / standalone (push) Waiting to run

- version.dll proxy (6 exports GetFileVersionInfoA/W etc, runtime forwarding to C:\Windows\System32\version.dll, less flagged than winhttp) built via CMake version SHARED
- install.sh: platform detector (Linux/Darwin/Windows), --install/--uninstall/--update/--repair, detects Steam via libraryfolders.vdf, pulls version.dll+winhttp.dll+DevourUnlocker.dll per OS, backs up, patches collectables.json via devour_unlocker.py, handles Forgejo vs GitHub API, no BepInEx required but BepInEx still built as fallback, macOS note (no GameAssembly.dylib available)
- install.ps1: same for Windows (Find-GameDir, backup, version.dll primary)
- check_offsets.py: verifies GameAssembly sha1 and IsRobeUpdated/UpdateLockedPets still in global-metadata, warns if GameAssembly changed since last install
This commit is contained in:
MiTHRAL 2026-08-24 00:03:19 -04:00
parent 4dd7b8d393
commit 6329350e90
5 changed files with 246 additions and 84 deletions

View file

@ -65,9 +65,8 @@ else()
target_compile_definitions(devour_core PUBLIC DEVOUR_LINUX=1) target_compile_definitions(devour_core PUBLIC DEVOUR_LINUX=1)
endif() endif()
# --- winhttp.dll proxy (Windows) — no static forwarding to winhttp_real (that DLL doesn't exist, caused load fail). # --- winhttp.dll + version.dll proxies (Windows) — version.dll is less flagged by anti-cheat, preferred
# We ship a minimal winhttp.dll that only exports DllMain for hooking; WinHttp APIs are not needed for Devour's unlock. # winhttp still built for compatibility, but version.dll is primary for new installs
# If you need full WinHttp proxy, install winhttp_orig.dll alongside and use runtime forwarding in winhttp_proxy.cpp.
if(BUILD_WINHTTP_PROXY AND WIN32) if(BUILD_WINHTTP_PROXY AND WIN32)
add_library(winhttp SHARED add_library(winhttp SHARED
src/platform/winhttp_proxy.cpp src/platform/winhttp_proxy.cpp
@ -76,10 +75,27 @@ if(BUILD_WINHTTP_PROXY AND WIN32)
target_link_libraries(winhttp PRIVATE devour_core) target_link_libraries(winhttp PRIVATE devour_core)
set_target_properties(winhttp PROPERTIES OUTPUT_NAME "winhttp" PREFIX "" SUFFIX ".dll") set_target_properties(winhttp PROPERTIES OUTPUT_NAME "winhttp" PREFIX "" SUFFIX ".dll")
target_compile_definitions(winhttp PRIVATE WINHTTP_PROXY=1) target_compile_definitions(winhttp PRIVATE WINHTTP_PROXY=1)
if(MINGW)
target_link_options(winhttp PRIVATE -static-libgcc -static-libstdc++ -Wl,-Bstatic -lwinpthread -Wl,-Bdynamic)
endif()
add_library(version SHARED
src/platform/version_proxy.cpp
src/main.cpp
)
target_link_libraries(version PRIVATE devour_core)
set_target_properties(version PROPERTIES OUTPUT_NAME "version" PREFIX "" SUFFIX ".dll")
target_compile_definitions(version PRIVATE VERSION_PROXY=1)
if(MINGW)
target_link_options(version PRIVATE -static-libgcc -static-libstdc++ -Wl,-Bstatic -lwinpthread -Wl,-Bdynamic)
endif()
add_library(DevourUnlocker SHARED src/main.cpp) add_library(DevourUnlocker SHARED src/main.cpp)
target_link_libraries(DevourUnlocker PRIVATE devour_core) target_link_libraries(DevourUnlocker PRIVATE devour_core)
set_target_properties(DevourUnlocker PROPERTIES OUTPUT_NAME "DevourUnlocker" PREFIX "" SUFFIX ".dll") set_target_properties(DevourUnlocker PROPERTIES OUTPUT_NAME "DevourUnlocker" PREFIX "" SUFFIX ".dll")
if(MINGW)
target_link_options(DevourUnlocker PRIVATE -static-libgcc -static-libstdc++)
endif()
endif() endif()
# Linux .so (LD_PRELOAD / Plugins/x86_64) # Linux .so (LD_PRELOAD / Plugins/x86_64)

View file

@ -1,10 +1,12 @@
param( param(
[string]$Repo = "mithral/DevourUnlocker", [string]$Repo = "ad3laid3/DevourUnlocker",
[string]$Tag = "latest", [string]$Tag = "latest",
[string]$GameDir = "", [string]$GameDir = "",
[switch]$Uninstall [switch]$Uninstall,
[switch]$Update
) )
$ErrorActionPreference = "Stop" $ErrorActionPreference = "Stop"
# Platform detector: Windows (native) — version.dll is primary (less flagged than winhttp.dll)
function Find-GameDir { function Find-GameDir {
$cands = @( $cands = @(
@ -29,38 +31,72 @@ if (-not $GameDir -or -not (Test-Path "$GameDir/DEVOUR.exe")) {
Write-Error "Could not find Devour install. Use -GameDir C:/.../Devour" Write-Error "Could not find Devour install. Use -GameDir C:/.../Devour"
exit 1 exit 1
} }
Write-Host "[*] Game dir: $GameDir" Write-Host "[*] OS: Windows Game dir: $GameDir Mode: $(if($Uninstall){"uninstall"}elseif($Update){"update"}else{"install"})"
if ($Uninstall) { if ($Uninstall) {
Remove-Item "$GameDir/winhttp.dll" -ErrorAction SilentlyContinue Write-Host "[*] Uninstalling..."
Remove-Item "$GameDir/DevourUnlocker.dll" -ErrorAction SilentlyContinue @("version.dll","winhttp.dll","DevourUnlocker.dll","DevourUnlocker.json","DevourUnlocker.log") | ForEach-Object { Remove-Item "$GameDir/$_" -ErrorAction SilentlyContinue }
if (Test-Path "$GameDir/winhttp.dll.bak") { Move-Item "$GameDir/winhttp.dll.bak" "$GameDir/winhttp.dll" -Force; Write-Host "[*] Restored original" } @("version.dll.bak","winhttp.dll.bak") | ForEach-Object { if (Test-Path "$GameDir/$_") { Move-Item "$GameDir/$_" "$GameDir/$($_.Replace('.bak',''))" -Force; Write-Host "[*] Restored $_" } }
Write-Host "[*] Uninstalled" # Revert collectables.json for current user
$remote = "$env:USERPROFILE/../AppData/LocalLow/Straight Back Games/DEVOUR" # not needed, Steam Cloud is primary
Write-Host "[*] Uninstalled. To revert collectables, run: python standalone/devour_unlocker.py unlock --lock"
exit 0 exit 0
} }
if ($Update) {
$ga = Join-Path $GameDir "GameAssembly.dll"
if (Test-Path $ga) {
$sha = (Get-FileHash $ga -Algorithm SHA1).Hash
Write-Host "[*] GameAssembly sha1 $sha — will re-fetch release and re-verify offsets"
}
}
if ($Tag -eq "latest") { if ($Tag -eq "latest") {
$api = "https://api.github.com/repos/$Repo/releases/latest" # Try Forgejo first, then GitHub
$rel = Invoke-RestMethod -Uri $api try {
$Tag = $rel.tag_name $api = "https://git.mithraic.cloud/api/v1/repos/$Repo/releases/latest"
$rel = Invoke-RestMethod -Uri $api -ErrorAction Stop
$Tag = $rel.tag_name
} catch {
$api = "https://api.github.com/repos/$Repo/releases/latest"
$rel = Invoke-RestMethod -Uri $api
$Tag = $rel.tag_name
}
} }
Write-Host "[*] Installing $Repo $Tag" Write-Host "[*] Installing $Repo $Tag"
$zipUrl = "https://github.com/$Repo/releases/download/$Tag/DevourUnlocker_winhttp_$Tag.zip" $zipName = "DevourUnlocker_version_$Tag.zip"
$zipUrl = "https://git.mithraic.cloud/$Repo/releases/download/$Tag/$zipName"
$altUrl = "https://github.com/$Repo/releases/download/$Tag/$zipName"
$tmp = Join-Path $env:TEMP "devour_$Tag" $tmp = Join-Path $env:TEMP "devour_$Tag"
New-Item -ItemType Directory -Force -Path $tmp | Out-Null New-Item -ItemType Directory -Force -Path $tmp | Out-Null
$zip = Join-Path $tmp "pkg.zip" $zip = Join-Path $tmp "pkg.zip"
Invoke-WebRequest -Uri $zipUrl -OutFile $zip try { Invoke-WebRequest -Uri $zipUrl -OutFile $zip -ErrorAction Stop }
catch { Invoke-WebRequest -Uri $altUrl -OutFile $zip }
Expand-Archive -Path $zip -DestinationPath $tmp -Force Expand-Archive -Path $zip -DestinationPath $tmp -Force
if (Test-Path "$GameDir/winhttp.dll") { # Backup if not ours
Copy-Item "$GameDir/winhttp.dll" "$GameDir/winhttp.dll.bak" -Force foreach($dll in @("version.dll","winhttp.dll")){
Write-Host "[*] Backed up winhttp.dll" if (Test-Path "$GameDir/$dll") {
$isOurs = Select-String -Path "$GameDir/$dll" -Pattern "DevourUnlocker" -Quiet -ErrorAction SilentlyContinue
if (-not $isOurs) { Copy-Item "$GameDir/$dll" "$GameDir/$dll.bak" -Force; Write-Host "[*] Backed up $dll" }
}
} }
Copy-Item "$tmp/winhttp.dll" "$GameDir/winhttp.dll" -Force if (Test-Path "$tmp/version.dll") { Copy-Item "$tmp/version.dll" "$GameDir/version.dll" -Force; Write-Host "[*] Installed version.dll (primary, less detectable)" }
if (Test-Path "$tmp/winhttp.dll") { Copy-Item "$tmp/winhttp.dll" "$GameDir/winhttp.dll" -Force; Write-Host "[*] Installed winhttp.dll (compat)" }
if (Test-Path "$tmp/DevourUnlocker.dll") { Copy-Item "$tmp/DevourUnlocker.dll" "$GameDir/DevourUnlocker.dll" -Force } if (Test-Path "$tmp/DevourUnlocker.dll") { Copy-Item "$tmp/DevourUnlocker.dll" "$GameDir/DevourUnlocker.dll" -Force }
if (-not (Test-Path "$GameDir/DevourUnlocker.json")) { # Record GameAssembly hash
if (Test-Path "$tmp/DevourUnlocker.json") { Copy-Item "$tmp/DevourUnlocker.json" "$GameDir/DevourUnlocker.json" } $ga = Join-Path $GameDir "GameAssembly.dll"
else { '{"unlockRobes":true,"unlockCharacters":true}' | Out-File "$GameDir/DevourUnlocker.json" } $sha = if(Test-Path $ga){(Get-FileHash $ga -Algorithm SHA1).Hash}else{""}
if (Test-Path "$tmp/DevourUnlocker.json") {
$j = Get-Content "$tmp/DevourUnlocker.json" | ConvertFrom-Json
$j | Add-Member -NotePropertyName gameAssemblySha -NotePropertyValue $sha -Force
$j | ConvertTo-Json | Out-File "$GameDir/DevourUnlocker.json"
} else {
@{unlockRobes=$true;unlockCharacters=$true;unlockPerks=$true;unlockPets=$true;unlockFlashlights=$true;unlockEmotes=$true;unlockOutfits=$true;gameAssemblySha=$sha} | ConvertTo-Json | Out-File "$GameDir/DevourUnlocker.json"
} }
Write-Host "[*] Installed to $GameDir — press F6 in-game for GUI, F5 to unload" # Patch Steam Cloud collectables.json via python helper (proper game call: HasCollectedAllBarbedWires etc)
Write-Host "[*] Patching Steam Cloud collectables.json (proper call: HasCollectedAllBarbedWires)..."
try { python "$PSScriptRoot/../standalone/devour_unlocker.py" unlock --all } catch { Write-Host "[!] Could not patch collectables.json — will still unlock via runtime hook" }
Write-Host "[*] Installed to $GameDir — version.dll is auto-loaded on Windows (no launch option). Just launch Devour."
Remove-Item $tmp -Recurse -Force Remove-Item $tmp -Recurse -Force

View file

@ -1,94 +1,147 @@
#!/usr/bin/env bash #!/usr/bin/env bash
set -euo pipefail set -euo pipefail
# DevourUnlocker installer — pulls winhttp.dll from GitHub Releases and drops beside DEVOUR.exe # DevourUnlocker — one-click per-platform installer (detects OS, pulls correct files, supports uninstall/update/repair)
# Works for CachyOS/Proton and native Linux; Windows users should use install.ps1 # Primary proxy is version.dll (less flagged than winhttp.dll) — winhttp kept for compat
# Usage: REPO=mithral/DevourUnlocker bash install.sh [--uninstall] [--tag v2.0.0] [--game-dir /path] # Usage: bash install.sh [--install] [--uninstall] [--update|--repair] [--tag v2.1.0] [--game-dir /path] [--repo ad3laid3/DevourUnlocker]
REPO="${REPO:-mithral/DevourUnlocker}" REPO="${REPO:-ad3laid3/DevourUnlocker}"
TAG="${TAG:-latest}" TAG="${TAG:-latest}"
GAME_DIR="" GAME_DIR=""
UNINSTALL=0 MODE="install"
while [[ $# -gt 0 ]]; do detect_os() {
case "$1" in case "$(uname -s)" in
--uninstall) UNINSTALL=1; shift;; Linux*) echo "linux" ;;
--tag) TAG="$2"; shift 2;; Darwin*) echo "macos" ;;
--game-dir) GAME_DIR="$2"; shift 2;; MINGW*|MSYS*|CYGWIN*) echo "windows" ;;
--repo) REPO="$2"; shift 2;; *) echo "linux" ;;
*) echo "Unknown arg $1"; exit 1;;
esac esac
done }
detect_game_dir() { detect_game_dir() {
local cands=( local cands=(
"$HOME/.local/share/Steam/steamapps/common/Devour" "$HOME/.local/share/Steam/steamapps/common/Devour"
"$HOME/.steam/steam/steamapps/common/Devour" "$HOME/.steam/steam/steamapps/common/Devour"
"/mnt/*/SteamLibrary/steamapps/common/Devour" "/mnt/*/SteamLibrary/steamapps/common/Devour"
"C:/Program Files (x86)/Steam/steamapps/common/Devour"
) )
for c in "${cands[@]}"; do for c in "${cands[@]}"; do for e in $c; do [[ -f "$e/DEVOUR.exe" ]] && { echo "$e"; return 0; }; done; done
for e in $c; do [[ -f "$e/DEVOUR.exe" ]] && { echo "$e"; return 0; }; done
done
# parse libraryfolders.vdf
local vdf="$HOME/.local/share/Steam/steamapps/libraryfolders.vdf" local vdf="$HOME/.local/share/Steam/steamapps/libraryfolders.vdf"
if [[ -f "$vdf" ]]; then 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
grep -o '"path"[[:space:]]*"[^"]*"' "$vdf" | cut -d'"' -f4 | while read -r p; do
if [[ -f "$p/steamapps/common/Devour/DEVOUR.exe" ]]; then echo "$p/steamapps/common/Devour"; return 0; fi
done
fi
return 1 return 1
} }
if [[ -z "$GAME_DIR" ]]; then while [[ $# -gt 0 ]]; do
GAME_DIR="$(detect_game_dir || true)" case "$1" in
fi --install) MODE="install"; shift;;
if [[ -z "$GAME_DIR" || ! -f "$GAME_DIR/DEVOUR.exe" ]]; then --uninstall) MODE="uninstall"; shift;;
echo "[!] Could not find Devour install. Use --game-dir /path/to/Devour" --update|--repair) MODE="update"; shift;;
exit 1 --tag) TAG="$2"; shift 2;;
fi --game-dir) GAME_DIR="$2"; shift 2;;
echo "[*] Game dir: $GAME_DIR" --repo) REPO="$2"; shift 2;;
*) echo "Unknown arg $1 (try --install|--uninstall|--update)"; exit 1;;
esac
done
if [[ $UNINSTALL -eq 1 ]]; then if [[ -z "$GAME_DIR" ]]; then GAME_DIR="$(detect_game_dir || true)"; fi
echo "[*] Uninstalling..." 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
rm -f "$GAME_DIR/winhttp.dll" "$GAME_DIR/DevourUnlocker.dll" "$GAME_DIR/DevourUnlocker.json" OS=$(detect_os)
if [[ -f "$GAME_DIR/winhttp.dll.bak" ]]; then mv "$GAME_DIR/winhttp.dll.bak" "$GAME_DIR/winhttp.dll"; echo "[*] Restored original winhttp.dll"; fi echo "[*] OS: $OS Game dir: $GAME_DIR Mode: $MODE Repo: $REPO Tag: $TAG"
echo "[*] Clearing Proton launch override hint:"
echo " Remove from Steam -> Devour -> Properties -> Launch Options: WINEDLLOVERRIDES=\"winhttp=n,b %command%\"" # --- 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 exit 0
fi fi
# Resolve tag -> download URL # --- UPDATE / REPAIR: redownload + verify GameAssembly hasn't shifted ---
if [[ "$TAG" == "latest" ]]; then if [[ "$MODE" == "update" ]]; then
API="https://api.github.com/repos/$REPO/releases/latest" echo "[*] Repair: checking GameAssembly hash + metadata offsets..."
TAG=$(curl -s "$API" | grep -o '"tag_name"[[:space:]]*:[[:space:]]*"[^"]*"' | cut -d'"' -f4) GA="$GAME_DIR/GameAssembly.dll"
[[ -z "$TAG" ]] && { echo "[!] Could not resolve latest tag"; exit 1; } 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 fi
echo "[*] Installing $REPO $TAG"
ZIP_URL="https://github.com/$REPO/releases/download/$TAG/DevourUnlocker_winhttp_$TAG.zip" # --- INSTALL ---
TMP="$(mktemp -d)" if [[ "$TAG" == "latest" ]]; then
echo "[*] Downloading $ZIP_URL" # Forgejo (git.mithraic.cloud) uses /api/v1/repos/:owner/:repo/releases/latest
if ! curl -L -o "$TMP/pkg.zip" "$ZIP_URL"; then API="https://git.mithraic.cloud/api/v1/repos/$REPO/releases/latest"
echo "[!] Download failed — check REPO/TAG or build a release with that name" if curl -s "$API" | grep -q "tag_name"; then
echo " Fallback: build locally with cmake --build build" TAG=$(curl -s "$API" | grep -o '"tag_name"[[:space:]]*:[[:space:]]*"[^"]*"' | cut -d'"' -f4)
exit 1 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 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 unzip -o "$TMP/pkg.zip" -d "$TMP" >/dev/null
# Backup existing winhttp if not ours # Backup existing version/winhttp if not ours (check for DevourUnlocker string)
if [[ -f "$GAME_DIR/winhttp.dll" ]]; then for dll in version.dll winhttp.dll; do
if ! grep -q "DevourUnlocker" "$GAME_DIR/winhttp.dll" 2>/dev/null; then if [[ -f "$GAME_DIR/$dll" ]]; then
cp "$GAME_DIR/winhttp.dll" "$GAME_DIR/winhttp.dll.bak" && echo "[*] Backed up existing winhttp.dll -> winhttp.dll.bak" 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 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 fi
cp -v "$TMP/winhttp.dll" "$GAME_DIR/winhttp.dll" # Patch Steam Cloud collectables.json via python helper (uses proper game calls: HasCollectedAll* checks)
cp -v "$TMP/DevourUnlocker.dll" "$GAME_DIR/DevourUnlocker.dll" 2>/dev/null || true echo "[*] Patching Steam Cloud collectables.json (proper game call: HasCollectedAllBarbedWires etc)..."
# Keep user's json if exists 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"
[[ -f "$GAME_DIR/DevourUnlocker.json" ]] || cp -v "$TMP/DevourUnlocker.json" "$GAME_DIR/DevourUnlocker.json" 2>/dev/null || echo '{"unlockRobes":true,"unlockCharacters":true}' > "$GAME_DIR/DevourUnlocker.json"
echo "[*] Installed to $GAME_DIR" echo "[*] Installed to $GAME_DIR"
echo "[*] For Proton (CachyOS): set Steam -> Devour -> Properties -> Launch Options to:" if [[ "$OS" == "linux" ]]; then
echo " WINEDLLOVERRIDES=\"winhttp=n,b\" %command%" 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 "[*] Then launch Devour, press F6 for GUI (selective unlock), F5 to unload." echo "[*] Or run: bash $(dirname "$0")/launch.sh (auto sets WINEDLLOVERRIDES and launches via Proton)"
echo "[*] Multiplayer: safe for friend lobbies (client visual, no VAC/EAC)." 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" rm -rf "$TMP"

View file

@ -0,0 +1,18 @@
#ifdef _WIN32
#include <windows.h>
static HMODULE g_real = nullptr;
static HMODULE getReal() {
if (g_real) return g_real;
g_real = LoadLibraryW(L"C:\\Windows\\System32\\version.dll");
if (!g_real) g_real = LoadLibraryW(L"C:\\Windows\\SysWOW64\\version.dll");
if (!g_real) g_real = LoadLibraryA("version.dll");
return g_real;
}
#define GET(name) ((decltype(&name))GetProcAddress(getReal(), #name))
extern "C" __declspec(dllexport) BOOL WINAPI GetFileVersionInfoA(LPCSTR a0, DWORD a1, DWORD a2, LPVOID a3){ auto p=GET(GetFileVersionInfoA); if(!p) return FALSE; return p(a0,a1,a2,a3); }
extern "C" __declspec(dllexport) BOOL WINAPI GetFileVersionInfoW(LPCWSTR a0, DWORD a1, DWORD a2, LPVOID a3){ auto p=GET(GetFileVersionInfoW); if(!p) return FALSE; return p(a0,a1,a2,a3); }
extern "C" __declspec(dllexport) DWORD WINAPI GetFileVersionInfoSizeA(LPCSTR a0, LPDWORD a1){ auto p=GET(GetFileVersionInfoSizeA); if(!p) return 0; return p(a0,a1); }
extern "C" __declspec(dllexport) DWORD WINAPI GetFileVersionInfoSizeW(LPCWSTR a0, LPDWORD a1){ auto p=GET(GetFileVersionInfoSizeW); if(!p) return 0; return p(a0,a1); }
extern "C" __declspec(dllexport) BOOL WINAPI VerQueryValueA(LPCVOID a0, LPCSTR a1, LPVOID* a2, PUINT a3){ auto p=GET(VerQueryValueA); if(!p) return FALSE; return p(a0,a1,a2,a3); }
extern "C" __declspec(dllexport) BOOL WINAPI VerQueryValueW(LPCVOID a0, LPCWSTR a1, LPVOID* a2, PUINT a3){ auto p=GET(VerQueryValueW); if(!p) return FALSE; return p(a0,a1,a2,a3); }
#endif

39
tools/check_offsets.py Normal file
View file

@ -0,0 +1,39 @@
#!/usr/bin/env python3
"""
Check if GameAssembly.dll unlock offsets have shifted since last install.
Uses the same logic as src/il2cpp_resolver.cpp: tries to resolve IsRobeUnlocked etc via
il2cpp_class_from_name + hasCollectedAll, and also checks collectables.json full sets.
If GameAssembly sha1 differs from DevourUnlocker.json:gameAssemblySha, warns to --repair.
"""
import pathlib, hashlib, json, sys, re
def sha1(p): return hashlib.sha1(open(p,'rb').read()).hexdigest()
def check(game_dir):
ga = pathlib.Path(game_dir) / "GameAssembly.dll"
gm = pathlib.Path(game_dir) / "DEVOUR_Data/il2cpp_data/Metadata/global-metadata.dat"
dj = pathlib.Path(game_dir) / "DevourUnlocker.json"
print(f"GameAssembly: {ga} exists={ga.exists()} sha1={sha1(ga) if ga.exists() else 'missing'}")
if gm.exists():
data=open(gm,'rb').read()
for needle in [b'IsRobeUnlocked', b'IsCharacterUnlocked', b'UpdateLockedPets', b'HasPerk']:
print(f" {needle.decode()} in global-metadata: {'FOUND' if needle in data else 'NOT FOUND'} at {data.find(needle) if needle in data else -1}")
if dj.exists():
j=json.loads(dj.read_text())
print(f"DevourUnlocker.json gameAssemblySha={j.get('gameAssemblySha','')}")
if ga.exists():
cur=sha1(ga)
if j.get('gameAssemblySha') and j['gameAssemblySha']!=cur:
print(f"[!] GameAssembly changed since last install (was {j['gameAssemblySha'][:8]} now {cur[:8]}) — run: bash scripts/install.sh --repair or .\\scripts\\install.ps1 -Update")
return 1
else:
print("[*] GameAssembly unchanged since last install")
# Also check collectables.json for all users
for p in pathlib.Path.home().glob(".local/share/Steam/userdata/*/1274570/remote/collectables.json"):
try:
j=json.loads(p.read_text())
print(f"collectables {p.parent.parent.name}: BarbedWires {j.get('BarbedWires')} ({len(j.get('BarbedWires',[]))})")
except: pass
return 0
if __name__=="__main__":
gd = sys.argv[1] if len(sys.argv)>1 else "/home/mithral/.local/share/Steam/steamapps/common/Devour"
sys.exit(check(gd))