v2.0.9: full store unlock — hook the ownership validators themselves
Metadata dig findings (Assembly-CSharp, 2349 types):
- SteamInventoryValidator.PlayerHasItem(connId, steamItemDefID): multiplayer/purchase
validation gate used by WaitForPurchased{Pet,Emote,Flashlight,Outfit}Validation
coroutines (<validator>5__4) -> hooked true
- SteamInventoryManager.HasRetrievedUserInventoryItems(): gates PopulateInventoryWhenAvailable;
if Steam inventory query under Proton returns nothing, item views never populate ->
hooked true so lobby tabs build with UserHasItem=true
- SteamInventoryManager.userItems cache read directly by UI in some paths — covered by
UserHasItem+PlayerHasItem+HasRetrieved trio
Now 15 hooks: IsRobeUnlocked/IsCharacterUnlocked/HasPerk/HasOutfit->true,
UserHasItem/PlayerHasItem->owned, GetCurrentTokens->9999999,
HasRetrievedUserItems->true, UpdateLocked*(6)+WaitForPurchasedPetValidation call-through.
Pets/perks/flashlights/emotes/outfits route through the game's own grant path — no
purchase browser, no coin spoofing visible anywhere.
This commit is contained in:
parent
48d0edac0a
commit
d9a50caa4f
3 changed files with 43 additions and 1 deletions
|
|
@ -212,6 +212,8 @@ ResolvedMethod Il2CppResolver::updateLockedRobes() { return resolve("Menu", "Upd
|
|||
ResolvedMethod Il2CppResolver::waitForPurchasedPet() { return resolve("Menu", "WaitForPurchasedPetValidation", 1); }
|
||||
ResolvedMethod Il2CppResolver::userHasItem() { return resolve("SteamInventoryManager", "UserHasItem", 1); }
|
||||
ResolvedMethod Il2CppResolver::getCurrentTokens() { return resolve("Menu", "GetCurrentTokens", 0); }
|
||||
ResolvedMethod Il2CppResolver::playerHasItem() { return resolve("SteamInventoryValidator", "PlayerHasItem", 2); }
|
||||
ResolvedMethod Il2CppResolver::hasRetrievedUserItems() { return resolve("SteamInventoryManager", "HasRetrievedUserInventoryItems", 0); }
|
||||
|
||||
void Il2CppResolver::dumpDiagnostics(){
|
||||
#ifdef _WIN32
|
||||
|
|
@ -382,6 +384,8 @@ ResolvedMethod Il2CppResolver::updateLockedRobes() { return {"UpdateLockedRobes"
|
|||
ResolvedMethod Il2CppResolver::waitForPurchasedPet() { return {"WaitForPurchasedPetValidation", nullptr}; }
|
||||
ResolvedMethod Il2CppResolver::userHasItem() { return {"UserHasItem", nullptr}; }
|
||||
ResolvedMethod Il2CppResolver::getCurrentTokens() { return {"GetCurrentTokens", nullptr}; }
|
||||
ResolvedMethod Il2CppResolver::playerHasItem() { return {"PlayerHasItem", nullptr}; }
|
||||
ResolvedMethod Il2CppResolver::hasRetrievedUserItems() { return {"HasRetrievedUserInventoryItems", nullptr}; }
|
||||
|
||||
#endif
|
||||
|
||||
|
|
|
|||
|
|
@ -42,6 +42,8 @@ public:
|
|||
static ResolvedMethod waitForPurchasedPet();
|
||||
static ResolvedMethod userHasItem(); // SteamInventoryManager.UserHasItem(steamItemDefID)
|
||||
static ResolvedMethod getCurrentTokens(); // Horror.Menu.GetCurrentTokens()
|
||||
static ResolvedMethod playerHasItem(); // SteamInventoryValidator.PlayerHasItem(connId, steamItemDefID)
|
||||
static ResolvedMethod hasRetrievedUserItems(); // SteamInventoryManager.HasRetrievedUserInventoryItems()
|
||||
static void dumpDiagnostics();
|
||||
static uintptr_t gameAssemblyBase();
|
||||
static size_t gameAssemblySize();
|
||||
|
|
|
|||
|
|
@ -29,6 +29,8 @@ using IsUnlockedFn1 = bool(__fastcall*)(void* thiz, void* str, void* method);
|
|||
using UpdateLockedFn = void(__fastcall*)(void* thiz, void* method);
|
||||
using UserHasItemFn = bool(__fastcall*)(void* thiz, unsigned long long itemDef, void* method);
|
||||
using GetTokensFn = int(__fastcall*)(void* thiz, void* method);
|
||||
using PlayerHasItemFn = bool(__fastcall*)(void* thiz, int connectionId, unsigned long long itemDef, void* method);
|
||||
using HasRetrievedFn = bool(__fastcall*)(void* thiz, void* method);
|
||||
|
||||
static IsUnlockedFn oIsRobe = nullptr;
|
||||
static IsUnlockedFn1 oIsChar = nullptr;
|
||||
|
|
@ -43,6 +45,8 @@ static UpdateLockedFn oUpdRobes = nullptr;
|
|||
static UpdateLockedFn oWaitPet = nullptr;
|
||||
static UserHasItemFn oUserHasItem = nullptr;
|
||||
static GetTokensFn oGetTokens = nullptr;
|
||||
static PlayerHasItemFn oPlayerHasItem = nullptr;
|
||||
static HasRetrievedFn oHasRetrieved = nullptr;
|
||||
static bool g_installed = false;
|
||||
static bool g_mhInit = false;
|
||||
|
||||
|
|
@ -79,6 +83,14 @@ static bool __fastcall hUserHasItem(void* thiz, unsigned long long itemDef, void
|
|||
(void)itemDef;
|
||||
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) {
|
||||
(void)connectionId; (void)itemDef;
|
||||
return true;
|
||||
}
|
||||
// Inventory-retrieved flag: unblocks PopulateInventoryWhenAvailable even if the Steam
|
||||
// inventory query returns nothing under Proton — views populate with UserHasItem=true
|
||||
static bool __fastcall hHasRetrievedUserItems(void* thiz, void* method) { return true; }
|
||||
// Perks bought with Tokens: report a huge balance so every perk is purchasable/selectable
|
||||
static int __fastcall hGetCurrentTokens(void* thiz, void* method) { return 9999999; }
|
||||
|
||||
|
|
@ -196,8 +208,10 @@ bool installPatches() {
|
|||
{
|
||||
auto uhi = Il2CppResolver::userHasItem();
|
||||
auto tok = Il2CppResolver::getCurrentTokens();
|
||||
auto phi = Il2CppResolver::playerHasItem();
|
||||
auto hri = Il2CppResolver::hasRetrievedUserItems();
|
||||
char b2[512];
|
||||
snprintf(b2, sizeof(b2), "[DevourUnlocker] Resolved UserHasItem=%p GetCurrentTokens=%p", uhi.address, tok.address);
|
||||
snprintf(b2, sizeof(b2), "[DevourUnlocker] Resolved UserHasItem=%p GetCurrentTokens=%p PlayerHasItem=%p HasRetrievedUserItems=%p", uhi.address, tok.address, phi.address, hri.address);
|
||||
logPatch(b2);
|
||||
if (uhi.valid()) {
|
||||
void* orig = nullptr;
|
||||
|
|
@ -217,6 +231,24 @@ bool installPatches() {
|
|||
any = true;
|
||||
} else logPatch("[DevourUnlocker] Hook GetCurrentTokens failed");
|
||||
} else logPatch("[DevourUnlocker] GetCurrentTokens not found");
|
||||
if (phi.valid()) {
|
||||
void* orig = nullptr;
|
||||
if (MH_CreateHook(phi.address, (void*)hPlayerHasItem, &orig) == MH_OK) {
|
||||
MH_EnableHook(phi.address);
|
||||
oPlayerHasItem = reinterpret_cast<PlayerHasItemFn>(orig);
|
||||
logPatch("[DevourUnlocker] Hooked PlayerHasItem -> always owned");
|
||||
any = true;
|
||||
} else logPatch("[DevourUnlocker] Hook PlayerHasItem failed");
|
||||
} else logPatch("[DevourUnlocker] PlayerHasItem not found");
|
||||
if (hri.valid()) {
|
||||
void* orig = nullptr;
|
||||
if (MH_CreateHook(hri.address, (void*)hHasRetrievedUserItems, &orig) == MH_OK) {
|
||||
MH_EnableHook(hri.address);
|
||||
oHasRetrieved = reinterpret_cast<HasRetrievedFn>(orig);
|
||||
logPatch("[DevourUnlocker] Hooked HasRetrievedUserInventoryItems -> true");
|
||||
any = true;
|
||||
} else logPatch("[DevourUnlocker] Hook HasRetrievedUserInventoryItems failed");
|
||||
} else logPatch("[DevourUnlocker] HasRetrievedUserInventoryItems not found");
|
||||
}
|
||||
any |= hookUpdateLocked(updPets.address, "UpdateLockedPets", Config::get().unlockPets, hUpdateLockedPets, &oUpdPets);
|
||||
any |= hookUpdateLocked(updPerks.address, "UpdateLockedPerks", Config::get().unlockPerks, hUpdateLockedPerks, &oUpdPerks);
|
||||
|
|
@ -245,6 +277,8 @@ bool installPatches() {
|
|||
auto pw = Il2CppResolver::waitForPurchasedPet();
|
||||
auto uh = Il2CppResolver::userHasItem();
|
||||
auto tk = Il2CppResolver::getCurrentTokens();
|
||||
auto ph = Il2CppResolver::playerHasItem();
|
||||
auto hr = Il2CppResolver::hasRetrievedUserItems();
|
||||
bool any2=false;
|
||||
if(r.valid()){ if(MH_CreateHook(r.address,(void*)hIsRobe,(void**)&oIsRobe)==MH_OK){ MH_EnableHook(r.address); any2=true; logPatch("[DevourUnlocker] Retry hooked IsRobeUnlocked"); } }
|
||||
if(c.valid()){ if(MH_CreateHook(c.address,(void*)hIsChar,(void**)&oIsChar)==MH_OK){ MH_EnableHook(c.address); any2=true; logPatch("[DevourUnlocker] Retry hooked IsCharacterUnlocked"); } }
|
||||
|
|
@ -252,6 +286,8 @@ bool installPatches() {
|
|||
if(ho.valid()){ if(MH_CreateHook(ho.address,(void*)hHasOutfit,(void**)&oHasOutfit)==MH_OK){ MH_EnableHook(ho.address); any2=true; logPatch("[DevourUnlocker] Retry hooked HasOutfit"); } }
|
||||
if(uh.valid()){ void* og=nullptr; if(MH_CreateHook(uh.address,(void*)hUserHasItem,&og)==MH_OK){ MH_EnableHook(uh.address); oUserHasItem=reinterpret_cast<UserHasItemFn>(og); any2=true; logPatch("[DevourUnlocker] Retry hooked UserHasItem"); } }
|
||||
if(tk.valid()){ void* og=nullptr; if(MH_CreateHook(tk.address,(void*)hGetCurrentTokens,&og)==MH_OK){ MH_EnableHook(tk.address); oGetTokens=reinterpret_cast<GetTokensFn>(og); any2=true; logPatch("[DevourUnlocker] Retry hooked GetCurrentTokens"); } }
|
||||
if(ph.valid()){ void* og=nullptr; if(MH_CreateHook(ph.address,(void*)hPlayerHasItem,&og)==MH_OK){ MH_EnableHook(ph.address); oPlayerHasItem=reinterpret_cast<PlayerHasItemFn>(og); any2=true; logPatch("[DevourUnlocker] Retry hooked PlayerHasItem"); } }
|
||||
if(hr.valid()){ void* og=nullptr; if(MH_CreateHook(hr.address,(void*)hHasRetrievedUserItems,&og)==MH_OK){ MH_EnableHook(hr.address); oHasRetrieved=reinterpret_cast<HasRetrievedFn>(og); any2=true; logPatch("[DevourUnlocker] Retry hooked HasRetrievedUserInventoryItems"); } }
|
||||
if(p1.valid()) any2 |= hookUpdateLocked(p1.address, "UpdateLockedPets[retry]", Config::get().unlockPets, hUpdateLockedPets, &oUpdPets);
|
||||
if(p2.valid()) any2 |= hookUpdateLocked(p2.address, "UpdateLockedPerks[retry]", Config::get().unlockPerks, hUpdateLockedPerks, &oUpdPerks);
|
||||
if(p3.valid()) any2 |= hookUpdateLocked(p3.address, "UpdateLockedFlashlights[retry]", Config::get().unlockFlashlights, hUpdateLockedFlashlights, &oUpdFlash);
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue