From 0d35274d6ad3c6c55197e79ea36b18afdb010201 Mon Sep 17 00:00:00 2001 From: "Eevee (Evelyn Woods)" Date: Fri, 1 Jan 2021 15:26:33 -0700 Subject: [PATCH] Add support for loading CC2 ZIPs, and parse out C2G game titles --- js/format-c2g.js | 10 ++++++++++ js/main.js | 27 ++++++++++++++++++++++++++- js/util.js | 24 ++++++++++++++++++++++++ 3 files changed, 60 insertions(+), 1 deletion(-) diff --git a/js/format-c2g.js b/js/format-c2g.js index 6f0cfce..516a3a1 100644 --- a/js/format-c2g.js +++ b/js/format-c2g.js @@ -1980,6 +1980,16 @@ const MAX_SIMULTANEOUS_REQUESTS = 5; fetch_map(path, level_number); level_number++; } + else if (stmt.kind === 'directive' && stmt.name === 'game') { + // TODO apparently cc2 lets you change this mid-game and will then use a different save + // slot (?!), but i can't even consider that until i actually execute these things in + // order + if (game.identifier === undefined) { + let title = stmt.args[0].value; + game.identifier = title; + game.title = title; + } + } statements.push(stmt); } diff --git a/js/main.js b/js/main.js index 19b1f0a..1125be1 100644 --- a/js/main.js +++ b/js/main.js @@ -1,5 +1,7 @@ // TODO bugs and quirks i'm aware of: // - steam: if a player character starts on a force floor they won't be able to make any voluntary movements until they are no longer on a force floor +import * as fflate from 'https://unpkg.com/fflate/esm/index.mjs'; + import { DIRECTIONS, INPUT_BITS, TICS_PER_SECOND } from './defs.js'; import * as c2g from './format-c2g.js'; import * as dat from './format-dat.js'; @@ -3138,6 +3140,13 @@ class Conductor { else if (magic === '\xac\xaa\x02\x00' || magic == '\xac\xaa\x02\x01') { stored_game = dat.parse_game(buf); } + else if (magic === 'PK\x03\x04') { + // That's the ZIP header + // FIXME move this here i guess and flesh it out some + // FIXME if this doesn't find something then we should abort + await this.splash.search_multi_source(new util.ZipFileSource(buf)); + return; + } else if (magic.toLowerCase() === 'game') { // TODO this isn't really a magic number and isn't required to be first, so, maybe // this one should just go by filename @@ -3149,12 +3158,28 @@ class Conductor { dir = path.replace(/[/][^/]+$/, ''); } stored_game = await c2g.parse_game(buf, source, dir); + + if (stored_game.identifier) { + // Migrate any scores saved under the old path-based identifier + let new_identifier = stored_game.identifier; + if (this.stash.packs[identifier] && ! this.stash.packs[new_identifier]) { + this.stash.packs[new_identifier] = this.stash.packs[identifier]; + delete this.stash.packs[identifier]; + this.save_stash(); + + window.localStorage.setItem( + STORAGE_PACK_PREFIX + new_identifier, + window.localStorage.getItem(STORAGE_PACK_PREFIX + identifier)); + window.localStorage.removeItem(STORAGE_PACK_PREFIX + identifier); + } + + identifier = new_identifier; + } } else { throw new Error("Unrecognized file format"); } - // TODO load title for a C2G if (! stored_game.title) { stored_game.title = title ?? identifier ?? "Untitled pack"; } diff --git a/js/util.js b/js/util.js index ceefec6..1647ee0 100644 --- a/js/util.js +++ b/js/util.js @@ -1,3 +1,5 @@ +import * as fflate from 'https://unpkg.com/fflate/esm/index.mjs'; + // Base class for custom errors export class LLError extends Error {} @@ -413,3 +415,25 @@ export class EntryFileSource extends FileSource { return await file.arrayBuffer(); } } +// Zip files, using fflate +// TODO somewhat unfortunately fflate only supports unzipping the whole thing at once, not +// individual files as needed, but it's also pretty new so maybe later? +export class ZipFileSource extends FileSource { + constructor(buf) { + super(); + // TODO async? has some setup time but won't freeze browser + let files = fflate.unzipSync(new Uint8Array(buf)); + this.files = {}; + for (let [path, file] of Object.entries(files)) { + this.files['/' + path.toLowerCase()] = file; + } + } + + async get(path) { + let file = this.files[path.toLowerCase()]; + if (! file) + throw new LLError(`No such file in zip: ${path}`); + + return file.buffer; + } +}