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

barchive.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/engines/draci/barchive.cpp $
 * $Id: barchive.cpp 50614 2010-07-03 03:48:26Z spalek $
 *
 */

#include "common/debug.h"
#include "common/str.h"
#include "common/stream.h"

#include "draci/barchive.h"
#include "draci/draci.h"

namespace Draci {

const char BArchive::_magicNumber[] = "BAR!";
const char BArchive::_dfwMagicNumber[] = "BS";

/**
 * @brief Loads a DFW archive
 * @param path Path to input file
 *
 * Tries to load the file as a DFW archive if opening as BAR fails. Should only be called
 * from openArchive(). Only one of the game files appears to use this format (HRA.DFW)
 * and this file is compressed using a simple run-length scheme.
 *
 * archive format: header
 *                 index table
 *                 file0, file1, ...
 *
 * header format: [uint16LE] file count
 *                [uint16LE] index table size
 *                [2 bytes]  magic number "BS"
 *
 * index table format: entry0, entry1, ...
 *
 * entry<N> format: [uint16LE] compressed size (not including the 2 bytes for the
 *                             "uncompressed size" field)
 *                  [uint32LE] fileN offset from start of file
 *
 * file<N> format: [uint16LE] uncompressed size
 *                 [uint16LE] compressed size (the same as in the index table entry)
 *                 [byte] stopper mark (for run-length compression)
 *                 [multiple bytes] compressed data
 */

00066 void BArchive::openDFW(const Common::String &path) {
      byte *table;
      uint16 tableSize;
      byte buf[2];

      _f.open(path);
      if (!_f.isOpen()) {
            debugC(2, kDraciArchiverDebugLevel, "Error opening file");
            return;
      }

      _fileCount = _f.readUint16LE();
      tableSize = _f.readUint16LE();

      _f.read(buf, 2);
      if (memcmp(buf, _dfwMagicNumber, 2) == 0) {
            debugC(2, kDraciArchiverDebugLevel, "Success");
            _isDFW = true;
      } else {
            debugC(2, kDraciArchiverDebugLevel, "Not a DFW archive");
            _f.close();
            return;
      }

      debugC(2, kDraciArchiverDebugLevel, "Archive info (DFW): %d files", _fileCount);

      // Read in index table
      table = new byte[tableSize];
      _f.read(table, tableSize);

      // Read in file headers, but do not read the actual data yet
      // The data will be read on demand to save memory
      _files = new BAFile[_fileCount];
      Common::MemoryReadStream tableReader(table, tableSize);
      for (uint i = 0; i < _fileCount; ++i) {
            _files[i]._compLength = tableReader.readUint16LE();
            _files[i]._offset = tableReader.readUint32LE();

            // Seek to the current file
            _f.seek(_files[i]._offset);

            _files[i]._length = _f.readUint16LE(); // Read in uncompressed length
            _f.readUint16LE(); // Compressed length again (already read from the index table)
            _files[i]._stopper = _f.readByte();

            _files[i]._data = NULL; // File data will be read in on demand
            _files[i]._crc = 0; // Dummy value; not used in DFW archives
      }

      // Indicate that the archive was successfully opened
      _opened = true;

      // Cleanup
      delete[] table;
}

/**
 * @brief BArchive open method
 * @param path Path to input file
 *
 * Opens a BAR (Bob's Archiver) archive, which is the game's archiving format.
 * BAR archives have a .DFW file extension, due to a historical interface.
 *
 * archive format: header,
 *                 file0, file1, ...
 *                 footer
 *
 * header format: [4 bytes] magic number "BAR!"
 *                [uint16LE] file count (number of archived streams),
 *                [uint32LE] footer offset from start of file
 *
 * file<N> format: [2 bytes] compressed length
 *                 [2 bytes] original length
 *                 [1 byte] compression type
 *                 [1 byte] CRC
 *                 [multiple bytes] actual data
 *
 * footer format: [array of uint32LE] offsets of individual files from start of archive
 *                (last entry is footer offset again)
 */

00147 void BArchive::openArchive(const Common::String &path) {
      byte buf[4];
      byte *footer;
      uint32 footerOffset, footerSize;

      // Close previously opened archive (if any)
      closeArchive();

      debugCN(2, kDraciArchiverDebugLevel, "Loading archive %s: ", path.c_str());

      _f.open(path);
      if (_f.isOpen()) {
            debugC(2, kDraciArchiverDebugLevel, "Success");
      } else {
            debugC(2, kDraciArchiverDebugLevel, "Error");
            return;
      }

      // Save path for reading in files later on
      _path = path;

      // Read archive header
      debugCN(2, kDraciArchiverDebugLevel, "Checking for BAR magic number: ");

      _f.read(buf, 4);
      if (memcmp(buf, _magicNumber, 4) == 0) {
            debugC(2, kDraciArchiverDebugLevel, "Success");

            // Indicate this archive is a BAR
            _isDFW = false;
      } else {
            debugC(2, kDraciArchiverDebugLevel, "Not a BAR archive");
            debugCN(2, kDraciArchiverDebugLevel, "Retrying as DFW: ");
            _f.close();

            // Try to open as DFW
            openDFW(_path);

            return;
      }

      _fileCount = _f.readUint16LE();
      footerOffset = _f.readUint32LE();
      footerSize = _f.size() - footerOffset;

      debugC(2, kDraciArchiverDebugLevel, "Archive info: %d files, %d data bytes",
            _fileCount, footerOffset - _archiveHeaderSize);

      // Read in footer
      footer = new byte[footerSize];
      _f.seek(footerOffset);
      _f.read(footer, footerSize);
      Common::MemoryReadStream reader(footer, footerSize);

      // Read in file headers, but do not read the actual data yet
      // The data will be read on demand to save memory
      _files = new BAFile[_fileCount];

      for (uint i = 0; i < _fileCount; i++) {
            uint32 fileOffset;

            fileOffset = reader.readUint32LE();
            _f.seek(fileOffset);                                  // Seek to next file in archive

            _files[i]._compLength = _f.readUint16LE();      // Compressed size
                                                                              // should be the same as uncompressed

            _files[i]._length = _f.readUint16LE();          // Original size

            _files[i]._offset = fileOffset;                       // Offset of file from start

            assert(_f.readByte() == 0 &&
                  "Compression type flag is non-zero (file is compressed)");

            _files[i]._crc = _f.readByte();     // CRC checksum of the file
            _files[i]._data = NULL;             // File data will be read in on demand
            _files[i]._stopper = 0;             // Dummy value; not used in BAR files, needed in DFW
      }

      // Last footer item should be equal to footerOffset
      assert(reader.readUint32LE() == footerOffset && "Footer offset mismatch");

      // Indicate that the archive has been successfully opened
      _opened = true;

      delete[] footer;
}

/**
 * @brief BArchive close method
 *
 * Closes the currently opened archive. It can be called explicitly to
 * free up memory.
 */
00241 void BArchive::closeArchive() {
      if (!_opened) {
            return;
      }

      for (uint i = 0; i < _fileCount; ++i) {
            if (_files[i]._data) {
                  delete[] _files[i]._data;
            }
      }

      delete[] _files;
      _f.close();

      _opened = false;
      _files = NULL;
      _fileCount = 0;
}

/**
 * @brief On-demand BAR file loader
 * @param i Index of file inside an archive
 * @return Pointer to a BAFile coresponding to the opened file or NULL (on failure)
 *
 * Loads individual BAR files from an archive to memory on demand.
 * Should not be called directly.
 */
00268 BAFile *BArchive::loadFileBAR(uint i) {
      // Else open archive and read in requested file
      if (!_f.isOpen()) {
            debugC(2, kDraciArchiverDebugLevel, "Error");
            return NULL;
      }

      // Read in the file (without the file header)
      _f.seek(_files[i]._offset + _fileHeaderSize);
      _files[i]._data = new byte[_files[i]._length];
      _f.read(_files[i]._data, _files[i]._length);

      // Calculate CRC
      byte tmp = 0;
      for (uint j = 0; j < _files[i]._length; j++) {
            tmp ^= _files[i]._data[j];
      }

      debugC(2, kDraciArchiverDebugLevel, "Read %d bytes", _files[i]._length);
      assert(tmp == _files[i]._crc && "CRC checksum mismatch");

      return _files + i;
}

/**
 * @brief On-demand DFW file loader
 * @param i Index of file inside an archive
 * @return Pointer to a BAFile coresponding to the opened file or NULL (on failure)
 *
 * Loads individual DFW files from an archive to memory on demand.
 * Should not be called directly.
 */
00300 BAFile *BArchive::loadFileDFW(uint i) {
      byte *buf;

      // Else open archive and read in requested file
      if (!_f.isOpen()) {
            debugC(2, kDraciArchiverDebugLevel, "Error");
            return NULL;
      }

      // Seek to raw data of the file
      // Five bytes are for the header (uncompressed and compressed length, stopper mark)
      _f.seek(_files[i]._offset + 5);

      // Since we are seeking directly to raw data, we subtract 3 bytes from the length
      // (to take account the compressed length and stopper mark)
      uint16 compressedLength = _files[i]._compLength - 3;
      uint16 uncompressedLength = _files[i]._length;

      debugC(2, kDraciArchiverDebugLevel,
            "File info (DFW): uncompressed %d bytes, compressed %d bytes",
            uncompressedLength, compressedLength);

      // Allocate a buffer for the file data
      buf = new byte[compressedLength];

      // Read in file data into the buffer
      _f.read(buf, compressedLength);

      // Allocate the space for the uncompressed file
      byte *dst;
      dst = _files[i]._data = new byte[uncompressedLength];

      Common::MemoryReadStream data(buf, compressedLength);

      // Uncompress file
      byte current, what;
      byte stopper = _files[i]._stopper;
      uint repeat;
      uint len = 0; // Sanity check (counts uncompressed bytes)

      current = data.readByte(); // Read initial byte
      while (!data.eos()) {
            if (current != stopper) {
                  *dst++ = current;
                  ++len;
            } else {
                  // Inflate block
                  repeat = data.readByte();
                  what = data.readByte();
                  len += repeat;
                  for (uint j = 0; j < repeat; ++j) {
                        *dst++ = what;
                  }
            }

            current = data.readByte();
      }

      assert(len == _files[i]._length && "Uncompressed file not of the expected length");

      delete[] buf;

      return _files + i;
}

/**
 * Clears the cache of the open files inside the archive without closing it.
 * If the files are subsequently accessed, they are read from the disk.
 */
00369 void BArchive::clearCache() {
      // Delete all cached data
      for (uint i = 0; i < _fileCount; ++i) {
            _files[i].close();
      }
}

const BAFile *BArchive::getFile(uint i) {
      // Check whether requested file exists
      if (i >= _fileCount) {
            return NULL;
      }

      debugCN(2, kDraciArchiverDebugLevel, "Accessing file %d from archive %s... ",
            i, _path.c_str());

      // Check if file has already been opened and return that
      if (_files[i]._data) {
            debugC(2, kDraciArchiverDebugLevel, "Cached");
            return _files + i;
      }

      BAFile *file;

      // file will be NULL if something goes wrong
      if (_isDFW) {
            file = loadFileDFW(i);
      } else {
            file = loadFileBAR(i);
      }

      return file;
}

} // End of namespace Draci

Generated by  Doxygen 1.6.0   Back to index