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