'use strict' const { spawnSync } = require('child_process') const fs = require('fs') const os = require('os') const path = require('path') const EXE = path.resolve(__dirname, 'dist', 'bm.exe') const CLEAN = fs.existsSync(path.resolve(__dirname, 'dist', 'bm_clean.exe')) ? path.resolve(__dirname, 'dist', 'bm_clean.exe') : EXE const ICON = path.resolve(__dirname, 'icon.ico') function findRcedit() { const candidates = [ path.join(os.homedir(), 'AppData', 'Roaming', 'npm', 'node_modules', 'rcedit', 'bin', 'rcedit-x64.exe'), ] try { const pkgDir = path.dirname(require.resolve('rcedit/package.json')) candidates.unshift(path.join(pkgDir, 'bin', 'rcedit-x64.exe')) } catch {} const fromPath = spawnSync('where', ['rcedit-x64.exe'], { encoding: 'utf8' }) if (fromPath.stdout) candidates.unshift(fromPath.stdout.trim().split('\n')[0]) return candidates.find(p => { try { return fs.statSync(p).isFile() } catch { return false } }) } const RCEDIT = findRcedit() if (!RCEDIT) { console.error('rcedit not found'); process.exit(1) } console.log('rcedit:', RCEDIT) if (CLEAN === EXE) { const tmp = EXE.replace('.exe', '_clean.exe') fs.copyFileSync(EXE, tmp) } const cleanPath = CLEAN === EXE ? EXE.replace('.exe', '_clean.exe') : CLEAN const origBuf = fs.readFileSync(cleanPath) // Find pkg position values const kbuf = Buffer.from("var PAYLOAD_POSITION = '", 'ascii') let pos = {} for (let i = 0; i < origBuf.length - 200; i++) { if (origBuf.slice(i, i + kbuf.length).equals(kbuf)) { const block = origBuf.slice(i, i + 400).toString('ascii') const pp = block.match(/PAYLOAD_POSITION = '(\d+)\s*'/) const ps = block.match(/PAYLOAD_SIZE = '(\d+)\s*'/) const pp2 = block.match(/PRELUDE_POSITION = '(\d+)\s*'/) const ps2 = block.match(/PRELUDE_SIZE = '(\d+)\s*'/) if (pp) { pos = { PAYLOAD_POSITION: +pp[1], PAYLOAD_SIZE: +ps[1], PRELUDE_POSITION: +pp2[1], PRELUDE_SIZE: +ps2[1], } console.log('pkg vars:', pos) break } } } if (!pos.PAYLOAD_POSITION) { console.error('vars not found'); process.exit(1) } const cleanBuf = origBuf const overlay = cleanBuf.slice(pos.PAYLOAD_POSITION) const peOnly = cleanBuf.slice(0, pos.PAYLOAD_POSITION) console.log(`overlay: ${overlay.length}b, PE: ${peOnly.length}b`) fs.writeFileSync(EXE, peOnly) console.log('rcedit...') const r = spawnSync(RCEDIT, [EXE, '--set-icon', ICON], { stdio: 'inherit' }) if (r.status !== 0) { process.exit(1) } const patchedPE = fs.readFileSync(EXE) const newPP = patchedPE.length const newPRP = newPP + pos.PAYLOAD_SIZE console.log(`new PE: ${patchedPE.length} new PP: ${newPP} new PRP: ${newPRP}`) const full = Buffer.from(Buffer.concat([patchedPE, overlay])) function replaceNum(buf, oldVal, newVal) { const oldStr = String(oldVal) const newStr = String(newVal) const needle = Buffer.from(`'${oldStr}`, 'ascii') let count = 0 let i = 0 while ((i = buf.indexOf(needle, i)) !== -1) { let end = i + 1 while (buf[end] !== 0x27 && end < i + 30) end++ const fieldLen = end - i - 1 if (fieldLen < oldStr.length) { i++; continue } const padded = newStr + ' '.repeat(fieldLen - newStr.length) buf.write(padded, i + 1, 'ascii') console.log(` patched '${oldStr}' → '${newStr}' at offset ${i}`) count++ i = end } return count } let total = 0 total += replaceNum(full, pos.PAYLOAD_POSITION, newPP) total += replaceNum(full, pos.PRELUDE_POSITION, newPRP) console.log(`total patches: ${total}`) fs.writeFileSync(EXE, full) fs.unlinkSync(EXE + '.notail').catch?.(() => {}) try { fs.unlinkSync(CLEAN + '.pe_only') } catch {} console.log(`done: ${EXE} = ${full.length} bytes`)