DevourUnlocker/standalone/gui.py
MiTHRAL 422f47f25c
Some checks are pending
Build DevourUnlocker / windows (push) Waiting to run
Build DevourUnlocker / linux (push) Waiting to run
Build DevourUnlocker / standalone (push) Waiting to run
v2.0.0: cross-platform winhttp proxy rewrite
- 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
2026-08-21 15:28:02 -04:00

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()