aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--backends/module.mk1
-rw-r--r--backends/saves/compressed/compressed-saves.cpp277
-rw-r--r--backends/saves/compressed/compressed-saves.h51
-rw-r--r--backends/saves/default/default-saves.cpp81
4 files changed, 332 insertions, 78 deletions
diff --git a/backends/module.mk b/backends/module.mk
index 9642ea6baa..0f0dda454d 100644
--- a/backends/module.mk
+++ b/backends/module.mk
@@ -19,6 +19,7 @@ MODULE_OBJS := \
plugins/win32/win32-provider.o \
saves/savefile.o \
saves/default/default-saves.o \
+ saves/compressed/compressed-saves.o \
timer/default/default-timer.o
# Include common rules
diff --git a/backends/saves/compressed/compressed-saves.cpp b/backends/saves/compressed/compressed-saves.cpp
new file mode 100644
index 0000000000..431caf2a08
--- /dev/null
+++ b/backends/saves/compressed/compressed-saves.cpp
@@ -0,0 +1,277 @@
+/* ScummVM - Scumm Interpreter
+ * Copyright (C) 2002-2006 The ScummVM project
+ *
+ * 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.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+#include "common/stdafx.h"
+#include "common/savefile.h"
+#include "common/util.h"
+#include "backends/saves/compressed/compressed-saves.h"
+
+#if defined(USE_ZLIB)
+#include <zlib.h>
+
+
+/**
+ * A simple wrapper class which can be used to wrap around an arbitrary
+ * other InSaveFile and will then provide on-the-fly decompression support.
+ * Assumes the compressed data to be in gzip format.
+ */
+class CompressedInSaveFile : public Common::InSaveFile {
+protected:
+ enum {
+ BUFSIZE = 16384 // 1 << MAX_WBITS
+ };
+
+ byte _buf[BUFSIZE];
+
+ Common::InSaveFile *_wrapped;
+ z_stream _stream;
+ int _zlibErr;
+ uint32 _pos;
+ uint32 _origSize;
+
+public:
+
+ CompressedInSaveFile(Common::InSaveFile *w) : _wrapped(w) {
+ assert(w != 0);
+
+ _stream.zalloc = Z_NULL;
+ _stream.zfree = Z_NULL;
+ _stream.opaque = Z_NULL;
+
+ // Verify file header is correct once more
+ w->seek(0, SEEK_SET);
+ assert(w->readUint16BE() == 0x1F8B);
+
+ // Retrieve the original file size
+ w->seek(-4, SEEK_END);
+ _origSize = w->readUint32LE();
+ _pos = 0;
+ w->seek(0, SEEK_SET);
+
+ // Adding 32 to windowBits indicates to zlib that it is supposed to
+ // automatically detect whether gzip or zlib headers are used for
+ // the compressed file.
+ _zlibErr = inflateInit2(&_stream, MAX_WBITS + 32);
+ if (_zlibErr != Z_OK)
+ return;
+
+ // Setup input buffer
+ _stream.next_in = _buf;
+ _stream.avail_in = 0;
+ }
+
+ ~CompressedInSaveFile() {
+ inflateEnd(&_stream);
+ delete _wrapped;
+ }
+
+ bool ioFailed() const { return (_zlibErr != Z_OK); }
+ void clearIOFailed() { /* errors here are not recoverable! */ }
+
+ uint32 read(void *dataPtr, uint32 dataSize) {
+ _stream.next_out = (byte *)dataPtr;
+ _stream.avail_out = dataSize;
+
+ // Keep going while we get no error
+ while (_zlibErr == Z_OK && _stream.avail_out) {
+ if (_stream.avail_in == 0 && !_wrapped->eos()) {
+ // If we are out of input data: Read more data, if available.
+ _stream.next_in = _buf;
+ _stream.avail_in = _wrapped->read(_buf, BUFSIZE);
+ }
+ _zlibErr = inflate(&_stream, Z_NO_FLUSH);
+ }
+
+ // Update the position counter
+ _pos += dataSize - _stream.avail_out;
+
+ return dataSize - _stream.avail_out;
+ }
+
+ bool eos() const {
+ return (_zlibErr == Z_STREAM_END);
+ //return _pos == _origSize;
+ }
+ uint32 pos() const {
+ return _pos;
+ }
+ uint32 size() const {
+ return _origSize;
+ }
+ void seek(int32 offset, int whence = SEEK_SET) {
+ int32 newPos;
+ switch(whence) {
+ case SEEK_END:
+ newPos = size() - offset;
+ break;
+ case SEEK_SET:
+ newPos = offset;
+ break;
+ case SEEK_CUR:
+ newPos = _pos + offset;
+ }
+ offset = newPos - _pos;
+
+ if (offset < 0)
+ error("Backward seeking not supported in compressed savefiles");
+
+ // We could implement backward seeking, but it is tricky to do efficiently.
+ // A simple solution would be to restart the whole decompression from the
+ // start of the file. Or we could decompress the whole file in one go
+ // in the constructor, and wrap it into a MemoryReadStream -- but that
+ // would be rather wasteful. As long as we don't need it, I'd rather not
+ // implement this at all. -- Fingolfin
+
+ // Skip the given amount of data (very inefficient if one tries to skip
+ // huge amounts of data, but usually client code will only skip a few
+ // bytes, so this should be fine.
+ byte tmpBuf[1024];
+ while (!ioFailed() && offset > 0) {
+ offset -= read(tmpBuf, MIN((int32)sizeof(tmpBuf), offset));
+ }
+ }
+};
+
+/**
+ * A simple wrapper class which can be used to wrap around an arbitrary
+ * other OutSaveFile and will then provide on-the-fly compression support.
+ * The compressed data is written in the gzip format.
+ */
+class CompressedOutSaveFile : public Common::OutSaveFile {
+protected:
+ enum {
+ BUFSIZE = 16384 // 1 << MAX_WBITS
+ };
+
+ byte _buf[BUFSIZE];
+ Common::OutSaveFile *_wrapped;
+ z_stream _stream;
+ int _zlibErr;
+
+ void processData(int flushType) {
+ // This function is called by both write() and finalize.
+ while (_zlibErr == Z_OK && (_stream.avail_in || flushType == Z_FINISH)) {
+ if (_stream.avail_out == 0) {
+ if (_wrapped->write(_buf, BUFSIZE) != BUFSIZE) {
+ _zlibErr = Z_ERRNO;
+ break;
+ }
+ _stream.next_out = _buf;
+ _stream.avail_out = BUFSIZE;
+ }
+ _zlibErr = deflate(&_stream, flushType);
+ }
+ }
+
+public:
+ CompressedOutSaveFile(Common::OutSaveFile *w) : _wrapped(w) {
+ assert(w != 0);
+ _stream.zalloc = Z_NULL;
+ _stream.zfree = Z_NULL;
+ _stream.opaque = Z_NULL;
+
+ // adding 16 to windowBits indicates to zlib that it is supposed to
+ // write gzip headers
+ _zlibErr = deflateInit2(&_stream,
+ Z_DEFAULT_COMPRESSION,
+ Z_DEFLATED,
+ MAX_WBITS + 16,
+ 8,
+ Z_DEFAULT_STRATEGY);
+ assert(_zlibErr == Z_OK);
+
+ _stream.next_out = _buf;
+ _stream.avail_out = BUFSIZE;
+ }
+
+ ~CompressedOutSaveFile() {
+ finalize();
+ deflateEnd(&_stream);
+ delete _wrapped;
+ }
+
+ bool ioFailed() const {
+ return (_zlibErr != Z_OK && _zlibErr != Z_STREAM_END) || _wrapped->ioFailed();
+ }
+
+ void clearIOFailed() {
+ // Note: we don't reset the _zlibErr here, as it is not
+ // clear in general ho
+ _wrapped->clearIOFailed();
+ }
+
+ void finalize() {
+ if (_zlibErr != Z_OK)
+ return;
+
+ // Process whatever remaining data there is.
+ processData(Z_FINISH);
+
+ // Since processData only writes out blocks of size BUFSIZE,
+ // we may have to flush some stragglers.
+ uint remainder = BUFSIZE - _stream.avail_out;
+ if (remainder > 0) {
+ if (_wrapped->write(_buf, remainder) != remainder) {
+ _zlibErr = Z_ERRNO;
+ }
+ }
+
+ // Finalize the wrapped savefile, too
+ _wrapped->finalize();
+ }
+
+ uint32 write(const void *dataPtr, uint32 dataSize) {
+ if (ioFailed())
+ return 0;
+
+ // Hook in the new data ...
+ _stream.next_in = (Bytef*)dataPtr;
+ _stream.avail_in = dataSize;
+
+ // ... and flush it to disk
+ processData(Z_NO_FLUSH);
+
+ return dataSize - _stream.avail_in;
+ }
+};
+
+#endif // USE_ZLIB
+
+Common::InSaveFile *wrapInSaveFile(Common::InSaveFile *toBeWrapped) {
+#if defined(USE_ZLIB)
+ if (toBeWrapped) {
+ bool isCompressed = (toBeWrapped->readUint16BE() == 0x1F8B);
+ toBeWrapped->seek(-2, SEEK_CUR);
+ if (isCompressed)
+ return new CompressedInSaveFile(toBeWrapped);
+ }
+#endif
+ return toBeWrapped;
+}
+
+Common::OutSaveFile *wrapOutSaveFile(Common::OutSaveFile *toBeWrapped) {
+#if defined(USE_ZLIB)
+ if (toBeWrapped)
+ return new CompressedOutSaveFile(toBeWrapped);
+#endif
+ return toBeWrapped;
+}
diff --git a/backends/saves/compressed/compressed-saves.h b/backends/saves/compressed/compressed-saves.h
new file mode 100644
index 0000000000..5bda5183bd
--- /dev/null
+++ b/backends/saves/compressed/compressed-saves.h
@@ -0,0 +1,51 @@
+/* ScummVM - Scumm Interpreter
+ * Copyright (C) 2002-2006 The ScummVM project
+ *
+ * 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.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+#ifndef BACKEND_SAVES_COMPRESSED_H
+#define BACKEND_SAVES_COMPRESSED_H
+
+#include "common/stdafx.h"
+#include "common/savefile.h"
+
+/**
+ * Take an arbitrary InSaveFile and wrap it in a high level InSaveFile which
+ * provides transparent on-the-fly decompression support.
+ * Assumes the data it retrieves from the wrapped savefile to be either
+ * uncompressed or in gzip format. In the former case, the original
+ * savefile is returned unmodified (and in particular, not wrapped).
+ *
+ * It is safe to call this with a NULL parameter (in this case, NULL is
+ * returned).
+ */
+Common::InSaveFile *wrapInSaveFile(Common::InSaveFile *toBeWrapped);
+
+/**
+ * Take an arbitrary OutSaveFile and wrap it in a high level OutSaveFile which
+ * provides transparent on-the-fly compression support.
+ * The compressed data is written in the gzip format.
+ *
+ * It is safe to call this with a NULL parameter (in this case, NULL is
+ * returned).
+ */
+Common::OutSaveFile *wrapOutSaveFile(Common::OutSaveFile *toBeWrapped);
+
+#endif
diff --git a/backends/saves/default/default-saves.cpp b/backends/saves/default/default-saves.cpp
index 8537d4c2b6..0e5b00d316 100644
--- a/backends/saves/default/default-saves.cpp
+++ b/backends/saves/default/default-saves.cpp
@@ -24,14 +24,11 @@
#include "common/savefile.h"
#include "common/util.h"
#include "backends/saves/default/default-saves.h"
+#include "backends/saves/compressed/compressed-saves.h"
#include <stdio.h>
#include <string.h>
-#ifdef USE_ZLIB
-#include <zlib.h>
-#endif
-
#if defined(UNIX) || defined(__SYMBIAN32__)
#include <errno.h>
#include <sys/stat.h>
@@ -85,70 +82,6 @@ public:
};
-#ifdef USE_ZLIB
-class GzipSaveFile : public Common::InSaveFile, public Common::OutSaveFile {
-private:
- gzFile fh;
- bool _ioError;
-public:
- GzipSaveFile(const char *filename, bool saveOrLoad) {
- _ioError = false;
- fh = gzopen(filename, (saveOrLoad? "wb" : "rb"));
- }
- ~GzipSaveFile() {
- if (fh)
- gzclose(fh);
- }
-
- bool eos() const { return gzeof(fh) != 0; }
- bool ioFailed() const { return _ioError; }
- void clearIOFailed() { _ioError = false; }
-
- bool isOpen() const { return fh != 0; }
-
- uint32 read(void *dataPtr, uint32 dataSize) {
- assert(fh);
- int ret = gzread(fh, dataPtr, dataSize);
- if (ret <= -1)
- _ioError = true;
- return ret;
- }
- uint32 write(const void *dataPtr, uint32 dataSize) {
- assert(fh);
- // Due to a "bug" in the zlib headers (or maybe I should say,
- // a bug in the C++ spec? Whatever <g>) we have to be a bit
- // hackish here and remove the const qualifier.
- // Note that gzwrite's buf param is declared as "const voidp"
- // which you might think is the same as "const void *" but it
- // is not - rather it is equal to "void const *" which is the
- // same as "void *". Hrmpf
- int ret = gzwrite(fh, const_cast<void *>(dataPtr), dataSize);
- if (ret <= 0)
- _ioError = true;
- return ret;
- }
-
- uint32 pos() const {
- assert(fh);
- return gztell(fh);
- }
- uint32 size() const {
- assert(fh);
- uint32 oldPos = gztell(fh);
- gzseek(fh, 0, SEEK_END);
- uint32 length = gztell(fh);
- gzseek(fh, oldPos, SEEK_SET);
- return length;
- }
-
- void seek(int32 offs, int whence = SEEK_SET) {
- assert(fh);
- gzseek(fh, offs, whence);
- }
-};
-#endif
-
-
static void join_paths(const char *filename, const char *directory,
char *buf, int bufsize) {
buf[bufsize-1] = '\0';
@@ -219,34 +152,26 @@ Common::OutSaveFile *DefaultSaveFileManager::openForSaving(const char *filename)
join_paths(filename, savePath, buf, sizeof(buf));
-#ifdef USE_ZLIB
- GzipSaveFile *sf = new GzipSaveFile(buf, true);
-#else
StdioSaveFile *sf = new StdioSaveFile(buf, true);
-#endif
if (!sf->isOpen()) {
delete sf;
sf = 0;
}
- return sf;
+ return wrapOutSaveFile(sf);
}
Common::InSaveFile *DefaultSaveFileManager::openForLoading(const char *filename) {
char buf[256];
join_paths(filename, getSavePath(), buf, sizeof(buf));
-#ifdef USE_ZLIB
- GzipSaveFile *sf = new GzipSaveFile(buf, false);
-#else
StdioSaveFile *sf = new StdioSaveFile(buf, false);
-#endif
if (!sf->isOpen()) {
delete sf;
sf = 0;
}
- return sf;
+ return wrapInSaveFile(sf);
}
void DefaultSaveFileManager::listSavefiles(const char * /* prefix */, bool *marks, int num) {