Compare commits

...

3 commits
v2.1.0 ... main

Author SHA1 Message Date
MiTHRAL
ecf1e680d6 v2.1.5: self-learning ownership — inject queried out-of-catalog defs (Supporter/DLC)
Some checks failed
Build DevourUnlocker / windows-cross (push) Failing after 8s
Build DevourUnlocker / linux (push) Failing after 6s
Build DevourUnlocker / standalone (push) Successful in 5s
User session traces: only defs 7 & 51 queried all session, both OUTSIDE the
59-id store catalog -> those are DLC-granted items (Supporter flashlights).
Blind 1..256 sweep polluted userItems and broke pet display (reverted v2.1.4).

Now hUserHasItem/hPlayerHasItem remember every def the game asks about into a
global registry; fabricateOwnership() injects any remembered-but-missing id on
its next 15s cycle. Only game-validated ids enter the list — converges to
everything-owned without garbage.
2026-08-24 13:54:41 -04:00
MiTHRAL
817a2eff78 v2.1.4: remove def-id range sweep — polluting userItems with unknown ids broke pet display; restore exact v2.1.0 injection semantics, keep pass-through traces + catalog dump
Some checks failed
Build DevourUnlocker / windows-cross (push) Failing after 7s
Build DevourUnlocker / linux (push) Failing after 7s
Build DevourUnlocker / standalone (push) Successful in 5s
2026-08-24 13:48:35 -04:00
MiTHRAL
fee1556da3 v2.1.1: PlayerHasItem -> true — emote validator gate (StoreEmotesOwnedOnSteam path)
Some checks failed
Build DevourUnlocker / windows-cross (push) Failing after 8s
Build DevourUnlocker / linux (push) Failing after 8s
Build DevourUnlocker / standalone (push) Successful in 5s
2026-08-24 13:38:56 -04:00
3 changed files with 40 additions and 3 deletions

View file

@ -17,6 +17,10 @@
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;
@ -553,7 +557,26 @@ 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);
f<<"[own] fabricate: catalog="<<cSize<<" had="<<(uSize>0?uSize:(int)have.size()-injected)<<" injected="<<injected<<"\n"; 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";
} }
}catch(...){} }catch(...){}
} catch(...){ injected=-1; } } catch(...){ injected=-1; }

View file

@ -2,6 +2,7 @@
#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]; };
@ -9,6 +10,12 @@ 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

View file

@ -90,13 +90,20 @@ 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)
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); if (oUserHasItem) return oUserHasItem(thiz, itemDef, method);
return true; 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
// 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); if (oPlayerHasItem) return oPlayerHasItem(thiz, connectionId, itemDef, method);
return true; 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