feat: add live-updating Tweaks UI and core packages
This commit is contained in:
@@ -0,0 +1,203 @@
|
||||
import os
|
||||
import shutil
|
||||
import tkinter as tk
|
||||
from tkinter import ttk, messagebox
|
||||
|
||||
import remote_packages
|
||||
from setup_tool import WowSetupTool, ToolTip, get_base_path
|
||||
|
||||
|
||||
class ModernWowSetupTool(WowSetupTool):
|
||||
"""Feature-branch entry point adding live GitHub package updates."""
|
||||
|
||||
def __init__(self, root):
|
||||
# Define this before WowSetupTool.__init__ because the parent constructor
|
||||
# calls self.build_ui(), which dispatches to our overridden Plugins tab.
|
||||
self.client_tweak = tk.StringVar(master=root, value="none")
|
||||
super().__init__(root)
|
||||
|
||||
def build_plugins_tab(self, parent):
|
||||
container = ttk.Frame(parent)
|
||||
container.pack(fill="both", expand=True, padx=10, pady=10)
|
||||
|
||||
top = ttk.Frame(container)
|
||||
top.pack(fill="both", expand=True)
|
||||
|
||||
left_frame = ttk.LabelFrame(top, text="Recommended Core")
|
||||
left_frame.pack(side="left", fill="both", expand=True, padx=(0, 5))
|
||||
|
||||
ttk.Label(
|
||||
left_frame,
|
||||
text="These are highly recommended for performance\nand client stability.",
|
||||
font=("", 8, "italic"),
|
||||
).pack(anchor="w", padx=10, pady=10)
|
||||
|
||||
for dll, var in self.core_plugins.items():
|
||||
cb = ttk.Checkbutton(left_frame, text=dll, variable=var)
|
||||
cb.pack(anchor="w", padx=10, pady=4)
|
||||
ToolTip(cb, self.descriptions.get(dll, ""))
|
||||
|
||||
right_frame = ttk.LabelFrame(top, text="Optional WeirdUtils")
|
||||
right_frame.pack(side="right", fill="both", expand=True, padx=(5, 0))
|
||||
|
||||
ttk.Label(
|
||||
right_frame,
|
||||
text="Additional quality-of-life adjustments.",
|
||||
font=("", 8, "italic"),
|
||||
).pack(anchor="w", padx=10, pady=10)
|
||||
|
||||
for dll, var in self.optional_plugins.items():
|
||||
cb = ttk.Checkbutton(right_frame, text=dll, variable=var)
|
||||
cb.pack(anchor="w", padx=10, pady=4)
|
||||
ToolTip(cb, self.descriptions.get(dll, ""))
|
||||
|
||||
tweaks_frame = ttk.LabelFrame(container, text="Tweaks")
|
||||
tweaks_frame.pack(fill="x", pady=(10, 0))
|
||||
|
||||
ttk.Label(
|
||||
tweaks_frame,
|
||||
text="Choose one. ClassicAPI and Auction Query Throttle are mutually exclusive.",
|
||||
font=("", 8, "italic"),
|
||||
).pack(anchor="w", padx=10, pady=(7, 3))
|
||||
|
||||
choices = ttk.Frame(tweaks_frame)
|
||||
choices.pack(fill="x", padx=10, pady=(0, 8))
|
||||
|
||||
rb_none = ttk.Radiobutton(
|
||||
choices, text="None", variable=self.client_tweak, value="none"
|
||||
)
|
||||
rb_none.pack(side="left", padx=(0, 16))
|
||||
|
||||
rb_classicapi = ttk.Radiobutton(
|
||||
choices,
|
||||
text="ClassicAPI",
|
||||
variable=self.client_tweak,
|
||||
value="classicapi",
|
||||
)
|
||||
rb_classicapi.pack(side="left", padx=(0, 16))
|
||||
ToolTip(
|
||||
rb_classicapi,
|
||||
"Downloads and installs the latest stable ClassicAPI.dll release from "
|
||||
"brues-code/ClassicAPI when setup is applied.",
|
||||
)
|
||||
|
||||
rb_auction = ttk.Radiobutton(
|
||||
choices,
|
||||
text="Auction Query Throttle",
|
||||
variable=self.client_tweak,
|
||||
value="auction",
|
||||
)
|
||||
rb_auction.pack(side="left")
|
||||
ToolTip(
|
||||
rb_auction,
|
||||
"Downloads and installs the latest stable AuctionQueryThrottle.dll release "
|
||||
"from brues-code/AuctionQueryThrottle when setup is applied.",
|
||||
)
|
||||
|
||||
def clean_unselected_files(self, target):
|
||||
super().clean_unselected_files(target)
|
||||
|
||||
choice = self.client_tweak.get()
|
||||
managed = {
|
||||
"classicapi": "ClassicAPI.dll",
|
||||
"auction": "AuctionQueryThrottle.dll",
|
||||
}
|
||||
|
||||
# Always remove the non-selected alternative. Selecting None removes both.
|
||||
for key, filename in managed.items():
|
||||
if choice != key:
|
||||
path = os.path.join(target, filename)
|
||||
if os.path.exists(path):
|
||||
try:
|
||||
os.remove(path)
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
def _fallback_core_dll(self, payload_base, target, dll_name, error):
|
||||
source_dll = os.path.join(payload_base, dll_name)
|
||||
if not os.path.exists(source_dll):
|
||||
raise RuntimeError(
|
||||
f"Could not download the latest {dll_name}, and no bundled fallback exists.\n\n{error}"
|
||||
) from error
|
||||
|
||||
shutil.copy2(source_dll, target)
|
||||
messagebox.showwarning(
|
||||
"Online update unavailable",
|
||||
f"Could not download the latest {dll_name}.\n\n"
|
||||
f"The bundled version will be used instead.\n\nDetails: {error}",
|
||||
)
|
||||
|
||||
def configure_plugins(self, target):
|
||||
payload_base = os.path.join(get_base_path(), "Payload")
|
||||
payload_weirdu = os.path.join(payload_base, "WeirdUtils")
|
||||
|
||||
dlls_text_lines = []
|
||||
if self.gpu_type.get() != "AMD":
|
||||
dlls_text_lines.append("dxvk")
|
||||
|
||||
# Core plugins. UnitXP and SuperWoW are refreshed from their upstream
|
||||
# sources every time setup is applied. Their bundled copies remain an
|
||||
# offline fallback so this feature does not reduce current reliability.
|
||||
for dll_name, var in self.core_plugins.items():
|
||||
if not var.get():
|
||||
continue
|
||||
|
||||
if dll_name == "UnitXP_SP3.dll":
|
||||
try:
|
||||
remote_packages.install_unitxp(target)
|
||||
except Exception as exc:
|
||||
self._fallback_core_dll(payload_base, target, dll_name, exc)
|
||||
|
||||
elif dll_name == "SuperWoWhook.dll":
|
||||
try:
|
||||
remote_packages.install_superwow(target)
|
||||
except Exception as exc:
|
||||
self._fallback_core_dll(payload_base, target, dll_name, exc)
|
||||
|
||||
else:
|
||||
source_dll = os.path.join(payload_base, dll_name)
|
||||
if os.path.exists(source_dll):
|
||||
shutil.copy2(source_dll, target)
|
||||
|
||||
dlls_text_lines.append(dll_name)
|
||||
|
||||
# Clean dependent addons when the matching core DLL is disabled.
|
||||
for dll_name, addon_folder in self.addon_dependencies.items():
|
||||
core_var = self.core_plugins.get(dll_name)
|
||||
if core_var is not None and not core_var.get():
|
||||
addon_path = os.path.join(target, "Interface", "AddOns", addon_folder)
|
||||
if os.path.exists(addon_path):
|
||||
shutil.rmtree(addon_path, ignore_errors=True)
|
||||
|
||||
# Optional WeirdUtils.
|
||||
for dll_name, var in self.optional_plugins.items():
|
||||
if var.get():
|
||||
source_dll = os.path.join(payload_weirdu, dll_name)
|
||||
if os.path.exists(source_dll):
|
||||
shutil.copy2(source_dll, target)
|
||||
dlls_text_lines.append(dll_name)
|
||||
|
||||
# Mutually-exclusive live Tweaks.
|
||||
choice = self.client_tweak.get()
|
||||
if choice == "classicapi":
|
||||
try:
|
||||
remote_packages.install_classicapi(target)
|
||||
except Exception as exc:
|
||||
raise RuntimeError(f"ClassicAPI update failed:\n{exc}") from exc
|
||||
dlls_text_lines.append("ClassicAPI.dll")
|
||||
|
||||
elif choice == "auction":
|
||||
try:
|
||||
remote_packages.install_auction_query_throttle(target)
|
||||
except Exception as exc:
|
||||
raise RuntimeError(f"Auction Query Throttle update failed:\n{exc}") from exc
|
||||
dlls_text_lines.append("AuctionQueryThrottle.dll")
|
||||
|
||||
with open(os.path.join(target, "dlls.txt"), "w") as f:
|
||||
f.write("\n".join(dlls_text_lines))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
root = tk.Tk()
|
||||
app = ModernWowSetupTool(root)
|
||||
root.mainloop()
|
||||
Reference in New Issue
Block a user