// zip.js —— 零依赖 ZIP 解压(stored/deflate),供技能安装与热更新复用。 "use strict"; const fs = require("fs"); const path = require("path"); const zlib = require("zlib"); // 解析中央目录,把 zip 内容写入 destDir;返回写出的文件数。 function extractZip(buf, destDir) { // 找 EOCD(0x06054b50),从尾部回扫 let eocd = -1; for (let i = buf.length - 22; i >= 0 && i >= buf.length - 22 - 65536; i--) { if (buf.readUInt32LE(i) === 0x06054b50) { eocd = i; break; } } if (eocd < 0) throw new Error("不是有效的 zip(找不到 EOCD)"); const count = buf.readUInt16LE(eocd + 10); let ptr = buf.readUInt32LE(eocd + 16); // 中央目录起始偏移 let written = 0; for (let n = 0; n < count; n++) { if (buf.readUInt32LE(ptr) !== 0x02014b50) break; const method = buf.readUInt16LE(ptr + 10); const compSize = buf.readUInt32LE(ptr + 20); const nameLen = buf.readUInt16LE(ptr + 28); const extraLen = buf.readUInt16LE(ptr + 30); const commentLen = buf.readUInt16LE(ptr + 32); const localOff = buf.readUInt32LE(ptr + 42); const name = buf.toString("utf8", ptr + 46, ptr + 46 + nameLen); ptr += 46 + nameLen + extraLen + commentLen; // 读本地头,定位数据 if (buf.readUInt32LE(localOff) !== 0x04034b50) continue; const lNameLen = buf.readUInt16LE(localOff + 26); const lExtraLen = buf.readUInt16LE(localOff + 28); const dataStart = localOff + 30 + lNameLen + lExtraLen; const comp = buf.subarray(dataStart, dataStart + compSize); // 防目录穿越 const safe = name.replace(/\\/g, "/").replace(/^\/+/, ""); if (safe.includes("../")) continue; const outPath = path.join(destDir, safe); if (safe.endsWith("/")) { fs.mkdirSync(outPath, { recursive: true }); continue; } fs.mkdirSync(path.dirname(outPath), { recursive: true }); let data; if (method === 0) data = comp; else if (method === 8) data = zlib.inflateRawSync(comp); else throw new Error(`不支持的压缩方式 ${method}`); fs.writeFileSync(outPath, data); written++; } return written; } module.exports = { extractZip };