diff --git a/shell/moduleloader.ts b/shell/moduleloader.ts index cac6feb..4820336 100644 --- a/shell/moduleloader.ts +++ b/shell/moduleloader.ts @@ -33,6 +33,8 @@ export async function loadModule(name: 'system3' | 'xsystem35'): Promise { function prepareSaveDir(m: EmscriptenModule) { m.FS.mkdir('/save', undefined); m.FS.mount(m.IDBFS, {}, '/save'); + m.FS.mkdir('/patton', undefined); + m.FS.mount(m.IDBFS, {}, '/patton'); m.addRunDependency('syncfs'); m.FS.syncfs(true, (err: any) => { m.removeRunDependency('syncfs'); diff --git a/shell/savedata.ts b/shell/savedata.ts index 059da0a..444fbf2 100644 --- a/shell/savedata.ts +++ b/shell/savedata.ts @@ -18,6 +18,8 @@ export class SaveDataManager { const idbfs = await idbfsModule.default(); idbfs.FS.mkdir('/save', undefined); idbfs.FS.mount(idbfs.IDBFS, {}, '/save'); + idbfs.FS.mkdir('/patton', undefined); + idbfs.FS.mount(idbfs.IDBFS, {}, '/patton'); const err = await new Promise((resolve) => idbfs.FS.syncfs(true, resolve)); if (err) throw err; return idbfs.FS; @@ -26,7 +28,7 @@ export class SaveDataManager { loadScript(JSZIP_SCRIPT); } - public hasSaveData(): Promise { + public async hasSaveData(): Promise { function find(fs: IDBFSModule['FS'], dir: string): boolean { if (!fs.isDir(fs.stat(dir, undefined).mode)) return false; @@ -40,14 +42,19 @@ export class SaveDataManager { } return false; } - return this.FSready.then((fs) => find(fs, '/save')); + const fs = await this.FSready; + return find(fs, '/save') || hasPattonSave(fs); } public async download() { await loadScript(JSZIP_SCRIPT); - let zip = new JSZip(); - storeZip(await this.FSready, '/save', zip.folder('save')); - let blob = await zip.generateAsync({type: 'blob', compression: 'DEFLATE'}); + const zip = new JSZip(); + const fs = await this.FSready; + storeZip(fs, '/save', zip.folder('save')); + if (hasPattonSave(fs)) { + storeZip(fs, '/patton', zip.folder('patton')); + } + const blob = await zip.generateAsync({type: 'blob', compression: 'DEFLATE'}); downloadAs('savedata.zip', URL.createObjectURL(blob)); gtag('event', 'Downloaded', { event_category: 'Savedata' }); } @@ -56,18 +63,19 @@ export class SaveDataManager { try { let fs = await this.FSready; if (file.name.toLowerCase().endsWith('.asd')) { - addSaveFile(fs, '/save/' + file.name, await file.arrayBuffer()); + fs.writeFile('/save/' + file.name, new Uint8Array(await file.arrayBuffer())); } else { await loadScript(JSZIP_SCRIPT); let zip = new JSZip(); await zip.loadAsync(await file.arrayBuffer(), JSZipOptions()); let entries: JSZipObject[] = []; zip.folder('save').forEach((path, z) => { entries.push(z); }); + zip.folder('patton').forEach((path, z) => { entries.push(z); }); for (let z of entries) { if (z.dir) fs.mkdirTree('/' + z.name.slice(0, -1), undefined); else - addSaveFile(fs, '/' + z.name, await z.async('arraybuffer')); + fs.writeFile('/' + z.name, new Uint8Array(await z.async('arraybuffer'))); } } await new Promise((resolve, reject) => { @@ -106,6 +114,11 @@ function storeZip(fs: IDBFSModule['FS'], dir: string, zip: JSZip) { } } -function addSaveFile(fs: IDBFSModule['FS'], path: string, content: ArrayBuffer) { - fs.writeFile(path, new Uint8Array(content)); +export function hasPattonSave(fs: IDBFSModule['FS']): boolean { + try { + fs.stat('/patton/patton.nhd', undefined); + return true; + } catch (e) { + return false; + } } diff --git a/shell/shell.ts b/shell/shell.ts index b1fa2f6..025fca2 100644 --- a/shell/shell.ts +++ b/shell/shell.ts @@ -14,6 +14,7 @@ import * as texthook from './textlog.js'; import {addToast} from './widgets.js'; import {message} from './strings.js'; import {config} from './config.js'; +import { hasPattonSave } from './savedata.js'; class System35Shell { constructor() { @@ -123,6 +124,7 @@ class System35Shell { } function scenario_address(): string | undefined { + if (!window.Module) return undefined; const m = Module as XSystem35Module; if (!m['_nact_current_page']) return undefined; return m._nact_current_page() + ':' + m._nact_current_addr().toString(16); @@ -164,13 +166,7 @@ window.addEventListener('beforeinstallprompt', (e: any) => { }); async function launchPatton() { - Module!.FS.mkdir('/patton', undefined); - Module!.FS.mount(Module!.IDBFS, {}, '/patton'); - await new Promise((resolve) => Module!.FS.syncfs(true, resolve)); - try { - Module!.FS.stat('/patton/patton.nhd', undefined); - // The HD image already exists, no preparation is needed. - } catch (_) { + if (!hasPattonSave(Module!.FS)) { // Store the game files to IDBFS. for (const fname of Module!.FS.readdir('/')) { if (Module!.FS.isDir(Module!.FS.stat(fname, undefined).mode)) { @@ -182,9 +178,12 @@ async function launchPatton() { const err = await new Promise((resolve) => Module!.FS.syncfs(false, resolve)); if (err) throw err; } - Module!.FS.unmount('/patton'); - Module!.FS.rmdir('/patton'); config.unloadConfirmation = false; window.location.href = '/patton/'; // Patton GO! - setTimeout(Module!._sys_restart, 1000); + setTimeout(async () => { + // This *may* run after the user is back from Patton. + const err = await new Promise((resolve) => Module!.FS.syncfs(true, resolve)); + if (err) throw err; + Module!._sys_restart(); + }, 1000); }