Compare commits
No commits in common. "main" and "v2.0.11" have entirely different histories.
5 changed files with 11 additions and 123 deletions
|
|
@ -144,13 +144,6 @@ 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
|
||||||
|
|
|
||||||
|
|
@ -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)
|
|
||||||
|
|
@ -17,10 +17,6 @@
|
||||||
|
|
||||||
namespace devour {
|
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
|
#ifdef _WIN32
|
||||||
static HMODULE g_gameAsm = nullptr;
|
static HMODULE g_gameAsm = nullptr;
|
||||||
|
|
||||||
|
|
@ -557,26 +553,7 @@ int Il2CppResolver::fabricateOwnership(){
|
||||||
char* s=strrchr(exe,'\\'); if(!s) s=strrchr(exe,'/');
|
char* s=strrchr(exe,'\\'); if(!s) s=strrchr(exe,'/');
|
||||||
if(s){
|
if(s){
|
||||||
std::ofstream f(std::string(exe,s-exe)+"\\DevourUnlocker.log",std::ios::app);
|
std::ofstream f(std::string(exe,s-exe)+"\\DevourUnlocker.log",std::ios::app);
|
||||||
static bool loggedCatalog=false;
|
f<<"[own] fabricate: catalog="<<cSize<<" had="<<(uSize>0?uSize:(int)have.size()-injected)<<" injected="<<injected<<"\n";
|
||||||
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";
|
|
||||||
}
|
}
|
||||||
}catch(...){}
|
}catch(...){}
|
||||||
} catch(...){ injected=-1; }
|
} catch(...){ injected=-1; }
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <set>
|
|
||||||
|
|
||||||
// Minimal IL2CPP types — avoids needing il2cpp headers at compile time
|
// Minimal IL2CPP types — avoids needing il2cpp headers at compile time
|
||||||
struct Il2CppString { void* klass; void* monitor; int32_t length; wchar_t chars[1]; };
|
struct Il2CppString { void* klass; void* monitor; int32_t length; wchar_t chars[1]; };
|
||||||
|
|
@ -10,12 +9,6 @@ struct MethodInfo;
|
||||||
|
|
||||||
namespace devour {
|
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.
|
// Resolves IL2CPP method pointers without hard offsets.
|
||||||
// Strategy 1: via il2cpp exports (class/method lookup)
|
// Strategy 1: via il2cpp exports (class/method lookup)
|
||||||
// Strategy 2: xref string sigscan as fallback
|
// Strategy 2: xref string sigscan as fallback
|
||||||
|
|
|
||||||
|
|
@ -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.
|
// 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) {
|
||||||
// pass-through + trace (v2.1.0 proved forcing true blocks purchases)
|
static thread_local int depth = 0;
|
||||||
RememberQueriedDef((uint64_t)itemDef);
|
if (depth < 2) { depth++; char b[128]; snprintf(b,sizeof(b),"[trace] UserHasItem(def=%llu) -> true", itemDef); logPatch(b); depth--; }
|
||||||
char b[96]; snprintf(b,sizeof(b),"[trace] UserHasItem(def=%llu)", (unsigned long long)itemDef); logPatch(b);
|
return true;
|
||||||
if (oUserHasItem) return oUserHasItem(thiz, itemDef, method);
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
// 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) {
|
||||||
// MUST stay pass-through: forcing true desyncs lobby validation vs injected
|
char b[160]; snprintf(b,sizeof(b),"[trace] PlayerHasItem(conn=%d def=%llu) -> true", connectionId, itemDef); logPatch(b);
|
||||||
// userItems and UNLOCKS REGRESS (pets locked again). See v2.1.1 regression.
|
return true;
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
// 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
|
||||||
// flows treat items as definitively NOT owned; original behavior is safer now
|
// 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);
|
if (oHasRetrieved) return oHasRetrieved(thiz, method);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
// Perks bought with Tokens: huge balance — perk modal deducts locally, so every perk
|
// Perks bought with Tokens: PASS THROUGH — spoofed 9,999,999 broke the token purchase modal.
|
||||||
// is instantly affordable. Store items don't use tokens (they route through Steam).
|
static int __fastcall hGetCurrentTokens(void* thiz, void* method) {
|
||||||
static int __fastcall hGetCurrentTokens(void* thiz, void* method) { return 9999999; }
|
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) {
|
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; }
|
||||||
|
|
@ -181,11 +177,6 @@ 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