1/**
2 * Store-only ZIP writer, enough for the dataset download without a
3 * dependency. WAV float data barely compresses, so storing costs little;
4 * every entry gets a real CRC so ordinary unzip tools verify it.
5 */
6interface ZipEntry { name: string; data: Uint8Array; }
8const CRC_TABLE = (() => {
9 const t = new Uint32Array(256);
10 for (let n = 0; n < 256; n++) {
11 let c = n;
12 for (let k = 0; k < 8; k++) c = c & 1 ? 0xedb88320 ^ (c >>> 1) : c >>> 1;
13 t[n] = c >>> 0;
14 }
15 return t;
16})();
18function crc32(data: Uint8Array): number {
19 let c = 0xffffffff;
20 for (let i = 0; i < data.length; i++) c = CRC_TABLE[(c ^ data[i]) & 0xff] ^ (c >>> 8);
21 return (c ^ 0xffffffff) >>> 0;
22}
24export function zipStore(entries: ZipEntry[]): Uint8Array {
25 const enc = new TextEncoder();
26 const parts: Uint8Array[] = [];
27 const central: Uint8Array[] = [];
28 let offset = 0;
30 for (const e of entries) {
31 const name = enc.encode(e.name);
32 const crc = crc32(e.data);
33 const local = new Uint8Array(30 + name.length);
34 const lv = new DataView(local.buffer);
35 lv.setUint32(0, 0x04034b50, true);
36 lv.setUint16(4, 20, true); // version needed
37 lv.setUint16(6, 0x0800, true); // utf-8 names
38 lv.setUint16(8, 0, true); // stored
39 lv.setUint16(10, 0, true); // time
40 lv.setUint16(12, 0x21, true); // date (1980-01-01)
41 lv.setUint32(14, crc, true);
42 lv.setUint32(18, e.data.length, true);
43 lv.setUint32(22, e.data.length, true);
44 lv.setUint16(26, name.length, true);
45 lv.setUint16(28, 0, true);
46 local.set(name, 30);
47 parts.push(local, e.data);
49 const cd = new Uint8Array(46 + name.length);
50 const cv = new DataView(cd.buffer);
51 cv.setUint32(0, 0x02014b50, true);
52 cv.setUint16(4, 20, true);
53 cv.setUint16(6, 20, true);
54 cv.setUint16(8, 0x0800, true);
55 cv.setUint16(10, 0, true);
56 cv.setUint16(12, 0, true);
57 cv.setUint16(14, 0x21, true);
58 cv.setUint32(16, crc, true);
59 cv.setUint32(20, e.data.length, true);
60 cv.setUint32(24, e.data.length, true);
61 cv.setUint16(28, name.length, true);
62 cv.setUint32(42, offset, true);
63 cd.set(name, 46);
64 central.push(cd);
65 offset += local.length + e.data.length;
66 }
68 const cdSize = central.reduce((a, c) => a + c.length, 0);
69 const eocd = new Uint8Array(22);
70 const ev = new DataView(eocd.buffer);
71 ev.setUint32(0, 0x06054b50, true);
72 ev.setUint16(8, entries.length, true);
73 ev.setUint16(10, entries.length, true);
74 ev.setUint32(12, cdSize, true);
75 ev.setUint32(16, offset, true);
77 const total = offset + cdSize + 22;
78 const out = new Uint8Array(total);
79 let p = 0;
80 for (const part of [...parts, ...central, eocd]) { out.set(part, p); p += part.length; }
81 return out;
82}