/** * Нативный ZIP — упаковка и распаковка без внешних библиотек. * * Использование: * const files = await unzip(zipBlob); // {name: Blob, ...} * const zip = await buildZip([{name, data}]); // Blob */ /** * Распаковать ZIP blob → {filename: Blob}. * Поддерживает только STORE (без сжатия) — как отдаёт drhider. */ async function unzip(zb) { const buf = await zb.arrayBuffer(); const dv = new DataView(buf); const files = {}; let pos = 0; while (pos < buf.byteLength - 4) { const sig = dv.getUint32(pos, true); if (sig === 0x04034b50) { // Local file header const start = pos; pos += 26; const nameLen = dv.getUint16(pos, true); pos += 2; const extraLen = dv.getUint16(pos, true); pos += 2; const name = new TextDecoder().decode( new Uint8Array(buf, pos, nameLen) ); pos += nameLen + extraLen; const compSize = dv.getUint32(start + 18, true); files[name] = new Blob([new Uint8Array(buf, pos, compSize)]); pos += compSize; } else { break; // Central directory или EOCD — конец файлов } } return files; } /** * Создать ZIP blob из entries [{name, data}]. * Метод: STORE (без сжатия), UTF-8 имена. */ async function buildZip(entries) { const parts = []; const centralDir = []; let offset = 0; for (const e of entries) { const data = new Uint8Array(await e.data.arrayBuffer()); const nameBytes = new TextEncoder().encode(e.name); // Local file header (30 bytes + filename) const lh = new Uint8Array(30 + nameBytes.length); const lv = new DataView(lh.buffer); lv.setUint32(0, 0x04034b50, true); // signature lv.setUint16(6, 0x800, true); // flags: UTF-8 lv.setUint32(18, data.length, true); // compressed size lv.setUint32(22, data.length, true); // uncompressed size lv.setUint16(26, nameBytes.length, true); lh.set(nameBytes, 30); parts.push(lh, data); // Central directory entry (46 bytes + filename) const ce = new Uint8Array(46 + nameBytes.length); const cv = new DataView(ce.buffer); cv.setUint32(0, 0x02014b50, true); cv.setUint16(8, 0x800, true); cv.setUint32(20, data.length, true); cv.setUint32(24, data.length, true); cv.setUint16(28, nameBytes.length, true); cv.setUint32(42, offset, true); ce.set(nameBytes, 46); centralDir.push(ce); offset += 30 + nameBytes.length + data.length; } // End of central directory (22 bytes) const cdSize = centralDir.reduce((s, c) => s + c.length, 0); const eocd = new Uint8Array(22); const ev = new DataView(eocd.buffer); ev.setUint32(0, 0x06054b50, true); ev.setUint16(8, entries.length, true); ev.setUint16(10, entries.length, true); ev.setUint32(12, cdSize, true); ev.setUint32(16, offset, true); return new Blob([...parts, ...centralDir, eocd], { type: 'application/zip' }); }