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