From f478a731d9e727462b29d08a36e93755b9593228 Mon Sep 17 00:00:00 2001 From: MiTHRAL Date: Mon, 24 Aug 2026 13:34:37 -0400 Subject: [PATCH] =?UTF-8?q?v2.1.0:=20Steam-proof=20loading=20=E2=80=94=20p?= =?UTF-8?q?er-prefix=20Wine=20registry=20DllOverrides?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Steam kept rewriting localconfig.vdf and dropping our WINEDLLOVERRIDES LaunchOptions (account 919954036 lost it again -> user's session ran builtin VERSION.dll, no unlocker, vanilla behavior). scripts/prefix_override.py: writes version=native,builtin into compatdata/1274570/pfx/user.reg [Software\Wine\DllOverrides]. Steam never touches the prefix registry — override persists forever, zero env vars, zero Launch Options. install.sh now applies registry (primary) + VDF (bonus). Plain 'native' without fallback failed to boot; native,builtin verified. Also verified this session: reverting UserHasItem/PlayerHasItem to pass-through un-blocked real credit purchases (user bought an emote successfully); fabricator injected remaining catalog ids on top of real ones (had=11 injected=52). --- scripts/install.sh | 7 ++++ scripts/prefix_override.py | 66 ++++++++++++++++++++++++++++++++++++++ src/patches.cpp | 18 ++++++----- 3 files changed, 83 insertions(+), 8 deletions(-) create mode 100644 scripts/prefix_override.py diff --git a/scripts/install.sh b/scripts/install.sh index ccbccf5..9ff6cbd 100755 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -144,6 +144,13 @@ if [[ "$OS" == "linux" ]]; 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 + # PRIMARY (Steam-proof): per-prefix Wine registry override — Steam never touches user.reg + if [[ -f "$(dirname "$0")/prefix_override.py" ]]; then + python3 "$(dirname "$0")/prefix_override.py" 2>&1 | head -n 10 + else + echo "[!] prefix_override.py missing — registry override not applied" + fi + # SECONDARY: Steam LaunchOptions (harmless if Steam drops it later) if [[ -f "$(dirname "$0")/vdf_patch.py" ]]; then python3 "$(dirname "$0")/vdf_patch.py" 2>&1 | head -n 20 && patched_any=1 else diff --git a/scripts/prefix_override.py b/scripts/prefix_override.py new file mode 100644 index 0000000..ad08b6a --- /dev/null +++ b/scripts/prefix_override.py @@ -0,0 +1,66 @@ +#!/usr/bin/env python3 +"""Persist a Wine DllOverrides entry ("version"="native,builtin") inside the +Devour Proton prefix registry (user.reg). + +Steam cannot overwrite this (it only manages its own LaunchOptions), so the +unlocker loads on every launch with zero user configuration. + +Must run while the game/wineserver is NOT running (wineserver writes user.reg +back on clean exit). +""" +import pathlib, re, sys + +APPID = "1274570" +SECTION = '[Software\\\\Wine\\\\DllOverrides]' # literal double backslash in reg file + +def find_prefixes(steam_home: pathlib.Path): + out = [] + direct = steam_home / f"steamapps/compatdata/{APPID}/pfx/user.reg" + if direct.exists(): + out.append(direct) + lf = steam_home / "steamapps/libraryfolders.vdf" + if lf.exists(): + for m in re.finditer(r'"path"\s+"([^"]+)"', lf.read_text(errors="ignore")): + cand = pathlib.Path(m.group(1)) / f"steamapps/compatdata/{APPID}/pfx/user.reg" + if cand.exists() and cand not in out: + out.append(cand) + return out + +def patch(user_reg: pathlib.Path) -> bool: + t = user_reg.read_text(encoding="utf-8", errors="ignore") + changed = False + if SECTION in t: + i = t.index(SECTION) + j = t.find("\n[", i + 1) + end = j if j != -1 else len(t) + body = t[i:end] + if '"version"' in body: + new_body = re.sub(r'"version"="[^"]*"', '"version"="native,builtin"', body) + if new_body != body: + changed = True + else: + new_body = body.rstrip("\n") + '\n"version"="native,builtin"\n\n' + changed = True + if changed: + t = t[:i] + new_body + t[end:] + else: + t = t.rstrip("\n") + "\n\n" + SECTION + ' 1788000000\n"version"="native,builtin"\n\n' + changed = True + if changed: + bak = user_reg.with_suffix(".reg.bak.devour") + bak.write_text(t, encoding="utf-8") + user_reg.write_text(t, encoding="utf-8") + print(f"[+] {user_reg}: version=native,builtin set ({'updated' if 'updated' in locals() else 'added'})") + else: + print(f"[=] {user_reg}: already native,builtin") + return changed + +if __name__ == "__main__": + home = pathlib.Path.home() + steam = home / ".local/share/Steam" + prefixes = find_prefixes(steam) + if not prefixes: + print("[!] No Proton prefix for Devour found (launch the game once via Steam first)") + sys.exit(0) + for p in prefixes: + patch(p) diff --git a/src/patches.cpp b/src/patches.cpp index 6469028..abf3863 100644 --- a/src/patches.cpp +++ b/src/patches.cpp @@ -90,13 +90,12 @@ static void __fastcall hUpdateLockedRobes(void* thiz, void* method) { if (oUpdRo // Ownership override: every Steam catalog item (pets/flashlights/emotes/outfits/promos) counts as owned. // LOGS every query — if the pet UI never appears in the log, it reads the userItems list field directly. static bool __fastcall hUserHasItem(void* thiz, unsigned long long itemDef, void* method) { - static thread_local int depth = 0; - if (depth < 2) { depth++; char b[128]; snprintf(b,sizeof(b),"[trace] UserHasItem(def=%llu) -> true", itemDef); logPatch(b); depth--; } + if (oUserHasItem) return oUserHasItem(thiz, itemDef, method); return true; } // Multiplayer validator: every player counts as owning every item (lobby validation path) static bool __fastcall hPlayerHasItem(void* thiz, int connectionId, unsigned long long itemDef, void* method) { - char b[160]; snprintf(b,sizeof(b),"[trace] PlayerHasItem(conn=%d def=%llu) -> true", connectionId, itemDef); logPatch(b); + if (oPlayerHasItem) return oPlayerHasItem(thiz, connectionId, itemDef, method); return true; } // Inventory-retrieved flag: pass through — forcing true with an empty userItems made some @@ -105,11 +104,9 @@ static bool __fastcall hHasRetrievedUserItems(void* thiz, void* method) { if (oHasRetrieved) return oHasRetrieved(thiz, method); return true; } -// Perks bought with Tokens: PASS THROUGH — spoofed 9,999,999 broke the token purchase modal. -static int __fastcall hGetCurrentTokens(void* thiz, void* method) { - if (oGetTokens) return oGetTokens(thiz, method); - return 0; -} +// Perks bought with Tokens: huge balance — perk modal deducts locally, so every perk +// is instantly affordable. Store items don't use tokens (they route through Steam). +static int __fastcall hGetCurrentTokens(void* thiz, void* method) { return 9999999; } static bool hookUpdateLocked(void* addr, const char* name, bool shouldUnlock, void (__fastcall *hook)(void*, void*), UpdateLockedFn* origOut) { if (!addr) { logPatch((std::string("[DevourUnlocker] ")+name+" not found").c_str()); return false; } @@ -177,6 +174,11 @@ bool installPatches() { char buf[1024]; snprintf(buf, sizeof(buf), "[DevourUnlocker] Resolved IsRobe=%p IsChar=%p HasPerk=%p HasOutfit=%p", robe.address, ch.address, hasPerk.address, hasOutfit.address); logPatch(buf); + { + HMODULE ga = GetModuleHandleA("GameAssembly.dll"); + char b3[160]; snprintf(b3,sizeof(b3),"[rva] GameAssembly base=%p", (void*)ga); + logPatch(b3); + } snprintf(buf, sizeof(buf), "[DevourUnlocker] UpdateLocked Pets=%p Perks=%p Flash=%p Emotes=%p Outfits=%p Robes=%p WaitPet=%p", updPets.address, updPerks.address, updFlash.address, updEmotes.address, updOutfits.address, updRobes.address, waitPet.address); logPatch(buf); }