v2.0.8: unlock all categories — ownership + currency override, not UI no-op
Root cause of 'robes unlocked but nothing else': UpdateLocked* no-op prevented lock marking but lobby UI defaults to locked until UpdateLocked* runs its grant logic. Robes worked because IsRobeUnlocked (hooked) gates selection directly. Fix from global-metadata.dat analysis (148772 methods scanned): - SteamInventoryManager.UserHasItem(steamItemDefID) -> always true: every Steam catalog item (pets/flashlights/emotes/outfits/promos) counts as owned - Horror.Menu.GetCurrentTokens() -> 9999999: perk token cost always affordable - UpdateLocked*/WaitForPurchasedPetValidation now CALL ORIGINAL via trampoline so the game's own grant logic runs with overridden ownership (clean UI state) - hookUpdateLocked stores orig fn pointer; retry thread also retries UserHasItem/Tokens and HasPerk/HasOutfit; logPatch on every hook success/failure for diagnosis Verified launch: 13 hooks installed (IsRobe/IsChar/HasPerk/HasOutfit/UserHasItem/ GetCurrentTokens/7xUpdateLocked+WaitPet), game stable, no crashes
This commit is contained in:
parent
bffc822614
commit
48d0edac0a
3 changed files with 189 additions and 80 deletions
|
|
@ -70,7 +70,7 @@ std::vector<void*> Il2CppResolver::scanPattern(const char* pattern, const char*
|
|||
return out;
|
||||
}
|
||||
|
||||
ResolvedMethod Il2CppResolver::resolve(const char* klassName, const char* methodName) {
|
||||
ResolvedMethod Il2CppResolver::resolve(const char* klassName, const char* methodName, int argc) {
|
||||
// Domain wait is now handled by installPatches once, not per-resolve to avoid 6*15s stalls
|
||||
// If domain not ready, fallback to string xref quickly
|
||||
auto domain_get_test = reinterpret_cast<void*(*)()>(getProc("il2cpp_domain_get"));
|
||||
|
|
@ -80,7 +80,8 @@ ResolvedMethod Il2CppResolver::resolve(const char* klassName, const char* method
|
|||
auto il2cpp_domain_get = reinterpret_cast<void*(*)()>(getProc("il2cpp_domain_get"));
|
||||
auto il2cpp_thread_attach = reinterpret_cast<void*(*)(void*)>(getProc("il2cpp_thread_attach"));
|
||||
auto klass_from_name = reinterpret_cast<void*(*)(void*, const char*, const char*)>(getProc("il2cpp_class_from_name"));
|
||||
auto class_get_method = reinterpret_cast<void*(*)(void*, const char*)>(getProc("il2cpp_class_get_method_from_name"));
|
||||
// Real IL2CPP signature: il2cpp_class_get_method_from_name(klass, name, argsCount)
|
||||
auto class_get_method = reinterpret_cast<void*(*)(void*, const char*, int)>(getProc("il2cpp_class_get_method_from_name"));
|
||||
auto domain_get_assemblies = reinterpret_cast<const void**(*)(void*, size_t*)>(getProc("il2cpp_domain_get_assemblies"));
|
||||
auto assembly_get_image = reinterpret_cast<void*(*)(void*)>(getProc("il2cpp_assembly_get_image"));
|
||||
|
||||
|
|
@ -99,25 +100,34 @@ ResolvedMethod Il2CppResolver::resolve(const char* klassName, const char* method
|
|||
}
|
||||
if (!assemblies || nAssemblies==0) return resolveByStringXref(methodName);
|
||||
|
||||
const char* candidates[] = {klassName, "NolanRankController", "LobbyController", "InventoryController", "GameController", "SurvivalLobbyController", "NolanRankController", nullptr};
|
||||
// Namespaces seen in global-metadata.dat: OptionsHelpers/InGameHelpers are global,
|
||||
// UpdateLocked*/WaitForPurchasedPetValidation live in Horror.Menu
|
||||
const char* namespaces[] = {"", "Horror", nullptr};
|
||||
auto tryMethod = [&](void* klass) -> void* {
|
||||
if (!klass) return nullptr;
|
||||
int argcs[4] = { argc, 1, 0, 2 };
|
||||
for (int ai = 0; ai < 4; ++ai) {
|
||||
if (ai == 0 && argc < 0) continue;
|
||||
void* m = class_get_method(klass, methodName, argcs[ai]);
|
||||
if (m) {
|
||||
void* ptr = *reinterpret_cast<void**>(m); // MethodInfo.methodPointer @ +0x00
|
||||
if (ptr) return ptr;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
};
|
||||
|
||||
for (size_t ai = 0; ai < nAssemblies; ++ai) {
|
||||
void* image = assembly_get_image((void*)assemblies[ai]);
|
||||
if (!image) continue;
|
||||
for (int ci = 0; candidates[ci]; ++ci) {
|
||||
void* klass = klass_from_name(image, "", candidates[ci]);
|
||||
if (!klass) {
|
||||
// Try with no namespace but also try with "Devour" namespace?
|
||||
klass = klass_from_name(image, "Devour", candidates[ci]);
|
||||
}
|
||||
for (int ni = 0; namespaces[ni]; ++ni) {
|
||||
void* klass = klass_from_name(image, namespaces[ni], klassName);
|
||||
if (!klass) continue;
|
||||
void* method = class_get_method(klass, methodName);
|
||||
if (method) {
|
||||
void* ptr = *reinterpret_cast<void**>(reinterpret_cast<uint8_t*>(method) + 0x18);
|
||||
void* ptr = tryMethod(klass);
|
||||
if (ptr) return {methodName, ptr};
|
||||
}
|
||||
}
|
||||
}
|
||||
// Brute force: enumerate all classes in all images for methodName (UpdateLocked* etc live in unknown class)
|
||||
// Brute force: enumerate all classes in all images for methodName (works regardless of namespace)
|
||||
auto image_get_class_count = reinterpret_cast<int(*)(void*)>(getProc("il2cpp_image_get_class_count"));
|
||||
auto image_get_class = reinterpret_cast<void*(*)(void*, int)>(getProc("il2cpp_image_get_class"));
|
||||
if (image_get_class_count && image_get_class) {
|
||||
|
|
@ -128,14 +138,11 @@ ResolvedMethod Il2CppResolver::resolve(const char* klassName, const char* method
|
|||
for (int ci = 0; ci < count; ++ci) {
|
||||
void* klass = image_get_class(image, ci);
|
||||
if (!klass) continue;
|
||||
void* method = class_get_method(klass, methodName);
|
||||
if (method) {
|
||||
void* ptr = *reinterpret_cast<void**>(reinterpret_cast<uint8_t*>(method) + 0x18);
|
||||
void* ptr = tryMethod(klass);
|
||||
if (ptr) return {methodName, ptr};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return resolveByStringXref(methodName);
|
||||
}
|
||||
|
||||
|
|
@ -183,18 +190,28 @@ ResolvedMethod Il2CppResolver::resolveByStringXref(const char* str, int occurren
|
|||
return {str, nullptr};
|
||||
}
|
||||
|
||||
ResolvedMethod Il2CppResolver::isRobeUnlocked() { return resolve("NolanRankController", "IsRobeUnlocked"); }
|
||||
ResolvedMethod Il2CppResolver::isCharacterUnlocked() { return resolve("NolanRankController", "IsCharacterUnlocked"); }
|
||||
ResolvedMethod Il2CppResolver::hasOutfit() { return resolve("NolanRankController", "HasOutfit"); }
|
||||
ResolvedMethod Il2CppResolver::hasPerk() { return resolve("NolanRankController", "HasPerk"); }
|
||||
ResolvedMethod Il2CppResolver::hasPet() { return resolve("NolanRankController", "HasPet"); }
|
||||
ResolvedMethod Il2CppResolver::updateLockedPets() { return resolve("SurvivalLobbyController", "UpdateLockedPets"); }
|
||||
ResolvedMethod Il2CppResolver::updateLockedPerks() { return resolve("SurvivalLobbyController", "UpdateLockedPerks"); }
|
||||
ResolvedMethod Il2CppResolver::updateLockedFlashlights() { return resolve("SurvivalLobbyController", "UpdateLockedFlashlights"); }
|
||||
ResolvedMethod Il2CppResolver::updateLockedEmotes() { return resolve("SurvivalLobbyController", "UpdateLockedEmotes"); }
|
||||
ResolvedMethod Il2CppResolver::updateLockedOutfits() { return resolve("SurvivalLobbyController", "UpdateLockedOutfits"); }
|
||||
ResolvedMethod Il2CppResolver::updateLockedRobes() { return resolve("SurvivalLobbyController", "UpdateLockedRobes"); }
|
||||
ResolvedMethod Il2CppResolver::waitForPurchasedPet() { return resolve("SurvivalLobbyController", "WaitForPurchasedPetValidation"); }
|
||||
// Real targets from global-metadata.dat (v31, 2022.3.62f2):
|
||||
// OptionsHelpers.IsRobeUnlocked(robe, character) — global ns
|
||||
// OptionsHelpers.IsCharacterUnlocked(prefab) — global ns
|
||||
// OptionsHelpers.HasOutfit(outfitName, characterName) — global ns
|
||||
// InGameHelpers.HasPerk(perk) / NolanPerkController.HasPerk(perk)
|
||||
// Horror.Menu.UpdateLocked{Pets,Perks,Flashlights,Emotes,Outfits}(selectionViews)
|
||||
// Horror.Menu.UpdateLockedRobes()
|
||||
// Horror.Menu.WaitForPurchasedPetValidation(pet)
|
||||
ResolvedMethod Il2CppResolver::isRobeUnlocked() { return resolve("OptionsHelpers", "IsRobeUnlocked", 2); }
|
||||
ResolvedMethod Il2CppResolver::isCharacterUnlocked() { return resolve("OptionsHelpers", "IsCharacterUnlocked", 1); }
|
||||
ResolvedMethod Il2CppResolver::hasOutfit() { return resolve("OptionsHelpers", "HasOutfit", 2); }
|
||||
ResolvedMethod Il2CppResolver::hasPerk() { return resolve("InGameHelpers", "HasPerk", 1); }
|
||||
ResolvedMethod Il2CppResolver::hasPet() { return {"HasPet", nullptr}; }
|
||||
ResolvedMethod Il2CppResolver::updateLockedPets() { return resolve("Menu", "UpdateLockedPets", 1); }
|
||||
ResolvedMethod Il2CppResolver::updateLockedPerks() { return resolve("Menu", "UpdateLockedPerks", 1); }
|
||||
ResolvedMethod Il2CppResolver::updateLockedFlashlights() { return resolve("Menu", "UpdateLockedFlashlights", 1); }
|
||||
ResolvedMethod Il2CppResolver::updateLockedEmotes() { return resolve("Menu", "UpdateLockedEmotes", 1); }
|
||||
ResolvedMethod Il2CppResolver::updateLockedOutfits() { return resolve("Menu", "UpdateLockedOutfits", 1); }
|
||||
ResolvedMethod Il2CppResolver::updateLockedRobes() { return resolve("Menu", "UpdateLockedRobes", 0); }
|
||||
ResolvedMethod Il2CppResolver::waitForPurchasedPet() { return resolve("Menu", "WaitForPurchasedPetValidation", 1); }
|
||||
ResolvedMethod Il2CppResolver::userHasItem() { return resolve("SteamInventoryManager", "UserHasItem", 1); }
|
||||
ResolvedMethod Il2CppResolver::getCurrentTokens() { return resolve("Menu", "GetCurrentTokens", 0); }
|
||||
|
||||
void Il2CppResolver::dumpDiagnostics(){
|
||||
#ifdef _WIN32
|
||||
|
|
@ -204,16 +221,52 @@ void Il2CppResolver::dumpDiagnostics(){
|
|||
char* slash = strrchr(exe, '\\'); if(!slash) slash = strrchr(exe, '/');
|
||||
std::string logPath = slash ? std::string(exe, slash - exe) + "\\DevourUnlocker.log" : "DevourUnlocker.log";
|
||||
std::ofstream f(logPath, std::ios::app);
|
||||
f << "[Diag] dumpDiagnostics ENTER (safe mode - minimal)\n"; f.flush();
|
||||
f << "[Diag] dumpDiagnostics ENTER (safe mode - targeted)\n"; f.flush();
|
||||
auto il2cpp_domain_get = reinterpret_cast<void*(*)()>(getProc("il2cpp_domain_get"));
|
||||
if(!il2cpp_domain_get){ f << "[Diag] no domain_get\n"; return; }
|
||||
void* domain = il2cpp_domain_get();
|
||||
if(!domain){ f << "[Diag] domain null (IL2CPP not ready - retry later)\n"; return; }
|
||||
// Minimal safe dump - do not enumerate all classes (caused strlen(null) crash)
|
||||
f << "[Diag] domain=" << domain << " - skipping full enumeration to avoid crash\n";
|
||||
f << "[Diag] If hooks not found, IL2CPP may not be ready until lobby - retry thread will handle\n";
|
||||
auto klass_from_name = reinterpret_cast<void*(*)(void*, const char*, const char*)>(getProc("il2cpp_class_from_name"));
|
||||
auto class_get_method = reinterpret_cast<void*(*)(void*, const char*, int)>(getProc("il2cpp_class_get_method_from_name"));
|
||||
auto domain_get_assemblies = reinterpret_cast<const void**(*)(void*, size_t*)>(getProc("il2cpp_domain_get_assemblies"));
|
||||
auto assembly_get_image = reinterpret_cast<void*(*)(void*)>(getProc("il2cpp_assembly_get_image"));
|
||||
if(!klass_from_name || !class_get_method || !domain_get_assemblies || !assembly_get_image){
|
||||
f << "[Diag] missing IL2CPP exports\n"; return;
|
||||
}
|
||||
size_t nAssemblies=0; const void** assemblies = domain_get_assemblies(domain, &nAssemblies);
|
||||
f << "[Diag] assemblies=" << nAssemblies << "\n";
|
||||
// Bounded targeted probe of the two real classes — no mass enumeration
|
||||
struct Probe { const char* ns; const char* cls; const char* m; int argc; };
|
||||
const Probe probes[] = {
|
||||
{"", "OptionsHelpers", "IsRobeUnlocked", 2},
|
||||
{"", "OptionsHelpers", "IsCharacterUnlocked", 1},
|
||||
{"", "OptionsHelpers", "HasOutfit", 2},
|
||||
{"", "InGameHelpers", "HasPerk", 1},
|
||||
{"Horror", "Menu", "UpdateLockedPets", 1},
|
||||
{"Horror", "Menu", "UpdateLockedRobes", 0},
|
||||
{"Horror", "Menu", "WaitForPurchasedPetValidation", 1},
|
||||
};
|
||||
for (const auto& p : probes) {
|
||||
void* found = nullptr; void* kfound = nullptr;
|
||||
for(size_t ai=0; ai<nAssemblies && !found; ++ai){
|
||||
void* image = assembly_get_image((void*)assemblies[ai]);
|
||||
if(!image) continue;
|
||||
void* k = klass_from_name(image, p.ns, p.cls);
|
||||
if(!k) continue;
|
||||
kfound = k;
|
||||
void* m = class_get_method(k, p.m, p.argc);
|
||||
if(m){
|
||||
void* ptr = *reinterpret_cast<void**>(m); // MethodInfo.methodPointer @ +0x00
|
||||
f << "[Diag] " << (p.ns[0]?p.ns:"") << "." << p.cls << "::" << p.m << "(" << p.argc << ") -> " << ptr << "\n";
|
||||
found = ptr;
|
||||
}
|
||||
}
|
||||
if(!found){
|
||||
f << "[Diag] " << (p.ns[0]?p.ns:"") << "." << p.cls << "::" << p.m
|
||||
<< " NOT found" << (kfound ? " (class ok, method null)" : " (class missing)") << "\n";
|
||||
}
|
||||
}
|
||||
f << "[Diag] dump done (safe)\n";
|
||||
return;
|
||||
// DISABLED heavy enumeration that caused crash (strlen null at 0x8eb3)
|
||||
#if 0
|
||||
auto domain_get_assemblies = reinterpret_cast<const void**(*)(void*, size_t*)>(getProc("il2cpp_domain_get_assemblies"));
|
||||
|
|
@ -262,7 +315,7 @@ void Il2CppResolver::dumpDiagnostics(){
|
|||
void* m = class_get_method(klass, targets[ti]);
|
||||
f << " try " << targets[ti] << " -> " << m;
|
||||
if(m){
|
||||
void* ptr = *reinterpret_cast<void**>(reinterpret_cast<uint8_t*>(m) + 0x18);
|
||||
void* ptr = *reinterpret_cast<void**>(m); // MethodInfo.methodPointer @ +0x00
|
||||
f << " ptr=" << ptr;
|
||||
auto method_get_name2 = reinterpret_cast<const char*(*)(void*)>(getProc("il2cpp_method_get_name"));
|
||||
if(method_get_name2){
|
||||
|
|
@ -293,7 +346,7 @@ void Il2CppResolver::dumpDiagnostics(){
|
|||
for(int ti=0; targets[ti]; ++ti){
|
||||
void* m = class_get_method(klass, targets[ti]);
|
||||
if(m) {
|
||||
void* ptr = *reinterpret_cast<void**>(reinterpret_cast<uint8_t*>(m) + 0x18);
|
||||
void* ptr = *reinterpret_cast<void**>(m); // MethodInfo.methodPointer @ +0x00
|
||||
f << " FOUND " << kname << "." << targets[ti] << " @ " << m << " -> " << ptr << " (image " << iname << ")\n";
|
||||
}
|
||||
}
|
||||
|
|
@ -327,6 +380,8 @@ ResolvedMethod Il2CppResolver::updateLockedEmotes() { return {"UpdateLockedEmote
|
|||
ResolvedMethod Il2CppResolver::updateLockedOutfits() { return {"UpdateLockedOutfits", nullptr}; }
|
||||
ResolvedMethod Il2CppResolver::updateLockedRobes() { return {"UpdateLockedRobes", nullptr}; }
|
||||
ResolvedMethod Il2CppResolver::waitForPurchasedPet() { return {"WaitForPurchasedPetValidation", nullptr}; }
|
||||
ResolvedMethod Il2CppResolver::userHasItem() { return {"UserHasItem", nullptr}; }
|
||||
ResolvedMethod Il2CppResolver::getCurrentTokens() { return {"GetCurrentTokens", nullptr}; }
|
||||
|
||||
#endif
|
||||
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ public:
|
|||
static void* getProc(const char* name);
|
||||
static std::vector<void*> scanPattern(const char* pattern, const char* mask, uintptr_t start, size_t size);
|
||||
|
||||
static ResolvedMethod resolve(const char* klassName, const char* methodName);
|
||||
static ResolvedMethod resolve(const char* klassName, const char* methodName, int argc = -1);
|
||||
static ResolvedMethod resolveByStringXref(const char* str, int occurrence = 0);
|
||||
|
||||
static ResolvedMethod isRobeUnlocked();
|
||||
|
|
@ -40,6 +40,8 @@ public:
|
|||
static ResolvedMethod updateLockedOutfits();
|
||||
static ResolvedMethod updateLockedRobes();
|
||||
static ResolvedMethod waitForPurchasedPet();
|
||||
static ResolvedMethod userHasItem(); // SteamInventoryManager.UserHasItem(steamItemDefID)
|
||||
static ResolvedMethod getCurrentTokens(); // Horror.Menu.GetCurrentTokens()
|
||||
static void dumpDiagnostics();
|
||||
static uintptr_t gameAssemblyBase();
|
||||
static size_t gameAssemblySize();
|
||||
|
|
|
|||
130
src/patches.cpp
130
src/patches.cpp
|
|
@ -24,13 +24,25 @@ namespace devour {
|
|||
|
||||
#ifdef _WIN32
|
||||
|
||||
using IsUnlockedFn = bool(__fastcall*)(void* thiz, void* str, void* method);
|
||||
using IsUnlockedFn = bool(__fastcall*)(void* thiz, void* str, void* str2, void* method);
|
||||
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);
|
||||
|
||||
static IsUnlockedFn oIsRobe = nullptr;
|
||||
static IsUnlockedFn oIsChar = nullptr;
|
||||
static IsUnlockedFn oHasPerk = nullptr;
|
||||
static IsUnlockedFn1 oIsChar = nullptr;
|
||||
static IsUnlockedFn1 oHasPerk = nullptr;
|
||||
static IsUnlockedFn oHasOutfit = nullptr;
|
||||
static UpdateLockedFn oUpdPets = nullptr;
|
||||
static UpdateLockedFn oUpdPerks = nullptr;
|
||||
static UpdateLockedFn oUpdFlash = nullptr;
|
||||
static UpdateLockedFn oUpdEmotes = nullptr;
|
||||
static UpdateLockedFn oUpdOutfits = nullptr;
|
||||
static UpdateLockedFn oUpdRobes = nullptr;
|
||||
static UpdateLockedFn oWaitPet = nullptr;
|
||||
static UserHasItemFn oUserHasItem = nullptr;
|
||||
static GetTokensFn oGetTokens = nullptr;
|
||||
static bool g_installed = false;
|
||||
static bool g_mhInit = false;
|
||||
|
||||
|
|
@ -49,31 +61,39 @@ static std::string il2cppStringToUtf8(void* ilStr) {
|
|||
return "";
|
||||
}
|
||||
|
||||
static bool __fastcall hIsRobe(void* thiz, void* robeStr, void* method) { return true; }
|
||||
static bool __fastcall hIsRobe(void* thiz, void* robeStr, void* characterStr, void* method) { return true; }
|
||||
static bool __fastcall hIsChar(void* thiz, void* charStr, void* method) { return true; }
|
||||
static bool __fastcall hHasPerk(void* thiz, void* perkStr, void* method) { return true; }
|
||||
static bool __fastcall hHasOutfit(void* thiz, void* outfitStr, void* method) { return true; }
|
||||
// Always-on: every UpdateLocked* is bypassed
|
||||
static void __fastcall hUpdateLockedPets(void* thiz, void* method) { logPatch("[DevourUnlocker] UpdateLockedPets bypassed (always-on)"); }
|
||||
static void __fastcall hWaitForPurchasedPet(void* thiz, void* method) { logPatch("[DevourUnlocker] WaitForPurchasedPetValidation bypassed (always-on)"); }
|
||||
static void __fastcall hUpdateLockedPerks(void* thiz, void* method) { logPatch("[DevourUnlocker] UpdateLockedPerks bypassed (always-on)"); }
|
||||
static void __fastcall hUpdateLockedFlashlights(void* thiz, void* method) { logPatch("[DevourUnlocker] UpdateLockedFlashlights bypassed"); }
|
||||
static void __fastcall hUpdateLockedEmotes(void* thiz, void* method) { logPatch("[DevourUnlocker] UpdateLockedEmotes bypassed"); }
|
||||
static void __fastcall hUpdateLockedOutfits(void* thiz, void* method) { logPatch("[DevourUnlocker] UpdateLockedOutfits bypassed"); }
|
||||
static void __fastcall hUpdateLockedRobes(void* thiz, void* method) { logPatch("[DevourUnlocker] UpdateLockedRobes bypassed"); }
|
||||
static bool __fastcall hHasOutfit(void* thiz, void* outfitStr, void* characterStr, void* method) { return true; }
|
||||
// UpdateLocked*: call the ORIGINAL so the UI runs its normal flow — ownership checks
|
||||
// (UserHasItem→true) make it mark everything unlocked instead of leaving defaults locked
|
||||
static void __fastcall hUpdateLockedPets(void* thiz, void* method) { if (oUpdPets) oUpdPets(thiz, method); }
|
||||
static void __fastcall hWaitForPurchasedPet(void* thiz, void* method) { if (oWaitPet) oWaitPet(thiz, method); }
|
||||
static void __fastcall hUpdateLockedPerks(void* thiz, void* method) { if (oUpdPerks) oUpdPerks(thiz, method); }
|
||||
static void __fastcall hUpdateLockedFlashlights(void* thiz, void* method) { if (oUpdFlash) oUpdFlash(thiz, method); }
|
||||
static void __fastcall hUpdateLockedEmotes(void* thiz, void* method) { if (oUpdEmotes) oUpdEmotes(thiz, method); }
|
||||
static void __fastcall hUpdateLockedOutfits(void* thiz, void* method) { if (oUpdOutfits) oUpdOutfits(thiz, method); }
|
||||
static void __fastcall hUpdateLockedRobes(void* thiz, void* method) { if (oUpdRobes) oUpdRobes(thiz, method); }
|
||||
// Ownership override: every Steam catalog item (pets/flashlights/emotes/outfits/promos) counts as owned
|
||||
static bool __fastcall hUserHasItem(void* thiz, unsigned long long itemDef, void* method) {
|
||||
(void)itemDef;
|
||||
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; }
|
||||
|
||||
static bool hookUpdateLocked(void* addr, const char* name, bool shouldUnlock, void (__fastcall *hook)(void*, void*)) {
|
||||
if (!addr) { std::cerr << "[DevourUnlocker] " << name << " not found\n"; logPatch((std::string("[DevourUnlocker] ")+name+" not found").c_str()); return false; }
|
||||
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; }
|
||||
(void)shouldUnlock; // always-on
|
||||
void* orig = nullptr;
|
||||
if (MH_CreateHook(addr, (void*)hook, &orig) == MH_OK) {
|
||||
MH_STATUS st = MH_CreateHook(addr, (void*)hook, &orig);
|
||||
if (st == MH_OK) {
|
||||
MH_EnableHook(addr);
|
||||
std::cout << "[DevourUnlocker] Hooked " << name << "\n";
|
||||
if (origOut) *origOut = reinterpret_cast<UpdateLockedFn>(orig);
|
||||
logPatch((std::string("[DevourUnlocker] Hooked ")+name).c_str());
|
||||
return true;
|
||||
}
|
||||
std::cerr << "[DevourUnlocker] Hook " << name << " failed\n";
|
||||
logPatch((std::string("[DevourUnlocker] Hook ")+name+" failed").c_str());
|
||||
logPatch((std::string("[DevourUnlocker] Hook ")+name+" failed st="+std::to_string((int)st)).c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
@ -154,31 +174,57 @@ bool installPatches() {
|
|||
if (ch.valid()) {
|
||||
if (MH_CreateHook(ch.address, (void*)hIsChar, (void**)&oIsChar) == MH_OK) {
|
||||
MH_EnableHook(ch.address);
|
||||
std::cout << "[DevourUnlocker] Hooked IsCharacterUnlocked\n";
|
||||
logPatch("[DevourUnlocker] Hooked IsCharacterUnlocked");
|
||||
any = true;
|
||||
} else std::cerr << "[DevourUnlocker] Hook IsChar failed\n";
|
||||
} else logPatch("[DevourUnlocker] Hook IsChar failed");
|
||||
}
|
||||
if (hasPerk.valid()) {
|
||||
if (MH_CreateHook(hasPerk.address, (void*)hHasPerk, (void**)&oHasPerk) == MH_OK) {
|
||||
MH_EnableHook(hasPerk.address);
|
||||
std::cout << "[DevourUnlocker] Hooked HasPerk\n";
|
||||
logPatch("[DevourUnlocker] Hooked HasPerk");
|
||||
any = true;
|
||||
}
|
||||
} else logPatch("[DevourUnlocker] Hook HasPerk failed");
|
||||
}
|
||||
if (hasOutfit.valid()) {
|
||||
if (MH_CreateHook(hasOutfit.address, (void*)hHasOutfit, (void**)&oHasOutfit) == MH_OK) {
|
||||
MH_EnableHook(hasOutfit.address);
|
||||
std::cout << "[DevourUnlocker] Hooked HasOutfit\n";
|
||||
logPatch("[DevourUnlocker] Hooked HasOutfit");
|
||||
any = true;
|
||||
} else logPatch("[DevourUnlocker] Hook HasOutfit failed");
|
||||
}
|
||||
// Ownership + currency overrides — the actual gates for pets/perks/flashlights/emotes/outfits
|
||||
{
|
||||
auto uhi = Il2CppResolver::userHasItem();
|
||||
auto tok = Il2CppResolver::getCurrentTokens();
|
||||
char b2[512];
|
||||
snprintf(b2, sizeof(b2), "[DevourUnlocker] Resolved UserHasItem=%p GetCurrentTokens=%p", uhi.address, tok.address);
|
||||
logPatch(b2);
|
||||
if (uhi.valid()) {
|
||||
void* orig = nullptr;
|
||||
if (MH_CreateHook(uhi.address, (void*)hUserHasItem, &orig) == MH_OK) {
|
||||
MH_EnableHook(uhi.address);
|
||||
oUserHasItem = reinterpret_cast<UserHasItemFn>(orig);
|
||||
logPatch("[DevourUnlocker] Hooked UserHasItem -> always owned");
|
||||
any = true;
|
||||
} else logPatch("[DevourUnlocker] Hook UserHasItem failed");
|
||||
} else logPatch("[DevourUnlocker] UserHasItem not found");
|
||||
if (tok.valid()) {
|
||||
void* orig = nullptr;
|
||||
if (MH_CreateHook(tok.address, (void*)hGetCurrentTokens, &orig) == MH_OK) {
|
||||
MH_EnableHook(tok.address);
|
||||
oGetTokens = reinterpret_cast<GetTokensFn>(orig);
|
||||
logPatch("[DevourUnlocker] Hooked GetCurrentTokens -> 9999999");
|
||||
any = true;
|
||||
} else logPatch("[DevourUnlocker] Hook GetCurrentTokens failed");
|
||||
} else logPatch("[DevourUnlocker] GetCurrentTokens not found");
|
||||
}
|
||||
any |= hookUpdateLocked(updPets.address, "UpdateLockedPets", Config::get().unlockPets, hUpdateLockedPets);
|
||||
any |= hookUpdateLocked(updPerks.address, "UpdateLockedPerks", Config::get().unlockPerks, hUpdateLockedPerks);
|
||||
any |= hookUpdateLocked(updFlash.address, "UpdateLockedFlashlights", Config::get().unlockFlashlights, hUpdateLockedFlashlights);
|
||||
any |= hookUpdateLocked(updEmotes.address, "UpdateLockedEmotes", Config::get().unlockEmotes, hUpdateLockedEmotes);
|
||||
any |= hookUpdateLocked(updOutfits.address, "UpdateLockedOutfits", Config::get().unlockOutfits, hUpdateLockedOutfits);
|
||||
any |= hookUpdateLocked(updRobes.address, "UpdateLockedRobes", Config::get().unlockRobes, hUpdateLockedRobes);
|
||||
any |= hookUpdateLocked(waitPet.address, "WaitForPurchasedPetValidation", Config::get().unlockPets, hWaitForPurchasedPet);
|
||||
any |= hookUpdateLocked(updPets.address, "UpdateLockedPets", Config::get().unlockPets, hUpdateLockedPets, &oUpdPets);
|
||||
any |= hookUpdateLocked(updPerks.address, "UpdateLockedPerks", Config::get().unlockPerks, hUpdateLockedPerks, &oUpdPerks);
|
||||
any |= hookUpdateLocked(updFlash.address, "UpdateLockedFlashlights", Config::get().unlockFlashlights, hUpdateLockedFlashlights, &oUpdFlash);
|
||||
any |= hookUpdateLocked(updEmotes.address, "UpdateLockedEmotes", Config::get().unlockEmotes, hUpdateLockedEmotes, &oUpdEmotes);
|
||||
any |= hookUpdateLocked(updOutfits.address, "UpdateLockedOutfits", Config::get().unlockOutfits, hUpdateLockedOutfits, &oUpdOutfits);
|
||||
any |= hookUpdateLocked(updRobes.address, "UpdateLockedRobes", Config::get().unlockRobes, hUpdateLockedRobes, &oUpdRobes);
|
||||
any |= hookUpdateLocked(waitPet.address, "WaitForPurchasedPetValidation", Config::get().unlockPets, hWaitForPurchasedPet, &oWaitPet);
|
||||
// If any hooks not found, IL2CPP may not be ready until lobby — retry for 60s
|
||||
bool needRetry = !updPets.valid() || !updPerks.valid() || !updFlash.valid() || !updEmotes.valid() || !updOutfits.valid() || !updRobes.valid() || !robe.valid() || !ch.valid() || !waitPet.valid();
|
||||
if (needRetry) {
|
||||
|
|
@ -197,17 +243,23 @@ bool installPatches() {
|
|||
auto p5 = Il2CppResolver::updateLockedOutfits();
|
||||
auto p6 = Il2CppResolver::updateLockedRobes();
|
||||
auto pw = Il2CppResolver::waitForPurchasedPet();
|
||||
auto uh = Il2CppResolver::userHasItem();
|
||||
auto tk = Il2CppResolver::getCurrentTokens();
|
||||
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"); } }
|
||||
if(p1.valid()) any2 |= hookUpdateLocked(p1.address, "UpdateLockedPets[retry]", Config::get().unlockPets, hUpdateLockedPets);
|
||||
if(p2.valid()) any2 |= hookUpdateLocked(p2.address, "UpdateLockedPerks[retry]", Config::get().unlockPerks, hUpdateLockedPerks);
|
||||
if(p3.valid()) any2 |= hookUpdateLocked(p3.address, "UpdateLockedFlashlights[retry]", Config::get().unlockFlashlights, hUpdateLockedFlashlights);
|
||||
if(p4.valid()) any2 |= hookUpdateLocked(p4.address, "UpdateLockedEmotes[retry]", Config::get().unlockEmotes, hUpdateLockedEmotes);
|
||||
if(p5.valid()) any2 |= hookUpdateLocked(p5.address, "UpdateLockedOutfits[retry]", Config::get().unlockOutfits, hUpdateLockedOutfits);
|
||||
if(p6.valid()) any2 |= hookUpdateLocked(p6.address, "UpdateLockedRobes[retry]", Config::get().unlockRobes, hUpdateLockedRobes);
|
||||
if(pw.valid()) any2 |= hookUpdateLocked(pw.address, "WaitForPurchasedPetValidation[retry]", Config::get().unlockPets, hWaitForPurchasedPet);
|
||||
if(any2){ logPatch("[DevourUnlocker] Retry hooked some — dumping diagnostics"); Il2CppResolver::dumpDiagnostics(); break; }
|
||||
if(hp.valid()){ if(MH_CreateHook(hp.address,(void*)hHasPerk,(void**)&oHasPerk)==MH_OK){ MH_EnableHook(hp.address); any2=true; logPatch("[DevourUnlocker] Retry hooked HasPerk"); } }
|
||||
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(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);
|
||||
if(p4.valid()) any2 |= hookUpdateLocked(p4.address, "UpdateLockedEmotes[retry]", Config::get().unlockEmotes, hUpdateLockedEmotes, &oUpdEmotes);
|
||||
if(p5.valid()) any2 |= hookUpdateLocked(p5.address, "UpdateLockedOutfits[retry]", Config::get().unlockOutfits, hUpdateLockedOutfits, &oUpdOutfits);
|
||||
if(p6.valid()) any2 |= hookUpdateLocked(p6.address, "UpdateLockedRobes[retry]", Config::get().unlockRobes, hUpdateLockedRobes, &oUpdRobes);
|
||||
if(pw.valid()) any2 |= hookUpdateLocked(pw.address, "WaitForPurchasedPetValidation[retry]", Config::get().unlockPets, hWaitForPurchasedPet, &oWaitPet);
|
||||
if(any2){ logPatch("[DevourUnlocker] Retry hooked missing targets — done"); break; }
|
||||
if(attempt==5 || attempt==10 || attempt==19) {
|
||||
char buf[128]; snprintf(buf,sizeof(buf),"[DevourUnlocker] Retry attempt %d still not found",attempt);
|
||||
logPatch(buf);
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue