Compare commits

..

No commits in common. "main" and "v2.0.11" have entirely different histories.

5 changed files with 11 additions and 123 deletions

View file

@ -144,13 +144,6 @@ 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

View file

@ -1,66 +0,0 @@
#!/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)

View file

@ -17,10 +17,6 @@
namespace devour {
static std::set<uint64_t> g_queriedDefs;
std::set<uint64_t>& QueriedDefs(){ return g_queriedDefs; }
void RememberQueriedDef(uint64_t id){ g_queriedDefs.insert(id); }
#ifdef _WIN32
static HMODULE g_gameAsm = nullptr;
@ -557,26 +553,7 @@ int Il2CppResolver::fabricateOwnership(){
char* s=strrchr(exe,'\\'); if(!s) s=strrchr(exe,'/');
if(s){
std::ofstream f(std::string(exe,s-exe)+"\\DevourUnlocker.log",std::ios::app);
static bool loggedCatalog=false;
if(!loggedCatalog && cSize>0){
loggedCatalog=true;
f<<"[own] catalog ids:";
for(int i=0;i<cSize;++i){ void* ip=*(void**)(cData+(size_t)i*8); if(!ip) continue; f<<" "<<*(uint32_t*)((unsigned char*)ip+0x10); }
f<<"\n";
}
// Self-learning: inject any def the game queried but that lives outside the
// store catalog (Supporter/DLC flashlights etc). Valid ids only - no sweeps.
int learned=0;
for(uint64_t qid : g_queriedDefs){
if(qid==0 || hasId(qid)) continue;
void* exc=nullptr; void* arg=&qid;
invoke_fn(addMethod,userList,&arg,&exc);
if(exc) continue;
have.push_back(qid);
++learned;
}
f<<"[own] fabricate: catalog="<<cSize<<" had="<<(uSize>0?uSize:(int)(have.size()-injected))<<" injected="<<injected<<" learned="<<learned<<"\n";
f<<"[own] fabricate: catalog="<<cSize<<" had="<<(uSize>0?uSize:(int)have.size()-injected)<<" injected="<<injected<<"\n";
}
}catch(...){}
} catch(...){ injected=-1; }

View file

@ -2,7 +2,6 @@
#include <cstdint>
#include <string>
#include <vector>
#include <set>
// Minimal IL2CPP types — avoids needing il2cpp headers at compile time
struct Il2CppString { void* klass; void* monitor; int32_t length; wchar_t chars[1]; };
@ -10,12 +9,6 @@ struct MethodInfo;
namespace devour {
// Def ids the game itself asked UserHasItem/PlayerHasItem about (DLC/promo defs
// absent from the store catalog live here). Fabricator injects these too.
std::set<uint64_t>& QueriedDefs();
void RememberQueriedDef(uint64_t id);
// Resolves IL2CPP method pointers without hard offsets.
// Strategy 1: via il2cpp exports (class/method lookup)
// Strategy 2: xref string sigscan as fallback

View file

@ -90,20 +90,14 @@ 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) {
// pass-through + trace (v2.1.0 proved forcing true blocks purchases)
RememberQueriedDef((uint64_t)itemDef);
char b[96]; snprintf(b,sizeof(b),"[trace] UserHasItem(def=%llu)", (unsigned long long)itemDef); logPatch(b);
if (oUserHasItem) return oUserHasItem(thiz, itemDef, method);
return false;
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--; }
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) {
// MUST stay pass-through: forcing true desyncs lobby validation vs injected
// userItems and UNLOCKS REGRESS (pets locked again). See v2.1.1 regression.
RememberQueriedDef((uint64_t)itemDef);
char b[128]; snprintf(b,sizeof(b),"[trace] PlayerHasItem(conn=%d def=%llu)", connectionId, (unsigned long long)itemDef); logPatch(b);
if (oPlayerHasItem) return oPlayerHasItem(thiz, connectionId, itemDef, method);
return false;
char b[160]; snprintf(b,sizeof(b),"[trace] PlayerHasItem(conn=%d def=%llu) -> true", connectionId, itemDef); logPatch(b);
return true;
}
// Inventory-retrieved flag: pass through — forcing true with an empty userItems made some
// flows treat items as definitively NOT owned; original behavior is safer now
@ -111,9 +105,11 @@ static bool __fastcall hHasRetrievedUserItems(void* thiz, void* method) {
if (oHasRetrieved) return oHasRetrieved(thiz, method);
return true;
}
// 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; }
// 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;
}
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; }
@ -181,11 +177,6 @@ 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);
}