From ab6112b0bf091ac5554a67fc69ab472b418cf2c8 Mon Sep 17 00:00:00 2001 From: kichikuou Date: Mon, 27 Mar 2017 21:54:29 +0900 Subject: [PATCH] Implement savedata download/upload --- .gitignore | 2 +- docs/xsystem35.html | 5 ++-- shell/cdimage.ts | 29 +++++------------------ shell/loader.ts | 2 +- shell/package.json | 1 + shell/settings.ts | 58 +++++++++++++++++++++++++++++++++++++++++++++ shell/shell.ts | 20 ++++++++++------ shell/util.ts | 19 +++++++++++++++ 8 files changed, 102 insertions(+), 34 deletions(-) diff --git a/.gitignore b/.gitignore index f18af70..9e927ee 100644 --- a/.gitignore +++ b/.gitignore @@ -2,7 +2,7 @@ *.o *.lo *.la -*.js +/docs/*.js /config.h /config.log /config.status diff --git a/docs/xsystem35.html b/docs/xsystem35.html index 43d1242..f759ea6 100644 --- a/docs/xsystem35.html +++ b/docs/xsystem35.html @@ -81,8 +81,8 @@
セーブデータ

セーブデータを ZIP ファイルに保存したり、ZIP ファイルから復元できます。

- - + +
@@ -97,5 +97,6 @@ + diff --git a/shell/cdimage.ts b/shell/cdimage.ts index 60ed496..d69ab58 100644 --- a/shell/cdimage.ts +++ b/shell/cdimage.ts @@ -1,3 +1,4 @@ +/// /// namespace CDImage { @@ -122,24 +123,6 @@ namespace CDImage { } } - function readAsArrayBuffer(blob: Blob): Promise { - return new Promise((resolve, reject) => { - let reader = new FileReader(); - reader.onload = () => { resolve(reader.result); }; - reader.onerror = () => { reject(reader.error); }; - reader.readAsArrayBuffer(blob); - }); - } - - function readAsText(blob: Blob): Promise { - return new Promise((resolve, reject) => { - let reader = new FileReader(); - reader.onload = () => { resolve(reader.result); }; - reader.onerror = () => { reject(reader.error); }; - reader.readAsText(blob); - }); - } - class ImageReaderBase { constructor(public image: File) { } @@ -150,7 +133,7 @@ namespace CDImage { sectorOffset: number): Promise { let sectors = Math.ceil(bytesToRead / sectorSize); let blob = this.image.slice(startOffset, startOffset + sectors * blockSize); - let buf = await readAsArrayBuffer(blob); + let buf = await readFileAsArrayBuffer(blob); let bufs: Uint8Array[] = []; for (let i = 0; i < sectors; i++) { bufs.push(new Uint8Array(buf, i * blockSize + sectorOffset, Math.min(bytesToRead, sectorSize))); @@ -170,7 +153,7 @@ namespace CDImage { readSector(sector: number): Promise { let start = sector * 2352 + 16; let end = start + 2048; - return readAsArrayBuffer(this.image.slice(start, end)); + return readFileAsArrayBuffer(this.image.slice(start, end)); } readSequentialSectors(startSector: number, length: number): Promise { @@ -178,7 +161,7 @@ namespace CDImage { } async parseCue(cueFile: File) { - let lines = (await readAsText(cueFile)).split('\n'); + let lines = (await readFileAsText(cueFile)).split('\n'); this.tracks = []; let currentTrack: number = null; for (let line of lines) { @@ -234,7 +217,7 @@ namespace CDImage { } async parseMds(mdsFile: File) { - let buf = await readAsArrayBuffer(mdsFile); + let buf = await readFileAsArrayBuffer(mdsFile); let signature = new TextDecoder().decode(new DataView(buf, 0, 16)); if (signature !== 'MEDIA DESCRIPTOR') @@ -262,7 +245,7 @@ namespace CDImage { readSector(sector: number): Promise { let start = sector * this.tracks[1].sectorSize + 16; let end = start + 2048; - return readAsArrayBuffer(this.image.slice(start, end)); + return readFileAsArrayBuffer(this.image.slice(start, end)); } readSequentialSectors(startSector: number, length: number): Promise { diff --git a/shell/loader.ts b/shell/loader.ts index 0bc0cd6..f282a77 100644 --- a/shell/loader.ts +++ b/shell/loader.ts @@ -52,7 +52,7 @@ namespace xsystem35 { } if (this.imgFile && this.cueFile) { this.imageReader = await CDImage.createReader(this.imgFile, this.cueFile); - await this.shell.fileSystemReady; + await xsystem35.fileSystemReady; this.install(); } } diff --git a/shell/package.json b/shell/package.json index 024e867..798351d 100644 --- a/shell/package.json +++ b/shell/package.json @@ -4,6 +4,7 @@ "description": "", "dependencies": { "@types/emscripten": "0.0.29", + "@types/jszip": "0.0.32", "@types/text-encoding": "0.0.30" } } diff --git a/shell/settings.ts b/shell/settings.ts index 256f69b..09d44f9 100644 --- a/shell/settings.ts +++ b/shell/settings.ts @@ -11,6 +11,9 @@ namespace xsystem35 { this.closeModal(); }; $('.modal-overlay').addEventListener('click', this.closeModal.bind(this)); + + $('#downloadSaveData').addEventListener('click', this.downloadSaveData.bind(this)); + $('#uploadSaveData').addEventListener('click', this.uploadSaveData.bind(this)); } private openModal() { @@ -22,5 +25,60 @@ namespace xsystem35 { $('#settings-modal').classList.remove('active'); document.removeEventListener('keydown', this.keyDownHandler); } + + private async downloadSaveData() { + await xsystem35.saveDirReady; + let zip = new JSZip(); + let folder = zip.folder('save'); + for (let name of FS.readdir('/save')) { + if (!name.toLowerCase().endsWith('.asd')) + continue; + let content = FS.readFile('/save/' + name, { encoding: 'binary' }); + folder.file(name, content); + } + let blob = await zip.generateAsync({type: 'blob', compression: 'DEFLATE'}); + let elem = document.createElement('a'); + elem.setAttribute('download', 'savedata.zip'); + elem.setAttribute('href', URL.createObjectURL(blob)); + elem.click(); + } + + private uploadSaveData() { + let input = document.createElement('input'); + input.type = 'file'; + input.addEventListener('change', (evt: Event) => { + this.extractSaveData(input.files[0]); + document.body.removeChild(input); + }); + input.style.display = 'none'; + document.body.appendChild(input); + input.click(); + } + + private async extractSaveData(file: File) { + function basename(path: string) { + return path.slice(path.lastIndexOf('/') + 1); + } + function addSaveFile(name: string, content: ArrayBuffer) { + console.log(name); + FS.writeFile('/save/' + name, new Uint8Array(content), { encoding: 'binary' }); + } + try { + await xsystem35.saveDirReady; + if (file.name.toLowerCase().endsWith('.zip')) { + let zip = new JSZip(); + await zip.loadAsync(await readFileAsArrayBuffer(file)); + for (let f of zip.file(/\.asd$/i)) + addSaveFile(basename(f.name), await f.async('arraybuffer')); + } else if (file.name.toLowerCase().endsWith('.asd')) { + addSaveFile(file.name, await readFileAsArrayBuffer(file)); + } else { + throw('ファイルを認識できません。'); + } + xsystem35.shell.syncfs(0); + } catch (err) { + console.log(err); + } + } } } diff --git a/shell/shell.ts b/shell/shell.ts index 0290ac6..5cd19cb 100644 --- a/shell/shell.ts +++ b/shell/shell.ts @@ -13,12 +13,13 @@ namespace xsystem35 { 'ttfont_mincho: ' + Font.fname, 'ttfont_gothic: ' + Font.fname, '', ].join('\n'); + export let fileSystemReady: Promise; + export let saveDirReady: Promise; export let cdPlayer: CDPlayer; export let audio: AudioManager; export let settings: Settings; export class System35Shell { - readonly fileSystemReady: Promise; private imageLoader: ImageLoader; status: HTMLElement = document.getElementById('status'); private zoom: ZoomManager; @@ -26,8 +27,10 @@ namespace xsystem35 { private antialiasCheckbox: HTMLInputElement; constructor() { - let fileSystemReady: () => void; - this.fileSystemReady = new Promise((resolve) => { fileSystemReady = resolve; }); + let fsReady: () => void; + fileSystemReady = new Promise((resolve) => { fsReady = resolve; }); + let idbfsReady: () => void; + saveDirReady = new Promise((resolve) => { idbfsReady = resolve; }); this.imageLoader = new ImageLoader(this); this.setStatus('Downloading...'); @@ -45,7 +48,7 @@ namespace xsystem35 { Module.noInitialRun = true; Module.setStatus = this.setStatus.bind(this); Module.preRun = [ - fileSystemReady, + fsReady, function loadFont() { FS.createPreloadedFile('/', Font.fname, Font.url, true, false); }, @@ -53,7 +56,10 @@ namespace xsystem35 { FS.mkdir('/save'); FS.mount(IDBFS, {}, '/save'); Module.addRunDependency('syncfs'); - FS.syncfs(true, (err) => { Module.removeRunDependency('syncfs'); }); + FS.syncfs(true, (err) => { + Module.removeRunDependency('syncfs'); + idbfsReady(); + }); }, ]; this.volumeControl = new VolumeControl(); @@ -92,14 +98,14 @@ namespace xsystem35 { } private fsyncTimer: number; - syncfs(fname: string) { + syncfs(timeout = 100) { window.clearTimeout(this.fsyncTimer); this.fsyncTimer = window.setTimeout(() => { FS.syncfs(false, (err) => { if (err) console.log('FS.syncfs error: ', err); }); - }, 100); + }, timeout); } private antialiasChanged() { diff --git a/shell/util.ts b/shell/util.ts index bcd938d..7d00b69 100644 --- a/shell/util.ts +++ b/shell/util.ts @@ -1,5 +1,23 @@ let $: (selector: string) => HTMLElement = document.querySelector.bind(document); +function readFileAsArrayBuffer(blob: Blob): Promise { + return new Promise((resolve, reject) => { + let reader = new FileReader(); + reader.onload = () => { resolve(reader.result); }; + reader.onerror = () => { reject(reader.error); }; + reader.readAsArrayBuffer(blob); + }); +} + +function readFileAsText(blob: Blob): Promise { + return new Promise((resolve, reject) => { + let reader = new FileReader(); + reader.onload = () => { resolve(reader.result); }; + reader.onerror = () => { reject(reader.error); }; + reader.readAsText(blob); + }); +} + // xsystem35 exported functions declare function _musfade_setvolval_all(vol: number): void; declare function _ags_setAntialiasedStringMode(on: number): void; @@ -19,6 +37,7 @@ declare namespace Module { } declare namespace FS { + function readFile(path: string, opts?: {encoding?: string; flags?: string}): any; function writeFile(path: string, data: ArrayBufferView | string, opts?: {encoding?: string; flags?: string; canOwn?: boolean}): void; }