/* ScummVM - Graphic Adventure Engine * * ScummVM is the legal property of its developers, whose names * are too numerous to list here. Please refer to the COPYRIGHT * file distributed with this source distribution. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * 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 received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * */ #include "glk/frotz/quetzal.h" #include "glk/frotz/processor.h" #include "common/memstream.h" namespace Glk { namespace Frotz { /** * Various parsing states within restoration. */ enum ParseState { GOT_HEADER = 0x01, GOT_STACK = 0x02, GOT_MEMORY = 0x04, GOT_NONE = 0x00, GOT_ALL = 0x07, GOT_ERROR = 0x80 }; #define WRITE_RUN(RUN) ws.writeByte(0); ws.writeByte((byte)(RUN)) bool Quetzal::save(Common::WriteStream *svf, Processor *proc, const Common::String &desc) { Processor &p = *proc; offset_t pc; zword i, j, n; zword nvars, nargs, nstk; zbyte var; int c; // Reset Quetzal writer _writer.clear(); // Write `IFhd' chunk { Common::WriteStream &ws = _writer.add(ID_IFhd); pc = p.getPC(); ws.writeUint16BE(p.h_release); ws.write(&p[H_SERIAL], 6); ws.writeUint16BE(p.h_checksum); ws.writeByte((pc >> 16) & 0xff); ws.writeByte((pc >> 8) & 0xff); ws.writeByte(pc & 0xff); } // Write `CMem' chunk. { Common::WriteStream &ws = _writer.add(ID_CMem); _storyFile->seek(0); // j holds current run length. for (i = 0, j = 0; i < p.h_dynamic_size; ++i) { c = _storyFile->readByte(); c ^= p[i]; if (c == 0) { // It's a run of equal bytes ++j; } else { // Write out any run there may be. if (j > 0) { for (; j > 0x100; j -= 0x100) { WRITE_RUN(0xFF); } WRITE_RUN(j - 1); j = 0; } // Any runs are now written. Write this (nonzero) byte ws.writeByte(c); } } } // Write `Stks' chunk. You are not expected to understand this. ;) { Common::WriteStream &ws = _writer.add(ID_Stks); // We construct a list of frame indices, most recent first, in `frames'. // These indices are the offsets into the `stack' array of the word before // the first word pushed in each frame. frames[0] = p._sp - p._stack; // The frame we'd get by doing a call now. for (i = p._fp - p._stack + 4, n = 0; i < STACK_SIZE + 4; i = p._stack[i - 3] + 5) frames[++n] = i; // All versions other than V6 can use evaluation stack outside a function // context. We write a faked stack frame (most fields zero) to cater for this. if (p.h_version != V6) { for (i = 0; i < 6; ++i) ws.writeByte(0); nstk = STACK_SIZE - frames[n]; ws.writeUint16BE(nstk); for (j = STACK_SIZE - 1; j >= frames[n]; --j) ws.writeUint16BE(p._stack[j]); } // Write out the rest of the stack frames. for (i = n; i > 0; --i) { zword *pf = p._stack + frames[i] - 4; // Points to call frame nvars = (pf[0] & 0x0F00) >> 8; nargs = pf[0] & 0x00FF; nstk = frames[i] - frames[i - 1] - nvars - 4; pc = ((uint)pf[3] << 9) | pf[2]; // Check type of call switch (pf[0] & 0xF000) { case 0x0000: // Function var = p[pc]; pc = ((pc + 1) << 8) | nvars; break; case 0x1000: // Procedure var = 0; pc = (pc << 8) | 0x10 | nvars; // Set procedure flag break; default: p.runtimeError(ERR_SAVE_IN_INTER); return 0; } if (nargs != 0) nargs = (1 << nargs) - 1; // Make args into bitmap // Write the main part of the frame... ws.writeUint32BE(pc); ws.writeByte(var); ws.writeByte(nargs); ws.writeUint16BE(nstk); // Write the variables and eval stack for (j = 0, --pf; jreadUint16BE(); if (tmpw != p.h_release) progress = GOT_ERROR; for (int idx = H_SERIAL; idx < H_SERIAL + 6; ++idx) { x = s->readByte(); if (x != p[idx]) progress = GOT_ERROR; } tmpw = s->readUint16BE(); if (tmpw != p.h_checksum) progress = GOT_ERROR; if (progress & GOT_ERROR) { p.print_string("File was not saved from this story!\n"); return fatal; } x = s->readByte(); pc = (uint)x << 16; x = s->readByte(); pc |= (uint)x << 8; x = s->readByte(); pc |= (uint)x; fatal = -1; // Setting PC means errors must be fatal p.setPC(pc); break; // `Stks' stacks chunk; restoring this is quite complex. ;) case ID_Stks: if (progress & GOT_STACK) { p.print_string("File contains two stack chunks!\n"); break; } progress |= GOT_STACK; fatal = -1; // Setting SP means errors must be fatal p._sp = p._stack + STACK_SIZE; // All versions other than V6 may use evaluation stack outside any function context. // As a result a faked function context will be present in the file here. We skip // this context, but load the associated stack onto the stack proper... if (p.h_version != V6) { if (currlen < 8) return fatal; s->skip(6); tmpw = s->readUint16BE(); if (tmpw > STACK_SIZE) { p.print_string("Save-file has too much stack (and I can't cope).\n"); return fatal; } currlen -= 8; if (currlen < (uint)tmpw * 2) return fatal; for (i = 0; i < tmpw; ++i) *--p._sp = s->readUint16BE(); currlen -= tmpw * 2; } // We now proceed to load the main block of stack frames for (p._fp = p._stack + STACK_SIZE, p._frameCount = 0; currlen > 0; currlen -= 8, ++p._frameCount) { if (currlen < 8) return fatal; if (p._sp - p._stack < 4) { // No space for frame p.print_string("Save-file has too much stack (and I can't cope).\n"); return fatal; } // Read PC, procedure flag and formal param count tmpl = s->readUint32BE(); y = (int)(tmpl & 0x0F); // Number of formals tmpw = y << 8; // Read result variable x = s->readByte(); // Check the procedure flag... if (tmpl & 0x10) { tmpw |= 0x1000; // It's a procedure tmpl >>= 8; // Shift to get PC value } else { // Functions have type 0, so no need to or anything tmpl >>= 8; // Shift to get PC value --tmpl; // Point at result byte. */ // Sanity check on result variable... if (p[tmpl] != (zbyte)x) { p.print_string("Save-file has wrong variable number on stack (possibly wrong game version?)\n"); return fatal; } } *--p._sp = (zword)(tmpl >> 9); // High part of PC *--p._sp = (zword)(tmpl & 0x1FF); // Low part of PC *--p._sp = (zword)(p._fp - p._stack - 1); // FP // Read and process argument mask x = s->readByte(); ++x; // Should now be a power of 2 for (i = 0; i<8; ++i) if (x & (1 << i)) break; if (x ^ (1 << i)) { // Not a power of 2 p.print_string("Save-file uses incomplete argument lists (which I can't handle)\n"); return fatal; } *--p._sp = tmpw | i; p._fp = p._sp; // FP for next frame // Read amount of eval stack used tmpw = s->readUint16BE(); tmpw += y; // Amount of stack + number of locals if (p._sp - p._stack <= tmpw) { p.print_string("Save-file has too much stack (and I can't cope).\n"); return fatal; } if (currlen < (uint)tmpw * 2) return fatal; for (i = 0; i < tmpw; ++i) --*p._sp = s->readUint16BE(); currlen -= tmpw * 2; } // End of `Stks' processing... break; // `CMem' compressed memory chunk; uncompress it case ID_CMem: if (!(progress & GOT_MEMORY)) { _storyFile->seek(0); i = 0; // Bytes written to data area for (; currlen > 0; --currlen) { x = s->readByte(); if (x == 0) { // Start of run // Check for bogus run if (currlen < 2) { p.print_string("File contains bogus `CMem' chunk.\n"); s->skip(currlen); currlen = 1; i = 0xFFFF; break; // Keep going; may be a `UMem' too } // Copy story file to memory during the run --currlen; x = s->readByte(); for (; x >= 0 && i < p.h_dynamic_size; --x, ++i) p[i] = _storyFile->readByte(); } else { // Not a run y = _storyFile->readByte(); p[i] = (zbyte)(x ^ y); ++i; } // Make sure we don't load too much if (i > p.h_dynamic_size) { p.print_string("warning: `CMem' chunk too long!\n"); s->skip(currlen); break; // Keep going; there may be a `UMem' too } } // If chunk is short, assume a run for (; i < p.h_dynamic_size; ++i) p[i] = _storyFile->readByte(); if (currlen == 0) progress |= GOT_MEMORY; // Only if succeeded break; } break; // 'UMem' Uncompressed memory chunk case ID_UMem: if (!(progress & GOT_MEMORY)) { // Must be exactly the right size if (currlen == p.h_dynamic_size) { if (s->read(p.zmp, currlen) == currlen) { progress |= GOT_MEMORY; // Only on success break; } } else { p.print_string("`UMem' chunk wrong size!\n"); } // Fall into default action (skip chunk) on errors } break; default: break; } delete s; } // We've reached the end of the file. For the restoration to have been a // success, we must have had one of each of the required chunks. if (!(progress & GOT_HEADER)) p.print_string("error: no valid header (`IFhd') chunk in file.\n"); if (!(progress & GOT_STACK)) p.print_string("error: no valid stack (`Stks') chunk in file.\n"); if (!(progress & GOT_MEMORY)) p.print_string("error: no valid memory (`CMem' or `UMem') chunk in file.\n"); return (progress == GOT_ALL ? 2 : fatal); } } // End of namespace Frotz } // End of namespace Glk