diff --git a/BepInEx/DevourUnlocker.cs b/BepInEx/DevourUnlocker.cs index 84b8540..73f21d6 100644 --- a/BepInEx/DevourUnlocker.cs +++ b/BepInEx/DevourUnlocker.cs @@ -38,148 +38,46 @@ namespace DevourUnlocker.BepInEx } // Generic unlock patches — try multiple owner classes + // Always-on: every IsRobe/IsCharacter/Has* check returns true, every UpdateLocked* is skipped static class Patches { - [HarmonyPatch] - class IsRobeUnlockedPatch - { - static System.Reflection.MethodBase TargetMethod() - { - var types = new[] { "NolanRankController", "LobbyController", "InventoryController", "UnlockManager" }; - foreach (var t in types) { - var type = AccessTools.TypeByName(t); - if (type == null) continue; - var m = AccessTools.Method(type, "IsRobeUnlocked"); - if (m != null) { Plugin.Log?.LogInfo($"Found IsRobeUnlocked on {t}"); return m; } - } - Plugin.Log?.LogWarning("IsRobeUnlocked not found"); - return null; - } - static bool Prefix(ref bool __result, object __instance, string robeName) - { - if (!DevourGUI.configUnlockRobes) return true; - if (DevourGUI.robeOverrides.TryGetValue(robeName ?? "", out var v)) { __result = v; return false; } - __result = true; - return false; - } + static System.Reflection.MethodBase Find(string typeName, string method) { + var t = AccessTools.TypeByName(typeName); + if (t==null) return null; + var m = AccessTools.Method(t, method); + if (m!=null) Plugin.Log?.LogInfo($"Found {typeName}.{method}"); + return m; } - - [HarmonyPatch] - class IsCharacterUnlockedPatch - { - static System.Reflection.MethodBase TargetMethod() - { - var types = new[] { "NolanRankController", "LobbyController", "CharacterManager" }; - foreach (var t in types) { - var type = AccessTools.TypeByName(t); - if (type == null) continue; - var m = AccessTools.Method(type, "IsCharacterUnlocked"); - if (m != null) return m; - } - return null; - } - static bool Prefix(ref bool __result, string prefabName) - { - if (!DevourGUI.configUnlockChars) return true; - if (DevourGUI.charOverrides.TryGetValue(prefabName ?? "", out var v)) { __result = v; return false; } - __result = true; - return false; - } - } - - // UpdateLocked* patches — force unlock for pets/perks/flashlights/emotes/outfits/robes - static System.Reflection.MethodBase FindUpdate(string name) { - var types = new[] { "LobbyController", "UILobbyController", "InventoryController", "NolanRankController" }; - foreach (var t in types) { - var type = AccessTools.TypeByName(t); - if (type==null) continue; - var m = AccessTools.Method(type, name); - if (m!=null) { Plugin.Log?.LogInfo($"Found {name} on {t}"); return m; } - } + static System.Reflection.MethodBase FindAny(string method, params string[] types){ + foreach(var tn in types){ var m=Find(tn,method); if(m!=null) return m; } + Plugin.Log?.LogWarning($"{method} not found in {string.Join(",",types)}"); return null; } - [HarmonyPatch] class UpdateLockedPetsPatch { static System.Reflection.MethodBase TargetMethod()=> FindUpdate("UpdateLockedPets"); static bool Prefix()=> !DevourGUI.configUnlockPets ? true : false; } - [HarmonyPatch] class UpdateLockedPerksPatch { static System.Reflection.MethodBase TargetMethod()=> FindUpdate("UpdateLockedPerks"); static bool Prefix()=> !DevourGUI.configUnlockPerks ? true : false; } - [HarmonyPatch] class UpdateLockedFlashlightsPatch { static System.Reflection.MethodBase TargetMethod()=> FindUpdate("UpdateLockedFlashlights"); static bool Prefix()=> !DevourGUI.configUnlockFlashlights ? true : false; } - [HarmonyPatch] class UpdateLockedEmotesPatch { static System.Reflection.MethodBase TargetMethod()=> FindUpdate("UpdateLockedEmotes"); static bool Prefix()=> !DevourGUI.configUnlockEmotes ? true : false; } - [HarmonyPatch] class UpdateLockedOutfitsPatch { static System.Reflection.MethodBase TargetMethod()=> FindUpdate("UpdateLockedOutfits"); static bool Prefix()=> !DevourGUI.configUnlockOutfits ? true : false; } - [HarmonyPatch] class UpdateLockedRobesPatch { static System.Reflection.MethodBase TargetMethod()=> FindUpdate("UpdateLockedRobes"); static bool Prefix()=> !DevourGUI.configUnlockRobes ? true : false; } - - [HarmonyPatch] class HasPerkPatch { - static System.Reflection.MethodBase TargetMethod(){ var t=AccessTools.TypeByName("NolanRankController"); return t==null?null:AccessTools.Method(t,"HasPerk"); } - static bool Prefix(ref bool __result, string perkName){ if(!DevourGUI.configUnlockPerks) return true; if(DevourGUI.perkOverrides.TryGetValue(perkName??"",out var v)){__result=v; return false;} __result=true; return false;} - } - [HarmonyPatch] class HasOutfitPatch { - static System.Reflection.MethodBase TargetMethod(){ var t=AccessTools.TypeByName("NolanRankController"); return t==null?null:AccessTools.Method(t,"HasOutfit"); } - static bool Prefix(ref bool __result, string outfitName){ if(!DevourGUI.configUnlockOutfits) return true; if(DevourGUI.outfitOverrides.TryGetValue(outfitName??"",out var v)){__result=v; return false;} __result=true; return false;} - } + [HarmonyPatch] class IsRobeUnlockedPatch { static System.Reflection.MethodBase TargetMethod()=> FindAny("IsRobeUnlocked","NolanRankController","SurvivalLobbyController","LobbyController"); static bool Prefix(ref bool __result){ __result=true; return false; } } + [HarmonyPatch] class IsCharacterUnlockedPatch { static System.Reflection.MethodBase TargetMethod()=> FindAny("IsCharacterUnlocked","NolanRankController","SurvivalLobbyController"); static bool Prefix(ref bool __result){ __result=true; return false; } } + [HarmonyPatch] class HasPerkPatch { static System.Reflection.MethodBase TargetMethod()=> FindAny("HasPerk","NolanRankController"); static bool Prefix(ref bool __result){ __result=true; return false; } } + [HarmonyPatch] class HasOutfitPatch { static System.Reflection.MethodBase TargetMethod()=> FindAny("HasOutfit","NolanRankController"); static bool Prefix(ref bool __result){ __result=true; return false; } } + [HarmonyPatch] class HasCollectedAllPatch { static System.Reflection.MethodBase TargetMethod()=> FindAny("HasCollectedAllBarbedWires","NolanRankController"); static bool Prefix(ref bool __result){ __result=true; return false; } } + // UpdateLocked* — skip (return false to prevent original from marking locked) + [HarmonyPatch] class UpdateLockedPetsPatch { static System.Reflection.MethodBase TargetMethod()=> FindAny("UpdateLockedPets","SurvivalLobbyController","LobbyController"); static bool Prefix()=> false; } + [HarmonyPatch] class UpdateLockedPerksPatch { static System.Reflection.MethodBase TargetMethod()=> FindAny("UpdateLockedPerks","SurvivalLobbyController"); static bool Prefix()=> false; } + [HarmonyPatch] class UpdateLockedFlashlightsPatch { static System.Reflection.MethodBase TargetMethod()=> FindAny("UpdateLockedFlashlights","SurvivalLobbyController"); static bool Prefix()=> false; } + [HarmonyPatch] class UpdateLockedEmotesPatch { static System.Reflection.MethodBase TargetMethod()=> FindAny("UpdateLockedEmotes","SurvivalLobbyController"); static bool Prefix()=> false; } + [HarmonyPatch] class UpdateLockedOutfitsPatch { static System.Reflection.MethodBase TargetMethod()=> FindAny("UpdateLockedOutfits","SurvivalLobbyController"); static bool Prefix()=> false; } + [HarmonyPatch] class UpdateLockedRobesPatch { static System.Reflection.MethodBase TargetMethod()=> FindAny("UpdateLockedRobes","SurvivalLobbyController"); static bool Prefix()=> false; } + [HarmonyPatch] class WaitForPurchasedPetValidationPatch { static System.Reflection.MethodBase TargetMethod()=> FindAny("WaitForPurchasedPetValidation","SurvivalLobbyController"); static bool Prefix()=> false; } } static class DevourGUI { - public static bool visible = true; - public static bool configUnlockRobes = true; - public static bool configUnlockChars = true; - public static bool configUnlockPerks = true; - public static bool configUnlockPets = true; - public static bool configUnlockFlashlights = true; - public static bool configUnlockEmotes = true; - public static bool configUnlockOutfits = true; - public static System.Collections.Generic.Dictionary robeOverrides = new(); - public static System.Collections.Generic.Dictionary charOverrides = new(); - public static System.Collections.Generic.Dictionary perkOverrides = new(); - public static System.Collections.Generic.Dictionary outfitOverrides = new(); - - static string[] robes = new[] { - "StraightJacket","Patchwork","CherryBlossom","Eggs","Poison","Web", - "TownHard","TownNightmare","TownBooks","Horseshoes", - "SlaughterhouseHard","SlaughterhouseNightmare","SlaughterhousePigs","BarbedWires", - "Golden","ManorHard","ManorNightmare","ManorHeadsBuried","Feathers", - "CarnivalHard","CarnivalNightmare","CarnivalDollHeads","Tickets", - "Rose","Fire","Inmate","Electric","BlackWithGoldTrim","Hard","Nightmare" - }; - static string[] chars = new[] {"Molly","Nathan","Sam","Zara","Cultist","Kai","Anna","April","Frank"}; - static string[] perks = new[] {"Acceleration","Aftershock","Conservationist","Crawler","DuracellBunny","Evader","FastWorker","FieldMedic","FirstResponder","FullyCharged","Immune","Inspired","Keymaster","Patient","Regeneration","Speedy","SupplyRunner","Trapper","Teamwork"}; - static string[] pets = new[] {"Crow","Pig","Goat","Rat","Spider","Cat","Pug"}; - static UnityEngine.Vector2 scroll; - static string currentTab = "Robes"; - + public static bool visible = false; // always-on, GUI hidden by default (F6 to show debug) public static void Draw() { - var rect = new UnityEngine.Rect(20, 20, 600, 640); + var rect = new UnityEngine.Rect(20, 20, 400, 100); UnityEngine.GUI.Box(rect, ""); GUILayout.BeginArea(rect); - GUILayout.Label("DevourUnlocker v2 — BepInEx Edition (F6 toggle) — ALL categories"); - GUILayout.BeginHorizontal(); - if(GUILayout.Button("Robes")) currentTab="Robes"; - if(GUILayout.Button("Chars")) currentTab="Chars"; - if(GUILayout.Button("Perks")) currentTab="Perks"; - if(GUILayout.Button("Pets")) currentTab="Pets"; - GUILayout.EndHorizontal(); - configUnlockRobes = GUILayout.Toggle(configUnlockRobes, "Robes"); - configUnlockChars = GUILayout.Toggle(configUnlockChars, "Chars"); - configUnlockPerks = GUILayout.Toggle(configUnlockPerks, "Perks"); - configUnlockPets = GUILayout.Toggle(configUnlockPets, "Pets"); - configUnlockFlashlights = GUILayout.Toggle(configUnlockFlashlights, "Flashlights"); - configUnlockEmotes = GUILayout.Toggle(configUnlockEmotes, "Emotes"); - configUnlockOutfits = GUILayout.Toggle(configUnlockOutfits, "Outfits"); - if (GUILayout.Button("Unlock ALL categories")) { configUnlockRobes=configUnlockChars=configUnlockPerks=configUnlockPets=configUnlockFlashlights=configUnlockEmotes=configUnlockOutfits=true; robeOverrides.Clear(); charOverrides.Clear(); perkOverrides.Clear(); } - if (GUILayout.Button("Lock ALL")) { configUnlockRobes=configUnlockChars=configUnlockPerks=configUnlockPets=configUnlockFlashlights=configUnlockEmotes=configUnlockOutfits=false; } - string[] list = currentTab=="Robes"? robes : currentTab=="Chars"? chars : currentTab=="Perks"? perks : pets; - var dict = currentTab=="Robes"? robeOverrides : currentTab=="Chars"? charOverrides : currentTab=="Perks"? perkOverrides : null; - // pets use simple global toggle via UpdateLockedPets bypass, per-item not needed but show - GUILayout.Label($"— {currentTab} —"); - scroll = GUILayout.BeginScrollView(scroll, GUILayout.Height(260)); - if(dict!=null){ - foreach (var r in list) { - bool cur = dict.TryGetValue(r, out var ov) ? ov : true; - bool nxt = GUILayout.Toggle(cur, r); - if (nxt != cur) dict[r] = nxt; - } - } else { - GUILayout.Label("Uses global toggle (UpdateLocked* bypass)"); - } - GUILayout.EndScrollView(); + GUILayout.Label("DevourUnlocker — Always On (all robes/chars/perks/pets unlocked)"); + GUILayout.Label("F6 to toggle this debug window"); GUILayout.EndArea(); } } diff --git a/src/main.cpp b/src/main.cpp index 77e8a93..adbf3a2 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,13 +1,29 @@ #include "config.hpp" +#include "patches.hpp" +#include "il2cpp_resolver.hpp" #ifdef _WIN32 #include +#include +static void logMin(const char* m){ + char exe[MAX_PATH]; GetModuleFileNameA(nullptr,exe,MAX_PATH); + char* s=strrchr(exe,'\\'); if(!s) s=strrchr(exe,'/'); + std::string p = s? std::string(exe,s-exe)+"\\DevourUnlocker.log":"DevourUnlocker.log"; + std::ofstream f(p,std::ios::app); f<second; - // Log once per unique robe - static std::unordered_map seen; - if (!seen[name]++) std::cout << "[DevourUnlocker] Robe unlocked: " << name << "\n"; - return true; -} - -static bool __fastcall hIsChar(void* thiz, void* charStr, void* method) { - auto& cfg = Config::get(); - if (!cfg.unlockCharacters) { - return oIsChar ? oIsChar(thiz, charStr, method) : false; - } - std::string name = il2cppStringToUtf8(charStr); - auto it = cfg.characterOverrides.find(name); - if (it != cfg.characterOverrides.end()) return it->second; - static std::unordered_map seen; - if (!seen[name]++) std::cout << "[DevourUnlocker] Character unlocked: " << name << "\n"; - return true; -} - -static bool __fastcall hHasPerk(void* thiz, void* perkStr, void* method) { - auto& cfg = Config::get(); - if (!cfg.unlockPerks) return oHasPerk ? oHasPerk(thiz, perkStr, method) : false; - std::string name = il2cppStringToUtf8(perkStr); - auto it = cfg.perkOverrides.find(name); - if (it != cfg.perkOverrides.end()) return it->second; - return true; -} -static bool __fastcall hHasOutfit(void* thiz, void* outfitStr, void* method) { - auto& cfg = Config::get(); - if (!cfg.unlockOutfits) return oHasOutfit ? oHasOutfit(thiz, outfitStr, method) : false; - std::string name = il2cppStringToUtf8(outfitStr); - auto it = cfg.outfitOverrides.find(name); - if (it != cfg.outfitOverrides.end()) return it->second; - return true; -} - -// UpdateLocked* hooks: force to mark all as unlocked, then skip original caller that sets locked flag -static void __fastcall hUpdateLockedPets(void* thiz, void* method) { - auto& cfg = Config::get(); - if (!cfg.unlockPets) { - return; - } - std::cout << "[DevourUnlocker] UpdateLockedPets bypassed (pets unlocked)\n"; - logPatch("[DevourUnlocker] UpdateLockedPets bypassed"); -} -static void __fastcall hWaitForPurchasedPet(void* thiz, void* method) { - if (!Config::get().unlockPets) return; - std::cout << "[DevourUnlocker] WaitForPurchasedPetValidation bypassed\n"; - logPatch("[DevourUnlocker] WaitForPurchasedPetValidation bypassed"); -} -static void __fastcall hUpdateLockedPerks(void* thiz, void* method) { - if (!Config::get().unlockPerks) return; - std::cout << "[DevourUnlocker] UpdateLockedPerks bypassed\n"; -} -static void __fastcall hUpdateLockedFlashlights(void* thiz, void* method) { - if (!Config::get().unlockFlashlights) return; - std::cout << "[DevourUnlocker] UpdateLockedFlashlights bypassed\n"; -} -static void __fastcall hUpdateLockedEmotes(void* thiz, void* method) { - if (!Config::get().unlockEmotes) return; - std::cout << "[DevourUnlocker] UpdateLockedEmotes bypassed\n"; -} -static void __fastcall hUpdateLockedOutfits(void* thiz, void* method) { - if (!Config::get().unlockOutfits) return; - std::cout << "[DevourUnlocker] UpdateLockedOutfits bypassed\n"; -} -static void __fastcall hUpdateLockedRobes(void* thiz, void* method) { - if (!Config::get().unlockRobes) return; - std::cout << "[DevourUnlocker] UpdateLockedRobes bypassed\n"; -} +static bool __fastcall hIsRobe(void* thiz, void* robeStr, 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 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; } - if (!shouldUnlock) return false; + (void)shouldUnlock; // always-on void* orig = nullptr; if (MH_CreateHook(addr, (void*)hook, &orig) == MH_OK) { MH_EnableHook(addr);