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