ci: add release workflow + rcedit devdep, fix patch_icon3 for CI

This commit is contained in:
Savsis? 2026-08-30 19:02:56 +05:00
parent ee59ba8247
commit 023ebae786
4 changed files with 390 additions and 1 deletions

110
patch_icon3.js Normal file
View file

@ -0,0 +1,110 @@
'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`)