always-on: BepInEx + winhttp always return true, no GUI toggle
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

- BepInEx: Patches now always-on (IsRobe/IsCharacter/HasPerk/HasOutfit true, UpdateLocked* skipped, WaitForPurchasedPetValidation skipped), GUI hidden by default, no selective checks
- native winhttp: src/main.cpp minimal HookThread that waits for GameAssembly + IL2CPP domain then installPatches (no ImGui/D3D11 dummy, no AllocConsole, no libgcc dep) — fixes crash at 0xB5A02F8 strlen null
- patches: hIsRobe/hIsChar etc now always true, hookUpdateLocked always bypass, retry for 60s until lobby
- winhttp_proxy: runtime forwarding to C:\Windows\System32\winhttp.dll for all 26 WinHttp* (fixes Wine crash)
- standalone: _patch_collectables_all now exact full sets (BarbedWires 6, Feathers 2, Tickets 7 etc) not superset 8, so HasCollectedAll* passes; diagnose checks WINEDLLOVERRIDES
This commit is contained in:
MiTHRAL 2026-08-23 23:31:56 -04:00
parent c1a32f3ec3
commit 4dd7b8d393
3 changed files with 60 additions and 212 deletions

View file

@ -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<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 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();
}
}

View file

@ -1,13 +1,29 @@
#include "config.hpp"
#include "patches.hpp"
#include "il2cpp_resolver.hpp"
#ifdef _WIN32
#include <windows.h>
#include <fstream>
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<<m<<std::endl;
}
static DWORD WINAPI HookThread(LPVOID){
logMin("[DevourUnlocker] HookThread start (always-on)");
// Wait for GameAssembly before hooking
for(int i=0;i<100;i++){ if(GetModuleHandleA("GameAssembly.dll")) break; Sleep(100); }
Sleep(2000); // give IL2CPP time to init
bool ok = devour::installPatches();
logMin(ok ? "[DevourUnlocker] HookThread: patches installed" : "[DevourUnlocker] HookThread: patches not yet, will retry via installPatches retry thread");
return 0;
}
BOOL APIENTRY DllMain(HMODULE hMod, DWORD reason, LPVOID) {
if (reason == DLL_PROCESS_ATTACH) {
DisableThreadLibraryCalls(hMod);
// Minimal — do NOT hook, do NOT log, do NOT create thread.
// The standalone collectables.json patch (patched via devour_unlocker.py) is the primary unlock.
// This winhttp.dll exists only to satisfy WINEDLLOVERRIDES and prove load, not to crash.
// If you need full hooks, build with -DBUILD_FULL_HOOKS=ON and use the full main.cpp
logMin("[DevourUnlocker] DllMain attach (always-on, no ImGui)");
CreateThread(nullptr,0,HookThread,nullptr,0,nullptr);
}
return TRUE;
}

View file

@ -49,88 +49,22 @@ static std::string il2cppStringToUtf8(void* ilStr) {
return "";
}
static bool __fastcall hIsRobe(void* thiz, void* robeStr, void* method) {
auto& cfg = Config::get();
if (!cfg.unlockRobes) {
return oIsRobe ? oIsRobe(thiz, robeStr, method) : false;
}
std::string name = il2cppStringToUtf8(robeStr);
auto it = cfg.robeOverrides.find(name);
if (it != cfg.robeOverrides.end()) return it->second;
// Log once per unique robe
static std::unordered_map<std::string,int> 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<std::string,int> 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);