Compare commits

...

8 commits

Author SHA1 Message Date
tomasalias
395947c58a chore: untrack and ignore GitHub workflows 2026-10-25 15:05:36 +01:00
tomasalias
d73474598a chore(docs): update all repository and download links to self-hosted Forgejo instance
Some checks failed
Build Windows Executable / Build BetterCurseForge.exe (push) Has been cancelled
Deploy Documentation / build (push) Has been cancelled
Deploy Documentation / deploy (push) Has been cancelled
2026-10-25 14:32:01 +01:00
tomasalias
fed62b18bc docs: rewrite documentation and README in clear, direct developer tone
Some checks are pending
Build Windows Executable / Build BetterCurseForge.exe (push) Waiting to run
Deploy Documentation / build (push) Waiting to run
Deploy Documentation / deploy (push) Blocked by required conditions
2026-10-25 14:03:15 +01:00
tomasalias
862ed0f7d6 chore(docs): configure root base path and add Forgejo nav link 2026-10-25 13:38:11 +01:00
tomasalias
c66cd9484a chore: ignore local icon updater tools
Some checks are pending
Build Windows Executable / Build BetterCurseForge.exe (push) Waiting to run
2026-10-25 12:56:19 +01:00
tomasalias
0d7e20af42 feat: update application icon to band-aid and add GitHub Actions build workflow 2026-10-24 23:05:16 +02:00
tomasalias
1581dcd48b fix(updater): prevent false positive triggers and unwanted app launches 2026-10-13 19:33:59 +02:00
tomasalias
d1a415eb86 docs: update releases and repository URLs to tomasalias/BetterCurseForge 2026-10-12 20:12:58 +02:00
15 changed files with 370 additions and 411 deletions

View file

@ -1,63 +0,0 @@
name: Deploy Documentation
on:
push:
branches:
- master
- main
paths:
- 'docs/**'
- '.github/workflows/deploy-docs.yml'
workflow_dispatch:
permissions:
contents: read
pages: write
id-token: write
concurrency:
group: pages
cancel-in-progress: false
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 20
cache: npm
cache-dependency-path: docs/package-lock.json
- name: Setup Pages
uses: actions/configure-pages@v4
- name: Install documentation dependencies
run: npm ci
working-directory: docs
- name: Build VitePress documentation
run: npm run build
working-directory: docs
- name: Upload Pages artifact
uses: actions/upload-pages-artifact@v3
with:
path: docs/.vitepress/dist
deploy:
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
needs: build
runs-on: ubuntu-latest
steps:
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4

10
.gitignore vendored
View file

@ -36,3 +36,13 @@ desktop.ini
.idea/
*.swp
*.swo
# Local utility tools
tools/
update_icon.bat
update_icon.py
docs_for_claude.json
# GitHub workflows
.github/

227
README.md
View file

@ -1,118 +1,73 @@
# BetterCurseForge
<div align="center">
<img src="assets/icon_512.png" alt="BetterCurseForge logo" width="96" height="96" />
<img src="assets/icon_512.png" alt="BetterCurseForge Logo" width="128" height="128" />
### The modern, lightweight client modification for CurseForge Launcher.
A patch for the CurseForge launcher on Windows. It blocks ads, adds a theme and plugin system, unlocks the subscriber-only skins, and re-applies itself after CurseForge updates.
[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
[![Platform](https://img.shields.io/badge/Platform-Windows-0078D6.svg?logo=windows)](https://github.com)
[![Zero Ads](https://img.shields.io/badge/Ads-Completely%20Blocked-success.svg)](#features)
[![Auto Repatch](https://img.shields.io/badge/Auto--Repatch-Autonomous-brightgreen.svg)](#-how-auto-repatch-works)
**Zero Ads • Custom Themes • Unlocked Skins • Extensible Plugins • Autonomous Auto-Repatch**
## What it does
</div>
- **Ad blocking.** Requests to the Overwolf, Adrta and DoubleClick ad servers are blocked at the Electron session level, and some CSS collapses whatever empty ad frames and banners are left. Ad elements are hidden, not removed from the DOM, because removing them breaks React and causes `insertBefore` errors.
- **Themes.** Themes show up in the launcher's own Settings > Themes page. CSS is injected live, so you don't need to restart to see changes. One theme is included, Purple Night.
- **Subscriber skins.** The official skins that normally need a CurseForge subscription (PixelForge, LifeForge, FantasyForge, Halloween and the rest) are unlocked.
- **Plugins.** Plugins are small JS modules with `onEnable` and `onDisable` hooks, scoped styling, and IPC to the main process. There's a sample one called Compact Mode.
- **Updates.** When CurseForge updates itself, the patch gets re-applied automatically. More on that [below](#how-the-repatch-works).
---
## Install
## ✨ Features
1. Download `BetterCurseForge.exe` from [Releases](http://92.5.113.44:3000/tomasalias/BetterCurseForge/releases).
2. Run it and click **Patch CurseForge**.
3. Start CurseForge as usual.
- 🚫 **Complete Zero-Ad Experience**: Multi-layer ad elimination without breaking React Fiber:
- Electron `session.webRequest` network request blocker stops Overwolf/Adrta/DoubleClick ad calls before they start.
- Fail-safe CSS rules completely neutralize, hide, and collapse all ad frames, banners, and swap buttons to 0px.
- Safe non-destructive DOM handling preserves React Fiber reconciliation, preventing `insertBefore` exceptions.
- 🎨 **First-Class Theme Engine**:
- Seamlessly embedded into CurseForge's native **Settings > Themes** screen.
- Live CSS injection with instant hot-reloading (no launcher restarts needed).
- Bundled with **Purple Night**—a custom dark theme featuring violet accents, sleek card elevations, and custom badge styling.
- 🔓 **Unlocked Native Premium Themes & Skins**:
- Unlocks all official CurseForge subscriber skins (PixelForge, LifeForge, FantasyForge, Halloween, etc.) without requiring a CurseForge subscription.
- 🧩 **Extensible Plugin System**:
- Modular plugin architecture with lifecycle hooks (`onEnable`, `onDisable`), safe IPC communication, and scoped styling.
- Includes sample plugins like **Compact Mode**.
- 🛡️ **Autonomous Auto-Repatch Engine**:
- **Survives in-app CurseForge updates automatically!**
- Offline, zero-delay ASAR extraction and repacking (< 3 seconds) using CurseForge's internal Node v24 runtime—no internet connection or external npm/npx required.
- Synchronizes Overwolf's binary `OWEASARSIG` integrity hashes directly inside `CurseForge.exe`.
- Background watcher daemon ensures your modifications are immediately re-applied when CurseForge updates.
---
## 🚀 Quick Start
### Easy Installation (GUI)
1. Download the latest `BetterCurseForge.exe` from [Releases](https://github.com).
2. Run `BetterCurseForge.exe`.
3. Click **Patch CurseForge**.
4. Launch CurseForge Launcher—your modded client is ready to use!
### Command-Line Interface (CLI)
`BetterCurseForge.exe` can also be run directly from PowerShell or Command Prompt:
It also works from PowerShell or Command Prompt:
```bash
# Check current patch and watcher status
# show patch and watcher status
.\BetterCurseForge.exe status
# Apply BetterCurseForge patch
# apply the patch
.\BetterCurseForge.exe patch
# Restore original vanilla CurseForge
# go back to stock CurseForge
.\BetterCurseForge.exe unpatch
# Run background watcher daemon directly
# run the background watcher in the foreground
.\BetterCurseForge.exe watch
# Trigger the smooth splash updater GUI manually
# open the splash updater by hand
.\BetterCurseForge.exe --repatch-splash
```
---
## How the repatch works
## 🔄 How Auto-Repatch Works
When CurseForge downloads an update, the Overwolf installer overwrites `resources/app.asar` with a stock copy and relaunches `CurseForge.exe`. That undoes the patch, and the unpatched launcher would normally appear on screen. BetterCurseForge deals with it like this:
Whenever CurseForge downloads an official update, the Overwolf installer replaces `resources/app.asar` with an unpatched vanilla archive and automatically restarts `CurseForge.exe`. BetterCurseForge guarantees a **smooth, zero-delay transition** where you never lose your patch and the unpatched app is never shown on screen:
A small watcher process sits in the background and watches CurseForge's install directory. When it sees an update or an unpatched `app.asar`, it polls for a new `CurseForge.exe` every 150 ms and kills the one the installer starts, so the vanilla launcher never gets drawn. Then it opens a splash window with a progress bar and runs the repatch.
```mermaid
flowchart TD
A[CurseForge Launcher Update] -->|Overwolf installer replaces files| B[Installer Attempts to Launch Vanilla App]
B -->|Active Interceptor <50ms| C[Vanilla Launcher Suppressed Silently]
C -->|Auto-Repatch Triggered| D[Dedicated Splash Updater GUI Appears]
D -->|Real-time Status & Progress| E[Offline Extraction & Patching <3s]
E -->|Synchronize OWEASARSIG| F[CurseForge.exe Integrity Signature Updated]
F -->|Autonomous Relaunch| G[Modded BetterCurseForge Launches Ready!]
The repatch unpacks and repacks `app.asar` using the Node runtime that ships inside CurseForge (`ELECTRON_RUN_AS_NODE=1`), so it needs no internet connection and no separate Node install. It takes a few seconds. It also updates the `OWEASARSIG` integrity signature stored in `CurseForge.exe` to match the new archive. When that's done, CurseForge relaunches.
Two named Windows mutexes, `BetterCurseForgePatchMutex` and `BetterCurseForgeSplashMutex`, stop two patch runs or two splash windows from happening at the same time.
## Themes and plugins
Everything user-facing lives in `%APPDATA%\CurseForge\BetterCurseForge\`:
```
themes/
purple-night.css
purple-night.json
plugins/
compact-mode/
manifest.json
index.js
```
1. **Active Vanilla Interceptor**: When an update or unpatched state is detected, BetterCurseForge polls in tight 150ms slices using lightweight Win32 kernel process enumeration. If the installer starts `CurseForge.exe`, it is terminated within 50ms before Chromium or Electron can render a single frame—guaranteeing the unpatched client never flashes on screen.
2. **Dedicated Splash Updater GUI**: Rather than patching silently without visual feedback, BetterCurseForge displays a sleek, centered dark-themed card (`#121214`) featuring the official application icon, an animated purple progress bar, and real-time status updates (*"Waiting for installer..."*, *"Extracting application archive..."*, *"Unlocking native premium themes..."*, *"Injecting zero-ad rules..."*, *"Updating security signatures..."*).
3. **Global Mutex Synchronization**: Uses Windows kernel named mutexes (`BetterCurseForgePatchMutex`, `BetterCurseForgeSplashMutex`) to guarantee atomic, single-process patching with zero race conditions or duplicate windows.
4. **Resilient Win32 Watcher Daemon**: A silent background process monitors CurseForge's installation directory with 0% CPU consumption during idle. It triggers the splash updater whenever an update is applied and handles all persistence automatically.
5. **100% Offline Zero-Delay ASAR Engine**: Unpacking and repacking use bundled tools combined with CurseForge's built-in Node runtime (`ELECTRON_RUN_AS_NODE=1`), completing the entire repatch cycle in 2–3 seconds without requiring an internet connection.
The GUI has **Open Themes Folder** and **Open Plugins Folder** buttons if you don't want to dig for it.
---
### Writing a theme
## 🎨 Themes & Plugins
User configurations, themes, and plugins are stored in your AppData directory:
```
%APPDATA%\CurseForge\BetterCurseForge\
├── themes/
│ ├── purple-night.css
│ └── purple-night.json
└── plugins/
└── compact-mode/
├── manifest.json
└── index.js
```
You can open these folders anytime via the **Open Themes Folder** and **Open Plugins Folder** buttons in the BetterCurseForge GUI.
### Creating a Custom Theme
Create a CSS file and manifest in `%APPDATA%\CurseForge\BetterCurseForge\themes\my-theme.css`:
A theme is a CSS file plus a JSON manifest with the same name. For example, `themes/my-theme.json`:
```json
{
@ -124,6 +79,8 @@ Create a CSS file and manifest in `%APPDATA%\CurseForge\BetterCurseForge\themes\
}
```
and `themes/my-theme.css`:
```css
:root {
--surface-brand-brand: #10b981 !important;
@ -131,32 +88,83 @@ Create a CSS file and manifest in `%APPDATA%\CurseForge\BetterCurseForge\themes\
}
```
---
### Writing a plugin
## 🛠️ Building from Source
A plugin lives in its own subfolder, `plugins/<plugin-name>/`, with a `manifest.json`, an `index.js`, and optionally a `styles.css`.
### Prerequisites
`plugins/my-plugin/manifest.json`:
- Windows 10 / 11
- Python 3.10+
- Node.js 20+ (for docs)
### Build Instructions
```powershell
# Clone the repository
git clone https://github.com/your-username/BetterCurseForge.git
cd BetterCurseForge
# Build standalone executable with PyInstaller
python -m pip install pyinstaller
python -m PyInstaller BetterCurseForge.spec --noconfirm
# Output executable will be in:
# dist/BetterCurseForge.exe
```json
{
"name": "My Plugin",
"description": "Custom tweak for CurseForge",
"version": "1.0.0",
"author": "Your Name",
"main": "index.js",
"styles": "styles.css"
}
```
### Running VitePress Documentation
`plugins/my-plugin/index.js`:
```javascript
// Plugins run inside the Electron renderer with access to BetterCurseForge
const { injectCSS, patcher, settings, on, off } = BetterCurseForge;
console.log("My plugin loaded");
// Cleanup when toggled off
plugin.onDisable = () => {
console.log("My plugin disabled");
};
```
#### What plugins can do
- **UI and DOM tweaks.** Add buttons, badges, links or panels with the normal DOM APIs. If the plugin folder has a `styles.css`, it's injected when the plugin is enabled and removed when it's disabled.
- **Method patching.** Hook into or replace internal JavaScript functions with `BetterCurseForge.patcher.before()`, `after()` or `instead()`.
- **Persistent settings.** Save and read preferences across restarts with `BetterCurseForge.settings.get(key)` and `set(key, value)`.
- **Events.** Listen for app lifecycle events with `BetterCurseForge.on(event, callback)`.
## Building from source
You need Windows 10 or 11 and Python 3.10+. Node.js 20+ is only needed if you want to run the docs site.
```powershell
git clone http://92.5.113.44:3000/tomasalias/BetterCurseForge.git
cd BetterCurseForge
python -m pip install pyinstaller
python -m PyInstaller BetterCurseForge.spec --noconfirm
```
The executable ends up in `dist/BetterCurseForge.exe`.
### Syncing changes
To pull changes:
```powershell
git pull origin master
```
If you have local changes that conflict:
```powershell
git stash
git pull origin master
git stash pop
```
To push your local changes:
```powershell
git add .
git commit -m "Description of changes"
git push origin master
```
To run the VitePress docs locally:
```powershell
cd docs
@ -164,9 +172,8 @@ npm install
npm run dev
```
---
## License
## 📄 License
[MIT](LICENSE).
This project is licensed under the [MIT License](LICENSE).
CurseForge and Overwolf are trademarks of their respective owners. BetterCurseForge is an independent open-source project and is not affiliated with or endorsed by Overwolf or CurseForge.
CurseForge and Overwolf are trademarks of their respective owners. This is an independent open-source project and isn't affiliated with or endorsed by either of them.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 126 KiB

After

Width:  |  Height:  |  Size: 108 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 184 KiB

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 KiB

After

Width:  |  Height:  |  Size: 18 KiB

View file

@ -3,13 +3,13 @@ import { defineConfig } from 'vitepress';
export default defineConfig({
title: 'BetterCurseForge',
description: 'Client mod for CurseForge Launcher',
base: process.env.BASE_URL || '/BetterCurseForge/',
base: process.env.BASE_URL || '/',
appearance: 'dark',
lastUpdated: true,
cleanUrls: true,
head: [
['link', { rel: 'icon', type: 'image/png', href: (process.env.BASE_URL || '/BetterCurseForge/') + 'logo.png' }],
['link', { rel: 'icon', type: 'image/png', href: (process.env.BASE_URL || '/') + 'logo.png' }],
['meta', { name: 'theme-color', content: '#9333ea' }],
['meta', { name: 'og:type', content: 'website' }],
['meta', { name: 'og:title', content: 'BetterCurseForge' }],
@ -23,7 +23,7 @@ export default defineConfig({
nav: [
{ text: 'Guide', link: '/guide/installation' },
{ text: 'API', link: '/api/' },
{ text: 'GitHub', link: 'https://github.com/BetterCurseForge/BetterCurseForge' },
{ text: 'Repository', link: 'http://92.5.113.44:3000/tomasalias/BetterCurseForge' },
],
sidebar: [
@ -57,9 +57,7 @@ export default defineConfig({
},
],
socialLinks: [
{ icon: 'github', link: 'https://github.com/BetterCurseForge/BetterCurseForge' },
],
footer: {
message: 'Released under the MIT License.',

View file

@ -1,12 +1,12 @@
# API Reference
BetterCurseForge exposes a global runtime object on `window.BetterCurseForge` within the CurseForge Electron renderer process. This API allows themes, plugins, and developer scripts to inject styles, monkey-patch methods, manage settings, and listen to application events.
BetterCurseForge adds a global object, `window.BetterCurseForge`, to CurseForge's Electron renderer process. Themes, plugins and your own scripts can use it to inject styles, patch methods, read and write settings, and listen for events.
---
## TypeScript Definition
## TypeScript definitions
For TypeScript development and autocompletion:
If you're using TypeScript, this gives you types and autocompletion:
```typescript
declare global {
@ -54,7 +54,7 @@ export interface BetterCurseForgeAPI {
### `BetterCurseForge.version`
- **Type**: `string`
- **Description**: The currently running BetterCurseForge runtime version.
- **Description**: Version of the BetterCurseForge runtime that's currently running.
```javascript
console.log(BetterCurseForge.version); // "1.0.0"
@ -64,11 +64,11 @@ console.log(BetterCurseForge.version); // "1.0.0"
### `BetterCurseForge.injectCSS(css)`
Dynamically injects CSS rules into the document `<head>`.
Adds CSS rules to the document `<head>`.
- **Parameters**:
- `css` *(string)*: Raw CSS string to inject.
- **Returns**: `() => void` — Function that removes the injected `<style>` tag when called.
- `css` *(string)*: The CSS to inject.
- **Returns**: `() => void`. Call it to remove the injected `<style>` tag.
```javascript
// Inject styles
@ -86,15 +86,15 @@ uninject();
## Settings API (`BetterCurseForge.settings`)
Manages persistent configuration stored in `%APPDATA%\CurseForge\BetterCurseForge\settings.json`.
Persistent configuration, stored in `%APPDATA%\CurseForge\BetterCurseForge\settings.json`.
### `settings.get(key?)`
Retrieves a setting by key, or returns the entire settings object if no key is specified.
Returns the setting for `key`, or the whole settings object if you don't pass a key.
- **Parameters**:
- `key` *(string, optional)*: Setting key name.
- **Returns**: `Promise<any>` — Value of the setting, or `null`.
- `key` *(string, optional)*: Name of the setting.
- **Returns**: `Promise<any>`. The value, or `null` if it isn't set.
```javascript
// Retrieve a specific value
@ -106,12 +106,12 @@ const allSettings = await BetterCurseForge.settings.get();
### `settings.set(key, value)`
Stores a setting value and writes it asynchronously to disk.
Saves a value and writes it to disk asynchronously.
- **Parameters**:
- `key` *(string)*: Setting key name.
- `value` *(any)*: JSON-serializable value.
- **Returns**: `Promise<boolean>` — `true` if saved successfully.
- `key` *(string)*: Name of the setting.
- `value` *(any)*: Any JSON-serializable value.
- **Returns**: `Promise<boolean>`. `true` if the save worked.
```javascript
await BetterCurseForge.settings.set('myPlugin.fontSize', 14);
@ -121,17 +121,17 @@ await BetterCurseForge.settings.set('myPlugin.fontSize', 14);
## Patcher API (`BetterCurseForge.patcher`)
Provides monkey-patching utilities to inspect, modify, or replace methods on JavaScript objects and modules safely.
Utilities for inspecting, modifying or replacing methods on JavaScript objects and modules. Every function returns an unpatch function.
### `patcher.before(obj, method, callback)`
Executes `callback(args)` **before** the target method is invoked.
Runs `callback(args)` **before** the target method is called.
- **Parameters**:
- `obj` *(object)*: Target object.
- `method` *(string)*: Method name on `obj`.
- `callback` *(Function)*: Function receiving arguments: `(args) => void`.
- **Returns**: `() => void` — Unpatch function that restores the original method.
- `callback` *(Function)*: `(args) => void`.
- **Returns**: `() => void`. Call it to restore the original method.
```javascript
const unpatch = BetterCurseForge.patcher.before(console, 'warn', (args) => {
@ -146,13 +146,13 @@ unpatch();
### `patcher.after(obj, method, callback)`
Executes `callback(args, result)` **after** the target method runs. You can return a new value to replace the method's return value.
Runs `callback(args, result)` **after** the target method. Return a value to replace the method's return value.
- **Parameters**:
- `obj` *(object)*: Target object.
- `method` *(string)*: Method name on `obj`.
- `callback` *(Function)*: `(args, result) => any`.
- **Returns**: `() => void` — Unpatch function.
- **Returns**: `() => void`. Unpatch function.
```javascript
const unpatch = BetterCurseForge.patcher.after(service, 'getTitle', (args, result) => {
@ -166,13 +166,13 @@ unpatch();
### `patcher.instead(obj, method, callback)`
Completely overrides the target method. Receives both the incoming arguments and a reference to the original method.
Replaces the target method. The callback gets the arguments and a reference to the original method.
- **Parameters**:
- `obj` *(object)*: Target object.
- `method` *(string)*: Method name on `obj`.
- `callback` *(Function)*: `(args, originalFn) => any`.
- **Returns**: `() => void` — Unpatch function.
- **Returns**: `() => void`. Unpatch function.
```javascript
const unpatch = BetterCurseForge.patcher.instead(target, 'openExternalUrl', (args, originalFn) => {
@ -188,12 +188,12 @@ unpatch();
---
## Event System (`BetterCurseForge.on / off / emit`)
## Event system (`BetterCurseForge.on / off / emit`)
BetterCurseForge includes an event bus for inter-plugin and lifecycle communication.
BetterCurseForge has an event bus that plugins can use to talk to each other and to react to lifecycle events.
### `BetterCurseForge.on(event, callback)`
Subscribes a listener to an event.
Adds a listener for an event.
```javascript
const onReady = () => console.log('BetterCurseForge is ready!');
@ -201,14 +201,14 @@ BetterCurseForge.on('ready', onReady);
```
### `BetterCurseForge.off(event, callback)`
Unsubscribes a listener.
Removes a listener.
```javascript
BetterCurseForge.off('ready', onReady);
```
### `BetterCurseForge.emit(event, ...args)`
Emits an event to all registered listeners.
Calls every listener registered for the event.
```javascript
BetterCurseForge.emit('custom-event', { detail: 123 });
@ -216,10 +216,10 @@ BetterCurseForge.emit('custom-event', { detail: 123 });
---
## Subsystem Managers
## Subsystem managers
### `BetterCurseForge.themes` (`ThemeManager`)
Provides programmatic control over custom themes:
Control custom themes from code:
```javascript
// Get all discovered themes
@ -242,7 +242,7 @@ BetterCurseForge.themes.removeTheme(true);
```
### `BetterCurseForge.plugins` (`PluginManager`)
Provides programmatic control over plugins:
Control plugins from code:
```javascript
// Get all installed plugins
@ -253,7 +253,7 @@ await BetterCurseForge.plugins.togglePlugin('compact-mode');
```
### `BetterCurseForge.ads` (`AdBlocker`)
Internal ad blocker runtime:
The internal ad blocker runtime:
```javascript
// Contains zero-ad CSS rules and ad container cleanup handlers

View file

@ -1,51 +1,51 @@
# Installation & Usage
This guide covers installing, managing, and uninstalling **BetterCurseForge** using either the desktop GUI application or the command-line interface.
How to install, check, and remove **BetterCurseForge**, using either the desktop app or the command line.
---
## Prerequisites
Before using BetterCurseForge, make sure you have:
You need:
- **Windows 10 or Windows 11** (64-bit)
- **CurseForge Launcher** installed (standalone or Overwolf edition)
- CurseForge closed before running the patcher
- CurseForge closed when you run the patcher
::: tip SAFE & 100% REVERSIBLE
Before modifying any files, BetterCurseForge creates untouched vanilla backups at `resources/app.asar.vanilla` and `CurseForge.exe.vanilla`. You can cleanly revert to stock CurseForge at any time with a single click.
::: tip BACKUPS
Before it changes anything, BetterCurseForge saves untouched copies of the original files at `resources/app.asar.vanilla` and `CurseForge.exe.vanilla`. You can go back to stock CurseForge whenever you want, with one click.
:::
---
## Method 1: Using the Desktop GUI (Recommended)
## Option 1: Desktop GUI (recommended)
BetterCurseForge comes as a standalone desktop executable with a sleek, dark-themed graphical interface.
BetterCurseForge is a single executable with a dark-themed GUI.
### Step 1: Download `BetterCurseForge.exe`
Download the latest executable from the official releases:
Grab the latest build from the releases page:
- [Download `BetterCurseForge.exe`](https://github.com/BetterCurseForge/BetterCurseForge/releases/latest)
- [Download `BetterCurseForge.exe`](http://92.5.113.44:3000/tomasalias/BetterCurseForge/releases)
Place it in any convenient folder (e.g., Downloads, Tools, or desktop).
Put it anywhere you like (Downloads, a tools folder, the desktop).
### Step 2: Open BetterCurseForge
Double-click `BetterCurseForge.exe` to launch the application.
Double-click `BetterCurseForge.exe` to open it.
![BetterCurseForge GUI](/logo.png){width=64 style="display:block;margin:1rem auto;"}
The application automatically:
- Detects your CurseForge installation folder from the Windows Registry.
- Checks whether your client is currently vanilla or patched.
- Displays the installed CurseForge version and installation directory.
On startup it:
- Finds your CurseForge install folder through the Windows Registry.
- Checks whether CurseForge is vanilla or patched.
- Shows the installed CurseForge version and install directory.
### Step 3: Click "Patch CurseForge"
1. Click the **Patch CurseForge** button.
2. The patcher will close any running CurseForge processes, create a vanilla backup, apply all enhancements (ad removal, theme engine, premium unlocks, and plugin loader), update the executable integrity signature, and repack the application.
3. You will see live output in the log panel confirming:
2. The patcher closes any running CurseForge processes, backs up the vanilla files, applies the patch (ad blocking, theme engine, skin unlocks, plugin loader), updates the integrity signature in the executable, and repacks the app.
3. The log panel shows progress as it goes:
```text
[+] Found CurseForge at: C:\Users\Username\AppData\Local\Programs\CurseForge Windows
[+] Unlocked native premium themes (2 route checks)
@ -60,17 +60,17 @@ The application automatically:
[+] Auto-repatch watcher active and persistent across updates.
[*] CurseForge successfully patched! Ads removed, themes & plugins enabled.
```
4. Click **Launch CurseForge** or open your usual desktop shortcut—you're done!
4. Click **Launch CurseForge** or start it from your usual shortcut. That's it.
---
## Method 2: Command-Line Interface (CLI)
## Option 2: Command line
For automated workflows, scripting, or headless environments, `BetterCurseForge.exe` provides a comprehensive CLI:
If you want to script it or run it without the GUI, `BetterCurseForge.exe` has a CLI.
### Patching via Terminal
### Patching
Open PowerShell or Command Prompt in the folder containing `BetterCurseForge.exe`:
Open PowerShell or Command Prompt in the folder with `BetterCurseForge.exe`:
::: code-group
@ -84,9 +84,9 @@ BetterCurseForge.exe patch
:::
### Checking Status
### Checking status
Verify whether your client is patched:
See whether CurseForge is patched:
```powershell
.\BetterCurseForge.exe status
@ -101,26 +101,26 @@ ASAR Hash: Verified
### Unpatching
Restore your vanilla CurseForge client:
Go back to stock CurseForge:
```powershell
.\BetterCurseForge.exe unpatch
```
This restores `resources/app.asar` from the vanilla backup and re-verifies original executable signatures.
This restores `resources/app.asar` from the vanilla backup and re-verifies the original executable signatures.
---
## Auto-Repatch: Surviving CurseForge Updates
## Auto-repatch after CurseForge updates
When the official CurseForge Launcher downloads an automatic update, it overwrites `app.asar` with an unpatched version and attempts to restart.
When CurseForge updates itself, it overwrites `app.asar` with a stock copy and restarts.
BetterCurseForge provides an autonomous, smooth transition with **zero screen flashing**:
BetterCurseForge handles this without the unpatched launcher ever showing up on screen.
### Managing Auto-Repatch in the GUI
- In the BetterCurseForge desktop application, simply toggle the **Auto-Repatch on Update** button (turns green when active).
### Managing it in the GUI
- Toggle the **Auto-Repatch on Update** button in the app. It turns green when it's on.
### Managing Auto-Repatch via CLI
### Managing it from the CLI
```powershell
# Run the background watcher daemon directly:
.\BetterCurseForge.exe watch
@ -133,18 +133,18 @@ BetterCurseForge provides an autonomous, smooth transition with **zero screen fl
.\BetterCurseForge.exe unpatch # Restores vanilla client and stops watcher
```
### How Auto-Repatch Works
1. **Background Watcher**: Runs silently with 0% CPU consumption via Windows directory event notifications and heartbeat checks.
2. **Active Vanilla Interceptor**: When an update or unpatched state is detected, the patcher polls every 150ms using Win32 kernel process enumeration. If the installer launches `CurseForge.exe`, it is killed within 50ms before any window or frame can appear.
3. **Dedicated Splash Updater GUI**: A modern, dark-themed `#121214` card appears on screen displaying the application icon, a purple `AUTO-UPDATE` badge, an animated progress bar, and real-time status messages while extracting, unlocking themes, removing ads, and repacking.
4. **Offline Zero-Delay Engine**: Offline ASAR extraction and repacking complete in 2–3 seconds using CurseForge's built-in Node v24 runtime (`ELECTRON_RUN_AS_NODE=1`), synchronizing the `OWEASARSIG` hash directly inside `CurseForge.exe`.
5. **Seamless Relaunch**: The patched BetterCurseForge client opens automatically, and the splash window gracefully closes.
### How it works
1. **Background watcher**: Runs in the background on Windows directory change notifications and periodic heartbeat checks, so CPU use is negligible.
2. **Killing the stock launcher**: Once it sees an update or an unpatched install, the watcher polls every 150 ms with Win32 process enumeration. If the installer starts `CurseForge.exe`, the watcher kills it within 50 ms, before a window can appear.
3. **Splash window**: A dark `#121214` card shows the app icon, a purple `AUTO-UPDATE` badge, a progress bar, and status messages while it extracts, unlocks themes, blocks ads and repacks.
4. **Offline repatch**: Extracting and repacking the ASAR takes 2–3 seconds and needs no internet. It uses the Node v24 runtime bundled with CurseForge (`ELECTRON_RUN_AS_NODE=1`) and writes the new `OWEASARSIG` hash into `CurseForge.exe`.
5. **Relaunch**: The patched CurseForge opens and the splash window closes.
---
## How It Works Under the Hood
## Under the hood
Modern versions of CurseForge include **ASAR Code Integrity Verification** enforced directly inside `CurseForge.exe`:
Recent CurseForge versions verify the integrity of `app.asar` from inside `CurseForge.exe`. This is the patch flow:
```mermaid
flowchart TD
@ -158,19 +158,19 @@ flowchart TD
H --> I["Launch Modded CurseForge with Full Permissions"]
```
1. **Multi-Layer Ad Elimination**: Blocks ad network requests at the Electron session level (`session.webRequest`) and collapses ad frames to 0px using CSS rules, completely preserving React Fiber virtual DOM reconciliation.
2. **Premium Skin Unlock**: Uses version-resilient AST/regex matching to unlock all official CurseForge skins (*Pixelforge*, *Lifeforge*, *Fantasyforge*, *Halloween*, etc.) in **Settings -> Themes**.
3. **ASAR Signature Synchronization**: Calculates the cryptographic SHA256 hash of the modified ASAR archive header and writes the new signature directly into `CurseForge.exe`, ensuring Electron boots without security errors or corruption alerts.
1. **Ad blocking**: Ad requests are blocked at the Electron session level (`session.webRequest`), and CSS collapses the ad frames to 0px. The elements stay in the DOM, so React Fiber's reconciliation isn't affected.
2. **Skin unlock**: AST and regex matching, written to tolerate version changes, unlocks the official CurseForge skins (*Pixelforge*, *Lifeforge*, *Fantasyforge*, *Halloween*, etc.) in **Settings -> Themes**.
3. **ASAR signature update**: It computes the SHA256 hash of the modified ASAR header and writes it into `CurseForge.exe`, so Electron starts without integrity errors.
---
## Troubleshooting & FAQ
## Troubleshooting
::: details How do I verify BetterCurseForge is running?
::: details How do I tell it's working?
1. Open CurseForge.
2. Notice the total absence of banner ads and video sponsor frames.
3. Navigate to **Settings -> Themes**: you will see the **Client Themes** section with *Purple Night* available, as well as unlocked *Premium* skins (*Pixelforge*, *Lifeforge*, etc.).
4. Press <kbd>Ctrl</kbd> + <kbd>Shift</kbd> + <kbd>I</kbd> to open DevTools. The console will display:
2. There should be no banner ads or video sponsor frames.
3. Go to **Settings -> Themes**. You should see a **Client Themes** section with *Purple Night* in it, and the *Premium* skins (*Pixelforge*, *Lifeforge*, etc.) unlocked.
4. Press <kbd>Ctrl</kbd> + <kbd>Shift</kbd> + <kbd>I</kbd> to open DevTools. The console should print:
```text
[BetterCF] Initializing renderer runtime...
[BetterCF] Theme manager ready (1 theme(s) found). Active: purple-night.css
@ -178,10 +178,10 @@ flowchart TD
```
:::
::: details Antivirus false-positive warning
Because `BetterCurseForge.exe` inspects running processes (`CurseForge.exe`), accesses the Windows Registry to locate install paths, and updates executable hashes, some heuristic scanners may flag it. BetterCurseForge is completely open source and safe. You can inspect the entire Python codebase in `patcher/` and build it yourself.
::: details Antivirus flags the exe
Some antivirus tools flag `BetterCurseForge.exe` as a false positive. It looks at running processes (`CurseForge.exe`), reads the Windows Registry to find the install path, and rewrites hashes in an executable, which is the kind of thing heuristic scanners react to. The project is open source: the Python code is in `patcher/`, and you can read it and build the exe yourself.
:::
::: details Permission denied / EPERM error
If your CurseForge launcher is installed under `C:\Program Files`, modifying files requires Administrator permissions. Right-click `BetterCurseForge.exe` and select **Run as Administrator**.
If CurseForge is installed under `C:\Program Files`, the patcher needs Administrator permissions to change files. Right-click `BetterCurseForge.exe` and choose **Run as Administrator**.
:::

View file

@ -1,18 +1,18 @@
# Writing Plugins
BetterCurseForge features a lightweight plugin system that allows you to extend CurseForge Launcher functionality, manipulate the DOM, hook runtime methods, and persist configuration.
Plugins are small JavaScript modules that run inside CurseForge. They can change the DOM, patch methods at runtime, and save settings between sessions.
---
## Plugin Directory Structure
## Plugin folder
Plugins are stored in the BetterCurseForge user directory:
Plugins live in the BetterCurseForge user directory:
```text
%APPDATA%\CurseForge\BetterCurseForge\plugins\
```
Each plugin lives in its own dedicated subfolder:
Each plugin gets its own subfolder:
```text
plugins/
@ -22,13 +22,13 @@ plugins/
└── styles.css # Optional: Companion stylesheet
```
You can open the plugins folder directly from the BetterCurseForge desktop GUI by clicking **Open Plugins Folder**.
The **Open Plugins Folder** button in the desktop app opens it.
---
## Plugin Manifest (`manifest.json`)
## Manifest (`manifest.json`)
The `manifest.json` file declares your plugin's metadata:
`manifest.json` describes the plugin:
```json
{
@ -42,22 +42,22 @@ The `manifest.json` file declares your plugin's metadata:
}
```
### Manifest Fields
### Fields
| Field | Type | Required | Description |
| :--- | :--- | :--- | :--- |
| `name` | `string` | **Yes** | Display name shown in the management UI. |
| `version` | `string` | **Yes** | Semantic version string (e.g. `1.0.0`). |
| `description` | `string` | No | Brief summary of what the plugin accomplishes. |
| `name` | `string` | **Yes** | Name shown in the management UI. |
| `version` | `string` | **Yes** | Semantic version (e.g. `1.0.0`). |
| `description` | `string` | No | Short summary of what the plugin does. |
| `author` | `string` | No | Author name or handle. |
| `main` | `string` | No | Entry JavaScript file. Defaults to `index.js`. |
| `styles` | `string` | No | Companion stylesheet injected automatically on enable. Defaults to `styles.css`. |
| `main` | `string` | No | Entry script. Defaults to `index.js`. |
| `styles` | `string` | No | Stylesheet that's injected automatically when the plugin is enabled. Defaults to `styles.css`. |
---
## Plugin Lifecycle & Execution
## Lifecycle
Plugin scripts are executed when enabled, receiving the `BetterCurseForge` global API and a `plugin` context object:
A plugin's script runs when the plugin is enabled. It gets the `BetterCurseForge` API and a `plugin` context object:
```javascript
// index.js
@ -73,16 +73,16 @@ plugin.onDisable = () => {
};
```
::: tip AUTOMATIC STYLE INJECTION & REMOVAL
If your plugin folder contains a `styles.css` (or file declared in `"styles"` in `manifest.json`), BetterCurseForge automatically injects it into `<head>` when your plugin is enabled and removes it when disabled! You do not need to manually inject or clean up companion stylesheets.
::: tip STYLESHEETS
If your plugin folder has a `styles.css` (or the file named in `"styles"` in `manifest.json`), BetterCurseForge injects it into `<head>` when the plugin is enabled and removes it when the plugin is disabled. You don't need to inject or clean it up yourself.
:::
---
## BetterCurseForge Plugin APIs
## Plugin API
### 1. `BetterCurseForge.injectCSS(css)`
Injects custom CSS dynamically into `<head>`. Returns a cleanup function that unmounts the style tag:
Injects CSS into `<head>`. Returns a function that removes the style tag:
```javascript
const removeStyles = BetterCurseForge.injectCSS(`
@ -98,7 +98,7 @@ plugin.onDisable = () => {
---
### 2. `BetterCurseForge.settings`
Stores and retrieves persistent settings saved to disk (`settings.json`):
Reads and writes settings that are saved to disk (`settings.json`):
```javascript
// Retrieve a stored setting (or null if not set)
@ -111,10 +111,10 @@ await BetterCurseForge.settings.set('compactMode.density', 'ultra');
---
### 3. `BetterCurseForge.patcher`
Safe method monkey-patching for objects and functions:
Helpers for monkey-patching methods on objects. Each call returns a function that undoes the patch:
#### `patcher.before(object, methodName, callback)`
Runs your callback before the target method executes. Receives `(args)`:
Runs your callback before the original method. The callback gets `(args)`:
```javascript
const unpatch = BetterCurseForge.patcher.before(console, 'error', (args) => {
@ -125,7 +125,7 @@ plugin.onDisable = () => unpatch();
```
#### `patcher.after(object, methodName, callback)`
Runs after the target method completes. Receives `(args, result)`. You can return a modified value:
Runs after the original method. The callback gets `(args, result)` and can return a new value to replace the result:
```javascript
const unpatch = BetterCurseForge.patcher.after(targetObj, 'getItemTitle', (args, result) => {
@ -136,7 +136,7 @@ plugin.onDisable = () => unpatch();
```
#### `patcher.instead(object, methodName, callback)`
Completely supersedes the target method. Receives `(args, originalMethod)`:
Replaces the original method. The callback gets `(args, originalMethod)`:
```javascript
const unpatch = BetterCurseForge.patcher.instead(targetObj, 'fetchData', (args, originalFn) => {
@ -150,7 +150,7 @@ plugin.onDisable = () => unpatch();
---
### 4. `BetterCurseForge.on(event, callback)` / `.off()` / `.emit()`
Application-wide event emitter:
An event emitter shared across the app:
```javascript
const onReady = () => console.log('CurseForge UI is fully loaded!');
@ -164,9 +164,9 @@ plugin.onDisable = () => {
---
## Complete Example: Compact Mode Plugin
## Example: Compact Mode
This plugin adds a high-density layout mode to CurseForge.
This plugin makes CurseForge's layout denser.
::: code-group

View file

@ -1,33 +1,33 @@
# Custom Themes & Theming Guide
# Themes
BetterCurseForge features a native, live-reloading theming engine. Themes allow you to completely restyle the CurseForge Launcher using standard CSS and theme manifests, fully integrated into the official user interface.
BetterCurseForge can load custom themes and reload them live. A theme is a CSS file, optionally with a JSON manifest, and it appears in CurseForge's own theme settings.
---
## In-App Theme Selector
## Picking a theme
BetterCurseForge embeds client themes directly into CurseForge's native **Settings -> Themes** menu.
Client themes are added to CurseForge's own **Settings -> Themes** page.
### How to Access Client Themes
1. Open CurseForge Launcher.
### Finding client themes
1. Open CurseForge.
2. Click the **Settings** gear icon in the bottom-left corner.
3. Select **Themes** from the settings sidebar.
4. Scroll down past the *Classic*, *Colorblind*, and *Premium* categories to find the **Client Themes** section:
3. Choose **Themes** in the settings sidebar.
4. Scroll past the *Classic*, *Colorblind* and *Premium* categories to reach **Client Themes**:
![Settings Themes View](/logo.svg){width=48 style="display:block;margin:1rem auto;"}
### Native Features in Settings -> Themes
- **Live Hover Preview**: Hovering your cursor over any theme card immediately previews the theme across the entire launcher in real time. Move your cursor away to revert, or click to apply.
- **Mutual Exclusivity**: Selecting a client theme seamlessly deselects native skins, and vice versa. Only one theme is active at a time.
- **"Open Folder" Button**: Directly opens your themes folder in Windows Explorer so you can drop in new stylesheets.
- **"Refresh" Button**: Reloads all installed custom themes from disk instantly without needing to restart CurseForge.
- **Native Premium Themes Unlocked**: All official CurseForge Premium themes (*Pixelforge*, *Lifeforge*, *Fantasyforge*, and *Spookyforge*) are permanently unlocked and free to use!
### What the theme page does
- **Hover preview**: Hover over a theme card to preview it across the whole launcher. Move the cursor away to revert, or click to apply it.
- **One theme at a time**: Picking a client theme deselects any native skin, and the other way around.
- **Open Folder button**: Opens your themes folder in Explorer so you can drop in new stylesheets.
- **Refresh button**: Reloads your custom themes from disk, no restart needed.
- **Premium themes unlocked**: The official Premium themes (*Pixelforge*, *Lifeforge*, *Fantasyforge* and *Spookyforge*) are unlocked and free to use.
---
## Theme Folder Location
## Theme folder
Themes are stored in your user application data directory:
Themes live in your user application data directory:
```text
%APPDATA%\CurseForge\BetterCurseForge\themes\
@ -38,16 +38,16 @@ Full path:
C:\Users\<YourUsername>\AppData\Roaming\CurseForge\BetterCurseForge\themes\
```
You can open this folder anytime by clicking the **Open Folder** button in **Settings -> Themes**.
The **Open Folder** button in **Settings -> Themes** opens it for you.
---
## Theme File Structure
## Theme layout
BetterCurseForge supports two convenient formats:
There are two ways to lay out a theme:
### 1. Single-File Theme (Recommended)
Place a `.css` file in the `themes/` directory, with an optional accompanying `.json` manifest:
### 1. Single file (recommended)
Put a `.css` file in `themes/`, with an optional `.json` manifest next to it:
```text
themes/
@ -55,8 +55,8 @@ themes/
└── purple-night.json (optional metadata & preview colors)
```
### 2. Folder-Based Theme
Create a folder inside `themes/` with a `theme.css` and `theme.json`:
### 2. Folder
Make a folder inside `themes/` containing `theme.css` and `theme.json`:
```text
themes/
@ -68,9 +68,9 @@ themes/
---
## Theme Manifest Format (`theme.json`)
## Manifest (`theme.json`)
A `.json` manifest provides metadata and defines the miniature preview card colors shown in the **Settings -> Themes** menu:
The manifest holds the theme's metadata and the colors of the small preview card shown in **Settings -> Themes**:
```json
{
@ -85,78 +85,78 @@ A `.json` manifest provides metadata and defines the miniature preview card colo
}
```
### Manifest Fields
### Fields
| Field | Type | Description |
| :--- | :--- | :--- |
| `name` | `string` | Display name shown on the theme card and in settings. |
| `version` | `string` | Semantic version string (e.g. `1.0.0`). |
| `description` | `string` | Short tooltip description displayed when hovering the title. |
| `name` | `string` | Name shown on the theme card and in settings. |
| `version` | `string` | Semantic version (e.g. `1.0.0`). |
| `description` | `string` | Short description, shown as a tooltip when you hover the title. |
| `author` | `string` | Author name or handle. |
| `primaryColor` | `string` | Hex color for the primary square icon in the thumbnail card. |
| `titleColor` | `string` | Hex color for the top bar in the thumbnail card. |
| `bodyColor` | `string` | Hex color for the lower text bar in the thumbnail card. |
| `backgroundColor`| `string` | Hex background color for the thumbnail card itself. |
| `primaryColor` | `string` | Hex color of the square icon on the preview card. |
| `titleColor` | `string` | Hex color of the top bar on the preview card. |
| `bodyColor` | `string` | Hex color of the lower text bar on the preview card. |
| `backgroundColor`| `string` | Hex background color of the preview card. |
---
## CurseForge CSS Variables Reference
## CSS variables
CurseForge is styled using CSS Custom Properties (variables). Overriding these variables allows you to theme 95% of the application cleanly without touching individual class names.
CurseForge is styled with CSS custom properties. Overriding them lets you theme most of the app without touching individual class names.
### Brand & Accents
### Brand & accents
| Variable | Description |
| :--- | :--- |
| `--surface-brand-brand` | Primary brand accent (active tabs, primary buttons, indicators). |
| `--surface-brand-brand` | Main brand accent: active tabs, primary buttons, indicators. |
| `--surface-brand-brand-hover` | Hover state for brand-colored elements. |
| `--color-primary` | Main accent color. |
| `--color-accent` | Secondary highlight color. |
| `--selected-item-border` | Border style for selected items and active inputs. |
### Surfaces & Backgrounds
### Surfaces & backgrounds
| Variable | Description |
| :--- | :--- |
| `--color-background` | Main application background (content views, modpack library). |
| `--color-background` | Main app background (content views, modpack library). |
| `--surface-base` | Darkest background layer (scroll tracks, container bases). |
| `--surface-primary` | Elevated primary surface (sidebars, sub-panels). |
| `--surface-secondary` | Secondary surface layer (navigation menus, dialog panels). |
| `--surface-tertiery` | Tertiary surface layer (mod cards, list row containers). |
| `--surface-on-surface` | Subtle translucent overlay for buttons (`rgba(...)`). |
| `--surface-primary` | Raised surface (sidebars, sub-panels). |
| `--surface-secondary` | Second surface layer (navigation menus, dialog panels). |
| `--surface-tertiery` | Third surface layer (mod cards, list row containers). |
| `--surface-on-surface` | Faint translucent overlay for buttons (`rgba(...)`). |
| `--surface-on-surface-hover` | Translucent hover overlay for buttons. |
### Modpack & Library Cards
### Modpack & library cards
| Variable | Description |
| :--- | :--- |
| `--surface-card` | Modpack / project card base background. |
| `--surface-card-hover` | Background color when hovering over modpack cards. |
| `--color-surface-card-active` | Active or clicked state for card items. |
| `--surface-card` | Base background of modpack and project cards. |
| `--surface-card-hover` | Background when hovering over a modpack card. |
| `--color-surface-card-active` | Active or clicked state of a card. |
### Typography & Text
### Typography & text
| Variable | Description |
| :--- | :--- |
| `--text-pure` | Pure white text (`#ffffff`). |
| `--text-primary` | Primary readable text color. |
| `--text-primary` | Main readable text color. |
| `--color-text-title` | Headings, modpack titles, section names. |
| `--color-text-body` | Standard body and description text. |
| `--color-text-body` | Normal body and description text. |
| `--color-text-secondary` | Subtitles, authors, version numbers. |
### Title Bar & Navigation
### Title bar & navigation
| Variable | Description |
| :--- | :--- |
| `--title-bar-background` | Background color or gradient for the top window title bar. |
| `--btn-window-action` | Minimize, maximize, and close button icons. |
| `--color-scrollbar-thumb` | Scrollbar thumb handle color. |
| `--title-bar-background` | Background color or gradient of the window title bar. |
| `--btn-window-action` | Icons on the minimize, maximize and close buttons. |
| `--color-scrollbar-thumb` | Scrollbar thumb color. |
---
## Example Theme: Emerald Night
## Example: Emerald Night
Here is a complete, working theme stylesheet you can save as `emerald-night.css`:
A full working theme. Save the stylesheet as `emerald-night.css` and the manifest as `emerald-night.json`:
::: code-group
@ -237,12 +237,12 @@ button[class*="primary"]:not([class*="secondary"]):hover {
---
## Tips for Inspecting Elements
## Inspecting elements
Because CurseForge runs on Electron and React, you can inspect any UI element using Chromium DevTools:
CurseForge is an Electron and React app, so you can inspect its UI with Chromium DevTools:
1. Press <kbd>Ctrl</kbd> + <kbd>Shift</kbd> + <kbd>I</kbd> in CurseForge to open DevTools.
2. Press <kbd>Ctrl</kbd> + <kbd>Shift</kbd> + <kbd>C</kbd> to activate the element inspector.
3. Click on any button, banner, header, or list item to reveal its computed styles and classes in the **Elements** panel.
4. Test CSS changes live in the **Styles** tab before adding them to your theme file.
5. In **Settings -> Themes**, click **Refresh** to instantly apply your modified theme stylesheet!
2. Press <kbd>Ctrl</kbd> + <kbd>Shift</kbd> + <kbd>C</kbd> to turn on the element inspector.
3. Click a button, banner, header or list item to see its computed styles and classes in the **Elements** panel.
4. Try CSS changes in the **Styles** tab before adding them to your theme.
5. In **Settings -> Themes**, click **Refresh** to load your updated stylesheet.

View file

@ -3,8 +3,8 @@ layout: home
hero:
name: "BetterCurseForge"
text: "Client mod for CurseForge Launcher"
tagline: "Ad-free experience, custom themes, unlocked premium skins, and plugins."
text: "A client mod for the CurseForge launcher"
tagline: "Ad blocking, custom themes, unlocked premium skins, and plugins."
image:
src: /logo.png
alt: BetterCurseForge
@ -13,35 +13,35 @@ hero:
text: Get Started
link: /guide/installation
- theme: alt
text: View on GitHub
link: https://github.com/BetterCurseForge/BetterCurseForge
text: View on Forgejo
link: http://92.5.113.44:3000/tomasalias/BetterCurseForge
features:
- icon: 🚫
title: Complete Ad Removal
details: Multi-layer elimination (zero-ad CSS rules, network-level request blocking, and layout collapsing) removes all video ads, banners, and empty containers without disrupting React reconciliation.
title: Ad blocking
details: Ad requests are blocked at the network level, and CSS rules collapse the video ads, banners and empty containers that are left. The elements stay in the DOM, so React keeps working.
- icon: 🎨
title: Client Themes & Free Skins
details: Integrated directly into Settings -> Themes with live hover previews, theme folder shortcuts, and all native CurseForge Premium themes unlocked for free.
title: Themes and free skins
details: Client themes show up in Settings -> Themes with live hover previews and a shortcut to the themes folder. The official premium skins are unlocked too.
- icon: 🔄
title: Auto-Repatch & Smooth Updates
details: Survives launcher updates effortlessly. The active interceptor suppresses unpatched windows, and a sleek splash GUI displays live progress while repatching in seconds.
title: Auto-repatch after updates
details: When the launcher updates, BetterCurseForge stops the unpatched copy from opening, shows a small splash window with progress, and repatches in a few seconds.
- icon: 🖥️
title: Modern GUI & CLI
details: Includes an intuitive dark-themed desktop application with one-click actions, as well as a full command-line interface for power users.
title: GUI and CLI
details: A dark-themed desktop app with one-click actions, plus a command-line interface for scripting.
- icon: 🧩
title: Plugin System
details: Extend launcher capabilities with lightweight JavaScript plugins using our runtime monkey-patcher and persistent settings API.
title: Plugins
details: Write small JavaScript plugins that patch methods at runtime and keep their settings between restarts.
- icon: 🛡️
title: Safe & 100% Reversible
details: Automatically creates clean vanilla backups (`app.asar.vanilla` and `CurseForge.exe.vanilla`). You can restore stock CurseForge at any time with a single click.
title: Backups and unpatching
details: Original files are backed up first (`app.asar.vanilla` and `CurseForge.exe.vanilla`), and you can restore stock CurseForge with one click.
---
<div class="quickstart-section" style="margin-top: 3rem; text-align: center;">
## Quick Start
Download `BetterCurseForge.exe` and patch in one click:
Download `BetterCurseForge.exe`, then open it for the GUI or patch from a terminal:
```bash
# Launch the desktop GUI:
@ -57,14 +57,15 @@ BetterCurseForge.exe patch
## Why BetterCurseForge?
CurseForge is the premier platform for Minecraft modpacks, World of Warcraft addons, and game mods, but the official desktop app is burdened with intrusive ad containers, locked premium themes, and heavy telemetry.
CurseForge hosts Minecraft modpacks, World of Warcraft addons and other game mods, but the desktop app comes with ad containers, premium-only themes and telemetry.
**BetterCurseForge** restores a clean, fast, and customizable experience:
BetterCurseForge changes that:
- **Seamless Native UI**: Client themes appear natively under **Settings -> Themes** alongside official themes, complete with real-time live hover previews.
- **Unlocked Premium Themes**: Access all native CurseForge Premium themes (*Pixelforge*, *Lifeforge*, *Fantasyforge*, *Spookyforge*) without a subscription.
- **Zero-Ad Layouts**: Ad columns and sidebars are eliminated from the layout, letting modpack grids expand to fill your entire display.
- **Automated Update Resistance**: Never worry about CurseForge updates breaking your setup—**Auto-Repatch** automatically detects updates and re-applies the patch in seconds.
- **One-Click Reversible**: Easily unpatch anytime via the GUI or `BetterCurseForge.exe unpatch` to restore vanilla CurseForge.
- **Native theme picker**: Client themes appear in **Settings -> Themes** next to the official ones, with live hover previews.
- **Premium themes unlocked**: The official premium themes (*Pixelforge*, *Lifeforge*, *Fantasyforge*, *Spookyforge*) work without a subscription.
- **No ad columns**: Ad columns and sidebars are collapsed, so modpack grids use the full width of the window.
- **Survives updates**: When CurseForge updates, BetterCurseForge detects it and reapplies the patch in a few seconds.
- **Easy to undo**: Unpatch from the GUI or with `BetterCurseForge.exe unpatch` to get vanilla CurseForge back.
</div>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 KiB

After

Width:  |  Height:  |  Size: 18 KiB

View file

@ -820,6 +820,10 @@ def is_curseforge_running() -> bool:
return False
def is_updater_running() -> bool:
"""
Checks if a CurseForge or Overwolf updater/installer process is currently running.
Strictly scoped to avoid false positives from unrelated apps (Discord, Logitech, etc.).
"""
if os.name == "nt":
try:
import ctypes
@ -847,9 +851,9 @@ def is_updater_running() -> bool:
if kernel32.Process32First(h_snap, ctypes.byref(pe)):
while True:
name = pe.szExeFile.decode('mbcs', errors='ignore').lower()
if name in ("setup.exe", "installer.exe", "update.exe") or \
("curseforge" in name and any(k in name for k in ("update", "setup", "install"))) or \
("overwolf" in name and any(k in name for k in ("update", "setup", "install"))):
# Strictly match CurseForge or Overwolf installer/updater processes
if ('curseforge' in name and any(k in name for k in ('update', 'setup', 'install'))) or \
('overwolf' in name and any(k in name for k in ('installer', 'updater', 'setup'))):
found = True
break
if not kernel32.Process32Next(h_snap, ctypes.byref(pe)):
@ -860,22 +864,6 @@ def is_updater_running() -> bool:
except Exception:
pass
try:
out = subprocess.check_output(
["tasklist", "/FO", "CSV", "/NH"],
text=True, stderr=subprocess.DEVNULL, creationflags=CREATE_NO_WINDOW
)
for line in out.splitlines():
line = line.strip().lower()
if not line:
continue
parts = [p.strip('"') for p in line.split('","')]
if parts:
pname = parts[0]
if pname in ("setup.exe", "installer.exe") or ("curseforge" in pname and "updater" in pname):
return True
except Exception:
pass
return False
def is_splash_running() -> bool:
@ -1224,16 +1212,22 @@ def run_watcher_loop():
if is_splash_running():
return
needs_patch = not is_curseforge_patched(cf_dir) or is_updater_running()
if needs_patch:
if now - last_splash_launch < 12.0:
# ONLY trigger if CurseForge is genuinely unpatched
if not is_curseforge_patched(cf_dir):
if now - last_splash_launch < 15.0:
return
last_splash_launch = now
wlog(f"CurseForge update or unpatched state detected ({trigger_reason}). Launching Splash Updater...")
wlog(f"CurseForge unpatched state detected ({trigger_reason}). Launching Splash Updater...")
ok = launch_splash_updater()
if not ok:
wlog("Could not launch Splash Updater; falling back to background patch.")
settled = wait_for_files_settled(cf_dir, timeout=90.0, log_fn=wlog, intercept_unpatched=True)
was_running = is_curseforge_running()
was_intercepted = [False]
def on_fb_log(msg: str):
if "silenc" in msg.lower():
was_intercepted[0] = True
wlog(msg)
settled = wait_for_files_settled(cf_dir, timeout=90.0, log_fn=on_fb_log, intercept_unpatched=True)
if settled:
kill_curseforge()
success = False
@ -1244,6 +1238,7 @@ def run_watcher_loop():
time.sleep(2)
if success:
wlog("★ CurseForge successfully re-patched after update!")
if was_running or was_intercepted[0]:
subprocess.Popen([str(cf_dir / "CurseForge.exe")], creationflags=CREATE_NO_WINDOW)
else:
wlog("Failed to re-patch CurseForge after update.")

View file

@ -642,6 +642,14 @@ class SplashUpdaterWindow:
time.sleep(3.0)
return
# Early guard: if no target_pid, CurseForge is already patched, and no updater is running, exit immediately
if not self.target_pid and patch_core.is_curseforge_patched(cf_dir) and not patch_core.is_updater_running():
self.root.after(0, self.root.destroy)
return
was_running = patch_core.is_curseforge_running() or (self.target_pid is not None)
was_intercepted = [False]
if self.target_pid:
self.root.after(0, self.set_status, "Waiting for previous instance to exit...", f"CurseForge PID {self.target_pid}")
for _ in range(30):
@ -658,6 +666,7 @@ class SplashUpdaterWindow:
elif "settle" in msg.lower() or "mb" in msg.lower():
self.root.after(0, self.set_status, "Finalizing update files...", msg)
elif "silenc" in msg.lower():
was_intercepted[0] = True
self.root.after(0, self.set_status, "Silenced vanilla launcher in background...", "Preventing unpatched window display...")
settled = patch_core.wait_for_files_settled(cf_dir, timeout=90.0, log_fn=on_settle_log, intercept_unpatched=True)
@ -696,11 +705,15 @@ class SplashUpdaterWindow:
time.sleep(2.0)
if success:
should_launch = was_running or was_intercepted[0] or (self.target_pid is not None)
if should_launch:
self.root.after(0, self.set_status, "✓ Complete! Launching BetterCurseForge...", "Enjoy your ad-free experience!", "success")
exe = cf_dir / "CurseForge.exe"
if exe.exists():
creation_flags = 0x00000008 | 0x00000200 if os.name == "nt" else 0 # DETACHED_PROCESS | CREATE_NEW_PROCESS_GROUP
subprocess.Popen([str(exe)], cwd=str(cf_dir), creationflags=creation_flags)
else:
self.root.after(0, self.set_status, "✓ Complete! BetterCurseForge is updated.", "CurseForge updated in background.", "success")
time.sleep(0.9)
else:
self.root.after(0, self.set_status, "Failed to apply patch.", "Check watcher.log for details.", "error")
@ -723,13 +736,11 @@ if __name__ == "__main__":
if arg in ("--watch", "watch"):
patch_core.run_watcher_loop()
sys.exit(0)
elif arg in ("--repatch-splash", "--splash", "splash"):
elif arg in ("--repatch-splash", "--splash", "splash", "--wait-for-update", "wait-for-update"):
pid = int(sys.argv[2]) if len(sys.argv) > 2 and sys.argv[2].isdigit() else None
splash = SplashUpdaterWindow(pid)
splash.run()
cf = patch_core.get_curseforge_dir()
if not pid and cf and patch_core.is_curseforge_patched(cf) and not patch_core.is_updater_running():
sys.exit(0)
elif arg in ("--wait-for-update", "wait-for-update"):
pid = int(sys.argv[2]) if len(sys.argv) > 2 and sys.argv[2].isdigit() else None
splash = SplashUpdaterWindow(pid)
splash.run()
sys.exit(0)