fix: resolve React DOM insertBefore reconciliation crash on launcher start
This commit is contained in:
parent
945302a3cb
commit
4e19e6f54e
2 changed files with 67 additions and 108 deletions
|
|
@ -28,7 +28,6 @@
|
||||||
// ═══════════════════════════════════════════════════════════════
|
// ═══════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
const AdBlocker = {
|
const AdBlocker = {
|
||||||
_observer: null,
|
|
||||||
_style: null,
|
_style: null,
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
|
|
@ -44,32 +43,26 @@
|
||||||
.subscribe-link,
|
.subscribe-link,
|
||||||
.subscribe-link-small-resolution,
|
.subscribe-link-small-resolution,
|
||||||
.subscribe-link-large-resolution,
|
.subscribe-link-large-resolution,
|
||||||
.promo-banner {
|
.promo-banner,
|
||||||
|
owadview,
|
||||||
|
[class*="ad-container"],
|
||||||
|
[class*="ad-banner"],
|
||||||
|
[class*="AdView"],
|
||||||
|
[class*="high-impact"],
|
||||||
|
[data-ad],
|
||||||
|
[data-ad-slot] {
|
||||||
display: none !important;
|
display: none !important;
|
||||||
width: 0 !important;
|
width: 0 !important;
|
||||||
height: 0 !important;
|
height: 0 !important;
|
||||||
min-width: 0 !important;
|
min-width: 0 !important;
|
||||||
max-width: 0 !important;
|
max-width: 0 !important;
|
||||||
|
margin: 0 !important;
|
||||||
|
padding: 0 !important;
|
||||||
overflow: hidden !important;
|
overflow: hidden !important;
|
||||||
pointer-events: none !important;
|
pointer-events: none !important;
|
||||||
position: absolute !important;
|
position: absolute !important;
|
||||||
visibility: hidden !important;
|
visibility: hidden !important;
|
||||||
}
|
opacity: 0 !important;
|
||||||
|
|
||||||
owadview {
|
|
||||||
display: none !important;
|
|
||||||
width: 0 !important;
|
|
||||||
height: 0 !important;
|
|
||||||
position: absolute !important;
|
|
||||||
visibility: hidden !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
[class*="ad-container"],
|
|
||||||
[class*="ad-banner"],
|
|
||||||
[class*="high-impact-ad"],
|
|
||||||
[data-ad],
|
|
||||||
[data-ad-slot] {
|
|
||||||
display: none !important;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[style*="grid-template"][style*="ads"] {
|
[style*="grid-template"][style*="ads"] {
|
||||||
|
|
@ -79,66 +72,13 @@
|
||||||
`;
|
`;
|
||||||
(document.head || document.documentElement).appendChild(this._style);
|
(document.head || document.documentElement).appendChild(this._style);
|
||||||
|
|
||||||
this._observer = new MutationObserver((mutations) => {
|
// Safe cleanup of ad text wrappers without removing DOM nodes (avoids breaking React Fiber)
|
||||||
for (const mutation of mutations) {
|
setTimeout(() => this._sweepAds(), 1500);
|
||||||
for (const node of mutation.addedNodes) {
|
setTimeout(() => this._sweepAds(), 4000);
|
||||||
if (node.nodeType !== Node.ELEMENT_NODE) continue;
|
|
||||||
this._removeAds(node);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
this._observer.observe(document.documentElement, {
|
|
||||||
childList: true,
|
|
||||||
subtree: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
setTimeout(() => this._sweepAds(), 1000);
|
|
||||||
setTimeout(() => this._sweepAds(), 3000);
|
|
||||||
setTimeout(() => this._sweepAds(), 6000);
|
|
||||||
},
|
|
||||||
|
|
||||||
_removeAds(node) {
|
|
||||||
if (node.tagName?.toLowerCase() === 'owadview') {
|
|
||||||
const parent = node.parentElement;
|
|
||||||
node.remove();
|
|
||||||
if (parent && parent.children.length === 0) {
|
|
||||||
parent.style.display = 'none';
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const classes = node.className?.toString() || '';
|
|
||||||
if (/\b(ad-container|ad-banner|ads-wrapper|AdView|high-impact|display-ad)\b/i.test(classes)) {
|
|
||||||
node.remove();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const adChildren = node.querySelectorAll?.('owadview, [class*="ad-container"], [class*="AdView"], [class*="high-impact"]');
|
|
||||||
if (adChildren) {
|
|
||||||
for (const child of adChildren) {
|
|
||||||
child.remove();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
_sweepAds() {
|
_sweepAds() {
|
||||||
const adSelectors = [
|
try {
|
||||||
'.curseforge-ad',
|
|
||||||
'.ad-inner',
|
|
||||||
'.overwolf-ad',
|
|
||||||
'.affiliate-banner',
|
|
||||||
'.subscribe-link',
|
|
||||||
'.subscribe-link-small-resolution',
|
|
||||||
'.subscribe-link-large-resolution',
|
|
||||||
'.promo-banner',
|
|
||||||
'owadview',
|
|
||||||
];
|
|
||||||
const els = document.querySelectorAll(adSelectors.join(', '));
|
|
||||||
for (const el of els) {
|
|
||||||
el.remove();
|
|
||||||
}
|
|
||||||
|
|
||||||
const walker = document.createTreeWalker(document.body || document.documentElement, NodeFilter.SHOW_TEXT, null);
|
const walker = document.createTreeWalker(document.body || document.documentElement, NodeFilter.SHOW_TEXT, null);
|
||||||
while (walker.nextNode()) {
|
while (walker.nextNode()) {
|
||||||
const text = walker.currentNode.textContent?.trim();
|
const text = walker.currentNode.textContent?.trim();
|
||||||
|
|
@ -153,6 +93,7 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} catch {}
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -515,6 +456,8 @@
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await renderCustomThemesSection(view);
|
await renderCustomThemesSection(view);
|
||||||
|
} catch (err) {
|
||||||
|
console.warn('[BetterCF] Themes section rendering failed:', err);
|
||||||
} finally {
|
} finally {
|
||||||
isInjecting = false;
|
isInjecting = false;
|
||||||
}
|
}
|
||||||
|
|
@ -592,8 +535,12 @@
|
||||||
lastRow.after(section);
|
lastRow.after(section);
|
||||||
} else {
|
} else {
|
||||||
const upsell = view.querySelector('.upsell') || view.querySelector('[class*="upsell"]');
|
const upsell = view.querySelector('.upsell') || view.querySelector('[class*="upsell"]');
|
||||||
if (upsell) {
|
if (upsell && upsell.parentNode) {
|
||||||
view.insertBefore(section, upsell);
|
try {
|
||||||
|
upsell.before(section);
|
||||||
|
} catch {
|
||||||
|
view.appendChild(section);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
view.appendChild(section);
|
view.appendChild(section);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ import hashlib
|
||||||
import winreg
|
import winreg
|
||||||
import time
|
import time
|
||||||
import subprocess
|
import subprocess
|
||||||
|
import re
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
CREATE_NO_WINDOW = 0x08000000 if os.name == "nt" else 0
|
CREATE_NO_WINDOW = 0x08000000 if os.name == "nt" else 0
|
||||||
|
|
@ -401,35 +402,46 @@ def patch_curseforge(log_callback=print) -> bool:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# 1. Zero-Ad & Themes Patch in desktop.js
|
# 1. Themes Patch in desktop.js (Ads are fully handled by desktop.css & network blocking)
|
||||||
desktop_js = temp_extract / "dist" / "desktop" / "desktop.js"
|
desktop_js = temp_extract / "dist" / "desktop" / "desktop.js"
|
||||||
if desktop_js.exists():
|
if desktop_js.exists():
|
||||||
content = desktop_js.read_text(encoding="utf-8")
|
content = desktop_js.read_text(encoding="utf-8")
|
||||||
# Clean up any legacy or duplicate __orig_eV replacements
|
# Restore clean eV component if previously patched (in modern CurseForge, eV is the modpack card author component, not ads)
|
||||||
|
if "/*BCF_AD_OFF*/eV=e=>{return null;let" in content:
|
||||||
|
content = content.replace("/*BCF_AD_OFF*/eV=e=>{return null;let", "eV=e=>{let")
|
||||||
|
log_callback("[+] Restored intact modpack card component (eV)")
|
||||||
while "let __orig_eV=e=>null;" in content:
|
while "let __orig_eV=e=>null;" in content:
|
||||||
content = content.replace("let __orig_eV=e=>null;", "")
|
content = content.replace("let __orig_eV=e=>null;", "")
|
||||||
if "eV=e=>null;let __orig_eV=e=>{" in content:
|
if "eV=e=>null;let __orig_eV=e=>{" in content:
|
||||||
content = content.replace("eV=e=>null;let __orig_eV=e=>{", "eV=e=>{")
|
content = content.replace("eV=e=>null;let __orig_eV=e=>{", "eV=e=>{")
|
||||||
|
|
||||||
# Idempotently disable React ad component (eV -> return null)
|
# Unlock all native premium themes (Settings & Popup routes) across versions
|
||||||
if "/*BCF_AD_OFF*/" not in content and "eV=e=>{let" in content:
|
content, c1 = re.subn(
|
||||||
content = content.replace("eV=e=>{let", "/*BCF_AD_OFF*/eV=e=>{return null;let", 1)
|
r'([a-zA-Z0-9_$]+)=e=>!!e\.forceUnlock\|\|!e\.isPremium\|\|[^,;]+',
|
||||||
log_callback("[+] Disabled React ad component (eV -> return null)")
|
r'\1=e=>!0',
|
||||||
if "showSwapAdsButton:T" in content:
|
content
|
||||||
content = content.replace("showSwapAdsButton:T", "showSwapAdsButton:!1")
|
)
|
||||||
log_callback("[+] Disabled swap ads button")
|
if c1 > 0:
|
||||||
if "R=e=>!!e.forceUnlock||!e.isPremium||!!M" in content:
|
log_callback(f"[+] Unlocked native premium themes ({c1} route checks)")
|
||||||
content = content.replace("R=e=>!!e.forceUnlock||!e.isPremium||!!M", "R=e=>!0")
|
|
||||||
log_callback("[+] Unlocked all native premium themes (Settings route)")
|
# Unlock premium theme state validation
|
||||||
if "P=e=>!!e.forceUnlock||!e.isPremium||f" in content:
|
content, c2 = re.subn(
|
||||||
content = content.replace("P=e=>!!e.forceUnlock||!e.isPremium||f", "P=e=>!0")
|
r'return!r\?\.forceUnlock&&r\?\.isPremium&&!t\?[a-zA-Z0-9_$.]+:n',
|
||||||
log_callback("[+] Unlocked all native premium themes (Popup route)")
|
r'return n',
|
||||||
if "return!r?.forceUnlock&&r?.isPremium&&!t?u.Sx.Classic:n" in content:
|
content
|
||||||
content = content.replace("return!r?.forceUnlock&&r?.isPremium&&!t?u.Sx.Classic:n", "return n")
|
)
|
||||||
|
if c2 > 0:
|
||||||
log_callback("[+] Unlocked premium theme state validation")
|
log_callback("[+] Unlocked premium theme state validation")
|
||||||
if "function iy(e){return e.subscription.plan?.active}" in content:
|
|
||||||
content = content.replace("function iy(e){return e.subscription.plan?.active}", "function iy(e){return !0}")
|
# Unlock subscription plan status for themes
|
||||||
log_callback("[+] Unlocked subscription plan status for themes")
|
content, c3 = re.subn(
|
||||||
|
r'function ([a-zA-Z0-9_$]+)\(e\)\{return e\.subscription\.plan\?\.active\}',
|
||||||
|
r'function \1(e){return !0}',
|
||||||
|
content
|
||||||
|
)
|
||||||
|
if c3 > 0:
|
||||||
|
log_callback(f"[+] Unlocked subscription plan status for themes ({c3} checks)")
|
||||||
|
|
||||||
desktop_js.write_text(content, encoding="utf-8")
|
desktop_js.write_text(content, encoding="utf-8")
|
||||||
|
|
||||||
# 2. Zero-Ad & Settings Themes CSS in desktop.css
|
# 2. Zero-Ad & Settings Themes CSS in desktop.css
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue