diff --git a/src/il2cpp_resolver.cpp b/src/il2cpp_resolver.cpp index e3759b1..442e00c 100644 --- a/src/il2cpp_resolver.cpp +++ b/src/il2cpp_resolver.cpp @@ -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 diff --git a/src/il2cpp_resolver.hpp b/src/il2cpp_resolver.hpp index 34bb824..b2756af 100644 --- a/src/il2cpp_resolver.hpp +++ b/src/il2cpp_resolver.hpp @@ -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(); diff --git a/src/patches.cpp b/src/patches.cpp index f6dfa39..8d90178 100644 --- a/src/patches.cpp +++ b/src/patches.cpp @@ -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(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(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(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(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(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(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);