diff --git a/BetterCurseForge.spec b/BetterCurseForge.spec index fde9750..7a610a6 100644 --- a/BetterCurseForge.spec +++ b/BetterCurseForge.spec @@ -5,8 +5,8 @@ a = Analysis( ['patcher\\patcher_gui.py'], pathex=[], binaries=[], - datas=[('bettercf', 'bettercf'), ('patcher\\vendor', 'vendor'), ('patcher\\patch_core.py', '.')], - hiddenimports=[], + datas=[('bettercf', 'bettercf'), ('patcher\\vendor', 'vendor'), ('patcher\\patch_core.py', '.'), ('assets', 'assets')], + hiddenimports=['PIL', 'PIL.Image', 'PIL.ImageTk'], hookspath=[], hooksconfig={}, runtime_hooks=[], @@ -35,4 +35,5 @@ exe = EXE( target_arch=None, codesign_identity=None, entitlements_file=None, + icon='assets\\icon.ico', ) diff --git a/README.md b/README.md index 3613882..8d2e64a 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@
-BetterCurseForge Logo +BetterCurseForge Logo ### The modern, lightweight client modification for CurseForge Launcher. diff --git a/assets/icon.ico b/assets/icon.ico new file mode 100644 index 0000000..05bfb38 Binary files /dev/null and b/assets/icon.ico differ diff --git a/assets/icon.png b/assets/icon.png new file mode 100644 index 0000000..33f9fbd Binary files /dev/null and b/assets/icon.png differ diff --git a/assets/icon_512.png b/assets/icon_512.png new file mode 100644 index 0000000..472018c Binary files /dev/null and b/assets/icon_512.png differ diff --git a/docs/.vitepress/config.ts b/docs/.vitepress/config.ts index 205530a..bcbfb28 100644 --- a/docs/.vitepress/config.ts +++ b/docs/.vitepress/config.ts @@ -9,7 +9,7 @@ export default defineConfig({ cleanUrls: true, head: [ - ['link', { rel: 'icon', type: 'image/svg+xml', href: '/logo.svg' }], + ['link', { rel: 'icon', type: 'image/png', href: (process.env.BASE_URL || '/BetterCurseForge/') + 'logo.png' }], ['meta', { name: 'theme-color', content: '#9333ea' }], ['meta', { name: 'og:type', content: 'website' }], ['meta', { name: 'og:title', content: 'BetterCurseForge' }], @@ -18,6 +18,7 @@ export default defineConfig({ themeConfig: { siteTitle: 'BetterCurseForge', + logo: '/logo.png', nav: [ { text: 'Guide', link: '/guide/installation' }, diff --git a/docs/index.md b/docs/index.md index a16c790..b9d0359 100644 --- a/docs/index.md +++ b/docs/index.md @@ -5,6 +5,9 @@ hero: name: "BetterCurseForge" text: "Client mod for CurseForge Launcher" tagline: "Ad-free experience, custom themes, unlocked premium skins, and plugins." + image: + src: /logo.png + alt: BetterCurseForge actions: - theme: brand text: Get Started diff --git a/docs/public/logo.png b/docs/public/logo.png new file mode 100644 index 0000000..472018c Binary files /dev/null and b/docs/public/logo.png differ diff --git a/patcher/patch_core.py b/patcher/patch_core.py index 88c76f2..8188cb1 100644 --- a/patcher/patch_core.py +++ b/patcher/patch_core.py @@ -26,6 +26,7 @@ PERMANENT_DIR = Path(os.environ.get("LOCALAPPDATA", "")) / "BetterCurseForge" PERMANENT_EXE = PERMANENT_DIR / "BetterCurseForge.exe" PERMANENT_BCF = PERMANENT_DIR / "bettercf" PERMANENT_VENDOR = PERMANENT_DIR / "vendor" +PERMANENT_ASSETS = PERMANENT_DIR / "assets" class GlobalNamedMutex: def __init__(self, name: str, timeout_ms: int = 5000): @@ -247,6 +248,20 @@ def find_vendor_dir() -> Path | None: return c return None +def find_assets_dir() -> Path | None: + candidates = [ + Path(getattr(sys, "_MEIPASS", "")) / "assets" if hasattr(sys, "_MEIPASS") else None, + Path(sys.executable).parent / "assets" if getattr(sys, "frozen", False) else None, + PERMANENT_ASSETS, + Path(__file__).parent / "assets", + Path(__file__).parent.parent / "assets", + Path.cwd() / "assets", + ] + for c in candidates: + if c and c.exists() and c.is_dir(): + return c + return None + def run_asar_tool(args: list[str], cf_dir: Path | None = None) -> tuple[bool, str]: """ Executes asar pack/extract offline using bundled @electron/asar with either @@ -775,6 +790,13 @@ def ensure_permanent_installation() -> Path: shutil.rmtree(PERMANENT_VENDOR, ignore_errors=True) shutil.copytree(src_vendor, PERMANENT_VENDOR) + src_assets = find_assets_dir() + if src_assets and src_assets.exists(): + if PERMANENT_ASSETS.resolve() != src_assets.resolve(): + if PERMANENT_ASSETS.exists(): + shutil.rmtree(PERMANENT_ASSETS, ignore_errors=True) + shutil.copytree(src_assets, PERMANENT_ASSETS) + if getattr(sys, "frozen", False): curr_exe = Path(sys.executable).resolve() if curr_exe != PERMANENT_EXE.resolve(): diff --git a/patcher/patcher_gui.py b/patcher/patcher_gui.py index 9599c63..8ce1daf 100644 --- a/patcher/patcher_gui.py +++ b/patcher/patcher_gui.py @@ -25,6 +25,12 @@ from tkinter import ttk, messagebox, scrolledtext from pathlib import Path from datetime import datetime +try: + from PIL import Image, ImageTk + HAS_PIL = True +except ImportError: + HAS_PIL = False + # Import patch core logic try: from . import patch_core @@ -33,6 +39,20 @@ except ImportError: VERSION = "1.0.0" +def find_asset(filename: str) -> Path | None: + candidates = [ + Path(getattr(sys, "_MEIPASS", "")) / "assets" / filename if hasattr(sys, "_MEIPASS") else None, + Path(getattr(sys, "_MEIPASS", "")) / filename if hasattr(sys, "_MEIPASS") else None, + Path(sys.executable).parent / "assets" / filename if getattr(sys, "frozen", False) else None, + getattr(patch_core, "PERMANENT_ASSETS", Path()) / filename, + Path(__file__).parent.parent / "assets" / filename, + Path.cwd() / "assets" / filename, + ] + for c in candidates: + if c and c.exists(): + return c + return None + # ═══════════════════════════════════════════════════════════════════════════════ # COLOR PALETTE (Modern Discord / Vencord Dark Theme) # ═══════════════════════════════════════════════════════════════════════════════ @@ -71,11 +91,34 @@ class BetterCFApp: self.install_dir = None self.is_working = False + self._icon_photo = None + self._header_icon_photo = None self._configure_styles() + self._setup_window_icon() self._build_ui() self._refresh_status() + def _setup_window_icon(self): + ico_path = find_asset("icon.ico") + if ico_path and os.name == "nt": + try: + self.root.iconbitmap(default=str(ico_path.resolve())) + except Exception: + pass + png_path = find_asset("icon_512.png") or find_asset("icon.png") + if png_path: + try: + if HAS_PIL: + im = Image.open(png_path) + self._icon_photo = ImageTk.PhotoImage(im.resize((64, 64), Image.Resampling.LANCZOS)) + self.root.iconphoto(True, self._icon_photo) + else: + self._icon_photo = tk.PhotoImage(file=str(png_path)) + self.root.iconphoto(True, self._icon_photo) + except Exception: + pass + def _configure_styles(self): style = ttk.Style() style.theme_use("clam") @@ -97,8 +140,19 @@ class BetterCFApp: title_frame = tk.Frame(header, bg=COLORS["bg_card"]) title_frame.pack(side="left") + # Header icon thumbnail + png_path = find_asset("icon_512.png") or find_asset("icon.png") + if png_path and HAS_PIL: + try: + header_img = Image.open(png_path).resize((32, 32), Image.Resampling.LANCZOS) + self._header_icon_photo = ImageTk.PhotoImage(header_img) + icon_lbl = tk.Label(title_frame, image=self._header_icon_photo, bg=COLORS["bg_card"]) + icon_lbl.pack(side="left", padx=(0, 10)) + except Exception: + pass + title_lbl = tk.Label( - title_frame, text="⬡ BetterCurseForge", font=("Segoe UI", 16, "bold"), + title_frame, text="BetterCurseForge", font=("Segoe UI", 16, "bold"), bg=COLORS["bg_card"], fg=COLORS["purple_glow"] ) title_lbl.pack(side="left")