71 lines
2.6 KiB
JavaScript
71 lines
2.6 KiB
JavaScript
/*!
|
|
* Tectum
|
|
* Copyright © 2026 Machiel Heinen.
|
|
* Alle rechten voorbehouden. Gebruik uitsluitend met schriftelijke toestemming.
|
|
* Zie LICENTIE.md.
|
|
*/
|
|
// ZIP zonder compressie (stored): meerdere bestanden in één download.
|
|
|
|
window.ZipWriter = (() => {
|
|
const CRC = 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;
|
|
CRC[n] = c >>> 0;
|
|
}
|
|
function crc32(parts) {
|
|
let c = 0xffffffff;
|
|
parts.forEach(b => { for (let i = 0; i < b.length; i++) c = CRC[(c ^ b[i]) & 0xff] ^ (c >>> 8); });
|
|
return (c ^ 0xffffffff) >>> 0;
|
|
}
|
|
|
|
// files: [{ name, data: Uint8Array | Uint8Array[] }]
|
|
function build(files) {
|
|
const enc = new TextEncoder();
|
|
const out = [], central = [];
|
|
let offset = 0;
|
|
const d = new Date();
|
|
const time = (d.getHours() << 11) | (d.getMinutes() << 5) | (d.getSeconds() >> 1);
|
|
const date = ((d.getFullYear() - 1980) << 9) | ((d.getMonth() + 1) << 5) | d.getDate();
|
|
files.forEach(f => {
|
|
const parts = Array.isArray(f.data) ? f.data : [f.data];
|
|
const size = parts.reduce((a, b) => a + b.length, 0);
|
|
const name = enc.encode(f.name), crc = crc32(parts);
|
|
const head = (sig, extra) => {
|
|
const h = new DataView(new ArrayBuffer(extra ? 46 : 30));
|
|
let p = 0;
|
|
const u32 = (v) => { h.setUint32(p, v, true); p += 4; }, u16 = (v) => { h.setUint16(p, v, true); p += 2; };
|
|
u32(sig);
|
|
if (extra) u16(20);
|
|
u16(20); u16(0x0800); u16(0); u16(time); u16(date); u32(crc); u32(size); u32(size); u16(name.length); u16(0);
|
|
if (extra) { u16(0); u16(0); u16(0); u32(0); u32(offset); }
|
|
return new Uint8Array(h.buffer);
|
|
};
|
|
const local = head(0x04034b50, false);
|
|
central.push(head(0x02014b50, true), name);
|
|
out.push(local, name, ...parts);
|
|
offset += local.length + name.length + size;
|
|
});
|
|
const cdSize = central.reduce((a, b) => a + b.length, 0);
|
|
const end = new DataView(new ArrayBuffer(22));
|
|
end.setUint32(0, 0x06054b50, true);
|
|
end.setUint16(8, files.length, true); end.setUint16(10, files.length, true);
|
|
end.setUint32(12, cdSize, true); end.setUint32(16, offset, true);
|
|
return out.concat(central, [new Uint8Array(end.buffer)]);
|
|
}
|
|
|
|
function save(files, filename) {
|
|
const blob = new Blob(build(files), { type: 'application/zip' });
|
|
const url = URL.createObjectURL(blob);
|
|
const a = document.createElement('a');
|
|
a.href = url;
|
|
a.download = filename;
|
|
document.body.appendChild(a);
|
|
a.click();
|
|
document.body.removeChild(a);
|
|
URL.revokeObjectURL(url);
|
|
}
|
|
|
|
return { build, save };
|
|
})();
|