Skip to content

Commit

Permalink
Support downloading / uploading HDD image for Patton
Browse files Browse the repository at this point in the history
  • Loading branch information
kichikuou committed Sep 22, 2024
1 parent c914c1c commit 2b3be5e
Show file tree
Hide file tree
Showing 3 changed files with 33 additions and 19 deletions.
2 changes: 2 additions & 0 deletions shell/moduleloader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ export async function loadModule(name: 'system3' | 'xsystem35'): Promise<any> {
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');
Expand Down
31 changes: 22 additions & 9 deletions shell/savedata.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -26,7 +28,7 @@ export class SaveDataManager {
loadScript(JSZIP_SCRIPT);
}

public hasSaveData(): Promise<boolean> {
public async hasSaveData(): Promise<boolean> {
function find(fs: IDBFSModule['FS'], dir: string): boolean {
if (!fs.isDir(fs.stat(dir, undefined).mode))
return false;
Expand All @@ -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' });
}
Expand All @@ -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) => {
Expand Down Expand Up @@ -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;
}
}
19 changes: 9 additions & 10 deletions shell/shell.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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)) {
Expand All @@ -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);
}

0 comments on commit 2b3be5e

Please sign in to comment.