v2.1.0: Steam-proof loading — per-prefix Wine registry DllOverrides
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).
This commit is contained in:
parent
7632978bf3
commit
f478a731d9
3 changed files with 83 additions and 8 deletions
|
|
@ -144,6 +144,13 @@ if [[ "$OS" == "linux" ]]; then
|
||||||
echo "[!] Steam is RUNNING — it will overwrite localconfig.vdf on exit and drop our LaunchOptions."
|
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"
|
echo " Close Steam first: steam -shutdown then re-run install.sh"
|
||||||
fi
|
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
|
if [[ -f "$(dirname "$0")/vdf_patch.py" ]]; then
|
||||||
python3 "$(dirname "$0")/vdf_patch.py" 2>&1 | head -n 20 && patched_any=1
|
python3 "$(dirname "$0")/vdf_patch.py" 2>&1 | head -n 20 && patched_any=1
|
||||||
else
|
else
|
||||||
|
|
|
||||||
66
scripts/prefix_override.py
Normal file
66
scripts/prefix_override.py
Normal file
|
|
@ -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)
|
||||||
|
|
@ -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.
|
// 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.
|
// 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 bool __fastcall hUserHasItem(void* thiz, unsigned long long itemDef, void* method) {
|
||||||
static thread_local int depth = 0;
|
if (oUserHasItem) return oUserHasItem(thiz, itemDef, method);
|
||||||
if (depth < 2) { depth++; char b[128]; snprintf(b,sizeof(b),"[trace] UserHasItem(def=%llu) -> true", itemDef); logPatch(b); depth--; }
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
// Multiplayer validator: every player counts as owning every item (lobby validation path)
|
// 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) {
|
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;
|
return true;
|
||||||
}
|
}
|
||||||
// Inventory-retrieved flag: pass through — forcing true with an empty userItems made some
|
// 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);
|
if (oHasRetrieved) return oHasRetrieved(thiz, method);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
// Perks bought with Tokens: PASS THROUGH — spoofed 9,999,999 broke the token purchase modal.
|
// Perks bought with Tokens: huge balance — perk modal deducts locally, so every perk
|
||||||
static int __fastcall hGetCurrentTokens(void* thiz, void* method) {
|
// is instantly affordable. Store items don't use tokens (they route through Steam).
|
||||||
if (oGetTokens) return oGetTokens(thiz, method);
|
static int __fastcall hGetCurrentTokens(void* thiz, void* method) { return 9999999; }
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool hookUpdateLocked(void* addr, const char* name, bool shouldUnlock, void (__fastcall *hook)(void*, void*), UpdateLockedFn* origOut) {
|
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; }
|
if (!addr) { logPatch((std::string("[DevourUnlocker] ")+name+" not found").c_str()); return false; }
|
||||||
|
|
@ -177,6 +174,11 @@ bool installPatches() {
|
||||||
char buf[1024];
|
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);
|
snprintf(buf, sizeof(buf), "[DevourUnlocker] Resolved IsRobe=%p IsChar=%p HasPerk=%p HasOutfit=%p", robe.address, ch.address, hasPerk.address, hasOutfit.address);
|
||||||
logPatch(buf);
|
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);
|
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);
|
logPatch(buf);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue