Logo Search packages:      
Sourcecode: scummvm version File versions  Download package

zlib.cpp

/* 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.
 *
 * $URL: https://scummvm.svn.sourceforge.net/svnroot/scummvm/scummvm/tags/release-1-2-1/common/zlib.cpp $
 * $Id: zlib.cpp 44495 2009-09-30 16:16:53Z fingolfin $
 *
 */

#include "common/zlib.h"
#include "common/util.h"

#if defined(USE_ZLIB)
  #ifdef __SYMBIAN32__
    #include <zlib\zlib.h>
  #else
    #include <zlib.h>
  #endif

  #if ZLIB_VERNUM < 0x1204
  #error Version 1.2.0.4 or newer of zlib is required for this code
  #endif
#endif


namespace Common {

#if defined(USE_ZLIB)

bool uncompress(byte *dst, unsigned long *dstLen, const byte *src, unsigned long srcLen) {
      return Z_OK == ::uncompress(dst, dstLen, src, srcLen);
}

/**
 * A simple wrapper class which can be used to wrap around an arbitrary
 * other SeekableReadStream and will then provide on-the-fly decompression support.
 * Assumes the compressed data to be in gzip format.
 */
class GZipReadStream : public Common::SeekableReadStream {
protected:
      enum {
            BUFSIZE = 16384         // 1 << MAX_WBITS
      };

      byte  _buf[BUFSIZE];

      Common::SeekableReadStream *_wrapped;
      z_stream _stream;
      int _zlibErr;
      uint32 _pos;
      uint32 _origSize;
      bool _eos;

public:

      GZipReadStream(Common::SeekableReadStream *w) : _wrapped(w) {
            assert(w != 0);

            _stream.zalloc = Z_NULL;
            _stream.zfree = Z_NULL;
            _stream.opaque = Z_NULL;

            // Verify file header is correct
            w->seek(0, SEEK_SET);
            uint16 header = w->readUint16BE();
            assert(header == 0x1F8B ||
                   ((header & 0x0F00) == 0x0800 && header % 31 == 0));

            if (header == 0x1F8B) {
                  // Retrieve the original file size
                  w->seek(-4, SEEK_END);
                  _origSize = w->readUint32LE();
            } else {
                  // Original size not available in zlib format
                  _origSize = 0;
            }
            _pos = 0;
            w->seek(0, SEEK_SET);
            _eos = false;

            // 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. This feature was added in zlib 1.2.0.4,
            // released 10 August 2003.
            // Note: This is *crucial* for savegame compatibility, do *not* remove!
            _zlibErr = inflateInit2(&_stream, MAX_WBITS + 32);
            if (_zlibErr != Z_OK)
                  return;

            // Setup input buffer
            _stream.next_in = _buf;
            _stream.avail_in = 0;
      }

      ~GZipReadStream() {
            inflateEnd(&_stream);
            delete _wrapped;
      }

      bool err() const { return (_zlibErr != Z_OK) && (_zlibErr != Z_STREAM_END); }
      void clearErr() {
            // only reset _eos; I/O errors are not recoverable
            _eos = false;
      }

      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;

            if (_zlibErr == Z_STREAM_END && _stream.avail_out > 0)
                  _eos = true;

            return dataSize - _stream.avail_out;
      }

      bool eos() const {
            return _eos;
      }
      int32 pos() const {
            return _pos;
      }
      int32 size() const {
            return _origSize;
      }
      bool seek(int32 offset, int whence = SEEK_SET) {
            int32 newPos = 0;
            assert(whence != SEEK_END);   // SEEK_END not supported
            switch (whence) {
            case SEEK_SET:
                  newPos = offset;
                  break;
            case SEEK_CUR:
                  newPos = _pos + offset;
            }

            assert(newPos >= 0);

            if ((uint32)newPos < _pos) {
                  // To search backward, we have to restart the whole decompression
                  // from the start of the file. A rather wasteful operation, best
                  // to avoid it. :/
#if DEBUG
                  warning("Backward seeking in GZipReadStream detected");
#endif
                  _pos = 0;
                  _wrapped->seek(0, SEEK_SET);
                  _zlibErr = inflateReset(&_stream);
                  if (_zlibErr != Z_OK)
                        return false;     // FIXME: STREAM REWRITE
                  _stream.next_in = _buf;
                  _stream.avail_in = 0;
            }

            offset = newPos - _pos;

            // 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 (!err() && offset > 0) {
                  offset -= read(tmpBuf, MIN((int32)sizeof(tmpBuf), offset));
            }

            _eos = false;
            return true;      // FIXME: STREAM REWRITE
      }
};

/**
 * A simple wrapper class which can be used to wrap around an arbitrary
 * other WriteStream and will then provide on-the-fly compression support.
 * The compressed data is written in the gzip format.
 */
class GZipWriteStream : public Common::WriteStream {
protected:
      enum {
            BUFSIZE = 16384         // 1 << MAX_WBITS
      };

      byte  _buf[BUFSIZE];
      Common::WriteStream *_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:
      GZipWriteStream(Common::WriteStream *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. This feature was added in zlib 1.2.0.4,
            // released 10 August 2003.
            // Note: This is *crucial* for savegame compatibility, do *not* remove!
            _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;
            _stream.avail_in = 0;
            _stream.next_in = 0;
      }

      ~GZipWriteStream() {
            finalize();
            deflateEnd(&_stream);
            delete _wrapped;
      }

      bool err() const {
            // CHECKME: does Z_STREAM_END make sense here?
            return (_zlibErr != Z_OK && _zlibErr != Z_STREAM_END) || _wrapped->err();
      }

      void clearErr() {
            // Note: we don't reset the _zlibErr here, as it is not
            // clear in general how
            _wrapped->clearErr();
      }

      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 (err())
                  return 0;

            // Hook in the new data ...
            // Note: We need to make a const_cast here, as zlib is not aware
            // of the const keyword.
            _stream.next_in = const_cast<byte *>((const byte *)dataPtr);
            _stream.avail_in = dataSize;

            // ... and flush it to disk
            processData(Z_NO_FLUSH);

            return dataSize - _stream.avail_in;
      }
};

#endif      // USE_ZLIB

Common::SeekableReadStream *wrapCompressedReadStream(Common::SeekableReadStream *toBeWrapped) {
#if defined(USE_ZLIB)
      if (toBeWrapped) {
            uint16 header = toBeWrapped->readUint16BE();
            bool isCompressed = (header == 0x1F8B ||
                             ((header & 0x0F00) == 0x0800 &&
                              header % 31 == 0));
            toBeWrapped->seek(-2, SEEK_CUR);
            if (isCompressed)
                  return new GZipReadStream(toBeWrapped);
      }
#endif
      return toBeWrapped;
}

Common::WriteStream *wrapCompressedWriteStream(Common::WriteStream *toBeWrapped) {
#if defined(USE_ZLIB)
      if (toBeWrapped)
            return new GZipWriteStream(toBeWrapped);
#endif
      return toBeWrapped;
}


}     // End of namespace Common

Generated by  Doxygen 1.6.0   Back to index