/* ============================================================ * tools/build_zip.mjs — package the game for itch.io. * Store-only ZIP (no deps). Excludes tools/ and dev files. * node tools/build_zip.mjs * ============================================================ */ import fs from 'node:fs'; import path from 'node:path'; const CRC_TABLE = (() => { const t = new Int32Array(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; t[n] = c; } return t; })(); function crc32(buf) { let c = 0xffffffff; for (let i = 0; i < buf.length; i++) c = CRC_TABLE[(c ^ buf[i]) & 0xff] ^ (c >>> 8); return (c ^ 0xffffffff) >>> 0; } const ROOT = path.join(process.cwd()); const INCLUDE = ['index.html', 'manifest.webmanifest', 'LICENSE', 'README.md', 'server.js', 'css', 'js', 'icons']; const EXCLUDE_DIR = new Set(['tools', 'node_modules', '.git']); function walk(rel, out) { const full = path.join(ROOT, rel); const st = fs.statSync(full); if (st.isDirectory()) { if (EXCLUDE_DIR.has(path.basename(rel))) return; for (const name of fs.readdirSync(full)) walk(path.join(rel, name), out); } else { out.push({ rel: rel.split(path.sep).join('/'), full }); } } const files = []; for (const f of INCLUDE) if (fs.existsSync(path.join(ROOT, f))) walk(f, files); /* ---- assemble store-only zip ---- */ const locals = []; const centrals = []; let offset = 0; const u16 = v => { const b = Buffer.alloc(2); b.writeUInt16LE(v); return b; }; const u32 = v => { const b = Buffer.alloc(4); b.writeUInt32LE(v); return b; }; for (const f of files) { const data = fs.readFileSync(f.full); const name = Buffer.from(f.rel, 'utf8'); const crc = crc32(data); const lh = Buffer.concat([ u32(0x04034b50), u16(20), u16(0), u16(0), u16(0x21), u16(0), u32(crc), u32(data.length), u32(data.length), u16(name.length), u16(0), name, ]); locals.push(lh, data); centrals.push(Buffer.concat([ u32(0x02014b50), u16(20), u16(20), u16(0), u16(0), u16(0x21), u16(0), u32(crc), u32(data.length), u32(data.length), u16(name.length), u16(0), u16(0), u16(0), u16(0), u32(0), u32(offset), name, ])); offset += lh.length + data.length; } const cdBuf = Buffer.concat(centrals); const eocd = Buffer.concat([ u32(0x06054b50), u16(0), u16(0), u16(files.length), u16(files.length), u32(cdBuf.length), u32(offset), u16(0), ]); const zip = Buffer.concat([...locals, cdBuf, eocd]); const outName = 'diablo2d-itch.zip'; fs.writeFileSync(path.join(ROOT, outName), zip); console.log(`wrote ${outName}: ${files.length} files, ${(zip.length / 1024).toFixed(1)} KB`);