aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPaul Gilbert2018-11-26 22:18:45 -0800
committerPaul Gilbert2018-12-08 19:05:59 -0800
commit2331a0e9fb3dfe335c15c470d3e7da49cc7ac5f7 (patch)
tree3b895569c5b10fff83dcdb0a96e1f5a545f0aa1e
parent33a23bca1c0363340d14ed4c84e558e82a9f245e (diff)
downloadscummvm-rg350-2331a0e9fb3dfe335c15c470d3e7da49cc7ac5f7.tar.gz
scummvm-rg350-2331a0e9fb3dfe335c15c470d3e7da49cc7ac5f7.tar.bz2
scummvm-rg350-2331a0e9fb3dfe335c15c470d3e7da49cc7ac5f7.zip
GLK: FROTZ: Quetzal saving and loading now works
-rw-r--r--engines/glk/frotz/config.cpp3
-rw-r--r--engines/glk/frotz/detection.cpp8
-rw-r--r--engines/glk/frotz/frotz.cpp180
-rw-r--r--engines/glk/frotz/processor.h19
-rw-r--r--engines/glk/frotz/quetzal.cpp37
-rw-r--r--engines/glk/frotz/quetzal.h15
-rw-r--r--engines/glk/streams.cpp12
-rw-r--r--engines/glk/streams.h20
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, MAX_NESTING> _redirect;
-private:
+protected:
/**
* \defgroup General support methods
* @{
@@ -190,6 +190,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
*/
bool bufferEmpty() const { return !_bufPos; }
@@ -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; }
};
/**