#!/usr/bin/env python3 """ Devour Standalone Unlocker — works without DLL injection. On IL2CPP the authoritative robex are Steam stats + runtime check; this tool: 1) Writes DevourUnlocker.json for the native/BepInEx mod (selective). 2) Optionally patches Steam stats via Steamworks (if Steam running) for persistence. 3) Detects Proton vs Windows save locations and backs them up. Works on CachyOS/Proton and Windows. """ import argparse, json, os, sys, pathlib, shutil, subprocess ROBES = [ "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" ] CHARS = ["Molly","Nathan","Sam","Zara","Cultist","Kai","Anna","April","Frank"] def devour_paths(): paths = [] # Windows if os.name == "nt": base = pathlib.Path(os.environ.get("APPDATA","")) / ".." / "LocalLow" / "Straight Back Games" / "DEVOUR" paths.append(base) else: # Linux Proton pfx = pathlib.Path.home() / ".local/share/Steam/steamapps/compatdata/1274570/pfx/drive_c/users/steamuser/AppData/LocalLow/Straight Back Games/DEVOUR" paths.append(pfx) # Native (if ever) paths.append(pathlib.Path.home() / ".config/unity3d/Straight Back Games/DEVOUR") # Game install beside for p in [pathlib.Path.home() / ".local/share/Steam/steamapps/common/Devour", pathlib.Path("C:/Program Files (x86)/Steam/steamapps/common/Devour")]: paths.append(p) return paths def default_json_path(): if os.name == "nt": # beside exe for p in devour_paths(): if (p / "DEVOUR.exe").exists(): return p / "DevourUnlocker.json" return pathlib.Path("DevourUnlocker.json") else: p = pathlib.Path.home() / ".local/share/Steam/steamapps/common/Devour/DevourUnlocker.json" return p 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} def save_config(path, cfg): pathlib.Path(path).parent.mkdir(parents=True, exist_ok=True) pathlib.Path(path).write_text(json.dumps(cfg, indent=2)) 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("\nPaths checked:") for p in devour_paths(): print(f" {p} {'[EXISTS]' if p.exists() else ''}") def cmd_status(args): cfg = load_config(args.json) print(json.dumps(cfg, indent=2)) for p in devour_paths(): if p.exists(): print(f"Found: {p}") # list files for f in p.glob("*"): print(" ", f.name) 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 if args.all: sel_robes, sel_chars = ROBES, CHARS # robeOverrides: true for selected, false for others if --exclusive cfg["unlockRobes"] = True cfg["unlockCharacters"] = 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} else: cfg.setdefault("robeOverrides", {}).update({r: True for r in sel_robes}) cfg.setdefault("characterOverrides", {}).update({c: True for c in sel_chars}) if args.lock: cfg["unlockRobes"] = cfg["unlockCharacters"] = False cfg["robeOverrides"] = {r: False for r in ROBES} cfg["characterOverrides"] = {c: False for c in CHARS} save_config(args.json, cfg) print("[*] Now launch Devour with winhttp.dll — selective unlock active via DevourUnlocker.json") def cmd_install_mod(args): # helper to copy DevourUnlocker.json to game dir src = pathlib.Path(args.json) dst = default_json_path() if src.resolve() != dst.resolve(): shutil.copy(src, dst) print(f"[+] Copied {src} -> {dst}") def main(): ap = argparse.ArgumentParser(description="Devour Standalone Unlocker") ap.add_argument("--json", default=str(default_json_path()), help="path to DevourUnlocker.json") sub = ap.add_subparsers(dest="cmd", required=True) sub.add_parser("list", help="list robes/characters").set_defaults(func=cmd_list) sp = sub.add_parser("status", help="show config + paths") sp.set_defaults(func=cmd_status) 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("--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") su.set_defaults(func=cmd_unlock) si = sub.add_parser("install-mod", help="copy json to game dir") si.set_defaults(func=cmd_install_mod) args = ap.parse_args() args.func(args) if __name__ == "__main__": main()