- 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
139 lines
5.8 KiB
C#
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();
|
|
}
|
|
}
|
|
}
|