export function buildSaveStateKey({ name, size, hash }) {
return `rom:${name}:${size}:${hash}`;
}
export async function createRomSaveKey({ name, size, bytes }) {
const hash = await computeRomHash(bytes);
return buildSaveStateKey({ name, size, hash });
}
export async function computeRomHash(bytes) {
const data = bytes instanceof Uint8Array ? bytes : new Uint8Array(bytes);
if (globalThis.crypto?.subtle?.digest) {
const digest = await globalThis.crypto.subtle.digest("SHA-256", data);
return bufferToHex(digest);
}
const { createHash } = await import("node:crypto");
const hash = createHash("sha256").update(Buffer.from(data)).digest("hex");
return hash;
}
export async function openSaveStateDb(name = "neser") {
if (!globalThis.indexedDB) {
throw new Error("IndexedDB not available");
}
return new Promise((resolve, reject) => {
const request = globalThis.indexedDB.open(name, 1);
request.onerror = () => reject(request.error);
request.onupgradeneeded = () => {
const db = request.result;
if (!db.objectStoreNames.contains("savestates")) {
db.createObjectStore("savestates");
}
};
request.onsuccess = () => resolve(request.result);
});
}
export async function saveState(db, key, bytes) {
const payload = bytes instanceof Uint8Array ? bytes : new Uint8Array(bytes);
return new Promise((resolve, reject) => {
const tx = db.transaction("savestates", "readwrite");
const store = tx.objectStore("savestates");
const request = store.put(payload, key);
request.onerror = () => reject(request.error);
tx.oncomplete = () => resolve();
tx.onerror = () => reject(tx.error || request.error);
tx.onabort = () => reject(tx.error || request.error);
});
}
export async function loadState(db, key) {
return new Promise((resolve, reject) => {
const tx = db.transaction("savestates", "readonly");
const store = tx.objectStore("savestates");
const request = store.get(key);
request.onerror = () => reject(request.error);
request.onsuccess = () => {
const result = request.result;
if (result) {
resolve(result instanceof Uint8Array ? result : new Uint8Array(result));
} else {
resolve(null);
}
};
});
}
export async function hasState(db, key) {
return new Promise((resolve, reject) => {
const tx = db.transaction("savestates", "readonly");
const store = tx.objectStore("savestates");
const request = store.getKey(key);
request.onerror = () => reject(request.error);
request.onsuccess = () => resolve(request.result !== undefined);
});
}
function bufferToHex(buffer) {
const bytes = new Uint8Array(buffer);
return Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0")).join("");
}