From 2331a0e9fb3dfe335c15c470d3e7da49cc7ac5f7 Mon Sep 17 00:00:00 2001 From: Paul Gilbert Date: Mon, 26 Nov 2018 22:18:45 -0800 Subject: GLK: FROTZ: Quetzal saving and loading now works --- engines/glk/frotz/config.cpp | 3 +- engines/glk/frotz/detection.cpp | 8 +- engines/glk/frotz/frotz.cpp | 180 +++++++--------------------------------- engines/glk/frotz/processor.h | 19 ++--- engines/glk/frotz/quetzal.cpp | 37 +++++---- engines/glk/frotz/quetzal.h | 15 ++-- engines/glk/streams.cpp | 12 +-- engines/glk/streams.h | 20 +++++ 8 files changed, 99 insertions(+), 195 deletions(-) diff --git a/engines/glk/frotz/config.cpp b/engines/glk/frotz/config.cpp index 21cfb03333..e53cd8c9ba 100644 --- a/engines/glk/frotz/config.cpp +++ b/engines/glk/frotz/config.cpp @@ -145,11 +145,10 @@ void Header::loadHeader(Common::SeekableReadStream &f) { /*--------------------------------------------------------------------------*/ -UserOptions::UserOptions() : _undo_slots(MAX_UNDO_SLOTS), _sound(true) { +UserOptions::UserOptions() : _undo_slots(MAX_UNDO_SLOTS), _sound(true), _quetzal(true) { _err_report_mode = getConfigInt("err_report_mode", ERR_REPORT_ONCE, ERR_REPORT_FATAL); _ignore_errors = getConfigBool("ignore_errors"); _expand_abbreviations = getConfigBool("expand_abbreviations"); - _quetzal = getConfigBool("quetzal", true); _tandyBit = getConfigBool("tandy_bit"); _piracy = getConfigBool("piracy"); _script_cols = getConfigInt("wrap_script_lines", 80, 999); diff --git a/engines/glk/frotz/detection.cpp b/engines/glk/frotz/detection.cpp index 6e1d23c963..895dd4e1ab 100644 --- a/engines/glk/frotz/detection.cpp +++ b/engines/glk/frotz/detection.cpp @@ -22,6 +22,7 @@ #include "glk/frotz/detection.h" #include "glk/frotz/detection_tables.h" +#include "glk/frotz/quetzal.h" #include "common/debug.h" #include "common/file.h" #include "common/md5.h" @@ -116,10 +117,10 @@ bool FrotzMetaEngine::detectGames(const Common::FSList &fslist, DetectedGames &g bool FrotzMetaEngine::readSavegameHeader(Common::SeekableReadStream *stream, Glk::SavegameHeader &header) { stream->seek(0); - if (stream->readUint32BE() != MKTAG('F', 'O', 'R', 'M')) + if (stream->readUint32BE() != ID_FORM) return false; stream->readUint32BE(); - if (stream->readUint32BE() != MKTAG('I', 'F', 'Z', 'S')) + if (stream->readUint32BE() != ID_IFZS) return false; header._interpType = INTERPRETER_FROTZ; @@ -129,13 +130,14 @@ bool FrotzMetaEngine::readSavegameHeader(Common::SeekableReadStream *stream, Glk uint type = stream->readUint32BE(); size_t len = stream->readUint32BE(); - if (type == MKTAG('A', 'N', 'N', 'O')) { + if (type == ID_ANNO) { // Read savegame name from the annotation chunk char *buffer = new char[len + 1]; stream->read(buffer, len); buffer[len] = '\0'; header._saveName = Common::String(buffer); break; + } else { if (len & 1) // Length must be even diff --git a/engines/glk/frotz/frotz.cpp b/engines/glk/frotz/frotz.cpp index 993c65e8e2..2ec7585057 100644 --- a/engines/glk/frotz/frotz.cpp +++ b/engines/glk/frotz/frotz.cpp @@ -23,6 +23,7 @@ #include "glk/frotz/frotz.h" #include "glk/frotz/frotz_types.h" #include "glk/frotz/screen.h" +#include "glk/frotz/quetzal.h" #include "common/config-manager.h" namespace Glk { @@ -65,167 +66,44 @@ void Frotz::initialize() { } Common::Error Frotz::saveGameData(strid_t file, const Common::String &desc) { -#ifdef TODO - long pc; - zword addr; - zword nsp, nfp; - int skip; - int i; + Quetzal q(story_fp); + bool success = q.save(*file, this, desc); - // Open game file - - if ((gfp = frotzopenprompt(FILE_SAVE)) == nullptr) - goto finished; - - if (_quetzal) { - success = save_quetzal(gfp, story_fp, blorb_ofs); - } - else { - // Write game file - - fputc((int)hi(h_release), gfp); - fputc((int)lo(h_release), gfp); - fputc((int)hi(h_checksum), gfp); - fputc((int)lo(h_checksum), gfp); - - GET_PC(pc) - - fputc((int)(pc >> 16) & 0xff, gfp); - fputc((int)(pc >> 8) & 0xff, gfp); - fputc((int)(pc)& 0xff, gfp); - - nsp = (int)(_sp - _stack); - nfp = (int)(_fp - _stack); - - fputc((int)hi(nsp), gfp); - fputc((int)lo(nsp), gfp); - fputc((int)hi(nfp), gfp); - fputc((int)lo(nfp), gfp); - - for (i = nsp; i < STACK_SIZE; i++) { - fputc((int)hi(_stack[i]), gfp); - fputc((int)lo(_stack[i]), gfp); - } - - fseek(story_fp, blorb_ofs, SEEK_SET); - - for (addr = 0, skip = 0; addr < h_dynamic_size; addr++) - if (zmp[addr] != fgetc(story_fp) || skip == 255 || addr + 1 == h_dynamic_size) { - fputc(skip, gfp); - fputc(zmp[addr], gfp); - skip = 0; - } - else skip++; - } - - // Close game file and check for errors - - if (fclose(gfp) == EOF || ferror(story_fp)) { + if (!success) print_string("Error writing save file\n"); - goto finished; - } - // Success - success = 1; -#endif return Common::kNoError; } Common::Error Frotz::loadGameData(strid_t file) { -#ifdef TODO - long pc; - zword release; - zword addr; - int i; - - // Open game file - if ((gfp = frotzopenprompt(FILE_RESTORE)) == nullptr) - goto finished; - - if (_quetzal) { - success = restore_quetzal (gfp, story_fp, blorb_ofs); - + Quetzal q(story_fp); + bool success = q.restore(*file, this) == 2; + + if (success) { + zbyte old_screen_rows; + zbyte old_screen_cols; + + // In V3, reset the upper window. + if (h_version == V3) + split_window(0); + + LOW_BYTE(H_SCREEN_ROWS, old_screen_rows); + LOW_BYTE(H_SCREEN_COLS, old_screen_cols); + + // Reload cached header fields + restart_header (); + + /* Since QUETZAL files may be saved on many different machines, + * the screen sizes may vary a lot. Erasing the status window + * seems to cover up most of the resulting badness. + */ + if (h_version > V3 && h_version != V6 && (h_screen_rows != old_screen_rows + || h_screen_cols != old_screen_cols)) + erase_window (1); } else { - // Load game file - - release = (unsigned) fgetc (gfp) << 8; - release |= fgetc (gfp); - - () fgetc (gfp); - () fgetc (gfp); - - // Check the release number - if (release == h_release) { - - pc = (long) fgetc (gfp) << 16; - pc |= (unsigned) fgetc (gfp) << 8; - pc |= fgetc (gfp); - - SET_PC (pc); - - _sp = _stack + (fgetc (gfp) << 8); - _sp += fgetc (gfp); - _fp = _stack + (fgetc (gfp) << 8); - _fp += fgetc (gfp); - - for (i = (int) (_sp - _stack); i < STACK_SIZE; i++) { - _stack[i] = (unsigned) fgetc (gfp) << 8; - _stack[i] |= fgetc (gfp); - } - - fseek (story_fp, blorb_ofs, SEEK_SET); - - for (addr = 0; addr < h_dynamic_size; addr++) { - int skip = fgetc (gfp); - for (i = 0; i < skip; i++) - zmp[addr++] = fgetc (story_fp); - zmp[addr] = fgetc (gfp); - () fgetc (story_fp); - } - - // Check for errors - if (ferror (gfp) || ferror (story_fp) || addr != h_dynamic_size) - success = -1; - else - - // Success - success = 2; - - } else print_string ("Invalid save file\n"); + error("Error reading save file"); } - if ((short) success >= 0) { - - // Close game file - fclose (gfp); - - if ((short) success > 0) { - zbyte old_screen_rows; - zbyte old_screen_cols; - - // In V3, reset the upper window. - if (h_version == V3) - split_window (0); - - LOW_BYTE (H_SCREEN_ROWS, old_screen_rows); - LOW_BYTE (H_SCREEN_COLS, old_screen_cols); - - /* Reload cached header fields. */ - restart_header (); - - /* - * Since QUETZAL files may be saved on many different machines, - * the screen sizes may vary a lot. Erasing the status window - * seems to cover up most of the resulting badness. - */ - if (h_version > V3 && h_version != V6 - && (h_screen_rows != old_screen_rows - || h_screen_cols != old_screen_cols)) - erase_window (1); - } - } else - os_fatal ("Error reading save file"); -#endif return Common::kNoError; } diff --git a/engines/glk/frotz/processor.h b/engines/glk/frotz/processor.h index 77a673df9d..fb9c12f86f 100644 --- a/engines/glk/frotz/processor.h +++ b/engines/glk/frotz/processor.h @@ -95,7 +95,7 @@ private: bool istream_replay; bool message; Common::FixedStack _redirect; -private: +protected: /** * \defgroup General support methods * @{ @@ -189,6 +189,11 @@ private: */ void new_line(); + /** + * Copy the contents of the text buffer to the output streams. + */ + void flush_buffer(); + /** * Returns true if the buffer is empty */ @@ -1540,18 +1545,6 @@ protected: void z_store(); /**@}*/ - - /** - * \defgroup Input support methods - * @{ - */ - - /** - * Copy the contents of the text buffer to the output streams. - */ - void flush_buffer(); - - /**@}*/ public: /** * Constructor diff --git a/engines/glk/frotz/quetzal.cpp b/engines/glk/frotz/quetzal.cpp index 9ff33ab8ae..d727515890 100644 --- a/engines/glk/frotz/quetzal.cpp +++ b/engines/glk/frotz/quetzal.cpp @@ -50,9 +50,9 @@ bool Quetzal::read_long(Common::ReadStream *f, uint *result) { return true; } -bool Quetzal::save(Common::WriteStream *svf, Processor *proc) { +bool Quetzal::save(Common::WriteStream *svf, Processor *proc, const Common::String &desc) { Processor &p = *proc; - uint ifzslen = 0, cmemlen = 0, stkslen = 0; + uint ifzslen = 0, cmemlen = 0, stkslen = 0, descLen = 0; uint pc; zword i, j, n; zword nvars, nargs, nstk; @@ -79,10 +79,20 @@ bool Quetzal::save(Common::WriteStream *svf, Processor *proc) { write_word(p.h_checksum); write_long(pc << 8); // Includes pad + // Write 'ANNO' chunk + descLen = desc.size() + 1; + write_chnk(ID_ANNO, descLen); + saveData.write(desc.c_str(), desc.size()); + write_byte(0); + if ((desc.size() % 2) == 0) { + write_byte(0); + ++descLen; + } + // Write `CMem' chunk. - cmempos = svf->pos(); + cmempos = saveData.pos(); write_chnk(ID_CMem, 0); - _storyFile->seek(_blorbOffset); + _storyFile->seek(0); // j holds current run length. for (i = 0, j = 0, cmemlen = 0; i < p.h_dynamic_size; ++i) { @@ -116,7 +126,7 @@ bool Quetzal::save(Common::WriteStream *svf, Processor *proc) { write_byte(0); // Write `Stks' chunk. You are not expected to understand this. ;) - stkspos = _storyFile->pos(); + stkspos = saveData.pos(); write_chnk(ID_Stks, 0); // We construct a list of frame indices, most recent first, in `frames'. @@ -182,7 +192,7 @@ bool Quetzal::save(Common::WriteStream *svf, Processor *proc) { } // Fill in variable chunk lengths - ifzslen = 3 * 8 + 4 + 14 + cmemlen + stkslen; + ifzslen = 4 * 8 + 4 + 14 + cmemlen + stkslen + descLen; if (cmemlen & 1) ++ifzslen; @@ -281,7 +291,7 @@ int Quetzal::restore(Common::SeekableReadStream *svf, Processor *proc) { fatal = -1; // Setting PC means errors must be fatal p.setPC(pc); - svf->skip(13); // Skip rest of chunk + svf->skip(currlen - 13); // Skip rest of chunk break; // `Stks' stacks chunk; restoring this is quite complex. ;) @@ -394,8 +404,7 @@ int Quetzal::restore(Common::SeekableReadStream *svf, Processor *proc) { // `CMem' compressed memory chunk; uncompress it case ID_CMem: if (!(progress & GOT_MEMORY)) { - // Don't complain if two - _storyFile->seek(_blorbOffset); + _storyFile->seek(0); i = 0; // Bytes written to data area for (; currlen > 0; --currlen) { @@ -416,10 +425,10 @@ int Quetzal::restore(Common::SeekableReadStream *svf, Processor *proc) { --currlen; x = svf->readByte(); for (; x >= 0 && i < p.h_dynamic_size; --x, ++i) - p[i] = svf->readByte(); + p[i] = _storyFile->readByte(); } else { // Not a run - y = svf->readByte(); + y = _storyFile->readByte(); p[i] = (zbyte)(x ^ y); ++i; } @@ -434,14 +443,14 @@ int Quetzal::restore(Common::SeekableReadStream *svf, Processor *proc) { // If chunk is short, assume a run for (; i < p.h_dynamic_size; ++i) - p[i] = svf->readByte(); + p[i] = _storyFile->readByte(); if (currlen == 0) progress |= GOT_MEMORY; // Only if succeeded break; } - // Intentional fall-through + // Intentional fall-through on error case ID_UMem: if (!(progress & GOT_MEMORY)) { @@ -458,7 +467,7 @@ int Quetzal::restore(Common::SeekableReadStream *svf, Processor *proc) { // Fall into default action (skip chunk) on errors } - // Intentional fall-through + // Intentional fall-through on error default: svf->seek(currlen, SEEK_CUR); // Skip chunk diff --git a/engines/glk/frotz/quetzal.h b/engines/glk/frotz/quetzal.h index 9d382abaf0..98765b8319 100644 --- a/engines/glk/frotz/quetzal.h +++ b/engines/glk/frotz/quetzal.h @@ -36,7 +36,8 @@ enum QueztalTag { ID_UMem = MKTAG('U', 'M', 'e', 'm'), ID_CMem = MKTAG('C', 'M', 'e', 'm'), ID_Stks = MKTAG('S', 't', 'k', 's'), - ID_ANNO = MKTAG('A', 'N', 'N', 'O') + ID_ANNO = MKTAG('A', 'N', 'N', 'O'), + ID_SCVM = MKTAG('S', 'C', 'V', 'M') }; class Processor; @@ -45,8 +46,6 @@ class Quetzal { private: Common::SeekableReadStream *_storyFile; Common::WriteStream *_out; - size_t _blorbOffset; - int _slot; zword frames[STACK_SIZE / 4 + 1]; private: /** @@ -63,7 +62,7 @@ private: void write_bytx(zword b) { _out->writeByte(b & 0xFF); } void write_word(zword w) { _out->writeUint16BE(w); } void write_long(uint l) { _out->writeUint32BE(l); } - void write_run(zword run) { _out->writeUint16LE(run); } + void write_run(zword run) { write_byte(0); write_byte(run); } void write_chnk(QueztalTag id, zword len) { _out->writeUint32BE(id); _out->writeUint32BE(len); @@ -72,19 +71,21 @@ public: /** * Constructor */ - Quetzal(Common::SeekableReadStream *storyFile, size_t blorbOffset, int slot) : - _storyFile(storyFile), _blorbOffset(blorbOffset), _slot(slot) {} + Quetzal(Common::SeekableReadStream *storyFile) : _storyFile(storyFile) {} /* * Save a game using Quetzal format. * @param svf Savegame file + * @param proc Pointer to the Frotz processor + * @param desc Savegame description * @returns Returns true if OK, false if failed */ - bool save(Common::WriteStream *svf, Processor *proc); + bool save(Common::WriteStream *svf, Processor *proc, const Common::String &desc); /** * Restore a saved game using Quetzal format * @param svf Savegame file + * @param proc Pointer to the Frotz processor * @returns Return 2 if OK, 0 if an error occurred before any damage was done, * -1 on a fatal error */ diff --git a/engines/glk/streams.cpp b/engines/glk/streams.cpp index 80f03dd7cb..d6c71cab3a 100644 --- a/engines/glk/streams.cpp +++ b/engines/glk/streams.cpp @@ -768,7 +768,7 @@ FileStream::FileStream(Streams *streams, frefid_t fref, glui32 fmode, glui32 roc Common::String fname = fref->_slotNumber == -1 ? fref->_filename : fref->getSaveName(); if (fmode == filemode_Write || fmode == filemode_ReadWrite || fmode == filemode_WriteAppend) { - _outFile = g_system->getSavefileManager()->openForSaving(fname, fref->_slotNumber != -1); + _outFile = g_system->getSavefileManager()->openForSaving(fname, fref->_slotNumber != -1 && g_vm->getInterpreterType() != INTERPRETER_FROTZ); if (!_outFile) error("Could open file for writing - %s", fname.c_str()); @@ -795,11 +795,13 @@ FileStream::FileStream(Streams *streams, frefid_t fref, glui32 fmode, glui32 roc readSavegameHeader(_inStream, header))) error("Invalid savegame"); - if (header._interpType != g_vm->getInterpreterType() || header._language != g_vm->getLanguage() - || header._md5 != g_vm->getGameMD5()) - error("Savegame is for a different game"); + if (g_vm->getInterpreterType() != INTERPRETER_FROTZ) { + if (header._interpType != g_vm->getInterpreterType() || header._language != g_vm->getLanguage() + || header._md5 != g_vm->getGameMD5()) + error("Savegame is for a different game"); - g_vm->_events->setTotalPlayTicks(header._totalFrames); + g_vm->_events->setTotalPlayTicks(header._totalFrames); + } } } } diff --git a/engines/glk/streams.h b/engines/glk/streams.h index 3150ce9c38..0b82b36a26 100644 --- a/engines/glk/streams.h +++ b/engines/glk/streams.h @@ -283,6 +283,16 @@ public: * Set the reverse video style */ virtual void setReverseVideo(bool reverse); + + /** + * Cast a stream to a ScummVM write stream + */ + virtual operator Common::WriteStream *() const { return nullptr; } + + /** + * Cast a stream to a ScummVM read stream + */ + virtual operator Common::SeekableReadStream *() const { return nullptr; } }; typedef Stream *strid_t; @@ -528,6 +538,16 @@ public: * Get a unicode line */ virtual glui32 getLineUni(glui32 *ubuf, glui32 len) override; + + /** + * Cast a stream to a ScummVM write stream + */ + virtual operator Common::WriteStream *() const override { return _outFile; } + + /** + * Cast a stream to a ScummVM read stream + */ + virtual operator Common::SeekableReadStream *() const override { return _inStream; } }; /** -- cgit v1.2.3