#!/usr/bin/env python3
"""
Bigg's Adventure - client patcher and launcher (private emulated server, login port 5999), PyQt6 GUI.

Patches are downloaded over HTTPS from the private website
(https://www.biggsadventure.com) after logging in with the game account:
  POST /api/patcher/login {username, password} -> {token, expires_in, ...}
  GET  /patch/patch_manifest.json          (Authorization: Bearer <token>)
  GET  /patch/patch_files/<manifest path>   (Authorization: Bearer <token>)
  POST /api/patcher/logs (multipart: file=<zip>, note, client) "Send Logs" button

"Send Logs" never starts the game: it zips hjclient.log, UIErrors.txt,
Logs/dbg.txt, the newest crash dump, the newest MacroQuest logs and a
client_info.txt (key file sizes/sha256 + Windows version) and uploads them.
Never eqclient.ini, chat logs or patcher settings.

Optional Cloudflare Access service token: patcher_access.json next to the
patcher ({"client_id": "...", "client_secret": "..."}) or the environment
variables BIGGS_CF_ID / BIGGS_CF_SECRET. When present, the
CF-Access-Client-Id / CF-Access-Client-Secret headers are sent on every
request to the website.

"Remember me" stores only the username and the short-lived patch token (+ its
expiry) in <config dir>/biggsadventure/patcher.json; the password is never
stored. <config dir> is %APPDATA% on Windows, $XDG_CONFIG_HOME or ~/.config
elsewhere.

The network/patch logic below (everything above the GUI section) has no Qt
dependency and is exercised headlessly by test_patcher_net.py.
"""

import base64
import hashlib
import json
import os
import random
import re
import shutil
import subprocess
import sys
import tempfile
import threading
import time
import urllib.error
import urllib.parse
import urllib.request
from pathlib import Path

APP_NAME = "biggsadventure"
PATCHER_VERSION = "3.8"
USER_AGENT = "BiggsAdventurePatcher/" + PATCHER_VERSION
DEFAULT_BASE_URL = "https://www.biggsadventure.com"
PLAY_HOST = "play.biggsadventure.com:5999"
STATUS_PATH = "/api/patcher/status"   # public banner data: players online, server up, last patch date
BANNER_REFRESH_MS = 60_000
BANNER_TIMEOUT = 4
LAN_HOST = "192.168.4.78:5999"
LAN_PATCH_URL = "http://192.168.4.78:8080"   # old LAN http patch server, LAN preset only
HTTP_TIMEOUT = 30
UPLOAD_TIMEOUT = 300
LOG_UPLOAD_MAX = 25 * 1024 * 1024
LOG_TAIL_BYTES = 2 * 1024 * 1024
CHUNK = 64 * 1024

# Server presets (RoF2 login port 5999). "patch" = where patch files come from.
CANDIDATE_SERVERS = [
    {"name": "play.biggsadventure.com:5999 (Bigg's Adventure)", "host": PLAY_HOST, "patch": "web"},
    # LAN preset: log in straight to the server on Trevor's network (no round trip through the dfw relay);
    # patches still come from the website, the old LAN http patch server is gone (2026-10-13).
    {"name": "192.168.4.78:5999 (LAN - Trevor's network, direct)", "host": LAN_HOST, "patch": "web"},
]

# Never overwrite / retire the 32-bit RoF2 client.
PROTECTED_FILES = {"eqgame.exe"}
# sha256 of the stock RoF2 eqgame.exe: the only exe the patcher will swap for the server's patched one.
STOCK_ROF2_EXE = {"2a8702ad9f722704f01355c0750be7d6f164a8b9c9128ba0cf286ea32b405b0e"}
# Earlier server builds of eqgame.exe that must be replaced by the manifest's version (allowed ASLR ->
# crashes at launch on real Windows). Upgraded even though they are not stock.
BROKEN_PATCHED_EXE = {
    "8879c9839d55a376761edb8625c42c59a03efe280f3f744f392549b3ac62d086",   # md5 e5ea76b0, served 2026-09-27..10-01
    "4a456734af62b465660610794780e48ac3b0161f7b96e13aee86267c45ea49a3",   # served 2026-09-10..09-27
}
# Current server builds of eqgame.exe. The patcher swaps between them to whatever the manifest serves (so a
# rollback is just serving the other one again); the replaced exe is kept as eqgame.exe.prev.
SERVER_EXE_BUILDS = {
    "5acd3b731e05d0d93d8be01b12c0e5c9f59d77a74feeb651c5db6ad93474d505",   # 2 GB build, served 2026-10-01..10-04
    "a8fa44ff02eaee0e234caf812db3673eb3672fa3ef3fb77b010549ed12728b61",   # same + large address aware (4 GB), 10-04
}
DOWNLOADS_PATH = "/downloads"   # website page with the full game client and the patcher

# MacroQuest is optional and only for players who installed it themselves: the manifest's MacroQuest zip and
# mq/ scripts are skipped unless MacroQuest.exe is already in the game folder.
MQ_EXES = ("MacroQuest.exe", "MacroQuest2.exe")


def is_mq_entry(rel):
    low = str(rel).replace("\\", "/").lower()
    return low.startswith("mq/") or (low.endswith(".zip") and "macroquest" in low)


def mq_installed(client_dir):
    return any((Path(client_dir) / n).is_file() for n in MQ_EXES)


def version_tuple(v):
    out = []
    for part in str(v or "").strip().split("."):
        m = re.match(r"\d+", part)
        out.append(int(m.group(0)) if m else 0)
    return tuple(out)


def newer_patcher(manifest):
    """The manifest's {"version", "url"} for a newer patcher than this one, else None."""
    info = manifest.get("patcher") if isinstance(manifest, dict) else None
    if not isinstance(info, dict) or not info.get("version"):
        return None
    return info if version_tuple(info["version"]) > version_tuple(PATCHER_VERSION) else None


# ----------------- paths & settings -----------------
def app_dir():
    """Folder holding the patcher (works for a frozen .exe too)."""
    if getattr(sys, "frozen", False):
        return Path(sys.executable).resolve().parent
    return Path(__file__).resolve().parent


def config_dir():
    if sys.platform.startswith("win"):
        base = os.environ.get("APPDATA") or str(Path.home() / "AppData" / "Roaming")
    else:
        base = os.environ.get("XDG_CONFIG_HOME") or str(Path.home() / ".config")
    return Path(base) / APP_NAME


def settings_path():
    return config_dir() / "patcher.json"


def load_settings():
    try:
        with open(settings_path(), "r", encoding="utf-8") as f:
            data = json.load(f)
        return data if isinstance(data, dict) else {}
    except (OSError, ValueError):
        return {}


def save_settings(data):
    """Atomically write settings (user-only permissions where supported)."""
    path = settings_path()
    path.parent.mkdir(parents=True, exist_ok=True)
    fd, tmp = tempfile.mkstemp(prefix=".patcher-", suffix=".json", dir=str(path.parent))
    try:
        with os.fdopen(fd, "w", encoding="utf-8") as f:
            json.dump(data, f, indent=2)
        try:
            os.chmod(tmp, 0o600)
        except OSError:
            pass
        os.replace(tmp, path)
    except Exception:
        try:
            os.unlink(tmp)
        except OSError:
            pass
        raise


def load_access_headers(directory=None):
    """Cloudflare Access service-token headers, or {} if not configured."""
    cid = os.environ.get("BIGGS_CF_ID", "").strip()
    secret = os.environ.get("BIGGS_CF_SECRET", "").strip()
    if not (cid and secret):
        path = Path(directory or app_dir()) / "patcher_access.json"
        try:
            with open(path, "r", encoding="utf-8") as f:
                data = json.load(f)
            cid = str(data.get("client_id", "")).strip()
            secret = str(data.get("client_secret", "")).strip()
        except (OSError, ValueError, AttributeError):
            cid = secret = ""
    if cid and secret:
        return {"CF-Access-Client-Id": cid, "CF-Access-Client-Secret": secret}
    return {}


def token_valid(token, expires_at, margin=300):
    return bool(token) and float(expires_at or 0) - margin > time.time()


def host_is_lan(host):
    return host.strip().removeprefix("Host=").strip() == LAN_HOST


# ----------------- file helpers -----------------
def get_sha256(filepath):
    p = Path(filepath)
    if not p.is_file():
        return None
    h = hashlib.sha256()
    try:
        with open(p, "rb") as f:
            while chunk := f.read(CHUNK):
                h.update(chunk)
        return h.hexdigest()
    except OSError:
        return None


def safe_rel_path(rel):
    """Manifest path -> relative Path, or None if it would escape the client folder."""
    norm = str(rel).replace("\\", "/")
    parts = [p for p in norm.split("/") if p not in ("", ".")]
    if not parts or norm.startswith("/") or ".." in parts or ":" in parts[0]:
        return None
    return Path(*parts)


def update_eqhost_file(host_string, client_dir=None):
    eqhost_path = Path(client_dir or os.getcwd()) / "eqhost.txt"
    clean_host = host_string.strip()
    host_line = clean_host if clean_host.startswith("Host=") else f"Host={clean_host}"
    content = f"[LoginServer]\n{host_line}\n"
    try:
        with open(eqhost_path, "w", encoding="ascii", newline="\n") as f:
            f.write(content)
        return True, f"Configured eqhost.txt -> {host_line}"
    except Exception as e:
        return False, f"Could not write eqhost.txt: {e}"


def read_current_eqhost(client_dir=None):
    eqhost_path = Path(client_dir or os.getcwd()) / "eqhost.txt"
    try:
        with open(eqhost_path, "r", encoding="ascii", errors="replace") as f:
            for line in f:
                line = line.strip()
                if line.startswith("Host="):
                    return line.split("=", 1)[1].strip()
    except OSError:
        pass
    return None


def _looks_like_zip_preview(client_dir):
    """True when Windows Explorer runs the patcher straight out of the downloaded zip (a temp folder)."""
    # Explorer: %TEMP%\Temp1_BiggsAdventurePatcher.zip\  7-Zip: %TEMP%\7zO1A2B\  WinRAR: %TEMP%\Rar$EXa123\
    low = [p.lower() for p in Path(client_dir).parts]
    return any(p.endswith(".zip") or p.startswith("rar$") or re.match(r"7zo[0-9a-f]+$", p) for p in low)


def check_client_folder(client_dir):
    """(ok, kind, message). kind "noclient": no eqgame.exe here (patching would just litter the folder);
    "folder": the folder can't be written (e.g. under Program Files), so patching can't work but the game can
    still start."""
    client_dir = Path(client_dir)
    if not (client_dir / "eqgame.exe").is_file():
        if _looks_like_zip_preview(client_dir):
            return False, "noclient", (
                "The patcher is running from inside the zip file. Right-click BiggsAdventurePatcher.zip, choose "
                "\"Extract All...\", put BiggsAdventurePatcher.exe in your game folder (the one with eqgame.exe) "
                "and run it from there.")
        return False, "noclient", (
            f"There is no EverQuest game here ({client_dir}).\n\n"
            "New player? Download the game client from the website (Downloads page), unzip it, and run "
            "BiggsAdventurePatcher.exe from inside that folder.\n\n"
            "Already have the game? Move BiggsAdventurePatcher.exe into your game folder (the one with "
            "eqgame.exe) and run it from there.")
    try:
        fd, tmp = tempfile.mkstemp(prefix=".patcher-write-test-", dir=str(client_dir))
        os.close(fd)
        os.unlink(tmp)
    except OSError:
        return False, "folder", (
            f"The patcher cannot write to the game folder ({client_dir}). Windows protects folders like "
            "C:\\Program Files. Move the whole game folder somewhere else, for example C:\\Games\\BiggsAdventure, "
            "and run the patcher from there.")
    return True, "", ""


def _in_use_hint(rel):
    return (f"{rel} is in use or blocked. Close EverQuest (and MacroQuest) completely, then press "
            "Check for Updates again. If it still fails, your antivirus may be blocking the file: allow it or "
            "add an exclusion for this folder.")


def _is_in_use(e):
    return isinstance(e, PermissionError) or getattr(e, "winerror", None) in (5, 32, 33)


def retire_client_files(names, client_dir, log, ignored=()):
    """Move files the server has retired (manifest "retire") into <client>/_retired/<date>/. Never deletes.
    Files in patcher_ignore.txt are left alone; a file that can't be moved (in use) is skipped with a message."""
    import datetime
    stamp = datetime.date.today().isoformat()
    client_dir = Path(client_dir)
    moved = 0
    for rel in names or []:
        rp = safe_rel_path(rel)
        if rp is None or rp.as_posix().lower() in PROTECTED_FILES or rp.as_posix().lower() in ignored:
            continue
        src = client_dir / rp
        if not (src.exists() or src.is_symlink()):
            continue
        try:
            dst_dir = client_dir / "_retired" / stamp / rp.parent
            dst_dir.mkdir(parents=True, exist_ok=True)
            dst = dst_dir / rp.name
            while dst.exists() or dst.is_symlink():
                dst = dst.with_name(dst.name + ".old")
            shutil.move(str(src), str(dst))
        except OSError as e:
            log(f"[!] Could not move the old file {rel} aside: {e}")
            if _is_in_use(e):
                log("[!] " + _in_use_hint(rel))
            continue
        log(f"[✓] Retired {rel} -> _retired/{stamp}/")
        moved += 1
    return moved


# ----------------- network client -----------------
class PatchError(Exception):
    """kind: auth | expired | locked | rate | access | network | server"""

    def __init__(self, kind, message):
        super().__init__(message)
        self.kind = kind


# The website's certificate is Let's Encrypt. Some PCs fail Windows' certificate check even though browsers work
# (antivirus HTTPS scanning such as Avast leaves an expired certificate in the Windows store, and Python picks it:
# "certificate has expired"). On a verify failure the client retries once trusting only these two Let's Encrypt
# roots (ISRG Root X1 and X2), which still checks the site's real certificate chain.
LETSENCRYPT_ROOTS_PEM = """-----BEGIN CERTIFICATE-----
MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw
TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh
cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4
WhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJu
ZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBY
MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54rVygc
h77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+
0TM8ukj13Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6U
A5/TR5d8mUgjU+g4rk8Kb4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sW
T8KOEUt+zwvo/7V3LvSye0rgTBIlDHCNAymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyH
B5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ4Q7e2RCOFvu396j3x+UC
B5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf1b0SHzUv
KBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWn
OlFuhjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTn
jh8BCNAw1FtxNrQHusEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbw
qHyGO0aoSCqI3Haadr8faqU9GY/rOPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CI
rU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV
HRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY9umbbjANBgkq
hkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZL
ubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ
3BebYhtF8GaV0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KK
NFtY2PwByVS5uCbMiogziUwthDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5
ORAzI4JMPJ+GslWYHb4phowim57iaztXOoJwTdwJx4nLCgdNbOhdjsnvzqvHu7Ur
TkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nxe5AW0wdeRlN8NwdC
jNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZAJzVc
oyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq
4RgqsahDYVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPA
mRGunUHBcnWEvgJBQl9nJEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57d
emyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc=
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIICGzCCAaGgAwIBAgIQQdKd0XLq7qeAwSxs6S+HUjAKBggqhkjOPQQDAzBPMQsw
CQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJuZXQgU2VjdXJpdHkgUmVzZWFyY2gg
R3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBYMjAeFw0yMDA5MDQwMDAwMDBaFw00
MDA5MTcxNjAwMDBaME8xCzAJBgNVBAYTAlVTMSkwJwYDVQQKEyBJbnRlcm5ldCBT
ZWN1cml0eSBSZXNlYXJjaCBHcm91cDEVMBMGA1UEAxMMSVNSRyBSb290IFgyMHYw
EAYHKoZIzj0CAQYFK4EEACIDYgAEzZvVn4CDCuwJSvMWSj5cz3es3mcFDR0HttwW
+1qLFNvicWDEukWVEYmO6gbf9yoWHKS5xcUy4APgHoIYOIvXRdgKam7mAHf7AlF9
ItgKbppbd9/w+kHsOdx1ymgHDB/qo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0T
AQH/BAUwAwEB/zAdBgNVHQ4EFgQUfEKWrt5LSDv6kviejM9ti6lyN5UwCgYIKoZI
zj0EAwMDaAAwZQIwe3lORlCEwkSHRhtFcP9Ymd70/aTSVaYgLXTWNLxBo1BfASdW
tL4ndQavEi51mI38AjEAi/V3bNTIZargCyzuFJ0nN6T5U6VR5CmD1/iQMVtCnwr1
/q4AaOeMSQ+2b1tbFfLn
-----END CERTIFICATE-----
"""


def _is_cert_verify_error(err):
    import ssl
    return isinstance(err, ssl.SSLCertVerificationError) or "CERTIFICATE_VERIFY_FAILED" in str(err)


def _letsencrypt_https_handler():
    import ssl
    ctx = ssl.create_default_context(cadata=LETSENCRYPT_ROOTS_PEM)
    return urllib.request.HTTPSHandler(context=ctx)


class _NoRedirect(urllib.request.HTTPRedirectHandler):
    # Never follow redirects: they would carry the Bearer/Access headers to
    # another host, and a redirect here means Cloudflare Access wants a login.
    def redirect_request(self, req, fp, code, msg, headers, newurl):
        return None


ACCESS_MSG = ("Access token missing or invalid: the website is protected by Cloudflare Access. "
              "Put a valid patcher_access.json next to the patcher (ask the server owner).")


class PatchClient:
    """Talks to the website (mode "web") or the old LAN http server (mode "lan")."""

    def __init__(self, base_url=DEFAULT_BASE_URL, access_headers=None, token=None,
                 token_expires=0.0, mode="web", timeout=HTTP_TIMEOUT):
        self.mode = mode
        self.base_url = (base_url if mode == "web" else LAN_PATCH_URL).rstrip("/")
        self.access_headers = dict(access_headers or {})
        self.token = token
        self.token_expires = float(token_expires or 0)
        self.timeout = timeout
        self._opener = urllib.request.build_opener(_NoRedirect())
        self._le_roots = False

    # -- plumbing
    def needs_login(self):
        return self.mode == "web" and not token_valid(self.token, self.token_expires)

    def _headers(self, auth=True, json_body=False, content_type=None):
        h = {"User-Agent": USER_AGENT, "Accept": "application/json, */*"}
        if self.mode == "web":
            h.update(self.access_headers)
            if auth and self.token:
                h["Authorization"] = "Bearer " + self.token
        if content_type:
            h["Content-Type"] = content_type
        elif json_body:
            h["Content-Type"] = "application/json"
        return h

    def _open(self, url, data=None, auth=True, content_type=None, timeout=None):
        req = urllib.request.Request(url, data=data, method="POST" if data is not None else "GET",
                                     headers=self._headers(auth, json_body=data is not None,
                                                           content_type=content_type))
        try:
            try:
                return self._opener.open(req, timeout=timeout or self.timeout)
            except urllib.error.URLError as e:
                if isinstance(e, urllib.error.HTTPError) or self._le_roots or not _is_cert_verify_error(e.reason):
                    raise
                # Windows' certificate store failed the check: from now on trust the Let's Encrypt roots instead.
                self._le_roots = True
                self._opener = urllib.request.build_opener(_NoRedirect(), _letsencrypt_https_handler())
                return self._opener.open(req, timeout=timeout or self.timeout)
        except urllib.error.HTTPError as e:
            raise self._http_error(e) from None
        except urllib.error.URLError as e:
            raise PatchError("network", self._offline_msg(url, e.reason)) from None
        except (OSError, ValueError) as e:
            raise PatchError("network", self._offline_msg(url, e)) from None

    @staticmethod
    def _offline_msg(url, detail):
        host = urllib.parse.urlsplit(url).netloc
        return (f"Cannot reach the patch server ({host}). Check that this PC is online, then try again. "
                f"If your internet works, the server may be down for a moment. (details: {detail})")

    @staticmethod
    def _is_access_page(resp_headers):
        loc = (resp_headers.get("Location") or "") if resp_headers else ""
        ctype = (resp_headers.get("Content-Type") or "") if resp_headers else ""
        return ("cloudflareaccess.com" in loc or "/cdn-cgi/access/" in loc
                or ctype.lower().startswith("text/html"))

    def _http_error(self, e):
        code = e.code
        try:
            body = e.read(4096)
        except Exception:
            body = b""
        err = ""
        server_msg = ""
        if 300 <= code < 400:
            if self.mode == "web" and self._is_access_page(e.headers):
                return PatchError("access", ACCESS_MSG)
            return PatchError("server", f"Unexpected redirect ({code}) to {e.headers.get('Location')}")
        try:
            parsed = json.loads(body.decode("utf-8"))
            err = str(parsed.get("error", ""))
            server_msg = str(parsed.get("message", ""))[:300]
        except (ValueError, UnicodeDecodeError, AttributeError):
            # An HTML page: Cloudflare Access only answers 401/403. A 5xx HTML page is Cloudflare saying the
            # website behind it is down (502/503/504/52x/530), not an access problem.
            if self.mode == "web" and code in (401, 403) and self._is_access_page(e.headers):
                return PatchError("access", ACCESS_MSG)
        if code >= 500 and not (code == 503 and err == "login_server_unavailable") and code != 507:
            return PatchError("server", f"The Bigg's Adventure website is not responding right now (error {code}). "
                                        "Try again in a few minutes.")
        if err == "upload_rate_limited":
            return PatchError("rate", server_msg or "Too many log uploads. Try again in an hour.")
        if code == 413:
            return PatchError("upload", server_msg or "The logs are too large to upload (25 MB max).")
        if code in (400, 507) and server_msg:
            return PatchError("upload", server_msg)
        if code == 401:
            if err == "invalid_credentials":
                return PatchError("auth", "Invalid username or password.")
            return PatchError("expired", "Your patch session expired. Please log in again.")
        if code == 403:
            if err == "account_locked":
                return PatchError("locked", "This account is locked. Contact the server owner.")
            if not err and self.mode == "web":
                return PatchError("access", ACCESS_MSG)
            return PatchError("server", f"Forbidden ({err or code}).")
        if code == 429:
            return PatchError("rate", "Too many login attempts. Wait a minute and try again.")
        if code == 503 and err == "login_server_unavailable":
            return PatchError("server", "The game login server is down right now. Try again later.")
        if code == 404:
            return PatchError("server", f"Not found on patch server: {e.url}")
        return PatchError("server", f"Patch server error {code}{f' ({err})' if err else ''}.")

    def _read_json(self, resp, what):
        with resp:
            ctype = resp.headers.get("Content-Type", "")
            raw = resp.read()
        if self.mode == "web" and ctype.lower().startswith("text/html"):
            raise PatchError("access", ACCESS_MSG)
        try:
            return json.loads(raw.decode("utf-8"))
        except (ValueError, UnicodeDecodeError):
            raise PatchError("server", f"Patch server returned an invalid {what}.") from None

    # -- API
    def login(self, username, password):
        """Returns (token, expires_at_epoch). Raises PatchError."""
        if self.mode != "web":
            return None, 0.0
        body = json.dumps({"username": username.strip().lower(), "password": password}).encode("utf-8")
        data = self._read_json(self._open(self.base_url + "/api/patcher/login", data=body, auth=False),
                               "login response")
        token = data.get("token") if isinstance(data, dict) else None
        if not token:
            raise PatchError("server", "Login response did not contain a token.")
        self.token = token
        self.token_expires = time.time() + int(data.get("expires_in", 3600))
        return self.token, self.token_expires

    def manifest_url(self):
        return self.base_url + ("/patch/patch_manifest.json" if self.mode == "web" else "/patch_manifest.json")

    def file_url(self, rel_path):
        prefix = "/patch/patch_files/" if self.mode == "web" else "/patch_files/"
        return self.base_url + prefix + urllib.parse.quote(Path(rel_path).as_posix(), safe="/")

    def get_manifest(self):
        data = self._read_json(self._open(self.manifest_url()), "manifest")
        if not isinstance(data, dict) or not isinstance(data.get("files", []), list):
            raise PatchError("server", "Patch manifest has an unexpected format.")
        return data

    def upload_logs(self, zip_bytes, note="", client_info=""):
        """POST the logs zip to /api/patcher/logs. Returns the server's JSON dict. Raises PatchError."""
        if not self.token:
            raise PatchError("expired", "Log in to send logs.")
        boundary = "----BiggsLogs" + hashlib.sha256(os.urandom(16)).hexdigest()[:24]
        parts = []
        for name, value in (("note", note or ""), ("client", client_info or "")):
            parts.append(f'--{boundary}\r\nContent-Disposition: form-data; name="{name}"\r\n'
                         f'Content-Type: text/plain; charset=utf-8\r\n\r\n'.encode("utf-8")
                         + value.encode("utf-8") + b"\r\n")
        parts.append(f'--{boundary}\r\nContent-Disposition: form-data; name="file"; filename="logs.zip"\r\n'
                     f'Content-Type: application/zip\r\n\r\n'.encode("ascii") + zip_bytes + b"\r\n")
        parts.append(f"--{boundary}--\r\n".encode("ascii"))
        body = b"".join(parts)
        resp = self._open(self.base_url + "/api/patcher/logs", data=body,
                          content_type=f"multipart/form-data; boundary={boundary}", timeout=UPLOAD_TIMEOUT)
        data = self._read_json(resp, "upload response")
        if not isinstance(data, dict) or not data.get("ok"):
            raise PatchError("upload", "The server did not accept the logs.")
        return data

    def download_file(self, rel_path, dest, expected_sha256, progress=None):
        """Stream to a temp file next to dest, verify sha256, then atomically replace dest.

        progress(bytes_done, bytes_total_or_None) is called while downloading.
        """
        return self.download_url(self.file_url(rel_path), dest, expected_sha256, progress, label=rel_path)

    def download_url(self, url, dest, expected_sha256, progress=None, label=None):
        """download_file for a full URL or a site-relative path ("/patch/...")."""
        rel_path = label or url
        if url.startswith("/"):
            url = self.base_url + url
        dest = Path(dest)
        dest.parent.mkdir(parents=True, exist_ok=True)
        resp = self._open(url)
        fd, tmp = tempfile.mkstemp(prefix="." + dest.name + ".", suffix=".part", dir=str(dest.parent))
        h = hashlib.sha256()
        try:
            with resp, os.fdopen(fd, "wb") as out:
                total = resp.headers.get("Content-Length")
                total = int(total) if total and total.isdigit() else None
                done = 0
                while True:
                    try:
                        chunk = resp.read(CHUNK)
                    except (OSError, ValueError) as e:
                        raise PatchError("network", f"Download of {rel_path} interrupted: {e}") from None
                    if not chunk:
                        break
                    out.write(chunk)
                    h.update(chunk)
                    done += len(chunk)
                    if progress:
                        progress(done, total)
            got = h.hexdigest()
            if expected_sha256 and got.lower() != str(expected_sha256).lower():
                raise PatchError("server", f"Checksum mismatch for {rel_path} (download corrupted or manifest stale).")
            os.replace(tmp, dest)
            tmp = None
            return got
        finally:
            if tmp:
                try:
                    os.unlink(tmp)
                except OSError:
                    pass


def ensure_logged_in(client, username, password):
    """Log in if the client has no valid token. Returns True if a new login happened."""
    if not client.needs_login():
        return False
    if not username or not password:
        raise PatchError("auth", "Enter your game username and password to download patches.")
    client.login(username, password)
    return True


def run_patch(client, client_dir, log=print, status=lambda s: None, progress=lambda p: None,
              username=None, password=None, on_token=None, include_mq=None):
    """Full patch pass. Returns (updated, failed, manifest). Raises PatchError on auth/network failure,
    and PatchError("noclient"/"folder") when the folder holds no game or can't be written.

    If the stored token turns out to be expired (401) and a password is available,
    logs in once more and retries. MacroQuest entries are skipped unless include_mq (default: MacroQuest.exe
    is already in the folder).
    """
    client_dir = Path(client_dir)
    ok, kind, msg = check_client_folder(client_dir)
    if not ok:
        raise PatchError(kind, msg)
    if include_mq is None:
        include_mq = mq_installed(client_dir)
    if ensure_logged_in(client, username, password) and on_token:
        on_token(client.token, client.token_expires)
    status("Fetching patch manifest...")
    log(f"Checking manifest at {client.manifest_url()} ...")
    try:
        manifest = client.get_manifest()
    except PatchError as e:
        if e.kind != "expired" or not password or client.mode != "web":
            raise
        client.token = None
        client.login(username, password)
        if on_token:
            on_token(client.token, client.token_expires)
        manifest = client.get_manifest()
    log("[✓] Connected to patch server.")

    news = manifest.get("news", "")
    if news:
        log(f"\nSERVER NEWS:\n{news}\n" + "-" * 65)

    files = manifest.get("files", []) or []
    ignored = load_ignore_list(client_dir)
    if ignored:
        log(f"[i] patcher_ignore.txt: leaving {len(ignored)} local file(s) alone: {', '.join(sorted(ignored))}")

    retire_client_files(manifest.get("retire"), client_dir, log, ignored)

    todo = []
    failed = 0
    for idx, info in enumerate(files):
        rel = info.get("path", "") if isinstance(info, dict) else ""
        rp = safe_rel_path(rel)
        if rp is None:
            log(f"[!] Skipping unsafe path in manifest: {rel}")
            continue
        low = rp.as_posix().lower()
        if low in ignored:
            continue  # local test copy (patcher_ignore.txt), eqgame.exe included
        if not include_mq and is_mq_entry(low):
            continue  # MacroQuest is never installed by the patcher
        if low in PROTECTED_FILES:
            # eqgame.exe is only replaced when the folder holds the stock RoF2 exe (the server's copy adds the
            # multiclass label/button patches) or an older server build. Any other exe is left alone: it may be
            # someone's own build.
            have = get_sha256(client_dir / rp)
            if have != str(info.get("sha256", "")).lower():
                stock = client_dir / (rp.as_posix() + ".stock")
                prev = client_dir / (rp.as_posix() + ".prev")
                try:
                    if have in STOCK_ROF2_EXE:
                        if not stock.exists():
                            shutil.copy2(client_dir / rp, stock)
                        log(f"[i] Upgrading the stock {rel} to the server's version (original kept as {stock.name}).")
                        todo.append((rel, rp, info))
                    elif have in SERVER_EXE_BUILDS:
                        shutil.copy2(client_dir / rp, prev)
                        log(f"[i] Updating {rel} to the server's current version (previous one kept as {prev.name}).")
                        todo.append((rel, rp, info))
                    elif have in BROKEN_PATCHED_EXE:
                        # Keep an existing .stock backup untouched; otherwise keep the old exe as .prev.
                        if not stock.exists() and not prev.exists():
                            shutil.copy2(client_dir / rp, prev)
                        log(f"[i] Updating {rel} to the fixed version (Windows crash fix).")
                        todo.append((rel, rp, info))
                    else:
                        log(f"[!] {rel} is not the stock RoF2 client or the server's version: left alone. "
                            "Class labels and some buttons need the server's version (use the full client download).")
                except OSError as e:
                    failed += 1
                    log(f"[!] Could not back up {rel} before updating it: {e}")
            continue
        status(f"Checking file ({idx + 1}/{len(files)}): {rel}")
        progress(int((idx + 1) / max(len(files), 1) * 30))
        if get_sha256(client_dir / rp) != str(info.get("sha256", "")).lower():
            todo.append((rel, rp, info))

    if not todo:
        progress(100)
        if not failed:
            log("[✓] All patch files are up to date.")
        make_skins_compatible(client_dir, files, log)
        return 0, failed, manifest

    total_bytes = sum(int(i.get("size") or 0) for _, _, i in todo) or len(todo)
    done_bytes = 0
    updated = 0
    for n, (rel, rp, info) in enumerate(todo, 1):
        size = int(info.get("size") or 0)
        status(f"Downloading ({n}/{len(todo)}): {rel}")
        log(f"[-->] Downloading {rel}")
        base = done_bytes

        def prog(d, _t, base=base):
            progress(30 + int(70 * min(base + d, total_bytes) / total_bytes))
        try:
            client.download_file(rel, client_dir / rp, info.get("sha256"), prog)
            updated += 1
            if rel.lower().endswith(".zip"):
                log(f"[-->] Extracting archive: {rel}...")
                _safe_extract(client_dir / rp, client_dir)
                log(f"[✓] Extracted {rel}")
            elif rp.as_posix().startswith("mq/lua/"):
                dest_lua = client_dir / "lua" / rp.name
                dest_lua.parent.mkdir(parents=True, exist_ok=True)
                shutil.copy2(client_dir / rp, dest_lua)
        except PatchError as e:
            if e.kind in ("expired", "access", "network"):
                raise
            failed += 1
            log(f"[!] {e}")
        except Exception as e:
            failed += 1
            log(f"[!] Error installing {rel}: {e}")
            if _is_in_use(e):
                log("[!] " + _in_use_hint(rel))
        done_bytes += size if size else 1
        progress(30 + int(70 * min(done_bytes, total_bytes) / total_bytes))
    progress(100)
    make_skins_compatible(client_dir, files, log)
    return updated, failed, manifest


def load_ignore_list(client_dir):
    """patcher_ignore.txt in the client folder: one client path per line (e.g. dinput8.dll or
    uifiles/default/EQUI.xml; # comments allowed). Those files are never replaced, so a tester can run staged
    client files without the patcher putting the shipped ones back."""
    try:
        lines = (Path(client_dir) / "patcher_ignore.txt").read_text(encoding="utf-8", errors="replace").splitlines()
    except OSError:
        return set()
    out = set()
    for line in lines:
        line = line.split("#", 1)[0].strip().replace("\\", "/")
        rp = safe_rel_path(line) if line else None
        if rp is not None:
            out.add(rp.as_posix().lower())
    return out


def make_skins_compatible(client_dir, files, log):
    """Custom UI skins (uifiles/<skin>/) replace the default copy of any file they contain, which hides the
    server's windows. For each skin: its own EQUI.xml gets <Include> lines for our HJ_*.xml windows, and its
    copies of the other window files we ship are renamed *.biggs-disabled so the game falls back to ours.
    Nothing is deleted; the rest of the skin is untouched."""
    ours = [Path(i.get("path", "")).name for i in files if isinstance(i, dict)
            and i.get("path", "").lower().startswith("uifiles/default/")]
    hj = [n for n in ours if n.lower().startswith("hj_")]
    # every window file we ship except EQUI.xml, our HJ_* windows included: a skin's (older) copy would win over
    # ours, e.g. a skin made by copying uifiles/default keeps a Bot Manager without the newer buttons
    replaced = {n.lower() for n in ours if n.lower() != "equi.xml"}
    ui_root = next((p for p in Path(client_dir).iterdir() if p.is_dir() and p.name.lower() == "uifiles"), None) \
        if Path(client_dir).is_dir() else None
    if not ui_root:
        return
    for skin in sorted(p for p in ui_root.iterdir() if p.is_dir() and p.name.lower() != "default"):
        try:
            for f in list(skin.iterdir()):
                low = f.name.lower()
                if low == "equi.xml" and hj:
                    text = f.read_text(encoding="utf-8", errors="replace")
                    missing = [n for n in hj if n.lower() not in text.lower()]
                    end = text.lower().rfind("</include>")
                    if missing and end >= 0:
                        backup = f.with_name(f.name + ".biggs-orig")
                        if not backup.exists():
                            shutil.copy2(f, backup)
                        eol = text.find("\n", end)
                        eol = len(text) if eol < 0 else eol + 1
                        indent = re.match(r"[ \t]*", text[text.rfind("\n", 0, end) + 1:]).group(0)
                        add = "".join(f"{indent}<Include>{n}</Include>\n" for n in missing)
                        f.write_text(text[:eol] + add + text[eol:], encoding="utf-8")
                        log(f"[✓] UI skin '{skin.name}': added {', '.join(missing)} to its EQUI.xml")
                elif low in replaced:
                    dest = f.with_name(f.name + ".biggs-disabled")
                    if dest.exists():
                        dest.unlink()
                    f.rename(dest)
                    log(f"[✓] UI skin '{skin.name}': set aside its {f.name} (uses the server's version; "
                        f"the skin's copy is kept as {dest.name})")
        except OSError as e:
            log(f"[!] UI skin '{skin.name}': could not update ({e})")


def _safe_extract(zip_path, target):
    import zipfile
    with zipfile.ZipFile(zip_path, "r") as zf:
        for m in zf.infolist():
            if safe_rel_path(m.filename) is None:
                raise ValueError(f"unsafe path in archive: {m.filename}")
        zf.extractall(Path(target))


# ----------------- Send Logs -----------------
# Only these client files are ever collected. Never eqclient.ini (may hold credentials),
# chat logs (Logs/eqlog_*.txt) or patcher settings.
KEY_FILE_PATTERNS = ["eqgame.exe", "eqgame.exe.stock", "eqgame.exe.prev", "dinput8.dll", "eqhost.txt", "spells_us.txt",
                     "Resources/GlobalLoad.txt", "uifiles/default/EQUI*.xml", "uifiles/default/HJ_*.xml"]


def _ci_child(parent, name):
    """Case-insensitive child lookup (the client folder comes from Windows)."""
    p = Path(parent) / name
    if p.exists():
        return p
    try:
        low = name.lower()
        return next((c for c in Path(parent).iterdir() if c.name.lower() == low), None)
    except OSError:
        return None


def _ci_path(base, rel):
    cur = Path(base)
    for part in rel.replace("\\", "/").split("/"):
        cur = _ci_child(cur, part)
        if cur is None:
            return None
    return cur


def _ci_glob(base, rel_pattern):
    """'Logs/MacroQuest*.log' -> matching files (case-insensitive on the file name part)."""
    import fnmatch
    rel_pattern = rel_pattern.replace("\\", "/")
    folder, _, pat = rel_pattern.rpartition("/")
    d = _ci_path(base, folder) if folder else Path(base)
    if d is None or not d.is_dir():
        return []
    try:
        return [c for c in d.iterdir() if c.is_file() and fnmatch.fnmatch(c.name.lower(), pat.lower())]
    except OSError:
        return []


def _newest(paths, n=1):
    def mtime(p):
        try:
            return p.stat().st_mtime
        except OSError:
            return 0
    return sorted(paths, key=mtime, reverse=True)[:n]


def _read_tail(path, limit):
    """(bytes, truncated) - the last `limit` bytes of a file (logs keep the newest lines at the end)."""
    size = path.stat().st_size
    with open(path, "rb") as f:
        if limit and size > limit:
            f.seek(size - limit)
            return f.read(limit), True
        return f.read(), False


def _windows_version():
    import platform
    parts = [platform.platform()]
    if sys.platform.startswith("win"):
        try:
            v = sys.getwindowsversion()
            parts.append(f"build {v.major}.{v.minor}.{v.build} {v.service_pack}".strip())
            parts.append(f"edition {platform.win32_edition()}")
        except Exception:
            pass
        try:
            import ctypes
            ntdll = ctypes.WinDLL("ntdll")
            if hasattr(ntdll, "wine_get_version"):
                ntdll.wine_get_version.restype = ctypes.c_char_p
                parts.append("Wine " + ntdll.wine_get_version().decode())
        except Exception:
            pass
    return "; ".join(parts)


def _redact_home(text):
    home = str(Path.home())
    return text.replace(home, "%USERPROFILE%") if home and len(home) > 3 else text


def log_sources(client_dir):
    """[(arcname, Path, tail_limit or None)] of files to send. Missing files are simply absent."""
    client_dir = Path(client_dir)
    out = []
    for rel in ("hjclient.log", "UIErrors.txt", "Logs/dbg.txt"):
        p = _ci_path(client_dir, rel)
        if p is not None and p.is_file():
            out.append((rel, p, LOG_TAIL_BYTES))
    dumps = _ci_glob(client_dir, "Logs/*.dmp") + _ci_glob(client_dir, "*.dmp")
    for p in _newest(dumps, 1):
        out.append(("Logs/" + p.name if p.parent != client_dir else p.name, p, None))
    for p in _newest(_ci_glob(client_dir, "Logs/MacroQuest*.log"), 2):
        out.append(("Logs/" + p.name, p, LOG_TAIL_BYTES))
    return out


def client_info_text(client_dir, note=""):
    import datetime
    client_dir = Path(client_dir)
    lines = [f"Bigg's Adventure patcher {PATCHER_VERSION} - Send Logs",
             f"Created: {datetime.datetime.now().astimezone().isoformat(timespec='seconds')}",
             f"Windows: {_windows_version()}",
             f"Python: {sys.version.split()[0]}  frozen={bool(getattr(sys, 'frozen', False))}",
             f"Client folder: {_redact_home(str(client_dir))}"]
    try:
        free = shutil.disk_usage(client_dir).free
        lines.append(f"Free disk space: {free / 1e9:.1f} GB")
    except OSError:
        pass
    lines.append(f"eqhost.txt Host: {read_current_eqhost(client_dir)}")
    lines += ["", "Key files (size, modified, sha256):"]
    for pat in KEY_FILE_PATTERNS:
        matches = sorted(_ci_glob(client_dir, pat), key=lambda p: p.name.lower()) if "*" in pat else \
            [p for p in [_ci_path(client_dir, pat)] if p is not None]
        if not matches:
            lines.append(f"  {pat}: MISSING")
        for p in matches:
            try:
                st = p.stat()
                mod = datetime.datetime.fromtimestamp(st.st_mtime).strftime("%Y-%m-%d %H:%M")
                sha = get_sha256(p) or "unreadable"
                note_ = ""
                if p.name.lower().startswith("eqgame.exe") and sha in STOCK_ROF2_EXE:
                    note_ = "  (stock RoF2)"
                elif p.name.lower().startswith("eqgame.exe") and sha == "a8fa44ff02eaee0e234caf812db3673eb3672fa3ef3fb77b010549ed12728b61":
                    note_ = "  (server build, 4 GB)"
                elif p.name.lower().startswith("eqgame.exe") and sha in SERVER_EXE_BUILDS:
                    note_ = "  (server build, 2 GB)"
                elif p.name.lower().startswith("eqgame.exe") and sha in BROKEN_PATCHED_EXE:
                    note_ = "  (OLD server build with ASLR - crashes on Windows)"
                lines.append(f"  {p.relative_to(client_dir).as_posix()}: {st.st_size} B  {mod}  {sha}{note_}")
            except OSError as e:
                lines.append(f"  {pat}: error {e}")
    lines += ["", "UI skin folders (uifiles/<skin>, besides default) and their window files we also ship:"]
    try:
        ui = _ci_path(client_dir, "uifiles")
        skins = sorted((c for c in ui.iterdir() if c.is_dir() and c.name.lower() != "default"),
                       key=lambda c: c.name.lower()) if ui is not None and ui.is_dir() else []
        if not skins:
            lines.append("  (none)")
        for sk in skins:
            hits = sorted(c.name for c in sk.iterdir() if c.is_file() and
                          (c.name.lower().startswith("hj_") or c.name.lower().startswith("equi_targetwindow")
                           or c.name.lower() == "equi.xml"))
            lines.append(f"  {sk.name}: {', '.join(hits) if hits else '(none of ours)'}")
    except OSError as e:
        lines.append(f"  error {e}")
    lines += ["", "DLLs in the client folder:"]
    try:
        for p in sorted((c for c in client_dir.iterdir() if c.is_file() and c.suffix.lower() == ".dll"),
                        key=lambda c: c.name.lower()):
            lines.append(f"  {p.name}: {p.stat().st_size} B  {get_sha256(p)}")
    except OSError as e:
        lines.append(f"  error {e}")
    lines += ["", "Logs folder:"]
    logs = _ci_path(client_dir, "Logs")
    try:
        if logs is not None and logs.is_dir():
            for p in sorted(logs.iterdir(), key=lambda c: c.name.lower()):
                if p.is_file() and not p.name.lower().startswith("eqlog_"):   # chat logs: names omitted too
                    lines.append(f"  {p.name}: {p.stat().st_size} B  "
                                 f"{datetime.datetime.fromtimestamp(p.stat().st_mtime).strftime('%Y-%m-%d %H:%M')}")
        else:
            lines.append("  (no Logs folder)")
    except OSError as e:
        lines.append(f"  error {e}")
    if note:
        lines += ["", "Player note:", note]
    return "\n".join(lines) + "\n"


def build_logs_zip(client_dir, note="", max_bytes=LOG_UPLOAD_MAX):
    """Zip the client logs in memory. Returns (zip_bytes, [summary lines]). Never fails on a missing file."""
    import io
    import zipfile
    sources = log_sources(client_dir)
    info = client_info_text(client_dir, note)

    def make(skip_dump):
        buf = io.BytesIO()
        summary = []
        with zipfile.ZipFile(buf, "w", zipfile.ZIP_DEFLATED, compresslevel=9) as zf:
            zf.writestr("client_info.txt", info)
            for arc, path, limit in sources:
                if skip_dump and arc.lower().endswith(".dmp"):
                    summary.append(f"{arc}: skipped (too large to send)")
                    continue
                try:
                    data, cut = _read_tail(path, limit)
                except OSError as e:
                    summary.append(f"{arc}: could not read ({e})")
                    continue
                zf.writestr(arc, data)
                summary.append(f"{arc}: {len(data) / 1024:.0f} KB{' (last 2 MB)' if cut else ''}")
        return buf.getvalue(), summary

    data, summary = make(False)
    if len(data) > max_bytes:
        data, summary = make(True)
    if len(data) > max_bytes:
        raise PatchError("upload", "The logs are too large to send even without the crash dump.")
    if not sources:
        summary.append("No log files found (only client_info.txt is sent). Is the patcher in the game folder?")
    return data, summary


def save_logs_copy(client_dir, zip_bytes):
    """Keep a local copy (e.g. when the upload fails) so the player can send it another way."""
    path = Path(client_dir) / f"BiggsLogs_{time.strftime('%Y%m%d_%H%M%S')}.zip"
    with open(path, "wb") as f:
        f.write(zip_bytes)
    return path


def send_logs(client, client_dir, note="", log=print, username=None, password=None, on_token=None):
    """Collect + upload. Returns the server's JSON. Raises PatchError (re-logs in once on an expired token)."""
    zip_bytes, summary = build_logs_zip(client_dir, note)
    log(f"Collected logs ({len(zip_bytes) / 1024:.0f} KB zipped):")
    for line in summary:
        log("   " + line)
    info = f"patcher {PATCHER_VERSION}; {_windows_version()}"[:200]
    try:
        if ensure_logged_in(client, username, password) and on_token:
            on_token(client.token, client.token_expires)
        try:
            return client.upload_logs(zip_bytes, note, info)
        except PatchError as e:
            if e.kind != "expired" or not password:
                raise
            client.token = None
            client.login(username, password)
            if on_token:
                on_token(client.token, client.token_expires)
            return client.upload_logs(zip_bytes, note, info)
    except PatchError as e:
        e.zip_bytes = zip_bytes   # caller can save a local copy
        raise


_IS_WIN = sys.platform.startswith("win")
_NO_WINDOW = getattr(subprocess, "CREATE_NO_WINDOW", 0x08000000)
MQ_STILL_RUNNING_MSG = (
    "MacroQuest is still running (it hides in the system tray, bottom right next to the clock) and would load into "
    "the game, which can crash it. Right-click the MacroQuest tray icon and choose Exit, then press PLAY again. "
    "(Want MacroQuest? Tick the MacroQuest box instead.)")


def mq_running():
    """Lower-case names of the MacroQuest processes that are running (Windows), set() if none, None if unknown."""
    if not _IS_WIN:
        return set()
    try:
        r = subprocess.run(["tasklist", "/FO", "CSV", "/NH"], capture_output=True, text=True, timeout=20,
                           creationflags=_NO_WINDOW)
    except Exception:
        return None
    if r.returncode != 0:
        return None
    running = {line.split(",", 1)[0].strip().strip('"').lower() for line in (r.stdout or "").splitlines()}
    return {n.lower() for n in MQ_EXES} & running


def close_mq():
    """Close a running MacroQuest. Returns (closed_any, still_running)."""
    before = mq_running()
    if before == set():
        return False, False
    images = sorted(before) if before else [n.lower() for n in MQ_EXES]
    closed = False
    for image in images:
        try:
            r = subprocess.run(["taskkill", "/IM", image, "/F"], capture_output=True, timeout=15,
                               creationflags=_NO_WINDOW)
            closed = closed or r.returncode == 0
        except Exception:
            pass
    still = None
    for _ in range(5):
        time.sleep(0.5 if closed or before else 0.1)
        still = mq_running()
        if not still:
            break
    return closed, bool(still)


def launch_eqgame(client_dir=None, launch_mq=False):
    """Start eqgame.exe patchme. launch_mq: start MacroQuest first (if found and not running yet). Without it, a
    MacroQuest left running from an earlier session is closed first (it injects into every eqgame.exe); if it
    can't be closed (e.g. it runs as administrator) the game is NOT started and the player is told what to do."""
    client_dir = str(client_dir or os.getcwd())
    eqgame_exe = os.path.join(client_dir, "eqgame.exe")
    if not os.path.exists(eqgame_exe):
        return False, f"eqgame.exe not found in {client_dir}"

    env = os.environ.copy()
    if not _IS_WIN and "WINEPREFIX" not in env:
        env["WINEPREFIX"] = str(Path.home() / ".wine")

    mq_started = mq_closed = False
    if launch_mq:
        if not mq_running():
            for candidate in [*MQ_EXES, "../E3_MQ2_RoF2/MacroQuest2.exe"]:
                mq_path = os.path.normpath(os.path.join(client_dir, candidate))
                if os.path.exists(mq_path):
                    mq_dir = os.path.dirname(mq_path)
                    try:
                        if _IS_WIN:
                            subprocess.Popen([mq_path], cwd=mq_dir)
                        else:
                            subprocess.Popen(["wine", mq_path], cwd=mq_dir, env=env)
                        mq_started = True
                        time.sleep(1.5)
                    except Exception:
                        pass
                    break
    elif _IS_WIN:
        mq_closed, still = close_mq()
        if still:
            return False, MQ_STILL_RUNNING_MSG
        if mq_closed:
            time.sleep(1.0)

    cmd = [eqgame_exe, "patchme"] if _IS_WIN else ["wine", eqgame_exe, "patchme"]
    try:
        subprocess.Popen(cmd, cwd=client_dir, env=env)
        msg = f"Launched the game ({' '.join(cmd)})"
        if mq_started:
            msg += " + MacroQuest"
        if mq_closed:
            msg = "Closed a running MacroQuest (MacroQuest box unticked). " + msg
        return True, msg
    except Exception as e:
        return False, f"Launch failed: {e}"


# ----------------- self-update -----------------
# The manifest's "patcher" entry: {"version", "url" (Downloads page), "exe_url", "sha256", "size"}. When it names a
# newer version, the running BiggsAdventurePatcher.exe downloads exe_url to <exe>.new, checks the sha256, renames
# itself to <exe>.old (Windows allows renaming a running image), moves .new into place and starts it with
# "--updated-from <old version>". If the rename is refused (antivirus, locks), a small .cmd helper in %TEMP% waits
# for this process to exit, moves the file and relaunches. On any failure the current exe keeps working and the
# player gets the manual "download the new patcher" message.
UPDATED_FROM_FLAG = "--updated-from"
MIN_EXE_BYTES = 1024 * 1024


def current_exe():
    """Path of the running patcher .exe, or None when running from source."""
    return Path(sys.executable).resolve() if getattr(sys, "frozen", False) else None


def updated_from_arg(argv=None):
    argv = sys.argv if argv is None else argv
    if UPDATED_FROM_FLAG in argv:
        i = argv.index(UPDATED_FROM_FLAG)
        return argv[i + 1] if i + 1 < len(argv) else "?"
    return None


def relaunch_args(argv=None):
    """argv[1:] without an earlier --updated-from X, plus --updated-from <this version>."""
    argv = list(sys.argv if argv is None else argv)[1:]
    out, skip = [], False
    for a in argv:
        if skip:
            skip = False
            continue
        if a == UPDATED_FROM_FLAG:
            skip = True
            continue
        out.append(a)
    return out + [UPDATED_FROM_FLAG, PATCHER_VERSION]


def _fresh_instance_env():
    """Environment for starting a new, independent copy of a PyInstaller onefile exe from inside one."""
    env = {k: v for k, v in os.environ.items() if not k.startswith(("_PYI_", "_MEI"))}
    env["PYINSTALLER_RESET_ENVIRONMENT"] = "1"
    return env


def cleanup_old_exe(exe=None, log=lambda s: None, tries=20, delay=0.5):
    """Delete <exe>.old (the previous patcher) and a stale <exe>.new. The old exe can stay locked for a moment
    while the previous process exits, so retry. Returns True when nothing is left."""
    exe = Path(exe) if exe else current_exe()
    if exe is None:
        return True
    leftovers = [exe.with_name(exe.name + ".old"), exe.with_name(exe.name + ".new")]
    for attempt in range(tries):
        left = []
        for p in leftovers:
            if p.exists():
                try:
                    p.unlink()
                    log(f"[i] Removed the previous patcher file {p.name}.")
                except OSError:
                    left.append(p)
        leftovers = left
        if not leftovers:
            return True
        if attempt + 1 < tries:
            time.sleep(delay)
    log(f"[i] Could not remove {', '.join(p.name for p in leftovers)} yet (still in use); will retry next start.")
    return False


def _with_update_logfile(log, exe=None):
    """Also append every self-update step to patcher_update.log next to the patcher (the .exe has no console)."""
    exe = Path(exe) if exe else current_exe()
    if exe is None:
        return log
    path = exe.parent / "patcher_update.log"

    def both(msg):
        log(msg)
        try:
            with open(path, "a", encoding="utf-8") as f:
                f.write(f"{time.strftime('%Y-%m-%d %H:%M:%S')} [{PATCHER_VERSION}] {msg}\n")
        except OSError:
            pass
    return both


def manual_update_text(version):
    return (f"A new version of the patcher ({version}) is available. You have {PATCHER_VERSION}.\n\n"
            "Download it from the website's Downloads page, unzip it and replace BiggsAdventurePatcher.exe "
            "in your game folder with the new one.")


def _helper_script(exe, new, pid, args):
    """.cmd that waits for this process to let go of exe, moves new over it and starts it (or the old one)."""
    q = lambda p: '"' + str(p) + '"'  # noqa: E731
    arg_s = " ".join(q(a) if " " in a else a for a in args)
    return "\r\n".join([
        "@echo off",
        f"rem Bigg's Adventure patcher self-update helper (waits for process {pid})",
        "set N=0",
        ":retry",
        f"move /y {q(new)} {q(exe)} >nul 2>&1 && goto ok",
        "set /a N+=1",
        "if %N% geq 90 goto giveup",
        "ping -n 2 127.0.0.1 >nul",
        "goto retry",
        ":ok",
        f"start \"\" /d {q(exe.parent)} {q(exe)} {arg_s}",
        "goto end",
        ":giveup",
        f"del /f /q {q(new)} >nul 2>&1",
        f"start \"\" /d {q(exe.parent)} {q(exe)}",
        ":end",
        '(goto) 2>nul & del "%~f0"',
        ""])


def self_update(client, manifest, exe=None, argv=None, log=print, progress=None, spawn=None, is_win=None,
                rename=None):
    """Returns (status, message):
      "none"    - no newer patcher (or this exe already is the published one)
      "manual"  - can't self-update here: show message (the manual instructions)
      "failed"  - tried and failed; the current exe is untouched and keeps working; show message
      "restart" - the new patcher has been started (or the helper will start it): exit this process now
    spawn/is_win/rename are injectable for tests."""
    spawn = spawn or subprocess.Popen
    if rename is None and os.environ.get("BIGGS_SELFUPDATE_FORCE_HELPER") == "1":   # test hook (wine)
        def rename(src, dst):
            raise PermissionError(13, "forced by BIGGS_SELFUPDATE_FORCE_HELPER")
    rename = rename or os.replace
    log = _with_update_logfile(log, exe)
    is_win = _IS_WIN if is_win is None else is_win
    info = newer_patcher(manifest)
    if not info:
        return "none", ""
    version = str(info["version"])
    manual = manual_update_text(version)
    exe = Path(exe) if exe else current_exe()
    if exe is None:
        log(f"[i] Patcher {version} is available (running from source: no self-update).")
        return "manual", manual
    if updated_from_arg(argv) is not None:
        # Just restarted after an update and the server still lists a newer version: never loop.
        log(f"[!] The server lists patcher {version} but this is {PATCHER_VERSION} right after an update.")
        return "manual", manual
    exe_url = str(info.get("exe_url") or "")
    sha = str(info.get("sha256") or "").lower()
    if getattr(client, "mode", "web") != "web" or not exe_url or not re.fullmatch(r"[0-9a-f]{64}", sha):
        return "manual", manual
    if get_sha256(exe) == sha:
        log(f"[i] This patcher already is the published build (server version label {version}).")
        return "none", ""
    folder = exe.parent
    if exe.name.lower() in load_ignore_list(folder):
        log(f"[i] patcher_ignore.txt lists {exe.name}: not updating the patcher.")
        return "manual", manual
    if _looks_like_zip_preview(folder):
        return "manual", manual
    try:
        fd, tmp = tempfile.mkstemp(prefix=".patcher-write-test-", dir=str(folder))
        os.close(fd)
        os.unlink(tmp)
    except OSError:
        log("[!] The patcher's folder can't be written: update the patcher by hand.")
        return "manual", manual

    new = exe.with_name(exe.name + ".new")
    old = exe.with_name(exe.name + ".old")
    cleanup_old_exe(exe, log, tries=1)      # a stale .old/.new from an earlier attempt
    log(f"[-->] Updating the patcher {PATCHER_VERSION} -> {version}: downloading...")
    try:
        client.download_url(exe_url, new, sha, progress, label="BiggsAdventurePatcher.exe")
        with open(new, "rb") as f:
            head = f.read(2)
        if head != b"MZ" or new.stat().st_size < MIN_EXE_BYTES:
            raise PatchError("server", "the downloaded patcher is not a Windows program")
    except (PatchError, OSError) as e:
        log(f"[!] Patcher update failed while downloading: {e}")
        try:
            new.unlink()
        except OSError:
            pass
        return "failed", manual
    log(f"[✓] Downloaded and verified {new.name} (sha256 {sha[:12]}...).")
    args = relaunch_args(argv)

    swapped = False
    try:
        rename(exe, old)            # rename the running image
        try:
            rename(new, exe)
        except OSError:
            try:
                rename(old, exe)    # put the working exe back
            except OSError:
                shutil.copy2(old, exe)
            raise
        swapped = True
        log(f"[✓] Installed patcher {version} (previous one kept as {old.name} until the next start).")
    except OSError as e:
        log(f"[i] Could not swap the running patcher directly ({e}); using the update helper.")

    if swapped:
        try:
            spawn([str(exe), *args], cwd=str(folder), env=_fresh_instance_env())
            log(f"[-->] Starting patcher {version}...")
            return "restart", f"Patcher updated to {version}. Restarting..."
        except OSError as e:
            log(f"[!] Patcher {version} is installed but could not be started ({e}). Close and reopen the patcher.")
            return "failed", f"The patcher was updated to {version}. Please close it and start it again."

    if not is_win:
        try:
            new.unlink()
        except OSError:
            pass
        return "failed", manual
    if any(c in str(exe) for c in '%!^&"'):
        log("[!] The patcher's folder name has characters the update helper can't handle.")
        try:
            new.unlink()
        except OSError:
            pass
        return "failed", manual
    try:
        tmpdir = Path(os.environ.get("TEMP") or tempfile.gettempdir())
        helper = tmpdir / f"biggs_patcher_update_{os.getpid()}.cmd"
        helper.write_text(_helper_script(exe, new, os.getpid(), args), encoding="ascii", errors="replace")
        spawn(["cmd.exe", "/c", str(helper)], cwd=str(tmpdir), env=_fresh_instance_env(),
              creationflags=_NO_WINDOW)
        log(f"[-->] The update helper will install patcher {version} and restart it ({helper.name}).")
        return "restart", f"Patcher updated to {version}. Restarting..."
    except OSError as e:
        log(f"[!] Patcher update failed: could not start the helper ({e}).")
        try:
            new.unlink()
        except OSError:
            pass
        return "failed", manual


def check_and_self_update(client, log=print, progress=None, **kw):
    """Fetch the manifest (client must hold a valid token) and self-update. Returns (status, message)."""
    try:
        manifest = client.get_manifest()
    except PatchError as e:
        log(f"[i] Patcher update check skipped: {e}")
        return "none", ""
    return self_update(client, manifest, log=log, progress=progress, **kw)


# ----------------- PyQt6 Implementation -----------------
def run_pyqt6_gui():
    from PyQt6.QtWidgets import (
        QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout,
        QLabel, QPushButton, QTextEdit, QProgressBar, QComboBox, QLineEdit, QMessageBox, QCheckBox,
        QInputDialog, QFrame, QGraphicsDropShadowEffect
    )
    from PyQt6.QtCore import pyqtSignal, QObject, QTimer, QUrl, Qt, QPointF, QRectF
    from PyQt6.QtGui import (QColor, QPalette, QDesktopServices, QPainter, QLinearGradient, QRadialGradient,
                             QPolygonF, QPainterPath, QPen, QFont, QPixmap)

    class WorkerSignals(QObject):
        log = pyqtSignal(str)
        status = pyqtSignal(str)
        progress = pyqtSignal(int)
        token = pyqtSignal(str, float)
        failed = pyqtSignal(str, str)      # kind, message
        finished = pyqtSignal(bool, bool)  # ok, launch_after
        logs_done = pyqtSignal(bool, str)  # ok, message
        update_available = pyqtSignal(str, str)  # newer patcher version, download page url
        self_update_done = pyqtSignal(str, str, str)  # status, message, version
        banners = pyqtSignal(object)  # status dict or None

    class ArtBackground(QWidget):
        """The dragon backdrop; if it cannot load, a painted night scene (mountains, a keep, a lone hero)."""

        def __init__(self):
            super().__init__()
            self.setAutoFillBackground(False)
            rnd = random.Random(7)
            self.stars = [(rnd.random(), rnd.random() * 0.55, rnd.choice((1.0, 1.0, 1.5, 2.0)), rnd.randint(90, 230))
                          for _ in range(140)]
            self.backdrop = QPixmap()
            try:
                self.backdrop.loadFromData(base64.b64decode("".join(BACKDROP_JPG_B64.split())), "JPG")
            except (ValueError, TypeError):
                self.backdrop = QPixmap()
            self.scaled = None

        @staticmethod
        def ridge(w, h, base, amp, seed, step):
            rnd = random.Random(seed)
            pts = [QPointF(0, h)]
            x, y = 0.0, base
            while x <= w + step:
                y = min(max(y + rnd.uniform(-amp, amp), base - amp * 3), base + amp * 1.5)
                pts.append(QPointF(x, h * y))
                x += step
            pts.append(QPointF(w, h))
            return QPolygonF(pts)

        def paintEvent(self, _e):
            p = QPainter(self)
            p.setRenderHint(QPainter.RenderHint.Antialiasing)
            w, h = self.width(), self.height()
            if not self.backdrop.isNull():
                # cover the window, anchored top-right so the dragon stays in view
                if self.scaled is None or self.scaled[0] != (w, h):
                    pm = self.backdrop.scaled(w, h, Qt.AspectRatioMode.KeepAspectRatioByExpanding,
                                              Qt.TransformationMode.SmoothTransformation)
                    self.scaled = ((w, h), pm)
                pm = self.scaled[1]
                p.drawPixmap(w - pm.width(), 0, pm)
                p.end()
                return

            sky = QLinearGradient(0, 0, 0, h)
            sky.setColorAt(0.0, QColor("#0b0f1d"))
            sky.setColorAt(0.45, QColor("#232a45"))
            sky.setColorAt(0.70, QColor("#4b4766"))
            sky.setColorAt(1.0, QColor("#141420"))
            p.fillRect(self.rect(), sky)

            p.setPen(Qt.PenStyle.NoPen)
            for sx, sy, size, alpha in self.stars:
                p.setBrush(QColor(255, 248, 225, alpha))
                p.drawEllipse(QPointF(sx * w, sy * h), size / 2, size / 2)

            # moon with a soft halo
            mx, my, mr = w * 0.80, h * 0.20, min(w, h) * 0.065
            halo = QRadialGradient(QPointF(mx, my), mr * 4)
            halo.setColorAt(0.0, QColor(255, 236, 190, 120))
            halo.setColorAt(1.0, QColor(255, 236, 190, 0))
            p.setBrush(halo)
            p.drawEllipse(QPointF(mx, my), mr * 4, mr * 4)
            p.setBrush(QColor("#f6e7c1"))
            p.drawEllipse(QPointF(mx, my), mr, mr)

            # mist band, then three mountain ridges, far to near
            mist = QLinearGradient(0, h * 0.36, 0, h * 0.56)
            mist.setColorAt(0.0, QColor(170, 160, 190, 0))
            mist.setColorAt(1.0, QColor(170, 160, 190, 60))
            p.fillRect(0, int(h * 0.36), w, int(h * 0.2), mist)
            for base, amp, seed, step, color in ((0.40, 0.016, 11, 38, "#2c2b44"), (0.47, 0.020, 23, 30, "#1d1d30"),
                                                 (0.555, 0.010, 5, 26, "#111119")):
                p.setBrush(QColor(color))
                p.drawPolygon(self.ridge(w, h, base, amp, seed, step))

            # keep on the right ridge
            p.setBrush(QColor("#0c0c14"))
            kx, ky = w * 0.88, h * 0.47
            tw = w * 0.022
            for dx, th in ((-2.2, 0.14), (0.0, 0.20), (2.0, 0.12), (3.6, 0.09)):
                p.drawRect(QRectF(kx + dx * tw, ky - h * th, tw, h * th + 2))
                p.drawPolygon(QPolygonF([QPointF(kx + dx * tw - tw * 0.2, ky - h * th),
                                         QPointF(kx + dx * tw + tw * 0.5, ky - h * th - tw * 1.6),
                                         QPointF(kx + dx * tw + tw * 1.2, ky - h * th)]))
            p.drawRect(QRectF(kx - 2.2 * tw, ky - h * 0.06, tw * 6.8, h * 0.06 + 2))
            p.setBrush(QColor(255, 200, 120, 200))
            p.drawRect(QRectF(kx + tw * 0.35, ky - h * 0.155, tw * 0.3, tw * 0.5))

            # lone hero on the near ridge, moonlit
            hx, hy, s = w * 0.62, h * 0.515, h * 0.0019
            p.setBrush(QColor("#07070c"))
            body = QPainterPath()
            body.moveTo(hx - 14 * s, hy)
            body.lineTo(hx - 8 * s, hy - 46 * s)
            body.lineTo(hx - 22 * s, hy - 40 * s)   # cloak flaring left
            body.lineTo(hx - 40 * s, hy - 4 * s)
            body.lineTo(hx - 28 * s, hy - 52 * s)
            body.lineTo(hx - 6 * s, hy - 70 * s)
            body.lineTo(hx + 6 * s, hy - 70 * s)
            body.lineTo(hx + 10 * s, hy - 46 * s)
            body.lineTo(hx + 14 * s, hy)
            body.closeSubpath()
            p.drawPath(body)
            p.drawEllipse(QPointF(hx, hy - 78 * s), 7 * s, 8 * s)
            pen = QPen(QColor("#07070c"), 3 * s)
            p.setPen(pen)
            p.drawLine(QPointF(hx + 8 * s, hy - 52 * s), QPointF(hx + 46 * s, hy - 24 * s))   # sword
            p.setPen(QPen(QColor(150, 40, 50, 230), 4 * s))
            p.drawLine(QPointF(hx - 20 * s, hy - 40 * s), QPointF(hx - 34 * s, hy - 10 * s))  # crimson cloak edge
            p.end()

    class CrestWidget(QWidget):
        """Gold disc with the realm's monogram."""

        def paintEvent(self, _e):
            p = QPainter(self)
            p.setRenderHint(QPainter.RenderHint.Antialiasing)
            r = min(self.width(), self.height()) / 2 - 4
            c = QPointF(self.width() / 2, self.height() / 2)
            g = QRadialGradient(c, r)
            g.setColorAt(0.0, QColor("#fff1c4"))
            g.setColorAt(0.85, QColor("#e2bf6a"))
            g.setColorAt(1.0, QColor("#8a6a2c"))
            p.setPen(QPen(QColor("#6b5326"), 3))
            p.setBrush(g)
            p.drawEllipse(c, r, r)
            p.setPen(QColor("#2a1f0e"))
            f = QFont("Georgia")
            f.setPointSizeF(r * 0.62)
            f.setBold(True)
            p.setFont(f)
            p.drawText(QRectF(c.x() - r, c.y() - r, 2 * r, 2 * r), int(Qt.AlignmentFlag.AlignCenter), "BA")
            p.end()

    class PatcherWindow(QMainWindow):
        def __init__(self):
            super().__init__()
            self.setWindowTitle(f"Bigg's Adventure Patcher {PATCHER_VERSION}")
            self.resize(1000, 660)
            self.setMinimumSize(900, 600)
            self.signals = WorkerSignals()
            self.signals.log.connect(self.append_log)
            self.signals.status.connect(self.set_status)
            self.signals.progress.connect(self.set_progress)
            self.signals.token.connect(self.on_token)
            self.signals.failed.connect(self.on_failed)
            self.signals.finished.connect(self.on_patch_finished)
            self.signals.logs_done.connect(self.on_logs_done)
            self.signals.update_available.connect(self.on_update_available)
            self.signals.self_update_done.connect(self.on_self_update_done)
            self.signals.banners.connect(self.on_banners)

            self.client_dir = app_dir()
            self.settings = load_settings()
            self.token = self.settings.get("token")
            self.token_expires = float(self.settings.get("token_expires") or 0)
            self.token_base = self.settings.get("token_base_url", DEFAULT_BASE_URL)
            self.busy = False
            self.self_updating = False
            self.last_error = self.last_error_kind = ""
            self.update_notified = False
            self.folder_ok, self.folder_kind, self.folder_msg = check_client_folder(self.client_dir)
            self.init_ui()

        def init_ui(self):
            palette = QPalette()
            palette.setColor(QPalette.ColorRole.Window, QColor("#0d1018"))
            palette.setColor(QPalette.ColorRole.WindowText, QColor("#E8DCC0"))
            palette.setColor(QPalette.ColorRole.Base, QColor("#141821"))
            palette.setColor(QPalette.ColorRole.AlternateBase, QColor("#1b202b"))
            palette.setColor(QPalette.ColorRole.Text, QColor("#E8DCC0"))
            palette.setColor(QPalette.ColorRole.Button, QColor("#1b202b"))
            palette.setColor(QPalette.ColorRole.ButtonText, QColor("#E8DCC0"))
            self.setPalette(palette)

            # Painted backdrop (the exe is one file, so no image files): night sky, moon, mountains, a keep.
            central = ArtBackground()
            self.setCentralWidget(central)
            outer = QHBoxLayout(central)
            outer.setContentsMargins(18, 18, 18, 14)
            outer.setSpacing(18)

            serif = "Georgia, 'Palatino Linotype', 'Book Antiqua', serif"
            panel_style = ("QFrame#panel { background-color: rgba(12, 14, 22, 205); "
                           "border: 2px solid #6b5326; border-radius: 10px; }")
            glass = "background-color: rgba(10, 12, 20, 185); border: 1px solid #4a3c22; border-radius: 6px;"
            lbl_style = "color: #C9B98F; font-size: 12px; min-width: 90px; background: transparent; border: none;"
            edit_style = ("background-color: rgba(20, 24, 33, 230); color: #F4ECD8; padding: 5px; "
                          "border: 1px solid #5a4a2a; border-radius: 4px;")
            small_btn = ("QPushButton { background-color: #2b2f3a; color: #E8DCC0; padding: 5px 10px; "
                         "border: 1px solid #5a4a2a; border-radius: 4px; } QPushButton:hover { background-color: #3a3f4c; }")
            cream_btn = (
                "QPushButton { background-color: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 #fbeec6, stop:1 #e9d39a); "
                f"color: #2a1f0e; font-family: {serif}; font-size: 13px; font-weight: bold; padding: 7px 10px; "
                "border: 1px solid #8a6a2c; border-radius: 3px; } "
                "QPushButton:hover { background-color: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 #fff6da, stop:1 #f2dfaa); } "
                "QPushButton:pressed { background-color: #d9c083; } "
                "QPushButton:checked { background-color: #d9c083; } "
                "QPushButton:disabled { background-color: #8c826c; color: #4a4336; }")

            # ---------------- left: crest, links, play
            left = QFrame()
            left.setObjectName("panel")
            left.setStyleSheet(panel_style)
            left.setFixedWidth(232)
            lv = QVBoxLayout(left)
            lv.setContentsMargins(18, 16, 18, 16)
            lv.setSpacing(8)

            crest = CrestWidget()
            crest.setFixedHeight(118)
            lv.addWidget(crest)
            lv.addSpacing(6)

            def link_button(text, path, tip):
                b = QPushButton(text)
                b.setStyleSheet(cream_btn)
                b.setToolTip(tip)
                b.setCursor(Qt.CursorShape.PointingHandCursor)
                b.clicked.connect(lambda _c=False, p=path: QDesktopServices.openUrl(QUrl(self.current_base_url() + p)))
                lv.addWidget(b)
                return b

            link_button("Website", "/", "Open the Bigg's Adventure website.")
            link_button("Patch Notes", "/history", "What changed lately.")
            link_button("Realm Status", "/status", "Who is online, restarts and timers.")
            link_button("Leaderboards", "/leaderboards", "Top parses and more.")
            link_button("Bazaar", "/bazaar", "Browse what traders are selling.")
            link_button("Item Search", "/items", "Search the item database.")

            # Send Logs never starts the game, so it works when the client crashes at launch.
            self.logs_btn = QPushButton("Send Logs")
            self.logs_btn.setToolTip("Send your client logs and crash data to the server team (does not start the game).")
            self.logs_btn.setStyleSheet(cream_btn)
            self.logs_btn.setCursor(Qt.CursorShape.PointingHandCursor)
            self.logs_btn.clicked.connect(self.start_send_logs)
            lv.addWidget(self.logs_btn)

            self.adv_btn = QPushButton("Settings ▸")
            self.adv_btn.setCheckable(True)
            self.adv_btn.setStyleSheet(cream_btn)
            self.adv_btn.setToolTip("Server, eqhost.txt and website settings.")
            self.adv_btn.toggled.connect(self.on_advanced_toggled)
            lv.addWidget(self.adv_btn)
            lv.addStretch(1)

            self.check_btn = QPushButton("Check for Updates")
            self.check_btn.setStyleSheet(cream_btn)
            self.check_btn.setCursor(Qt.CursorShape.PointingHandCursor)
            self.check_btn.clicked.connect(lambda: self.start_patch_thread(False))
            lv.addWidget(self.check_btn)

            self.play_btn = QPushButton("PLAY")
            self.play_btn.setMinimumHeight(46)
            self.play_btn.setCursor(Qt.CursorShape.PointingHandCursor)
            self.play_btn.setStyleSheet(
                "QPushButton { background-color: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 #fff3c8, stop:1 #e6c66a); "
                f"color: #1f160a; font-family: {serif}; font-size: 20px; font-weight: bold; letter-spacing: 2px; "
                "border: 2px solid #3f7a32; border-radius: 4px; } "
                "QPushButton:hover { border: 2px solid #6fcf5a; background-color: #fff5d2; } "
                "QPushButton:disabled { background-color: #8c826c; color: #4a4336; border: 2px solid #555; }")
            self.play_btn.clicked.connect(lambda: self.start_patch_thread(True))
            lv.addWidget(self.play_btn)

            self.mq_checkbox = QCheckBox("Auto-Launch MacroQuest")
            # Only offered when MacroQuest is actually in the game folder (the admin's own setup); players never see it.
            # Unticked unless the player ticked it before (a MacroQuest unpacked by older patchers must not start by
            # itself). Unticked + PLAY closes a MacroQuest left running in the tray.
            mq_present = mq_installed(self.client_dir)
            self.mq_checkbox.setChecked(mq_present and bool(self.settings.get("launch_mq")))
            self.mq_checkbox.setVisible(mq_present)
            self.mq_checkbox.toggled.connect(lambda _c: self.persist())
            self.mq_checkbox.setStyleSheet("color: #9be37f; font-size: 11px; background: transparent;")
            lv.addWidget(self.mq_checkbox)
            outer.addWidget(left)

            # ---------------- right: banners, title, account, settings, log
            right = QVBoxLayout()
            right.setSpacing(8)
            outer.addLayout(right, 1)

            banners = QHBoxLayout()
            banners.setSpacing(12)
            banner_style = (f"QLabel {{ color: #F4ECD8; font-family: {serif}; font-size: 13px; font-weight: bold; "
                            "background-color: rgba(18, 20, 30, 215); border: 2px solid #8a6a2c; border-radius: 14px; "
                            "padding: 6px 18px; }")
            self.banner_players = QLabel("Players Online:  —")
            self.banner_server = QLabel("Server:  —")
            self.banner_xp = QLabel("Experience:  —")
            self.banner_patch = QLabel("Last Patch:  —")
            self.banner_style = banner_style
            for b in (self.banner_players, self.banner_server, self.banner_xp, self.banner_patch):
                b.setStyleSheet(banner_style)
                b.setAlignment(Qt.AlignmentFlag.AlignCenter)
                banners.addWidget(b)
            banners.addStretch(1)
            right.addLayout(banners)

            title_lbl = QLabel("Bigg's Adventure")
            title_lbl.setStyleSheet(f"color: #F3DFA2; font-family: {serif}; font-size: 46px; font-weight: bold; "
                                    "background: transparent;")
            shadow = QGraphicsDropShadowEffect()
            shadow.setBlurRadius(18)
            shadow.setOffset(2, 3)
            shadow.setColor(QColor(0, 0, 0, 230))
            title_lbl.setGraphicsEffect(shadow)
            subtitle_lbl = QLabel("A private EverQuest realm  •  three classes, one hero")
            subtitle_lbl.setStyleSheet(f"color: #C9B98F; font-family: {serif}; font-size: 14px; font-style: italic; "
                                       "background: transparent;")
            right.addWidget(title_lbl)
            right.addWidget(subtitle_lbl)
            right.addStretch(1)

            # Account row
            acct = QFrame()
            acct.setStyleSheet(f"QFrame {{ {glass} }}")
            acct_layout = QHBoxLayout(acct)
            acct_layout.setContentsMargins(10, 6, 10, 6)
            u_lbl = QLabel("Account:")
            u_lbl.setStyleSheet(lbl_style.replace("min-width: 90px;", ""))
            self.user_input = QLineEdit()
            self.user_input.setStyleSheet(edit_style)
            self.user_input.setPlaceholderText("username")
            self.user_input.setMaxLength(15)
            self.user_input.setText(self.settings.get("username", ""))
            self.pass_input = QLineEdit()
            self.pass_input.setEchoMode(QLineEdit.EchoMode.Password)
            self.pass_input.setStyleSheet(edit_style)
            self.pass_input.setPlaceholderText("password")
            self.pass_input.setMaxLength(64)
            self.pass_input.returnPressed.connect(lambda: self.start_patch_thread(False))
            self.remember_cb = QCheckBox("Remember me")
            self.remember_cb.setChecked(bool(self.settings.get("remember")))
            self.remember_cb.setStyleSheet("color: #E8DCC0; font-size: 12px; background: transparent; border: none;")
            self.remember_cb.toggled.connect(lambda _c: self.persist())
            self.logout_btn = QPushButton("Log out")
            self.logout_btn.setStyleSheet(small_btn)
            self.logout_btn.clicked.connect(self.logout)
            acct_layout.addWidget(u_lbl)
            acct_layout.addWidget(self.user_input, 1)
            acct_layout.addWidget(self.pass_input, 1)
            acct_layout.addWidget(self.remember_cb)
            acct_layout.addWidget(self.logout_btn)
            right.addWidget(acct)

            # Settings panel (hidden until Settings is pressed): server preset, eqhost.txt, website base URL.
            self.adv_widget = QFrame()
            self.adv_widget.setStyleSheet(f"QFrame {{ {glass} }}")
            adv_v = QVBoxLayout(self.adv_widget)
            adv_v.setContentsMargins(10, 6, 10, 6)
            adv_v.setSpacing(6)

            preset_layout = QHBoxLayout()
            preset_lbl = QLabel("Preset Server:")
            preset_lbl.setStyleSheet(lbl_style)
            self.server_combo = QComboBox()
            self.server_combo.setStyleSheet("background-color: #1b202b; color: #F4ECD8; padding: 4px; "
                                            "border: 1px solid #5a4a2a; border-radius: 4px;")
            for s in CANDIDATE_SERVERS:
                self.server_combo.addItem(s["name"], s)
            preset_layout.addWidget(preset_lbl)
            preset_layout.addWidget(self.server_combo, 1)
            adv_v.addLayout(preset_layout)

            # Host row: keeps eqhost.txt only when it already points at one of our servers. A folder from another
            # EQ install (e.g. login.eqemulator.net) is switched to play.biggsadventure.com, otherwise Patch/Play
            # would save the foreign host right back.
            host_layout = QHBoxLayout()
            host_lbl = QLabel("Active eqhost:")
            host_lbl.setStyleSheet(lbl_style)
            self.host_input = QLineEdit()
            self.foreign_host = read_current_eqhost(self.client_dir)
            if self.foreign_host in {s["host"] for s in CANDIDATE_SERVERS} or self.folder_kind == "noclient":
                self.foreign_host = None
            current_h = PLAY_HOST if self.foreign_host is not None else (read_current_eqhost(self.client_dir) or PLAY_HOST)
            self.host_input.setText(current_h)
            for i, s in enumerate(CANDIDATE_SERVERS):
                if s["host"] == current_h:
                    self.server_combo.setCurrentIndex(i)
            self.server_combo.currentIndexChanged.connect(self.on_preset_selected)
            self.host_input.setStyleSheet("background-color: rgba(20, 24, 33, 230); color: #F3DFA2; padding: 5px; "
                                          "border: 1px solid #5a4a2a; border-radius: 4px; font-family: Consolas, monospace; "
                                          "font-weight: bold;")
            self.apply_host_btn = QPushButton("Save Host")
            self.apply_host_btn.setStyleSheet(small_btn)
            self.apply_host_btn.clicked.connect(self.save_custom_host)
            host_layout.addWidget(host_lbl)
            host_layout.addWidget(self.host_input, 1)
            host_layout.addWidget(self.apply_host_btn)
            adv_v.addLayout(host_layout)

            # Website base URL override (saved as "base_url")
            base_layout = QHBoxLayout()
            b_lbl = QLabel("Patch website:")
            b_lbl.setStyleSheet(lbl_style)
            self.base_input = QLineEdit()
            self.base_input.setStyleSheet(edit_style)
            self.base_input.setText(self.settings.get("base_url") or DEFAULT_BASE_URL)
            self.base_input.editingFinished.connect(self.save_base_url)
            reset_btn = QPushButton("Default")
            reset_btn.setStyleSheet(small_btn)
            reset_btn.clicked.connect(self.reset_base_url)
            base_layout.addWidget(b_lbl)
            base_layout.addWidget(self.base_input, 1)
            base_layout.addWidget(reset_btn)
            adv_v.addLayout(base_layout)
            self.adv_widget.setVisible(False)
            right.addWidget(self.adv_widget)

            self.log_text = QTextEdit()
            self.log_text.setReadOnly(True)
            self.log_text.setMinimumHeight(120)
            self.log_text.setMaximumHeight(175)
            self.log_text.setStyleSheet(
                "QTextEdit { background-color: rgba(8, 10, 16, 200); color: #EADFC4; border: 1px solid #4a3c22; "
                "border-radius: 6px; font-family: Consolas, monospace; font-size: 12px; padding: 8px; }")
            right.addWidget(self.log_text)

            self.status_lbl = QLabel("Ready to connect and play.")
            self.status_lbl.setStyleSheet("color: #C9B98F; font-size: 12px; background: transparent;")
            right.addWidget(self.status_lbl)

            self.progress_bar = QProgressBar()
            self.progress_bar.setRange(0, 100)
            self.progress_bar.setValue(0)
            self.progress_bar.setStyleSheet(
                "QProgressBar { background-color: rgba(18, 20, 30, 220); border: 1px solid #6b5326; border-radius: 4px; "
                "height: 16px; text-align: center; color: #F4ECD8; font-size: 11px; } "
                "QProgressBar::chunk { background-color: qlineargradient(x1:0, y1:0, x2:1, y2:0, stop:0 #b8862f, "
                "stop:1 #f3d77e); border-radius: 3px; }")
            right.addWidget(self.progress_bar)

            version_lbl = QLabel(f"v{PATCHER_VERSION}")
            version_lbl.setStyleSheet("color: #8d8064; font-size: 10px; background: transparent;")
            version_lbl.setAlignment(Qt.AlignmentFlag.AlignRight)
            right.addWidget(version_lbl)

            self.start_banner_refresh()

            prev = updated_from_arg()
            if prev:
                self.append_log(f"[✓] Patcher updated to {PATCHER_VERSION} (was {prev}).")
            if current_exe() is not None:
                threading.Thread(target=cleanup_old_exe, kwargs={"log": self.signals.log.emit}, daemon=True).start()
            self.append_log(f"Welcome to the Bigg's Adventure Patcher {PATCHER_VERSION}!\n"
                            "• Log in with your game account to download patches.\n"
                            "• Then press PLAY and pick Bigg's Adventure from the server list.\n"
                            "• Game crashing or acting up? Press Send Logs to send your logs to the team.\n" + "=" * 65)
            if self.folder_kind == "noclient":
                self.append_log("[!] " + self.folder_msg)
                self.set_status("No game in this folder - see the message above.")
                QTimer.singleShot(300, lambda: self.show_folder_problem())
                return
            if self.foreign_host:
                self.save_custom_host()
                self.append_log(f"[✓] eqhost.txt pointed at {self.foreign_host}; switched to {PLAY_HOST}.")
            if load_access_headers(self.client_dir):
                self.append_log("[✓] Cloudflare Access service token loaded.")
            if token_valid(self.token, self.token_expires) and self.user_input.text():
                self.append_log(f"[✓] Remembered login for {self.user_input.text()} "
                                "(no password needed until it expires).")
            self.save_custom_host()
            if self.folder_kind == "folder":
                self.append_log("[!] " + self.folder_msg)
            self.start_self_update_check()

        # ---- banners (players online, server, last patch): public website endpoint, refreshed every minute
        def start_banner_refresh(self):
            self.banner_timer = QTimer(self)
            self.banner_timer.timeout.connect(self.fetch_banners)
            self.banner_timer.start(BANNER_REFRESH_MS)
            QTimer.singleShot(200, self.fetch_banners)

        def fetch_banners(self):
            if getattr(self, "banner_busy", False):
                return
            self.banner_busy = True
            base = self.current_base_url()

            def work():
                data = None
                try:
                    client = PatchClient(base, load_access_headers(self.client_dir))
                    with client._open(base + STATUS_PATH, auth=False, timeout=BANNER_TIMEOUT) as resp:
                        data = json.loads(resp.read(4096).decode("utf-8"))
                except Exception:
                    data = None
                self.signals.banners.emit(data)

            threading.Thread(target=work, daemon=True).start()

        def on_banners(self, data):
            self.banner_busy = False
            if not isinstance(data, dict):
                self.banner_players.setText("Players Online:  —")
                self.banner_server.setText("Server:  —")
                return
            up = bool(data.get("world_up")) and bool(data.get("login_up"))
            n = data.get("players_online")
            self.banner_players.setText(f"Players Online:  {int(n)}" if up and isinstance(n, int) else "Players Online:  —")
            self.banner_server.setText("Server:  Online" if up else "Server:  Offline")
            self.banner_server.setToolTip("" if up else "The game server is down or restarting.")
            bonus = data.get("xp_bonus")
            if isinstance(bonus, int) and bonus > 0:
                self.banner_xp.setText(f"Experience:  +{bonus}% Bonus")
                self.banner_xp.setStyleSheet(self.banner_style.replace("#8a6a2c", "#7fd65a").replace("#F4ECD8", "#d8ffc4"))
            else:
                self.banner_xp.setText("Experience:  Normal" if isinstance(bonus, int) else "Experience:  —")
                self.banner_xp.setStyleSheet(self.banner_style)
            last = data.get("last_patch")
            self.banner_patch.setText(f"Last Patch:  {last}" if isinstance(last, str) and last else "Last Patch:  —")

        # ---- settings helpers
        def current_base_url(self):
            return (self.base_input.text().strip() or DEFAULT_BASE_URL).rstrip("/")

        def persist(self):
            """Save settings. Never the password: only username + token + expiry (when remembered)."""
            data = {}
            base = self.current_base_url()
            if base != DEFAULT_BASE_URL:
                data["base_url"] = base
            mq_cb = getattr(self, "mq_checkbox", None)
            if mq_cb is not None and not mq_cb.isHidden() and mq_cb.isChecked():
                data["launch_mq"] = True
            elif mq_cb is None and self.settings.get("launch_mq"):
                data["launch_mq"] = True
            if self.remember_cb.isChecked():
                data["remember"] = True
                data["username"] = self.user_input.text().strip().lower()
                if token_valid(self.token, self.token_expires, margin=0):
                    data["token"] = self.token
                    data["token_expires"] = self.token_expires
                    data["token_base_url"] = self.token_base
            try:
                save_settings(data)
            except OSError as e:
                self.append_log(f"[!] Could not save settings: {e}")

        def on_advanced_toggled(self, checked):
            self.adv_widget.setVisible(checked)
            self.adv_btn.setText("Settings ▾" if checked else "Settings ▸")

        def save_base_url(self):
            if self.current_base_url() != self.token_base:
                self.token = None
            self.persist()

        def reset_base_url(self):
            self.base_input.setText(DEFAULT_BASE_URL)
            self.save_base_url()

        def logout(self):
            self.token = None
            self.token_expires = 0.0
            self.pass_input.clear()
            self.persist()
            self.append_log("[✓] Logged out (stored token removed).")

        def on_token(self, token, expires):
            self.token = token
            self.token_expires = expires
            self.token_base = self.current_base_url()
            self.pass_input.clear()
            self.persist()

        # ---- host
        def on_preset_selected(self):
            srv = self.server_combo.currentData()
            if srv:
                self.host_input.setText(srv["host"])
                self.save_custom_host()

        def save_custom_host(self):
            target = self.host_input.text().strip()
            if not target:
                target = PLAY_HOST
                self.host_input.setText(target)
            ok, msg = update_eqhost_file(target, self.client_dir)
            self.append_log(f"[{'✓' if ok else '!'}] {msg}")

        def append_log(self, text):
            self.log_text.append(text)
            self.log_text.verticalScrollBar().setValue(self.log_text.verticalScrollBar().maximum())

        def set_status(self, text):
            self.status_lbl.setText(text)

        def set_progress(self, val):
            self.progress_bar.setValue(val)

        # ---- patch worker (Check for Updates, and Play = patch then launch)
        # ---- folder problems / new patcher
        def downloads_url(self):
            return self.current_base_url() + DOWNLOADS_PATH

        def show_folder_problem(self, msg=None):
            box = QMessageBox(self)
            box.setIcon(QMessageBox.Icon.Warning)
            box.setWindowTitle("Game not found" if self.folder_kind == "noclient" else "Game folder problem")
            box.setText(msg or self.folder_msg)
            open_btn = box.addButton("Open the Downloads page", QMessageBox.ButtonRole.ActionRole) \
                if self.folder_kind == "noclient" else None
            box.addButton(QMessageBox.StandardButton.Close)
            box.exec()
            if open_btn is not None and box.clickedButton() is open_btn:
                QDesktopServices.openUrl(QUrl(self.downloads_url()))

        # ---- self-update
        def start_self_update_check(self):
            """On start: with a remembered (still valid) login, check the manifest for a newer patcher."""
            if current_exe() is None or self.folder_kind == "noclient" or self.busy:
                return
            base = self.current_base_url()
            token = self.token if base == self.token_base else None
            client = PatchClient(base, load_access_headers(self.client_dir), token, self.token_expires)
            if client.needs_login():
                return    # checked after the next login (Check for Updates / PLAY)
            self.busy = True
            self.set_buttons(False)
            self.set_status("Checking for a newer patcher...")

            def work():
                try:
                    manifest = client.get_manifest()
                except PatchError as e:
                    self.signals.log.emit(f"[i] Patcher update check skipped: {e}")
                    self.signals.self_update_done.emit("none", "", "")
                    return
                self.run_self_update(client, manifest)
            threading.Thread(target=work, daemon=True).start()

        def run_self_update(self, client, manifest):
            """Worker thread. Always ends with self_update_done."""
            s = self.signals
            newer = newer_patcher(manifest) or {}
            try:
                s.status.emit("Updating the patcher...")
                status, msg = self_update(client, manifest, log=s.log.emit,
                                          progress=lambda d, t: s.progress.emit(int(100 * d / t) if t else 0))
            except Exception as e:  # never leave the player without a working patcher
                s.log.emit(f"[!] Patcher update failed: {e}")
                status, msg = "failed", manual_update_text(newer.get("version", "?"))
            s.self_update_done.emit(status, msg, str(newer.get("url") or DOWNLOADS_PATH))

        def on_self_update_done(self, status, msg, url):
            self.busy = False
            self.self_updating = False
            self.set_buttons(True)
            if status == "restart":
                self.append_log(f"[✓] {msg}")
                self.set_status(msg)
                QTimer.singleShot(400, QApplication.instance().quit)
                return
            self.set_status("Ready.")
            if status in ("manual", "failed") and msg:
                ver = re.search(r"\(([^)]+)\) is available", msg)
                self.on_update_available(ver.group(1) if ver else "", url, msg)

        def on_update_available(self, version, url, text=None):
            if self.update_notified:
                return
            self.update_notified = True
            if url.startswith("/"):
                url = self.current_base_url() + url
            text = text or manual_update_text(version)
            if "available" not in text:     # e.g. "updated, please restart"
                QMessageBox.information(self, "Patcher", text)
                return
            self.append_log(f"[!] New patcher {version} available: {url}")
            ans = QMessageBox.question(self, "New patcher available", text + "\n\nOpen the Downloads page now?")
            if ans == QMessageBox.StandardButton.Yes:
                QDesktopServices.openUrl(QUrl(url))

        def start_patch_thread(self, launch_after=False):
            if self.busy:
                return
            self.folder_ok, self.folder_kind, self.folder_msg = check_client_folder(self.client_dir)
            if self.folder_kind == "noclient":
                self.append_log("[!] " + self.folder_msg)
                self.show_folder_problem()
                return
            if self.folder_kind == "folder":
                self.append_log("[!] " + self.folder_msg)
                if not launch_after:
                    self.show_folder_problem()
                    return
                ans = QMessageBox.question(self, "Cannot update the game",
                                           self.folder_msg + "\n\nStart the game without updating?")
                if ans == QMessageBox.StandardButton.Yes:
                    self.launch_game()
                return
            self.last_error = self.last_error_kind = ""
            self.save_custom_host()
            target_host = self.host_input.text().strip()
            base = self.current_base_url()
            token = self.token if base == self.token_base else None
            client = PatchClient(base, load_access_headers(self.client_dir), token,
                                 self.token_expires)
            username = self.user_input.text().strip().lower()
            password = self.pass_input.text()
            if client.needs_login() and not (username and password):
                self.on_failed("auth", "Enter your game username and password to download patches.")
                return
            self.busy = True
            self.check_btn.setEnabled(False)
            self.play_btn.setEnabled(False)
            self.progress_bar.setValue(0)
            threading.Thread(target=self.run_patch_worker,
                             args=(client, username, password, bool(launch_after)), daemon=True).start()

        def run_patch_worker(self, client, username, password, launch_after):
            s = self.signals
            try:
                if client.needs_login():
                    s.status.emit("Logging in...")
                updated, failed, manifest = run_patch(
                    client, self.client_dir, log=s.log.emit, status=s.status.emit,
                    progress=s.progress.emit, username=username, password=password,
                    on_token=lambda t, e: s.token.emit(t, float(e)))
                newer = newer_patcher(manifest)
                if newer:
                    # Play: start the game first (finished below), then update the patcher.
                    self.self_updating = True
                    s.finished.emit(failed == 0, launch_after)
                    self.run_self_update(client, manifest)
                    return
                s.log.emit(f"\n[✓] Patch check completed! ({updated} updated, {failed} failed)")
                s.status.emit("Client is fully patched and ready!" if not failed
                              else f"Patching finished with {failed} error(s).")
                s.finished.emit(failed == 0, launch_after)
            except PatchError as e:
                s.failed.emit(e.kind, str(e))
                # Offer "launch anyway" only when the patch server simply could not be reached.
                s.finished.emit(False, launch_after and e.kind in ("network", "server", "folder"))
            except Exception as e:  # never leave the UI stuck
                s.failed.emit("server", f"Unexpected error: {e}")
                s.finished.emit(False, False)

        # ---- Send Logs worker
        def set_buttons(self, enabled):
            self.check_btn.setEnabled(enabled)
            self.play_btn.setEnabled(enabled)
            self.logs_btn.setEnabled(enabled)

        def start_send_logs(self):
            if self.busy:
                return
            # Logs always go to the website, even on the LAN preset (the LAN http server has no upload).
            base = self.current_base_url()
            token = self.token if base == self.token_base else None
            client = PatchClient(base, load_access_headers(self.client_dir), token, self.token_expires, mode="web")
            username = self.user_input.text().strip().lower()
            password = self.pass_input.text()
            if client.needs_login() and not (username and password):
                self.on_failed("auth", "Enter your game username and password, then press SEND LOGS again.")
                return
            note, ok = QInputDialog.getMultiLineText(
                self, "Send logs",
                "What happened? (what you were doing, which character/zone, roughly when)\n"
                "Your hjclient.log, UIErrors.txt, Logs/dbg.txt, the newest crash dump and a list of\n"
                "client file versions will be sent. Not your eqclient.ini or chat logs.")
            if not ok:
                return
            self.busy = True
            self.set_buttons(False)
            self.set_status("Sending logs...")
            self.append_log("\nCollecting logs (the game is not started)...")
            threading.Thread(target=self.run_send_logs_worker,
                             args=(client, note.strip()[:2000], username, password), daemon=True).start()

        def run_send_logs_worker(self, client, note, username, password):
            s = self.signals
            try:
                res = send_logs(client, self.client_dir, note, log=s.log.emit, username=username,
                                password=password, on_token=lambda t, e: s.token.emit(t, float(e)))
                s.logs_done.emit(True, f"Logs sent to the team ({res.get('files', '?')} files, "
                                       f"{int(res.get('bytes', 0)) // 1024} KB). Thank you!")
            except PatchError as e:
                if e.kind in ("auth", "expired"):
                    s.failed.emit(e.kind, str(e))   # clears the token and asks for the password
                    s.logs_done.emit(False, "")
                    return
                msg = str(e)
                zb = getattr(e, "zip_bytes", None)
                if zb:
                    try:
                        msg += f"\nA copy was saved as {save_logs_copy(self.client_dir, zb).name} in the game folder; " \
                               "you can upload it on the website (Send logs page) instead."
                    except OSError:
                        pass
                s.logs_done.emit(False, msg)
            except Exception as e:  # never leave the UI stuck
                s.logs_done.emit(False, f"Unexpected error while sending logs: {e}")

        def on_logs_done(self, ok, msg):
            self.busy = False
            self.set_buttons(True)
            if not msg:
                return
            self.append_log(f"[{'✓' if ok else '!'}] {msg}")
            self.set_status("Logs sent." if ok else "Sending logs failed.")
            if ok:
                QMessageBox.information(self, "Logs sent", msg)
            else:
                QMessageBox.warning(self, "Could not send logs", msg)

        def on_failed(self, kind, msg):
            self.last_error, self.last_error_kind = msg, kind
            self.append_log(f"[!] {msg}")
            self.set_status(msg if len(msg) < 140 else msg[:137] + "...")
            if kind in ("auth", "expired"):
                self.token = None
                self.persist()
                self.pass_input.setFocus()
                QMessageBox.warning(self, "Login required", msg)
            elif kind in ("locked", "rate", "access"):
                QMessageBox.warning(self, "Cannot download patches", msg)
            elif kind in ("noclient", "folder"):
                self.folder_kind, self.folder_msg = kind, msg
                self.show_folder_problem(msg)

        def on_patch_finished(self, ok, launch_after):
            if self.self_updating:      # the worker goes on to update the patcher; keep the buttons off
                self.set_status("Updating the patcher...")
            else:
                self.busy = False
                self.check_btn.setEnabled(True)
                self.play_btn.setEnabled(True)
            if not launch_after:
                if not ok and self.last_error_kind in ("network", "server"):
                    QMessageBox.warning(self, "Update check failed", self.last_error)
                elif not ok and not self.last_error:
                    QMessageBox.warning(self, "Some files were not updated",
                                        "Some files could not be updated. The messages in the window say why "
                                        "(usually: close EverQuest and MacroQuest, then try again).")
                return
            if not ok:
                why = self.last_error or "Some files could not be updated (see the messages in the window)."
                ans = QMessageBox.question(self, "Patching incomplete",
                                           f"{why}\n\nLaunch the game anyway?")
                if ans != QMessageBox.StandardButton.Yes:
                    return
            self.launch_game()

        def launch_game(self):
            self.save_custom_host()
            launch_mq = self.mq_checkbox.isChecked()
            self.append_log(f"Launching the game{' with MacroQuest' if launch_mq else ''}...")
            ok, msg = launch_eqgame(self.client_dir, launch_mq=launch_mq)
            if ok:
                self.append_log(f"[✓] {msg}")
                self.status_lbl.setText("Game launched!")
            else:
                self.append_log(f"[!] Error: {msg}")
                QMessageBox.critical(self, "Launch Error", msg)

    app = QApplication(sys.argv)
    win = PatcherWindow()
    win.show()
    smoke_ms = os.environ.get("BIGGS_SMOKE_MS", "")
    if smoke_ms.isdigit():
        # Test hook: save screenshots of the window (and any open dialog) to BIGGS_SMOKE_PNG, then quit.
        def _smoke_shot():
            out = os.environ.get("BIGGS_SMOKE_PNG") or str(app_dir() / "smoke.png")
            win.grab().save(out)
            lines = []
            modal = QApplication.activeModalWidget()
            if modal is not None:
                modal.grab().save(out.replace(".png", "") + "_dialog.png")
                lines.append(f"DIALOG: {modal.windowTitle()} | {getattr(modal, 'text', lambda: '')()}")
            lines.append("LOG: " + win.log_text.toPlainText())
            with open(out.replace(".png", "") + ".txt", "w", encoding="utf-8") as f:
                f.write("\n".join(lines) + "\n")
            app.exit(0)
        QTimer.singleShot(int(smoke_ms), _smoke_shot)
        login = os.environ.get("BIGGS_SMOKE_LOGIN", "")
        if ":" in login:     # test hook: log in and press CHECK FOR UPDATES
            win.user_input.setText(login.split(":", 1)[0])
            win.pass_input.setText(login.split(":", 1)[1])
            QTimer.singleShot(800, lambda: win.start_patch_thread(False))
    sys.exit(app.exec())


# ----------------- CLI Mode Fallback -----------------
def _arg_value(flag):
    if flag in sys.argv:
        i = sys.argv.index(flag)
        if i + 1 < len(sys.argv):
            return sys.argv[i + 1]
    return None


def run_cli_send_logs():
    """--send-logs [--note TEXT]: non-interactive when frozen (the .exe has no console).
    Uses the remembered login token, or BIGGS_USER / BIGGS_PASS, or prompts when run from a terminal."""
    client_dir = app_dir()
    settings = load_settings()
    base = (settings.get("base_url") or DEFAULT_BASE_URL).rstrip("/")
    token = settings.get("token") if settings.get("token_base_url", DEFAULT_BASE_URL) == base else None
    client = PatchClient(base, load_access_headers(client_dir), token, settings.get("token_expires", 0), mode="web")
    username = os.environ.get("BIGGS_USER") or settings.get("username") or ""
    password = os.environ.get("BIGGS_PASS") or ""
    note = _arg_value("--note") or ""
    if client.needs_login() and not password and sys.stdin and sys.stdin.isatty():
        import getpass
        try:
            username = username or input("Username: ").strip()
            password = getpass.getpass("Password: ")
        except (EOFError, OSError, RuntimeError):
            password = ""   # no usable console (windowed .exe): fail cleanly below, keep a local copy
    try:
        res = send_logs(client, client_dir, note, username=username, password=password)
        print(f"[✓] Logs sent ({res.get('files')} files, {int(res.get('bytes', 0)) // 1024} KB).")
        return 0
    except PatchError as e:
        print(f"[!] {e}")
        if getattr(e, "zip_bytes", None):
            try:
                print(f"[i] Saved a copy: {save_logs_copy(client_dir, e.zip_bytes)}")
            except OSError:
                pass
        return 1


def report_startup_error(what):
    """Write the traceback to patcher_error.log next to the patcher and show a message box on Windows
    (the .exe has no console, so otherwise the player sees nothing)."""
    import traceback
    path = app_dir() / "patcher_error.log"
    text = f"{time.strftime('%Y-%m-%d %H:%M:%S')} patcher {PATCHER_VERSION}: {what}\n{traceback.format_exc()}\n"
    try:
        with open(path, "a", encoding="utf-8") as f:
            f.write(text)
    except OSError:
        pass
    print(text, file=sys.stderr)
    if sys.platform.startswith("win"):
        try:
            import ctypes
            ctypes.windll.user32.MessageBoxW(
                None, f"{what}.\n\nDetails were saved to:\n{path}\n\nPlease send that file to the server team.",
                "Bigg's Adventure Patcher", 0x10)
        except Exception:
            pass


def cli_self_update():
    """CLI: with a remembered login, update the patcher first. Returns True when this process should exit."""
    if current_exe() is None:
        return False
    cleanup_old_exe(log=print, tries=6)
    prev = updated_from_arg()
    if prev:
        print(f"[✓] Patcher updated to {PATCHER_VERSION} (was {prev}).")
    settings = load_settings()
    base = (settings.get("base_url") or DEFAULT_BASE_URL).rstrip("/")
    token = settings.get("token") if settings.get("token_base_url", DEFAULT_BASE_URL) == base else None
    client = PatchClient(base, load_access_headers(app_dir()), token, settings.get("token_expires", 0))
    if client.needs_login():
        return False
    status, msg = check_and_self_update(client, log=print)
    if msg and status != "restart":
        print(msg)
    return status == "restart"


def run_cli_patcher():
    client_dir = app_dir()
    print("=" * 65)
    print(f"Bigg's Adventure Patcher {PATCHER_VERSION} (command line)")
    print("=" * 65)
    print(f"Working Directory: {client_dir}")
    try:
        if cli_self_update():
            return
    except Exception as e:  # never block the CLI on the update
        print(f"[!] Patcher update check failed: {e}")

    host = PLAY_HOST
    if len(sys.argv) > 1 and not sys.argv[1].startswith("--"):
        host = sys.argv[1]

    ok, msg = update_eqhost_file(host, client_dir)
    print(f"[{'✓' if ok else '!'}] {msg}")

    if "--launch" in sys.argv:
        ok, msg = launch_eqgame(client_dir)
        print(f"[{'✓' if ok else '!'}] {msg}")


# Patcher backdrop (3.7): Trevor's Walking Dragon illusion (race 530) on a moonlit crag, rendered from the
# client's drg.eqg. JPEG, base64, so the one-file exe needs no image next to it.
BACKDROP_JPG_B64 = """
/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAQDAwQDAwQEBAQFBQQFBwsHBwYGBw4KCggLEA4RERAOEA8SFBoWEhMYEw8QFh8XGBsbHR0dERYgIh8cIhocHRz/
2wBDAQUFBQcGBw0HBw0cEhASHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBz/wgARCAPeBdwDASIAAhEBAxEB/8QA
GwABAQEBAQEBAQAAAAAAAAAAAAECAwQFBgf/xAAZAQEBAQEBAQAAAAAAAAAAAAAAAQIDBAX/2gAMAwEAAhADEAAAAf4RvjveXTj0N3Nrdxo2zTUWltM3UBTN
tMc+3M5Y64jlnrDk3mE9fnrFgsAB2407+dSKAFgWAUgHXv4zWdZALNQlD154aPRjCulzsupoksN8bgnPWTOdQioTUEoGiFE+h4AoVRqbMzWoxb6l8tVHTHsX
yVTWfX5RpYVRWiy1ZbSejjYltJbSW0TWjHXGjOd+08DfojydsQqDOeiuc65Oc6Q5tw5zpDnNwzneTOdwmd5MzWazOmEzNQwow1ky1KzNQmdQk1TE7ciAyokp
JLVyvZOEosVZqROlxqrcD2c+XQ1rNreuejUUm80ttMc+uDi6ZOU6YOWOuIzZTrxoSiKI16zxatMN0y9vmOU6ZMLTKiKIoAAAHpPPcq1rGzp05bOlwN5is49f
kjMuBGzAPV5SDSnt8OohaaiBRqdSa12OE6jk7ZXlbZJoVVGrTNoaWGp0XNaJbSW2JbRbQtE1TPq89MtjGro5OgxNwk0Mtw6+PpI5zpa4zcM43DGdwzncMZ6Z
rE6ZMTUTE1DM1DOd5GdSszUMzUEsMzUSTUIFk1E6ctZWL1OCkgBqod14BN9eOzqxqt3OjWs7LVpVM53DnneYxz6YOedw5evzSIoiiKJqDdm6ltMzcObUMZ6Y
Ionr8uowtqL6o8d1mgCwAWDprls6Xls1OnA1m5GKM+zyCLRAWwKKBWozrVqdJo3vOzNuRlJctxJVVqahVK9XmGlhpolaWaaJbYX34PJdULTOtUx7PNTLXoPL
doxNrcTpJMOmTE3K5zpkxNjlOmTnNwxncMZ6ZMTpisTeTE3lM53kzNQzNdK88+jhPHjphZnUM+nz0xneT1eSxJKJKJKIBKWFsgB6Dz0Kg6d/N3HXl0res7LW
qqjE1kxjeTE1I5Z9nlOeOmBAWBYFmjXTHQ0Uk0s7ePriXjnpgiwWA1ksUjUAACwA93jyFgqDSCAFIoFJQUGmi61TN9PIzqwtyNIJLItlJVW6lLZqFUamhqah
pomlVbYaaJbSbUl1TN1THRZebdMNjm2Oc6Q5tyznnrk556Q556ZMducOeemDOd5MTeanXlkxnrU4TfY8vp78U7548jjz3i2TWST1+UzL0OM1EysqTUJKJKiS
qgAFgazSKALvO63vns6b46O2uW66XOzOd5Oedwxnpk556Zjlz74Ovl6YJYAKlOm+XQ3ZqyWRbjWUxnpiXE1CKIoiiNQmoSwAABSUIpYoiialPR5hChq7XFtJ
arepsmrSZ3DDSM+jjTM3DOpYWlvv8NG5YtUamhqaGuvOW26JbRp7Y8erRbSzVJdWWT6n6nL8J7v6T256/nWv6L1j+V/O/tHGv42/oX5Ppn5GOudM46Zs556Z
MZ6ZOeemTnnpDnncrnN4M41qTl31ETro8875PPy3yt5Z3mpLo5Trzsz25RctZTKwijM1Cevy9Tg1kiiLACwAoU1rNN756NazTW+e67bxumd5MSyGdROc3lee
emExnpiXHXAyAaG5bN3FNSBLCS5JAiiKIqAAoAsAD0eeBaiiKFgAFhpq26aMdufc89uhqU3rGxnUIaJdIznpDm0hVWlLaLqahbRqaOmW4laVpo9HFqFuhWhb
3lx+4+x9fhrhjtjlrhnqrPbGzpliM8euK/Nfiv6x4umf5bPZ5++eOOss5Z6ZOed5MZ3msZ15E6Nby5Z6+Y9GOHROtys6+bOFcvRw1c+jzwzN5rE1EmdxczUT
M1CSiSjKwSiTUIAKdOe4maootmxc6NXOqu8bOu+erNyaMZ1kkSGdZJncOc6ZMZ65OToMXYlAUEKzSY3kyoiiwAAHfgAAAAACiaQlAsANbz0L6/Ns68e+K53c
iaVZQevyUupomlJNwxOkjDclmmybuzF3CVYtaPb5JuGppW5obmobmybaH9L+D+94bMuOs56Q456ZJqaHLrk5KpvO4+V/Mv7J+N65/EZ6Z9GeeemE556cicfL
6U4z3cpJntleHD2czwa9vKMWZrr5t50zNZrM1mmNQzNRMzUJLCSwgJKMrCLBKMqqKIolC1oatMtLJWhqU1rFOl56q5ozNZIkiwJASjN1kLo5t8xMjUgWCzWS
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
kozSgAO/bxUihrIAAKqKAgsooiiKJqaLqbFC7zqrNQz0mjCwzNQxNQUJZkznWSKIoiiKIolEAAWAaMqJQFJqbW6aHbnCWUtzo3rPQmemTNUbzqNWaLVFlKot
zZdMjesajdzs10z0Nbzs1y66Pzfu+xyk9PzvlzD2dvl+2v03ze/L0Y9E1157/Jf0f8b+m7Y79Z19fLj8H9T+W8nbEZ8vV4Pf8NPqfE+x+cxJnpoxcdZbdQz0
ZO2M6s5Z9NPJ0vOXtrhK9+M76Tj6fPz05zWTM1kksTMsJLBjUrM1CSjKwkqJLKSiSjKgaMgFIqggqooiiKPd4VJ6vMN86IvsPEqms2OmsbLVppRVFDOekOer
DE6DnOkOU0MZ3EzqeqXxqqKIukyoiiKIoiiejgIolABvNOuuVXv38Q2lLvOjpc01mjNdDGpTVmi6xY1rGjTIqVakje+XQ6dMdDpvPQbljVxo3vHRfjfK/X/l
cTfp8MzPp/ovg9es+q3m68fqz8FP2v1Pk+n6HD2/I+/86PzcZ+b6cfn/AH5zMfL9vixV5bLpmu8uom8dDlOtMXeaxNYNzPezpnWOrnz64OeOmTGdxM51lcyy
pLEZsIsJLTEoksIsICLCKAABajUJQKIoSiKBQtMKIoi2oWL0x0q6lTWs6VbSW0w0rM3Iy0rnjriOU3lM5sMqWKIpCiKIoiiKIoiiKIoAFALrNN3JemsaNa50
6XA3c6Lc6NayKg1cWNJDprFLINdOXWXr24dTt05dDozS1Te+ezp5u9j8P7fpfG5vd9X4v1ZPb7fzPt6vtfiP2X5SX9Z9b8x+r9nH6GPPz7Z/PHn+b6fmer5/
p5zyeX1+fN5asLZs104brr049ziaN5vWzhw9WY5dramdzsxnpk5Z65OWeuDnjrlOU6Q5zeTM1DKwkozLCAiwkokoiqldjioiiKJQKIoiiKBRVpNDM3U5tRZp
obE1ZS6zo1rUW22stw5tQme/AxqZTGOuDlneYiljUQoiiKJSooiiKIoihKiUBVlBZRZTW+ejTMOmuXQ3rFN3OxrA0mDbFNs6jWsU0lNb57O3Xj0OvTjte2+e
o3vGjdzTdwN/mP00j8t183Pk+rzx3PRrwdOmen7L8l9j04/Q48fxu2ePxM+z5vdq7y4fP9/CXzCXNYT0c+vPU9Ws6XLtky661OfXGClJN56szQ553mzGOmTl
npkxNDGekOWemDC0zN5Oc1CShLTOdww1mCqno4AUiiKJQFIoiqigUtzTSaItSTVMatIpbc6RvPStbzV3ZoTWUmNQz145EQmdZOfPrDmoKOuvOgqkojUIoiiK
O/n1CKWUQAUAUFlFlFFag3382o3edNsaFlLZS2Uus01c6i7xs6evydxvPQ1vGjpcaNaxpd3NjVzTz/k/2nzcvk+j5/t5uvPe48k6+Ws+jyNT0fSvXFjHRfJP
UPneX6/HU+bnrxzfdMdI8/v4fU3M606M56ZrE1kzneUY1KzNZM51knPpk553kysJnWTGOkMTWSZsMzUJNCPZ5TM1Dt5NwyAUAAsKFAAABohUlUtlLRVVFUk3
KzVXW87S1TWs0slJnUOeemDM3kzncMZ65OLWQAAAAAAUiiNQiiLTNolAUlDUB6vL6Tz2UWUoKpVC2UqWNkNXNNWU3vOzXTGjr05dDdlLco6b5bOlzotzDckX
4vz/ANP+e5unr5ejm8Xj9niPVp6jye3xfQODn1rpx78Trw9e9T5/zv0XxC9eHfGvodfkfS9GOrrxtsiJjWBmwyDOdYMgmd5MZ3KxNw553DE1DGd4jEuRApSL
CTQzNQ5qIqpQKIolAolCKIoFRVBS2WlUVsjVXndibmkssLZS2WpLCZ3DnneYy2rOekjhntg5tQiqiiKIojUPV5KiWiTrmsWiN5Ioi0jUIqCgoFBRQVRZRVUo
VRVhqaN7xs1vGzfTns6XOi2aLqDWudjUzTcgcuo/M67eXhrvy75y79PF3sz9D43vOr0+fU7eX18Zfp/T8no3n53xPtfIxfPxzqXprr3rHv8AjfQ7Z9HLy9q1
ntxtiBm5GdQxVI3DnOkOeeuTlN5OeOnMxjeTGd5MqgUstrM3DE3kjpIw3a53Yw604vRo8z1Q8z0jzPXo8T1o8r1DzX0W3z67I53YzdaMtqxdUxOgw2M3VMNQ
irLcDbCXcWzE2MXQxOmY557Dz59Q8j1q8j1jyPaPE9eE87qrDpTi6053oOU6w5Og5ug5ulObojm6Dneg57tLy7Q53dOd3Tnd1cXYy3TF2Lj1+czdUW7M7bJq
6i26JuaLpoTQxaUhLZYTS3wfD/U/JxPP04deOuX0vh+2z08c9U9/Xr+n7Z/P+f7nwbevi7fJOmeV5X53p45xfsb+do6+nze3U5eP1ajt5evLT2+b1eLpNTrj
VzNDF2M3QxN5M51Dnjrk4474OOO2DlNw5twzdUxd0w1DE6C62ljQzdCW0y0MW0i0y2t5zok5tjN3V5uks5t2M3VrLYw2XLRMzpFxaSNUzOkMToObYzbVy0TD
cMzoObdXDZMN6ObrDDYxbTM6K5zrTlnuPPPTE889I897xeN7Di7w5Oo5XpTjO4889MTzXuOF7U4Xt6j597Djew5XoOd0MtRZKFg1eY3gidMQ9nX5tPq35VPq
b+Ta+vfkaPqPl9o9982zu4U6+W+c+P6fl/d4PBw+14rJ6tdcr9f5no7xnUafE+58qPkd+X0eDxvoeOu3ffHLv6Omu0+V6+3Cvn/Xxcy/H9ec32+b1/Nr1Z9P
n6FrSNDGOmTnNwxnpDnjrk4474OM64OboOd3TLY5zpDDcOjSazbSNUxdQk3DN0I0I0rM3YxdKltMTcM3VMXVMXQy2MNCTQxNjNtI1Uw2MTpDLVXE3DM2MtUz
dUw2TN1axOkOd1TDcjLVMtKjQy1TE6Qw2MtjDcMTcjNtItrE2MW0jVJbaw2MzY53SMNwxNjDaObpDDasNjGeg5tozdUxpaNDE3DLQy0MzY+T9j5f0fM9Py/q
/N6zv7PF7uS8b5+71dvnLfqY+fo+Z9Ht4+Lvyu8rrl1Pu/Px6PRnzTcaw1DLUTzPRjLlrnyw334o9U576plLSjM3E556ZOWeuTlnrDm3Tnd053VOc6Dm2MTK
a1rnToxTTMNXI3cWtINMjTI2wNsjTMNsU3edNsjVwNMQ3MDdwOl506XBNsWqkNMyNTI2xV3edTbA6TCt3nTpMjcyNMjTMNsDo5jpeejVzDTA2wN5mTUzmOl5
U6udrbEOjnTpedOl52tsjbmNzOY3MDbA3cDcyKkjTKqyNMU0wN3nY3edrbA2wNMDd5083p8/q4Pb873eXtPL9L531POz5enD03bEjczDdxC57fL5PpW87PV7
Pn+5PNnx8Omvfn4cj7l+Lpfrz5GD7D4vU+rvw9U9rwc8PoY5+yTy6+n8rd63zNPTnhTrnIIASoKyNSdjkyOQlWVbckqValLZbLYAUQWUoEsKEKFlBTE1FgCD
VyN3NTVirEhAIKlCUtzSyhZQSqlFgsQILYjVza0lJNQSiS5JKiVRYpLIWWrYNaxTciiSLmwAUKgoEsIIAiwgqoNJQlLAECdjx+jz9uM+j4fpfJ6zn9n436Pm
+FMzprUmTbBejntOvzfsfGxPs8e11PN282MXx8NTWooijKolUiqzdDPbmPf7PiMz7Xb4f1Dz2e6vG6eXTqvKus58o9LyU9by8V9+fmyPbPHU+gq2LEllUVKV
aLCxVlSIW2VFFSkVQUlWpQmdSMylyBZpLrOhYEsEAFAFFlRZqgIIWWksIBViAWUtza0lqLIzNQi0zaIoyoiwWUXNNIEAUAlACpQCNDMxxjrxyjDUl1mrMtwu
aMalj1XPXL6Ofkbs9Xu+VZPp/Od7b6vH8c9N8Opr3a8A+i8PWT1fR+DnU+h7fj2Pr+LFs+n+U9nzJrq4l7TkOrlTo5jpeVOrlTpedOjG6taI1ax0g9O/HJPZ
nyo9Tyq9Hlstw3Iw2MNw91rTLcMXQzdBbTN1aw3DM2MToMXQzaI1TLVSNDN0rLYxnpI5zcXDdObYzqkTZcTYw2MVBbow2TLYzdKy2MNjDUjLSstjGtUxOsOb
YxdUzXM245j0TiOuvPo7OWa7uI6zkjqxSs6DYw0rN1TN1TDoOd6K5zpiJeY6cmFxOiOTojm2Oc6DndjDdObt1OHPtxyxjojlO48/P08Y8+ulOTtDnOo5XpTi
7DjPRDjroOeOuTm3qObtuvHj6A+c9fI5O1OF7q4u443tTlvejOtarLastDDYw3DDY5tjE6I5ukObpD1sNN3A2xTVwN3FOl52tzI0zDbI3Mw2yNsU3cDd5k6X
mOrna1M5jUyXbEOjlmO+OOT0POPQ4Q9GeI2xDowOrnTprla7a88PTvyQ9ufIT1PKX1XyVPXfNk9U8ZfW8g9mPMjvOMOjA3edOjna3eY2wNsDo5jbA2xDprgj
vvyw9l8Kvo35pPp5+eX248o755o63na6OdNzA3MDbEjczDpMdB11lEllxneYyDVzszjpDjOuTGOljGrs5ugxN2uetjlOvM5Z65jO9bMTeSRTny9HKOXWdQtr
DasLTNK0yNMDcyNXA3MjcyNMDTI0zDUyju4q7OQ63hzPU8dPbfFs9d8cr2zwj2PIj158o9GvMPU8o9m/BqvY8ZPZfFF9ufJU745pd3FTWua3o5jpMDbmjpeZ
OkwrTMXbFk3eaurnV6OY6OY2wN3mOjnU2xDbmXcwN3kjreVrbETo5l63lTpedrbEjo5U6OcOjmOjmOkxDbETpOZejA2wTd5l6OcOl5063jo6zmNuY6uQ6MDT
nI9O+G47Z5ZO182jrjlI7Xls6XnqrM84644jteOjprnDrMhvlk7zlk788w1eeTteOjpnIlkjWAusSujls6MUdOPQ64uCZtrLMrbA2wNsDcyNMw3MjTMjTAjI
1JC3I0zTUgqDVyNMqrNioNMjbKtsDbI0g0hLcDckNMjcyNMjVyrSQqI0yNMjVwNsWtMjTNNM00yNSQskKyjTI3cK3IKyN3FNsU1Mi3A3Mw2xTcyNMiySKkN3
FrTMNMo0yNXnTd5jo52tsU0yjfLPPN675aju4jrOI6uOT0TjTtrz09bz2umMYjV55PRvz9TpcK2xDWMyNznTd4Q7OWl6OUTreNO+MQ3vjs3lC3lk7uOjrriO
/PGDrrz6OmeeTu5Z07uGzoyrTI1IKkjUgqDzTTJqDWuau989O84D0OW61ZSNqw2MXQy0MtjDYy1TN1Uy0rE6I5tjDYxdU5tjN1aw2ObaMNjDdMTorm2jN1ax
dUxdUw2MTpDnOkOc6I53YxdWsNjDYzdUzOkOc6Qw2ObpDF0MtjDVObYw6DndUxOg5TrmMXZcOhMToMNjCYl6YxnN1lIXI3cQ3MjUyNMw6XnTq5w3mZNMDr14
dDo5yut5DcyEkkssKyW5QtxTrMZOrmO+eY1Mw6a407OY3mCyQskN5ExbTOirjQzuU3eauuuA7uWjLaMtDLYw2MXYw2MNjGygNXA6XmOt5brVkNXA6Z5DtrzD
0zgO85U6XlDo5DtjEOmuKPTfLa9M46rowNucOzno0tI0rK6MtjLQzNUxNwxNow2Mt2sNjm6DF1TLQ5ug5ukOboOboObYw2MN05ug5ugw2Oc6w53YxOg5t8I1
5sTKpc2yqghSk0MVYmd5MrCACLAlWrvNNIIg0CSwSxEQqFubAQqUTUIsiazTVloUznpkzSIEWW3VztI2rF2MNjDYxOkNqItMtCLai0zaIoy2MNDLQjQy0Ioj
QijLQy0MtUy0MtQiiNUw2MtDLQiiLTLQy0MbCxKubTOojSKsoag1cDrrgr0MdKiwJoKMtDLQzNDLUI1gt5o6OI7TiO150252tJSY5+TLXHUzbCJQAXI1c2qy
NXNhnUMzWRNDFsEpJZVqQ0zSsjbI0yKkSwJGZdJbIFdOeyxTNsItC5GsaLKMyyQW3OlCh14j0MdKi2zKiTSObA2wOjnTo503eY6MK6OdNsDbA25joxDo5jo5
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
XR9F6LrS6Lo2XRMT1pSlL1PVFKJlKUpSlKUul0vSiEIQhCEIbCUfEhCEIQhCEIQhDboiXkj5NJRkJrCae49i0mkIQg0clORtLkl5I8hO9CafK0hCEJpCEIQh
CEIQhCEIQhCEIQhCEIQhCEIQhCEIQhCEIQhCE1IQhCClyP8AjpemEITSEH211IfZfcXQ+tG5CEIQhCEIQhCEIQhCEIQhCEIQmkIZXIn5JJPgUPO5CEMrlnsH
s0PcbkIQhCEIJpy9CvCPgPcewXoEvKPcexFT5KlyVeUQhNNjD2ZCaQhCEIQhCEIQhCEIQhCEIQhCEIQhCE0QhCEglYDPJ999y6UfW+il1us6J30yEIQhCEIQ
hCEIQhCEIQhCEIQhCEIQhCEIQhCEIQhCEIQhCEIQhCEITRCEILYPQ9jPaPO5BK5Pcew9hX5KPiR4I9iZyQhCEIQhCEIQhCEIQhCEIQhCEIQgtMnxOr6r0PWd
M7FKUvbvUtIToQ+0lrAWSE0hCEJ0X6a6Uul6b0Uv0lKUpS6UvWo2E8kPSXJ7j3IpS60pdKUar3pV4Wl7hu5PcLzR8dKfGh7ESwGeT7a6EUuj65relPSl1ur6
70zV9D6Z0pwS9y9dKUpSlKUpSlKUpSlKUpSlKUpSlKUpSl0UpSlKUpSlKUpSlKUpSlLopSlKUpSlKUpTAryUpRPsYvNE+Bs9aE6EvOg/BHuG73ZSlKUpSlKU
QGds9L9BOp6XVdL6mUXVS9hdhoVFLopSlKUpSlKUpSlKUpSlKUpSlKUpSlKUpSlKUpSlKUpSlKUpSlKUpSlKUpSlKUpSlKUpSlKUpSlKUpSlKUpSlKUpSnj0
WrKUT6H0XppemE6HqtZ3ETpvQ30LqXS+jYRpSlKUpSlKUpSlKUpSlKUpSlKXRSlKUpSlKUpSlKUpSlKUpSlKUpSlKUpSlKUpSlKUpdSl0UpSlLouilKUuil0
UpS6JdDG9EIWj0etKUvWhj6IT6J9ylLql2F0JwpSlKUpSlKUpSl0UpSlKUpSlKUpSlKUpSlKUpSlKUpSlKUpSlKUpSlKUpSlKUpSlKUpSlKUpSlKUpSlKUpS
lKXRSl6aNj0QtWPqoupCKNjFpNITtrsMXZonquzemlKUpSlKUpSlKUpS6KUpSlKUpSlKUpSlKUpSlKUpSlKUpSlKUpSlKUpSlKUpSlKUpSlKUpSlKUpSlKUp
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
6F+L/9k=
"""


if __name__ == "__main__":
    for _stream in (sys.stdout, sys.stderr):   # None in the windowed .exe; a cp1252 console can't print "✓"
        try:
            _stream.reconfigure(errors="replace")
        except (AttributeError, ValueError):
            pass
    script_dir = app_dir()
    if (script_dir / "eqgame.exe").exists():
        os.chdir(script_dir)

    if "--send-logs" in sys.argv:
        try:
            rc = run_cli_send_logs()
        except Exception:
            report_startup_error("--send-logs failed")
            rc = 1
        sys.exit(rc)
    if "--cli" in sys.argv or "--launch" in sys.argv:
        run_cli_patcher()
    else:
        try:
            run_pyqt6_gui()
        except Exception:
            report_startup_error("The patcher window could not start")
            run_cli_patcher()
