"use strict"; (function (global) { const Snapshot = global.TarinaiSnapshot; if (!Snapshot) throw new Error("TarinaiSnapshot is not available for save_codec.js"); const EXPORT_PREFIX = "TN3!"; const SAVE_TEXT_ALPHABET = (() => { let out = ""; for (let i = 33; i <= 126; i++) { const ch = String.fromCharCode(i); if (ch === "*" || ch === "'" || ch === "_") continue; out += ch; } return out; })(); const SAVE_TEXT_REVERSE = (() => { const map = new Map(); for (let i = 0; i < SAVE_TEXT_ALPHABET.length; i++) map.set(SAVE_TEXT_ALPHABET[i], i); return map; })(); if (SAVE_TEXT_ALPHABET.length !== 91) throw new Error("save alphabet must contain 91 printable ASCII chars"); function base91Encode(bytes) { let b = 0; let n = 0; let out = ""; for (const byte of bytes || []) { b |= (byte & 255) << n; n += 8; if (n > 13) { let v = b & 8191; if (v > 88) { b >>= 13; n -= 13; } else { v = b & 16383; b >>= 14; n -= 14; } out += SAVE_TEXT_ALPHABET[v % 91] + SAVE_TEXT_ALPHABET[Math.floor(v / 91)]; } } if (n) { out += SAVE_TEXT_ALPHABET[b % 91]; if (n > 7 || b > 90) out += SAVE_TEXT_ALPHABET[Math.floor(b / 91)]; } return out; } function base91Decode(text) { const clean = String(text || "").trim(); let v = -1; let b = 0; let n = 0; const out = []; for (const ch of clean) { const c = SAVE_TEXT_REVERSE.get(ch); if (c == null) continue; if (v < 0) { v = c; } else { v += c * 91; b |= v << n; n += (v & 8191) > 88 ? 13 : 14; do { out.push(b & 255); b >>= 8; n -= 8; } while (n > 7); v = -1; } } if (v >= 0) out.push((b | (v << n)) & 255); return new Uint8Array(out); } async function compressBytes(bytes) { if (typeof CompressionStream === "function") { const stream = new Blob([bytes]).stream().pipeThrough(new CompressionStream("deflate-raw")); return new Uint8Array(await new Response(stream).arrayBuffer()); } return bytes; } async function decompressBytes(bytes, codec = "R") { if (codec === "D") { if (typeof DecompressionStream !== "function") throw new Error("deflate decoder is not available in this browser"); const stream = new Blob([bytes]).stream().pipeThrough(new DecompressionStream("deflate-raw")); return new Uint8Array(await new Response(stream).arrayBuffer()); } return bytes; } async function encodeSnapshot(snapshot) { if (!snapshot || snapshot.v !== Snapshot.version) throw new Error("snapshot version mismatch"); const json = JSON.stringify(snapshot); const bytes = new TextEncoder().encode(json); let codec = "R"; let packed = bytes; try { const compressed = await compressBytes(bytes); if (compressed.length < bytes.length) { codec = "D"; packed = compressed; } } catch (error) { console.warn("save compression fallback", error); } return `${EXPORT_PREFIX}${codec}${base91Encode(packed)}`; } async function decodeSnapshot(text) { const raw = String(text || "").trim(); if (!raw) throw new Error("empty import text"); if (raw.startsWith("{") || raw.startsWith("[")) { const data = JSON.parse(raw); if (data?.v !== Snapshot.version) throw new Error("unsupported save version"); return data; } if (!raw.startsWith(EXPORT_PREFIX)) throw new Error("unsupported save text"); const codec = raw.charAt(EXPORT_PREFIX.length) || "R"; const payload = raw.slice(EXPORT_PREFIX.length + 1); const packed = base91Decode(payload); const bytes = await decompressBytes(packed, codec); const json = new TextDecoder().decode(bytes); const data = JSON.parse(json); if (data?.v !== Snapshot.version) throw new Error("unsupported save version"); return data; } global.TarinaiSaveCodec = { EXPORT_PREFIX, SAVE_TEXT_ALPHABET, base91Encode, base91Decode, encodeSnapshot, decodeSnapshot, }; })(typeof window !== "undefined" ? window : globalThis);