1 /* ScummVM - Graphic Adventure Engine
2  *
3  * ScummVM is the legal property of its developers, whose names
4  * are too numerous to list here. Please refer to the COPYRIGHT
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 "director/archive.h"
24 #include "director/director.h"
25 
26 #include "common/debug.h"
27 #include "common/macresman.h"
28 
29 namespace Director {
30 
31 // Base Archive code
32 
Archive()33 Archive::Archive() {
34 	_stream = 0;
35 	_isBigEndian = true;
36 }
37 
~Archive()38 Archive::~Archive() {
39 	close();
40 }
41 
openFile(const Common::String & fileName)42 bool Archive::openFile(const Common::String &fileName) {
43 	Common::File *file = new Common::File();
44 
45 	if (!file->open(fileName)) {
46 		delete file;
47 		return false;
48 	}
49 
50 	if (!openStream(file)) {
51 		close();
52 		return false;
53 	}
54 
55 	_fileName = fileName;
56 
57 	return true;
58 }
59 
close()60 void Archive::close() {
61 	_types.clear();
62 
63 	if (_stream)
64 		delete _stream;
65 
66 	_stream = 0;
67 }
68 
hasResource(uint32 tag,int id) const69 bool Archive::hasResource(uint32 tag, int id) const {
70 	if (!_types.contains(tag))
71 		return false;
72 
73 	if (id == -1)
74 		return true;
75 
76 	return _types[tag].contains(id);
77 }
78 
hasResource(uint32 tag,const Common::String & resName) const79 bool Archive::hasResource(uint32 tag, const Common::String &resName) const {
80 	if (!_types.contains(tag) || resName.empty())
81 		return false;
82 
83 	const ResourceMap &resMap = _types[tag];
84 
85 	for (ResourceMap::const_iterator it = resMap.begin(); it != resMap.end(); it++)
86 		if (it->_value.name.matchString(resName))
87 			return true;
88 
89 	return false;
90 }
91 
getResource(uint32 tag,uint16 id)92 Common::SeekableSubReadStreamEndian *Archive::getResource(uint32 tag, uint16 id) {
93 	if (!_types.contains(tag))
94 		error("Archive does not contain '%s' %04x", tag2str(tag), id);
95 
96 	const ResourceMap &resMap = _types[tag];
97 
98 	if (!resMap.contains(id))
99 		error("Archive does not contain '%s' %04x", tag2str(tag), id);
100 
101 	const Resource &res = resMap[id];
102 
103 	return new Common::SeekableSubReadStreamEndian(_stream, res.offset, res.offset + res.size, _isBigEndian, DisposeAfterUse::NO);
104 }
105 
getResourceDetail(uint32 tag,uint16 id)106 Resource Archive::getResourceDetail(uint32 tag, uint16 id) {
107 	if (!_types.contains(tag))
108 		error("Archive does not contain '%s' %04x", tag2str(tag), id);
109 
110 	const ResourceMap &resMap = _types[tag];
111 
112 	if (!resMap.contains(id))
113 		error("Archive does not contain '%s' %04x", tag2str(tag), id);
114 
115 	return resMap[id];
116 }
117 
getOffset(uint32 tag,uint16 id) const118 uint32 Archive::getOffset(uint32 tag, uint16 id) const {
119 	if (!_types.contains(tag))
120 		error("Archive does not contain '%s' %04x", tag2str(tag), id);
121 
122 	const ResourceMap &resMap = _types[tag];
123 
124 	if (!resMap.contains(id))
125 		error("Archive does not contain '%s' %04x", tag2str(tag), id);
126 
127 	return resMap[id].offset;
128 }
129 
findResourceID(uint32 tag,const Common::String & resName) const130 uint16 Archive::findResourceID(uint32 tag, const Common::String &resName) const {
131 	if (!_types.contains(tag) || resName.empty())
132 		return 0xFFFF;
133 
134 	const ResourceMap &resMap = _types[tag];
135 
136 	for (ResourceMap::const_iterator it = resMap.begin(); it != resMap.end(); it++)
137 		if (it->_value.name.matchString(resName))
138 			return it->_key;
139 
140 	return 0xFFFF;
141 }
142 
getName(uint32 tag,uint16 id) const143 Common::String Archive::getName(uint32 tag, uint16 id) const {
144 	if (!_types.contains(tag))
145 		error("Archive does not contain '%s' %04x", tag2str(tag), id);
146 
147 	const ResourceMap &resMap = _types[tag];
148 
149 	if (!resMap.contains(id))
150 		error("Archive does not contain '%s' %04x", tag2str(tag), id);
151 
152 	return resMap[id].name;
153 }
154 
getResourceTypeList() const155 Common::Array<uint32> Archive::getResourceTypeList() const {
156 	Common::Array<uint32> typeList;
157 
158 	for (TypeMap::const_iterator it = _types.begin(); it != _types.end(); it++)
159 		typeList.push_back(it->_key);
160 
161 	return typeList;
162 }
163 
getResourceIDList(uint32 type) const164 Common::Array<uint16> Archive::getResourceIDList(uint32 type) const {
165 	Common::Array<uint16> idList;
166 
167 	if (!_types.contains(type))
168 		return idList;
169 
170 	const ResourceMap &resMap = _types[type];
171 
172 	for (ResourceMap::const_iterator it = resMap.begin(); it != resMap.end(); it++)
173 		idList.push_back(it->_key);
174 
175 	return idList;
176 }
177 
convertTagToUppercase(uint32 tag)178 uint32 Archive::convertTagToUppercase(uint32 tag) {
179 	uint32 newTag = toupper(tag >> 24) << 24;
180 	newTag |= toupper((tag >> 16) & 0xFF) << 16;
181 	newTag |= toupper((tag >> 8) & 0xFF) << 8;
182 
183 	return newTag | toupper(tag & 0xFF);
184 }
185 
186 // Mac Archive code
187 
MacArchive()188 MacArchive::MacArchive() : Archive(), _resFork(0) {
189 }
190 
~MacArchive()191 MacArchive::~MacArchive() {
192 	delete _resFork;
193 }
194 
close()195 void MacArchive::close() {
196 	Archive::close();
197 	delete _resFork;
198 	_resFork = 0;
199 }
200 
openFile(const Common::String & fileName)201 bool MacArchive::openFile(const Common::String &fileName) {
202 	close();
203 
204 	_resFork = new Common::MacResManager();
205 
206 	if (!_resFork->open(fileName) || !_resFork->hasResFork()) {
207 		close();
208 		return false;
209 	}
210 
211 	_fileName = _resFork->getBaseFileName();
212 	if (_fileName.hasSuffix(".bin")) {
213 		for (int i = 0; i < 4; i++)
214 			_fileName.deleteLastChar();
215 	}
216 
217 	Common::MacResTagArray tagArray = _resFork->getResTagArray();
218 
219 	for (uint32 i = 0; i < tagArray.size(); i++) {
220 		ResourceMap &resMap = _types[tagArray[i]];
221 		Common::MacResIDArray idArray = _resFork->getResIDArray(tagArray[i]);
222 
223 		for (uint32 j = 0; j < idArray.size(); j++) {
224 			Resource &res = resMap[idArray[j]];
225 
226 			res.offset = res.size = 0; // unused
227 			res.name = _resFork->getResName(tagArray[i], idArray[j]);
228 			debug(3, "Found MacArchive resource '%s' %d: %s", tag2str(tagArray[i]), idArray[j], res.name.c_str());
229 		}
230 	}
231 
232 	return true;
233 }
234 
openStream(Common::SeekableReadStream * stream,uint32 startOffset)235 bool MacArchive::openStream(Common::SeekableReadStream *stream, uint32 startOffset) {
236 	// TODO: Add support for this (v4 Windows games)
237 	return false;
238 }
239 
getResource(uint32 tag,uint16 id)240 Common::SeekableSubReadStreamEndian *MacArchive::getResource(uint32 tag, uint16 id) {
241 	assert(_resFork);
242 	Common::SeekableReadStream *stream = _resFork->getResource(tag, id);
243 	return new Common::SeekableSubReadStreamEndian(stream, 0, stream->size(), true, DisposeAfterUse::NO);
244 }
245 
246 // RIFF Archive code
247 
openStream(Common::SeekableReadStream * stream,uint32 startOffset)248 bool RIFFArchive::openStream(Common::SeekableReadStream *stream, uint32 startOffset) {
249 	close();
250 
251 	_startOffset = startOffset;
252 
253 	stream->seek(startOffset);
254 
255 	if (convertTagToUppercase(stream->readUint32BE()) != MKTAG('R', 'I', 'F', 'F'))
256 		return false;
257 
258 	stream->readUint32LE(); // size
259 
260 	if (convertTagToUppercase(stream->readUint32BE()) != MKTAG('R', 'M', 'M', 'P'))
261 		return false;
262 
263 	if (convertTagToUppercase(stream->readUint32BE()) != MKTAG('C', 'F', 'T', 'C'))
264 		return false;
265 
266 	uint32 cftcSize = stream->readUint32LE();
267 	uint32 startPos = stream->pos();
268 	stream->readUint32LE(); // unknown (always 0?)
269 
270 	while ((uint32)stream->pos() < startPos + cftcSize) {
271 		uint32 tag = convertTagToUppercase(stream->readUint32BE());
272 
273 		uint32 size = stream->readUint32LE();
274 		uint32 id = stream->readUint32LE();
275 		uint32 offset = stream->readUint32LE();
276 
277 		if (tag == 0)
278 			break;
279 
280 		uint32 startResPos = stream->pos();
281 		stream->seek(startOffset + offset + 12);
282 
283 		Common::String name = "";
284 		byte nameSize = stream->readByte();
285 
286 		if (nameSize) {
287 			for (uint8 i = 0; i < nameSize; i++) {
288 				name += stream->readByte();
289 			}
290 		}
291 
292 		stream->seek(startResPos);
293 
294 		debug(3, "Found RIFF resource '%s' %d: %d @ 0x%08x (0x%08x)", tag2str(tag), id, size, offset, startOffset + offset);
295 
296 		ResourceMap &resMap = _types[tag];
297 		Resource &res = resMap[id];
298 		res.offset = offset;
299 		res.size = size;
300 		res.name = name;
301 		res.tag = tag;
302 	}
303 
304 	_stream = stream;
305 	return true;
306 }
307 
getResource(uint32 tag,uint16 id)308 Common::SeekableSubReadStreamEndian *RIFFArchive::getResource(uint32 tag, uint16 id) {
309 	if (!_types.contains(tag))
310 		error("Archive does not contain '%s' %04x", tag2str(tag), id);
311 
312 	const ResourceMap &resMap = _types[tag];
313 
314 	if (!resMap.contains(id))
315 		error("Archive does not contain '%s' %04x", tag2str(tag), id);
316 
317 	const Resource &res = resMap[id];
318 
319 	// Adjust to skip the resource header
320 	uint32 offset = res.offset + 12;
321 	uint32 size = res.size - 4;
322 	// Skip the Pascal string
323 	_stream->seek(_startOffset + offset);
324 	byte stringSize = _stream->readByte(); // 1 for this byte
325 
326 	offset += stringSize + 1;
327 	size -= stringSize + 1;
328 
329 	// Align to nearest word boundary
330 	if (offset & 1) {
331 		offset++;
332 		size--;
333 	}
334 
335 	return new Common::SeekableSubReadStreamEndian(_stream, _startOffset + offset, _startOffset + offset + size, true, DisposeAfterUse::NO);
336 }
337 
338 // RIFX Archive code
339 
openStream(Common::SeekableReadStream * stream,uint32 startOffset)340 bool RIFXArchive::openStream(Common::SeekableReadStream *stream, uint32 startOffset) {
341 	close();
342 
343 	stream->seek(startOffset);
344 
345 	uint32 headerTag = stream->readUint32BE();
346 
347 	if (headerTag == MKTAG('R', 'I', 'F', 'X'))
348 		_isBigEndian = true;
349 	else if (SWAP_BYTES_32(headerTag) == MKTAG('R', 'I', 'F', 'X'))
350 		_isBigEndian = false;
351 	else
352 		return false;
353 
354 	Common::SeekableSubReadStreamEndian subStream(stream, startOffset + 4, stream->size(), _isBigEndian, DisposeAfterUse::NO);
355 
356 	subStream.readUint32(); // size
357 
358 	uint32 rifxType = subStream.readUint32();
359 
360 	if (rifxType != MKTAG('M', 'V', '9', '3') &&
361 		rifxType != MKTAG('A', 'P', 'P', 'L') &&
362 		rifxType != MKTAG('M', 'C', '9', '5'))
363 		return false;
364 
365 	if (subStream.readUint32() != MKTAG('i', 'm', 'a', 'p'))
366 		return false;
367 
368 	subStream.readUint32(); // imap length
369 	subStream.readUint32(); // unknown
370 	uint32 mmapOffset = subStream.readUint32() - startOffset - 4;
371 	uint32 version = subStream.readUint32(); // 0 for 4.0, 0x4c1 for 5.0, 0x4c7 for 6.0, 0x708 for 8.5, 0x742 for 10.0
372 	warning("RIFX: version: %x", version);
373 
374 	subStream.seek(mmapOffset);
375 
376 	if (subStream.readUint32() != MKTAG('m', 'm', 'a', 'p'))
377 		return false;
378 
379 	subStream.readUint32(); // mmap length
380 	subStream.readUint16(); // unknown
381 	subStream.readUint16(); // unknown
382 	subStream.readUint32(); // resCount + empty entries
383 	uint32 resCount = subStream.readUint32();
384 	subStream.skip(8); // all 0xFF
385 	subStream.readUint32(); // unknown
386 
387 	Common::Array<Resource> resources;
388 	resources.reserve(2048);
389 
390 	// Need to look for these two resources
391 	const Resource *keyRes = 0;
392 	const Resource *casRes = 0;
393 
394 	for (uint32 i = 0; i < resCount; i++) {
395 		uint32 tag = subStream.readUint32();
396 		uint32 size = subStream.readUint32();
397 		uint32 offset = subStream.readUint32();
398 		uint16 flags = subStream.readUint16();
399 		uint16 unk1 = subStream.readUint16();
400 		uint32 unk2 = subStream.readUint32();
401 
402 		debug(3, "Found RIFX resource index %d: '%s', %d @ 0x%08x (%d), flags: %x unk1: %x unk2: %x",
403 			i, tag2str(tag), size, offset, offset, flags, unk1, unk2);
404 
405 		Resource res;
406 		res.offset = offset;
407 		res.size = size;
408 		res.tag = tag;
409 		resources.push_back(res);
410 
411 		// APPL is a special case; it has an embedded "normal" archive
412 		if (rifxType == MKTAG('A', 'P', 'P', 'L') && tag == MKTAG('F', 'i', 'l', 'e'))
413 			return openStream(stream, offset);
414 
415 		// Looking for two types here
416 		if (tag == MKTAG('K', 'E', 'Y', '*'))
417 			keyRes = &resources[resources.size() - 1];
418 		else if (tag == MKTAG('C', 'A', 'S', '*'))
419 			casRes = &resources[resources.size() - 1];
420 		// or the children of
421 		else if (tag == MKTAG('S', 'T', 'X', 'T') ||
422 				 tag == MKTAG('B', 'I', 'T', 'D') ||
423 				 tag == MKTAG('D', 'I', 'B', ' ') ||
424 				 tag == MKTAG('R', 'T', 'E', '0') ||
425 				 tag == MKTAG('R', 'T', 'E', '1') ||
426 				 tag == MKTAG('R', 'T', 'E', '2'))
427 			_types[tag][i] = res;
428 	}
429 
430 	// We need to have found the 'File' resource already
431 	if (rifxType == MKTAG('A', 'P', 'P', 'L')) {
432 		warning("No 'File' resource present in APPL archive");
433 		return false;
434 	}
435 
436 	// A KEY* must be present
437 	if (!keyRes) {
438 		warning("No 'KEY*' resource present");
439 		return false;
440 	}
441 
442 	uint castTag = MKTAG('C', 'A', 'S', 't');
443 
444 	// Parse the CAS*, if present
445 	if (casRes) {
446 		Common::SeekableSubReadStreamEndian casStream(stream, casRes->offset + 8, casRes->offset + 8 + casRes->size, _isBigEndian, DisposeAfterUse::NO);
447 
448 		uint casSize = casRes->size / 4;
449 
450 		debugCN(2, kDebugLoading, "CAS*: %d [", casSize);
451 
452 		for (uint i = 0; i < casSize; i++) {
453 			uint32 index = casStream.readUint32BE();
454 			debugCN(2, kDebugLoading, "%d ", index);
455 
456 			Resource &res = resources[index];
457 			res.index = index;
458 			res.castId = i + 1;
459 			_types[castTag][res.castId] = res;
460 		}
461 		debugC(2, kDebugLoading, "]");
462 	}
463 
464 	// Parse the KEY*
465 	Common::SeekableSubReadStreamEndian keyStream(stream, keyRes->offset + 8, keyRes->offset + 8 + keyRes->size, _isBigEndian, DisposeAfterUse::NO);
466 	uint16 unk1 = keyStream.readUint16();
467 	uint16 unk2 = keyStream.readUint16();
468 	uint32 unk3 = keyStream.readUint32();
469 	uint32 keyCount = keyStream.readUint32();
470 
471 	debugC(2, kDebugLoading, "KEY*: unk1: %d unk2: %d unk3: %d keyCount: %d", unk1, unk2, unk3, keyCount);
472 
473 	for (uint32 i = 0; i < keyCount; i++) {
474 		uint32 index = keyStream.readUint32();
475 		uint32 id = keyStream.readUint32();
476 		uint32 resTag = keyStream.readUint32();
477 
478 		debugC(2, kDebugLoading, "KEY*: index: %d id: %d resTag: %s", index, id, tag2str(resTag));
479 
480 		Resource &res = resources[index];
481 		debug(3, "Found RIFX resource: '%s' id: 0x%04x, %d @ 0x%08x (%d)", tag2str(resTag), id, res.size, res.offset, res.offset);
482 		_types[resTag][id] = res;
483 		//_types[resTag][1024 + i + 1] = res;
484 
485 		if (id < 1024) {
486 			for (uint cast = 0; cast < _types[castTag].size(); cast++) {
487 				if (_types[castTag][cast].index == id) {
488 					res.index = index;
489 					_types[castTag][cast].children.push_back(res);
490 					break;
491 				}
492 			}
493 		}
494 	}
495 
496 	_stream = stream;
497 	return true;
498 }
499 
getResource(uint32 tag,uint16 id)500 Common::SeekableSubReadStreamEndian *RIFXArchive::getResource(uint32 tag, uint16 id) {
501 	if (!_types.contains(tag))
502 		error("Archive does not contain '%s' %04x", tag2str(tag), id);
503 
504 	const ResourceMap &resMap = _types[tag];
505 
506 	if (!resMap.contains(id))
507 		error("Archive does not contain '%s' %04x", tag2str(tag), id);
508 
509 	const Resource &res = resMap[id];
510 
511 	uint32 offset = res.offset + 8;
512 	uint32 size = res.size;
513 
514 	return new Common::SeekableSubReadStreamEndian(_stream, offset, offset + size, true, DisposeAfterUse::NO);
515 }
516 
getResourceDetail(uint32 tag,uint16 id)517 Resource RIFXArchive::getResourceDetail(uint32 tag, uint16 id) {
518 	if (!_types.contains(tag))
519 		error("Archive does not contain '%s' %04x", tag2str(tag), id);
520 
521 	const ResourceMap &resMap = _types[tag];
522 
523 	if (!resMap.contains(id))
524 		error("Archive does not contain '%s' %04x", tag2str(tag), id);
525 
526 	return resMap[id];
527 }
528 
529 
530 } // End of namespace Director
531