diff --git a/main.js b/main.js index 44345df6..1fb1cf73 100644 --- a/main.js +++ b/main.js @@ -3797,7 +3797,11 @@ const ARIADNE_UNINSTALL_KEYS = [ 'HKLM:\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\{7E7A5F1C-3B4E-4C8A-9E1D-ARIADNERSLVR}_is1', 'HKLM:\\SOFTWARE\\WOW6432Node\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\{7E7A5F1C-3B4E-4C8A-9E1D-ARIADNERSLVR}_is1', ]; -const ARIADNE_MANIFEST_URL = "https://silentmode.st/releases-manifest.json"; +// Same manifest the Theseus updater reads (UPDATE_MANIFEST_URL). The copy on +// silentmode.st is a mirror that has lagged behind dl.silentmode.st (2026-09-09: +// it still listed Theseus 0.3.31 while dl had 0.3.44), so the Ariadne "Update" +// button was checking against a stale version list. +const ARIADNE_MANIFEST_URL = "https://dl.silentmode.st/releases-manifest.json"; const ARIADNE_DL_ORIGIN = "https://dl.silentmode.st"; const ARIADNE_MANIFEST_TTL_MS = 30 * 60 * 1000; let ariadneManifestCache = null; // { at:number, entry:{version,filename,sha256,url}|null } @@ -3920,11 +3924,41 @@ function cmpVersions(a, b) { function ariadneSetState(on) { return new Promise((resolve, reject) => { const { spawn } = require("child_process"); - const verb = on ? "Start-ScheduledTask" : "Stop-ScheduledTask"; - const cmd = `foreach ($t in 'BNS Resolver Daemon','BNS Sia Bridge') { try { ${verb} -TaskName $t -ErrorAction Stop } catch {} }`; + // Off = stop AND disable, on = enable AND start. The daemon task has an + // at-startup trigger, so a plain Stop-ScheduledTask came back on the + // next reboot and "Turn off" silently didn't stick. + // + // The inner script goes across as -EncodedCommand (base64 UTF-16LE). The + // previous version embedded it in a double-quoted string on the OUTER + // powershell's command line, which interpolated `$t` to nothing before + // the elevated shell ever saw it — the elevated shell got + // `foreach ( in ...)`, failed to parse, and the outer shell still exited + // 0, so the toggle reported success while doing nothing. + const inner = on + ? `$ok = $true +foreach ($t in 'BNS Resolver Daemon','BNS Sia Bridge') { + try { Enable-ScheduledTask -TaskName $t -ErrorAction Stop | Out-Null; Start-ScheduledTask -TaskName $t -ErrorAction Stop } + catch { if ($t -eq 'BNS Resolver Daemon') { $ok = $false } } +} +if ($ok) { exit 0 } else { exit 2 }` + : `$ok = $true +foreach ($t in 'BNS Resolver Daemon','BNS Sia Bridge') { + try { Stop-ScheduledTask -TaskName $t -ErrorAction Stop } catch {} + try { Disable-ScheduledTask -TaskName $t -ErrorAction Stop | Out-Null } + catch { if ($t -eq 'BNS Resolver Daemon') { $ok = $false } } +} +if ($ok) { exit 0 } else { exit 2 }`; + const encoded = Buffer.from(inner, "utf16le").toString("base64"); + // -PassThru + exit $p.ExitCode: the elevated shell's exit code (0 ok, + // 2 = daemon task missing) reaches us; a declined UAC prompt makes + // Start-Process throw, which exits the outer shell non-zero. const ps = spawn("powershell.exe", ["-NoProfile", "-Command", - "Start-Process powershell -Verb RunAs -Wait -WindowStyle Hidden -ArgumentList '-NoProfile','-Command',\"" + cmd.replace(/"/g, "`\"") + "\""], { windowsHide: true }); - ps.on("close", (code) => { if (code === 0) resolve(true); else reject(new Error("UAC declined or task failed (exit " + code + ")")); }); + `$p = Start-Process powershell -Verb RunAs -Wait -WindowStyle Hidden -PassThru -ArgumentList '-NoProfile','-NonInteractive','-EncodedCommand','${encoded}'; exit $p.ExitCode`], { windowsHide: true }); + ps.on("close", (code) => { + if (code === 0) resolve(true); + else if (code === 2) reject(new Error("the 'BNS Resolver Daemon' scheduled task is missing — reinstall Ariadne's Thread")); + else reject(new Error("UAC declined or task failed (exit " + code + ")")); + }); ps.on("error", (e) => reject(e)); }); } @@ -3938,8 +3972,10 @@ async function ariadneInstall() { const exePath = await ariadneDownloadInstaller(entry); const { spawn } = require("child_process"); return new Promise((resolve, reject) => { + // -PassThru + exit $p.ExitCode so Inno's own exit code reaches us; a bare + // -Wait always returned 0 and a failed silent install looked like success. const ps = spawn("powershell.exe", ["-NoProfile", "-Command", - `Start-Process -FilePath '${exePath.replace(/'/g, "''")}' -ArgumentList '/VERYSILENT','/SUPPRESSMSGBOXES','/NORESTART' -Verb RunAs -Wait` + `$p = Start-Process -FilePath '${exePath.replace(/'/g, "''")}' -ArgumentList '/VERYSILENT','/SUPPRESSMSGBOXES','/NORESTART' -Verb RunAs -Wait -PassThru; exit $p.ExitCode` ], { windowsHide: true }); const cleanup = () => { try { fs.unlinkSync(exePath); } catch {} }; ps.on("close", (code) => { cleanup(); code === 0 ? resolve(true) : reject(new Error("installer exited " + code)); }); @@ -3962,7 +3998,7 @@ function ariadneUninstall() { if (!uninstallCmd) return reject(new Error("Ariadne uninstaller not registered")); // uninstallCmd typically: "C:\Program Files (x86)\...\unins000.exe" /VERYSILENT // Spawn it elevated. Inno's silent uninstall respects /VERYSILENT so no UI. - const runCmd = `Start-Process -FilePath 'cmd.exe' -ArgumentList '/c ${uninstallCmd.replace(/'/g, "''")} /SUPPRESSMSGBOXES /NORESTART' -Verb RunAs -Wait`; + const runCmd = `$p = Start-Process -FilePath 'cmd.exe' -ArgumentList '/c ${uninstallCmd.replace(/'/g, "''")} /SUPPRESSMSGBOXES /NORESTART' -Verb RunAs -Wait -PassThru; exit $p.ExitCode`; const ps2 = spawn("powershell.exe", ["-NoProfile", "-Command", runCmd], { windowsHide: true }); ps2.on("close", (code) => code === 0 ? resolve(true) : reject(new Error("uninstaller exited " + code))); ps2.on("error", reject);