- 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
68 lines
3.6 KiB
Python
Executable file
68 lines
3.6 KiB
Python
Executable file
#!/usr/bin/env python3
|
|
"""Tkinter GUI for standalone — selective checkboxes, writes DevourUnlocker.json"""
|
|
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
|
|
|
|
class App(tk.Tk):
|
|
def __init__(self):
|
|
super().__init__()
|
|
self.title("DevourUnlocker — Selective Unlock")
|
|
self.geometry("560x680")
|
|
self.json_path = default_json_path()
|
|
self.cfg = load_config(str(self.json_path))
|
|
self.vars = {}
|
|
self._build()
|
|
|
|
def _build(self):
|
|
ttk.Label(self, text="DevourUnlocker v2 — Standalone (multiplayer safe)").pack(pady=6)
|
|
ttk.Label(self, text=f"Config: {self.json_path}").pack()
|
|
btn_row = ttk.Frame(self); btn_row.pack(pady=6)
|
|
ttk.Button(btn_row, text="Unlock All", command=self.unlock_all).pack(side=tk.LEFT, padx=4)
|
|
ttk.Button(btn_row, text="Lock All", command=self.lock_all).pack(side=tk.LEFT, padx=4)
|
|
ttk.Button(btn_row, text="Save", command=self.save).pack(side=tk.LEFT, padx=4)
|
|
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)]:
|
|
f = ttk.Frame(nb)
|
|
nb.add(f, text=title)
|
|
canvas = tk.Canvas(f)
|
|
scroll = ttk.Scrollbar(f, command=canvas.yview)
|
|
inner = ttk.Frame(canvas)
|
|
inner.bind("<Configure>", lambda e, c=canvas: c.configure(scrollregion=c.bbox("all")))
|
|
canvas.create_window((0,0), window=inner, anchor="nw")
|
|
canvas.configure(yscrollcommand=scroll.set)
|
|
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))
|
|
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)
|
|
|
|
def unlock_all(self):
|
|
for v in self.vars.values(): v.set(True)
|
|
def lock_all(self):
|
|
for v in self.vars.values(): v.set(False)
|
|
def reload(self):
|
|
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
|
|
save_config(str(self.json_path), self.cfg)
|
|
messagebox.showinfo("Saved", f"Wrote {self.json_path}\nLaunch Devour with winhttp.dll enabled.")
|
|
|
|
if __name__ == "__main__":
|
|
App().mainloop()
|