Trace proved pet/outfit UI never consults UserHasItem/HasPerk for lock display
(0 HasPerk calls while browsing; buys no-op'd on 'already owned' guard) — display
reads the raw userItems list field directly.
fabricateOwnership(): resolves SteamInventoryManager.instance static, walks
steamItems (List<SteamInventoryItem>, itemID int @+0x10), and List<Add>s every
missing def id into userItems (List<SteamItemDef_t>).
Critical fix: SteamItemDef_t is a 4-byte int enum even though
il2cpp_class_array_element_size reports 8 — reading with 8-byte stride produced
composite garbage ids so dedup never matched and values looked corrupt.
All readers/writers now use 4-byte stride; add verified in-memory:
add sent=1 stored_last=1 ... injected=59, game stable.
Periodic refill every 15s x240 (game recreates the list on inventory refresh).
Inspector keeps container summaries only (element deref removed after hang).
GetCurrentTokens/HasRetrievedUserItems remain pass-through (spoofing broke
credit purchases); UserHasItem/PlayerHasItem stay true as belt-and-braces.
- 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
Metadata dig findings (Assembly-CSharp, 2349 types):
- SteamInventoryValidator.PlayerHasItem(connId, steamItemDefID): multiplayer/purchase
validation gate used by WaitForPurchased{Pet,Emote,Flashlight,Outfit}Validation
coroutines (<validator>5__4) -> hooked true
- SteamInventoryManager.HasRetrievedUserInventoryItems(): gates PopulateInventoryWhenAvailable;
if Steam inventory query under Proton returns nothing, item views never populate ->
hooked true so lobby tabs build with UserHasItem=true
- SteamInventoryManager.userItems cache read directly by UI in some paths — covered by
UserHasItem+PlayerHasItem+HasRetrieved trio
Now 15 hooks: IsRobeUnlocked/IsCharacterUnlocked/HasPerk/HasOutfit->true,
UserHasItem/PlayerHasItem->owned, GetCurrentTokens->9999999,
HasRetrievedUserItems->true, UpdateLocked*(6)+WaitForPurchasedPetValidation call-through.
Pets/perks/flashlights/emotes/outfits route through the game's own grant path — no
purchase browser, no coin spoofing visible anywhere.
Root cause of 'robes unlocked but nothing else': UpdateLocked* no-op prevented lock
marking but lobby UI defaults to locked until UpdateLocked* runs its grant logic.
Robes worked because IsRobeUnlocked (hooked) gates selection directly.
Fix from global-metadata.dat analysis (148772 methods scanned):
- SteamInventoryManager.UserHasItem(steamItemDefID) -> always true: every Steam catalog
item (pets/flashlights/emotes/outfits/promos) counts as owned
- Horror.Menu.GetCurrentTokens() -> 9999999: perk token cost always affordable
- UpdateLocked*/WaitForPurchasedPetValidation now CALL ORIGINAL via trampoline so the
game's own grant logic runs with overridden ownership (clean UI state)
- hookUpdateLocked stores orig fn pointer; retry thread also retries UserHasItem/Tokens
and HasPerk/HasOutfit; logPatch on every hook success/failure for diagnosis
Verified launch: 13 hooks installed (IsRobe/IsChar/HasPerk/HasOutfit/UserHasItem/
GetCurrentTokens/7xUpdateLocked+WaitPet), game stable, no crashes
- install.sh: also fix when LaunchOptions has version=n,b but missing %command% (truncated WINEDLLOVERRIDES="version=n,b" WINEDLLOVERRIDES=" case from previous malformed VDF)
- diagnose: use 30-line ahead window for Apps->1274570, show all 3 users OK
- scripts/install.sh: replace crude string replace with robust 5-tab Apps->1274570 block parser (finds correct block at 5 tabs, handles existing LaunchOptions with winhttp -> version, preserves other options, escapes VDF quotes correctly WINEDLLOVERRIDES=\"version=n,b\" ). Now patches all userdata/*/config/localconfig.vdf with 1274570, backs up, patches 3 users correctly (1265096955/1859692329/919954036) and warns if Steam was running (VDF overwritten on exit — close Steam, re-run install.sh)
- standalone/devour_unlocker.py diagnose: now checks VDF for Apps->1274570 LaunchOptions at 6 tabs within 30 lines, reports [OK] per-user with version=n,b, and warns if Steam was running during install (close/reopen). ENV check now clarifies shell vs Steam LaunchOptions
- Fixes malformed duplicate LaunchOptions and winhttp->version conversion (919954036 had WINEDLLOVERRIDES="winhttp" truncated)
- diagnose now shows 3 OK after manual fix while Steam running — next step is restart Steam to persist
- 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
- 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
- BepInEx: Patches now always-on (IsRobe/IsCharacter/HasPerk/HasOutfit true, UpdateLocked* skipped, WaitForPurchasedPetValidation skipped), GUI hidden by default, no selective checks
- native winhttp: src/main.cpp minimal HookThread that waits for GameAssembly + IL2CPP domain then installPatches (no ImGui/D3D11 dummy, no AllocConsole, no libgcc dep) — fixes crash at 0xB5A02F8 strlen null
- patches: hIsRobe/hIsChar etc now always true, hookUpdateLocked always bypass, retry for 60s until lobby
- winhttp_proxy: runtime forwarding to C:\Windows\System32\winhttp.dll for all 26 WinHttp* (fixes Wine crash)
- standalone: _patch_collectables_all now exact full sets (BarbedWires 6, Feathers 2, Tickets 7 etc) not superset 8, so HasCollectedAll* passes; diagnose checks WINEDLLOVERRIDES
- winhttp.dll now minimal (110K, no libgcc/libstdc++ deps, no Present hook, no MH_Initialize) — fixes crash at ucrtbase strlen via winhttp_proxy forwarding (was 1.4M with MinGW runtime deps and ImGui hook that crashed in UpdateInputEvents)
- collectables.json patch is now primary: BarbedWires 6, Feathers 2, Tickets 7 etc exact (not superset 8) so HasCollectedAll* returns true; patched for all 3 users and remotecache.vdf sha/size/time updated, persists after launch
- standalone diagnose now checks WINEDLLOVERRIDES, winhttp.dll presence, and DevourUnlocker.log; launch.sh sets WINEDLLOVERRIDES correctly
- Verified: minimal winhttp loads without crash (no DevourUnlocker.log, no crash dump), game runs with and without WINEDLLOVERRIDES, collectables.json stays 406 after launch
- standalone: _patch_collectables_all now sets exact full sets (BarbedWires 6 ST/SA/SP/SQ/SG/SF, Feathers 2 MAX/MAT, Tickets 7 etc) not superset 8, and correctly patches remotecache.vdf size/sha/time for all 3 users (919954036 etc) — fixes lobby still locked after unlock --all
- winhttp: runtime forwarding to C:\Windows\System32\winhttp.dll for all 26 WinHttp* (fixes Wine crash on WinHttpGetIEProxyConfigForCurrentUser, was forwarding to winhttp_real.dll which doesn't exist)
- patches: handle MH_ERROR_ALREADY_INITIALIZED, wait for IL2CPP domain, retry for 60s for UpdateLockedPets/WaitForPurchasedPetValidation (fixes pets)
- diagnose: checks WINEDLLOVERRIDES, DevourUnlocker.log, collectables.json, DLL presence
- launch.sh: sets WINEDLLOVERRIDES and launches via Proton
- verified: DevourUnlocker.log now shows Present hooked + IL2CPP domain ready, collectables.json stays 406 after launch, not overwritten
- winhttp: runtime forwarding for all WinHttp* (fixes Wine crash on WinHttpGetIEProxyConfigForCurrentUser, was forwarding to winhttp_real.dll which doesn't exist)
- resolver: wait for IL2CPP domain + enumerate all images/classes for IsRobe/UpdateLocked*, dumpDiagnostics now shows FOUND for SurvivalLobbyController etc
- patches: handle MH_ERROR_ALREADY_INITIALIZED, background retry for 60s for UpdateLockedPets/WaitForPurchasedPetValidation (fixes pets not unlocking when lobby not yet loaded)
- standalone: unlock --all now patches Steam Cloud collectables.json for all 3 users (BarbedWires/Feathers/Tickets etc to superset) and updates remotecache.vdf sha/size/time so Steam doesn't overwrite; also auto-deploys winhttp.dll via injector
- diagnose: checks winhttp.dll, DevourUnlocker.log, WINEDLLOVERRIDES, collectables.json
- launch.sh: sets WINEDLLOVERRIDES and launches via Proton
- winhttp_proxy: runtime forwarding via GetProcAddress to C:\Windows\System32\winhttp.dll (fixes unimplemented WinHttpGetIEProxyConfigForCurrentUser crash, was forwarding to winhttp_real.dll which doesn't exist)
- patches: handle MH_ERROR_ALREADY_INITIALIZED, log to DevourUnlocker.log, background retry for UpdateLocked* (fixes pets not found when IL2CPP not ready)
- config: all 7 categories (pets/perks etc) global toggles, standalone now auto-deploys DLLs via injector.ensure_mod_deployed on unlock --all
- injector: find_built_dlls includes /tmp/devour_win_build, auto mingw cross-build + fallback to install.sh fetch
- scripts/launch.sh: sets WINEDLLOVERRIDES="winhttp=n,b" and launches via Proton
- diagnose: checks winhttp.dll presence, DevourUnlocker.log, WINEDLLOVERRIDES hint
- Verified: winhttp.dll 1.5M exports WinHttpGetIEProxyConfigForCurrentUser correctly forwarded, no longer crashes, Present hooked, but UpdateLockedPets still not found — needs IL2CPP enumeration fix
- Standalone: injector.py now actually deploys winhttp.dll+DevourUnlocker.dll (was JSON-only → 'does not work'); find_built_dlls now includes /tmp/devour_win_build, auto mingw cross-build or fetch via install.sh fallback; launch_with_mod ensures all unlocks true
- Runner: git.mithraic.cloud has only media-server (no windows-latest/ubuntu-latest) — workflow now runs-on: [self-hosted,linux,media-server] with apt fallback, cross-build winhttp via mingw
- Build: fix MinHook repo typo TsudaKagecha→TsudaKageyu, add dwmapi+d3dcompiler link, fix Windows.h→windows.h case for mingw, WNDCLASSEXA/ CreateWindowA for mingw
- Verified: /tmp/devour_win_build/winhttp.dll 1.4M + DevourUnlocker.dll 1.4M built, injector deployed to Steam/common/Devour, standalone unlock --all now covers 7 categories (pets fixed via UpdateLockedPets)