final always-on: version.dll minimal (103K, less flagged) + winhttp 14M with actual IsRobe hooks, collectables exact
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: minimal DllMain only (no ImGui, no libgcc dep, 103K vs 1.4M) — less flagged by anti-cheat, loads without WINEDLLOVERRIDES on Windows, via version=n,b on Proton
- winhttp.dll: full always-on hooks (IsRobe/IsCharacter/HasPerk/UpdateLockedPets etc) with 60s retry until lobby, now correctly forwards WinHttp* to C:\Windows\System32\winhttp.dll (was winhttp_real crash)
- standalone: unlock --all now patches collectables.json to exact 6/2/7 (BarbedWires 6 etc) not 8 superset, so HasCollectedAll* passes; also updates remotecache.vdf sha/size
- scripts: install.sh now prefers version.dll, auto-sets WINEDLLOVERRIDES for Proton, handles --update redownload + GameAssembly sha check via tools/check_offsets.py
- verify: version.dll via "version=n,b" now loads without crash (no DevourUnlocker.log, no crash dump), collectables.json stays 406 after lobby load
This commit is contained in:
MiTHRAL 2026-08-24 00:11:29 -04:00
parent 6329350e90
commit a2f5a7e5f7
10 changed files with 292 additions and 7 deletions

View file

@ -0,0 +1,84 @@
{
"format": 1,
"restore": {
"/home/mithral/Documents/Devour Unlocker FORKED/DevourUnlocker/BepInEx/DevourUnlocker.csproj": {}
},
"projects": {
"/home/mithral/Documents/Devour Unlocker FORKED/DevourUnlocker/BepInEx/DevourUnlocker.csproj": {
"version": "2.0.0",
"restore": {
"projectUniqueName": "/home/mithral/Documents/Devour Unlocker FORKED/DevourUnlocker/BepInEx/DevourUnlocker.csproj",
"projectName": "DevourUnlocker",
"projectPath": "/home/mithral/Documents/Devour Unlocker FORKED/DevourUnlocker/BepInEx/DevourUnlocker.csproj",
"packagesPath": "/home/mithral/.nuget/packages/",
"outputPath": "/home/mithral/Documents/Devour Unlocker FORKED/DevourUnlocker/BepInEx/obj/",
"projectStyle": "PackageReference",
"configFilePaths": [
"/home/mithral/.nuget/NuGet/NuGet.Config"
],
"originalTargetFrameworks": [
"netstandard2.1"
],
"sources": {
"https://api.nuget.org/v3/index.json": {}
},
"frameworks": {
"netstandard2.1": {
"targetAlias": "netstandard2.1",
"projectReferences": {}
}
},
"warningProperties": {
"warnAsError": [
"NU1605"
]
},
"restoreAuditProperties": {
"enableAudit": "true",
"auditLevel": "low",
"auditMode": "direct"
},
"SdkAnalysisLevel": "10.0.100"
},
"frameworks": {
"netstandard2.1": {
"targetAlias": "netstandard2.1",
"dependencies": {
"BepInEx.Analyzers": {
"suppressParent": "All",
"target": "Package",
"version": "[1.*, )"
},
"BepInEx.Core": {
"target": "Package",
"version": "[6.0.0-be.688, )"
}
},
"imports": [
"net461",
"net462",
"net47",
"net471",
"net472",
"net48",
"net481"
],
"assetTargetFallback": true,
"warn": true,
"downloadDependencies": [
{
"name": "NETStandard.Library.Ref",
"version": "[2.1.0, 2.1.0]"
}
],
"frameworkReferences": {
"NETStandard.Library": {
"privateAssets": "all"
}
},
"runtimeIdentifierGraphPath": "/usr/share/dotnet/sdk/10.0.111/RuntimeIdentifierGraph.json"
}
}
}
}
}

View file

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8" standalone="no"?>
<Project ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup Condition=" '$(ExcludeRestorePackageImports)' != 'true' ">
<RestoreSuccess Condition=" '$(RestoreSuccess)' == '' ">False</RestoreSuccess>
<RestoreTool Condition=" '$(RestoreTool)' == '' ">NuGet</RestoreTool>
<ProjectAssetsFile Condition=" '$(ProjectAssetsFile)' == '' ">$(MSBuildThisFileDirectory)project.assets.json</ProjectAssetsFile>
<NuGetPackageRoot Condition=" '$(NuGetPackageRoot)' == '' ">/home/mithral/.nuget/packages/</NuGetPackageRoot>
<NuGetPackageFolders Condition=" '$(NuGetPackageFolders)' == '' ">/home/mithral/.nuget/packages/</NuGetPackageFolders>
<NuGetProjectStyle Condition=" '$(NuGetProjectStyle)' == '' ">PackageReference</NuGetProjectStyle>
<NuGetToolVersion Condition=" '$(NuGetToolVersion)' == '' ">7.0.0</NuGetToolVersion>
</PropertyGroup>
<ItemGroup Condition=" '$(ExcludeRestorePackageImports)' != 'true' ">
<SourceRoot Include="/home/mithral/.nuget/packages/" />
</ItemGroup>
</Project>

View file

@ -0,0 +1,2 @@
<?xml version="1.0" encoding="utf-8" standalone="no"?>
<Project ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" />

View file

@ -0,0 +1,112 @@
{
"version": 3,
"targets": {
".NETStandard,Version=v2.1": {}
},
"libraries": {},
"projectFileDependencyGroups": {
".NETStandard,Version=v2.1": [
"BepInEx.Analyzers >= 1.*",
"BepInEx.Core >= 6.0.0-be.688"
]
},
"packageFolders": {
"/home/mithral/.nuget/packages/": {}
},
"project": {
"version": "2.0.0",
"restore": {
"projectUniqueName": "/home/mithral/Documents/Devour Unlocker FORKED/DevourUnlocker/BepInEx/DevourUnlocker.csproj",
"projectName": "DevourUnlocker",
"projectPath": "/home/mithral/Documents/Devour Unlocker FORKED/DevourUnlocker/BepInEx/DevourUnlocker.csproj",
"packagesPath": "/home/mithral/.nuget/packages/",
"outputPath": "/home/mithral/Documents/Devour Unlocker FORKED/DevourUnlocker/BepInEx/obj/",
"projectStyle": "PackageReference",
"configFilePaths": [
"/home/mithral/.nuget/NuGet/NuGet.Config"
],
"originalTargetFrameworks": [
"netstandard2.1"
],
"sources": {
"https://api.nuget.org/v3/index.json": {}
},
"frameworks": {
"netstandard2.1": {
"targetAlias": "netstandard2.1",
"projectReferences": {}
}
},
"warningProperties": {
"warnAsError": [
"NU1605"
]
},
"restoreAuditProperties": {
"enableAudit": "true",
"auditLevel": "low",
"auditMode": "direct"
},
"SdkAnalysisLevel": "10.0.100"
},
"frameworks": {
"netstandard2.1": {
"targetAlias": "netstandard2.1",
"dependencies": {
"BepInEx.Analyzers": {
"suppressParent": "All",
"target": "Package",
"version": "[1.*, )"
},
"BepInEx.Core": {
"target": "Package",
"version": "[6.0.0-be.688, )"
}
},
"imports": [
"net461",
"net462",
"net47",
"net471",
"net472",
"net48",
"net481"
],
"assetTargetFallback": true,
"warn": true,
"downloadDependencies": [
{
"name": "NETStandard.Library.Ref",
"version": "[2.1.0, 2.1.0]"
}
],
"frameworkReferences": {
"NETStandard.Library": {
"privateAssets": "all"
}
},
"runtimeIdentifierGraphPath": "/usr/share/dotnet/sdk/10.0.111/RuntimeIdentifierGraph.json"
}
}
},
"logs": [
{
"code": "NU1101",
"level": "Error",
"message": "Unable to find package BepInEx.Analyzers. No packages exist with this id in source(s): nuget.org",
"libraryId": "BepInEx.Analyzers",
"targetGraphs": [
".NETStandard,Version=v2.1"
]
},
{
"code": "NU1101",
"level": "Error",
"message": "Unable to find package BepInEx.Core. No packages exist with this id in source(s): nuget.org",
"libraryId": "BepInEx.Core",
"targetGraphs": [
".NETStandard,Version=v2.1"
]
}
]
}

View file

@ -0,0 +1,33 @@
{
"version": 2,
"dgSpecHash": "77xgSA5MVEU=",
"success": false,
"projectFilePath": "/home/mithral/Documents/Devour Unlocker FORKED/DevourUnlocker/BepInEx/DevourUnlocker.csproj",
"expectedPackageFiles": [
"/home/mithral/.nuget/packages/netstandard.library.ref/2.1.0/netstandard.library.ref.2.1.0.nupkg.sha512"
],
"logs": [
{
"code": "NU1101",
"level": "Error",
"message": "Unable to find package BepInEx.Analyzers. No packages exist with this id in source(s): nuget.org",
"projectPath": "/home/mithral/Documents/Devour Unlocker FORKED/DevourUnlocker/BepInEx/DevourUnlocker.csproj",
"filePath": "/home/mithral/Documents/Devour Unlocker FORKED/DevourUnlocker/BepInEx/DevourUnlocker.csproj",
"libraryId": "BepInEx.Analyzers",
"targetGraphs": [
".NETStandard,Version=v2.1"
]
},
{
"code": "NU1101",
"level": "Error",
"message": "Unable to find package BepInEx.Core. No packages exist with this id in source(s): nuget.org",
"projectPath": "/home/mithral/Documents/Devour Unlocker FORKED/DevourUnlocker/BepInEx/DevourUnlocker.csproj",
"filePath": "/home/mithral/Documents/Devour Unlocker FORKED/DevourUnlocker/BepInEx/DevourUnlocker.csproj",
"libraryId": "BepInEx.Core",
"targetGraphs": [
".NETStandard,Version=v2.1"
]
}
]
}

View file

@ -79,15 +79,16 @@ if(BUILD_WINHTTP_PROXY AND WIN32)
target_link_options(winhttp PRIVATE -static-libgcc -static-libstdc++ -Wl,-Bstatic -lwinpthread -Wl,-Bdynamic)
endif()
# version.dll is minimal — just to satisfy loader, unlock is via collectables.json (no IL2CPP hook, no crash)
add_library(version SHARED
src/platform/version_proxy.cpp
src/main.cpp
src/main_version.cpp
)
target_link_libraries(version PRIVATE devour_core)
target_link_libraries(version PRIVATE kernel32)
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)
target_link_options(version PRIVATE -static-libgcc -static-libstdc++)
endif()
add_library(DevourUnlocker SHARED src/main.cpp)

View file

@ -137,8 +137,33 @@ echo "[*] Patching Steam Cloud collectables.json (proper game call: HasCollected
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 (so user doesn't need to do it manually)
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)"
# Try to set LaunchOptions in localconfig.vdf for 1274570 to include WINEDLLOVERRIDES
for lc in "$HOME/.local/share/Steam/userdata/"*/config/localconfig.vdf; do
if [[ -f "$lc" ]] && grep -q "1274570" "$lc"; then
# Backup
cp "$lc" "$lc.bak.$(date +%s)" 2>/dev/null || true
# Use python to edit VDF (VDF is not JSON, but we can do crude sed for LaunchOptions)
python3 -c "
import pathlib, re
p=pathlib.Path('$lc')
t=p.read_text(errors='ignore')
# If LaunchOptions already contains WINEDLLOVERRIDES, leave it
if 'WINEDLLOVERRIDES' not in t:
# Find \"1274570\" block and add LaunchOptions
# VDF snippet for 1274570 is like \"1274570\" { ... \"LaunchOptions\" \"...\" }
# If LaunchOptions not present, add it
if '\"LaunchOptions\"' not in t.split('\"1274570\"')[1].split('}')[0]:
# Insert LaunchOptions after 1274570 block start
t=t.replace('\"1274570\"', '\"1274570\"\n\t\t\t\t\"LaunchOptions\"\t\t\"WINEDLLOVERRIDES=version=n,b %command%\"', 1)
open('$lc','w').write(t)
print('[*] Auto-set Steam launch options for Devour to WINEDLLOVERRIDES=\"version=n,b\" %command%')
" 2>&1 | head -n 5
break
fi
done
echo "[*] Linux/Proton: version.dll is auto-loaded beside DEVOUR.exe (if LaunchOptions not auto-set, 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."

View file

@ -12,9 +12,8 @@ static void logMin(const char* m){
}
static DWORD WINAPI HookThread(LPVOID){
logMin("[DevourUnlocker] HookThread start (always-on)");
// Wait for GameAssembly before hooking
for(int i=0;i<100;i++){ if(GetModuleHandleA("GameAssembly.dll")) break; Sleep(100); }
Sleep(2000); // give IL2CPP time to init
Sleep(2000);
bool ok = devour::installPatches();
logMin(ok ? "[DevourUnlocker] HookThread: patches installed" : "[DevourUnlocker] HookThread: patches not yet, will retry via installPatches retry thread");
return 0;

14
src/main_version.cpp Normal file
View file

@ -0,0 +1,14 @@
#ifdef _WIN32
#include <windows.h>
BOOL APIENTRY DllMain(HMODULE hMod, DWORD reason, LPVOID) {
if (reason == DLL_PROCESS_ATTACH) {
DisableThreadLibraryCalls(hMod);
// Minimal version.dll — no hooks, just to satisfy loader
// Unlock is via collectables.json (patched by standalone) which is always-on
}
return TRUE;
}
#else
#include <iostream>
__attribute__((constructor)) void devour_init() {}
#endif

View file

@ -228,7 +228,7 @@ def cmd_diagnose(args):
for c in devour_paths():
if (c / "DEVOUR.exe").exists():
gd=c; break
for dll in ["winhttp.dll","DevourUnlocker.dll","libgcc_s_seh-1.dll","libstdc++-6.dll"]:
for dll in ["version.dll","winhttp.dll","DevourUnlocker.dll","libgcc_s_seh-1.dll","libstdc++-6.dll"]:
if gd:
print(f"{dll} at {gd/dll} exists={(gd/dll).exists()}")
if (gd/dll).exists():