const textEncoder = new TextEncoder();
const CRC_TABLE = (() => {
const table = new Uint32Array(256);
for (let n = 0; n < 256; n++) {
let c = n;
for (let k = 0; k < 8; k++) c = c & 1 ? 0xedb88320 ^ (c >>> 1) : c >>> 1;
table[n] = c >>> 0;
}
return table;
})();
function crc32(bytes) {
let crc = 0xffffffff;
for (let i = 0; i < bytes.length; i++) crc = CRC_TABLE[(crc ^ bytes[i]) & 0xff] ^ (crc >>> 8);
return (crc ^ 0xffffffff) >>> 0;
}
function dosDateTime(date) {
const time = (date.getHours() << 11) | (date.getMinutes() << 5) | (date.getSeconds() >> 1);
const day = ((date.getFullYear() - 1980) << 9) | ((date.getMonth() + 1) << 5) | date.getDate();
return { time: time & 0xffff, day: day & 0xffff };
}
export function buildZip(files) {
const { time, day } = dosDateTime(new Date());
const entries = files.map((file) => ({
name: textEncoder.encode(file.path),
data: textEncoder.encode(file.content),
}));
const chunks = [];
const central = [];
let offset = 0;
for (const entry of entries) {
const crc = crc32(entry.data);
const local = new DataView(new ArrayBuffer(30));
local.setUint32(0, 0x04034b50, true);
local.setUint16(4, 20, true); local.setUint16(6, 0x0800, true); local.setUint16(8, 0, true); local.setUint16(10, time, true);
local.setUint16(12, day, true);
local.setUint32(14, crc, true);
local.setUint32(18, entry.data.length, true);
local.setUint32(22, entry.data.length, true);
local.setUint16(26, entry.name.length, true);
local.setUint16(28, 0, true);
chunks.push(new Uint8Array(local.buffer), entry.name, entry.data);
const head = new DataView(new ArrayBuffer(46));
head.setUint32(0, 0x02014b50, true);
head.setUint16(4, 20, true); head.setUint16(6, 20, true); head.setUint16(8, 0x0800, true);
head.setUint16(10, 0, true);
head.setUint16(12, time, true);
head.setUint16(14, day, true);
head.setUint32(16, crc, true);
head.setUint32(20, entry.data.length, true);
head.setUint32(24, entry.data.length, true);
head.setUint16(28, entry.name.length, true);
head.setUint32(42, offset, true);
central.push(new Uint8Array(head.buffer), entry.name);
offset += 30 + entry.name.length + entry.data.length;
}
const centralSize = central.reduce((total, part) => total + part.length, 0);
const end = new DataView(new ArrayBuffer(22));
end.setUint32(0, 0x06054b50, true);
end.setUint16(8, entries.length, true);
end.setUint16(10, entries.length, true);
end.setUint32(12, centralSize, true);
end.setUint32(16, offset, true);
return new Blob([...chunks, ...central, new Uint8Array(end.buffer)], { type: "application/zip" });
}