add app and apps icons
This commit is contained in:
parent
e0173d410b
commit
7508811374
16 changed files with 554 additions and 7 deletions
174
src/icons.js
Normal file
174
src/icons.js
Normal file
|
|
@ -0,0 +1,174 @@
|
|||
'use strict'
|
||||
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
|
||||
function rvaToOffset(sections, rva) {
|
||||
for (const s of sections) {
|
||||
if (rva >= s.va && rva < s.va + (s.vs || s.rawSize)) return rva - s.va + s.raw
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
function dirEntries(buf, dirOff) {
|
||||
const named = buf.readUInt16LE(dirOff + 12)
|
||||
const ids = buf.readUInt16LE(dirOff + 14)
|
||||
const out = []
|
||||
for (let i = 0; i < named + ids; i++) {
|
||||
const e = dirOff + 16 + i * 8
|
||||
const nameOrId = buf.readUInt32LE(e)
|
||||
const value = buf.readUInt32LE(e + 4)
|
||||
out.push({ id: nameOrId & 0x7fffffff, isName: (nameOrId & 0x80000000) !== 0, value, isDir: (value & 0x80000000) !== 0 })
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
function parseIcons(buf) {
|
||||
try {
|
||||
if (buf.length < 64 || buf.readUInt16LE(0) !== 0x5a4d) return null
|
||||
const peOff = buf.readUInt32LE(0x3c)
|
||||
if (peOff + 24 > buf.length || buf.readUInt32LE(peOff) !== 0x00004550) return null
|
||||
const numSections = buf.readUInt16LE(peOff + 6)
|
||||
const optSize = buf.readUInt16LE(peOff + 20)
|
||||
const optOff = peOff + 24
|
||||
const magic = buf.readUInt16LE(optOff)
|
||||
const resRva = magic === 0x20b ? buf.readUInt32LE(optOff + 128) : buf.readUInt32LE(optOff + 112)
|
||||
if (!resRva) return []
|
||||
|
||||
const secOff = optOff + optSize
|
||||
const sections = []
|
||||
for (let i = 0; i < numSections; i++) {
|
||||
const s = secOff + i * 40
|
||||
sections.push({
|
||||
va: buf.readUInt32LE(s + 12),
|
||||
vs: buf.readUInt32LE(s + 8),
|
||||
rawSize: buf.readUInt32LE(s + 16),
|
||||
raw: buf.readUInt32LE(s + 20)
|
||||
})
|
||||
}
|
||||
|
||||
const rootOff = rvaToOffset(sections, resRva)
|
||||
if (rootOff == null) return []
|
||||
|
||||
const typeEntry = dirEntries(buf, rootOff).find(e => e.id === 3 && e.isDir)
|
||||
if (!typeEntry) return []
|
||||
const iconDirOff = rvaToOffset(sections, resRva + (typeEntry.value & 0x7fffffff))
|
||||
if (iconDirOff == null) return []
|
||||
|
||||
const iconDatas = []
|
||||
for (const nameEntry of dirEntries(buf, iconDirOff)) {
|
||||
if (!nameEntry.isDir) continue
|
||||
const langDirOff = rvaToOffset(sections, resRva + (nameEntry.value & 0x7fffffff))
|
||||
if (langDirOff == null) continue
|
||||
const langEntry = dirEntries(buf, langDirOff)[0]
|
||||
if (!langEntry || langEntry.isDir) continue
|
||||
const dataEntryOff = rvaToOffset(sections, resRva + langEntry.value)
|
||||
if (dataEntryOff == null) continue
|
||||
const dataRva = buf.readUInt32LE(dataEntryOff)
|
||||
const dataSize = buf.readUInt32LE(dataEntryOff + 4)
|
||||
const dataOff = rvaToOffset(sections, dataRva)
|
||||
if (dataOff == null || !dataSize) continue
|
||||
iconDatas.push({ id: nameEntry.id, buf: Buffer.from(buf.subarray(dataOff, dataOff + dataSize)) })
|
||||
}
|
||||
if (!iconDatas.length) return []
|
||||
|
||||
const groupEntry = dirEntries(buf, rootOff).find(e => e.id === 14 && e.isDir)
|
||||
if (groupEntry) {
|
||||
const grpDirOff = rvaToOffset(sections, resRva + (groupEntry.value & 0x7fffffff))
|
||||
if (grpDirOff != null) {
|
||||
for (const nameEntry of dirEntries(buf, grpDirOff)) {
|
||||
if (!nameEntry.isDir) continue
|
||||
const langDirOff = rvaToOffset(sections, resRva + (nameEntry.value & 0x7fffffff))
|
||||
if (langDirOff == null) continue
|
||||
const langEntry = dirEntries(buf, langDirOff)[0]
|
||||
if (!langEntry || langEntry.isDir) continue
|
||||
const dataEntryOff = rvaToOffset(sections, resRva + langEntry.value)
|
||||
if (dataEntryOff == null) continue
|
||||
const dataRva = buf.readUInt32LE(dataEntryOff)
|
||||
const dataSize = buf.readUInt32LE(dataEntryOff + 4)
|
||||
const dataOff = rvaToOffset(sections, dataRva)
|
||||
if (dataOff == null || !dataSize) continue
|
||||
const grp = buf.subarray(dataOff, dataOff + dataSize)
|
||||
if (grp.length < 6) continue
|
||||
const count = grp.readUInt16LE(4)
|
||||
const best = { area: -1, buf: null }
|
||||
for (let i = 0; i < count; i++) {
|
||||
const e = 6 + i * 14
|
||||
const w = grp.readUInt8(e) || 256
|
||||
const h = grp.readUInt8(e + 1) || 256
|
||||
const id = grp.readUInt16LE(e + 12)
|
||||
const icon = iconDatas.find(ic => ic.id === id)
|
||||
if (icon && w * h > best.area) {
|
||||
best.area = w * h
|
||||
best.buf = icon.buf
|
||||
}
|
||||
}
|
||||
if (best.buf) return [best.buf]
|
||||
}
|
||||
}
|
||||
}
|
||||
return [iconDatas.reduce((a, b) => (b.buf.length > a.buf.length ? b : a)).buf]
|
||||
} catch {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
function pngSize(img) {
|
||||
if (img.length > 24 && img.readUInt32BE(0) === 0x89504e47) {
|
||||
return { w: img.readUInt32BE(16), h: img.readUInt32BE(20) }
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
function buildIco(images) {
|
||||
const header = Buffer.alloc(6)
|
||||
header.writeUInt16LE(0, 0)
|
||||
header.writeUInt16LE(1, 2)
|
||||
header.writeUInt16LE(images.length, 4)
|
||||
const metas = []
|
||||
const datas = []
|
||||
let offset = 6 + images.length * 16
|
||||
for (const img of images) {
|
||||
let w, h, colors = 0, planes = 1, bpp = 32
|
||||
if (img.readUInt32BE(0) === 0x89504e47) {
|
||||
const dim = pngSize(img) || { w: 256, h: 256 }
|
||||
w = dim.w
|
||||
h = dim.h
|
||||
} else {
|
||||
w = img.readUInt8(0) || 256
|
||||
h = img.readUInt8(1) || 256
|
||||
colors = img.readUInt8(2)
|
||||
planes = img.length > 4 ? img.readUInt16LE(4) || 1 : 1
|
||||
bpp = img.length > 6 ? img.readUInt16LE(6) || 32 : 32
|
||||
}
|
||||
const meta = Buffer.alloc(16)
|
||||
meta.writeUInt8(w >= 256 ? 0 : w, 0)
|
||||
meta.writeUInt8(h >= 256 ? 0 : h, 1)
|
||||
meta.writeUInt8(colors, 2)
|
||||
meta.writeUInt8(0, 3)
|
||||
meta.writeUInt16LE(planes, 4)
|
||||
meta.writeUInt16LE(bpp, 6)
|
||||
meta.writeUInt32LE(img.length, 8)
|
||||
meta.writeUInt32LE(offset, 12)
|
||||
metas.push(meta)
|
||||
datas.push(img)
|
||||
offset += img.length
|
||||
}
|
||||
return Buffer.concat([header, ...metas, ...datas])
|
||||
}
|
||||
|
||||
function extractIconFromExe(exePath, outPath) {
|
||||
try {
|
||||
const buf = fs.readFileSync(exePath)
|
||||
const icon = parseIcons(buf)
|
||||
if (!icon || !icon.length) return { ok: false, error: 'no-icon' }
|
||||
const ico = buildIco(icon)
|
||||
fs.mkdirSync(path.dirname(outPath), { recursive: true })
|
||||
fs.writeFileSync(outPath, ico)
|
||||
return { ok: true, path: outPath }
|
||||
} catch (err) {
|
||||
return { ok: false, error: String(err && err.message || err) }
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { extractIconFromExe, parseIcons, buildIco }
|
||||
Loading…
Add table
Add a link
Reference in a new issue