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