Mercurial > web-gnusto
changeset 73:5c35e18a9d1a
Added a modified version of beret.js from gnusto (originally at src/xpcom/beret/beret.js in the gnusto source tree), because this file contains code to load quetzal save files (gnusto-engine.js contains code to save quetzal files).
Implemented basic save/restore functionality; right now we're just using DOM storage in Firefox 2/3.
author | Atul Varma <varmaa@toolness.com> |
---|---|
date | Tue, 20 May 2008 23:24:09 -0700 |
parents | 9c96d374aaae |
children | d449d74a1f96 |
files | base64.js beret.js engine-runner.js gnusto.html web-zui.js |
diffstat | 5 files changed, 266 insertions(+), 3 deletions(-) [+] |
line wrap: on
line diff
--- a/base64.js Sun May 18 23:47:15 2008 -0700 +++ b/base64.js Tue May 20 23:24:09 2008 -0700 @@ -7,6 +7,28 @@ var base64_tab = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="; +function encode_base64(data) { + var out = "", c1, c2, c3, e1, e2, e3, e4; + for (var i = 0; i < data.length; ) { + c1 = data[i++]; + c2 = data[i++]; + c3 = data[i++]; + e1 = c1 >> 2; + e2 = ((c1 & 3) << 4) + (c2 >> 4); + e3 = ((c2 & 15) << 2) + (c3 >> 6); + e4 = c3 & 63; + if (isNaN(c2)) + e3 = e4 = 64; + else if (isNaN(c3)) + e4 = 64; + out += (base64_tab.charAt(e1) + + base64_tab.charAt(e2) + + base64_tab.charAt(e3) + + base64_tab.charAt(e4)); + } + return out; +} + function decode_base64(data) { var out = [], c1, c2, c3, e1, e2, e3, e4; for (var i = 0; i < data.length; ) {
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/beret.js Tue May 20 23:24:09 2008 -0700 @@ -0,0 +1,207 @@ +// -*- Mode: Java; tab-width: 2; -*- +// $Id: beret.js,v 1.22 2006/10/24 16:11:09 naltrexone42 Exp $ +// +// Copyright (c) 2003 Thomas Thurman +// thomas@thurman.org.uk +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of version 2 of the GNU General Public License +// as published by the Free Software Foundation. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have be able to view the GNU General Public License at +// http://www.gnu.org/copyleft/gpl.html ; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. + +//////////////////////////////////////////////////////////////// +// +// iff_parse +// +// Parses an IFF file entirely contained in the array |s|. +// The return value is a list. The first element is the form type +// of the file; subsequent elements represent chunks. Each chunk is +// represented by a list whose first element is the chunk type, +// whose second element is the starting offset of the data within +// the array, and whose third element is the length. +// +function iff_parse(s) { + + function num_from(offset) { + return s[offset]<<24 | s[offset+1]<<16 | s[offset+2]<<8 | s[offset+3]; + } + + function string_from(offset) { + return String.fromCharCode(s[offset]) + + String.fromCharCode(s[offset+1]) + + String.fromCharCode(s[offset+2]) + + String.fromCharCode(s[offset+3]); + } + + var result = [string_from(8)]; + + var cursor = 12; + + while (cursor < s.length) { + var chunk = [string_from(cursor)]; + var chunk_length = num_from(cursor+4); + + if (chunk_length<0 || (chunk_length+cursor)>s.length) { + // fixme: do something sensible here + throw new Error('WEEBLE, panic\n'); + return []; + } + + chunk.push(cursor+8); + chunk.push(chunk_length); + + result.push(chunk); + + cursor += 8 + chunk_length; + if (chunk_length % 2) cursor++; + } + + return result; +} + +function Beret(engine) { + this.m_engine = engine; +} + +Beret.prototype = { + + //////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////// + // // + // PUBLIC METHODS // + // // + // Documentation for these methods is in the IDL. // + // // + //////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////// + + load: function b_load(content) { + + function magic_number_is_string(str) { + for (var ij=0; ij<str.length; ij++) { + if (str.charCodeAt(ij)!=content[ij]) { + return false; + } + } + return true; + } + + if (magic_number_is_string('FORM')) { // An IFF file. + + var iff_details = iff_parse(content); + + if (iff_details.length==0) { + + // Invalid IFF file. + + this.m_filetype = 'invalid unknown iff'; + + } else if (iff_details[0]=='IFZS') { + + // Quetzal saved file. + + // Currently, we require that a loaded Quetzal file + // is from the same story that's currently loaded. + // One day we'll have a way of getting the right + // story out of the registry. + + // FIXME: We also don't check this yet. We should. We will. + + var memory = 0; + var memory_is_compressed = 0; + var stks = 0; + var pc = 0; + + for (var i=1; i<iff_details.length; i++) { + var tag = iff_details[i][0]; + if (tag=='IFhd') { + + // validate that saved game release number matches loaded game + var release_num_location = iff_details[i][1]; + var serial_num_location = iff_details[i][1]+2; + if ( + // if no game is loaded... + (!this.m_engine) || + // or the game has a different release number... + (this.m_engine.getByte(0x02) != content[release_num_location]) || (this.m_engine.getByte(0x03) != content[release_num_location+1]) || + // or the game has a different serial number... + (this.m_engine.getByte(0x12) != content[serial_num_location]) || (this.m_engine.getByte(0x13) != content[serial_num_location+1]) || + (this.m_engine.getByte(0x14) != content[serial_num_location+2]) || (this.m_engine.getByte(0x15) != content[serial_num_location+3]) || + (this.m_engine.getByte(0x16) != content[serial_num_location+4]) || (this.m_engine.getByte(0x17) != content[serial_num_location+5]) + // w\We should also validate checksum, but I don't believe we store this if a game is too old to have one. So let's not risk it. + ){ + //The save game isn't for the currently loaded game. Bail out. + this.m_filetype = 'mismatch'; + break; + } + + var pc_location = iff_details[i][1]+10; + pc = content[pc_location]<<16 | + content[pc_location+1]<<8 | + content[pc_location+2]; + } else if (tag=='Stks') { + if (stks!=0) { + throw new Error('fixme: error: multiple Stks\n'); + } + stks = content.slice(iff_details[i][1], + iff_details[i][2]+iff_details[i][1]); + } else if (tag=='CMem' || tag=='UMem') { + + if (memory!=0) { + throw new Error('fixme: error: multiple memory segments\n'); + } + + memory_is_compressed = (tag=='CMem'); + + memory = content.slice(iff_details[i][1], + iff_details[i][2]+iff_details[i][1]); + } + } + + if (memory==0) { + throw new Error('fixme: error: no memory in quetzal\n'); + } else if (stks==0) { + throw new Error('fixme: error: no stacks in quetzal\n'); + } else if (pc==0) { + throw new Error('fixme: error: no header in quetzal\n'); + } else { + this.m_filetype = 'ok saved quetzal zcode'; + this.m_engine.loadSavedGame(memory.length, memory, + memory_is_compressed, + stks.length, stks, + pc); + } + } + } else { + // OK, just give up. + this.m_filetype = 'error unknown general'; + } + }, + + get filetype() { + return this.m_filetype; + }, + + get engine() { + return this.m_engine; + }, + + //////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////// + // // + // PRIVATE VARIABLES // + // // + //////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////// + + m_filetype: 'error none unseen', + m_engine: null +};
--- a/engine-runner.js Sun May 18 23:47:15 2008 -0700 +++ b/engine-runner.js Tue May 20 23:24:09 2008 -0700 @@ -15,9 +15,9 @@ }, onCharacterInput: function(callback) { }, - onSave: function(callback) { + onSave: function(data) { }, - onRestore: function(callback) { + onRestore: function() { }, onQuit: function() { }, @@ -167,8 +167,21 @@ self._zui.onCharacterInput(self._receiveCharacterInput); break; case GNUSTO_EFFECT_SAVE: + engine.saveGame(); + if (self._zui.onSave(engine.saveGameData())) + engine.answer(0, 1); + else + engine.answer(0, 0); + break; case GNUSTO_EFFECT_RESTORE: - throw new Error("Unimplemented effect: " + effect); + var saveGameData = self._zui.onRestore(); + if (saveGameData) { + var beret = new Beret(engine); + beret.load(saveGameData); + } else { + engine.answer(0, 0); + } + break; case GNUSTO_EFFECT_QUIT: self.stop(); self._zui.onQuit();
--- a/gnusto.html Sun May 18 23:47:15 2008 -0700 +++ b/gnusto.html Tue May 20 23:24:09 2008 -0700 @@ -17,6 +17,7 @@ <script type="text/javascript" src="remedial.js"></script> <script type="text/javascript" src="base64.js"></script> <script type="text/javascript" src="stories/curses.js"></script> +<script type="text/javascript" src="beret.js"></script> <script type="text/javascript" src="gnusto-engine.min.js"></script> <script type="text/javascript" src="engine-runner.js"></script> <script type="text/javascript" src="console.js"></script>
--- a/web-zui.js Sun May 18 23:47:15 2008 -0700 +++ b/web-zui.js Tue May 20 23:24:09 2008 -0700 @@ -187,6 +187,26 @@ self._currentCallback = callback; }, + onSave: function(data) { + // TODO: Attempt to use other forms of local storage + // (e.g. Google Gears, HTML 5 database storage, etc) if + // available; if none are available, we should return false. + + globalStorage[location.hostname]['saveData'] = encode_base64(data); + return true; + }, + + onRestore: function() { + // TODO: Attempt to use other forms of local storage if + // available; if none are available, we should return null. + + var saveData = globalStorage[location.hostname]['saveData']; + if (saveData) + return decode_base64(saveData.value); + else + return null; + }, + onQuit: function() { self._finalize(); },