Replaced the old code for compressed savegames (which was using the gzopen/gzread/etc. API, and thuse tied to FILE/fopen/fread/etc.) with a new wrapper approach, which allows reading/writing gzip data via arbitrary SaveFile implementations, and thus can be used with custom savefile implementations

svn-id: r25669
This commit is contained in:
Max Horn 2007-02-18 02:25:39 +00:00
parent 5fc65f2230
commit 7f07e6e48a
4 changed files with 332 additions and 78 deletions

View File

@ -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

View File

@ -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;
}

View File

@ -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

View File

@ -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) {