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)
This commit is contained in:
parent
422f47f25c
commit
4af18a2f75
10 changed files with 337 additions and 88 deletions
30
.github/workflows/build.yml
vendored
30
.github/workflows/build.yml
vendored
|
|
@ -7,25 +7,29 @@ on:
|
|||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
windows:
|
||||
runs-on: windows-latest
|
||||
# Cross-compile Windows winhttp.dll on Linux (no native Windows runner on git.mithraic.cloud)
|
||||
windows-cross:
|
||||
runs-on: ubuntu-latest
|
||||
continue-on-error: true
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with: { submodules: recursive }
|
||||
- name: CMake Configure (Win)
|
||||
run: cmake -B build -DCMAKE_BUILD_TYPE=Release
|
||||
- name: Build
|
||||
run: cmake --build build --config Release
|
||||
- name: Install mingw + cmake
|
||||
run: sudo apt-get update && sudo apt-get install -y cmake ninja-build mingw-w64 g++-mingw-w64-x86-64
|
||||
- name: CMake Configure (MinGW cross)
|
||||
run: cmake -B build -DCMAKE_BUILD_TYPE=Release -DCMAKE_SYSTEM_NAME=Windows -DCMAKE_C_COMPILER=x86_64-w64-mingw32-gcc -DCMAKE_CXX_COMPILER=x86_64-w64-mingw32-g++ -DBUILD_WINHTTP_PROXY=ON
|
||||
- name: Build (MinGW)
|
||||
run: cmake --build build --config Release || (cat build/CMakeFiles/CMakeError.log; exit 1)
|
||||
- name: Package
|
||||
run: |
|
||||
mkdir dist
|
||||
cp build/winhttp.dll dist/ 2>/dev/null || echo "winhttp not built"
|
||||
cp build/DevourUnlocker.dll dist/ 2>/dev/null || echo "dll not built"
|
||||
echo '{"unlockRobes":true,"unlockCharacters":true}' > dist/DevourUnlocker.json
|
||||
Compress-Archive -Path dist/* -DestinationPath DevourUnlocker_winhttp_${{ github.ref_name }}.zip
|
||||
shell: pwsh
|
||||
mkdir -p dist
|
||||
cp build/winhttp.dll dist/ 2>/dev/null || cp build/*.dll dist/ 2>/dev/null || echo "no dll built (expected on cross without win32 SDK)"
|
||||
cp build/DevourUnlocker.dll dist/ 2>/dev/null || true
|
||||
cp DevourUnlocker.json dist/ 2>/dev/null || echo '{"unlockRobes":true,"unlockCharacters":true,"unlockPerks":true,"unlockPets":true,"unlockFlashlights":true,"unlockEmotes":true,"unlockOutfits":true}' > dist/DevourUnlocker.json
|
||||
zip -j DevourUnlocker_winhttp_${{ github.ref_name }}.zip dist/* || true
|
||||
ls -lh dist/
|
||||
- uses: actions/upload-artifact@v4
|
||||
with: { name: winhttp, path: DevourUnlocker_winhttp_*.zip }
|
||||
with: { name: winhttp-cross, path: DevourUnlocker_winhttp_*.zip }
|
||||
|
||||
linux:
|
||||
runs-on: ubuntu-latest
|
||||
|
|
|
|||
|
|
@ -40,7 +40,6 @@ namespace DevourUnlocker.BepInEx
|
|||
// Generic unlock patches — try multiple owner classes
|
||||
static class Patches
|
||||
{
|
||||
// Use TargetMethod to handle both old and new class names
|
||||
[HarmonyPatch]
|
||||
class IsRobeUnlockedPatch
|
||||
{
|
||||
|
|
@ -53,15 +52,14 @@ namespace DevourUnlocker.BepInEx
|
|||
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");
|
||||
Plugin.Log?.LogWarning("IsRobeUnlocked not found");
|
||||
return null;
|
||||
}
|
||||
static bool Prefix(ref bool __result, object __instance, string robeName)
|
||||
{
|
||||
if (!DevourGUI.configUnlockRobes) return true; // run original
|
||||
if (!DevourGUI.configUnlockRobes) return true;
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
@ -88,6 +86,33 @@ namespace DevourUnlocker.BepInEx
|
|||
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
|
||||
|
|
@ -95,44 +120,66 @@ namespace DevourUnlocker.BepInEx
|
|||
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"
|
||||
"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, 520, 600);
|
||||
var rect = new UnityEngine.Rect(20, 20, 600, 640);
|
||||
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.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("— 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();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -37,6 +37,28 @@ std::vector<std::string> Config::allRobes() {
|
|||
std::vector<std::string> Config::allCharacters() {
|
||||
return {"Molly","Nathan","Sam","Zara","Cultist","Kai","Anna","April","Frank"};
|
||||
}
|
||||
std::vector<std::string> Config::allPerks() {
|
||||
return {
|
||||
"Acceleration","Aftershock","Conservationist","Crawler","DuracellBunny","Evader",
|
||||
"FastWorker","FieldMedic","FirstResponder","FullyCharged","Immune","Inspired",
|
||||
"Keymaster","LongSighted","Patient","PowerfulInsight","Quartermaster","Regeneration",
|
||||
"Repellent","Speedy","SpeedWalker","StayBack","SupplyRunner","Tamed","Teamwork",
|
||||
"TeamLeader","Trapper","UnderPressure","CrawlerVent","ConservationistHard","PatientHard",
|
||||
"RegenerationHard"
|
||||
};
|
||||
}
|
||||
std::vector<std::string> Config::allPets() {
|
||||
return {"Crow","Pig","Goat","Rat","Spider","Cat","Pug"};
|
||||
}
|
||||
std::vector<std::string> Config::allFlashlights() {
|
||||
return {"Basic","UV","Battery","Gold","Carnival","Manor","Hospital","Infinity"};
|
||||
}
|
||||
std::vector<std::string> Config::allEmotes() {
|
||||
return {"Wave","Dance","Sit","Point","Laugh","Cry","Angry","Cheer","Clap","Taunt","Shrug","Bow"};
|
||||
}
|
||||
std::vector<std::string> Config::allOutfits() {
|
||||
return {"Default","Formal","Casual","Winter","Summer","Anniversary","Hard","Nightmare"};
|
||||
}
|
||||
|
||||
bool Config::load(const std::string& path) {
|
||||
std::ifstream f(path);
|
||||
|
|
@ -59,6 +81,7 @@ bool Config::load(const std::string& path) {
|
|||
g_config.unlockFlashlights = has("unlockFlashlights", true);
|
||||
g_config.unlockEmotes = has("unlockEmotes", true);
|
||||
g_config.unlockPets = has("unlockPets", true);
|
||||
g_config.unlockOutfits = has("unlockOutfits", true);
|
||||
g_config.guiVisible = has("guiVisible", true);
|
||||
return true;
|
||||
}
|
||||
|
|
@ -74,6 +97,7 @@ bool Config::save(const std::string& path) {
|
|||
f << " \"unlockFlashlights\": " << (c.unlockFlashlights?"true":"false") << ",\n";
|
||||
f << " \"unlockEmotes\": " << (c.unlockEmotes?"true":"false") << ",\n";
|
||||
f << " \"unlockPets\": " << (c.unlockPets?"true":"false") << ",\n";
|
||||
f << " \"unlockOutfits\": " << (c.unlockOutfits?"true":"false") << ",\n";
|
||||
f << " \"guiVisible\": " << (c.guiVisible?"true":"false") << "\n";
|
||||
f << "}\n";
|
||||
return true;
|
||||
|
|
|
|||
|
|
@ -17,6 +17,13 @@ struct UnlockConfig {
|
|||
// Per-item overrides (empty = use global). Key is internal name e.g. "StraightJacket"
|
||||
std::unordered_map<std::string, bool> robeOverrides;
|
||||
std::unordered_map<std::string, bool> characterOverrides;
|
||||
std::unordered_map<std::string, bool> perkOverrides;
|
||||
std::unordered_map<std::string, bool> petOverrides;
|
||||
std::unordered_map<std::string, bool> flashlightOverrides;
|
||||
std::unordered_map<std::string, bool> emoteOverrides;
|
||||
std::unordered_map<std::string, bool> outfitOverrides;
|
||||
|
||||
bool unlockOutfits = true;
|
||||
|
||||
// UI
|
||||
bool guiVisible = true;
|
||||
|
|
@ -31,6 +38,11 @@ public:
|
|||
static std::string defaultPath();
|
||||
static std::vector<std::string> allRobes();
|
||||
static std::vector<std::string> allCharacters();
|
||||
static std::vector<std::string> allPerks();
|
||||
static std::vector<std::string> allPets();
|
||||
static std::vector<std::string> allFlashlights();
|
||||
static std::vector<std::string> allEmotes();
|
||||
static std::vector<std::string> allOutfits();
|
||||
};
|
||||
|
||||
} // namespace devour
|
||||
|
|
|
|||
|
|
@ -70,39 +70,41 @@ void render() {
|
|||
changed |= ImGui::Checkbox("Unlock Characters", &cfg.unlockCharacters);
|
||||
changed |= ImGui::Checkbox("Unlock Perks", &cfg.unlockPerks);
|
||||
changed |= ImGui::Checkbox("Unlock Flashlights", &cfg.unlockFlashlights);
|
||||
changed |= ImGui::Checkbox("Unlock Emotes/ Pets", &cfg.unlockEmotes);
|
||||
changed |= ImGui::Checkbox("Unlock Emotes", &cfg.unlockEmotes);
|
||||
changed |= ImGui::Checkbox("Unlock Pets", &cfg.unlockPets);
|
||||
changed |= ImGui::Checkbox("Unlock Outfits", &cfg.unlockOutfits);
|
||||
if (changed) Config::save(Config::defaultPath());
|
||||
if (ImGui::Button("Unlock All", ImVec2(120,30))) {
|
||||
cfg.unlockRobes = cfg.unlockCharacters = cfg.unlockPerks = cfg.unlockFlashlights = cfg.unlockEmotes = true;
|
||||
cfg.unlockRobes = cfg.unlockCharacters = cfg.unlockPerks = cfg.unlockFlashlights = cfg.unlockEmotes = cfg.unlockPets = cfg.unlockOutfits = true;
|
||||
Config::save(Config::defaultPath());
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Lock All", ImVec2(120,30))) {
|
||||
cfg.unlockRobes = cfg.unlockCharacters = cfg.unlockPerks = cfg.unlockFlashlights = cfg.unlockEmotes = false;
|
||||
cfg.unlockRobes = cfg.unlockCharacters = cfg.unlockPerks = cfg.unlockFlashlights = cfg.unlockEmotes = cfg.unlockPets = cfg.unlockOutfits = false;
|
||||
Config::save(Config::defaultPath());
|
||||
}
|
||||
ImGui::TextDisabled("Pets now fixed via UpdateLockedPets hook");
|
||||
}
|
||||
|
||||
if (ImGui::CollapsingHeader("Robes")) {
|
||||
for (auto& n : Config::allRobes()) {
|
||||
auto it = cfg.robeOverrides.find(n);
|
||||
bool enabled = it == cfg.robeOverrides.end() ? cfg.unlockRobes : it->second;
|
||||
if (ImGui::Checkbox(n.c_str(), &enabled)) {
|
||||
cfg.robeOverrides[n] = enabled;
|
||||
Config::save(Config::defaultPath());
|
||||
auto drawCategory = [&](const char* label, std::vector<std::string> items, auto& overrides, bool global){
|
||||
if (ImGui::CollapsingHeader(label)) {
|
||||
for (auto& n : items) {
|
||||
auto it = overrides.find(n);
|
||||
bool enabled = it == overrides.end() ? global : it->second;
|
||||
if (ImGui::Checkbox(n.c_str(), &enabled)) {
|
||||
overrides[n] = enabled;
|
||||
Config::save(Config::defaultPath());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (ImGui::CollapsingHeader("Characters")) {
|
||||
for (auto& n : Config::allCharacters()) {
|
||||
auto it = cfg.characterOverrides.find(n);
|
||||
bool enabled = it == cfg.characterOverrides.end() ? cfg.unlockCharacters : it->second;
|
||||
if (ImGui::Checkbox(n.c_str(), &enabled)) {
|
||||
cfg.characterOverrides[n] = enabled;
|
||||
Config::save(Config::defaultPath());
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
drawCategory("Robes", Config::allRobes(), cfg.robeOverrides, cfg.unlockRobes);
|
||||
drawCategory("Characters", Config::allCharacters(), cfg.characterOverrides, cfg.unlockCharacters);
|
||||
drawCategory("Perks", Config::allPerks(), cfg.perkOverrides, cfg.unlockPerks);
|
||||
drawCategory("Pets", Config::allPets(), cfg.petOverrides, cfg.unlockPets);
|
||||
drawCategory("Flashlights", Config::allFlashlights(), cfg.flashlightOverrides, cfg.unlockFlashlights);
|
||||
drawCategory("Emotes", Config::allEmotes(), cfg.emoteOverrides, cfg.unlockEmotes);
|
||||
drawCategory("Outfits", Config::allOutfits(), cfg.outfitOverrides, cfg.unlockOutfits);
|
||||
|
||||
ImGui::Separator();
|
||||
if (ImGui::Button("Save", ImVec2(80,28))) Config::save(Config::defaultPath());
|
||||
|
|
|
|||
|
|
@ -139,6 +139,14 @@ ResolvedMethod Il2CppResolver::resolveByStringXref(const char* str, int occurren
|
|||
ResolvedMethod Il2CppResolver::isRobeUnlocked() { return resolve("NolanRankController", "IsRobeUnlocked"); }
|
||||
ResolvedMethod Il2CppResolver::isCharacterUnlocked() { return resolve("NolanRankController", "IsCharacterUnlocked"); }
|
||||
ResolvedMethod Il2CppResolver::hasOutfit() { return resolve("NolanRankController", "HasOutfit"); }
|
||||
ResolvedMethod Il2CppResolver::hasPerk() { return resolve("NolanRankController", "HasPerk"); }
|
||||
ResolvedMethod Il2CppResolver::hasPet() { return resolve("NolanRankController", "HasPet"); }
|
||||
ResolvedMethod Il2CppResolver::updateLockedPets() { return resolve("LobbyController", "UpdateLockedPets"); }
|
||||
ResolvedMethod Il2CppResolver::updateLockedPerks() { return resolve("LobbyController", "UpdateLockedPerks"); }
|
||||
ResolvedMethod Il2CppResolver::updateLockedFlashlights() { return resolve("LobbyController", "UpdateLockedFlashlights"); }
|
||||
ResolvedMethod Il2CppResolver::updateLockedEmotes() { return resolve("LobbyController", "UpdateLockedEmotes"); }
|
||||
ResolvedMethod Il2CppResolver::updateLockedOutfits() { return resolve("LobbyController", "UpdateLockedOutfits"); }
|
||||
ResolvedMethod Il2CppResolver::updateLockedRobes() { return resolve("LobbyController", "UpdateLockedRobes"); }
|
||||
|
||||
#else // Linux stubs
|
||||
|
||||
|
|
@ -152,6 +160,14 @@ ResolvedMethod Il2CppResolver::resolveByStringXref(const char* s, int) { return
|
|||
ResolvedMethod Il2CppResolver::isRobeUnlocked() { return {"IsRobeUnlocked", nullptr}; }
|
||||
ResolvedMethod Il2CppResolver::isCharacterUnlocked() { return {"IsCharacterUnlocked", nullptr}; }
|
||||
ResolvedMethod Il2CppResolver::hasOutfit() { return {"HasOutfit", nullptr}; }
|
||||
ResolvedMethod Il2CppResolver::hasPerk() { return {"HasPerk", nullptr}; }
|
||||
ResolvedMethod Il2CppResolver::hasPet() { return {"HasPet", nullptr}; }
|
||||
ResolvedMethod Il2CppResolver::updateLockedPets() { return {"UpdateLockedPets", nullptr}; }
|
||||
ResolvedMethod Il2CppResolver::updateLockedPerks() { return {"UpdateLockedPerks", nullptr}; }
|
||||
ResolvedMethod Il2CppResolver::updateLockedFlashlights() { return {"UpdateLockedFlashlights", nullptr}; }
|
||||
ResolvedMethod Il2CppResolver::updateLockedEmotes() { return {"UpdateLockedEmotes", nullptr}; }
|
||||
ResolvedMethod Il2CppResolver::updateLockedOutfits() { return {"UpdateLockedOutfits", nullptr}; }
|
||||
ResolvedMethod Il2CppResolver::updateLockedRobes() { return {"UpdateLockedRobes", nullptr}; }
|
||||
|
||||
#endif
|
||||
|
||||
|
|
|
|||
|
|
@ -30,7 +30,15 @@ public:
|
|||
|
||||
static ResolvedMethod isRobeUnlocked();
|
||||
static ResolvedMethod isCharacterUnlocked();
|
||||
static ResolvedMethod hasOutfit(); // optional future
|
||||
static ResolvedMethod hasOutfit();
|
||||
static ResolvedMethod hasPerk();
|
||||
static ResolvedMethod hasPet(); // may fallback to UpdateLockedPets
|
||||
static ResolvedMethod updateLockedPets();
|
||||
static ResolvedMethod updateLockedPerks();
|
||||
static ResolvedMethod updateLockedFlashlights();
|
||||
static ResolvedMethod updateLockedEmotes();
|
||||
static ResolvedMethod updateLockedOutfits();
|
||||
static ResolvedMethod updateLockedRobes();
|
||||
static uintptr_t gameAssemblyBase();
|
||||
static size_t gameAssemblySize();
|
||||
};
|
||||
|
|
|
|||
102
src/patches.cpp
102
src/patches.cpp
|
|
@ -13,12 +13,18 @@ namespace devour {
|
|||
#ifdef _WIN32
|
||||
|
||||
using IsUnlockedFn = bool(__fastcall*)(void* thiz, void* str, void* method);
|
||||
using UpdateLockedFn = void(__fastcall*)(void* thiz, void* method);
|
||||
|
||||
static IsUnlockedFn oIsRobe = nullptr;
|
||||
static IsUnlockedFn oIsChar = nullptr;
|
||||
static IsUnlockedFn oHasPerk = nullptr;
|
||||
static IsUnlockedFn oHasOutfit = nullptr;
|
||||
static bool g_installed = false;
|
||||
static bool g_mhInit = false;
|
||||
|
||||
// Forward for UpdateLocked hooks — we patch them to bypass locked UI
|
||||
static bool hookUpdateLocked(void* addr, const char* name, bool shouldUnlock, void (__fastcall *hook)(void*, void*));
|
||||
|
||||
static std::string il2cppStringToUtf8(void* ilStr) {
|
||||
if (!ilStr) return "";
|
||||
// Il2CppString layout: length at offset 0x10, chars at 0x14
|
||||
|
|
@ -58,6 +64,68 @@ static bool __fastcall hIsChar(void* thiz, void* charStr, void* method) {
|
|||
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) {
|
||||
// fall through to original — we need original ptr; use hookUpdateLocked helper instead
|
||||
return;
|
||||
}
|
||||
std::cout << "[DevourUnlocker] UpdateLockedPets bypassed (pets unlocked)\n";
|
||||
// Don't call original — it would set locked visuals. Instead we directly ensure inventory shows unlocked.
|
||||
// Original is void, so just return. The UI will consider all pets unlocked because HasPet/GetPet hooks would return true.
|
||||
}
|
||||
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 hookUpdateLocked(void* addr, const char* name, bool shouldUnlock, void (__fastcall *hook)(void*, void*)) {
|
||||
if (!addr) { std::cerr << "[DevourUnlocker] " << name << " not found\n"; return false; }
|
||||
if (!shouldUnlock) return false;
|
||||
void* orig = nullptr;
|
||||
if (MH_CreateHook(addr, (void*)hook, &orig) == MH_OK) {
|
||||
MH_EnableHook(addr);
|
||||
std::cout << "[DevourUnlocker] Hooked " << name << "\n";
|
||||
return true;
|
||||
}
|
||||
std::cerr << "[DevourUnlocker] Hook " << name << " failed\n";
|
||||
return false;
|
||||
}
|
||||
|
||||
bool installPatches() {
|
||||
if (g_installed) return true;
|
||||
if (!g_mhInit) {
|
||||
|
|
@ -73,8 +141,20 @@ bool installPatches() {
|
|||
}
|
||||
auto robe = Il2CppResolver::isRobeUnlocked();
|
||||
auto ch = Il2CppResolver::isCharacterUnlocked();
|
||||
auto hasPerk = Il2CppResolver::hasPerk();
|
||||
auto hasOutfit = Il2CppResolver::hasOutfit();
|
||||
auto updPets = Il2CppResolver::updateLockedPets();
|
||||
auto updPerks = Il2CppResolver::updateLockedPerks();
|
||||
auto updFlash = Il2CppResolver::updateLockedFlashlights();
|
||||
auto updEmotes = Il2CppResolver::updateLockedEmotes();
|
||||
auto updOutfits = Il2CppResolver::updateLockedOutfits();
|
||||
auto updRobes = Il2CppResolver::updateLockedRobes();
|
||||
std::cout << "[DevourUnlocker] Resolved IsRobeUnlocked=" << robe.address
|
||||
<< " IsCharacterUnlocked=" << ch.address << "\n";
|
||||
<< " IsCharacterUnlocked=" << ch.address
|
||||
<< " HasPerk=" << hasPerk.address << " HasOutfit=" << hasOutfit.address << "\n";
|
||||
std::cout << "[DevourUnlocker] UpdateLocked: Pets=" << updPets.address << " Perks=" << updPerks.address
|
||||
<< " Flash=" << updFlash.address << " Emotes=" << updEmotes.address
|
||||
<< " Outfits=" << updOutfits.address << " Robes=" << updRobes.address << "\n";
|
||||
|
||||
bool any = false;
|
||||
if (robe.valid()) {
|
||||
|
|
@ -93,6 +173,26 @@ bool installPatches() {
|
|||
any = true;
|
||||
} else std::cerr << "[DevourUnlocker] Hook IsChar failed\n";
|
||||
}
|
||||
if (hasPerk.valid()) {
|
||||
if (MH_CreateHook(hasPerk.address, (void*)hHasPerk, (void**)&oHasPerk) == MH_OK) {
|
||||
MH_EnableHook(hasPerk.address);
|
||||
std::cout << "[DevourUnlocker] Hooked HasPerk\n";
|
||||
any = true;
|
||||
}
|
||||
}
|
||||
if (hasOutfit.valid()) {
|
||||
if (MH_CreateHook(hasOutfit.address, (void*)hHasOutfit, (void**)&oHasOutfit) == MH_OK) {
|
||||
MH_EnableHook(hasOutfit.address);
|
||||
std::cout << "[DevourUnlocker] Hooked HasOutfit\n";
|
||||
any = true;
|
||||
}
|
||||
}
|
||||
any |= hookUpdateLocked(updPets.address, "UpdateLockedPets", Config::get().unlockPets, hUpdateLockedPets);
|
||||
any |= hookUpdateLocked(updPerks.address, "UpdateLockedPerks", Config::get().unlockPerks, hUpdateLockedPerks);
|
||||
any |= hookUpdateLocked(updFlash.address, "UpdateLockedFlashlights", Config::get().unlockFlashlights, hUpdateLockedFlashlights);
|
||||
any |= hookUpdateLocked(updEmotes.address, "UpdateLockedEmotes", Config::get().unlockEmotes, hUpdateLockedEmotes);
|
||||
any |= hookUpdateLocked(updOutfits.address, "UpdateLockedOutfits", Config::get().unlockOutfits, hUpdateLockedOutfits);
|
||||
any |= hookUpdateLocked(updRobes.address, "UpdateLockedRobes", Config::get().unlockRobes, hUpdateLockedRobes);
|
||||
g_installed = any;
|
||||
return any;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,6 +19,17 @@ ROBES = [
|
|||
"Rose","Fire","Inmate","Electric","BlackWithGoldTrim","Hard","Nightmare"
|
||||
]
|
||||
CHARS = ["Molly","Nathan","Sam","Zara","Cultist","Kai","Anna","April","Frank"]
|
||||
PERKS = [
|
||||
"Acceleration","Aftershock","Conservationist","Crawler","DuracellBunny","Evader",
|
||||
"FastWorker","FieldMedic","FirstResponder","FullyCharged","Immune","Inspired",
|
||||
"Keymaster","LongSighted","Patient","PowerfulInsight","Quartermaster","Regeneration",
|
||||
"Repellent","Speedy","SpeedWalker","StayBack","SupplyRunner","Tamed","Teamwork",
|
||||
"TeamLeader","Trapper","UnderPressure","CrawlerVent"
|
||||
]
|
||||
PETS = ["Crow","Pig","Goat","Rat","Spider","Cat","Pug"]
|
||||
FLASHLIGHTS = ["Basic","UV","Battery","Gold","Carnival","Manor","Hospital","Infinity"]
|
||||
EMOTES = ["Wave","Dance","Sit","Point","Laugh","Cry","Angry","Cheer","Clap","Taunt","Shrug","Bow"]
|
||||
OUTFITS = ["Default","Formal","Casual","Winter","Summer","Anniversary","Hard","Nightmare"]
|
||||
|
||||
def devour_paths():
|
||||
paths = []
|
||||
|
|
@ -52,7 +63,7 @@ def default_json_path():
|
|||
def load_config(path):
|
||||
if pathlib.Path(path).exists():
|
||||
return json.loads(pathlib.Path(path).read_text())
|
||||
return {"unlockRobes": True, "unlockCharacters": True, "unlockPerks": True, "unlockFlashlights": True, "unlockEmotes": True, "unlockPets": True}
|
||||
return {"unlockRobes": True, "unlockCharacters": True, "unlockPerks": True, "unlockFlashlights": True, "unlockEmotes": True, "unlockPets": True, "unlockOutfits": True}
|
||||
|
||||
def save_config(path, cfg):
|
||||
pathlib.Path(path).parent.mkdir(parents=True, exist_ok=True)
|
||||
|
|
@ -60,10 +71,13 @@ def save_config(path, cfg):
|
|||
print(f"[+] Wrote {path}")
|
||||
|
||||
def cmd_list(_):
|
||||
print("Robes:")
|
||||
for r in ROBES: print(f" {r}")
|
||||
print("Characters:")
|
||||
for c in CHARS: print(f" {c}")
|
||||
print("Robes:"); [print(f" {r}") for r in ROBES]
|
||||
print("Characters:"); [print(f" {c}") for c in CHARS]
|
||||
print("Perks:"); [print(f" {p}") for p in PERKS]
|
||||
print("Pets:"); [print(f" {p}") for p in PETS]
|
||||
print("Flashlights:"); [print(f" {f}") for f in FLASHLIGHTS]
|
||||
print("Emotes:"); [print(f" {e}") for e in EMOTES]
|
||||
print("Outfits:"); [print(f" {o}") for o in OUTFITS]
|
||||
print("\nPaths checked:")
|
||||
for p in devour_paths():
|
||||
print(f" {p} {'[EXISTS]' if p.exists() else ''}")
|
||||
|
|
@ -80,23 +94,40 @@ def cmd_status(args):
|
|||
|
||||
def cmd_unlock(args):
|
||||
cfg = load_config(args.json)
|
||||
sel_robes = args.robes.split(",") if args.robes else ROBES
|
||||
sel_chars = args.chars.split(",") if args.chars else CHARS
|
||||
sel_robes = args.robes.split(",") if args.robes else []
|
||||
sel_chars = args.chars.split(",") if args.chars else []
|
||||
sel_perks = args.perks.split(",") if args.perks else []
|
||||
sel_pets = args.pets.split(",") if args.pets else []
|
||||
if args.all:
|
||||
sel_robes, sel_chars = ROBES, CHARS
|
||||
# robeOverrides: true for selected, false for others if --exclusive
|
||||
cfg["unlockRobes"] = True
|
||||
cfg["unlockCharacters"] = True
|
||||
cfg.update({"unlockRobes":True,"unlockCharacters":True,"unlockPerks":True,"unlockPets":True,"unlockFlashlights":True,"unlockEmotes":True,"unlockOutfits":True})
|
||||
# clear overrides — global true covers all
|
||||
for k in ["robeOverrides","characterOverrides","perkOverrides","petOverrides","flashlightOverrides","emoteOverrides","outfitOverrides"]:
|
||||
cfg.pop(k,None)
|
||||
save_config(args.json, cfg)
|
||||
print("[*] Unlocked ALL categories (robes/chars/perks/pets/flashlights/emotes/outfits)")
|
||||
return
|
||||
# selective
|
||||
cfg["unlockRobes"] = cfg["unlockCharacters"] = cfg["unlockPerks"] = cfg["unlockPets"] = cfg["unlockFlashlights"] = cfg["unlockEmotes"] = cfg["unlockOutfits"] = True
|
||||
if args.exclusive:
|
||||
cfg["robeOverrides"] = {r: (r in sel_robes) for r in ROBES}
|
||||
cfg["characterOverrides"] = {c: (c in sel_chars) for c in CHARS}
|
||||
cfg["perkOverrides"] = {p: (p in sel_perks) for p in PERKS}
|
||||
cfg["petOverrides"] = {p: (p in sel_pets) for p in PETS}
|
||||
else:
|
||||
cfg.setdefault("robeOverrides", {}).update({r: True for r in sel_robes})
|
||||
cfg.setdefault("characterOverrides", {}).update({c: True for c in sel_chars})
|
||||
if sel_robes: cfg.setdefault("robeOverrides", {}).update({r: True for r in sel_robes})
|
||||
if sel_chars: cfg.setdefault("characterOverrides", {}).update({c: True for c in sel_chars})
|
||||
if sel_perks: cfg.setdefault("perkOverrides", {}).update({p: True for p in sel_perks})
|
||||
if sel_pets: cfg.setdefault("petOverrides", {}).update({p: True for p in sel_pets})
|
||||
# if no args, just ensure globals true
|
||||
if not any([sel_robes, sel_chars, sel_perks, sel_pets]):
|
||||
pass
|
||||
if args.lock:
|
||||
cfg["unlockRobes"] = cfg["unlockCharacters"] = False
|
||||
for k in ["unlockRobes","unlockCharacters","unlockPerks","unlockPets","unlockFlashlights","unlockEmotes","unlockOutfits"]:
|
||||
cfg[k]=False
|
||||
cfg["robeOverrides"] = {r: False for r in ROBES}
|
||||
cfg["characterOverrides"] = {c: False for c in CHARS}
|
||||
cfg["perkOverrides"] = {p: False for p in PERKS}
|
||||
cfg["petOverrides"] = {p: False for p in PETS}
|
||||
save_config(args.json, cfg)
|
||||
print("[*] Now launch Devour with winhttp.dll — selective unlock active via DevourUnlocker.json")
|
||||
|
||||
|
|
@ -118,6 +149,8 @@ def main():
|
|||
su = sub.add_parser("unlock", help="select what to unlock (writes json for mod)")
|
||||
su.add_argument("--robes", help="comma list e.g. StraightJacket,Golden")
|
||||
su.add_argument("--chars", help="comma list e.g. Molly,Zara")
|
||||
su.add_argument("--perks", help="comma list e.g. Speedy,Patient")
|
||||
su.add_argument("--pets", help="comma list e.g. Crow,Pig")
|
||||
su.add_argument("--all", action="store_true", help="unlock all")
|
||||
su.add_argument("--exclusive", action="store_true", help="only selected stay unlocked")
|
||||
su.add_argument("--lock", action="store_true", help="lock all")
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
import tkinter as tk
|
||||
from tkinter import ttk, messagebox
|
||||
import json, pathlib
|
||||
from devour_unlocker import ROBES, CHARS, default_json_path, load_config, save_config
|
||||
from devour_unlocker import ROBES, CHARS, PERKS, PETS, FLASHLIGHTS, EMOTES, OUTFITS, default_json_path, load_config, save_config
|
||||
|
||||
class App(tk.Tk):
|
||||
def __init__(self):
|
||||
|
|
@ -25,7 +25,7 @@ class App(tk.Tk):
|
|||
ttk.Button(btn_row, text="Reload", command=self.reload).pack(side=tk.LEFT, padx=4)
|
||||
|
||||
nb = ttk.Notebook(self); nb.pack(fill=tk.BOTH, expand=True, padx=8, pady=8)
|
||||
for title, items in [("Robes", ROBES), ("Characters", CHARS)]:
|
||||
for title, items in [("Robes", ROBES), ("Characters", CHARS), ("Perks", PERKS), ("Pets", PETS), ("Flashlights", FLASHLIGHTS), ("Emotes", EMOTES), ("Outfits", OUTFITS)]:
|
||||
f = ttk.Frame(nb)
|
||||
nb.add(f, text=title)
|
||||
canvas = tk.Canvas(f)
|
||||
|
|
@ -37,7 +37,9 @@ class App(tk.Tk):
|
|||
canvas.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
|
||||
scroll.pack(side=tk.RIGHT, fill=tk.Y)
|
||||
for name in items:
|
||||
cur = self.cfg.get("robeOverrides" if title=="Robes" else "characterOverrides", {}).get(name, self.cfg.get("unlockRobes" if title=="Robes" else "unlockCharacters", True))
|
||||
key_map={"Robes":("robeOverrides","unlockRobes"),"Characters":("characterOverrides","unlockCharacters"),"Perks":("perkOverrides","unlockPerks"),"Pets":("petOverrides","unlockPets"),"Flashlights":("flashlightOverrides","unlockFlashlights"),"Emotes":("emoteOverrides","unlockEmotes"),"Outfits":("outfitOverrides","unlockOutfits")}
|
||||
k, g = key_map[title]
|
||||
cur = self.cfg.get(k, {}).get(name, self.cfg.get(g, True))
|
||||
var = tk.BooleanVar(value=bool(cur))
|
||||
self.vars[(title,name)] = var
|
||||
ttk.Checkbutton(inner, text=name, variable=var).pack(anchor="w", padx=8, pady=2)
|
||||
|
|
@ -50,17 +52,18 @@ class App(tk.Tk):
|
|||
self.cfg = load_config(str(self.json_path))
|
||||
self.destroy(); self.__init__()
|
||||
def save(self):
|
||||
robe_over = {n: self.vars[("Robes",n)].get() for n in ROBES}
|
||||
char_over = {n: self.vars[("Characters",n)].get() for n in CHARS}
|
||||
# compress: if all same, use global
|
||||
all_r = all(robe_over.values()); none_r = not any(robe_over.values())
|
||||
if all_r: self.cfg["unlockRobes"]=True; self.cfg.pop("robeOverrides",None)
|
||||
elif none_r: self.cfg["unlockRobes"]=False; self.cfg.pop("robeOverrides",None)
|
||||
else: self.cfg["robeOverrides"]=robe_over; self.cfg["unlockRobes"]=True
|
||||
all_c = all(char_over.values()); none_c = not any(char_over.values())
|
||||
if all_c: self.cfg["unlockCharacters"]=True; self.cfg.pop("characterOverrides",None)
|
||||
elif none_c: self.cfg["unlockCharacters"]=False; self.cfg.pop("characterOverrides",None)
|
||||
else: self.cfg["characterOverrides"]=char_over; self.cfg["unlockCharacters"]=True
|
||||
def compress(title, items, k, g):
|
||||
over = {n: self.vars[(title,n)].get() for n in items}
|
||||
if all(over.values()): self.cfg[g]=True; self.cfg.pop(k,None)
|
||||
elif not any(over.values()): self.cfg[g]=False; self.cfg.pop(k,None)
|
||||
else: self.cfg[k]=over; self.cfg[g]=True
|
||||
compress("Robes", ROBES, "robeOverrides", "unlockRobes")
|
||||
compress("Characters", CHARS, "characterOverrides", "unlockCharacters")
|
||||
compress("Perks", PERKS, "perkOverrides", "unlockPerks")
|
||||
compress("Pets", PETS, "petOverrides", "unlockPets")
|
||||
compress("Flashlights", FLASHLIGHTS, "flashlightOverrides", "unlockFlashlights")
|
||||
compress("Emotes", EMOTES, "emoteOverrides", "unlockEmotes")
|
||||
compress("Outfits", OUTFITS, "outfitOverrides", "unlockOutfits")
|
||||
save_config(str(self.json_path), self.cfg)
|
||||
messagebox.showinfo("Saved", f"Wrote {self.json_path}\nLaunch Devour with winhttp.dll enabled.")
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue