DevourUnlocker/BepInEx/DevourUnlocker.cs
MiTHRAL 4af18a2f75
Some checks are pending
Build DevourUnlocker / windows-cross (push) Waiting to run
Build DevourUnlocker / linux (push) Waiting to run
Build DevourUnlocker / standalone (push) Waiting to run
v2.0.1: expand to all item types + fix pets + linux CI
- Config: add allPets (Crow/Pig/Goat/Rat/Spider/Cat/Pug), allPerks (29 inc Acceleration/Quartermaster etc), flashlights/emotes/outfits; unlockOutfits global
- Resolver: resolve HasPerk/HasPet/UpdateLocked* for Pets/Perks/Flashlights/Emotes/Outfits/Robes
- Patches: hook IsRobe/IsCharacter + HasPerk/HasOutfit + UpdateLockedPets/Perks/Flashlights/Emotes/Outfits/Robes bypass — fixes pets not unlocking
- GUI: 7 tabs (Robes/Chars/Perks/Pets/Flashlights/Emotes/Outfits), Unlock All covers all categories
- Standalone: --perks/--pets/--all now covers all categories, petOverrides etc, list shows 30 robes+9 chars+29 perks+7 pets+8 flashlights+12 emotes+8 outfits
- GUI standalone: 7 notebooks with per-category checkboxes
- BepInEx: add UpdateLocked* prefix patches + HasPerk/HasOutfit, GUI tabs for all
- CI: switch windows-latest to ubuntu-latest mingw cross (git.mithraic.cloud has only linux runner, windows jobs were stuck Waiting)
2026-08-21 21:15:58 -04:00

186 lines
10 KiB
C#

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<string,bool> robeOverrides = new();
public static System.Collections.Generic.Dictionary<string,bool> charOverrides = new();
public static System.Collections.Generic.Dictionary<string,bool> perkOverrides = new();
public static System.Collections.Generic.Dictionary<string,bool> 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();
}
}
}