Files
scummvm/engines/grim/update/mscab.cpp
T
Le Philousophe 5bdd9e6f50 ALL: Unify zlib and gzio API
zlib is used when available and falls back on gzio.
This allows performance improvements as our CRC32 and gzio
implementations are slower than base zlib.
As zlib is available when libpng is present, this is sensible to
benefit from it.
2023-08-28 21:49:03 +02:00

301 lines
8.1 KiB
C++

/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#include "engines/grim/update/mscab.h"
#include "common/file.h"
#include "common/archive.h"
#include "common/memstream.h"
#include "common/compression/deflate.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();
delete _data;
delete _decompressor;
}
MsCabinet::MsCabinet(Common::SeekableReadStream *data) :
_data(data), _decompressor(nullptr) {
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::Path &path) const {
Common::String name = path.toString();
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::Path &path) const {
Common::String name = path.toString();
return Common::ArchiveMemberPtr(new Common::GenericArchiveMember(name, *this));
}
Common::SeekableReadStream *MsCabinet::createReadStreamForMember(const Common::Path &path) const {
Common::String name = path.toString();
byte *fileBuf;
if (!hasFile(name))
return nullptr;
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()) {
delete _decompressor;
_decompressor = new Decompressor(entry.folder, _data);
}
if (!_decompressor->decompressFile(fileBuf, entry))
return nullptr;
_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(nullptr), _decompressedBlock(nullptr), _fileBuf(nullptr),
_inBlockEnd(0), _inBlockStart(0), _endBlock(0), _startBlock(0) {
// Alloc the decompression buffers
_compressedBlock = new byte[kCabInputmax];
_decompressedBlock = new byte[kCabBlockSize];
}
MsCabinet::Decompressor::~Decompressor() {
delete[] _decompressedBlock;
delete[] _compressedBlock;
delete[] _fileBuf;
}
bool MsCabinet::Decompressor::decompressFile(byte *&fileBuf, const FileEntry &entry) {
// Ref: http://blogs.kde.org/node/3181
uint16 uncompressedLen, compressedLen;
byte hdrS[4];
byte *buf_tmp, *dict;
bool decRes;
// Sanity checks
if (!_compressedBlock || 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];
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
_data->readUint32LE(); // checksum
_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;
// 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 : nullptr;
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 = nullptr;
return true;
}
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;
}
}
} // End of namespace Grim