ResidualVM logo ResidualVM website - Forums - Contact us BuildBot - Doxygen - Wiki curved edge

mscab.cpp

Go to the documentation of this file.
00001 /* ResidualVM - A 3D game interpreter
00002  *
00003  * ResidualVM is the legal property of its developers, whose names
00004  * are too numerous to list here. Please refer to the AUTHORS
00005  * file distributed with this source distribution.
00006  *
00007  * This program is free software; you can redistribute it and/or
00008  * modify it under the terms of the GNU General Public License
00009  * as published by the Free Software Foundation; either version 2
00010  * of the License, or (at your option) any later version.
00011  *
00012  * This program is distributed in the hope that it will be useful,
00013  * but WITHOUT ANY WARRANTY; without even the implied warranty of
00014  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00015  * GNU General Public License for more details.
00016  *
00017  * You should have received a copy of the GNU General Public License
00018  * along with this program; if not, write to the Free Software
00019  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
00020  *
00021  */
00022 
00023 #include "engines/grim/update/mscab.h"
00024 
00025 #include "common/file.h"
00026 #include "common/archive.h"
00027 #include "common/memstream.h"
00028 #include "common/zlib.h"
00029 #include "common/str.h"
00030 
00031 namespace Grim {
00032 
00033 MsCabinet::~MsCabinet() {
00034     for (CacheMap::iterator it = _cache.begin(); it != _cache.end(); it++)
00035         delete[] it->_value;
00036 
00037     _folderMap.clear();
00038     _fileMap.clear();
00039 
00040     delete _data;
00041 
00042     delete _decompressor;
00043 }
00044 
00045 MsCabinet::MsCabinet(Common::SeekableReadStream *data) :
00046     _data(data), _decompressor(nullptr) {
00047     if (!_data)
00048         return;
00049 
00050     //CFHEADER PARSING
00051 
00052     // Verify Head-signature
00053     uint32 tag = _data->readUint32BE();
00054     if (tag != MKTAG('M','S','C','F'))
00055         return;
00056 
00057     /* uint32 reserved1 = */ _data->readUint32LE();
00058     uint32 length = _data->readUint32LE();
00059     if (length > uint32(_data->size()))
00060         return;
00061 
00062     /* uint32 reserved2 = */ _data->readUint32LE();
00063     uint32 filesOffset = _data->readUint32LE();
00064     /* uint32 reserved3 = */ _data->readUint32LE();
00065 
00066     byte versionMinor = _data->readByte();
00067     byte versionMajor = _data->readByte();
00068     if (versionMajor != 1 || versionMinor != 3)
00069         return;
00070 
00071     uint16 numFolders = _data->readUint16LE();
00072     uint16 numFiles = _data->readUint16LE();
00073     if (numFolders == 0 || numFiles == 0)
00074         return;
00075 
00076     //This implementation doesn't support multicabinet and reserved fields
00077     uint16 flags = _data->readUint16LE();
00078     if (flags != 0)
00079         return;
00080 
00081     /* uint16 setId = */ _data->readUint16LE();
00082     /* uint16 iCab = */ _data->readUint16LE();
00083 
00084     if (_data->err())
00085         return;
00086 
00087     //CFFOLDERs PARSING
00088     for (uint16 i = 0; i < numFolders; ++i) {
00089         FolderEntry fEntry;
00090 
00091         fEntry.offset = _data->readUint32LE();
00092         fEntry.num_blocks = _data->readUint16LE();
00093         fEntry.comp_type = _data->readUint16LE();
00094 
00095         if (_data->err())
00096             return;
00097 
00098         _folderMap[i] = fEntry;
00099     }
00100 
00101     //CFFILEs PARSING
00102     _data->seek(filesOffset);
00103     if (_data->err())
00104         return;
00105 
00106     for (uint16 i = 0; i < numFiles; ++i) {
00107         FileEntry fEntry;
00108 
00109         fEntry.length = _data->readUint32LE();
00110         fEntry.folderOffset = _data->readUint32LE();
00111         uint16 iFolder = _data->readUint16LE();
00112         /* uint16 date = */ _data->readUint16LE();
00113         /* uint16 time = */ _data->readUint16LE();
00114         /* uint16 attribs = */ _data->readUint16LE();
00115         Common::String name = readString(_data);
00116         for (uint l = 0; l < name.size(); ++l)
00117             if (name[l] == '\\')
00118                 name.setChar('/', l);
00119 
00120         if (_data->err()) {
00121             _fileMap.clear();
00122             return;
00123         }
00124 
00125         if (_folderMap.contains(iFolder))
00126             fEntry.folder = &_folderMap[iFolder];
00127         else {
00128             _fileMap.clear();
00129             return;
00130         }
00131 
00132         _fileMap[name] = fEntry;
00133     }
00134 }
00135 
00136 /* read a null-terminated string from a stream
00137    Copied from ScummVm MohawkEngine_LivingBooks.*/
00138 Common::String MsCabinet::readString(Common::ReadStream *stream) {
00139     Common::String ret;
00140     while (!stream->eos()) {
00141         byte in = stream->readByte();
00142         if (!in)
00143             break;
00144         ret += in;
00145     }
00146     return ret;
00147 }
00148 
00149 bool MsCabinet::hasFile(const Common::String &name) const {
00150     return _fileMap.contains(name);
00151 }
00152 
00153 int MsCabinet::listMembers(Common::ArchiveMemberList &list) const {
00154     for (FileMap::const_iterator it = _fileMap.begin(); it != _fileMap.end(); it++)
00155         list.push_back(getMember(it->_key));
00156 
00157     return _fileMap.size();
00158 }
00159 
00160 const Common::ArchiveMemberPtr MsCabinet::getMember(const Common::String &name) const {
00161     return Common::ArchiveMemberPtr(new Common::GenericArchiveMember(name, this));
00162 }
00163 
00164 Common::SeekableReadStream *MsCabinet::createReadStreamForMember(const Common::String &name) const {
00165     byte *fileBuf;
00166 
00167     if (!hasFile(name))
00168         return nullptr;
00169 
00170     const FileEntry &entry = _fileMap[name];
00171 
00172     //Check if the file has already been decompressed and it's in the cache,
00173     // otherwise decompress it and put it in the cache
00174     if (_cache.contains(name))
00175         fileBuf = _cache[name];
00176     else {
00177         //Check if the decompressor should be reinitialized
00178         if (!_decompressor || entry.folder != _decompressor->getFolder()) {
00179             delete _decompressor;
00180 
00181             _decompressor = new Decompressor(entry.folder, _data);
00182         }
00183 
00184         if (!_decompressor->decompressFile(fileBuf, entry))
00185             return nullptr;
00186 
00187         _cache[name] = fileBuf;
00188     }
00189 
00190     return new Common::MemoryReadStream(fileBuf, entry.length, DisposeAfterUse::NO);
00191 }
00192 
00193 MsCabinet::Decompressor::Decompressor(const MsCabinet::FolderEntry *folder, Common::SeekableReadStream *data) :
00194     _curFolder(folder), _data(data), _curBlock(-1), _compressedBlock(nullptr), _decompressedBlock(nullptr), _fileBuf(nullptr),
00195     _inBlockEnd(0), _inBlockStart(0), _endBlock(0), _startBlock(0) {
00196 
00197     //Alloc the decompression buffers
00198     _compressedBlock = new byte[kCabInputmax];
00199     _decompressedBlock = new byte[kCabBlockSize];
00200 }
00201 
00202 MsCabinet::Decompressor::~Decompressor() {
00203     delete[] _decompressedBlock;
00204 
00205     delete[] _compressedBlock;
00206 
00207     delete[] _fileBuf;
00208 }
00209 
00210 bool MsCabinet::Decompressor::decompressFile(byte *&fileBuf, const FileEntry &entry) {
00211 #ifdef USE_ZLIB
00212     // Ref: http://blogs.kde.org/node/3181
00213     uint16 uncompressedLen, compressedLen;
00214     byte hdrS[4];
00215     byte *buf_tmp, *dict;
00216     bool decRes;
00217 
00218     //Sanity checks
00219     if (!_compressedBlock || entry.folder != _curFolder)
00220         return false;
00221 
00222     _startBlock = entry.folderOffset / kCabBlockSize;
00223     _inBlockStart = entry.folderOffset % kCabBlockSize;
00224     _endBlock = (entry.folderOffset + entry.length) / kCabBlockSize;
00225     _inBlockEnd = (entry.folderOffset + entry.length) % kCabBlockSize;
00226 
00227     //Check if the decompressor should be reinitialized
00228     if (_curBlock > _startBlock || _curBlock == -1) {
00229         _data->seek(entry.folder->offset);
00230         //Check the compression method (only mszip supported)
00231         if (entry.folder->comp_type != kMszipCompression)
00232             return false;
00233 
00234         _curBlock = -1;     //No block decompressed
00235     }
00236 
00237     //Check if the file is contained in the folder
00238     if ((entry.length + entry.folderOffset) / kCabBlockSize > entry.folder->num_blocks)
00239         return false;
00240 
00241     _fileBuf = new byte[entry.length];
00242 
00243     buf_tmp = _fileBuf;
00244 
00245     //if a part of this file has been decompressed in the last block, make a copy of it
00246     copyBlock(buf_tmp);
00247 
00248     while ((_curBlock + 1) <= _endBlock) {
00249         // Read the CFDATA header
00250         _data->readUint32LE(); // checksum
00251         _data->read(hdrS, 4);
00252         compressedLen = READ_LE_UINT16(hdrS);
00253         uncompressedLen = READ_LE_UINT16(hdrS + 2);
00254 
00255         if (_data->err())
00256             return false;
00257 
00258         if (compressedLen > kCabInputmax || uncompressedLen > kCabBlockSize)
00259             return false;
00260 
00261         //Read the compressed block
00262         if (_data->read(_compressedBlock, compressedLen) != compressedLen)
00263             return false;
00264 
00265         //Check the CK header
00266         if (_compressedBlock[0] != 'C' || _compressedBlock[1] != 'K')
00267             return false;
00268 
00269         //Decompress the block. If it isn't the first, provide the previous block as dictonary
00270         dict = (_curBlock >= 0) ? _decompressedBlock : nullptr;
00271         decRes = Common::inflateZlibHeaderless(_decompressedBlock, uncompressedLen, _compressedBlock + 2, compressedLen - 2, dict, kCabBlockSize);
00272         if (!decRes)
00273             return false;
00274 
00275         _curBlock++;
00276 
00277         //Copy the decompressed data, if needed
00278         copyBlock(buf_tmp);
00279     }
00280 
00281     fileBuf = _fileBuf;
00282     _fileBuf = nullptr;
00283     return true;
00284 #else
00285     warning("zlib required to extract MSCAB");
00286     return false;
00287 #endif
00288 }
00289 
00290 void MsCabinet::Decompressor::copyBlock(byte *&data_ptr) const {
00291     uint16 start, end, size;
00292 
00293     if (_startBlock <= _curBlock && _curBlock <= _endBlock) {
00294         start = (_startBlock == _curBlock) ? _inBlockStart : 0;
00295         end = (_endBlock == _curBlock) ? _inBlockEnd : uint16(kCabBlockSize);
00296         size = end - start;
00297 
00298         memcpy(data_ptr, _decompressedBlock + start, size);
00299         data_ptr += size;
00300     }
00301 }
00302 
00303 } // End of namespace Grim


Generated on Sat Oct 12 2019 05:00:57 for ResidualVM by doxygen 1.7.1
curved edge   curved edge