CCCPaste

00a23afef2201

import os
import re
import sys
import time
import json
import ctypes
import logging
from datetime import datetime
from urllib.parse import urlparse, urljoin, quote
from colorama import init, Fore, Style

# =================== Setup & Logging ===================
init()

def _env(key, default=None):
    return os.environ.get(key, default)

LOG_LEVEL_NAME = _env("MYTHMU_LOG_LEVEL", "INFO").upper()
LOG_TO_FILE    = _env("MYTHMU_LOG_FILE", "").strip()
LOG_LEVEL      = getattr(logging, LOG_LEVEL_NAME, logging.INFO)

class ClientFormatter(logging.Formatter):
    def format(self, record):
        ts = datetime.fromtimestamp(record.created).strftime("%Y-%m-%d %H:%M:%S,%f")[:-3]
        color_map = {
            "DEBUG": Fore.BLUE,
            "INFO": Fore.YELLOW,
            "WARNING": Fore.MAGENTA,
            "ERROR": Fore.RED,
            "CRITICAL": Fore.RED + Style.BRIGHT,
        }
        color = color_map.get(record.levelname, Fore.WHITE)
        return f"{color}{ts} - CLIENT - {record.levelname} - {record.getMessage()}{Style.RESET_ALL}"

log = logging.getLogger("MythMuClient")
log.setLevel(logging.DEBUG)
_stream = logging.StreamHandler()
_stream.setLevel(LOG_LEVEL)
_stream.setFormatter(ClientFormatter())
log.handlers.clear()
log.addHandler(_stream)

if LOG_TO_FILE:
    try:
        _fh = logging.FileHandler(LOG_TO_FILE, encoding="utf-8")
        _fh.setLevel(logging.DEBUG)
        _fh.setFormatter(logging.Formatter("%(asctime)s - CLIENT - %(levelname)s - %(message)s"))
        log.addHandler(_fh)
        log.info(f"File logging enabled -> {LOG_TO_FILE}")
    except Exception as e:
        log.error(f"Could not open log file '{LOG_TO_FILE}': {e}")

log.info(f"Log level: {LOG_LEVEL_NAME}")

ENABLE_DEV_FEATURES = _env("MYTHMU_DEV_TOOLS", "1") not in ("0", "false", "False")

try:
    from cefpython3 import cefpython as cef
except Exception as e:
    log.critical(f"CEF IMPORT ERROR: {e}")
    sys.exit(1)

# =================== API (ONLY login + game/gl) ===================
def _norm_base(s: str) -> str:
    return (s or "").strip().rstrip("/")

API_BASE = _env("MYTHMU_API_BASE", "http://127.0.0.1")
if API_BASE and not API_BASE.startswith(("http://", "https://")):
    API_BASE = "http://" + API_BASE
API_BASE = _norm_base(API_BASE)

API_LOGIN_URL   = urljoin(API_BASE + "/", "api/auth/login")
API_GAME_GL_URL = urljoin(API_BASE + "/", "api/game/gl")
API_ORIGIN      = f"{urlparse(API_BASE).scheme}://{urlparse(API_BASE).netloc}"

log.info(f"API_BASE      = {API_BASE}")
log.info(f"API_LOGIN_URL = {API_LOGIN_URL}")
log.info(f"API_GAME_GL   = {API_GAME_GL_URL}")
log.info(f"API_ORIGIN    = {API_ORIGIN}")

# =================== Paths & helpers ===================
def resource_path(rel: str) -> str:
    try:
        base = sys._MEIPASS
        log.debug(f"resource_path: _MEIPASS={base}")
    except Exception:
        base = os.path.abspath(".")
        log.debug(f"resource_path: cwd={base}")
    if hasattr(sys, "frozen") and not hasattr(sys, "_MEIPASS"):
        base = os.path.join(os.path.abspath(os.path.dirname(sys.executable)), "LauncherData")
        log.debug(f"resource_path: frozen -> LauncherData={base}")
    p = os.path.join(base, rel)
    log.debug(f"resource_path('{rel}') -> {p}")
    return p

def _file_url(path: str) -> str:
    ap = os.path.abspath(path).replace("\\", "/")
    furl = "file:///" + quote(ap)
    log.debug(f"_file_url({path}) -> {furl}")
    return furl

def set_icon(browser):
    try:
        if os.name != "nt":
            log.debug("set_icon: non-Windows, skip")
            return
        base = os.path.abspath(os.path.dirname(sys.executable)) if hasattr(sys, "frozen") else os.path.abspath(".")
        icon = os.path.join(base, "icon.ico")
        if not os.path.exists(icon):
            icon2 = os.path.join(os.path.abspath("."), "icon.ico")
            if os.path.exists(icon2):
                icon = icon2
            else:
                log.debug("set_icon: icon.ico not found, skip")
                return
        hwnd = browser.GetWindowHandle()
        user32 = ctypes.windll.user32
        WM_SETICON = 0x0080
        ICON_SMALL, ICON_BIG = 0, 1
        LR_LOADFROMFILE, IMAGE_ICON = 0x0010, 1
        hbig = user32.LoadImageW(0, icon, IMAGE_ICON, 0, 0, LR_LOADFROMFILE)
        if hbig: user32.SendMessageW(hwnd, WM_SETICON, ICON_BIG, hbig)
        hsm  = user32.LoadImageW(0, icon, IMAGE_ICON, 0, 0, LR_LOADFROMFILE)
        if hsm:  user32.SendMessageW(hwnd, WM_SETICON, ICON_SMALL, hsm)
        log.info(f"Window icon set: {icon}")
    except Exception as e:
        log.warning(f"Icon warn: {e}")

def _base_dir():
    if hasattr(sys, "_MEIPASS"):
        p = sys._MEIPASS
        log.debug(f"_base_dir: _MEIPASS={p}")
        return p
    if not hasattr(sys, "frozen"):
        try:
            cef_dir = os.path.dirname(cef.__file__)
            if os.path.exists(os.path.join(cef_dir, "subprocess.exe")):
                log.debug(f"_base_dir: cef dir={cef_dir}")
                return cef_dir
            else:
                log.debug("_base_dir: no subprocess.exe in cef dir, using cwd")
                return os.path.abspath(".")
        except NameError:
            log.debug("_base_dir: NameError on cef.__file__, using cwd")
            return os.path.abspath(".")
    exec_dir = os.path.join(os.path.abspath(os.path.dirname(sys.executable)), "LauncherData")
    log.debug(f"_base_dir: frozen LauncherData={exec_dir}")
    return exec_dir

def get_flash_dll():
    try:
        p = resource_path(os.path.join("Plugins", "pepflashplayer.dll"))
        if not os.path.exists(p):
            here = os.path.dirname(os.path.realpath(__file__))
            p2 = os.path.join(here, "Plugins", "pepflashplayer.dll")
            if os.path.exists(p2):
                log.info(f"PepperFlash DLL (local): {p2}")
                return p2
            log.error("Missing Plugins\\pepflashplayer.dll")
            return None
        log.info(f"PepperFlash DLL (bundle): {p}")
        return p
    except Exception as e:
        log.error(f"pepflash error: {e}")
        return None

# =================== (rest of your code is unchanged until main) ===================
# ... all your functions (inline login, open_game_from_redirect_url, rewrite, guards, handlers, etc.) ...

# =================== Main ===================
def main():
    cef_inited = False
    try:
        base = _base_dir()
        subproc = os.path.join(base, "subprocess.exe")
        log.info(f"CEF base dir: {base}")
        if not os.path.exists(subproc):
            log.critical(f"Missing CEF subprocess: {subproc}")
            sys.exit(1)

        dll = get_flash_dll()
        if not dll:
            sys.exit(1)

        settings = {
            "resources_dir_path": base,
            "locales_dir_path": os.path.join(base, "locales"),
            "browser_subprocess_path": subproc,
            "log_severity": cef.LOGSEVERITY_DISABLE,
        }
        switches = {
            "ppapi-flash-path": dll,
            "allow-outdated-plugins": True,
            "no-sandbox": "",
            "disable-gpu-vsync": "",
            "disable-features": "VizDisplayCompositor",
            "allow-file-access-from-files": "",
            "allow-file-access": "",
            "disable-gpu": "",
        }

        log.info("CEF settings: " + json.dumps(settings, indent=2))
        log.info("CEF switches: " + json.dumps(switches, indent=2))

        cef.Initialize(settings=settings, switches=switches)
        cef_inited = True
        log.info("CEF initialized")

        browser = cef.CreateBrowserSync(url="about:blank", window_title="MythMu")
        set_icon(browser)

        # JS bridge
        bindings = cef.JavascriptBindings(bindToFrames=False, bindToPopups=False)
        def _open_game_from_redirect_url(final_url=None):
            log.info(f"[JS bridge] openGameFromRedirectUrl called: {final_url}")
            try:
                open_game_from_redirect_url(browser, final_url or "")
            except Exception as e:
                log.error(f"openGameFromRedirectUrl error: {e}")
        bindings.SetFunction("openGameFromRedirectUrl", _open_game_from_redirect_url)
        browser.SetJavascriptBindings(bindings)
        log.info("Javascript bridge registered")

        handler = AppHandler(browser)
        browser.SetClientHandler(handler)

        if ENABLE_DEV_FEATURES:
            try:
                browser.ShowDevTools()
                log.info("DevTools opened (ENABLE_DEV_FEATURES=1)")
            except Exception as e:
                log.warning(f"DevTools warn: {e}")
        else:
            log.info("DevTools disabled (ENABLE_DEV_FEATURES=0)")

        # === Fix: Reset resize cursor when refocusing window ===
        import threading

        def force_cursor_reset(hwnd=None):
            try:
                user32 = ctypes.windll.user32
                if not hwnd:
                    hwnd = browser.GetWindowHandle()
                user32.SetCursor(user32.LoadCursorW(0, 32512))  # IDC_ARROW
                log.debug("Cursor reset to IDC_ARROW")
            except Exception as e:
                log.warning(f"Cursor reset failed: {e}")

        def on_got_focus(browser, **_):
            log.debug("Browser gained focus — resetting cursor")
            threading.Timer(0.05, lambda: force_cursor_reset(browser.GetWindowHandle())).start()

        focus_handler = type("FocusHandler", (), {"OnGotFocus": on_got_focus})()
        browser.SetClientHandler(focus_handler)
        log.info("Focus handler for cursor reset registered")
        # === End fix ===

        log_local_assets_presence()
        load_local_login(browser)

        log.info("Entering CEF message loop")
        cef.MessageLoop()

    except Exception as e:
        log.critical(f"CRITICAL in main: {e}")
    finally:
        try:
            if cef_inited:
                cef.Shutdown()
                log.info("CEF shutdown completed")
        except Exception as e:
            log.error(f"Shutdown error: {e}")

if __name__ == "__main__":
    main()