/** * Bundled to public/zip-store.js (see scripts/bundle-zip-store.mjs). * Exposes window.buildZipStore for in-app WEBSITE publish. * * Uses a minimal PKZIP (STORE) implementation — the same family as * /zip-store.js at repo root. Some fflate/zipSync outputs (UTF-8 flags, extra * fields) are rejected or misread by the Java path Qortal uses for WEBSITE * validation, yielding false MISSING_INDEX_FILE. This format matches the * expectations of java.util zip readers / ZipUtils in core. */ const crcTable = (function makeTable() { const t = new Uint32Array(256); for (let i = 0; i < 256; i++) { let c = i; for (let k = 0; k < 8; k++) c = c & 1 ? 0xedb88320 ^ (c >>> 1) : c >>> 1; t[i] = c >>> 0; } return t; })(); function crc32(buf) { let c = 0xffffffff; for (let i = 0; i < buf.length; i++) c = crcTable[(c ^ buf[i]) & 0xff] ^ (c >>> 8); return (c ^ 0xffffffff) >>> 0; } function toUtf8(v) { if (v instanceof Uint8Array) return v; if (typeof v === 'string') return new TextEncoder().encode(String(v)); return new Uint8Array(v); } /** Qortal: root index* / default* / home* first, then a stable order. */ const ROOT_FIRST = [ 'index.html', 'index.htm', 'default.html', 'default.htm', 'home.html', 'home.htm', 'manifest.json', ]; function sortZipKeys(keys) { const remaining = new Set(keys); const out = []; for (const p of ROOT_FIRST) { if (remaining.has(p)) { out.push(p); remaining.delete(p); } } out.push(...Array.from(remaining).sort((a, b) => a.localeCompare(b))); return out; } /** * @param {Record} files * @returns {Uint8Array} */ function buildZipStoreRaw(files) { const names = sortZipKeys(Object.keys(files)); const entries = names.map((name) => { const data = toUtf8(files[name]); return { name, data }; }); let offset = 0; const parts = []; const central = []; for (const e of entries) { const nameBytes = toUtf8(e.name); const nameLen = nameBytes.length; if (nameLen > 65535) { throw new Error('zip path too long: ' + e.name); } const data = e.data; const size = data.length; const crc = crc32(data); const sig = 0x04034b50; const ver = 20; const flags = 0; const method = 0; const time = 0; const date = 0; const csize = size; const hlen = 30 + nameLen; const local = new DataView(new ArrayBuffer(hlen + size)); let p = 0; local.setUint32(p, sig, true); p += 4; local.setUint16(p, ver, true); p += 2; local.setUint16(p, flags, true); p += 2; local.setUint16(p, method, true); p += 2; local.setUint16(p, time, true); p += 2; local.setUint16(p, date, true); p += 2; local.setUint32(p, crc, true); p += 4; local.setUint32(p, csize, true); p += 4; local.setUint32(p, size, true); p += 4; local.setUint16(p, nameLen, true); p += 2; local.setUint16(p, 0, true); p += 2; new Uint8Array(local.buffer).set(nameBytes, p); p += nameLen; new Uint8Array(local.buffer).set(data, p); parts.push(new Uint8Array(local.buffer)); central.push({ nameBytes, crc, size, csize, localOffset: offset }); offset += local.buffer.byteLength; } const cdParts = []; let cdSize = 0; const cdOffset = offset; for (let i = 0; i < central.length; i++) { const c = central[i]; const nameLen = c.nameBytes.length; const hlen = 46 + nameLen; const buf = new DataView(new ArrayBuffer(46)); let q = 0; buf.setUint32(q, 0x02014b50, true); q += 4; buf.setUint16(q, 20, true); q += 2; buf.setUint16(q, 20, true); q += 2; buf.setUint16(q, 0, true); q += 2; buf.setUint16(q, 0, true); q += 2; buf.setUint16(q, 0, true); q += 2; buf.setUint16(q, 0, true); q += 2; buf.setUint32(q, c.crc, true); q += 4; buf.setUint32(q, c.csize, true); q += 4; buf.setUint32(q, c.size, true); q += 4; buf.setUint16(q, nameLen, true); q += 2; buf.setUint16(q, 0, true); q += 2; buf.setUint16(q, 0, true); q += 2; buf.setUint16(q, 0, true); q += 2; buf.setUint16(q, 0, true); q += 2; buf.setUint32(q, 0, true); q += 4; buf.setUint32(q, c.localOffset, true); q += 4; const header = new Uint8Array(buf.buffer); const merged = new Uint8Array(hlen); merged.set(header, 0); merged.set(c.nameBytes, 46); cdParts.push(merged); cdSize += merged.length; } const eoc = new DataView(new ArrayBuffer(22)); eoc.setUint32(0, 0x06054b50, true); eoc.setUint16(4, 0, true); eoc.setUint16(6, 0, true); eoc.setUint16(8, entries.length, true); eoc.setUint16(10, entries.length, true); eoc.setUint32(12, cdSize, true); eoc.setUint32(16, cdOffset, true); eoc.setUint16(20, 0, true); const totalLen = offset + cdSize + 22; const out = new Uint8Array(totalLen); let o = 0; for (const part of parts) { out.set(part, o); o += part.length; } for (const part of cdParts) { out.set(part, o); o += part.length; } out.set(new Uint8Array(eoc.buffer), o); return out; } export { buildZipStoreRaw };