1 /* ResidualVM - A 3D game interpreter
2  *
3  * ResidualVM is the legal property of its developers, whose names
4  * are too numerous to list here. Please refer to the AUTHORS
5  * file distributed with this source distribution.
6  *
7  * This program is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License
9  * as published by the Free Software Foundation; either version 2
10  * of the License, or (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, write to the Free Software
19  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20  *
21  */
22 
23 #include "engines/grim/update/mscab.h"
24 
25 #include "common/file.h"
26 #include "common/archive.h"
27 #include "common/memstream.h"
28 #include "common/zlib.h"
29 #include "common/str.h"
30 
31 namespace Grim {
32 
~MsCabinet()33 MsCabinet::~MsCabinet() {
34 	for (CacheMap::iterator it = _cache.begin(); it != _cache.end(); it++)
35 		delete[] it->_value;
36 
37 	_folderMap.clear();
38 	_fileMap.clear();
39 
40 	delete _data;
41 
42 	delete _decompressor;
43 }
44 
MsCabinet(Common::SeekableReadStream * data)45 MsCabinet::MsCabinet(Common::SeekableReadStream *data) :
46 	_data(data), _decompressor(nullptr) {
47 	if (!_data)
48 		return;
49 
50 	//CFHEADER PARSING
51 
52 	// Verify Head-signature
53 	uint32 tag = _data->readUint32BE();
54 	if (tag != MKTAG('M','S','C','F'))
55 		return;
56 
57 	/* uint32 reserved1 = */ _data->readUint32LE();
58 	uint32 length = _data->readUint32LE();
59 	if (length > uint32(_data->size()))
60 		return;
61 
62 	/* uint32 reserved2 = */ _data->readUint32LE();
63 	uint32 filesOffset = _data->readUint32LE();
64 	/* uint32 reserved3 = */ _data->readUint32LE();
65 
66 	byte versionMinor = _data->readByte();
67 	byte versionMajor = _data->readByte();
68 	if (versionMajor != 1 || versionMinor != 3)
69 		return;
70 
71 	uint16 numFolders = _data->readUint16LE();
72 	uint16 numFiles = _data->readUint16LE();
73 	if (numFolders == 0 || numFiles == 0)
74 		return;
75 
76 	//This implementation doesn't support multicabinet and reserved fields
77 	uint16 flags = _data->readUint16LE();
78 	if (flags != 0)
79 		return;
80 
81 	/* uint16 setId = */ _data->readUint16LE();
82 	/* uint16 iCab = */ _data->readUint16LE();
83 
84 	if (_data->err())
85 		return;
86 
87 	//CFFOLDERs PARSING
88 	for (uint16 i = 0; i < numFolders; ++i) {
89 		FolderEntry fEntry;
90 
91 		fEntry.offset = _data->readUint32LE();
92 		fEntry.num_blocks = _data->readUint16LE();
93 		fEntry.comp_type = _data->readUint16LE();
94 
95 		if (_data->err())
96 			return;
97 
98 		_folderMap[i] = fEntry;
99 	}
100 
101 	//CFFILEs PARSING
102 	_data->seek(filesOffset);
103 	if (_data->err())
104 		return;
105 
106 	for (uint16 i = 0; i < numFiles; ++i) {
107 		FileEntry fEntry;
108 
109 		fEntry.length = _data->readUint32LE();
110 		fEntry.folderOffset = _data->readUint32LE();
111 		uint16 iFolder = _data->readUint16LE();
112 		/* uint16 date = */ _data->readUint16LE();
113 		/* uint16 time = */ _data->readUint16LE();
114 		/* uint16 attribs = */ _data->readUint16LE();
115 		Common::String name = readString(_data);
116 		for (uint l = 0; l < name.size(); ++l)
117 			if (name[l] == '\\')
118 				name.setChar('/', l);
119 
120 		if (_data->err()) {
121 			_fileMap.clear();
122 			return;
123 		}
124 
125 		if (_folderMap.contains(iFolder))
126 			fEntry.folder = &_folderMap[iFolder];
127 		else {
128 			_fileMap.clear();
129 			return;
130 		}
131 
132 		_fileMap[name] = fEntry;
133 	}
134 }
135 
136 /* read a null-terminated string from a stream
137    Copied from ScummVm MohawkEngine_LivingBooks.*/
readString(Common::ReadStream * stream)138 Common::String MsCabinet::readString(Common::ReadStream *stream) {
139 	Common::String ret;
140 	while (!stream->eos()) {
141 		byte in = stream->readByte();
142 		if (!in)
143 			break;
144 		ret += in;
145 	}
146 	return ret;
147 }
148 
hasFile(const Common::String & name) const149 bool MsCabinet::hasFile(const Common::String &name) const {
150 	return _fileMap.contains(name);
151 }
152 
listMembers(Common::ArchiveMemberList & list) const153 int MsCabinet::listMembers(Common::ArchiveMemberList &list) const {
154 	for (FileMap::const_iterator it = _fileMap.begin(); it != _fileMap.end(); it++)
155 		list.push_back(getMember(it->_key));
156 
157 	return _fileMap.size();
158 }
159 
getMember(const Common::String & name) const160 const Common::ArchiveMemberPtr MsCabinet::getMember(const Common::String &name) const {
161 	return Common::ArchiveMemberPtr(new Common::GenericArchiveMember(name, this));
162 }
163 
createReadStreamForMember(const Common::String & name) const164 Common::SeekableReadStream *MsCabinet::createReadStreamForMember(const Common::String &name) const {
165 	byte *fileBuf;
166 
167 	if (!hasFile(name))
168 		return nullptr;
169 
170 	const FileEntry &entry = _fileMap[name];
171 
172 	//Check if the file has already been decompressed and it's in the cache,
173 	// otherwise decompress it and put it in the cache
174 	if (_cache.contains(name))
175 		fileBuf = _cache[name];
176 	else {
177 		//Check if the decompressor should be reinitialized
178 		if (!_decompressor || entry.folder != _decompressor->getFolder()) {
179 			delete _decompressor;
180 
181 			_decompressor = new Decompressor(entry.folder, _data);
182 		}
183 
184 		if (!_decompressor->decompressFile(fileBuf, entry))
185 			return nullptr;
186 
187 		_cache[name] = fileBuf;
188 	}
189 
190 	return new Common::MemoryReadStream(fileBuf, entry.length, DisposeAfterUse::NO);
191 }
192 
Decompressor(const MsCabinet::FolderEntry * folder,Common::SeekableReadStream * data)193 MsCabinet::Decompressor::Decompressor(const MsCabinet::FolderEntry *folder, Common::SeekableReadStream *data) :
194 	_curFolder(folder), _data(data), _curBlock(-1), _compressedBlock(nullptr), _decompressedBlock(nullptr), _fileBuf(nullptr),
195 	_inBlockEnd(0), _inBlockStart(0), _endBlock(0), _startBlock(0) {
196 
197 	//Alloc the decompression buffers
198 	_compressedBlock = new byte[kCabInputmax];
199 	_decompressedBlock = new byte[kCabBlockSize];
200 }
201 
~Decompressor()202 MsCabinet::Decompressor::~Decompressor() {
203 	delete[] _decompressedBlock;
204 
205 	delete[] _compressedBlock;
206 
207 	delete[] _fileBuf;
208 }
209 
decompressFile(byte * & fileBuf,const FileEntry & entry)210 bool MsCabinet::Decompressor::decompressFile(byte *&fileBuf, const FileEntry &entry) {
211 #ifdef USE_ZLIB
212 	// Ref: http://blogs.kde.org/node/3181
213 	uint16 uncompressedLen, compressedLen;
214 	byte hdrS[4];
215 	byte *buf_tmp, *dict;
216 	bool decRes;
217 
218 	//Sanity checks
219 	if (!_compressedBlock || entry.folder != _curFolder)
220 		return false;
221 
222 	_startBlock = entry.folderOffset / kCabBlockSize;
223 	_inBlockStart = entry.folderOffset % kCabBlockSize;
224 	_endBlock = (entry.folderOffset + entry.length) / kCabBlockSize;
225 	_inBlockEnd = (entry.folderOffset + entry.length) % kCabBlockSize;
226 
227 	//Check if the decompressor should be reinitialized
228 	if (_curBlock > _startBlock || _curBlock == -1) {
229 		_data->seek(entry.folder->offset);
230 		//Check the compression method (only mszip supported)
231 		if (entry.folder->comp_type != kMszipCompression)
232 			return false;
233 
234 		_curBlock = -1;     //No block decompressed
235 	}
236 
237 	//Check if the file is contained in the folder
238 	if ((entry.length + entry.folderOffset) / kCabBlockSize > entry.folder->num_blocks)
239 		return false;
240 
241 	_fileBuf = new byte[entry.length];
242 
243 	buf_tmp = _fileBuf;
244 
245 	//if a part of this file has been decompressed in the last block, make a copy of it
246 	copyBlock(buf_tmp);
247 
248 	while ((_curBlock + 1) <= _endBlock) {
249 		// Read the CFDATA header
250 		_data->readUint32LE(); // checksum
251 		_data->read(hdrS, 4);
252 		compressedLen = READ_LE_UINT16(hdrS);
253 		uncompressedLen = READ_LE_UINT16(hdrS + 2);
254 
255 		if (_data->err())
256 			return false;
257 
258 		if (compressedLen > kCabInputmax || uncompressedLen > kCabBlockSize)
259 			return false;
260 
261 		//Read the compressed block
262 		if (_data->read(_compressedBlock, compressedLen) != compressedLen)
263 			return false;
264 
265 		//Check the CK header
266 		if (_compressedBlock[0] != 'C' || _compressedBlock[1] != 'K')
267 			return false;
268 
269 		//Decompress the block. If it isn't the first, provide the previous block as dictonary
270 		dict = (_curBlock >= 0) ? _decompressedBlock : nullptr;
271 		decRes = Common::inflateZlibHeaderless(_decompressedBlock, uncompressedLen, _compressedBlock + 2, compressedLen - 2, dict, kCabBlockSize);
272 		if (!decRes)
273 			return false;
274 
275 		_curBlock++;
276 
277 		//Copy the decompressed data, if needed
278 		copyBlock(buf_tmp);
279 	}
280 
281 	fileBuf = _fileBuf;
282 	_fileBuf = nullptr;
283 	return true;
284 #else
285 	warning("zlib required to extract MSCAB");
286 	return false;
287 #endif
288 }
289 
copyBlock(byte * & data_ptr) const290 void MsCabinet::Decompressor::copyBlock(byte *&data_ptr) const {
291 	uint16 start, end, size;
292 
293 	if (_startBlock <= _curBlock && _curBlock <= _endBlock) {
294 		start = (_startBlock == _curBlock) ? _inBlockStart : 0;
295 		end = (_endBlock == _curBlock) ? _inBlockEnd : uint16(kCabBlockSize);
296 		size = end - start;
297 
298 		memcpy(data_ptr, _decompressedBlock + start, size);
299 		data_ptr += size;
300 	}
301 }
302 
303 } // End of namespace Grim
304