mirror of
https://github.com/libretro/scummvm.git
synced 2025-01-15 06:18:33 +00:00
350 lines
9.1 KiB
C++
350 lines
9.1 KiB
C++
/* ResidualVM - A 3D game interpreter
|
|
*
|
|
* ResidualVM 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 library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2.1 of the License, or (at your option) any later version.
|
|
*
|
|
* This library 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
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with this library; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
|
|
*
|
|
*/
|
|
|
|
/* Portions of this file are part of libmspack.
|
|
* (C) 2003-2004 Stuart Caie.
|
|
*
|
|
* This source code is adapted and stripped for ResidualVM project.
|
|
*
|
|
* libmspack is free software; you can redistribute it and/or modify it under
|
|
* the terms of the GNU Lesser General Public License (LGPL) version 2.1
|
|
*
|
|
* For further details, see the file COPYING.LIB distributed with libmspack
|
|
*/
|
|
|
|
#include "engines/grim/update/mscab.h"
|
|
|
|
#include "common/file.h"
|
|
#include "common/archive.h"
|
|
#include "common/memstream.h"
|
|
#include "common/zlib.h"
|
|
#include "common/str.h"
|
|
|
|
namespace Grim {
|
|
|
|
MsCabinet::~MsCabinet() {
|
|
for (CacheMap::iterator it = _cache.begin(); it != _cache.end(); it++)
|
|
delete[] it->_value;
|
|
|
|
_folderMap.clear();
|
|
_fileMap.clear();
|
|
|
|
if (_data)
|
|
delete _data;
|
|
|
|
if (_decompressor)
|
|
delete _decompressor;
|
|
}
|
|
|
|
MsCabinet::MsCabinet(Common::SeekableReadStream* data) :
|
|
_data(data), _decompressor(0) {
|
|
if (!_data)
|
|
return;
|
|
|
|
//CFHEADER PARSING
|
|
|
|
// Verify Head-signature
|
|
uint32 tag = _data->readUint32BE();
|
|
if (tag != MKTAG('M','S','C','F'))
|
|
return;
|
|
|
|
/* uint32 reserved1 = */ _data->readUint32LE();
|
|
uint32 length = _data->readUint32LE();
|
|
if (length > uint32(_data->size()))
|
|
return;
|
|
|
|
/* uint32 reserved2 = */ _data->readUint32LE();
|
|
uint32 filesOffset = _data->readUint32LE();
|
|
/* uint32 reserved3 = */ _data->readUint32LE();
|
|
|
|
byte versionMinor = _data->readByte();
|
|
byte versionMajor = _data->readByte();
|
|
if (versionMajor != 1 || versionMinor != 3)
|
|
return;
|
|
|
|
uint16 numFolders = _data->readUint16LE();
|
|
uint16 numFiles = _data->readUint16LE();
|
|
if (numFolders == 0 || numFiles == 0)
|
|
return;
|
|
|
|
//This implementation doesn't support multicabinet and reserved fields
|
|
uint16 flags = _data->readUint16LE();
|
|
if (flags != 0)
|
|
return;
|
|
|
|
/* uint16 setId = */ _data->readUint16LE();
|
|
/* uint16 iCab = */ _data->readUint16LE();
|
|
|
|
if (_data->err())
|
|
return;
|
|
|
|
//CFFOLDERs PARSING
|
|
for (uint16 i = 0; i < numFolders; ++i) {
|
|
FolderEntry fEntry;
|
|
|
|
fEntry.offset = _data->readUint32LE();
|
|
fEntry.num_blocks = _data->readUint16LE();
|
|
fEntry.comp_type = _data->readUint16LE();
|
|
|
|
if (_data->err())
|
|
return;
|
|
|
|
_folderMap[i] = fEntry;
|
|
}
|
|
|
|
//CFFILEs PARSING
|
|
_data->seek(filesOffset);
|
|
if (_data->err())
|
|
return;
|
|
|
|
for (uint16 i = 0; i < numFiles; ++i) {
|
|
FileEntry fEntry;
|
|
|
|
fEntry.length = _data->readUint32LE();
|
|
fEntry.folderOffset = _data->readUint32LE();
|
|
uint16 iFolder = _data->readUint16LE();
|
|
/* uint16 date = */ _data->readUint16LE();
|
|
/* uint16 time = */ _data->readUint16LE();
|
|
/* uint16 attribs = */ _data->readUint16LE();
|
|
Common::String name = readString(_data);
|
|
for (uint l = 0; l < name.size(); ++l)
|
|
if (name[l] == '\\')
|
|
name.setChar('/', l);
|
|
|
|
if (_data->err()) {
|
|
_fileMap.clear();
|
|
return;
|
|
}
|
|
|
|
if (_folderMap.contains(iFolder))
|
|
fEntry.folder = &_folderMap[iFolder];
|
|
else {
|
|
_fileMap.clear();
|
|
return;
|
|
}
|
|
|
|
_fileMap[name] = fEntry;
|
|
}
|
|
}
|
|
|
|
/* read a null-terminated string from a stream
|
|
Copied from ScummVm MohawkEngine_LivingBooks.*/
|
|
Common::String MsCabinet::readString(Common::ReadStream *stream) {
|
|
Common::String ret;
|
|
while (!stream->eos()) {
|
|
byte in = stream->readByte();
|
|
if (!in)
|
|
break;
|
|
ret += in;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
bool MsCabinet::hasFile(const Common::String &name) const {
|
|
return _fileMap.contains(name);
|
|
}
|
|
|
|
int MsCabinet::listMembers(Common::ArchiveMemberList &list) const {
|
|
for (FileMap::const_iterator it = _fileMap.begin(); it != _fileMap.end(); it++)
|
|
list.push_back(getMember(it->_key));
|
|
|
|
return _fileMap.size();
|
|
}
|
|
|
|
const Common::ArchiveMemberPtr MsCabinet::getMember(const Common::String &name) const {
|
|
return Common::ArchiveMemberPtr(new Common::GenericArchiveMember(name, this));
|
|
}
|
|
|
|
Common::SeekableReadStream *MsCabinet::createReadStreamForMember(const Common::String &name) const {
|
|
byte *fileBuf;
|
|
|
|
if (!hasFile(name))
|
|
return 0;
|
|
|
|
const FileEntry &entry = _fileMap[name];
|
|
|
|
//Check if the file has already been decompressed and it's in the cache,
|
|
// otherwise decompress it and put it in the cache
|
|
if (_cache.contains(name))
|
|
fileBuf = _cache[name];
|
|
else {
|
|
//Check if the decompressor should be reinitialized
|
|
if (!_decompressor || entry.folder != _decompressor->getFolder()) {
|
|
if (_decompressor)
|
|
delete _decompressor;
|
|
|
|
_decompressor = new Decompressor(entry.folder, _data);
|
|
}
|
|
|
|
if (!_decompressor->decompressFile(fileBuf, entry))
|
|
return 0;
|
|
|
|
_cache[name] = fileBuf;
|
|
}
|
|
|
|
return new Common::MemoryReadStream(fileBuf, entry.length, DisposeAfterUse::NO);
|
|
}
|
|
|
|
MsCabinet::Decompressor::Decompressor(const MsCabinet::FolderEntry *folder, Common::SeekableReadStream* data) :
|
|
_curFolder(folder), _data(data), _curBlock(-1), _compressedBlock(0), _decompressedBlock(0), _fileBuf(0) {
|
|
|
|
//Alloc the decompression buffers
|
|
_compressedBlock = new byte[kCabInputmax];
|
|
_decompressedBlock = new byte[kCabBlockSize];
|
|
}
|
|
|
|
MsCabinet::Decompressor::~Decompressor() {
|
|
if (_decompressedBlock)
|
|
delete[] _decompressedBlock;
|
|
|
|
if (_compressedBlock)
|
|
delete[] _compressedBlock;
|
|
|
|
if (_fileBuf)
|
|
delete[] _fileBuf;
|
|
}
|
|
|
|
bool MsCabinet::Decompressor::decompressFile(byte *&fileBuf, const FileEntry &entry) {
|
|
#ifdef USE_ZLIB
|
|
// Ref: http://blogs.kde.org/node/3181
|
|
uint32 cksum, cksum2;
|
|
uint16 uncompressedLen, compressedLen;
|
|
byte hdrS[4];
|
|
byte *buf_tmp, *dict;
|
|
bool decRes;
|
|
|
|
//Sanity checks
|
|
if (!_compressedBlock || !_compressedBlock)
|
|
return false;
|
|
|
|
if (entry.folder != _curFolder)
|
|
return false;
|
|
|
|
_startBlock = entry.folderOffset / kCabBlockSize;
|
|
_inBlockStart = entry.folderOffset % kCabBlockSize;
|
|
_endBlock = (entry.folderOffset + entry.length) / kCabBlockSize;
|
|
_inBlockEnd = (entry.folderOffset + entry.length) % kCabBlockSize;
|
|
|
|
//Check if the decompressor should be reinitialized
|
|
if (_curBlock > _startBlock || _curBlock == -1) {
|
|
_data->seek(entry.folder->offset);
|
|
//Check the compression method (only mszip supported)
|
|
if (entry.folder->comp_type != kMszipCompression)
|
|
return false;
|
|
|
|
_curBlock = -1; //No block decompressed
|
|
}
|
|
|
|
//Check if the file is contained in the folder
|
|
if ((entry.length + entry.folderOffset) / kCabBlockSize > entry.folder->num_blocks)
|
|
return false;
|
|
|
|
_fileBuf = new byte[entry.length];
|
|
if (!_fileBuf)
|
|
return false;
|
|
|
|
buf_tmp = _fileBuf;
|
|
|
|
//if a part of this file has been decompressed in the last block, make a copy of it
|
|
copyBlock(buf_tmp);
|
|
|
|
while((_curBlock + 1) <= _endBlock) {
|
|
// Read the CFDATA header
|
|
cksum = _data->readUint32LE();
|
|
_data->read(hdrS, 4);
|
|
compressedLen = READ_LE_UINT16(hdrS);
|
|
uncompressedLen = READ_LE_UINT16(hdrS + 2);
|
|
|
|
if (_data->err())
|
|
return false;
|
|
|
|
if (compressedLen > kCabInputmax || uncompressedLen > kCabBlockSize)
|
|
return false;
|
|
|
|
//Read the compressed block
|
|
if (_data->read(_compressedBlock, compressedLen) != compressedLen)
|
|
return false;
|
|
|
|
//Perform checksum test on the block (if one is stored)
|
|
if (cksum != 0) {
|
|
cksum2 = checksum(_compressedBlock, compressedLen, 0);
|
|
if (checksum(hdrS, 4, cksum2) != cksum)
|
|
return false;
|
|
}
|
|
|
|
//Check the CK header
|
|
if (_compressedBlock[0] != 'C' && _compressedBlock[1] == 'K')
|
|
return false;
|
|
|
|
//Decompress the block. If it isn't the first, provide the previous block as dictonary
|
|
dict = (_curBlock >= 0) ? _decompressedBlock : 0;
|
|
decRes = Common::inflateZlibHeaderless(_decompressedBlock, uncompressedLen, _compressedBlock + 2, compressedLen - 2, dict, kCabBlockSize);
|
|
if (!decRes)
|
|
return false;
|
|
|
|
_curBlock++;
|
|
|
|
//Copy the decompressed data, if needed
|
|
copyBlock(buf_tmp);
|
|
}
|
|
|
|
fileBuf = _fileBuf;
|
|
_fileBuf = 0;
|
|
return true;
|
|
#else
|
|
warning("zlib required to extract MSCAB");
|
|
return false;
|
|
#endif
|
|
}
|
|
|
|
void MsCabinet::Decompressor::copyBlock(byte *&data_ptr) const {
|
|
uint16 start, end, size;
|
|
|
|
if(_startBlock <= _curBlock && _curBlock <= _endBlock) {
|
|
start = (_startBlock == _curBlock) ? _inBlockStart: 0;
|
|
end = (_endBlock == _curBlock) ? _inBlockEnd : uint16(kCabBlockSize);
|
|
size = end - start;
|
|
|
|
memcpy(data_ptr, _decompressedBlock + start, size);
|
|
data_ptr += size;
|
|
}
|
|
}
|
|
|
|
uint32 MsCabinet::Decompressor::checksum(byte *data, uint32 bytes, uint32 cksum) const {
|
|
uint32 len, ul = 0;
|
|
|
|
for (len = bytes >> 2; len--; data += 4) {
|
|
cksum ^= READ_LE_UINT32(data);
|
|
}
|
|
|
|
switch (bytes & 3) {
|
|
case 3: ul |= *data++ << 16;
|
|
case 2: ul |= *data++ << 8;
|
|
case 1: ul |= *data;
|
|
}
|
|
cksum ^= ul;
|
|
|
|
return cksum;
|
|
}
|
|
|
|
} // End of namespace Grim
|