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 "common/scummsys.h"
24 #include "common/crc.h"
25 #include "common/debug.h"
26 #include "common/util.h"
27 #include "common/file.h"
28 #include "common/fs.h"
29 #include "common/macresman.h"
30 #include "common/md5.h"
31 #include "common/substream.h"
32 #include "common/textconsole.h"
33 #include "common/archive.h"
34 
35 #ifdef MACOSX
36 #include "common/config-manager.h"
37 #endif
38 
39 namespace Common {
40 
41 #define MBI_ZERO1 0
42 #define MBI_NAMELEN 1
43 #define MBI_ZERO2 74
44 #define MBI_ZERO3 82
45 #define MBI_DFLEN 83
46 #define MBI_RFLEN 87
47 #define MAXNAMELEN 63
48 
MacResManager()49 MacResManager::MacResManager() {
50 	_stream = nullptr;
51 	// _baseFileName cleared by String constructor
52 
53 	_mode = kResForkNone;
54 
55 	_resForkOffset = -1;
56 	_resForkSize = 0;
57 
58 	_dataOffset = 0;
59 	_dataLength = 0;
60 	_mapOffset = 0;
61 	_mapLength = 0;
62 	_resMap.reset();
63 	_resTypes = nullptr;
64 	_resLists = nullptr;
65 }
66 
~MacResManager()67 MacResManager::~MacResManager() {
68 	close();
69 }
70 
close()71 void MacResManager::close() {
72 	_resForkOffset = -1;
73 	_mode = kResForkNone;
74 
75 	for (int i = 0; i < _resMap.numTypes; i++) {
76 		for (int j = 0; j < _resTypes[i].items; j++)
77 			if (_resLists[i][j].nameOffset != -1)
78 				delete[] _resLists[i][j].name;
79 
80 		delete[] _resLists[i];
81 	}
82 
83 	delete[] _resLists; _resLists = nullptr;
84 	delete[] _resTypes; _resTypes = nullptr;
85 	delete _stream; _stream = nullptr;
86 	_resMap.numTypes = 0;
87 }
88 
hasDataFork() const89 bool MacResManager::hasDataFork() const {
90 	return !_baseFileName.empty();
91 }
92 
hasResFork() const93 bool MacResManager::hasResFork() const {
94 	return !_baseFileName.empty() && _mode != kResForkNone;
95 }
96 
getResForkDataSize() const97 uint32 MacResManager::getResForkDataSize() const {
98 	if (!hasResFork())
99 		return 0;
100 
101 	_stream->seek(_resForkOffset + 4);
102 	return _stream->readUint32BE();
103 }
104 
computeResForkMD5AsString(uint32 length) const105 String MacResManager::computeResForkMD5AsString(uint32 length) const {
106 	if (!hasResFork())
107 		return String();
108 
109 	_stream->seek(_resForkOffset);
110 	uint32 dataOffset = _stream->readUint32BE() + _resForkOffset;
111 	/* uint32 mapOffset = */ _stream->readUint32BE();
112 	uint32 dataLength = _stream->readUint32BE();
113 
114 
115 	SeekableSubReadStream resForkStream(_stream, dataOffset, dataOffset + dataLength);
116 	return computeStreamMD5AsString(resForkStream, MIN<uint32>(length, _resForkSize));
117 }
118 
open(const Path & fileName)119 bool MacResManager::open(const Path &fileName) {
120 	return open(fileName, SearchMan);
121 }
122 
open(const Path & fileName,Archive & archive)123 bool MacResManager::open(const Path &fileName, Archive &archive) {
124 	close();
125 
126 #ifdef MACOSX
127 	// Check the actual fork on a Mac computer
128 	const ArchiveMemberPtr archiveMember = archive.getMember(fileName);
129 	const Common::FSNode *plainFsNode = dynamic_cast<const Common::FSNode *>(archiveMember.get());
130 	if (plainFsNode) {
131 		String fullPath = plainFsNode->getPath() + "/..namedfork/rsrc";
132 		FSNode resFsNode = FSNode(fullPath);
133 		SeekableReadStream *macResForkRawStream = resFsNode.createReadStream();
134 		if (macResForkRawStream && loadFromRawFork(*macResForkRawStream)) {
135 			_baseFileName = fileName;
136 			return true;
137 		}
138 
139 		delete macResForkRawStream;
140 	}
141 #endif
142 
143 	// Prefer standalone files first, starting with raw forks
144 	SeekableReadStream *stream = archive.createReadStreamForMember(fileName.append(".rsrc"));
145 	if (stream && loadFromRawFork(*stream)) {
146 		_baseFileName = fileName;
147 		return true;
148 	}
149 	delete stream;
150 
151 	// Then try for AppleDouble using Apple's naming
152 	stream = archive.createReadStreamForMember(constructAppleDoubleName(fileName));
153 	if (stream && loadFromAppleDouble(*stream)) {
154 		_baseFileName = fileName;
155 		return true;
156 	}
157 	delete stream;
158 
159 	// Check .bin for MacBinary next
160 	stream = archive.createReadStreamForMember(fileName.append(".bin"));
161 	if (stream && loadFromMacBinary(*stream)) {
162 		_baseFileName = fileName;
163 		return true;
164 	}
165 	delete stream;
166 
167 	// As a last resort, see if just the data fork exists
168 	stream = archive.createReadStreamForMember(fileName);
169 	if (stream) {
170 		_baseFileName = fileName;
171 
172 		// Maybe file is in MacBinary but without .bin extension?
173 		// Check it here
174 		if (isMacBinary(*stream)) {
175 			stream->seek(0);
176 			if (loadFromMacBinary(*stream))
177 				return true;
178 		}
179 
180 		stream->seek(0);
181 		_stream = stream;
182 		return true;
183 	}
184 
185 	// The file doesn't exist
186 	return false;
187 }
188 
exists(const Path & fileName)189 bool MacResManager::exists(const Path &fileName) {
190 	// Try the file name by itself
191 	if (File::exists(fileName))
192 		return true;
193 
194 	// Try the .rsrc extension
195 	if (File::exists(fileName.append(".rsrc")))
196 		return true;
197 
198 	// Check if we have a MacBinary file
199 	File tempFile;
200 	if (tempFile.open(fileName.append(".bin")) && isMacBinary(tempFile))
201 		return true;
202 
203 	// Check if we have an AppleDouble file
204 	if (tempFile.open(constructAppleDoubleName(fileName)) && tempFile.readUint32BE() == 0x00051607)
205 		return true;
206 
207 	return false;
208 }
209 
listFiles(StringArray & files,const String & pattern)210 void MacResManager::listFiles(StringArray &files, const String &pattern) {
211 	// Base names discovered so far.
212 	typedef HashMap<String, bool, IgnoreCase_Hash, IgnoreCase_EqualTo> BaseNameSet;
213 	BaseNameSet baseNames;
214 
215 	// List files itself.
216 	ArchiveMemberList memberList;
217 	SearchMan.listMatchingMembers(memberList, pattern);
218 	SearchMan.listMatchingMembers(memberList, pattern + ".rsrc");
219 	SearchMan.listMatchingMembers(memberList, pattern + ".bin");
220 	SearchMan.listMatchingMembers(memberList, constructAppleDoubleName(pattern));
221 
222 	for (ArchiveMemberList::const_iterator i = memberList.begin(), end = memberList.end(); i != end; ++i) {
223 		String filename = (*i)->getName();
224 
225 		// For raw resource forks and MacBinary files we strip the extension
226 		// here to obtain a valid base name.
227 		int lastDotPos = filename.size() - 1;
228 		for (; lastDotPos >= 0; --lastDotPos) {
229 			if (filename[lastDotPos] == '.') {
230 				break;
231 			}
232 		}
233 
234 		if (lastDotPos != -1) {
235 			const char *extension = filename.c_str() + lastDotPos + 1;
236 			bool removeExtension = false;
237 
238 			// TODO: Should we really keep filenames suggesting raw resource
239 			// forks or MacBinary files but not being such around? This might
240 			// depend on the pattern the client requests...
241 			if (!scumm_stricmp(extension, "rsrc")) {
242 				SeekableReadStream *stream = (*i)->createReadStream();
243 				removeExtension = stream && isRawFork(*stream);
244 				delete stream;
245 			} else if (!scumm_stricmp(extension, "bin")) {
246 				SeekableReadStream *stream = (*i)->createReadStream();
247 				removeExtension = stream && isMacBinary(*stream);
248 				delete stream;
249 			}
250 
251 			if (removeExtension) {
252 				filename.erase(lastDotPos);
253 			}
254 		}
255 
256 		// Strip AppleDouble '._' prefix if applicable.
257 		bool isAppleDoubleName = false;
258 		const String filenameAppleDoubleStripped = disassembleAppleDoubleName(filename, &isAppleDoubleName).toString();
259 
260 		if (isAppleDoubleName) {
261 			SeekableReadStream *stream = (*i)->createReadStream();
262 			if (stream->readUint32BE() == 0x00051607) {
263 				filename = filenameAppleDoubleStripped;
264 			}
265 			// TODO: Should we really keep filenames suggesting AppleDouble
266 			// but not being AppleDouble around? This might depend on the
267 			// pattern the client requests...
268 			delete stream;
269 		}
270 
271 		baseNames[filename] = true;
272 	}
273 
274 	// Append resulting base names to list to indicate found files.
275 	for (BaseNameSet::const_iterator i = baseNames.begin(), end = baseNames.end(); i != end; ++i) {
276 		files.push_back(i->_key);
277 	}
278 }
279 
loadFromAppleDouble(SeekableReadStream & stream)280 bool MacResManager::loadFromAppleDouble(SeekableReadStream &stream) {
281 	if (stream.readUint32BE() != 0x00051607) // tag
282 		return false;
283 
284 	stream.skip(20); // version + home file system
285 
286 	uint16 entryCount = stream.readUint16BE();
287 
288 	for (uint16 i = 0; i < entryCount; i++) {
289 		uint32 id = stream.readUint32BE();
290 		uint32 offset = stream.readUint32BE();
291 		uint32 length = stream.readUint32BE(); // length
292 
293 		if (id == 2) {
294 			// Found the resource fork!
295 			_resForkOffset = offset;
296 			_mode = kResForkAppleDouble;
297 			_resForkSize = length;
298 			return load(stream);
299 		}
300 	}
301 
302 	return false;
303 }
304 
isMacBinary(SeekableReadStream & stream)305 bool MacResManager::isMacBinary(SeekableReadStream &stream) {
306 	byte infoHeader[MBI_INFOHDR];
307 	int resForkOffset = -1;
308 
309 	if (stream.read(infoHeader, MBI_INFOHDR) != MBI_INFOHDR)
310 		return false;
311 
312 	CRC_BINHEX crc;
313 	crc.init();
314 	uint16 checkSum = crc.crcFast(infoHeader, 124);
315 
316 	// Sanity check on the CRC. Some movies could look like MacBinary
317 	if (checkSum != READ_BE_UINT16(&infoHeader[124]))
318 		return false;
319 
320 	if (infoHeader[MBI_ZERO1] == 0 && infoHeader[MBI_ZERO2] == 0 &&
321 		infoHeader[MBI_ZERO3] == 0 && infoHeader[MBI_NAMELEN] <= MAXNAMELEN) {
322 
323 		// Pull out fork lengths
324 		uint32 dataSize = READ_BE_UINT32(infoHeader + MBI_DFLEN);
325 		uint32 rsrcSize = READ_BE_UINT32(infoHeader + MBI_RFLEN);
326 
327 		uint32 dataSizePad = (((dataSize + 127) >> 7) << 7);
328 		// Files produced by ISOBuster are not padded, thus, compare with the actual size
329 		//uint32 rsrcSizePad = (((rsrcSize + 127) >> 7) << 7);
330 
331 		// Length check
332 		if (MBI_INFOHDR + dataSizePad + rsrcSize <= (uint32)stream.size()) {
333 			resForkOffset = MBI_INFOHDR + dataSizePad;
334 		}
335 	}
336 
337 	if (resForkOffset < 0)
338 		return false;
339 
340 	return true;
341 }
342 
isRawFork(SeekableReadStream & stream)343 bool MacResManager::isRawFork(SeekableReadStream &stream) {
344 	// TODO: Is there a better way to detect whether this is a raw fork?
345 	const uint32 dataOffset = stream.readUint32BE();
346 	const uint32 mapOffset = stream.readUint32BE();
347 	const uint32 dataLength = stream.readUint32BE();
348 	const uint32 mapLength = stream.readUint32BE();
349 
350 	return    !stream.eos() && !stream.err()
351 	       && dataOffset < (uint32)stream.size() && dataOffset + dataLength <= (uint32)stream.size()
352 	       && mapOffset < (uint32)stream.size() && mapOffset + mapLength <= (uint32)stream.size();
353 }
354 
loadFromMacBinary(SeekableReadStream & stream)355 bool MacResManager::loadFromMacBinary(SeekableReadStream &stream) {
356 	byte infoHeader[MBI_INFOHDR];
357 	stream.read(infoHeader, MBI_INFOHDR);
358 
359 	// Maybe we have MacBinary?
360 	if (infoHeader[MBI_ZERO1] == 0 && infoHeader[MBI_ZERO2] == 0 &&
361 		infoHeader[MBI_ZERO3] == 0 && infoHeader[MBI_NAMELEN] <= MAXNAMELEN) {
362 
363 		// Pull out fork lengths
364 		uint32 dataSize = READ_BE_UINT32(infoHeader + MBI_DFLEN);
365 		uint32 rsrcSize = READ_BE_UINT32(infoHeader + MBI_RFLEN);
366 
367 		uint32 dataSizePad = (((dataSize + 127) >> 7) << 7);
368 		// Files produced by ISOBuster are not padded, thus, compare with the actual size
369 		//uint32 rsrcSizePad = (((rsrcSize + 127) >> 7) << 7);
370 
371 		// Length check
372 		if (MBI_INFOHDR + dataSizePad + rsrcSize <= (uint32)stream.size()) {
373 			_resForkOffset = MBI_INFOHDR + dataSizePad;
374 			_resForkSize = rsrcSize;
375 		}
376 	}
377 
378 	if (_resForkOffset < 0)
379 		return false;
380 
381 	_mode = kResForkMacBinary;
382 	return load(stream);
383 }
384 
loadFromRawFork(SeekableReadStream & stream)385 bool MacResManager::loadFromRawFork(SeekableReadStream &stream) {
386 	_mode = kResForkRaw;
387 	_resForkOffset = 0;
388 	_resForkSize = stream.size();
389 	return load(stream);
390 }
391 
load(SeekableReadStream & stream)392 bool MacResManager::load(SeekableReadStream &stream) {
393 	if (_mode == kResForkNone)
394 		return false;
395 
396 	stream.seek(_resForkOffset);
397 
398 	_dataOffset = stream.readUint32BE() + _resForkOffset;
399 	_mapOffset = stream.readUint32BE() + _resForkOffset;
400 	_dataLength = stream.readUint32BE();
401 	_mapLength = stream.readUint32BE();
402 
403 	// do sanity check
404 	if (stream.eos() || _dataOffset >= (uint32)stream.size() || _mapOffset >= (uint32)stream.size() ||
405 			_dataLength + _mapLength  > (uint32)stream.size()) {
406 		_resForkOffset = -1;
407 		_mode = kResForkNone;
408 		return false;
409 	}
410 
411 	debug(7, "got header: data %d [%d] map %d [%d]",
412 		_dataOffset, _dataLength, _mapOffset, _mapLength);
413 
414 	_stream = &stream;
415 
416 	readMap();
417 	return true;
418 }
419 
getDataFork()420 SeekableReadStream *MacResManager::getDataFork() {
421 	if (!_stream)
422 		return nullptr;
423 
424 	if (_mode == kResForkMacBinary) {
425 		_stream->seek(MBI_DFLEN);
426 		uint32 dataSize = _stream->readUint32BE();
427 		return new SeekableSubReadStream(_stream, MBI_INFOHDR, MBI_INFOHDR + dataSize);
428 	}
429 
430 	File *file = new File();
431 	if (file->open(_baseFileName))
432 		return file;
433 	delete file;
434 
435 	return nullptr;
436 }
437 
getResIDArray(uint32 typeID)438 MacResIDArray MacResManager::getResIDArray(uint32 typeID) {
439 	int typeNum = -1;
440 	MacResIDArray res;
441 
442 	for (int i = 0; i < _resMap.numTypes; i++)
443 		if (_resTypes[i].id == typeID) {
444 			typeNum = i;
445 			break;
446 		}
447 
448 	if (typeNum == -1)
449 		return res;
450 
451 	res.resize(_resTypes[typeNum].items);
452 
453 	for (int i = 0; i < _resTypes[typeNum].items; i++)
454 		res[i] = _resLists[typeNum][i].id;
455 
456 	return res;
457 }
458 
getResTagArray()459 MacResTagArray MacResManager::getResTagArray() {
460 	MacResTagArray tagArray;
461 
462 	if (!hasResFork())
463 		return tagArray;
464 
465 	tagArray.resize(_resMap.numTypes);
466 
467 	for (uint32 i = 0; i < _resMap.numTypes; i++)
468 		tagArray[i] = _resTypes[i].id;
469 
470 	return tagArray;
471 }
472 
getResName(uint32 typeID,uint16 resID) const473 String MacResManager::getResName(uint32 typeID, uint16 resID) const {
474 	int typeNum = -1;
475 
476 	for (int i = 0; i < _resMap.numTypes; i++)
477 		if (_resTypes[i].id == typeID) {
478 			typeNum = i;
479 			break;
480 		}
481 
482 	if (typeNum == -1)
483 		return "";
484 
485 	for (int i = 0; i < _resTypes[typeNum].items; i++)
486 		if (_resLists[typeNum][i].id == resID)
487 			return _resLists[typeNum][i].name;
488 
489 	return "";
490 }
491 
getResource(uint32 typeID,uint16 resID)492 SeekableReadStream *MacResManager::getResource(uint32 typeID, uint16 resID) {
493 	int typeNum = -1;
494 	int resNum = -1;
495 
496 	for (int i = 0; i < _resMap.numTypes; i++)
497 		if (_resTypes[i].id == typeID) {
498 			typeNum = i;
499 			break;
500 		}
501 
502 	if (typeNum == -1)
503 		return nullptr;
504 
505 	for (int i = 0; i < _resTypes[typeNum].items; i++)
506 		if (_resLists[typeNum][i].id == resID) {
507 			resNum = i;
508 			break;
509 		}
510 
511 	if (resNum == -1)
512 		return nullptr;
513 
514 	_stream->seek(_dataOffset + _resLists[typeNum][resNum].dataOffset);
515 	uint32 len = _stream->readUint32BE();
516 
517 	// Ignore resources with 0 length
518 	if (!len)
519 		return nullptr;
520 
521 	return _stream->readStream(len);
522 }
523 
getResource(const String & fileName)524 SeekableReadStream *MacResManager::getResource(const String &fileName) {
525 	for (uint32 i = 0; i < _resMap.numTypes; i++) {
526 		for (uint32 j = 0; j < _resTypes[i].items; j++) {
527 			if (_resLists[i][j].nameOffset != -1 && fileName.equalsIgnoreCase(_resLists[i][j].name)) {
528 				_stream->seek(_dataOffset + _resLists[i][j].dataOffset);
529 				uint32 len = _stream->readUint32BE();
530 
531 				// Ignore resources with 0 length
532 				if (!len)
533 					return nullptr;
534 
535 				return _stream->readStream(len);
536 			}
537 		}
538 	}
539 
540 	return nullptr;
541 }
542 
getResource(uint32 typeID,const String & fileName)543 SeekableReadStream *MacResManager::getResource(uint32 typeID, const String &fileName) {
544 	for (uint32 i = 0; i < _resMap.numTypes; i++) {
545 		if (_resTypes[i].id != typeID)
546 			continue;
547 
548 		for (uint32 j = 0; j < _resTypes[i].items; j++) {
549 			if (_resLists[i][j].nameOffset != -1 && fileName.equalsIgnoreCase(_resLists[i][j].name)) {
550 				_stream->seek(_dataOffset + _resLists[i][j].dataOffset);
551 				uint32 len = _stream->readUint32BE();
552 
553 				// Ignore resources with 0 length
554 				if (!len)
555 					return nullptr;
556 
557 				return _stream->readStream(len);
558 			}
559 		}
560 	}
561 
562 	return nullptr;
563 }
564 
readMap()565 void MacResManager::readMap() {
566 	_stream->seek(_mapOffset + 22);
567 
568 	_resMap.resAttr = _stream->readUint16BE();
569 	_resMap.typeOffset = _stream->readUint16BE();
570 	_resMap.nameOffset = _stream->readUint16BE();
571 	_resMap.numTypes = _stream->readUint16BE();
572 	_resMap.numTypes++;
573 
574 	_stream->seek(_mapOffset + _resMap.typeOffset + 2);
575 	_resTypes = new ResType[_resMap.numTypes];
576 
577 	for (int i = 0; i < _resMap.numTypes; i++) {
578 		_resTypes[i].id = _stream->readUint32BE();
579 		_resTypes[i].items = _stream->readUint16BE();
580 		_resTypes[i].offset = _stream->readUint16BE();
581 		_resTypes[i].items++;
582 
583 		debug(8, "resType: <%s> items: %d offset: %d (0x%x)", tag2str(_resTypes[i].id), _resTypes[i].items,  _resTypes[i].offset, _resTypes[i].offset);
584 	}
585 
586 	_resLists = new ResPtr[_resMap.numTypes];
587 
588 	for (int i = 0; i < _resMap.numTypes; i++) {
589 		_resLists[i] = new Resource[_resTypes[i].items];
590 		_stream->seek(_resTypes[i].offset + _mapOffset + _resMap.typeOffset);
591 
592 		for (int j = 0; j < _resTypes[i].items; j++) {
593 			ResPtr resPtr = _resLists[i] + j;
594 
595 			resPtr->id = _stream->readUint16BE();
596 			resPtr->nameOffset = _stream->readUint16BE();
597 			resPtr->dataOffset = _stream->readUint32BE();
598 			_stream->readUint32BE();
599 			resPtr->name = nullptr;
600 
601 			resPtr->attr = resPtr->dataOffset >> 24;
602 			resPtr->dataOffset &= 0xFFFFFF;
603 		}
604 
605 		for (int j = 0; j < _resTypes[i].items; j++) {
606 			if (_resLists[i][j].nameOffset != -1) {
607 				_stream->seek(_resLists[i][j].nameOffset + _mapOffset + _resMap.nameOffset);
608 
609 				byte len = _stream->readByte();
610 				_resLists[i][j].name = new char[len + 1];
611 				_resLists[i][j].name[len] = 0;
612 				_stream->read(_resLists[i][j].name, len);
613 			}
614 		}
615 	}
616 }
617 
constructAppleDoubleName(Path name)618 Path MacResManager::constructAppleDoubleName(Path name) {
619 	// Insert "._" before the last portion of a path name
620 	String rawName = name.rawString();
621 	for (int i = rawName.size() - 1; i >= 0; i--) {
622 		if (i == 0) {
623 			rawName.insertChar('_', 0);
624 			rawName.insertChar('.', 0);
625 		} else if (rawName[i] == DIR_SEPARATOR) {
626 			rawName.insertChar('_', i + 1);
627 			rawName.insertChar('.', i + 1);
628 			break;
629 		}
630 	}
631 
632 	return Path(rawName, DIR_SEPARATOR);
633 }
634 
disassembleAppleDoubleName(Path name,bool * isAppleDouble)635 Path MacResManager::disassembleAppleDoubleName(Path name, bool *isAppleDouble) {
636 	if (isAppleDouble) {
637 		*isAppleDouble = false;
638 	}
639 
640 	// Remove "._" before the last portion of a path name.
641 	String rawName = name.rawString();
642 	for (int i = rawName.size() - 1; i >= 0; --i) {
643 		if (i == 0) {
644 			if (rawName.size() > 2 && rawName[0] == '.' && rawName[1] == '_') {
645 				rawName.erase(0, 2);
646 				if (isAppleDouble) {
647 					*isAppleDouble = true;
648 				}
649 			}
650 		} else if (rawName[i] == DIR_SEPARATOR) {
651 			if ((uint)(i + 2) < rawName.size() && rawName[i + 1] == '.' && rawName[i + 2] == '_') {
652 				rawName.erase(i + 1, 2);
653 				if (isAppleDouble) {
654 					*isAppleDouble = true;
655 				}
656 			}
657 			break;
658 		}
659 	}
660 
661 	return Path(rawName, DIR_SEPARATOR);
662 }
663 
dumpRaw()664 void MacResManager::dumpRaw() {
665 	byte *data = nullptr;
666 	uint dataSize = 0;
667 	Common::DumpFile out;
668 
669 	for (int i = 0; i < _resMap.numTypes; i++) {
670 		for (int j = 0; j < _resTypes[i].items; j++) {
671 			_stream->seek(_dataOffset + _resLists[i][j].dataOffset);
672 			uint32 len = _stream->readUint32BE();
673 
674 			if (dataSize < len) {
675 				free(data);
676 				data = (byte *)malloc(len);
677 				dataSize = len;
678 			}
679 
680 			Common::String filename = Common::String::format("./dumps/%s-%s-%d", _baseFileName.toString().c_str(), tag2str(_resTypes[i].id), j);
681 			_stream->read(data, len);
682 
683 			if (!out.open(filename)) {
684 				warning("MacResManager::dumpRaw(): Can not open dump file %s", filename.c_str());
685 				return;
686 			}
687 
688 			out.write(data, len);
689 
690 			out.flush();
691 			out.close();
692 
693 		}
694 	}
695 }
696 
parseVers(SeekableReadStream * vvers)697 MacResManager::MacVers *MacResManager::parseVers(SeekableReadStream *vvers) {
698 	MacVers *v = new MacVers;
699 
700 	v->majorVer = vvers->readByte();
701 	v->minorVer = vvers->readByte();
702 	byte devStage = vvers->readByte();
703 	const char *s;
704 	switch (devStage) {
705 	case 0x20: s = "Prealpha"; break;
706 	case 0x40: s = "Alpha";    break;
707 	case 0x60: s = "Beta";     break;
708 	case 0x80: s = "Final";    break;
709 	default:   s = "";
710 	}
711 	v->devStr = s;
712 
713 	v->preReleaseVer = vvers->readByte();
714 	v->region = vvers->readUint16BE();
715 	v->str = vvers->readPascalString();
716 	v->msg = vvers->readPascalString();
717 
718 	return v;
719 }
720 
721 } // End of namespace Common
722