DevourUnlocker/BepInEx/DevourUnlocker.cs
MiTHRAL 422f47f25c
Some checks are pending
Build DevourUnlocker / windows (push) Waiting to run
Build DevourUnlocker / linux (push) Waiting to run
Build DevourUnlocker / standalone (push) Waiting to run
v2.0.0: cross-platform winhttp proxy rewrite
- Replace hard offsets (0x74B0B0) with il2cpp resolver + string-xref sigscan
- winhttp.dll proxy (zero BepInEx, forwards WinHttp* exports) for Windows + Proton (WINEDLLOVERRIDES=winhttp=n,b)
- Selective ImGui GUI (F6) per-robe/character overrides, persisted to DevourUnlocker.json
- CMake cross-platform build, Linux .so verified
- Standalone Python CLI+Tkinter (CachyOS/Proton paths, compatdata/1274570)
- Install scripts pulling from git releases (install.sh/ps1), BepInEx Harmony fallback, CI build.yml
- Legacy DirectX11 Edition kept as reference, x64/Release artifacts removed
2026-08-21 15:28:02 -04:00

139 lines
5.8 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
{
// Use TargetMethod to handle both old and new class names
[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 — will retry via standalone");
return null;
}
static bool Prefix(ref bool __result, object __instance, string robeName)
{
if (!DevourGUI.configUnlockRobes) return true; // run original
if (DevourGUI.robeOverrides.TryGetValue(robeName ?? "", out var v)) { __result = v; return false; }
__result = true;
if (!string.IsNullOrEmpty(robeName)) Plugin.Log?.LogInfo($"[Robe] {robeName} -> unlocked");
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;
}
}
}
static class DevourGUI
{
public static bool visible = true;
public static bool configUnlockRobes = true;
public static bool configUnlockChars = true;
public static System.Collections.Generic.Dictionary<string,bool> robeOverrides = new();
public static System.Collections.Generic.Dictionary<string,bool> charOverrides = 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"
};
static string[] chars = new[] {"Molly","Nathan","Sam","Zara","Cultist","Kai","Anna","April","Frank"};
static UnityEngine.Vector2 scroll;
public static void Draw()
{
var rect = new UnityEngine.Rect(20, 20, 520, 600);
UnityEngine.GUI.Box(rect, "");
GUILayout.BeginArea(rect);
GUILayout.Label("DevourUnlocker v2 — BepInEx Edition (F6 toggle)");
GUILayout.Label("Multiplayer safe — client visual unlock");
configUnlockRobes = GUILayout.Toggle(configUnlockRobes, "Unlock Robes");
configUnlockChars = GUILayout.Toggle(configUnlockChars, "Unlock Characters");
if (GUILayout.Button("Unlock All")) { configUnlockRobes = configUnlockChars = true; robeOverrides.Clear(); charOverrides.Clear(); }
if (GUILayout.Button("Lock All")) { configUnlockRobes = configUnlockChars = false; }
GUILayout.Label("— Robes —");
scroll = GUILayout.BeginScrollView(scroll, GUILayout.Height(220));
foreach (var r in robes) {
bool cur = robeOverrides.TryGetValue(r, out var ov) ? ov : configUnlockRobes;
bool nxt = GUILayout.Toggle(cur, r);
if (nxt != cur) robeOverrides[r] = nxt;
}
GUILayout.EndScrollView();
GUILayout.Label("— Characters —");
foreach (var c in chars) {
bool cur = charOverrides.TryGetValue(c, out var ov) ? ov : configUnlockChars;
bool nxt = GUILayout.Toggle(cur, c);
if (nxt != cur) charOverrides[c] = nxt;
}
GUILayout.EndArea();
}
}
}