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 @@
-

+

### 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")