using BepInEx; using BepInEx.Logging; using HarmonyLib; using System; using System.Linq; namespace DevourUnlocker.BepInEx { [BepInPlugin("com.mithral.devourunlocker", "DevourUnlocker", "2.0.0")] public class Plugin : BaseUnityPlugin { internal static ManualLogSource Log; private Harmony _harmony; void Awake() { Log = Logger; Log.LogInfo("DevourUnlocker BepInEx v2 loaded — selective unlock"); _harmony = new Harmony("com.mithral.devourunlocker"); try { _harmony.PatchAll(); Log.LogInfo("Harmony patches applied"); } catch (Exception e) { Log.LogError(e); } } void OnGUI() { if (!DevourGUI.visible) return; DevourGUI.Draw(); } void Update() { if (UnityEngine.Input.GetKeyDown(UnityEngine.KeyCode.F6)) DevourGUI.visible = !DevourGUI.visible; } void OnDestroy() { _harmony?.UnpatchSelf(); } } // Generic unlock patches — try multiple owner classes 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; } } [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; } } 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;} } } 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 void Draw() { var rect = new UnityEngine.Rect(20, 20, 600, 640); 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.EndArea(); } } }