@l1yp/file-viewer 0.1.4 → 0.1.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -0
- package/dist/components/AnsiText.d.ts +47 -0
- package/dist/components/AnsiText.js +41 -0
- package/dist/components/AnsiText.js.map +1 -0
- package/dist/components/CodeView.js +1 -1
- package/dist/components/CodeView.js.map +1 -1
- package/dist/components/CodeView.vue_vue_type_script_setup_true_lang.js +80 -71
- package/dist/components/CodeView.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/DocxEmbeddedFiles.js +9 -0
- package/dist/components/DocxEmbeddedFiles.js.map +1 -0
- package/dist/components/DocxEmbeddedFiles.vue.d.ts +15 -0
- package/dist/components/DocxEmbeddedFiles.vue_vue_type_script_setup_true_lang.js +69 -0
- package/dist/components/DocxEmbeddedFiles.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/DocxViewer.js +1 -1
- package/dist/components/DocxViewer.js.map +1 -1
- package/dist/components/DocxViewer.vue_vue_type_script_setup_true_lang.js +180 -50
- package/dist/components/DocxViewer.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/FileViewerBody.js +1 -1
- package/dist/components/FileViewerBody.js.map +1 -1
- package/dist/components/FileViewerBody.vue.d.ts +1 -0
- package/dist/components/FileViewerBody.vue_vue_type_script_setup_true_lang.js +117 -114
- package/dist/components/FileViewerBody.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/FileViewerPanel.js +1 -1
- package/dist/components/FileViewerPanel.js.map +1 -1
- package/dist/components/FileViewerPanel.vue_vue_type_script_setup_true_lang.js +18 -18
- package/dist/components/FileViewerPanel.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/LogViewer.js +1 -1
- package/dist/components/LogViewer.js.map +1 -1
- package/dist/components/LogViewer.vue.d.ts +3 -1
- package/dist/components/LogViewer.vue_vue_type_script_setup_true_lang.js +284 -280
- package/dist/components/LogViewer.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/lib/ansi.d.ts +44 -0
- package/dist/lib/ansi.js +224 -0
- package/dist/lib/ansi.js.map +1 -0
- package/dist/lib/cfb.d.ts +37 -0
- package/dist/lib/cfb.js +112 -0
- package/dist/lib/cfb.js.map +1 -0
- package/dist/lib/docxEmbeddedDom.d.ts +22 -0
- package/dist/lib/docxEmbeddedDom.js +128 -0
- package/dist/lib/docxEmbeddedDom.js.map +1 -0
- package/dist/lib/docxEmbeddings.d.ts +41 -0
- package/dist/lib/docxEmbeddings.js +226 -0
- package/dist/lib/docxEmbeddings.js.map +1 -0
- package/dist/lib/msi.js +62 -154
- package/dist/lib/msi.js.map +1 -1
- package/dist/lib/olePackage.d.ts +8 -0
- package/dist/lib/olePackage.js +37 -0
- package/dist/lib/olePackage.js.map +1 -0
- package/dist/lib/pptxFirstSlide.d.ts +11 -0
- package/dist/lib/pptxFirstSlide.js +34 -0
- package/dist/lib/pptxFirstSlide.js.map +1 -0
- package/dist/style.css +1 -1
- package/package.json +1 -1
package/dist/lib/msi.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"msi.js","names":[],"sources":["../../src/lib/msi.ts"],"sourcesContent":["/**\n * Minimal in-browser MSI (Windows Installer) reader — no dependency, no WASM.\n *\n * An `.msi` is NOT a PE executable (that's `.exe`/`.dll` — see lib/binobj.ts), and NOT a zip (that's\n * `.appx`/`.msix` — see lib/appx.ts). It's an OLE2 **Compound File** (a.k.a. structured storage / CFBF):\n * a mini-filesystem of \"streams\" inside one file (the same container legacy `.doc`/`.xls`/`.msg` use). The\n * install metadata we surface lives in two of those streams:\n * - the standard **SummaryInformation** property set (`\\x05SummaryInformation`): title / subject / author /\n * comments, the target platform+language template, the package (revision) code, the authoring tool, dates.\n * - the **Property table** (ProductName / ProductVersion / ProductCode / UpgradeCode / Manufacturer / …),\n * spread across the MSI string pool (`_StringData` + `_StringPool`) and the `Property` table stream.\n *\n * We parse only what a \"what is this installer?\" view needs — never the embedded cabinet / payload files.\n * The logic is validated byte-for-byte against real installers using the Windows `WindowsInstaller.Installer`\n * COM automation as an oracle (the Property table + SummaryInformation match it exactly).\n *\n * Used by MsiViewer (the side viewer's MSI mode): the bytes come from the same download endpoint as any\n * other file, are parsed entirely client-side, and never leave the browser.\n */\n\nexport interface MsiMeta {\n label: string\n value: string\n}\nexport interface MsiProperty {\n name: string\n value: string\n}\n\nexport interface MsiInfo {\n /** Resolved product identity from the Property table (any field may be absent if the table is unreadable). */\n productName?: string\n productVersion?: string\n manufacturer?: string\n productCode?: string\n upgradeCode?: string\n /** Install language: a human label when the LCID is recognized (with the raw id), else the raw id. */\n language?: string\n /** Full Property table (name → value), in stored order. Empty if the table couldn't be decoded. */\n properties: MsiProperty[]\n /** SummaryInformation rows, pre-labeled (Chinese), in display order. */\n summary: MsiMeta[]\n /** Decoded names of the database tables present — a quick \"shape of the installer\" cue. */\n tables: string[]\n /** Non-fatal notes (e.g. the Property table was unreadable but SummaryInformation succeeded). */\n notes: string[]\n}\n\n/** Thrown when the bytes aren't an OLE compound file at all (so the viewer can show a clear message). */\nexport class NotMsiError extends Error {\n constructor(message: string) {\n super(message)\n this.name = 'NotMsiError'\n }\n}\n\n// ── Compound File (CFBF / MS-CFB) reader ────────────────────────────────────────────────────────────\nconst CFB_SIG = [0xd0, 0xcf, 0x11, 0xe0, 0xa1, 0xb1, 0x1a, 0xe1]\nconst ENDOFCHAIN = 0xfffffffe\nconst FREESECT = 0xffffffff\n\ninterface CfbEntry {\n /** Raw UTF-16 directory name (null-stripped) — MSI table names are still mangled (see decodeStreamName). */\n name: string\n /** 1 = storage, 2 = stream, 5 = root storage. */\n type: number\n start: number\n size: number\n}\n\nclass Cfb {\n private dv: DataView\n private buf: ArrayBuffer\n private sectorSize: number\n private miniSectorSize: number\n private miniCutoff: number\n private fat: number[] = []\n private miniFat: number[] = []\n private miniStream: Uint8Array = new Uint8Array(0)\n readonly entries: CfbEntry[] = []\n\n constructor(buf: ArrayBuffer) {\n this.buf = buf\n const dv = new DataView(buf)\n this.dv = dv\n if (buf.byteLength < 512) throw new NotMsiError('文件过小,不是有效的复合文件')\n for (let i = 0; i < 8; i++) {\n if (dv.getUint8(i) !== CFB_SIG[i]) {\n throw new NotMsiError('不是 OLE 复合文件(缺少 D0CF11E0 魔数)')\n }\n }\n const major = dv.getUint16(0x1a, true)\n this.sectorSize = 1 << dv.getUint16(0x1e, true)\n this.miniSectorSize = 1 << dv.getUint16(0x20, true)\n const firstDir = dv.getUint32(0x30, true)\n this.miniCutoff = dv.getUint32(0x38, true)\n const firstMiniFat = dv.getUint32(0x3c, true)\n const firstDifat = dv.getUint32(0x44, true)\n const per = this.sectorSize / 4\n\n // DIFAT (109 entries in the header, then any chained DIFAT sectors) → list of FAT sectors → FAT array.\n const difat: number[] = []\n for (let i = 0; i < 109; i++) {\n const v = dv.getUint32(0x4c + i * 4, true)\n if (v !== FREESECT && v !== ENDOFCHAIN) difat.push(v)\n }\n let ds = firstDifat\n let guard = 0\n while (ds !== ENDOFCHAIN && ds !== FREESECT && guard++ < 1_000_000) {\n const base = this.sectorOffset(ds)\n for (let i = 0; i < per - 1; i++) {\n const v = dv.getUint32(base + i * 4, true)\n if (v !== FREESECT) difat.push(v)\n }\n ds = dv.getUint32(base + (per - 1) * 4, true)\n }\n for (const fs of difat) {\n const base = this.sectorOffset(fs)\n for (let i = 0; i < per; i++) this.fat.push(dv.getUint32(base + i * 4, true))\n }\n\n // Directory: a chain of 128-byte entries starting at firstDir.\n const dirBytes = this.readFatStream(firstDir, this.chain(firstDir).length * this.sectorSize)\n const ddv = new DataView(dirBytes.buffer, dirBytes.byteOffset, dirBytes.byteLength)\n for (let o = 0; o + 128 <= dirBytes.length; o += 128) {\n const type = ddv.getUint8(o + 66)\n if (type === 0) continue // unallocated entry\n const nameLen = ddv.getUint16(o + 64, true) // bytes incl. null terminator\n let name = ''\n for (let c = 0; c + 1 < nameLen; c += 2) {\n const code = ddv.getUint16(o + c, true)\n if (code === 0) break\n name += String.fromCharCode(code)\n }\n const start = ddv.getUint32(o + 116, true)\n // v3 stores the size in 4 bytes (the high 4 may be garbage); v4 uses the full 8.\n const size =\n major <= 3 ? ddv.getUint32(o + 120, true) : Number(ddv.getBigUint64(o + 120, true))\n this.entries.push({ name, type, start, size })\n }\n\n // Mini stream (held by the root entry, in the regular FAT) + the MiniFAT (chained from firstMiniFat).\n const root = this.entries.find((e) => e.type === 5)\n if (root) this.miniStream = this.readFatStream(root.start, root.size)\n for (const s of this.chain(firstMiniFat)) {\n const base = this.sectorOffset(s)\n for (let i = 0; i < per; i++) this.miniFat.push(dv.getUint32(base + i * 4, true))\n }\n }\n\n private sectorOffset(s: number): number {\n // Sector S starts at (S+1)*sectorSize: the 512-byte header is padded to a full sector, so for v3\n // (512-byte) this is 512 + S*512, and for v4 (4096-byte) it's 4096 + S*4096 (NOT 512 + S*4096).\n return (s + 1) * this.sectorSize\n }\n /** Follow a FAT chain to a list of sector indices (bounded against corruption). */\n private chain(start: number): number[] {\n const out: number[] = []\n let s = start\n let guard = 0\n while (s !== ENDOFCHAIN && s !== FREESECT && s < this.fat.length && guard++ < 10_000_000) {\n out.push(s)\n s = this.fat[s]\n }\n return out\n }\n /** Copy one big-FAT sector, clamped to the buffer (a truncated file yields short data, not a throw). */\n private sectorBytes(s: number): Uint8Array {\n const off = this.sectorOffset(s)\n const end = Math.min(this.buf.byteLength, off + this.sectorSize)\n const out = new Uint8Array(this.sectorSize)\n if (off < this.buf.byteLength) out.set(new Uint8Array(this.buf, off, end - off))\n return out\n }\n private readFatStream(start: number, size: number): Uint8Array {\n const ch = this.chain(start)\n const out = new Uint8Array(ch.length * this.sectorSize)\n for (let i = 0; i < ch.length; i++) out.set(this.sectorBytes(ch[i]), i * this.sectorSize)\n return out.subarray(0, size)\n }\n private readMiniStream(start: number, size: number): Uint8Array {\n // The stream occupies exactly `need` mini-sectors; read only that many even if the chain runs longer\n // (some writers leave trailing/looping links past the stream end — copying them would overflow `out`).\n const need = Math.ceil(size / this.miniSectorSize)\n const out = new Uint8Array(need * this.miniSectorSize)\n let s = start\n let i = 0\n while (s !== ENDOFCHAIN && s !== FREESECT && i < need) {\n const off = s * this.miniSectorSize\n out.set(this.miniStream.subarray(off, off + this.miniSectorSize), i * this.miniSectorSize)\n i++\n s = this.miniFat[s]\n }\n return out.subarray(0, size)\n }\n /** The decompressed bytes of one directory entry's stream (small streams live in the mini stream). */\n stream(e: CfbEntry): Uint8Array {\n if (e.size === 0) return new Uint8Array(0)\n if (e.type === 5 || e.size >= this.miniCutoff) return this.readFatStream(e.start, e.size)\n return this.readMiniStream(e.start, e.size)\n }\n}\n\n// ── MSI stream-name (un)mangling ────────────────────────────────────────────────────────────────────\n// MSI encodes table/stream names into a private Unicode range using a 64-char alphabet (Wine's\n// decode_streamname). A leading 0x4840 wide-char marks \"this is a database table\".\nfunction mime2utf(x: number): string {\n if (x < 10) return String.fromCharCode(x + 48) // 0-9\n if (x < 36) return String.fromCharCode(x - 10 + 65) // A-Z\n if (x < 62) return String.fromCharCode(x - 36 + 97) // a-z\n return x === 62 ? '.' : '_'\n}\nfunction decodeStreamName(raw: string): { name: string; isTable: boolean } {\n let out = ''\n let isTable = false\n for (let i = 0; i < raw.length; i++) {\n const ch = raw.charCodeAt(i)\n if (ch >= 0x3800 && ch < 0x4800) {\n const v = ch - 0x3800 // two 6-bit characters packed into one wide-char\n out += mime2utf(v & 0x3f) + mime2utf((v >> 6) & 0x3f)\n } else if (ch >= 0x4800 && ch < 0x4840) {\n out += mime2utf(ch - 0x4800) // a single character\n } else if (ch === 0x4840) {\n isTable = true // table marker (prefix); contributes nothing to the name\n } else {\n out += String.fromCharCode(ch) // literal (ASCII names, the \\x05 property-set prefix, …)\n }\n }\n return { name: out, isTable }\n}\n\n// ── SummaryInformation (OLE property set, MS-OLEPS) ─────────────────────────────────────────────────\ninterface PropVal {\n type: number\n value: string | number\n}\n\nfunction parsePropertySet(bytes: Uint8Array): { codepage: number; props: Map<number, PropVal> } {\n const v = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength)\n // 28-byte set header + one 20-byte (FMTID + offset) section locator; the section offset sits at byte 44.\n const sectionOff = v.getUint32(44, true)\n const numProps = v.getUint32(sectionOff + 4, true)\n const rows: { pid: number; abs: number }[] = []\n for (let i = 0; i < numProps; i++) {\n const pid = v.getUint32(sectionOff + 8 + i * 8, true)\n const off = v.getUint32(sectionOff + 8 + i * 8 + 4, true)\n rows.push({ pid, abs: sectionOff + off })\n }\n // The codepage (PID 1) governs how the LPSTR string properties decode — read it first.\n let codepage = 1252\n const cpRow = rows.find((r) => r.pid === 1)\n if (cpRow && v.getUint32(cpRow.abs, true) === 2) {\n let cp = v.getInt16(cpRow.abs + 4, true)\n if (cp < 0) cp += 65536 // 65001 (UTF-8) overflows a signed 16-bit read\n codepage = cp\n }\n const dec = new TextDecoder(cpLabel(codepage))\n const props = new Map<number, PropVal>()\n for (const { pid, abs } of rows) {\n const type = v.getUint32(abs, true)\n if (type === 2) props.set(pid, { type, value: v.getInt16(abs + 4, true) })\n else if (type === 3) props.set(pid, { type, value: v.getInt32(abs + 4, true) })\n else if (type === 30) {\n // LPSTR: codepage bytes, NUL-terminated — cut at the first NUL (avoids a control-char regex).\n let raw = bytes.subarray(abs + 8, abs + 8 + v.getUint32(abs + 4, true))\n const nul = raw.indexOf(0)\n if (nul >= 0) raw = raw.subarray(0, nul)\n props.set(pid, { type, value: dec.decode(raw) })\n } else if (type === 64) {\n props.set(pid, {\n type,\n value: formatFiletime(v.getUint32(abs + 4, true), v.getUint32(abs + 8, true)),\n })\n }\n }\n return { codepage, props }\n}\n\nfunction buildSummary(props: Map<number, PropVal>): MsiMeta[] {\n const out: MsiMeta[] = []\n const push = (pid: number, label: string, fmt?: (v: string | number) => string): void => {\n const p = props.get(pid)\n if (p == null) return\n const value = fmt ? fmt(p.value) : String(p.value)\n if (value) out.push({ label, value })\n }\n push(3, '主题', String) // PID_SUBJECT — usually the product/package name\n push(4, '作者 / 厂商', String) // PID_AUTHOR\n push(6, '备注', String) // PID_COMMENTS — description\n push(2, '标题', String) // PID_TITLE\n push(5, '关键字', String) // PID_KEYWORDS\n push(7, '平台 / 语言', (v) => formatTemplate(String(v))) // PID_TEMPLATE\n push(9, '包代码', String) // PID_REVNUMBER — the package (revision) code GUID\n push(18, '制作工具', String) // PID_APPNAME — authoring application\n push(12, '创建时间', String) // PID_CREATE_DTM\n push(13, '上次保存', String) // PID_LASTSAVE_DTM\n push(14, '架构版本', (v) => formatSchema(Number(v))) // PID_PAGECOUNT — min installer version\n push(15, '源类型', (v) => formatWordCount(Number(v))) // PID_WORDCOUNT — source flags\n push(19, '安全', (v) => formatSecurity(Number(v))) // PID_SECURITY\n return out\n}\n\n// ── MSI string pool + Property table ────────────────────────────────────────────────────────────────\n// `_StringPool` is an array of 4-byte slots: [length: u16][refcount: u16] (length FIRST — validated against\n// the COM oracle). Slot 0 carries the codepage (+ a long-string-refs flag in the high bit). `_StringData` is\n// every string concatenated in index order; string i lives at the running offset, `length` bytes long.\nfunction buildStringTable(\n pool: Uint8Array,\n data: Uint8Array,\n fallbackCp: number,\n): { strings: string[]; longRefs: boolean } {\n const pdv = new DataView(pool.buffer, pool.byteOffset, pool.byteLength)\n const cpRaw = pdv.getUint16(0, true) | (pdv.getUint16(2, true) << 16)\n const longRefs = (cpRaw & 0x80000000) !== 0 // ⇒ 3-byte string references in table data\n let cp = cpRaw & 0x7fffffff\n if (cp === 0) cp = fallbackCp || 1252 // codepage 0 = system ANSI; fall back to the SummaryInfo codepage\n const dec = new TextDecoder(cpLabel(cp))\n const strings: string[] = ['']\n let off = 0\n const count = Math.floor(pool.length / 4)\n for (let i = 1; i < count; i++) {\n const len = pdv.getUint16(i * 4, true)\n if (len === 0) {\n strings.push('')\n continue\n }\n strings.push(dec.decode(data.subarray(off, off + len)))\n off += len\n }\n return { strings, longRefs }\n}\n\n// The Property table is two string columns (Property name, Value) stored column-major: all the name refs,\n// then all the value refs. Each ref is a 2-byte (or 3-byte, with long string refs) index into the pool.\nfunction decodePropertyTable(\n tbl: Uint8Array,\n st: { strings: string[]; longRefs: boolean },\n): MsiProperty[] {\n const refSize = st.longRefs ? 3 : 2\n const dv = new DataView(tbl.buffer, tbl.byteOffset, tbl.byteLength)\n const rows = Math.floor(tbl.length / (refSize * 2))\n const ref = (idx: number): number => {\n const o = idx * refSize\n let v = dv.getUint16(o, true)\n if (refSize === 3) v += dv.getUint8(o + 2) << 16\n return v\n }\n const out: MsiProperty[] = []\n for (let r = 0; r < rows; r++) {\n const name = st.strings[ref(r)] ?? ''\n const value = st.strings[ref(rows + r)] ?? ''\n if (name) out.push({ name, value })\n }\n return out\n}\n\n// ── orchestrator ────────────────────────────────────────────────────────────────────────────────────\nexport function parseMsi(buf: ArrayBuffer): MsiInfo {\n const cfb = new Cfb(buf) // throws NotMsiError if the bytes aren't a compound file\n const decoded = cfb.entries.map((e) => ({ e, ...decodeStreamName(e.name) }))\n const notes: string[] = []\n\n const tables = decoded\n .filter((d) => d.isTable)\n .map((d) => d.name)\n .sort((a, b) => a.localeCompare(b))\n\n // SummaryInformation (standard, near-universal in MSIs).\n let summary: MsiMeta[] = []\n let siCodepage = 0\n const siD = decoded.find((d) => d.name === '\u0005SummaryInformation')\n if (siD) {\n try {\n const ps = parsePropertySet(cfb.stream(siD.e))\n siCodepage = ps.codepage\n summary = buildSummary(ps.props)\n } catch (e) {\n notes.push('SummaryInformation 解析失败:' + errMsg(e))\n }\n }\n\n // Property table (best-effort: a malformed string pool never blanks the summary above).\n let properties: MsiProperty[] = []\n const sd = decoded.find((d) => d.name === '_StringData')\n const sp = decoded.find((d) => d.name === '_StringPool')\n const pr = decoded.find((d) => d.name === 'Property')\n if (sd && sp && pr) {\n try {\n const st = buildStringTable(cfb.stream(sp.e), cfb.stream(sd.e), siCodepage)\n properties = decodePropertyTable(cfb.stream(pr.e), st)\n } catch (e) {\n notes.push('Property 表解析失败:' + errMsg(e))\n }\n }\n\n const map = new Map(properties.map((p) => [p.name, p.value]))\n const langId = map.get('ProductLanguage')\n const info: MsiInfo = {\n productName: map.get('ProductName'),\n productVersion: map.get('ProductVersion'),\n manufacturer: map.get('Manufacturer'),\n productCode: map.get('ProductCode'),\n upgradeCode: map.get('UpgradeCode'),\n language: langId ? formatLanguage(langId) : undefined,\n properties,\n summary,\n tables,\n notes,\n }\n if (!properties.length && !summary.length) {\n notes.push('未找到 MSI 摘要信息或属性表(可能是别的复合文件,而非 MSI 数据库)')\n }\n return info\n}\n\n// ── formatting helpers ──────────────────────────────────────────────────────────────────────────────\nfunction errMsg(e: unknown): string {\n return e instanceof Error ? e.message : String(e)\n}\n\nfunction cpLabel(cp: number): string {\n switch (cp) {\n case 65001:\n return 'utf-8'\n case 1200:\n return 'utf-16le'\n case 1201:\n return 'utf-16be'\n case 932:\n return 'shift_jis'\n case 936:\n return 'gbk'\n case 949:\n return 'euc-kr'\n case 950:\n return 'big5'\n case 1250:\n return 'windows-1250'\n case 1251:\n return 'windows-1251'\n case 1253:\n return 'windows-1253'\n case 1254:\n return 'windows-1254'\n case 1255:\n return 'windows-1255'\n case 1256:\n return 'windows-1256'\n case 1257:\n return 'windows-1257'\n case 1258:\n return 'windows-1258'\n default:\n return 'windows-1252' // 1252 and the system-ANSI (0) fallback\n }\n}\n\nfunction formatFiletime(lo: number, hi: number): string {\n const ft = hi * 4294967296 + lo\n if (!ft) return ''\n const ms = ft / 10000 - 11644473600000\n const d = new Date(ms)\n if (Number.isNaN(d.getTime())) return ''\n const p = (n: number): string => String(n).padStart(2, '0')\n return `${d.getUTCFullYear()}-${p(d.getUTCMonth() + 1)}-${p(d.getUTCDate())} ${p(d.getUTCHours())}:${p(d.getUTCMinutes())} UTC`\n}\n\n// MSI \"Template\" = \"<platform>;<langid>[,<langid>…]\" e.g. \"x64;1033\". Surface the platform + language names.\nfunction formatTemplate(t: string): string {\n const [platform, langs] = t.split(';')\n const names = (langs ?? '')\n .split(',')\n .map((s) => s.trim())\n .filter(Boolean)\n .map((id) => formatLanguage(id))\n const parts: string[] = []\n if (platform && platform.trim()) parts.push(platform.trim())\n if (names.length) parts.push(names.join(', '))\n return parts.join(' · ') || t\n}\n\n// PID_PAGECOUNT for an install package = the minimum Windows Installer version (e.g. 200 ⇒ 2.00, 500 ⇒ 5.00).\nfunction formatSchema(n: number): string {\n if (!n) return ''\n return `${Math.floor(n / 100)}.${String(n % 100).padStart(2, '0')}`\n}\n\n// PID_WORDCOUNT = source flags bitfield.\nfunction formatWordCount(n: number): string {\n const flags: string[] = []\n flags.push(n & 0x2 ? '压缩 (cabinet)' : '未压缩')\n if (n & 0x1) flags.push('短文件名')\n if (n & 0x4) flags.push('管理映像')\n if (n & 0x8) flags.push('从源运行')\n return flags.join(' · ')\n}\n\nfunction formatSecurity(n: number): string {\n switch (n) {\n case 0:\n return '无限制'\n case 1:\n return '建议只读'\n case 2:\n return '强制只读'\n case 4:\n return '已锁定'\n default:\n return String(n)\n }\n}\n\n// Common Windows LCIDs → Chinese language names (best-effort; unknown ids fall back to the raw number).\nconst LCID_NAMES: Record<number, string> = {\n 0: '语言中性',\n 1025: '阿拉伯语',\n 1028: '中文(繁体)',\n 1029: '捷克语',\n 1030: '丹麦语',\n 1031: '德语',\n 1032: '希腊语',\n 1033: '英语(美国)',\n 1034: '西班牙语',\n 1035: '芬兰语',\n 1036: '法语',\n 1037: '希伯来语',\n 1038: '匈牙利语',\n 1040: '意大利语',\n 1041: '日语',\n 1042: '韩语',\n 1043: '荷兰语',\n 1044: '挪威语',\n 1045: '波兰语',\n 1046: '葡萄牙语(巴西)',\n 1049: '俄语',\n 1051: '斯洛伐克语',\n 1053: '瑞典语',\n 1054: '泰语',\n 1055: '土耳其语',\n 1057: '印尼语',\n 1058: '乌克兰语',\n 1060: '斯洛文尼亚语',\n 1066: '越南语',\n 2052: '中文(简体)',\n 2057: '英语(英国)',\n 2070: '葡萄牙语',\n 3076: '中文(香港)',\n 3082: '西班牙语(西班牙)',\n 4100: '中文(新加坡)',\n}\nfunction formatLanguage(id: string): string {\n const n = Number(id)\n const name = LCID_NAMES[n]\n return name ? `${name} (${id})` : id\n}\n"],"mappings":";AAiDA,IAAa,IAAb,cAAiC,MAAM;CACrC,YAAY,GAAiB;EAE3B,AADA,MAAM,CAAO,GACb,KAAK,OAAO;CACd;AACF,GAGM,IAAU;CAAC;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;AAAI,GACzD,IAAa,YACb,IAAW,YAWX,IAAN,MAAU;CACR;CACA;CACA;CACA;CACA;CACA,MAAwB,CAAC;CACzB,UAA4B,CAAC;CAC7B,aAAiC,IAAI,WAAY;CACjD,UAA+B,CAAC;CAEhC,YAAY,GAAkB;EAC5B,KAAK,MAAM;EACX,IAAM,IAAK,IAAI,SAAS,CAAG;EAE3B,IADA,KAAK,KAAK,GACN,EAAI,aAAa,KAAK,MAAM,IAAI,EAAY,gBAAgB;EAChE,KAAK,IAAI,IAAI,GAAG,IAAI,GAAG,KACrB,IAAI,EAAG,SAAS,CAAC,MAAM,EAAQ,IAC7B,MAAM,IAAI,EAAY,6BAA6B;EAGvD,IAAM,IAAQ,EAAG,UAAU,IAAM,EAAI;EAErC,AADA,KAAK,aAAa,KAAK,EAAG,UAAU,IAAM,EAAI,GAC9C,KAAK,iBAAiB,KAAK,EAAG,UAAU,IAAM,EAAI;EAClD,IAAM,IAAW,EAAG,UAAU,IAAM,EAAI;EACxC,KAAK,aAAa,EAAG,UAAU,IAAM,EAAI;EACzC,IAAM,IAAe,EAAG,UAAU,IAAM,EAAI,GACtC,IAAa,EAAG,UAAU,IAAM,EAAI,GACpC,IAAM,KAAK,aAAa,GAGxB,IAAkB,CAAC;EACzB,KAAK,IAAI,IAAI,GAAG,IAAI,KAAK,KAAK;GAC5B,IAAM,IAAI,EAAG,UAAU,KAAO,IAAI,GAAG,EAAI;GACzC,AAAI,MAAM,KAAY,MAAM,KAAY,EAAM,KAAK,CAAC;EACtD;EACA,IAAI,IAAK,GACL,IAAQ;EACZ,OAAO,MAAO,KAAc,MAAO,KAAY,MAAU,MAAW;GAClE,IAAM,IAAO,KAAK,aAAa,CAAE;GACjC,KAAK,IAAI,IAAI,GAAG,IAAI,IAAM,GAAG,KAAK;IAChC,IAAM,IAAI,EAAG,UAAU,IAAO,IAAI,GAAG,EAAI;IACzC,AAAI,MAAM,KAAU,EAAM,KAAK,CAAC;GAClC;GACA,IAAK,EAAG,UAAU,KAAQ,IAAM,KAAK,GAAG,EAAI;EAC9C;EACA,KAAK,IAAM,KAAM,GAAO;GACtB,IAAM,IAAO,KAAK,aAAa,CAAE;GACjC,KAAK,IAAI,IAAI,GAAG,IAAI,GAAK,KAAK,KAAK,IAAI,KAAK,EAAG,UAAU,IAAO,IAAI,GAAG,EAAI,CAAC;EAC9E;EAGA,IAAM,IAAW,KAAK,cAAc,GAAU,KAAK,MAAM,CAAQ,CAAC,CAAC,SAAS,KAAK,UAAU,GACrF,IAAM,IAAI,SAAS,EAAS,QAAQ,EAAS,YAAY,EAAS,UAAU;EAClF,KAAK,IAAI,IAAI,GAAG,IAAI,OAAO,EAAS,QAAQ,KAAK,KAAK;GACpD,IAAM,IAAO,EAAI,SAAS,IAAI,EAAE;GAChC,IAAI,MAAS,GAAG;GAChB,IAAM,IAAU,EAAI,UAAU,IAAI,IAAI,EAAI,GACtC,IAAO;GACX,KAAK,IAAI,IAAI,GAAG,IAAI,IAAI,GAAS,KAAK,GAAG;IACvC,IAAM,IAAO,EAAI,UAAU,IAAI,GAAG,EAAI;IACtC,IAAI,MAAS,GAAG;IAChB,KAAQ,OAAO,aAAa,CAAI;GAClC;GACA,IAAM,IAAQ,EAAI,UAAU,IAAI,KAAK,EAAI,GAEnC,IACJ,KAAS,IAAI,EAAI,UAAU,IAAI,KAAK,EAAI,IAAI,OAAO,EAAI,aAAa,IAAI,KAAK,EAAI,CAAC;GACpF,KAAK,QAAQ,KAAK;IAAE;IAAM;IAAM;IAAO;GAAK,CAAC;EAC/C;EAGA,IAAM,IAAO,KAAK,QAAQ,MAAM,MAAM,EAAE,SAAS,CAAC;EAClD,AAAI,MAAM,KAAK,aAAa,KAAK,cAAc,EAAK,OAAO,EAAK,IAAI;EACpE,KAAK,IAAM,KAAK,KAAK,MAAM,CAAY,GAAG;GACxC,IAAM,IAAO,KAAK,aAAa,CAAC;GAChC,KAAK,IAAI,IAAI,GAAG,IAAI,GAAK,KAAK,KAAK,QAAQ,KAAK,EAAG,UAAU,IAAO,IAAI,GAAG,EAAI,CAAC;EAClF;CACF;CAEA,aAAqB,GAAmB;EAGtC,QAAQ,IAAI,KAAK,KAAK;CACxB;CAEA,MAAc,GAAyB;EACrC,IAAM,IAAgB,CAAC,GACnB,IAAI,GACJ,IAAQ;EACZ,OAAO,MAAM,KAAc,MAAM,KAAY,IAAI,KAAK,IAAI,UAAU,MAAU,MAE5E,AADA,EAAI,KAAK,CAAC,GACV,IAAI,KAAK,IAAI;EAEf,OAAO;CACT;CAEA,YAAoB,GAAuB;EACzC,IAAM,IAAM,KAAK,aAAa,CAAC,GACzB,IAAM,KAAK,IAAI,KAAK,IAAI,YAAY,IAAM,KAAK,UAAU,GACzD,IAAM,IAAI,WAAW,KAAK,UAAU;EAE1C,OADI,IAAM,KAAK,IAAI,cAAY,EAAI,IAAI,IAAI,WAAW,KAAK,KAAK,GAAK,IAAM,CAAG,CAAC,GACxE;CACT;CACA,cAAsB,GAAe,GAA0B;EAC7D,IAAM,IAAK,KAAK,MAAM,CAAK,GACrB,IAAM,IAAI,WAAW,EAAG,SAAS,KAAK,UAAU;EACtD,KAAK,IAAI,IAAI,GAAG,IAAI,EAAG,QAAQ,KAAK,EAAI,IAAI,KAAK,YAAY,EAAG,EAAE,GAAG,IAAI,KAAK,UAAU;EACxF,OAAO,EAAI,SAAS,GAAG,CAAI;CAC7B;CACA,eAAuB,GAAe,GAA0B;EAG9D,IAAM,IAAO,KAAK,KAAK,IAAO,KAAK,cAAc,GAC3C,IAAM,IAAI,WAAW,IAAO,KAAK,cAAc,GACjD,IAAI,GACJ,IAAI;EACR,OAAO,MAAM,KAAc,MAAM,KAAY,IAAI,IAAM;GACrD,IAAM,IAAM,IAAI,KAAK;GAGrB,AAFA,EAAI,IAAI,KAAK,WAAW,SAAS,GAAK,IAAM,KAAK,cAAc,GAAG,IAAI,KAAK,cAAc,GACzF,KACA,IAAI,KAAK,QAAQ;EACnB;EACA,OAAO,EAAI,SAAS,GAAG,CAAI;CAC7B;CAEA,OAAO,GAAyB;EAG9B,OAFI,EAAE,SAAS,IAAU,IAAI,WAAY,IACrC,EAAE,SAAS,KAAK,EAAE,QAAQ,KAAK,aAAmB,KAAK,cAAc,EAAE,OAAO,EAAE,IAAI,IACjF,KAAK,eAAe,EAAE,OAAO,EAAE,IAAI;CAC5C;AACF;AAKA,SAAS,EAAS,GAAmB;CAInC,OAHI,IAAI,KAAW,OAAO,aAAa,IAAI,EAAE,IACzC,IAAI,KAAW,OAAO,aAAa,IAAI,KAAK,EAAE,IAC9C,IAAI,KAAW,OAAO,aAAa,IAAI,KAAK,EAAE,IAC3C,MAAM,KAAK,MAAM;AAC1B;AACA,SAAS,EAAiB,GAAiD;CACzE,IAAI,IAAM,IACN,IAAU;CACd,KAAK,IAAI,IAAI,GAAG,IAAI,EAAI,QAAQ,KAAK;EACnC,IAAM,IAAK,EAAI,WAAW,CAAC;EAC3B,IAAI,KAAM,SAAU,IAAK,OAAQ;GAC/B,IAAM,IAAI,IAAK;GACf,KAAO,EAAS,IAAI,EAAI,IAAI,EAAU,KAAK,IAAK,EAAI;EACtD,OAAO,AAAI,KAAM,SAAU,IAAK,QAC9B,KAAO,EAAS,IAAK,KAAM,IAClB,MAAO,QAChB,IAAU,KAEV,KAAO,OAAO,aAAa,CAAE;CAEjC;CACA,OAAO;EAAE,MAAM;EAAK;CAAQ;AAC9B;AAQA,SAAS,EAAiB,GAAsE;CAC9F,IAAM,IAAI,IAAI,SAAS,EAAM,QAAQ,EAAM,YAAY,EAAM,UAAU,GAEjE,IAAa,EAAE,UAAU,IAAI,EAAI,GACjC,IAAW,EAAE,UAAU,IAAa,GAAG,EAAI,GAC3C,IAAuC,CAAC;CAC9C,KAAK,IAAI,IAAI,GAAG,IAAI,GAAU,KAAK;EACjC,IAAM,IAAM,EAAE,UAAU,IAAa,IAAI,IAAI,GAAG,EAAI,GAC9C,IAAM,EAAE,UAAU,IAAa,IAAI,IAAI,IAAI,GAAG,EAAI;EACxD,EAAK,KAAK;GAAE;GAAK,KAAK,IAAa;EAAI,CAAC;CAC1C;CAEA,IAAI,IAAW,MACT,IAAQ,EAAK,MAAM,MAAM,EAAE,QAAQ,CAAC;CAC1C,IAAI,KAAS,EAAE,UAAU,EAAM,KAAK,EAAI,MAAM,GAAG;EAC/C,IAAI,IAAK,EAAE,SAAS,EAAM,MAAM,GAAG,EAAI;EAEvC,AADI,IAAK,MAAG,KAAM,QAClB,IAAW;CACb;CACA,IAAM,IAAM,IAAI,YAAY,EAAQ,CAAQ,CAAC,GACvC,oBAAQ,IAAI,IAAqB;CACvC,KAAK,IAAM,EAAE,QAAK,YAAS,GAAM;EAC/B,IAAM,IAAO,EAAE,UAAU,GAAK,EAAI;EAClC,IAAI,MAAS,GAAG,EAAM,IAAI,GAAK;GAAE;GAAM,OAAO,EAAE,SAAS,IAAM,GAAG,EAAI;EAAE,CAAC;OACpE,IAAI,MAAS,GAAG,EAAM,IAAI,GAAK;GAAE;GAAM,OAAO,EAAE,SAAS,IAAM,GAAG,EAAI;EAAE,CAAC;OACzE,IAAI,MAAS,IAAI;GAEpB,IAAI,IAAM,EAAM,SAAS,IAAM,GAAG,IAAM,IAAI,EAAE,UAAU,IAAM,GAAG,EAAI,CAAC,GAChE,IAAM,EAAI,QAAQ,CAAC;GAEzB,AADI,KAAO,MAAG,IAAM,EAAI,SAAS,GAAG,CAAG,IACvC,EAAM,IAAI,GAAK;IAAE;IAAM,OAAO,EAAI,OAAO,CAAG;GAAE,CAAC;EACjD,OAAO,AAAI,MAAS,MAClB,EAAM,IAAI,GAAK;GACb;GACA,OAAO,EAAe,EAAE,UAAU,IAAM,GAAG,EAAI,GAAG,EAAE,UAAU,IAAM,GAAG,EAAI,CAAC;EAC9E,CAAC;CAEL;CACA,OAAO;EAAE;EAAU;CAAM;AAC3B;AAEA,SAAS,EAAa,GAAwC;CAC5D,IAAM,IAAiB,CAAC,GAClB,KAAQ,GAAa,GAAe,MAA+C;EACvF,IAAM,IAAI,EAAM,IAAI,CAAG;EACvB,IAAI,KAAK,MAAM;EACf,IAAM,IAAQ,IAAM,EAAI,EAAE,KAAK,IAAI,OAAO,EAAE,KAAK;EACjD,AAAI,KAAO,EAAI,KAAK;GAAE;GAAO;EAAM,CAAC;CACtC;CAcA,OAbA,EAAK,GAAG,MAAM,MAAM,GACpB,EAAK,GAAG,WAAW,MAAM,GACzB,EAAK,GAAG,MAAM,MAAM,GACpB,EAAK,GAAG,MAAM,MAAM,GACpB,EAAK,GAAG,OAAO,MAAM,GACrB,EAAK,GAAG,YAAY,MAAM,EAAe,OAAO,CAAC,CAAC,CAAC,GACnD,EAAK,GAAG,OAAO,MAAM,GACrB,EAAK,IAAI,QAAQ,MAAM,GACvB,EAAK,IAAI,QAAQ,MAAM,GACvB,EAAK,IAAI,QAAQ,MAAM,GACvB,EAAK,IAAI,SAAS,MAAM,EAAa,OAAO,CAAC,CAAC,CAAC,GAC/C,EAAK,IAAI,QAAQ,MAAM,EAAgB,OAAO,CAAC,CAAC,CAAC,GACjD,EAAK,IAAI,OAAO,MAAM,EAAe,OAAO,CAAC,CAAC,CAAC,GACxC;AACT;AAMA,SAAS,EACP,GACA,GACA,GAC0C;CAC1C,IAAM,IAAM,IAAI,SAAS,EAAK,QAAQ,EAAK,YAAY,EAAK,UAAU,GAChE,IAAQ,EAAI,UAAU,GAAG,EAAI,IAAK,EAAI,UAAU,GAAG,EAAI,KAAK,IAC5D,KAAY,IAAQ,eAAgB,GACtC,IAAK,IAAQ;CACjB,AAAI,MAAO,MAAG,IAAK,KAAc;CACjC,IAAM,IAAM,IAAI,YAAY,EAAQ,CAAE,CAAC,GACjC,IAAoB,CAAC,EAAE,GACzB,IAAM,GACJ,IAAQ,KAAK,MAAM,EAAK,SAAS,CAAC;CACxC,KAAK,IAAI,IAAI,GAAG,IAAI,GAAO,KAAK;EAC9B,IAAM,IAAM,EAAI,UAAU,IAAI,GAAG,EAAI;EACrC,IAAI,MAAQ,GAAG;GACb,EAAQ,KAAK,EAAE;GACf;EACF;EAEA,AADA,EAAQ,KAAK,EAAI,OAAO,EAAK,SAAS,GAAK,IAAM,CAAG,CAAC,CAAC,GACtD,KAAO;CACT;CACA,OAAO;EAAE;EAAS;CAAS;AAC7B;AAIA,SAAS,EACP,GACA,GACe;CACf,IAAM,IAAU,EAAG,WAAW,IAAI,GAC5B,IAAK,IAAI,SAAS,EAAI,QAAQ,EAAI,YAAY,EAAI,UAAU,GAC5D,IAAO,KAAK,MAAM,EAAI,UAAU,IAAU,EAAE,GAC5C,KAAO,MAAwB;EACnC,IAAM,IAAI,IAAM,GACZ,IAAI,EAAG,UAAU,GAAG,EAAI;EAE5B,OADI,MAAY,MAAG,KAAK,EAAG,SAAS,IAAI,CAAC,KAAK,KACvC;CACT,GACM,IAAqB,CAAC;CAC5B,KAAK,IAAI,IAAI,GAAG,IAAI,GAAM,KAAK;EAC7B,IAAM,IAAO,EAAG,QAAQ,EAAI,CAAC,MAAM,IAC7B,IAAQ,EAAG,QAAQ,EAAI,IAAO,CAAC,MAAM;EAC3C,AAAI,KAAM,EAAI,KAAK;GAAE;GAAM;EAAM,CAAC;CACpC;CACA,OAAO;AACT;AAGA,SAAgB,EAAS,GAA2B;CAClD,IAAM,IAAM,IAAI,EAAI,CAAG,GACjB,IAAU,EAAI,QAAQ,KAAK,OAAO;EAAE;EAAG,GAAG,EAAiB,EAAE,IAAI;CAAE,EAAE,GACrE,IAAkB,CAAC,GAEnB,IAAS,EACZ,QAAQ,MAAM,EAAE,OAAO,CAAC,CACxB,KAAK,MAAM,EAAE,IAAI,CAAC,CAClB,MAAM,GAAG,MAAM,EAAE,cAAc,CAAC,CAAC,GAGhC,IAAqB,CAAC,GACtB,IAAa,GACX,IAAM,EAAQ,MAAM,MAAM,EAAE,SAAS,qBAAqB;CAChE,IAAI,GACF,IAAI;EACF,IAAM,IAAK,EAAiB,EAAI,OAAO,EAAI,CAAC,CAAC;EAE7C,AADA,IAAa,EAAG,UAChB,IAAU,EAAa,EAAG,KAAK;CACjC,SAAS,GAAG;EACV,EAAM,KAAK,6BAA6B,EAAO,CAAC,CAAC;CACnD;CAIF,IAAI,IAA4B,CAAC,GAC3B,IAAK,EAAQ,MAAM,MAAM,EAAE,SAAS,aAAa,GACjD,IAAK,EAAQ,MAAM,MAAM,EAAE,SAAS,aAAa,GACjD,IAAK,EAAQ,MAAM,MAAM,EAAE,SAAS,UAAU;CACpD,IAAI,KAAM,KAAM,GACd,IAAI;EACF,IAAM,IAAK,EAAiB,EAAI,OAAO,EAAG,CAAC,GAAG,EAAI,OAAO,EAAG,CAAC,GAAG,CAAU;EAC1E,IAAa,EAAoB,EAAI,OAAO,EAAG,CAAC,GAAG,CAAE;CACvD,SAAS,GAAG;EACV,EAAM,KAAK,oBAAoB,EAAO,CAAC,CAAC;CAC1C;CAGF,IAAM,IAAM,IAAI,IAAI,EAAW,KAAK,MAAM,CAAC,EAAE,MAAM,EAAE,KAAK,CAAC,CAAC,GACtD,IAAS,EAAI,IAAI,iBAAiB,GAClC,IAAgB;EACpB,aAAa,EAAI,IAAI,aAAa;EAClC,gBAAgB,EAAI,IAAI,gBAAgB;EACxC,cAAc,EAAI,IAAI,cAAc;EACpC,aAAa,EAAI,IAAI,aAAa;EAClC,aAAa,EAAI,IAAI,aAAa;EAClC,UAAU,IAAS,EAAe,CAAM,IAAI,KAAA;EAC5C;EACA;EACA;EACA;CACF;CAIA,OAHI,CAAC,EAAW,UAAU,CAAC,EAAQ,UACjC,EAAM,KAAK,wCAAwC,GAE9C;AACT;AAGA,SAAS,EAAO,GAAoB;CAClC,OAAO,aAAa,QAAQ,EAAE,UAAU,OAAO,CAAC;AAClD;AAEA,SAAS,EAAQ,GAAoB;CACnC,QAAQ,GAAR;EACE,KAAK,OACH,OAAO;EACT,KAAK,MACH,OAAO;EACT,KAAK,MACH,OAAO;EACT,KAAK,KACH,OAAO;EACT,KAAK,KACH,OAAO;EACT,KAAK,KACH,OAAO;EACT,KAAK,KACH,OAAO;EACT,KAAK,MACH,OAAO;EACT,KAAK,MACH,OAAO;EACT,KAAK,MACH,OAAO;EACT,KAAK,MACH,OAAO;EACT,KAAK,MACH,OAAO;EACT,KAAK,MACH,OAAO;EACT,KAAK,MACH,OAAO;EACT,KAAK,MACH,OAAO;EACT,SACE,OAAO;CACX;AACF;AAEA,SAAS,EAAe,GAAY,GAAoB;CACtD,IAAM,IAAK,IAAK,aAAa;CAC7B,IAAI,CAAC,GAAI,OAAO;CAChB,IAAM,IAAK,IAAK,MAAQ,eAClB,IAAI,IAAI,KAAK,CAAE;CACrB,IAAI,OAAO,MAAM,EAAE,QAAQ,CAAC,GAAG,OAAO;CACtC,IAAM,KAAK,MAAsB,OAAO,CAAC,CAAC,CAAC,SAAS,GAAG,GAAG;CAC1D,OAAO,GAAG,EAAE,eAAe,EAAE,GAAG,EAAE,EAAE,YAAY,IAAI,CAAC,EAAE,GAAG,EAAE,EAAE,WAAW,CAAC,EAAE,GAAG,EAAE,EAAE,YAAY,CAAC,EAAE,GAAG,EAAE,EAAE,cAAc,CAAC,EAAE;AAC5H;AAGA,SAAS,EAAe,GAAmB;CACzC,IAAM,CAAC,GAAU,KAAS,EAAE,MAAM,GAAG,GAC/B,KAAS,KAAS,GAAA,CACrB,MAAM,GAAG,CAAC,CACV,KAAK,MAAM,EAAE,KAAK,CAAC,CAAC,CACpB,OAAO,OAAO,CAAC,CACf,KAAK,MAAO,EAAe,CAAE,CAAC,GAC3B,IAAkB,CAAC;CAGzB,OAFI,KAAY,EAAS,KAAK,KAAG,EAAM,KAAK,EAAS,KAAK,CAAC,GACvD,EAAM,UAAQ,EAAM,KAAK,EAAM,KAAK,IAAI,CAAC,GACtC,EAAM,KAAK,KAAK,KAAK;AAC9B;AAGA,SAAS,EAAa,GAAmB;CAEvC,OADK,IACE,GAAG,KAAK,MAAM,IAAI,GAAG,EAAE,GAAG,OAAO,IAAI,GAAG,CAAC,CAAC,SAAS,GAAG,GAAG,MADjD;AAEjB;AAGA,SAAS,EAAgB,GAAmB;CAC1C,IAAM,IAAkB,CAAC;CAKzB,OAJA,EAAM,KAAK,IAAI,IAAM,iBAAiB,KAAK,GACvC,IAAI,KAAK,EAAM,KAAK,MAAM,GAC1B,IAAI,KAAK,EAAM,KAAK,MAAM,GAC1B,IAAI,KAAK,EAAM,KAAK,MAAM,GACvB,EAAM,KAAK,KAAK;AACzB;AAEA,SAAS,EAAe,GAAmB;CACzC,QAAQ,GAAR;EACE,KAAK,GACH,OAAO;EACT,KAAK,GACH,OAAO;EACT,KAAK,GACH,OAAO;EACT,KAAK,GACH,OAAO;EACT,SACE,OAAO,OAAO,CAAC;CACnB;AACF;AAGA,IAAM,IAAqC;CACzC,GAAG;CACH,MAAM;CACN,MAAM;CACN,MAAM;CACN,MAAM;CACN,MAAM;CACN,MAAM;CACN,MAAM;CACN,MAAM;CACN,MAAM;CACN,MAAM;CACN,MAAM;CACN,MAAM;CACN,MAAM;CACN,MAAM;CACN,MAAM;CACN,MAAM;CACN,MAAM;CACN,MAAM;CACN,MAAM;CACN,MAAM;CACN,MAAM;CACN,MAAM;CACN,MAAM;CACN,MAAM;CACN,MAAM;CACN,MAAM;CACN,MAAM;CACN,MAAM;CACN,MAAM;CACN,MAAM;CACN,MAAM;CACN,MAAM;CACN,MAAM;CACN,MAAM;AACR;AACA,SAAS,EAAe,GAAoB;CAE1C,IAAM,IAAO,EADH,OAAO,CACO;CACxB,OAAO,IAAO,GAAG,EAAK,IAAI,EAAG,KAAK;AACpC"}
|
|
1
|
+
{"version":3,"file":"msi.js","names":[],"sources":["../../src/lib/msi.ts"],"sourcesContent":["/**\n * Minimal in-browser MSI (Windows Installer) reader — no dependency, no WASM.\n *\n * An `.msi` is NOT a PE executable (that's `.exe`/`.dll` — see lib/binobj.ts), and NOT a zip (that's\n * `.appx`/`.msix` — see lib/appx.ts). It's an OLE2 **Compound File** (a.k.a. structured storage / CFBF):\n * a mini-filesystem of \"streams\" inside one file (the same container legacy `.doc`/`.xls`/`.msg` use). The\n * install metadata we surface lives in two of those streams:\n * - the standard **SummaryInformation** property set (`\\x05SummaryInformation`): title / subject / author /\n * comments, the target platform+language template, the package (revision) code, the authoring tool, dates.\n * - the **Property table** (ProductName / ProductVersion / ProductCode / UpgradeCode / Manufacturer / …),\n * spread across the MSI string pool (`_StringData` + `_StringPool`) and the `Property` table stream.\n *\n * We parse only what a \"what is this installer?\" view needs — never the embedded cabinet / payload files.\n * The logic is validated byte-for-byte against real installers using the Windows `WindowsInstaller.Installer`\n * COM automation as an oracle (the Property table + SummaryInformation match it exactly).\n *\n * Used by MsiViewer (the side viewer's MSI mode): the bytes come from the same download endpoint as any\n * other file, are parsed entirely client-side, and never leave the browser.\n */\n\nimport { CompoundFile, CompoundFileError } from '@/lib/cfb'\n\nexport interface MsiMeta {\n label: string\n value: string\n}\nexport interface MsiProperty {\n name: string\n value: string\n}\n\nexport interface MsiInfo {\n /** Resolved product identity from the Property table (any field may be absent if the table is unreadable). */\n productName?: string\n productVersion?: string\n manufacturer?: string\n productCode?: string\n upgradeCode?: string\n /** Install language: a human label when the LCID is recognized (with the raw id), else the raw id. */\n language?: string\n /** Full Property table (name → value), in stored order. Empty if the table couldn't be decoded. */\n properties: MsiProperty[]\n /** SummaryInformation rows, pre-labeled (Chinese), in display order. */\n summary: MsiMeta[]\n /** Decoded names of the database tables present — a quick \"shape of the installer\" cue. */\n tables: string[]\n /** Non-fatal notes (e.g. the Property table was unreadable but SummaryInformation succeeded). */\n notes: string[]\n}\n\n/** Thrown when the bytes aren't an OLE compound file at all (so the viewer can show a clear message). */\nexport class NotMsiError extends Error {\n constructor(message: string) {\n super(message)\n this.name = 'NotMsiError'\n }\n}\n\n// ── MSI stream-name (un)mangling ────────────────────────────────────────────────────────────────────\n// MSI encodes table/stream names into a private Unicode range using a 64-char alphabet (Wine's\n// decode_streamname). A leading 0x4840 wide-char marks \"this is a database table\".\nfunction mime2utf(x: number): string {\n if (x < 10) return String.fromCharCode(x + 48) // 0-9\n if (x < 36) return String.fromCharCode(x - 10 + 65) // A-Z\n if (x < 62) return String.fromCharCode(x - 36 + 97) // a-z\n return x === 62 ? '.' : '_'\n}\nfunction decodeStreamName(raw: string): { name: string; isTable: boolean } {\n let out = ''\n let isTable = false\n for (let i = 0; i < raw.length; i++) {\n const ch = raw.charCodeAt(i)\n if (ch >= 0x3800 && ch < 0x4800) {\n const v = ch - 0x3800 // two 6-bit characters packed into one wide-char\n out += mime2utf(v & 0x3f) + mime2utf((v >> 6) & 0x3f)\n } else if (ch >= 0x4800 && ch < 0x4840) {\n out += mime2utf(ch - 0x4800) // a single character\n } else if (ch === 0x4840) {\n isTable = true // table marker (prefix); contributes nothing to the name\n } else {\n out += String.fromCharCode(ch) // literal (ASCII names, the \\x05 property-set prefix, …)\n }\n }\n return { name: out, isTable }\n}\n\n// ── SummaryInformation (OLE property set, MS-OLEPS) ─────────────────────────────────────────────────\ninterface PropVal {\n type: number\n value: string | number\n}\n\nfunction parsePropertySet(bytes: Uint8Array): { codepage: number; props: Map<number, PropVal> } {\n const v = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength)\n // 28-byte set header + one 20-byte (FMTID + offset) section locator; the section offset sits at byte 44.\n const sectionOff = v.getUint32(44, true)\n const numProps = v.getUint32(sectionOff + 4, true)\n const rows: { pid: number; abs: number }[] = []\n for (let i = 0; i < numProps; i++) {\n const pid = v.getUint32(sectionOff + 8 + i * 8, true)\n const off = v.getUint32(sectionOff + 8 + i * 8 + 4, true)\n rows.push({ pid, abs: sectionOff + off })\n }\n // The codepage (PID 1) governs how the LPSTR string properties decode — read it first.\n let codepage = 1252\n const cpRow = rows.find((r) => r.pid === 1)\n if (cpRow && v.getUint32(cpRow.abs, true) === 2) {\n let cp = v.getInt16(cpRow.abs + 4, true)\n if (cp < 0) cp += 65536 // 65001 (UTF-8) overflows a signed 16-bit read\n codepage = cp\n }\n const dec = new TextDecoder(cpLabel(codepage))\n const props = new Map<number, PropVal>()\n for (const { pid, abs } of rows) {\n const type = v.getUint32(abs, true)\n if (type === 2) props.set(pid, { type, value: v.getInt16(abs + 4, true) })\n else if (type === 3) props.set(pid, { type, value: v.getInt32(abs + 4, true) })\n else if (type === 30) {\n // LPSTR: codepage bytes, NUL-terminated — cut at the first NUL (avoids a control-char regex).\n let raw = bytes.subarray(abs + 8, abs + 8 + v.getUint32(abs + 4, true))\n const nul = raw.indexOf(0)\n if (nul >= 0) raw = raw.subarray(0, nul)\n props.set(pid, { type, value: dec.decode(raw) })\n } else if (type === 64) {\n props.set(pid, {\n type,\n value: formatFiletime(v.getUint32(abs + 4, true), v.getUint32(abs + 8, true)),\n })\n }\n }\n return { codepage, props }\n}\n\nfunction buildSummary(props: Map<number, PropVal>): MsiMeta[] {\n const out: MsiMeta[] = []\n const push = (pid: number, label: string, fmt?: (v: string | number) => string): void => {\n const p = props.get(pid)\n if (p == null) return\n const value = fmt ? fmt(p.value) : String(p.value)\n if (value) out.push({ label, value })\n }\n push(3, '主题', String) // PID_SUBJECT — usually the product/package name\n push(4, '作者 / 厂商', String) // PID_AUTHOR\n push(6, '备注', String) // PID_COMMENTS — description\n push(2, '标题', String) // PID_TITLE\n push(5, '关键字', String) // PID_KEYWORDS\n push(7, '平台 / 语言', (v) => formatTemplate(String(v))) // PID_TEMPLATE\n push(9, '包代码', String) // PID_REVNUMBER — the package (revision) code GUID\n push(18, '制作工具', String) // PID_APPNAME — authoring application\n push(12, '创建时间', String) // PID_CREATE_DTM\n push(13, '上次保存', String) // PID_LASTSAVE_DTM\n push(14, '架构版本', (v) => formatSchema(Number(v))) // PID_PAGECOUNT — min installer version\n push(15, '源类型', (v) => formatWordCount(Number(v))) // PID_WORDCOUNT — source flags\n push(19, '安全', (v) => formatSecurity(Number(v))) // PID_SECURITY\n return out\n}\n\n// ── MSI string pool + Property table ────────────────────────────────────────────────────────────────\n// `_StringPool` is an array of 4-byte slots: [length: u16][refcount: u16] (length FIRST — validated against\n// the COM oracle). Slot 0 carries the codepage (+ a long-string-refs flag in the high bit). `_StringData` is\n// every string concatenated in index order; string i lives at the running offset, `length` bytes long.\nfunction buildStringTable(\n pool: Uint8Array,\n data: Uint8Array,\n fallbackCp: number,\n): { strings: string[]; longRefs: boolean } {\n const pdv = new DataView(pool.buffer, pool.byteOffset, pool.byteLength)\n const cpRaw = pdv.getUint16(0, true) | (pdv.getUint16(2, true) << 16)\n const longRefs = (cpRaw & 0x80000000) !== 0 // ⇒ 3-byte string references in table data\n let cp = cpRaw & 0x7fffffff\n if (cp === 0) cp = fallbackCp || 1252 // codepage 0 = system ANSI; fall back to the SummaryInfo codepage\n const dec = new TextDecoder(cpLabel(cp))\n const strings: string[] = ['']\n let off = 0\n const count = Math.floor(pool.length / 4)\n for (let i = 1; i < count; i++) {\n const len = pdv.getUint16(i * 4, true)\n if (len === 0) {\n strings.push('')\n continue\n }\n strings.push(dec.decode(data.subarray(off, off + len)))\n off += len\n }\n return { strings, longRefs }\n}\n\n// The Property table is two string columns (Property name, Value) stored column-major: all the name refs,\n// then all the value refs. Each ref is a 2-byte (or 3-byte, with long string refs) index into the pool.\nfunction decodePropertyTable(\n tbl: Uint8Array,\n st: { strings: string[]; longRefs: boolean },\n): MsiProperty[] {\n const refSize = st.longRefs ? 3 : 2\n const dv = new DataView(tbl.buffer, tbl.byteOffset, tbl.byteLength)\n const rows = Math.floor(tbl.length / (refSize * 2))\n const ref = (idx: number): number => {\n const o = idx * refSize\n let v = dv.getUint16(o, true)\n if (refSize === 3) v += dv.getUint8(o + 2) << 16\n return v\n }\n const out: MsiProperty[] = []\n for (let r = 0; r < rows; r++) {\n const name = st.strings[ref(r)] ?? ''\n const value = st.strings[ref(rows + r)] ?? ''\n if (name) out.push({ name, value })\n }\n return out\n}\n\n// ── orchestrator ────────────────────────────────────────────────────────────────────────────────────\nexport function parseMsi(buf: ArrayBuffer): MsiInfo {\n let cfb: CompoundFile\n try {\n cfb = new CompoundFile(buf)\n } catch (error) {\n if (error instanceof CompoundFileError) throw new NotMsiError(error.message)\n throw error\n }\n const decoded = cfb.entries.map((e) => ({ e, ...decodeStreamName(e.name) }))\n const notes: string[] = []\n\n const tables = decoded\n .filter((d) => d.isTable)\n .map((d) => d.name)\n .sort((a, b) => a.localeCompare(b))\n\n // SummaryInformation (standard, near-universal in MSIs).\n let summary: MsiMeta[] = []\n let siCodepage = 0\n const siD = decoded.find((d) => d.name === '\u0005SummaryInformation')\n if (siD) {\n try {\n const ps = parsePropertySet(cfb.stream(siD.e))\n siCodepage = ps.codepage\n summary = buildSummary(ps.props)\n } catch (e) {\n notes.push('SummaryInformation 解析失败:' + errMsg(e))\n }\n }\n\n // Property table (best-effort: a malformed string pool never blanks the summary above).\n let properties: MsiProperty[] = []\n const sd = decoded.find((d) => d.name === '_StringData')\n const sp = decoded.find((d) => d.name === '_StringPool')\n const pr = decoded.find((d) => d.name === 'Property')\n if (sd && sp && pr) {\n try {\n const st = buildStringTable(cfb.stream(sp.e), cfb.stream(sd.e), siCodepage)\n properties = decodePropertyTable(cfb.stream(pr.e), st)\n } catch (e) {\n notes.push('Property 表解析失败:' + errMsg(e))\n }\n }\n\n const map = new Map(properties.map((p) => [p.name, p.value]))\n const langId = map.get('ProductLanguage')\n const info: MsiInfo = {\n productName: map.get('ProductName'),\n productVersion: map.get('ProductVersion'),\n manufacturer: map.get('Manufacturer'),\n productCode: map.get('ProductCode'),\n upgradeCode: map.get('UpgradeCode'),\n language: langId ? formatLanguage(langId) : undefined,\n properties,\n summary,\n tables,\n notes,\n }\n if (!properties.length && !summary.length) {\n notes.push('未找到 MSI 摘要信息或属性表(可能是别的复合文件,而非 MSI 数据库)')\n }\n return info\n}\n\n// ── formatting helpers ──────────────────────────────────────────────────────────────────────────────\nfunction errMsg(e: unknown): string {\n return e instanceof Error ? e.message : String(e)\n}\n\nfunction cpLabel(cp: number): string {\n switch (cp) {\n case 65001:\n return 'utf-8'\n case 1200:\n return 'utf-16le'\n case 1201:\n return 'utf-16be'\n case 932:\n return 'shift_jis'\n case 936:\n return 'gbk'\n case 949:\n return 'euc-kr'\n case 950:\n return 'big5'\n case 1250:\n return 'windows-1250'\n case 1251:\n return 'windows-1251'\n case 1253:\n return 'windows-1253'\n case 1254:\n return 'windows-1254'\n case 1255:\n return 'windows-1255'\n case 1256:\n return 'windows-1256'\n case 1257:\n return 'windows-1257'\n case 1258:\n return 'windows-1258'\n default:\n return 'windows-1252' // 1252 and the system-ANSI (0) fallback\n }\n}\n\nfunction formatFiletime(lo: number, hi: number): string {\n const ft = hi * 4294967296 + lo\n if (!ft) return ''\n const ms = ft / 10000 - 11644473600000\n const d = new Date(ms)\n if (Number.isNaN(d.getTime())) return ''\n const p = (n: number): string => String(n).padStart(2, '0')\n return `${d.getUTCFullYear()}-${p(d.getUTCMonth() + 1)}-${p(d.getUTCDate())} ${p(d.getUTCHours())}:${p(d.getUTCMinutes())} UTC`\n}\n\n// MSI \"Template\" = \"<platform>;<langid>[,<langid>…]\" e.g. \"x64;1033\". Surface the platform + language names.\nfunction formatTemplate(t: string): string {\n const [platform, langs] = t.split(';')\n const names = (langs ?? '')\n .split(',')\n .map((s) => s.trim())\n .filter(Boolean)\n .map((id) => formatLanguage(id))\n const parts: string[] = []\n if (platform && platform.trim()) parts.push(platform.trim())\n if (names.length) parts.push(names.join(', '))\n return parts.join(' · ') || t\n}\n\n// PID_PAGECOUNT for an install package = the minimum Windows Installer version (e.g. 200 ⇒ 2.00, 500 ⇒ 5.00).\nfunction formatSchema(n: number): string {\n if (!n) return ''\n return `${Math.floor(n / 100)}.${String(n % 100).padStart(2, '0')}`\n}\n\n// PID_WORDCOUNT = source flags bitfield.\nfunction formatWordCount(n: number): string {\n const flags: string[] = []\n flags.push(n & 0x2 ? '压缩 (cabinet)' : '未压缩')\n if (n & 0x1) flags.push('短文件名')\n if (n & 0x4) flags.push('管理映像')\n if (n & 0x8) flags.push('从源运行')\n return flags.join(' · ')\n}\n\nfunction formatSecurity(n: number): string {\n switch (n) {\n case 0:\n return '无限制'\n case 1:\n return '建议只读'\n case 2:\n return '强制只读'\n case 4:\n return '已锁定'\n default:\n return String(n)\n }\n}\n\n// Common Windows LCIDs → Chinese language names (best-effort; unknown ids fall back to the raw number).\nconst LCID_NAMES: Record<number, string> = {\n 0: '语言中性',\n 1025: '阿拉伯语',\n 1028: '中文(繁体)',\n 1029: '捷克语',\n 1030: '丹麦语',\n 1031: '德语',\n 1032: '希腊语',\n 1033: '英语(美国)',\n 1034: '西班牙语',\n 1035: '芬兰语',\n 1036: '法语',\n 1037: '希伯来语',\n 1038: '匈牙利语',\n 1040: '意大利语',\n 1041: '日语',\n 1042: '韩语',\n 1043: '荷兰语',\n 1044: '挪威语',\n 1045: '波兰语',\n 1046: '葡萄牙语(巴西)',\n 1049: '俄语',\n 1051: '斯洛伐克语',\n 1053: '瑞典语',\n 1054: '泰语',\n 1055: '土耳其语',\n 1057: '印尼语',\n 1058: '乌克兰语',\n 1060: '斯洛文尼亚语',\n 1066: '越南语',\n 2052: '中文(简体)',\n 2057: '英语(英国)',\n 2070: '葡萄牙语',\n 3076: '中文(香港)',\n 3082: '西班牙语(西班牙)',\n 4100: '中文(新加坡)',\n}\nfunction formatLanguage(id: string): string {\n const n = Number(id)\n const name = LCID_NAMES[n]\n return name ? `${name} (${id})` : id\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export declare class OlePackageError extends Error {
|
|
2
|
+
constructor(message: string);
|
|
3
|
+
}
|
|
4
|
+
/**
|
|
5
|
+
* Extract the OOXML ZIP stored in an Office OLE object's `Package` stream.
|
|
6
|
+
* Modern Word/WPS commonly wrap embedded PPTX/DOCX/XLSX this way instead of linking the package directly.
|
|
7
|
+
*/
|
|
8
|
+
export declare function extractOlePackage(oleBytes: Uint8Array, maxPackageBytes?: number): Uint8Array;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { CompoundFile as e, CompoundFileError as t } from "./cfb.js";
|
|
2
|
+
//#region src/lib/olePackage.ts
|
|
3
|
+
var n = [
|
|
4
|
+
80,
|
|
5
|
+
75,
|
|
6
|
+
3,
|
|
7
|
+
4
|
|
8
|
+
], r = 100 * 1024 * 1024, i = class extends Error {
|
|
9
|
+
constructor(e) {
|
|
10
|
+
super(e), this.name = "OlePackageError";
|
|
11
|
+
}
|
|
12
|
+
};
|
|
13
|
+
function a(e) {
|
|
14
|
+
let t = new ArrayBuffer(e.byteLength);
|
|
15
|
+
return new Uint8Array(t).set(e), t;
|
|
16
|
+
}
|
|
17
|
+
function o(e) {
|
|
18
|
+
return n.every((t, n) => e[n] === t);
|
|
19
|
+
}
|
|
20
|
+
function s(n, s = r) {
|
|
21
|
+
let c;
|
|
22
|
+
try {
|
|
23
|
+
c = new e(a(n));
|
|
24
|
+
} catch (e) {
|
|
25
|
+
throw e instanceof t ? new i(e.message) : e;
|
|
26
|
+
}
|
|
27
|
+
let l = c.findStream("Package");
|
|
28
|
+
if (!l) throw new i("OLE 对象中没有 Package stream");
|
|
29
|
+
if (l.size > s) throw new i(`OLE Package 超过 ${s / 1024 / 1024} MiB,已拒绝展开`);
|
|
30
|
+
let u = c.stream(l);
|
|
31
|
+
if (!o(u)) throw new i("OLE Package 不是有效的 OOXML ZIP");
|
|
32
|
+
return u.slice();
|
|
33
|
+
}
|
|
34
|
+
//#endregion
|
|
35
|
+
export { s as extractOlePackage };
|
|
36
|
+
|
|
37
|
+
//# sourceMappingURL=olePackage.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"olePackage.js","names":[],"sources":["../../src/lib/olePackage.ts"],"sourcesContent":["import { CompoundFile, CompoundFileError } from '@/lib/cfb'\n\nconst ZIP_LOCAL_FILE_SIGNATURE = [0x50, 0x4b, 0x03, 0x04]\nconst DEFAULT_MAX_PACKAGE_BYTES = 100 * 1024 * 1024\n\nexport class OlePackageError extends Error {\n constructor(message: string) {\n super(message)\n this.name = 'OlePackageError'\n }\n}\n\nfunction exactArrayBuffer(bytes: Uint8Array): ArrayBuffer {\n const result = new ArrayBuffer(bytes.byteLength)\n new Uint8Array(result).set(bytes)\n return result\n}\n\nfunction startsWithZipSignature(bytes: Uint8Array): boolean {\n return ZIP_LOCAL_FILE_SIGNATURE.every((value, index) => bytes[index] === value)\n}\n\n/**\n * Extract the OOXML ZIP stored in an Office OLE object's `Package` stream.\n * Modern Word/WPS commonly wrap embedded PPTX/DOCX/XLSX this way instead of linking the package directly.\n */\nexport function extractOlePackage(\n oleBytes: Uint8Array,\n maxPackageBytes = DEFAULT_MAX_PACKAGE_BYTES,\n): Uint8Array {\n let compound: CompoundFile\n try {\n compound = new CompoundFile(exactArrayBuffer(oleBytes))\n } catch (error) {\n if (error instanceof CompoundFileError) throw new OlePackageError(error.message)\n throw error\n }\n const entry = compound.findStream('Package')\n if (!entry) throw new OlePackageError('OLE 对象中没有 Package stream')\n if (entry.size > maxPackageBytes) {\n throw new OlePackageError(`OLE Package 超过 ${maxPackageBytes / 1024 / 1024} MiB,已拒绝展开`)\n }\n const bytes = compound.stream(entry)\n if (!startsWithZipSignature(bytes)) throw new OlePackageError('OLE Package 不是有效的 OOXML ZIP')\n return bytes.slice()\n}\n"],"mappings":";;AAEA,IAAM,IAA2B;CAAC;CAAM;CAAM;CAAM;AAAI,GAClD,IAA4B,MAAM,OAAO,MAElC,IAAb,cAAqC,MAAM;CACzC,YAAY,GAAiB;EAE3B,AADA,MAAM,CAAO,GACb,KAAK,OAAO;CACd;AACF;AAEA,SAAS,EAAiB,GAAgC;CACxD,IAAM,IAAS,IAAI,YAAY,EAAM,UAAU;CAE/C,OADA,IAAI,WAAW,CAAM,CAAC,CAAC,IAAI,CAAK,GACzB;AACT;AAEA,SAAS,EAAuB,GAA4B;CAC1D,OAAO,EAAyB,OAAO,GAAO,MAAU,EAAM,OAAW,CAAK;AAChF;AAMA,SAAgB,EACd,GACA,IAAkB,GACN;CACZ,IAAI;CACJ,IAAI;EACF,IAAW,IAAI,EAAa,EAAiB,CAAQ,CAAC;CACxD,SAAS,GAAO;EAEd,MADI,aAAiB,IAAyB,IAAI,EAAgB,EAAM,OAAO,IACzE;CACR;CACA,IAAM,IAAQ,EAAS,WAAW,SAAS;CAC3C,IAAI,CAAC,GAAO,MAAM,IAAI,EAAgB,0BAA0B;CAChE,IAAI,EAAM,OAAO,GACf,MAAM,IAAI,EAAgB,kBAAkB,IAAkB,OAAO,KAAK,WAAW;CAEvF,IAAM,IAAQ,EAAS,OAAO,CAAK;CACnC,IAAI,CAAC,EAAuB,CAAK,GAAG,MAAM,IAAI,EAAgB,6BAA6B;CAC3F,OAAO,EAAM,MAAM;AACrB"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export interface PptxFirstSlideController {
|
|
2
|
+
dispose: () => void;
|
|
3
|
+
}
|
|
4
|
+
interface MountPptxFirstSlideOptions {
|
|
5
|
+
container: HTMLElement;
|
|
6
|
+
bytes: Uint8Array;
|
|
7
|
+
signal?: AbortSignal;
|
|
8
|
+
}
|
|
9
|
+
/** Render only the first slide of an extracted PPTX into an existing inline document host. */
|
|
10
|
+
export declare function mountPptxFirstSlide(options: MountPptxFirstSlideOptions): Promise<PptxFirstSlideController>;
|
|
11
|
+
export {};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import e from "pdfjs-dist/build/pdf.worker.min.mjs?url";
|
|
2
|
+
import { PptxViewer as t, RECOMMENDED_ZIP_LIMITS as n } from "@aiden0z/pptx-renderer";
|
|
3
|
+
import r from "pdfjs-dist/build/pdf.min.mjs?url";
|
|
4
|
+
//#region src/lib/pptxFirstSlide.ts
|
|
5
|
+
async function i(i) {
|
|
6
|
+
let a = new t(i.container, {
|
|
7
|
+
fitMode: "contain",
|
|
8
|
+
zipLimits: n,
|
|
9
|
+
lazyMedia: !0,
|
|
10
|
+
lazySlides: !0,
|
|
11
|
+
pdfjs: {
|
|
12
|
+
moduleUrl: r,
|
|
13
|
+
workerUrl: e
|
|
14
|
+
}
|
|
15
|
+
}), o = !1;
|
|
16
|
+
try {
|
|
17
|
+
if (await a.open(i.bytes, {
|
|
18
|
+
renderMode: "slide",
|
|
19
|
+
lazyMedia: !0,
|
|
20
|
+
lazySlides: !0,
|
|
21
|
+
signal: i.signal
|
|
22
|
+
}), i.signal?.aborted) throw Error("PPTX 首屏渲染已取消");
|
|
23
|
+
if (a.slideCount < 1) throw Error("PPTX 中没有可渲染的幻灯片");
|
|
24
|
+
} catch (e) {
|
|
25
|
+
throw a.destroy(), i.container.replaceChildren(), e;
|
|
26
|
+
}
|
|
27
|
+
return { dispose: () => {
|
|
28
|
+
o || (o = !0, a.destroy(), i.container.replaceChildren());
|
|
29
|
+
} };
|
|
30
|
+
}
|
|
31
|
+
//#endregion
|
|
32
|
+
export { i as mountPptxFirstSlide };
|
|
33
|
+
|
|
34
|
+
//# sourceMappingURL=pptxFirstSlide.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pptxFirstSlide.js","names":[],"sources":["../../src/lib/pptxFirstSlide.ts"],"sourcesContent":["import { PptxViewer, RECOMMENDED_ZIP_LIMITS } from '@aiden0z/pptx-renderer'\nimport pdfModuleUrl from 'pdfjs-dist/build/pdf.min.mjs?url'\nimport pdfWorkerUrl from 'pdfjs-dist/build/pdf.worker.min.mjs?url'\n\nexport interface PptxFirstSlideController {\n dispose: () => void\n}\n\ninterface MountPptxFirstSlideOptions {\n container: HTMLElement\n bytes: Uint8Array\n signal?: AbortSignal\n}\n\n/** Render only the first slide of an extracted PPTX into an existing inline document host. */\nexport async function mountPptxFirstSlide(\n options: MountPptxFirstSlideOptions,\n): Promise<PptxFirstSlideController> {\n const viewer = new PptxViewer(options.container, {\n fitMode: 'contain',\n zipLimits: RECOMMENDED_ZIP_LIMITS,\n lazyMedia: true,\n lazySlides: true,\n pdfjs: { moduleUrl: pdfModuleUrl, workerUrl: pdfWorkerUrl },\n })\n let disposed = false\n\n try {\n await viewer.open(options.bytes, {\n renderMode: 'slide',\n lazyMedia: true,\n lazySlides: true,\n signal: options.signal,\n })\n if (options.signal?.aborted) throw new Error('PPTX 首屏渲染已取消')\n if (viewer.slideCount < 1) throw new Error('PPTX 中没有可渲染的幻灯片')\n } catch (error) {\n viewer.destroy()\n options.container.replaceChildren()\n throw error\n }\n\n return {\n dispose: () => {\n if (disposed) return\n disposed = true\n viewer.destroy()\n options.container.replaceChildren()\n },\n }\n}\n"],"mappings":";;;;AAeA,eAAsB,EACpB,GACmC;CACnC,IAAM,IAAS,IAAI,EAAW,EAAQ,WAAW;EAC/C,SAAS;EACT,WAAW;EACX,WAAW;EACX,YAAY;EACZ,OAAO;GAAE,WAAW;GAAc,WAAW;EAAa;CAC5D,CAAC,GACG,IAAW;CAEf,IAAI;EAOF,IANA,MAAM,EAAO,KAAK,EAAQ,OAAO;GAC/B,YAAY;GACZ,WAAW;GACX,YAAY;GACZ,QAAQ,EAAQ;EAClB,CAAC,GACG,EAAQ,QAAQ,SAAS,MAAU,MAAM,cAAc;EAC3D,IAAI,EAAO,aAAa,GAAG,MAAU,MAAM,iBAAiB;CAC9D,SAAS,GAAO;EAGd,MAFA,EAAO,QAAQ,GACf,EAAQ,UAAU,gBAAgB,GAC5B;CACR;CAEA,OAAO,EACL,eAAe;EACT,MACJ,IAAW,IACX,EAAO,QAAQ,GACf,EAAQ,UAAU,gBAAgB;CACpC,EACF;AACF"}
|