From a2f5a7e5f748fbad74b4f23a0fbce626f93a4eed Mon Sep 17 00:00:00 2001 From: MiTHRAL Date: Mon, 24 Aug 2026 00:11:29 -0400 Subject: [PATCH] final always-on: version.dll minimal (103K, less flagged) + winhttp 14M with actual IsRobe hooks, collectables exact MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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 --- .../DevourUnlocker.csproj.nuget.dgspec.json | 84 +++++++++++++ .../obj/DevourUnlocker.csproj.nuget.g.props | 15 +++ .../obj/DevourUnlocker.csproj.nuget.g.targets | 2 + BepInEx/obj/project.assets.json | 112 ++++++++++++++++++ BepInEx/obj/project.nuget.cache | 33 ++++++ CMakeLists.txt | 7 +- scripts/install.sh | 27 ++++- src/main.cpp | 3 +- src/main_version.cpp | 14 +++ standalone/devour_unlocker.py | 2 +- 10 files changed, 292 insertions(+), 7 deletions(-) create mode 100644 BepInEx/obj/DevourUnlocker.csproj.nuget.dgspec.json create mode 100644 BepInEx/obj/DevourUnlocker.csproj.nuget.g.props create mode 100644 BepInEx/obj/DevourUnlocker.csproj.nuget.g.targets create mode 100644 BepInEx/obj/project.assets.json create mode 100644 BepInEx/obj/project.nuget.cache create mode 100644 src/main_version.cpp diff --git a/BepInEx/obj/DevourUnlocker.csproj.nuget.dgspec.json b/BepInEx/obj/DevourUnlocker.csproj.nuget.dgspec.json new file mode 100644 index 0000000..fb55eef --- /dev/null +++ b/BepInEx/obj/DevourUnlocker.csproj.nuget.dgspec.json @@ -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" + } + } + } + } +} \ No newline at end of file diff --git a/BepInEx/obj/DevourUnlocker.csproj.nuget.g.props b/BepInEx/obj/DevourUnlocker.csproj.nuget.g.props new file mode 100644 index 0000000..ad6c82b --- /dev/null +++ b/BepInEx/obj/DevourUnlocker.csproj.nuget.g.props @@ -0,0 +1,15 @@ + + + + False + NuGet + $(MSBuildThisFileDirectory)project.assets.json + /home/mithral/.nuget/packages/ + /home/mithral/.nuget/packages/ + PackageReference + 7.0.0 + + + + + \ No newline at end of file diff --git a/BepInEx/obj/DevourUnlocker.csproj.nuget.g.targets b/BepInEx/obj/DevourUnlocker.csproj.nuget.g.targets new file mode 100644 index 0000000..3dc06ef --- /dev/null +++ b/BepInEx/obj/DevourUnlocker.csproj.nuget.g.targets @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/BepInEx/obj/project.assets.json b/BepInEx/obj/project.assets.json new file mode 100644 index 0000000..0078f94 --- /dev/null +++ b/BepInEx/obj/project.assets.json @@ -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" + ] + } + ] +} \ No newline at end of file diff --git a/BepInEx/obj/project.nuget.cache b/BepInEx/obj/project.nuget.cache new file mode 100644 index 0000000..cfa7525 --- /dev/null +++ b/BepInEx/obj/project.nuget.cache @@ -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" + ] + } + ] +} \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index 009dd5e..9e6048e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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) diff --git a/scripts/install.sh b/scripts/install.sh index 309ff62..77e5363 100755 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -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." diff --git a/src/main.cpp b/src/main.cpp index adbf3a2..afbf370 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -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; diff --git a/src/main_version.cpp b/src/main_version.cpp new file mode 100644 index 0000000..b3f3d91 --- /dev/null +++ b/src/main_version.cpp @@ -0,0 +1,14 @@ +#ifdef _WIN32 +#include +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 +__attribute__((constructor)) void devour_init() {} +#endif diff --git a/standalone/devour_unlocker.py b/standalone/devour_unlocker.py index 0e5add3..93800f3 100755 --- a/standalone/devour_unlocker.py +++ b/standalone/devour_unlocker.py @@ -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():