Hide CurseForge ads from the launcher

CurseForge (no ads)

Starts the CurseForge desktop app for Windows with the ad panel on the right hidden. No CurseForge files are changed.

Install

  1. On this page, click Raw in the top right corner of the CurseForge-NoAds.ps1 box below.
  2. Press Ctrl+A to select all the text, then Ctrl+C to copy it.
  3. Open Notepad and press Ctrl+V to paste.
  4. Click File → Save as and:
    • pick a folder where the file can stay, for example C:\Tools (shortcuts made by the script point to this location),
    • set Save as type to All files (*.*),
    • set File name to CurseForge-NoAds.ps1,
    • click Save.

Run

There are two ways to run the script. Pick one.

A. With the menu (easiest)

Right-click CurseForge-NoAds.ps1 and choose Run with PowerShell. A menu opens; type a number and press Enter.

Option
1 Start CurseForge without ads
2 Create Desktop shortcut
3 Create Start Menu shortcut (offers to delete the normal one)
4 Remove shortcuts
5 Set CurseForge location
6 Open log file
7 What does this script do?

Tip: create a shortcut with option 2 or 3, then always start CurseForge from that shortcut.

B. With parameters (no menu)

For use in your own scripts or shortcuts. Open PowerShell in the script's folder and run, for example:

powershell -NoProfile -ExecutionPolicy Bypass -File .\CurseForge-NoAds.ps1 -Launch
Parameter
-Launch Start CurseForge without ads
-CreateShortcut Create Desktop shortcut
-CreateStartMenuShortcut Create Start Menu shortcut
-RemoveShortcuts Remove shortcuts
-ExePath Where CurseForge is installed, if it is not found automatically
-LogPath Where to write the log

Notes

  • Ads are only hidden when CurseForge is started through the script, not when it starts with Windows.
  • The log (CurseForge-NoAds.log, next to the script) ends with Ad panel hidden. when it worked.
  • To undo: remove the shortcuts (option 4) and delete the script.
<#
.SYNOPSIS
Starts CurseForge with the right-side ad panel hidden.
.DESCRIPTION
No CurseForge files are modified. CurseForge is started with Electron's
main-process inspector bound to 127.0.0.1. The script connects once,
registers a CSS rule that hides the ad panel in every app window (it is
re-applied whenever a window reloads), and then closes the inspector port.
Because nothing on disk is changed, CurseForge updates do not undo this.
If a future update renames the panel's CSS class, the ads will return and
the selector below needs updating.
Run without parameters to get an interactive menu.
.PARAMETER ExePath
Path to CurseForge.exe, or to the folder that contains it. When omitted,
the script looks for it in this order: a running CurseForge process, the
installer's uninstall entry in the registry, Start Menu and Desktop
shortcuts, then common install folders. Given on its own, it starts
CurseForge (same as -Launch).
.PARAMETER Launch
Starts CurseForge without ads, without showing the menu.
.PARAMETER CreateShortcut
Creates a "CurseForge (no ads)" shortcut on the Desktop and exits.
.PARAMETER CreateStartMenuShortcut
Creates a "CurseForge (no ads)" shortcut in the Start Menu and exits.
.PARAMETER RemoveShortcuts
Removes the "CurseForge (no ads)" shortcuts from the Desktop and Start Menu and exits.
.PARAMETER LogPath
Where to write the log file. Defaults to CurseForge-NoAds.log next to
this script, or in %TEMP% if the script's folder cannot be written to.
.EXAMPLE
.\CurseForge-NoAds.ps1
Shows the menu.
.EXAMPLE
.\CurseForge-NoAds.ps1 -Launch -ExePath "D:\Apps\CurseForge Windows"
#>
param(
[string]$ExePath,
[switch]$Launch,
[switch]$CreateShortcut,
[switch]$CreateStartMenuShortcut,
[switch]$RemoveShortcuts,
[string]$LogPath
)
$ErrorActionPreference = 'Stop'
$port = 9334
# Returns $true if the file can be created or appended to.
function Test-Writable([string]$path) {
try {
$dir = Split-Path $path -Parent
if ($dir -and -not (Test-Path -LiteralPath $dir)) { [void](New-Item -ItemType Directory -Path $dir) }
[IO.File]::Open($path, 'Append', 'Write', 'ReadWrite').Dispose()
return $true
} catch { return $false }
}
$log = if ($LogPath) { [Environment]::ExpandEnvironmentVariables($LogPath.Trim('"')) } else { Join-Path $PSScriptRoot 'CurseForge-NoAds.log' }
if (-not (Test-Writable $log)) { $log = Join-Path $env:TEMP 'CurseForge-NoAds.log' }
$log = [IO.Path]::GetFullPath($log)
$shortcutName = 'CurseForge (no ads).lnk'
$shortcutDirs = [ordered]@{
'Desktop' = [Environment]::GetFolderPath('Desktop')
'Start Menu' = Join-Path ([Environment]::GetFolderPath('StartMenu')) 'Programs'
}
$script:Interactive = $false
# A failed log write must never stop CurseForge from starting.
function Add-LogLine($msg) {
try { "$(Get-Date -Format s) $msg" | Out-File -FilePath $log -Append -Encoding utf8 } catch { }
}
function Write-Log($msg) {
Add-LogLine $msg
if ($script:Interactive) { Write-Host " $msg" }
}
# In the menu, errors are shown and the menu continues. Otherwise the script
# usually runs from a shortcut with a hidden window, so errors go to a message box.
function Stop-WithError($msg) {
Add-LogLine $msg
if ($script:Interactive) { throw $msg }
Add-Type -AssemblyName System.Windows.Forms
[void][System.Windows.Forms.MessageBox]::Show($msg, 'CurseForge (no ads)', 'OK', 'Error')
exit 1
}
# Turns a path, a folder, a quoted path or an icon reference ("...exe,0") into
# a full path to CurseForge.exe, or $null if there is no such file.
function Resolve-CurseForgeExe([string]$path) {
if ([string]::IsNullOrWhiteSpace($path)) { return $null }
$path = [Environment]::ExpandEnvironmentVariables($path.Trim().Trim('"'))
$path = $path -replace ',\s*-?\d+ , ''
if (Test-Path -LiteralPath $path -PathType Container) { $path = Join-Path $path 'CurseForge.exe' }
if ((Split-Path $path -Leaf) -ne 'CurseForge.exe') { return $null }
if (Test-Path -LiteralPath $path -PathType Leaf) { return (Resolve-Path -LiteralPath $path).ProviderPath }
return $null
}
function Find-CurseForgeExe {
# 1. A running instance knows exactly where it lives.
foreach ($p in Get-Process -Name CurseForge -ErrorAction SilentlyContinue) {
$found = Resolve-CurseForgeExe $p.Path
if ($found) { return $found }
}
# 2. The installer's uninstall entry (InstallLocation is often empty, so check the other fields too).
$uninstallKeys = 'HKCU:\Software\Microsoft\Windows\CurrentVersion\Uninstall\*',
'HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\*',
'HKLM:\Software\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*'
$entries = Get-ItemProperty $uninstallKeys -ErrorAction SilentlyContinue | Where-Object { $_.DisplayName -like 'CurseForge*' }
foreach ($e in $entries) {
$uninstallDir = $null
if ($e.UninstallString -match '^\s*"([^"]+)"|^\s*(\S+)') { $uninstallDir = Split-Path ($Matches[1] + $Matches[2]) -Parent }
foreach ($candidate in $e.DisplayIcon, $e.InstallLocation, $uninstallDir) {
$found = Resolve-CurseForgeExe $candidate
if ($found) { return $found }
}
}
# 3. Start Menu and Desktop shortcuts.
$shell = New-Object -ComObject WScript.Shell
$linkDirs = "$env:APPDATA\Microsoft\Windows\Start Menu\Programs",
"$env:ProgramData\Microsoft\Windows\Start Menu\Programs",
[Environment]::GetFolderPath('Desktop'),
[Environment]::GetFolderPath('CommonDesktopDirectory')
$links = Get-ChildItem -LiteralPath ($linkDirs | Where-Object { $_ -and (Test-Path $_) }) -Recurse -Filter '*CurseForge*.lnk' -ErrorAction SilentlyContinue
foreach ($l in $links) {
$found = Resolve-CurseForgeExe $shell.CreateShortcut($l.FullName).TargetPath
if ($found) { return $found }
}
# 4. Common install folders.
$folders = "$env:LOCALAPPDATA\Programs\CurseForge Windows",
"$env:LOCALAPPDATA\Programs\CurseForge",
"$env:ProgramFiles\CurseForge Windows",
"$env:ProgramFiles\CurseForge",
"${env:ProgramFiles(x86)}\CurseForge Windows",
"${env:ProgramFiles(x86)}\CurseForge"
foreach ($f in $folders) {
$found = Resolve-CurseForgeExe $f
if ($found) { return $found }
}
return $null
}
# Returns the CurseForge.exe to use, or stops with an error.
function Get-TargetExe {
if ($ExePath) {
$exe = Resolve-CurseForgeExe $ExePath
if (-not $exe) { Stop-WithError "CurseForge.exe not found at '$ExePath'. Give the path to CurseForge.exe or the folder that contains it." }
} else {
$exe = Find-CurseForgeExe
if (-not $exe) { Stop-WithError "Could not find CurseForge.exe automatically. Use -ExePath `"`" (or menu option 5)." }
}
return $exe
}
function New-NoAdsShortcut([string]$exe, [string]$location) {
$lnk = Join-Path $shortcutDirs[$location] $shortcutName
$s = (New-Object -ComObject WScript.Shell).CreateShortcut($lnk)
$s.TargetPath = "$env:SystemRoot\System32\WindowsPowerShell\v1.0\powershell.exe"
$s.Arguments = "-NoProfile -ExecutionPolicy Bypass -WindowStyle Hidden -File `"$PSCommandPath`" -Launch -ExePath `"$exe`"" + $(if ($LogPath) { " -LogPath `"$log`"" })
$s.WorkingDirectory = Split-Path $exe -Parent
$s.IconLocation = "$exe,0"
$s.Description = 'Starts CurseForge with the ad panel hidden'
$s.Save()
Write-Log "Created $location shortcut: $lnk"
}
# Start Menu shortcuts that open CurseForge.exe directly (the installer's own
# shortcut). Ours start powershell.exe, so they are never matched.
function Find-OriginalStartMenuShortcuts([string]$exe) {
$shell = New-Object -ComObject WScript.Shell
$dirs = "$env:APPDATA\Microsoft\Windows\Start Menu\Programs",
"$env:ProgramData\Microsoft\Windows\Start Menu\Programs"
Get-ChildItem -LiteralPath ($dirs | Where-Object { Test-Path $_ }) -Recurse -Filter '*.lnk' -ErrorAction SilentlyContinue |
Where-Object { (Resolve-CurseForgeExe $shell.CreateShortcut($_.FullName).TargetPath) -ieq $exe } |
ForEach-Object { $_.FullName }
}
# Sends a file to the Recycle Bin, so the user can restore it.
function Remove-ToRecycleBin([string]$path) {
Add-Type -AssemblyName Microsoft.VisualBasic
[Microsoft.VisualBasic.FileIO.FileSystem]::DeleteFile($path, 'OnlyErrorDialogs', 'SendToRecycleBin')
}
function Confirm-RemoveOriginalStartMenuShortcuts([string]$exe) {
$originals = @(Find-OriginalStartMenuShortcuts $exe)
if ($originals.Count -eq 0) { return }
Write-Host ''
Write-Host ' The Start Menu also has the normal CurseForge shortcut, which opens CurseForge with ads:'
foreach ($o in $originals) { Write-Host " $o" }
Write-Host ''
Write-Host ' Deleting it means only "CurseForge (no ads)" shows up when you search the Start Menu.'
Write-Host ' It goes to the Recycle Bin, so you can restore it. A CurseForge update may add it back.'
Write-Host ''
if ((Read-Host ' Delete the normal CurseForge shortcut? [y/N]') -notmatch '^(y|yes) ) { Write-Host ' Kept it.'; return }
foreach ($o in $originals) {
try { Remove-ToRecycleBin $o; Write-Log "Moved original shortcut to the Recycle Bin: $o" }
catch { Write-Host " Could not delete $o ($($_.Exception.Message)). Shortcuts for all users need administrator rights." -ForegroundColor Yellow }
}
}
function Remove-NoAdsShortcuts {
$removed = 0
foreach ($location in $shortcutDirs.Keys) {
$lnk = Join-Path $shortcutDirs[$location] $shortcutName
if (Test-Path -LiteralPath $lnk) { Remove-Item -LiteralPath $lnk; Write-Log "Removed $location shortcut: $lnk"; $removed++ }
}
if ($removed -eq 0) { Write-Log 'No shortcuts to remove.' }
}
function Confirm-Restart {
$msg = 'CurseForge is already running. Restart it without ads? (Wait for any downloads or installs to finish first.)'
if ($script:Interactive) { return (Read-Host " $msg [y/N]") -match '^(y|yes) }
Add-Type -AssemblyName System.Windows.Forms
return [System.Windows.Forms.MessageBox]::Show($msg, 'CurseForge (no ads)', 'YesNo', 'Question') -eq 'Yes'
}
function Start-CurseForgeNoAds([string]$exe) {
Write-Log "Using $exe"
# CurseForge is single-instance: a second launch only focuses the running one,
# so an instance started without the inspector has to be restarted.
$running = Get-Process -Name CurseForge -ErrorAction SilentlyContinue | Where-Object { $_.Path -and ($_.Path -ieq $exe) }
if ($running) {
if (-not (Confirm-Restart)) { Write-Log 'Cancelled; CurseForge left running as it was.'; return }
$running | Stop-Process -Force
Start-Sleep -Seconds 2
}
Start-Process -FilePath $exe -ArgumentList "--inspect=127.0.0.1:$port"
$wsUrl = $null
for ($i = 0; $i -lt 60 -and -not $wsUrl; $i++) {
try { $wsUrl = (Invoke-RestMethod -Uri "http://127.0.0.1:$port/json/list" -TimeoutSec 2)[0].webSocketDebuggerUrl }
catch { Start-Sleep -Milliseconds 500 }
}
if (-not $wsUrl) { Write-Log 'Inspector endpoint did not come up; CurseForge is running with ads.'; return }
# Runs inside CurseForge's main process.
$js = @'
(() => {
// require() only exists while this evaluation runs, so load modules up front.
const { app, webContents } = require('electron');
const inspector = require('inspector');
const css = 'aside.curseforge-ad,.swap-ads-location{display:none!important}';
const hook = (w) => {
if (w.getType() !== 'window' || w.__cfNoAds) return;
w.__cfNoAds = true;
const apply = () => w.insertCSS(css).catch(() => {});
w.on('dom-ready', apply);
if (!w.isLoading()) apply();
};
webContents.getAllWebContents().forEach(hook);
app.on('web-contents-created', (_e, w) => hook(w));
setTimeout(() => inspector.close(), 500);
return 'ok';
})()
'@
$ct = [Threading.CancellationToken]::None
$ws = New-Object System.Net.WebSockets.ClientWebSocket
try {
[void]$ws.ConnectAsync([Uri]$wsUrl, $ct).GetAwaiter().GetResult()
$req = @{ id = 1; method = 'Runtime.evaluate'
params = @{ expression = $js; includeCommandLineAPI = $true; returnByValue = $true } } |
ConvertTo-Json -Compress -Depth 5
$bytes = [Text.Encoding]::UTF8.GetBytes($req)
[void]$ws.SendAsync((New-Object 'ArraySegment[byte]' (,$bytes)), 'Text', $true, $ct).GetAwaiter().GetResult()
$buf = New-Object byte[] 65536
$sb = New-Object Text.StringBuilder
do {
$r = $ws.ReceiveAsync((New-Object 'ArraySegment[byte]' (,$buf)), $ct).GetAwaiter().GetResult()
[void]$sb.Append([Text.Encoding]::UTF8.GetString($buf, 0, $r.Count))
} while (-not $r.EndOfMessage)
$reply = $sb.ToString()
if ($reply -match '"value":"ok"') { Write-Log 'Ad panel hidden.' } else { Write-Log "Unexpected reply: $reply" }
}
catch { Write-Log "Injection failed: $_" }
finally { $ws.Dispose() }
}
function Show-Help {
Write-Host @'
CurseForge shows ads in a large panel on the right side of its window.
This script opens CurseForge with that panel hidden, so your mods and
addons can use the whole window.
How it works
When you start CurseForge through this script (option 1 or one of the
shortcuts), the script opens CurseForge and tells it to hide the ad
panel. It is the same idea as an ad blocker in a web browser.
What it does NOT do
- It does not change, delete or replace any CurseForge files.
- It does not install anything or change Windows settings.
- It does not send anything over the internet or collect any data.
Things to know
- Ads are only hidden when CurseForge is started through this script.
If you open CurseForge the normal way, or it starts by itself when
Windows starts, the ads are shown as usual.
- If CurseForge is already open, the script asks to restart it. Let
any downloads or installs finish before you say yes.
- CurseForge updates normally keep working with this script. If the
ads come back after an update, the script needs to be updated.
- To get the ads hidden every time, create a shortcut (option 2 or 3)
and use that shortcut instead of the normal CurseForge one. Option 3
also offers to delete the normal Start Menu shortcut (it goes to the
Recycle Bin, so you can restore it).
How to undo it
Remove the shortcuts (option 4) and open CurseForge the normal way.
Nothing else needs to be undone, because nothing else was changed.
Technical detail, for those who want it
The script starts CurseForge with a debugging feature turned on that
only this computer can reach. It uses it once to hide the panel, then
turns it off again within about a second.
Ads help pay CurseForge and mod authors. CurseForge also sells a
subscription that removes the ads officially.
'@
}
# Clearing fails or makes no sense when output is redirected, so skip it then.
function Clear-Screen {
if ([Console]::IsOutputRedirected) { return }
try { Clear-Host } catch { }
}
function Show-Menu {
$script:Interactive = $true
$exe = if ($ExePath) { Resolve-CurseForgeExe $ExePath } else { Find-CurseForgeExe }
$options = [ordered]@{
'1' = 'Start CurseForge without ads'
'2' = 'Create Desktop shortcut'
'3' = 'Create Start Menu shortcut'
'4' = 'Remove shortcuts'
'5' = 'Set CurseForge location'
'6' = 'Open log file'
'7' = 'What does this script do?'
}
while ($true) {
Clear-Screen
Write-Host ''
Write-Host ' CurseForge (no ads)' -ForegroundColor Cyan
Write-Host ' ==================='
if ($exe) { Write-Host " CurseForge: $exe" } else { Write-Host ' CurseForge: not found - use option 5 to set the location' -ForegroundColor Yellow }
$state = foreach ($location in $shortcutDirs.Keys) {
$has = Test-Path -LiteralPath (Join-Path $shortcutDirs[$location] $shortcutName)
"$location $(if ($has) { 'yes' } else { 'no' })"
}
Write-Host " Shortcuts: $($state -join ', ')"
Write-Host " Log file: $log"
Write-Host ''
foreach ($key in $options.Keys) { Write-Host " $key $($options[$key])" }
Write-Host ' Q Quit'
Write-Host ''
$choice = (Read-Host ' Choose').Trim()
if ($choice -match '^[qQ] ) { return }
# Show the chosen option on its own screen.
Clear-Screen
$title = if ($options.Contains($choice)) { $options[$choice] } else { 'Unknown choice' }
Write-Host ''
Write-Host " CurseForge (no ads) - $title" -ForegroundColor Cyan
Write-Host " $('=' * "CurseForge (no ads) - $title".Length)"
Write-Host ''
try {
switch -Regex ($choice) {
'^[123] { if (-not $exe) { throw 'CurseForge.exe location is not set. Use option 5 first.' } }
'^1 { Start-CurseForgeNoAds $exe }
'^2 { New-NoAdsShortcut $exe 'Desktop' }
'^3 { New-NoAdsShortcut $exe 'Start Menu'; Confirm-RemoveOriginalStartMenuShortcuts $exe }
'^4 { Remove-NoAdsShortcuts }
'^5 {
$answer = Read-Host ' Path to CurseForge.exe or its folder (Enter = detect automatically)'
$new = if ([string]::IsNullOrWhiteSpace($answer)) { Find-CurseForgeExe } else { Resolve-CurseForgeExe $answer }
if ($new) {
$exe = $new
Write-Host " Using $exe"
Write-Host ' Existing shortcuts keep their old path; create them again to update them.'
} else {
Write-Host ' CurseForge.exe not found there; location unchanged.' -ForegroundColor Yellow
}
}
'^6 { if (Test-Path -LiteralPath $log) { Start-Process notepad.exe -ArgumentList "`"$log`"" } else { Write-Host ' No log file yet.' } }
'^7 { Show-Help }
default { Write-Host " '$choice' is not an option. Type a number from the menu, or Q to quit." -ForegroundColor Yellow }
}
}
catch { Write-Host " $($_.Exception.Message)" -ForegroundColor Red }
# Wait, so the result can be read before the screen is cleared.
Write-Host ''
[void](Read-Host ' Press Enter to return to the menu')
}
}
$anyAction = $Launch -or $CreateShortcut -or $CreateStartMenuShortcut -or $RemoveShortcuts
if (-not $anyAction -and -not $ExePath) { Show-Menu; return }
if ($RemoveShortcuts) { Remove-NoAdsShortcuts }
if ($CreateShortcut -or $CreateStartMenuShortcut -or $Launch -or -not $anyAction) {
$exe = Get-TargetExe
if ($CreateShortcut) { New-NoAdsShortcut $exe 'Desktop' }
if ($CreateStartMenuShortcut) { New-NoAdsShortcut $exe 'Start Menu' }
if ($Launch -or -not $anyAction) { Start-CurseForgeNoAds $exe }
}
添加评论
点赞收藏
点踩分享查看原文
评论
?
参与讨论