diff --git a/scripts/patch-windows-binaries.mjs b/scripts/patch-windows-binaries.mjs index 6df9528..d29e3d0 100644 --- a/scripts/patch-windows-binaries.mjs +++ b/scripts/patch-windows-binaries.mjs @@ -1,9 +1,51 @@ -import { existsSync } from 'node:fs' +import { copyFileSync, existsSync, unlinkSync } from 'node:fs' import path from 'node:path' import { fileURLToPath } from 'node:url' import rcedit from 'rcedit' const PATCH_TARGETS = new Set(['launcher.exe']) +const MAX_ATTEMPTS = 6 +const BASE_DELAY_MS = 500 + +function sleep(ms) { + return new Promise((resolve) => setTimeout(resolve, ms)) +} + +async function patchExecutable(executablePath) { + let lastError + + for (let attempt = 1; attempt <= MAX_ATTEMPTS; attempt++) { + const tempPath = `${executablePath}.rcedit-${process.pid}-${attempt}.tmp` + + try { + // Patch a copy then replace — avoids AV/indexer locks on the live PE. + copyFileSync(executablePath, tempPath) + await rcedit(tempPath, { + 'requested-execution-level': 'asInvoker' + }) + copyFileSync(tempPath, executablePath) + return + } catch (error) { + lastError = error + const message = error instanceof Error ? error.message : String(error) + console.warn( + `patch-windows-binaries: attempt ${attempt}/${MAX_ATTEMPTS} failed for ${executablePath}: ${message}` + ) + + if (attempt < MAX_ATTEMPTS) { + await sleep(BASE_DELAY_MS * 2 ** (attempt - 1)) + } + } finally { + try { + unlinkSync(tempPath) + } catch { + // Ignore cleanup failures. + } + } + } + + throw lastError +} export async function patchWindowsBinaries(appDir) { if (process.platform !== 'win32') { @@ -23,9 +65,7 @@ export async function patchWindowsBinaries(appDir) { } try { - await rcedit(executablePath, { - 'requested-execution-level': 'asInvoker' - }) + await patchExecutable(executablePath) console.log(`patch-windows-binaries: set asInvoker on ${executablePath}`) } catch (error) { console.warn(