diff --git a/patcher/patch_core.py b/patcher/patch_core.py index 32db968..d6c03bb 100644 --- a/patcher/patch_core.py +++ b/patcher/patch_core.py @@ -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,7 +1238,8 @@ def run_watcher_loop(): time.sleep(2) if success: wlog("★ CurseForge successfully re-patched after update!") - subprocess.Popen([str(cf_dir / "CurseForge.exe")], creationflags=CREATE_NO_WINDOW) + 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.") else: diff --git a/patcher/patcher_gui.py b/patcher/patcher_gui.py index 9332925..f24ba90 100644 --- a/patcher/patcher_gui.py +++ b/patcher/patcher_gui.py @@ -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: - 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) + 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"): - 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) - elif arg in ("--wait-for-update", "wait-for-update"): + 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 + 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) splash = SplashUpdaterWindow(pid) splash.run() sys.exit(0)