Ticket #5796: resource.cpp

File resource.cpp, 69.4 KB (added by SF/tinekefrineke, 13 years ago)

Fixed file

Line 
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 * $URL$
22 * $Id$
23 *
24 */
25
26// Resource library
27
28#include "common/file.h"
29#include "common/fs.h"
30#include "common/macresman.h"
31
32#include "sci/resource.h"
33#include "sci/resource_intern.h"
34#include "sci/util.h"
35
36namespace Sci {
37
38enum {
39 SCI0_RESMAP_ENTRIES_SIZE = 6,
40 SCI1_RESMAP_ENTRIES_SIZE = 6,
41 SCI11_RESMAP_ENTRIES_SIZE = 5
42};
43
44/** resource type for SCI1 resource.map file */
45struct resource_index_t {
46 uint16 wOffset;
47 uint16 wSize;
48};
49
50//////////////////////////////////////////////////////////////////////
51
52static SciVersion s_sciVersion = SCI_VERSION_NONE; // FIXME: Move this inside a suitable class, e.g. SciEngine
53
54SciVersion getSciVersion() {
55 assert(s_sciVersion != SCI_VERSION_NONE);
56 return s_sciVersion;
57}
58
59const char *getSciVersionDesc(SciVersion version) {
60 switch (version) {
61 case SCI_VERSION_NONE:
62 return "Invalid SCI version";
63 case SCI_VERSION_0_EARLY:
64 return "Early SCI0";
65 case SCI_VERSION_0_LATE:
66 return "Late SCI0";
67 case SCI_VERSION_01:
68 return "SCI01";
69 case SCI_VERSION_1_EGA:
70 return "SCI1 EGA";
71 case SCI_VERSION_1_EARLY:
72 return "Early SCI1";
73 case SCI_VERSION_1_MIDDLE:
74 return "Middle SCI1";
75 case SCI_VERSION_1_LATE:
76 return "Late SCI1";
77 case SCI_VERSION_1_1:
78 return "SCI1.1";
79 case SCI_VERSION_2:
80 return "SCI2";
81 case SCI_VERSION_2_1:
82 return "SCI2.1";
83 case SCI_VERSION_3:
84 return "SCI3";
85 default:
86 return "Unknown";
87 }
88}
89
90//////////////////////////////////////////////////////////////////////
91
92
93#undef SCI_REQUIRE_RESOURCE_FILES
94
95//#define SCI_VERBOSE_resMan 1
96
97static const char *sci_error_types[] = {
98 "No error",
99 "I/O error",
100 "Resource is empty (size 0)",
101 "resource.map entry is invalid",
102 "resource.map file not found",
103 "No resource files found",
104 "Unknown compression method",
105 "Decompression failed: Decompression buffer overflow",
106 "Decompression failed: Sanity check failed",
107 "Decompression failed: Resource too big",
108 "SCI version is unsupported"
109};
110
111static const char *s_resourceTypeNames[] = {
112 "view", "pic", "script", "text", "sound",
113 "memory", "vocab", "font", "cursor",
114 "patch", "bitmap", "palette", "cdaudio",
115 "audio", "sync", "message", "map", "heap",
116 "audio36", "sync36", "xlate", "robot", "vmd",
117 "chunk", "macibin", "macibis", "macpict"
118};
119
120static const char *s_resourceTypeSuffixes[] = {
121 "v56", "p56", "scr", "tex", "snd",
122 "", "voc", "fon", "cur", "pat",
123 "bit", "pal", "cda", "aud", "syn",
124 "msg", "map", "hep", "", "",
125 "trn", "rbt", "vmd", "chk", "",
126 "", ""
127};
128
129const char *getResourceTypeName(ResourceType restype) {
130 if (restype != kResourceTypeInvalid)
131 return s_resourceTypeNames[restype];
132 else
133 return "invalid";
134}
135
136static const ResourceType s_resTypeMapSci0[] = {
137 kResourceTypeView, kResourceTypePic, kResourceTypeScript, kResourceTypeText, // 0x00-0x03
138 kResourceTypeSound, kResourceTypeMemory, kResourceTypeVocab, kResourceTypeFont, // 0x04-0x07
139 kResourceTypeCursor, kResourceTypePatch, kResourceTypeBitmap, kResourceTypePalette, // 0x08-0x0B
140 kResourceTypeCdAudio, kResourceTypeAudio, kResourceTypeSync, kResourceTypeMessage, // 0x0C-0x0F
141 kResourceTypeMap, kResourceTypeHeap, kResourceTypeAudio36, kResourceTypeSync36, // 0x10-0x13
142 kResourceTypeTranslation // 0x14
143};
144
145// TODO: 12 should be "Wave", but SCI seems to just store it in Audio resources
146static const ResourceType s_resTypeMapSci21[] = {
147 kResourceTypeView, kResourceTypePic, kResourceTypeScript, kResourceTypeText, // 0x00-0x03
148 kResourceTypeSound, kResourceTypeMemory, kResourceTypeVocab, kResourceTypeFont, // 0x04-0x07
149 kResourceTypeCursor, kResourceTypePatch, kResourceTypeBitmap, kResourceTypePalette, // 0x08-0x0B
150 kResourceTypeInvalid, kResourceTypeAudio, kResourceTypeSync, kResourceTypeMessage, // 0x0C-0x0F
151 kResourceTypeMap, kResourceTypeHeap, kResourceTypeChunk, kResourceTypeAudio36, // 0x10-0x13
152 kResourceTypeSync36, kResourceTypeTranslation, kResourceTypeRobot, kResourceTypeVMD // 0x14-0x17
153};
154
155ResourceType ResourceManager::convertResType(byte type) {
156 type &= 0x7f;
157
158 if (_mapVersion != kResVersionSci32) {
159 // SCI0 - SCI2
160 if (type < ARRAYSIZE(s_resTypeMapSci0))
161 return s_resTypeMapSci0[type];
162 } else {
163 // SCI2.1+
164 if (type < ARRAYSIZE(s_resTypeMapSci21)) {
165 // LSL6 hires doesn't have the chunk resource type, to match
166 // the resource types of the lowres version, thus we use the
167 // older resource types here
168 if (g_sci && g_sci->getGameId() == GID_LSL6HIRES)
169 return s_resTypeMapSci0[type];
170 else
171 return s_resTypeMapSci21[type];
172 }
173 }
174
175 return kResourceTypeInvalid;
176}
177
178//-- Resource main functions --
179Resource::Resource(ResourceManager *resMan, ResourceId id) : _resMan(resMan), _id(id) {
180 data = NULL;
181 size = 0;
182 _fileOffset = 0;
183 _status = kResStatusNoMalloc;
184 _lockers = 0;
185 _source = NULL;
186 _header = NULL;
187 _headerSize = 0;
188}
189
190Resource::~Resource() {
191 delete[] data;
192 if (_source && _source->getSourceType() == kSourcePatch)
193 delete _source;
194}
195
196void Resource::unalloc() {
197 delete[] data;
198 data = NULL;
199 _status = kResStatusNoMalloc;
200}
201
202void Resource::writeToStream(Common::WriteStream *stream) const {
203 stream->writeByte(getType() | 0x80); // 0x80 is required by old sierra sci, otherwise it wont accept the patch file
204 stream->writeByte(_headerSize);
205 if (_headerSize > 0)
206 stream->write(_header, _headerSize);
207 stream->write(data, size);
208}
209
210uint32 Resource::getAudioCompressionType() const {
211 return _source->getAudioCompressionType();
212}
213
214uint32 AudioVolumeResourceSource::getAudioCompressionType() const {
215 return _audioCompressionType;
216}
217
218
219ResourceSource::ResourceSource(ResSourceType type, const Common::String &name, int volNum, const Common::FSNode *resFile)
220 : _sourceType(type), _name(name), _volumeNumber(volNum), _resourceFile(resFile) {
221 _scanned = false;
222}
223
224ResourceSource::~ResourceSource() {
225}
226
227MacResourceForkResourceSource::MacResourceForkResourceSource(const Common::String &name, int volNum)
228 : ResourceSource(kSourceMacResourceFork, name, volNum) {
229 _macResMan = new Common::MacResManager();
230 assert(_macResMan);
231}
232
233MacResourceForkResourceSource::~MacResourceForkResourceSource() {
234 delete _macResMan;
235}
236
237//-- resMan helper functions --
238
239// Resource source list management
240
241ResourceSource *ResourceManager::addExternalMap(const Common::String &filename, int volume_nr) {
242 ResourceSource *newsrc = new ExtMapResourceSource(filename, volume_nr);
243
244 _sources.push_back(newsrc);
245 return newsrc;
246}
247
248ResourceSource *ResourceManager::addExternalMap(const Common::FSNode *mapFile, int volume_nr) {
249 ResourceSource *newsrc = new ExtMapResourceSource(mapFile->getName(), volume_nr, mapFile);
250
251 _sources.push_back(newsrc);
252 return newsrc;
253}
254
255ResourceSource *ResourceManager::addSource(ResourceSource *newsrc) {
256 assert(newsrc);
257
258 _sources.push_back(newsrc);
259 return newsrc;
260}
261
262ResourceSource *ResourceManager::addPatchDir(const Common::String &dirname) {
263 ResourceSource *newsrc = new DirectoryResourceSource(dirname);
264
265 _sources.push_back(newsrc);
266 return 0;
267}
268
269ResourceSource *ResourceManager::findVolume(ResourceSource *map, int volume_nr) {
270 for (Common::List<ResourceSource *>::iterator it = _sources.begin(); it != _sources.end(); ++it) {
271 ResourceSource *src = (*it)->findVolume(map, volume_nr);
272 if (src)
273 return src;
274 }
275
276 return NULL;
277}
278
279// Resource manager constructors and operations
280
281bool Resource::loadPatch(Common::SeekableReadStream *file) {
282 Resource *res = this;
283
284 // We assume that the resource type matches res->type
285 // We also assume that the current file position is right at the actual data (behind resourceid/headersize byte)
286
287 res->data = new byte[res->size];
288
289 if (res->_headerSize > 0)
290 res->_header = new byte[res->_headerSize];
291
292 if ((res->data == NULL) || ((res->_headerSize > 0) && (res->_header == NULL))) {
293 error("Can't allocate %d bytes needed for loading %s", res->size + res->_headerSize, res->_id.toString().c_str());
294 }
295
296 unsigned int really_read;
297 if (res->_headerSize > 0) {
298 really_read = file->read(res->_header, res->_headerSize);
299 if (really_read != res->_headerSize)
300 error("Read %d bytes from %s but expected %d", really_read, res->_id.toString().c_str(), res->_headerSize);
301 }
302
303 really_read = file->read(res->data, res->size);
304 if (really_read != res->size)
305 error("Read %d bytes from %s but expected %d", really_read, res->_id.toString().c_str(), res->size);
306
307 res->_status = kResStatusAllocated;
308 return true;
309}
310
311bool Resource::loadFromPatchFile() {
312 Common::File file;
313 const Common::String &filename = _source->getLocationName();
314 if (file.open(filename) == false) {
315 warning("Failed to open patch file %s", filename.c_str());
316 unalloc();
317 return false;
318 }
319 // Skip resourceid and header size byte
320 file.seek(2, SEEK_SET);
321 return loadPatch(&file);
322}
323
324Common::SeekableReadStream *ResourceManager::getVolumeFile(ResourceSource *source) {
325 Common::List<Common::File *>::iterator it = _volumeFiles.begin();
326 Common::File *file;
327
328 if (source->_resourceFile)
329 return source->_resourceFile->createReadStream();
330
331 const char *filename = source->getLocationName().c_str();
332
333 // check if file is already opened
334 while (it != _volumeFiles.end()) {
335 file = *it;
336 if (scumm_stricmp(file->getName(), filename) == 0) {
337 // move file to top
338 if (it != _volumeFiles.begin()) {
339 _volumeFiles.erase(it);
340 _volumeFiles.push_front(file);
341 }
342 return file;
343 }
344 ++it;
345 }
346 // adding a new file
347 file = new Common::File;
348 if (file->open(filename)) {
349 if (_volumeFiles.size() == MAX_OPENED_VOLUMES) {
350 it = --_volumeFiles.end();
351 delete *it;
352 _volumeFiles.erase(it);
353 }
354 _volumeFiles.push_front(file);
355 return file;
356 }
357 // failed
358 delete file;
359 return NULL;
360}
361
362void ResourceManager::loadResource(Resource *res) {
363 res->_source->loadResource(this, res);
364}
365
366
367void PatchResourceSource::loadResource(ResourceManager *resMan, Resource *res) {
368 bool result = res->loadFromPatchFile();
369 if (!result) {
370 // TODO: We used to fallback to the "default" code here if loadFromPatchFile
371 // failed, but I am not sure whether that is really appropriate.
372 // In fact it looks like a bug to me, so I commented this out for now.
373 //ResourceSource::loadResource(res);
374 }
375}
376
377static Common::Array<uint32> resTypeToMacTags(ResourceType type);
378
379void MacResourceForkResourceSource::loadResource(ResourceManager *resMan, Resource *res) {
380 Common::SeekableReadStream *stream = 0;
381 Common::Array<uint32> tagArray = resTypeToMacTags(res->getType());
382
383 for (uint32 i = 0; i < tagArray.size() && !stream; i++)
384 stream = _macResMan->getResource(tagArray[i], res->getNumber());
385
386 if (!stream)
387 error("Could not get Mac resource fork resource: %s %d", getResourceTypeName(res->getType()), res->getNumber());
388
389 int error = res->decompress(resMan->getVolVersion(), stream);
390 if (error) {
391 warning("Error %d occurred while reading %s from Mac resource file: %s",
392 error, res->_id.toString().c_str(), sci_error_types[error]);
393 res->unalloc();
394 }
395}
396
397Common::SeekableReadStream *ResourceSource::getVolumeFile(ResourceManager *resMan, Resource *res) {
398 Common::SeekableReadStream *fileStream = resMan->getVolumeFile(this);
399
400 if (!fileStream) {
401 warning("Failed to open %s", getLocationName().c_str());
402 if (res)
403 res->unalloc();
404 }
405
406 return fileStream;
407}
408
409void WaveResourceSource::loadResource(ResourceManager *resMan, Resource *res) {
410 Common::SeekableReadStream *fileStream = getVolumeFile(resMan, res);
411 if (!fileStream)
412 return;
413
414 fileStream->seek(res->_fileOffset, SEEK_SET);
415 res->loadFromWaveFile(fileStream);
416 if (_resourceFile)
417 delete fileStream;
418}
419
420void AudioVolumeResourceSource::loadResource(ResourceManager *resMan, Resource *res) {
421 Common::SeekableReadStream *fileStream = getVolumeFile(resMan, res);
422 if (!fileStream)
423 return;
424
425 if (_audioCompressionType) {
426 // this file is compressed, so lookup our offset in the offset-translation table and get the new offset
427 // also calculate the compressed size by using the next offset
428 int32 *mappingTable = _audioCompressionOffsetMapping;
429 int32 compressedOffset = 0;
430
431 do {
432 if (*mappingTable == res->_fileOffset) {
433 mappingTable++;
434 compressedOffset = *mappingTable;
435 // Go to next compressed offset and use that to calculate size of compressed sample
436 switch (res->getType()) {
437 case kResourceTypeSync:
438 case kResourceTypeSync36:
439 // we should already have a (valid) size
440 break;
441 default:
442 mappingTable += 2;
443 res->size = *mappingTable - compressedOffset;
444 }
445 break;
446 }
447 mappingTable += 2;
448 } while (*mappingTable);
449
450 if (!compressedOffset)
451 error("could not translate offset to compressed offset in audio volume");
452 fileStream->seek(compressedOffset, SEEK_SET);
453
454 switch (res->getType()) {
455 case kResourceTypeAudio:
456 case kResourceTypeAudio36:
457 // Directly read the stream, compressed audio wont have resource type id and header size for SCI1.1
458 res->loadFromAudioVolumeSCI1(fileStream);
459 if (_resourceFile)
460 delete fileStream;
461 return;
462 default:
463 break;
464 }
465 } else {
466 // original file, directly seek to given offset and get SCI1/SCI1.1 audio resource
467 fileStream->seek(res->_fileOffset, SEEK_SET);
468 }
469 if (getSciVersion() < SCI_VERSION_1_1)
470 res->loadFromAudioVolumeSCI1(fileStream);
471 else
472 res->loadFromAudioVolumeSCI11(fileStream);
473
474 if (_resourceFile)
475 delete fileStream;
476}
477
478void ResourceSource::loadResource(ResourceManager *resMan, Resource *res) {
479 Common::SeekableReadStream *fileStream = getVolumeFile(resMan, res);
480 if (!fileStream)
481 return;
482
483 //fprintf(stderr, "%s\n", dynamic_cast<Common::File *>(fileStream)->getName());
484
485 fileStream->seek(res->_fileOffset, SEEK_SET);
486
487 int error = res->decompress(resMan->getVolVersion(), fileStream);
488 if (error) {
489 warning("Error %d occurred while reading %s from resource file %s: %s",
490 error, res->_id.toString().c_str(), res->getResourceLocation().c_str(),
491 sci_error_types[error]);
492 res->unalloc();
493 }
494
495 if (_resourceFile)
496 delete fileStream;
497}
498
499
500Resource *ResourceManager::testResource(ResourceId id) {
501
502 Resource * resrc = _resMap.getVal(id, NULL);
503 return resrc;
504}
505
506int ResourceManager::addAppropriateSources() {
507 Common::ArchiveMemberList files;
508
509 if (Common::File::exists("resource.map")) {
510 // SCI0-SCI2 file naming scheme
511 ResourceSource *map = addExternalMap("resource.map");
512
513 SearchMan.listMatchingMembers(files, "resource.0??");
514
515 for (Common::ArchiveMemberList::const_iterator x = files.begin(); x != files.end(); ++x) {
516 const Common::String name = (*x)->getName();
517 const char *dot = strrchr(name.c_str(), '.');
518 int number = atoi(dot + 1);
519
520 addSource(new VolumeResourceSource(name, map, number));
521 }
522#ifdef ENABLE_SCI32
523 // GK1CD hires content
524 if (Common::File::exists("alt.map") && Common::File::exists("resource.alt"))
525 addSource(new VolumeResourceSource("resource.alt", addExternalMap("alt.map", 10), 10));
526#endif
527 } else if (Common::File::exists("Data1")) {
528 // Mac SCI1.1+ file naming scheme
529 SearchMan.listMatchingMembers(files, "Data?*");
530
531 for (Common::ArchiveMemberList::const_iterator x = files.begin(); x != files.end(); ++x) {
532 Common::String filename = (*x)->getName();
533 addSource(new MacResourceForkResourceSource(filename, atoi(filename.c_str() + 4)));
534 }
535#ifdef ENABLE_SCI32
536 // Mac SCI32 games have extra folders for patches
537 addPatchDir("Robot Folder");
538 addPatchDir("Sound Folder");
539 addPatchDir("Voices Folder");
540 addPatchDir("Voices");
541 //addPatchDir("VMD Folder");
542
543 // There can also be a "Patches" resource fork with patches
544 if (Common::File::exists("Patches"))
545 addSource(new MacResourceForkResourceSource("Patches", 100));
546 } else {
547 // SCI2.1-SCI3 file naming scheme
548 Common::ArchiveMemberList mapFiles;
549 SearchMan.listMatchingMembers(mapFiles, "resmap.0??");
550 SearchMan.listMatchingMembers(files, "ressci.0??");
551
552 // We need to have the same number of maps as resource archives
553 if (mapFiles.empty() || files.empty() || mapFiles.size() != files.size())
554 return 0;
555
556 for (Common::ArchiveMemberList::const_iterator mapIterator = mapFiles.begin(); mapIterator != mapFiles.end(); ++mapIterator) {
557 Common::String mapName = (*mapIterator)->getName();
558 int mapNumber = atoi(strrchr(mapName.c_str(), '.') + 1);
559
560 for (Common::ArchiveMemberList::const_iterator fileIterator = files.begin(); fileIterator != files.end(); ++fileIterator) {
561 Common::String resName = (*fileIterator)->getName();
562 int resNumber = atoi(strrchr(resName.c_str(), '.') + 1);
563
564 if (mapNumber == resNumber) {
565 addSource(new VolumeResourceSource(resName, addExternalMap(mapName, mapNumber), mapNumber));
566 break;
567 }
568 }
569 }
570
571 // SCI2.1 resource patches
572 if (Common::File::exists("resmap.pat") && Common::File::exists("ressci.pat")) {
573 // We add this resource with a map which surely won't exist
574 addSource(new VolumeResourceSource("ressci.pat", addExternalMap("resmap.pat", 100), 100));
575 }
576 }
577#else
578 } else
579 return 0;
580#endif
581
582 addPatchDir(".");
583
584 if (Common::File::exists("message.map"))
585 addSource(new VolumeResourceSource("resource.msg", addExternalMap("message.map"), 0));
586
587 if (Common::File::exists("altres.map"))
588 addSource(new VolumeResourceSource("altres.000", addExternalMap("altres.map"), 0));
589
590 return 1;
591}
592
593int ResourceManager::addAppropriateSources(const Common::FSList &fslist) {
594 ResourceSource *map = 0;
595#ifdef ENABLE_SCI32
596 ResourceSource *sci21PatchMap = 0;
597 const Common::FSNode *sci21PatchRes = 0;
598#endif
599
600 // First, find resource.map
601 for (Common::FSList::const_iterator file = fslist.begin(); file != fslist.end(); ++file) {
602 if (file->isDirectory())
603 continue;
604
605 Common::String filename = file->getName();
606 filename.toLowercase();
607
608 if (filename.contains("resource.map"))
609 map = addExternalMap(file);
610
611 if (filename.contains("resmap.0")) {
612 const char *dot = strrchr(file->getName().c_str(), '.');
613 int number = atoi(dot + 1);
614 map = addExternalMap(file, number);
615 }
616
617#ifdef ENABLE_SCI32
618 // SCI2.1 resource patches
619 if (filename.contains("resmap.pat"))
620 sci21PatchMap = addExternalMap(file, 100);
621
622 if (filename.contains("ressci.pat"))
623 sci21PatchRes = file;
624#endif
625 }
626
627 if (!map)
628 return 0;
629
630#ifdef ENABLE_SCI32
631 if (sci21PatchMap && sci21PatchRes)
632 addSource(new VolumeResourceSource(sci21PatchRes->getName(), sci21PatchMap, 100, sci21PatchRes));
633#endif
634
635 // Now find all the resource.0?? files
636 for (Common::FSList::const_iterator file = fslist.begin(); file != fslist.end(); ++file) {
637 if (file->isDirectory())
638 continue;
639
640 Common::String filename = file->getName();
641 filename.toLowercase();
642
643 if (filename.contains("resource.0") || filename.contains("ressci.0")) {
644 const char *dot = strrchr(filename.c_str(), '.');
645 int number = atoi(dot + 1);
646
647 addSource(new VolumeResourceSource(file->getName(), map, number, file));
648 }
649 }
650
651 // This function is only called by the advanced detector, and we don't really need
652 // to add a patch directory or message.map here
653
654 return 1;
655}
656
657int ResourceManager::addInternalSources() {
658 Common::List<ResourceId> *resources = listResources(kResourceTypeMap);
659 Common::List<ResourceId>::iterator itr = resources->begin();
660
661 while (itr != resources->end()) {
662 ResourceSource *src = addSource(new IntMapResourceSource("MAP", itr->getNumber()));
663
664 if ((itr->getNumber() == 65535) && Common::File::exists("RESOURCE.SFX"))
665 addSource(new AudioVolumeResourceSource(this, "RESOURCE.SFX", src, 0));
666 else if (Common::File::exists("RESOURCE.AUD"))
667 addSource(new AudioVolumeResourceSource(this, "RESOURCE.AUD", src, 0));
668
669 ++itr;
670 }
671
672 delete resources;
673 return 1;
674}
675
676void ResourceManager::scanNewSources() {
677 for (Common::List<ResourceSource *>::iterator it = _sources.begin(); it != _sources.end(); ++it) {
678 ResourceSource *source = *it;
679
680 if (!source->_scanned) {
681 source->_scanned = true;
682 source->scanSource(this);
683 }
684 }
685}
686
687void DirectoryResourceSource::scanSource(ResourceManager *resMan) {
688 resMan->readResourcePatches();
689
690 // We can't use getSciVersion() at this point, thus using _volVersion
691 if (resMan->_volVersion >= kResVersionSci11) // SCI1.1+
692 resMan->readResourcePatchesBase36();
693
694 resMan->readWaveAudioPatches();
695}
696
697void ExtMapResourceSource::scanSource(ResourceManager *resMan) {
698 if (resMan->_mapVersion < kResVersionSci1Late)
699 resMan->readResourceMapSCI0(this);
700 else
701 resMan->readResourceMapSCI1(this);
702}
703
704void ExtAudioMapResourceSource::scanSource(ResourceManager *resMan) {
705 resMan->readAudioMapSCI1(this);
706}
707
708void IntMapResourceSource::scanSource(ResourceManager *resMan) {
709 resMan->readAudioMapSCI11(this);
710}
711
712#ifdef ENABLE_SCI32
713
714// Chunk resources are resources that hold other resources. They are normally called
715// when using the kLoadChunk SCI2.1 kernel function. However, for example, the Lighthouse
716// SCI2.1 demo has a chunk but no scripts outside of the chunk.
717
718// A chunk resource is pretty straightforward in terms of layout
719// It begins with 11-byte entries in the header:
720// =========
721// b resType
722// w nEntry
723// dw offset
724// dw length
725
726ChunkResourceSource::ChunkResourceSource(const Common::String &name, uint16 number)
727 : ResourceSource(kSourceChunk, name) {
728
729 _number = 0;
730}
731
732void ChunkResourceSource::scanSource(ResourceManager *resMan) {
733 Resource *chunk = resMan->findResource(ResourceId(kResourceTypeChunk, _number), false);
734
735 if (!chunk)
736 error("Trying to load non-existent chunk");
737
738 byte *ptr = chunk->data;
739 uint32 firstOffset = 0;
740
741 for (;;) {
742 ResourceType type = resMan->convertResType(*ptr);
743 uint16 number = READ_LE_UINT16(ptr + 1);
744 ResourceId id(type, number);
745
746 ResourceEntry entry;
747 entry.offset = READ_LE_UINT32(ptr + 3);
748 entry.length = READ_LE_UINT32(ptr + 7);
749
750 _resMap[id] = entry;
751 ptr += 11;
752
753 debugC(kDebugLevelResMan, 2, "Found %s in chunk %d", id.toString().c_str(), _number);
754
755 resMan->updateResource(id, this, entry.length);
756
757 // There's no end marker to the data table, but the first resource
758 // begins directly after the entry table. So, when we hit the first
759 // resource, we're at the end of the entry table.
760
761 if (!firstOffset)
762 firstOffset = entry.offset;
763
764 if ((size_t)(ptr - chunk->data) >= firstOffset)
765 break;
766 }
767}
768
769void ChunkResourceSource::loadResource(ResourceManager *resMan, Resource *res) {
770 Resource *chunk = resMan->findResource(ResourceId(kResourceTypeChunk, _number), false);
771
772 if (!_resMap.contains(res->_id))
773 error("Trying to load non-existent resource from chunk %d: %s %d", _number, getResourceTypeName(res->_id.getType()), res->_id.getNumber());
774
775 ResourceEntry entry = _resMap[res->_id];
776 res->data = new byte[entry.length];
777 res->size = entry.length;
778 res->_header = 0;
779 res->_headerSize = 0;
780 res->_status = kResStatusAllocated;
781
782 // Copy the resource data over
783 memcpy(res->data, chunk->data + entry.offset, entry.length);
784}
785
786void ResourceManager::addResourcesFromChunk(uint16 id) {
787 addSource(new ChunkResourceSource(Common::String::printf("Chunk %d", id), id));
788 scanNewSources();
789}
790
791#endif
792
793void ResourceManager::freeResourceSources() {
794 for (Common::List<ResourceSource *>::iterator it = _sources.begin(); it != _sources.end(); ++it)
795 delete *it;
796
797 _sources.clear();
798}
799
800ResourceManager::ResourceManager() {
801}
802
803void ResourceManager::init() {
804 _memoryLocked = 0;
805 _memoryLRU = 0;
806 _LRU.clear();
807 _resMap.clear();
808 _audioMapSCI1 = NULL;
809
810 // FIXME: put this in an Init() function, so that we can error out if detection fails completely
811
812 _mapVersion = detectMapVersion();
813 _volVersion = detectVolVersion();
814 if ((_volVersion == kResVersionUnknown) && (_mapVersion != kResVersionUnknown)) {
815 warning("Volume version not detected, but map version has been detected. Setting volume version to map version");
816 _volVersion = _mapVersion;
817 }
818
819 if ((_mapVersion == kResVersionUnknown) && (_volVersion != kResVersionUnknown)) {
820 warning("Map version not detected, but volume version has been detected. Setting map version to volume version");
821 _mapVersion = _volVersion;
822 }
823
824 debugC(1, kDebugLevelResMan, "resMan: Detected resource map version %d: %s", _mapVersion, versionDescription(_mapVersion));
825 debugC(1, kDebugLevelResMan, "resMan: Detected volume version %d: %s", _volVersion, versionDescription(_volVersion));
826
827 if ((_mapVersion == kResVersionUnknown) && (_volVersion == kResVersionUnknown)) {
828 warning("Volume and map version not detected, assuming that this is not a sci game");
829 _viewType = kViewUnknown;
830 return;
831 }
832
833 scanNewSources();
834 addInternalSources();
835 scanNewSources();
836
837 detectSciVersion();
838
839 debugC(1, kDebugLevelResMan, "resMan: Detected %s", getSciVersionDesc(getSciVersion()));
840
841 switch (_viewType) {
842 case kViewEga:
843 debugC(1, kDebugLevelResMan, "resMan: Detected EGA graphic resources");
844 break;
845 case kViewVga:
846 debugC(1, kDebugLevelResMan, "resMan: Detected VGA graphic resources");
847 break;
848 case kViewVga11:
849 debugC(1, kDebugLevelResMan, "resMan: Detected SCI1.1 VGA graphic resources");
850 break;
851 case kViewAmiga:
852 debugC(1, kDebugLevelResMan, "resMan: Detected Amiga graphic resources");
853 break;
854 default:
855#ifdef ENABLE_SCI32
856 error("resMan: Couldn't determine view type");
857#else
858 if (getSciVersion() >= SCI_VERSION_2) {
859 // SCI support isn't built in, thus the view type won't be determined for
860 // SCI2+ games. This will be handled further up, so throw no error here
861 } else {
862 error("resMan: Couldn't determine view type");
863 }
864#endif
865 }
866
867#ifdef ENABLE_SCI32
868 if (getSciVersion() >= SCI_VERSION_2_1) {
869 // If we have no scripts, but chunk 0 is present, open up the chunk
870 // to try to get to any scripts in there. The Lighthouse SCI2.1 demo
871 // does exactly this.
872
873 Common::List<ResourceId> *scriptList = listResources(kResourceTypeScript);
874
875 if (scriptList->empty() && testResource(ResourceId(kResourceTypeChunk, 0)))
876 addResourcesFromChunk(0);
877
878 delete scriptList;
879 }
880#endif
881}
882
883ResourceManager::~ResourceManager() {
884 // freeing resources
885 ResourceMap::iterator itr = _resMap.begin();
886 while (itr != _resMap.end()) {
887 delete itr->_value;
888 ++itr;
889 }
890 freeResourceSources();
891
892 Common::List<Common::File *>::iterator it = _volumeFiles.begin();
893 while (it != _volumeFiles.end()) {
894 delete *it;
895 ++it;
896 }
897}
898
899void ResourceManager::removeFromLRU(Resource *res) {
900 if (res->_status != kResStatusEnqueued) {
901 warning("resMan: trying to remove resource that isn't enqueued");
902 return;
903 }
904 _LRU.remove(res);
905 _memoryLRU -= res->size;
906 res->_status = kResStatusAllocated;
907}
908
909void ResourceManager::addToLRU(Resource *res) {
910 if (res->_status != kResStatusAllocated) {
911 warning("resMan: trying to enqueue resource with state %d", res->_status);
912 return;
913 }
914 _LRU.push_front(res);
915 _memoryLRU += res->size;
916#if SCI_VERBOSE_resMan
917 debug("Adding %s.%03d (%d bytes) to lru control: %d bytes total",
918 getResourceTypeName(res->type), res->number, res->size,
919 mgr->_memoryLRU);
920#endif
921 res->_status = kResStatusEnqueued;
922}
923
924void ResourceManager::printLRU() {
925 int mem = 0;
926 int entries = 0;
927 Common::List<Resource *>::iterator it = _LRU.begin();
928 Resource *res;
929
930 while (it != _LRU.end()) {
931 res = *it;
932 debug("\t%s: %d bytes", res->_id.toString().c_str(), res->size);
933 mem += res->size;
934 ++entries;
935 ++it;
936 }
937
938 debug("Total: %d entries, %d bytes (mgr says %d)", entries, mem, _memoryLRU);
939}
940
941void ResourceManager::freeOldResources() {
942 while (MAX_MEMORY < _memoryLRU) {
943 assert(!_LRU.empty());
944 Resource *goner = *_LRU.reverse_begin();
945 removeFromLRU(goner);
946 goner->unalloc();
947#ifdef SCI_VERBOSE_resMan
948 printf("resMan-debug: LRU: Freeing %s.%03d (%d bytes)\n", getResourceTypeName(goner->type), goner->number, goner->size);
949#endif
950 }
951}
952
953Common::List<ResourceId> *ResourceManager::listResources(ResourceType type, int mapNumber) {
954 Common::List<ResourceId> *resources = new Common::List<ResourceId>;
955
956 ResourceMap::iterator itr = _resMap.begin();
957 while (itr != _resMap.end()) {
958 if ((itr->_value->getType() == type) && ((mapNumber == -1) || (itr->_value->getNumber() == mapNumber)))
959 resources->push_back(itr->_value->_id);
960 ++itr;
961 }
962
963 return resources;
964}
965
966Resource *ResourceManager::findResource(ResourceId id, bool lock) {
967 Resource *retval = testResource(id);
968
969 if (!retval)
970 return NULL;
971
972 if (retval->_status == kResStatusNoMalloc)
973 loadResource(retval);
974 else if (retval->_status == kResStatusEnqueued)
975 removeFromLRU(retval);
976 // Unless an error occurred, the resource is now either
977 // locked or allocated, but never queued or freed.
978
979 freeOldResources();
980
981 if (lock) {
982 if (retval->_status == kResStatusAllocated) {
983 retval->_status = kResStatusLocked;
984 retval->_lockers = 0;
985 _memoryLocked += retval->size;
986 }
987 retval->_lockers++;
988 } else if (retval->_status != kResStatusLocked) { // Don't lock it
989 if (retval->_status == kResStatusAllocated)
990 addToLRU(retval);
991 }
992
993 if (retval->data)
994 return retval;
995 else {
996 warning("resMan: Failed to read %s", retval->_id.toString().c_str());
997 return NULL;
998 }
999}
1000
1001void ResourceManager::unlockResource(Resource *res) {
1002 assert(res);
1003
1004 if (res->_status != kResStatusLocked) {
1005 debugC(kDebugLevelResMan, 2, "[resMan] Attempt to unlock unlocked resource %s", res->_id.toString().c_str());
1006 return;
1007 }
1008
1009 if (!--res->_lockers) { // No more lockers?
1010 res->_status = kResStatusAllocated;
1011 _memoryLocked -= res->size;
1012 addToLRU(res);
1013 }
1014
1015 freeOldResources();
1016}
1017
1018const char *ResourceManager::versionDescription(ResVersion version) const {
1019 switch (version) {
1020 case kResVersionUnknown:
1021 return "Unknown";
1022 case kResVersionSci0Sci1Early:
1023 return "SCI0 / Early SCI1";
1024 case kResVersionSci1Middle:
1025 return "Middle SCI1";
1026 case kResVersionSci1Late:
1027 return "Late SCI1";
1028 case kResVersionSci11:
1029 return "SCI1.1";
1030 case kResVersionSci11Mac:
1031 return "Mac SCI1.1+";
1032 case kResVersionSci32:
1033 return "SCI32";
1034 }
1035
1036 return "Version not valid";
1037}
1038
1039ResVersion ResourceManager::detectMapVersion() {
1040 Common::SeekableReadStream *fileStream = 0;
1041 byte buff[6];
1042 ResourceSource *rsrc= 0;
1043
1044 for (Common::List<ResourceSource *>::iterator it = _sources.begin(); it != _sources.end(); ++it) {
1045 rsrc = *it;
1046
1047 if (rsrc->getSourceType() == kSourceExtMap) {
1048 if (rsrc->_resourceFile) {
1049 fileStream = rsrc->_resourceFile->createReadStream();
1050 } else {
1051 Common::File *file = new Common::File();
1052 file->open(rsrc->getLocationName());
1053 if (file->isOpen())
1054 fileStream = file;
1055 }
1056 break;
1057 } else if (rsrc->getSourceType() == kSourceMacResourceFork)
1058 return kResVersionSci11Mac;
1059 }
1060
1061 if (!fileStream)
1062 error("Failed to open resource map file");
1063
1064 // detection
1065 // SCI0 and SCI01 maps have last 6 bytes set to FF
1066 fileStream->seek(-4, SEEK_END);
1067 uint32 uEnd = fileStream->readUint32LE();
1068 if (uEnd == 0xFFFFFFFF) {
1069 // check if 0 or 01 - try to read resources in SCI0 format and see if exists
1070 fileStream->seek(0, SEEK_SET);
1071 while (fileStream->read(buff, 6) == 6 && !(buff[0] == 0xFF && buff[1] == 0xFF && buff[2] == 0xFF)) {
1072 if (findVolume(rsrc, (buff[5] & 0xFC) >> 2) == NULL)
1073 return kResVersionSci1Middle;
1074 }
1075 return kResVersionSci0Sci1Early;
1076 }
1077
1078 // SCI1 and SCI1.1 maps consist of a fixed 3-byte header, a directory list (3-bytes each) that has one entry
1079 // of id FFh and points to EOF. The actual entries have 6-bytes on SCI1 and 5-bytes on SCI1.1
1080 byte directoryType = 0;
1081 uint16 directoryOffset = 0;
1082 uint16 lastDirectoryOffset = 0;
1083 uint16 directorySize = 0;
1084 ResVersion mapDetected = kResVersionUnknown;
1085 fileStream->seek(0, SEEK_SET);
1086
1087 while (!fileStream->eos()) {
1088 directoryType = fileStream->readByte();
1089 directoryOffset = fileStream->readUint16LE();
1090
1091 // Only SCI32 has directory type < 0x80
1092 if (directoryType < 0x80 && (mapDetected == kResVersionUnknown || mapDetected == kResVersionSci32))
1093 mapDetected = kResVersionSci32;
1094 else if (directoryType < 0x80 || ((directoryType & 0x7f) > 0x20 && directoryType != 0xFF))
1095 break;
1096
1097 // Offset is above file size? -> definitely not SCI1/SCI1.1
1098 if (directoryOffset > fileStream->size())
1099 break;
1100
1101 if (lastDirectoryOffset && mapDetected == kResVersionUnknown) {
1102 directorySize = directoryOffset - lastDirectoryOffset;
1103 if ((directorySize % 5) && (directorySize % 6 == 0))
1104 mapDetected = kResVersionSci1Late;
1105 if ((directorySize % 5 == 0) && (directorySize % 6))
1106 mapDetected = kResVersionSci11;
1107 }
1108
1109 if (directoryType == 0xFF) {
1110 // FFh entry needs to point to EOF
1111 if (directoryOffset != fileStream->size())
1112 break;
1113
1114 delete fileStream;
1115
1116 if (mapDetected)
1117 return mapDetected;
1118 return kResVersionSci1Late;
1119 }
1120
1121 lastDirectoryOffset = directoryOffset;
1122 }
1123
1124 delete fileStream;
1125
1126 return kResVersionUnknown;
1127}
1128
1129ResVersion ResourceManager::detectVolVersion() {
1130 Common::SeekableReadStream *fileStream = 0;
1131 ResourceSource *rsrc;
1132
1133 for (Common::List<ResourceSource *>::iterator it = _sources.begin(); it != _sources.end(); ++it) {
1134 rsrc = *it;
1135
1136 if (rsrc->getSourceType() == kSourceVolume) {
1137 if (rsrc->_resourceFile) {
1138 fileStream = rsrc->_resourceFile->createReadStream();
1139 } else {
1140 Common::File *file = new Common::File();
1141 file->open(rsrc->getLocationName());
1142 if (file->isOpen())
1143 fileStream = file;
1144 }
1145 break;
1146 } else if (rsrc->getSourceType() == kSourceMacResourceFork)
1147 return kResVersionSci11Mac;
1148 }
1149
1150 if (!fileStream) {
1151 error("Failed to open volume file - if you got resource.p01/resource.p02/etc. files, merge them together into resource.000");
1152 // resource.p01/resource.p02/etc. may be there when directly copying the files from the original floppies
1153 // the sierra installer would merge those together (perhaps we could do this as well?)
1154 // possible TODO
1155 // example for such game: Laura Bow 2
1156 return kResVersionUnknown;
1157 }
1158
1159 // SCI0 volume format: {wResId wPacked+4 wUnpacked wCompression} = 8 bytes
1160 // SCI1 volume format: {bResType wResNumber wPacked+4 wUnpacked wCompression} = 9 bytes
1161 // SCI1.1 volume format: {bResType wResNumber wPacked wUnpacked wCompression} = 9 bytes
1162 // SCI32 volume format: {bResType wResNumber dwPacked dwUnpacked wCompression} = 13 bytes
1163 // Try to parse volume with SCI0 scheme to see if it make sense
1164 // Checking 1MB of data should be enough to determine the version
1165 uint16 resId, wCompression;
1166 uint32 dwPacked, dwUnpacked;
1167 ResVersion curVersion = kResVersionSci0Sci1Early;
1168 bool failed = false;
1169 bool sci11Align = false;
1170
1171 // Check for SCI0, SCI1, SCI1.1 and SCI32 v2 (Gabriel Knight 1 CD) formats
1172 while (!fileStream->eos() && fileStream->pos() < 0x100000) {
1173 if (curVersion > kResVersionSci0Sci1Early)
1174 fileStream->readByte();
1175 resId = fileStream->readUint16LE();
1176 dwPacked = (curVersion < kResVersionSci32) ? fileStream->readUint16LE() : fileStream->readUint32LE();
1177 dwUnpacked = (curVersion < kResVersionSci32) ? fileStream->readUint16LE() : fileStream->readUint32LE();
1178 wCompression = fileStream->readUint16LE();
1179 if (fileStream->eos()) {
1180 delete fileStream;
1181 return curVersion;
1182 }
1183
1184 int chk = (curVersion == kResVersionSci0Sci1Early) ? 4 : 20;
1185 int offs = curVersion < kResVersionSci11 ? 4 : 0;
1186 if ((curVersion < kResVersionSci32 && wCompression > chk)
1187 || (curVersion == kResVersionSci32 && wCompression != 0 && wCompression != 32)
1188 || (wCompression == 0 && dwPacked != dwUnpacked + offs)
1189 || (dwUnpacked < dwPacked - offs)) {
1190
1191 // Retry with a newer SCI version
1192 if (curVersion == kResVersionSci0Sci1Early) {
1193 curVersion = kResVersionSci1Late;
1194 } else if (curVersion == kResVersionSci1Late) {
1195 curVersion = kResVersionSci11;
1196 } else if (curVersion == kResVersionSci11 && !sci11Align) {
1197 // Later versions (e.g. QFG1VGA) have resources word-aligned
1198 sci11Align = true;
1199 } else if (curVersion == kResVersionSci11) {
1200 curVersion = kResVersionSci32;
1201 } else {
1202 // All version checks failed, exit loop
1203 failed = true;
1204 break;
1205 }
1206
1207 fileStream->seek(0);
1208 continue;
1209 }
1210
1211 if (curVersion < kResVersionSci11)
1212 fileStream->seek(dwPacked - 4, SEEK_CUR);
1213 else if (curVersion == kResVersionSci11)
1214 fileStream->seek(sci11Align && ((9 + dwPacked) % 2) ? dwPacked + 1 : dwPacked, SEEK_CUR);
1215 else if (curVersion == kResVersionSci32)
1216 fileStream->seek(dwPacked, SEEK_CUR);
1217 }
1218
1219 delete fileStream;
1220
1221 if (!failed)
1222 return curVersion;
1223
1224 // Failed to detect volume version
1225 return kResVersionUnknown;
1226}
1227
1228// version-agnostic patch application
1229void ResourceManager::processPatch(ResourceSource *source, ResourceType resourceType, uint16 resourceNr, uint32 tuple) {
1230 Common::SeekableReadStream *fileStream = 0;
1231 Resource *newrsc = 0;
1232 ResourceId resId = ResourceId(resourceType, resourceNr, tuple);
1233 ResourceType checkForType = resourceType;
1234
1235 // base36 encoded patches (i.e. audio36 and sync36) have the same type as their non-base36 encoded counterparts
1236 if (checkForType == kResourceTypeAudio36)
1237 checkForType = kResourceTypeAudio;
1238 else if (checkForType == kResourceTypeSync36)
1239 checkForType = kResourceTypeSync;
1240
1241 if (source->_resourceFile) {
1242 fileStream = source->_resourceFile->createReadStream();
1243 } else {
1244 Common::File *file = new Common::File();
1245 if (!file->open(source->getLocationName())) {
1246 warning("ResourceManager::processPatch(): failed to open %s", source->getLocationName().c_str());
1247 return;
1248 }
1249 fileStream = file;
1250 }
1251
1252 int fsize = fileStream->size();
1253 if (fsize < 3) {
1254 debug("Patching %s failed - file too small", source->getLocationName().c_str());
1255 return;
1256 }
1257
1258 byte patchType = convertResType(fileStream->readByte());
1259 byte patchDataOffset = fileStream->readByte();
1260
1261 delete fileStream;
1262
1263 if (patchType != checkForType) {
1264 debug("Patching %s failed - resource type mismatch", source->getLocationName().c_str());
1265 return;
1266 }
1267
1268 // Fixes SQ5/German, patch file special case logic taken from SCI View disassembly
1269 if (patchDataOffset & 0x80) {
1270 switch (patchDataOffset & 0x7F) {
1271 case 0:
1272 patchDataOffset = 24;
1273 break;
1274 case 1:
1275 patchDataOffset = 2;
1276 break;
1277 case 4:
1278 patchDataOffset = 8;
1279 break;
1280 default:
1281 error("Resource patch unsupported special case %X", patchDataOffset & 0x7F);
1282 return;
1283 }
1284 }
1285
1286 if (patchDataOffset + 2 >= fsize) {
1287 debug("Patching %s failed - patch starting at offset %d can't be in file of size %d",
1288 source->getLocationName().c_str(), patchDataOffset + 2, fsize);
1289 return;
1290 }
1291
1292 // Overwrite everything, because we're patching
1293 newrsc = updateResource(resId, source, fsize - patchDataOffset - 2);
1294 newrsc->_headerSize = patchDataOffset;
1295 newrsc->_fileOffset = 0;
1296
1297
1298 debugC(1, kDebugLevelResMan, "Patching %s - OK", source->getLocationName().c_str());
1299}
1300
1301void ResourceManager::readResourcePatchesBase36() {
1302 // The base36 encoded audio36 and sync36 resources use a different naming scheme, because they
1303 // cannot be described with a single resource number, but are a result of a
1304 // <number, noun, verb, cond, seq> tuple. Please don't be confused with the normal audio patches
1305 // (*.aud) and normal sync patches (*.syn). audio36 patches can be seen for example in the AUD
1306 // folder of GK1CD, and are like this file: @0CS0M00.0X1. GK1CD is the first game where these
1307 // have been observed. The actual audio36 and sync36 resources exist in SCI1.1 as well, but the
1308 // first game where external patch files for them have been found is GK1CD. The names of these
1309 // files are base36 encoded, and we handle their decoding here. audio36 files start with a '@',
1310 // whereas sync36 start with a '#'. Mac versions begin with 'A' (probably meaning AIFF). Torin
1311 // has several that begin with 'B'.
1312
1313 Common::String name, inputName;
1314 Common::ArchiveMemberList files;
1315 ResourceSource *psrcPatch;
1316
1317 for (int i = kResourceTypeAudio36; i <= kResourceTypeSync36; ++i) {
1318 files.clear();
1319
1320 // audio36 resources start with a @, A, or B
1321 // sync36 resources start with a #
1322 if (i == kResourceTypeAudio36) {
1323 SearchMan.listMatchingMembers(files, "@???????.???");
1324 SearchMan.listMatchingMembers(files, "A???????.???");
1325 SearchMan.listMatchingMembers(files, "B???????.???");
1326 } else
1327 SearchMan.listMatchingMembers(files, "#???????.???");
1328
1329 for (Common::ArchiveMemberList::const_iterator x = files.begin(); x != files.end(); ++x) {
1330 name = (*x)->getName();
1331
1332 inputName = (*x)->getName();
1333 inputName.toUppercase();
1334 inputName.deleteChar(0); // delete the first character (type)
1335 inputName.deleteChar(7); // delete the dot
1336
1337 // The base36 encoded resource contains the following:
1338 // uint16 resourceId, byte noun, byte verb, byte cond, byte seq
1339 uint16 resourceNr = strtol(Common::String(inputName.c_str(), 3).c_str(), 0, 36); // 3 characters
1340 uint16 noun = strtol(Common::String(inputName.c_str() + 3, 2).c_str(), 0, 36); // 2 characters
1341 uint16 verb = strtol(Common::String(inputName.c_str() + 5, 2).c_str(), 0, 36); // 2 characters
1342 uint16 cond = strtol(Common::String(inputName.c_str() + 7, 2).c_str(), 0, 36); // 2 characters
1343 uint16 seq = strtol(Common::String(inputName.c_str() + 9, 1).c_str(), 0, 36); // 1 character
1344
1345 // Check, if we got valid results
1346 if ((noun <= 255) && (verb <= 255) && (cond <= 255) && (seq <= 255)) {
1347 ResourceId resource36((ResourceType)i, resourceNr, noun, verb, cond, seq);
1348
1349 /*
1350 if (i == kResourceTypeAudio36)
1351 debug("audio36 patch: %s => %s. tuple:%d, %s\n", name.c_str(), inputName.c_str(), resource36.tuple, resource36.toString().c_str());
1352 else
1353 debug("sync36 patch: %s => %s. tuple:%d, %s\n", name.c_str(), inputName.c_str(), resource36.tuple, resource36.toString().c_str());
1354 */
1355
1356 // Make sure that the audio patch is a valid resource
1357 if (i == kResourceTypeAudio36) {
1358 Common::SeekableReadStream *stream = SearchMan.createReadStreamForMember(name);
1359 uint32 tag = stream->readUint32BE();
1360
1361 if (tag == MKID_BE('RIFF') || tag == MKID_BE('FORM')) {
1362 delete stream;
1363 processWavePatch(resource36, name);
1364 continue;
1365 }
1366
1367 // Check for SOL as well
1368 tag = (tag << 16) | stream->readUint16BE();
1369
1370 if (tag != MKID_BE('SOL\0')) {
1371 delete stream;
1372 continue;
1373 }
1374
1375 delete stream;
1376 }
1377
1378 psrcPatch = new PatchResourceSource(name);
1379 processPatch(psrcPatch, (ResourceType)i, resourceNr, resource36.getTuple());
1380 }
1381 }
1382 }
1383}
1384
1385void ResourceManager::readResourcePatches() {
1386 // Note: since some SCI1 games(KQ5 floppy, SQ4) might use SCI0 naming scheme for patch files
1387 // this function tries to read patch file with any supported naming scheme,
1388 // regardless of s_sciVersion value
1389
1390 Common::String mask, name;
1391 Common::ArchiveMemberList files;
1392 uint16 resourceNr = 0;
1393 const char *szResType;
1394 ResourceSource *psrcPatch;
1395
1396 for (int i = kResourceTypeView; i < kResourceTypeInvalid; ++i) {
1397 // Ignore the types that can't be patched (and Robot/VMD is handled externally for now)
1398 if (!s_resourceTypeSuffixes[i] || i == kResourceTypeRobot || i == kResourceTypeVMD)
1399 continue;
1400
1401 files.clear();
1402 szResType = getResourceTypeName((ResourceType)i);
1403 // SCI0 naming - type.nnn
1404 mask = szResType;
1405 mask += ".???";
1406 SearchMan.listMatchingMembers(files, mask);
1407 // SCI1 and later naming - nnn.typ
1408 mask = "*.";
1409 mask += s_resourceTypeSuffixes[i];
1410 SearchMan.listMatchingMembers(files, mask);
1411
1412 for (Common::ArchiveMemberList::const_iterator x = files.begin(); x != files.end(); ++x) {
1413 bool bAdd = false;
1414 name = (*x)->getName();
1415
1416 // SCI1 scheme
1417 if (isdigit(name[0])) {
1418 char *end = 0;
1419 resourceNr = strtol(name.c_str(), &end, 10);
1420 bAdd = (*end == '.'); // Ensure the next character is the period
1421 } else {
1422 // SCI0 scheme
1423 int resname_len = strlen(szResType);
1424 if (scumm_strnicmp(name.c_str(), szResType, resname_len) == 0
1425 && !isalpha(name[resname_len + 1])) {
1426 resourceNr = atoi(name.c_str() + resname_len + 1);
1427 bAdd = true;
1428 }
1429 }
1430
1431 if (bAdd) {
1432 psrcPatch = new PatchResourceSource(name);
1433 processPatch(psrcPatch, (ResourceType)i, resourceNr);
1434 }
1435 }
1436 }
1437}
1438
1439int ResourceManager::readResourceMapSCI0(ResourceSource *map) {
1440 Common::SeekableReadStream *fileStream = 0;
1441 ResourceType type;
1442 uint16 number, id;
1443 uint32 offset;
1444
1445 if (map->_resourceFile) {
1446 fileStream = map->_resourceFile->createReadStream();
1447 if (!fileStream)
1448 return SCI_ERROR_RESMAP_NOT_FOUND;
1449 } else {
1450 Common::File *file = new Common::File();
1451 if (!file->open(map->getLocationName()))
1452 return SCI_ERROR_RESMAP_NOT_FOUND;
1453 fileStream = file;
1454 }
1455
1456 fileStream->seek(0, SEEK_SET);
1457
1458 byte bMask = (_mapVersion == kResVersionSci1Middle) ? 0xF0 : 0xFC;
1459 byte bShift = (_mapVersion == kResVersionSci1Middle) ? 28 : 26;
1460
1461 do {
1462 id = fileStream->readUint16LE();
1463 offset = fileStream->readUint32LE();
1464
1465 if (fileStream->eos() || fileStream->err()) {
1466 delete fileStream;
1467 warning("Error while reading %s", map->getLocationName().c_str());
1468 return SCI_ERROR_RESMAP_NOT_FOUND;
1469 }
1470 if (offset == 0xFFFFFFFF)
1471 break;
1472
1473 type = convertResType(id >> 11);
1474 number = id & 0x7FF;
1475 ResourceId resId = ResourceId(type, number);
1476 // adding a new resource
1477 if (_resMap.contains(resId) == false) {
1478 ResourceSource *source = findVolume(map, offset >> bShift);
1479 if (!source) {
1480 warning("Could not get volume for resource %d, VolumeID %d", id, offset >> bShift);
1481 if (_mapVersion != _volVersion) {
1482 warning("Retrying with the detected volume version instead");
1483 warning("Map version was: %d, retrying with: %d", _mapVersion, _volVersion);
1484 _mapVersion = _volVersion;
1485 bMask = (_mapVersion == kResVersionSci1Middle) ? 0xF0 : 0xFC;
1486 bShift = (_mapVersion == kResVersionSci1Middle) ? 28 : 26;
1487 source = findVolume(map, offset >> bShift);
1488 }
1489 }
1490
1491 addResource(resId, source, offset & (((~bMask) << 24) | 0xFFFFFF));
1492
1493 }
1494 } while (!fileStream->eos());
1495
1496 delete fileStream;
1497 return 0;
1498}
1499
1500int ResourceManager::readResourceMapSCI1(ResourceSource *map) {
1501 Common::SeekableReadStream *fileStream = 0;
1502
1503 if (map->_resourceFile) {
1504 fileStream = map->_resourceFile->createReadStream();
1505 if (!fileStream)
1506 return SCI_ERROR_RESMAP_NOT_FOUND;
1507 } else {
1508 Common::File *file = new Common::File();
1509 if (!file->open(map->getLocationName()))
1510 return SCI_ERROR_RESMAP_NOT_FOUND;
1511 fileStream = file;
1512 }
1513
1514 resource_index_t resMap[32];
1515 memset(resMap, 0, sizeof(resource_index_t) * 32);
1516 byte type = 0, prevtype = 0;
1517 byte nEntrySize = _mapVersion == kResVersionSci11 ? SCI11_RESMAP_ENTRIES_SIZE : SCI1_RESMAP_ENTRIES_SIZE;
1518 ResourceId resId;
1519
1520 // Read resource type and offsets to resource offsets block from .MAP file
1521 // The last entry has type=0xFF (0x1F) and offset equals to map file length
1522 do {
1523 type = fileStream->readByte() & 0x1F;
1524 resMap[type].wOffset = fileStream->readUint16LE();
1525 resMap[prevtype].wSize = (resMap[type].wOffset
1526 - resMap[prevtype].wOffset) / nEntrySize;
1527 prevtype = type;
1528 } while (type != 0x1F); // the last entry is FF
1529
1530 // reading each type's offsets
1531 uint32 fileOffset = 0;
1532 for (type = 0; type < 32; type++) {
1533 if (resMap[type].wOffset == 0) // this resource does not exist in map
1534 continue;
1535 fileStream->seek(resMap[type].wOffset);
1536 for (int i = 0; i < resMap[type].wSize; i++) {
1537 uint16 number = fileStream->readUint16LE();
1538 int volume_nr = 0;
1539 if (_mapVersion == kResVersionSci11) {
1540 // offset stored in 3 bytes
1541 fileOffset = fileStream->readUint16LE();
1542 fileOffset |= fileStream->readByte() << 16;
1543 fileOffset <<= 1;
1544 } else {
1545 // offset/volume stored in 4 bytes
1546 fileOffset = fileStream->readUint32LE();
1547 if (_mapVersion < kResVersionSci11) {
1548 volume_nr = fileOffset >> 28; // most significant 4 bits
1549 fileOffset &= 0x0FFFFFFF; // least significant 28 bits
1550 } else {
1551 // in SCI32 it's a plain offset
1552 }
1553 }
1554 if (fileStream->eos() || fileStream->err()) {
1555 delete fileStream;
1556 warning("Error while reading %s", map->getLocationName().c_str());
1557 return SCI_ERROR_RESMAP_NOT_FOUND;
1558 }
1559 resId = ResourceId(convertResType(type), number);
1560
1561 // NOTE: We add the map's volume number here to the specified volume number
1562 // for SCI2.1 and SCI3 maps that are not resmap.000. The resmap.* files' numbers
1563 // need to be used in concurrence with the volume specified in the map to get
1564 // the actual resource file.
1565 int mapVolumeNr = volume_nr + map->_volumeNumber;
1566 ResourceSource *source = findVolume(map, mapVolumeNr);
1567 // FIXME: this code has serious issues with multiple RESMAP.* files (like in unmodified gk2)
1568 // adding a resource with source == NULL would crash later on
1569 if (!source)
1570 error("Unable to find volume for map %s volumeNr %d", map->getLocationName().c_str(), mapVolumeNr);
1571
1572 Resource *resource = _resMap.getVal(resId, NULL);
1573 if (!resource) {
1574 addResource(resId, source, fileOffset);
1575 } else {
1576 //// if resource is already present, change it to new content
1577 //// this is needed at least for pharkas/german. This version
1578 //// contains several duplicate resources INSIDE the resource
1579 //// data files like fonts, views, scripts, etc. And if we use
1580 //// the first entries, half of the game will be english and
1581 //// umlauts will also be missing :P
1582 //resource->_source = source;
1583 //resource->_fileOffset = fileOffset;
1584 //resource->size = 0;
1585 }
1586 }
1587 }
1588
1589 delete fileStream;
1590 return 0;
1591}
1592
1593struct {
1594 uint32 tag;
1595 ResourceType type;
1596} static const macResTagMap[] = {
1597 { MKID_BE('V56 '), kResourceTypeView },
1598 { MKID_BE('P56 '), kResourceTypePic },
1599 { MKID_BE('SCR '), kResourceTypeScript },
1600 { MKID_BE('TEX '), kResourceTypeText },
1601 { MKID_BE('SND '), kResourceTypeSound },
1602 { MKID_BE('VOC '), kResourceTypeVocab },
1603 { MKID_BE('FON '), kResourceTypeFont },
1604 { MKID_BE('CURS'), kResourceTypeCursor },
1605 { MKID_BE('crsr'), kResourceTypeCursor },
1606 { MKID_BE('Pat '), kResourceTypePatch },
1607 { MKID_BE('PAL '), kResourceTypePalette },
1608 { MKID_BE('snd '), kResourceTypeAudio },
1609 { MKID_BE('MSG '), kResourceTypeMessage },
1610 { MKID_BE('HEP '), kResourceTypeHeap },
1611 { MKID_BE('IBIN'), kResourceTypeMacIconBarPictN },
1612 { MKID_BE('IBIS'), kResourceTypeMacIconBarPictS },
1613 { MKID_BE('PICT'), kResourceTypeMacPict },
1614 { MKID_BE('SYN '), kResourceTypeSync }
1615};
1616
1617static Common::Array<uint32> resTypeToMacTags(ResourceType type) {
1618 Common::Array<uint32> tags;
1619
1620 for (uint32 i = 0; i < ARRAYSIZE(macResTagMap); i++)
1621 if (macResTagMap[i].type == type)
1622 tags.push_back(macResTagMap[i].tag);
1623
1624 return tags;
1625}
1626
1627void MacResourceForkResourceSource::scanSource(ResourceManager *resMan) {
1628 if (!_macResMan->open(getLocationName().c_str()))
1629 error("%s is not a valid Mac resource fork", getLocationName().c_str());
1630
1631 Common::MacResTagArray tagArray = _macResMan->getResTagArray();
1632
1633 for (uint32 i = 0; i < tagArray.size(); i++) {
1634 ResourceType type = kResourceTypeInvalid;
1635
1636 // Map the Mac tags to our ResourceType
1637 for (uint32 j = 0; j < ARRAYSIZE(macResTagMap); j++)
1638 if (tagArray[i] == macResTagMap[j].tag) {
1639 type = macResTagMap[j].type;
1640 break;
1641 }
1642
1643 if (type == kResourceTypeInvalid)
1644 continue;
1645
1646 Common::MacResIDArray idArray = _macResMan->getResIDArray(tagArray[i]);
1647
1648 for (uint32 j = 0; j < idArray.size(); j++) {
1649 // Get the size of the file
1650 Common::SeekableReadStream *stream = _macResMan->getResource(tagArray[i], idArray[j]);
1651
1652 // Some IBIS resources have a size of 0, so we skip them
1653 if (!stream)
1654 continue;
1655
1656 uint32 fileSize = stream->size();
1657 delete stream;
1658
1659 ResourceId resId = ResourceId(type, idArray[j]);
1660
1661 // Overwrite Resource instance. Resource forks may contain patches.
1662 resMan->updateResource(resId, this, fileSize);
1663 }
1664 }
1665}
1666
1667void ResourceManager::addResource(ResourceId resId, ResourceSource *src, uint32 offset, uint32 size) {
1668 // Adding new resource only if it does not exist
1669 if (src->getLocationName() == "73.msg")
1670 src = src;
1671
1672 if (!_resMap.contains(resId)) {
1673 Resource *res = new Resource(this, resId);
1674 _resMap.setVal(resId, res);
1675 res->_source = src;
1676 res->_fileOffset = offset;
1677 res->size = size;
1678 }
1679}
1680
1681Resource *ResourceManager::updateResource(ResourceId resId, ResourceSource *src, uint32 size) {
1682 // Update a patched resource, whether it exists or not
1683 Resource *res = 0;
1684
1685 if (_resMap.contains(resId)) {
1686 res = _resMap.getVal(resId);
1687 } else {
1688 res = new Resource(this, resId);
1689 _resMap.setVal(resId, res);
1690 }
1691
1692 res->_status = kResStatusNoMalloc;
1693 res->_source = src;
1694 res->_headerSize = 0;
1695 res->size = size;
1696
1697 return res;
1698}
1699
1700int Resource::readResourceInfo(ResVersion volVersion, Common::SeekableReadStream *file,
1701 uint32 &szPacked, ResourceCompression &compression) {
1702 // SCI0 volume format: {wResId wPacked+4 wUnpacked wCompression} = 8 bytes
1703 // SCI1 volume format: {bResType wResNumber wPacked+4 wUnpacked wCompression} = 9 bytes
1704 // SCI1.1 volume format: {bResType wResNumber wPacked wUnpacked wCompression} = 9 bytes
1705 // SCI32 volume format : {bResType wResNumber dwPacked dwUnpacked wCompression} = 13 bytes
1706 uint16 w, number;
1707 uint32 wCompression, szUnpacked;
1708 ResourceType type;
1709
1710 switch (volVersion) {
1711 case kResVersionSci0Sci1Early:
1712 case kResVersionSci1Middle:
1713 w = file->readUint16LE();
1714 type = _resMan->convertResType(w >> 11);
1715 number = w & 0x7FF;
1716 szPacked = file->readUint16LE() - 4;
1717 szUnpacked = file->readUint16LE();
1718 wCompression = file->readUint16LE();
1719 break;
1720 case kResVersionSci1Late:
1721 type = _resMan->convertResType(file->readByte());
1722 number = file->readUint16LE();
1723 szPacked = file->readUint16LE() - 4;
1724 szUnpacked = file->readUint16LE();
1725 wCompression = file->readUint16LE();
1726 break;
1727 case kResVersionSci11:
1728 type = _resMan->convertResType(file->readByte());
1729 number = file->readUint16LE();
1730 szPacked = file->readUint16LE();
1731 szUnpacked = file->readUint16LE();
1732 wCompression = file->readUint16LE();
1733 break;
1734 case kResVersionSci11Mac:
1735 // Doesn't store this data in the resource. Fortunately,
1736 // we already have this data.
1737 type = getType();
1738 number = getNumber();
1739 szPacked = file->size();
1740 szUnpacked = file->size();
1741 wCompression = 0;
1742 break;
1743#ifdef ENABLE_SCI32
1744 case kResVersionSci32:
1745 type = _resMan->convertResType(file->readByte());
1746 number = file->readUint16LE();
1747 szPacked = file->readUint32LE();
1748 szUnpacked = file->readUint32LE();
1749 wCompression = file->readUint16LE();
1750 break;
1751#endif
1752 default:
1753 return SCI_ERROR_INVALID_RESMAP_ENTRY;
1754 }
1755
1756 // check if there were errors while reading
1757 if ((file->eos() || file->err()))
1758 return SCI_ERROR_IO_ERROR;
1759
1760 _id = ResourceId(type, number);
1761 size = szUnpacked;
1762
1763 // checking compression method
1764 switch (wCompression) {
1765 case 0:
1766 compression = kCompNone;
1767 break;
1768 case 1:
1769 compression = (getSciVersion() <= SCI_VERSION_01) ? kCompLZW : kCompHuffman;
1770 break;
1771 case 2:
1772 compression = (getSciVersion() <= SCI_VERSION_01) ? kCompHuffman : kCompLZW1;
1773 break;
1774 case 3:
1775 compression = kCompLZW1View;
1776 break;
1777 case 4:
1778 compression = kCompLZW1Pic;
1779 break;
1780 case 18:
1781 case 19:
1782 case 20:
1783 compression = kCompDCL;
1784 break;
1785#ifdef ENABLE_SCI32
1786 case 32:
1787 compression = kCompSTACpack;
1788 break;
1789#endif
1790 default:
1791 compression = kCompUnknown;
1792 }
1793
1794 return compression == kCompUnknown ? SCI_ERROR_UNKNOWN_COMPRESSION : 0;
1795}
1796
1797int Resource::decompress(ResVersion volVersion, Common::SeekableReadStream *file) {
1798 int errorNum;
1799 uint32 szPacked = 0;
1800 ResourceCompression compression = kCompUnknown;
1801
1802 // fill resource info
1803 errorNum = readResourceInfo(volVersion, file, szPacked, compression);
1804 if (errorNum)
1805 return errorNum;
1806
1807 // getting a decompressor
1808 Decompressor *dec = NULL;
1809 switch (compression) {
1810 case kCompNone:
1811 dec = new Decompressor;
1812 break;
1813 case kCompHuffman:
1814 dec = new DecompressorHuffman;
1815 break;
1816 case kCompLZW:
1817 case kCompLZW1:
1818 case kCompLZW1View:
1819 case kCompLZW1Pic:
1820 dec = new DecompressorLZW(compression);
1821 break;
1822 case kCompDCL:
1823 dec = new DecompressorDCL;
1824 break;
1825#ifdef ENABLE_SCI32
1826 case kCompSTACpack:
1827 dec = new DecompressorLZS;
1828 break;
1829#endif
1830 default:
1831 error("Resource %s: Compression method %d not supported", _id.toString().c_str(), compression);
1832 return SCI_ERROR_UNKNOWN_COMPRESSION;
1833 }
1834
1835 data = new byte[size];
1836 _status = kResStatusAllocated;
1837 errorNum = data ? dec->unpack(file, data, szPacked, size) : SCI_ERROR_RESOURCE_TOO_BIG;
1838 if (errorNum)
1839 unalloc();
1840
1841 delete dec;
1842 return errorNum;
1843}
1844
1845ResourceCompression ResourceManager::getViewCompression() {
1846 int viewsTested = 0;
1847
1848 // Test 10 views to see if any are compressed
1849 for (int i = 0; i < 1000; i++) {
1850 Common::SeekableReadStream *fileStream = 0;
1851 Resource *res = testResource(ResourceId(kResourceTypeView, i));
1852
1853 if (!res)
1854 continue;
1855
1856 if (res->_source->getSourceType() != kSourceVolume)
1857 continue;
1858
1859 fileStream = getVolumeFile(res->_source);
1860
1861 if (!fileStream)
1862 continue;
1863 fileStream->seek(res->_fileOffset, SEEK_SET);
1864
1865 uint32 szPacked;
1866 ResourceCompression compression;
1867
1868 if (res->readResourceInfo(_volVersion, fileStream, szPacked, compression)) {
1869 if (res->_source->_resourceFile)
1870 delete fileStream;
1871 continue;
1872 }
1873
1874 if (res->_source->_resourceFile)
1875 delete fileStream;
1876
1877 if (compression != kCompNone)
1878 return compression;
1879
1880 if (++viewsTested == 10)
1881 break;
1882 }
1883
1884 return kCompNone;
1885}
1886
1887ViewType ResourceManager::detectViewType() {
1888 for (int i = 0; i < 1000; i++) {
1889 Resource *res = findResource(ResourceId(kResourceTypeView, i), 0);
1890
1891 if (res) {
1892 // Skip views coming from patch files
1893 if (res->_source->getSourceType() == kSourcePatch)
1894 continue;
1895
1896 switch (res->data[1]) {
1897 case 128:
1898 // If the 2nd byte is 128, it's a VGA game
1899 return kViewVga;
1900 case 0:
1901 // EGA or Amiga, try to read as Amiga view
1902
1903 if (res->size < 10)
1904 return kViewUnknown;
1905
1906 // Read offset of first loop
1907 uint16 offset = READ_LE_UINT16(res->data + 8);
1908
1909 if (offset + 6U >= res->size)
1910 return kViewUnknown;
1911
1912 // Read offset of first cel
1913 offset = READ_LE_UINT16(res->data + offset + 4);
1914
1915 if (offset + 4U >= res->size)
1916 return kViewUnknown;
1917
1918 // Check palette offset, amiga views have no palette
1919 if (READ_LE_UINT16(res->data + 6) != 0)
1920 return kViewEga;
1921
1922 uint16 width = READ_LE_UINT16(res->data + offset);
1923 offset += 2;
1924 uint16 height = READ_LE_UINT16(res->data + offset);
1925 offset += 6;
1926
1927 // To improve the heuristic, we skip very small views
1928 if (height < 10)
1929 continue;
1930
1931 // Check that the RLE data stays within bounds
1932 int y;
1933 for (y = 0; y < height; y++) {
1934 int x = 0;
1935
1936 while ((x < width) && (offset < res->size)) {
1937 byte op = res->data[offset++];
1938 x += (op & 0x07) ? op & 0x07 : op >> 3;
1939 }
1940
1941 // Make sure we got exactly the right number of pixels for this row
1942 if (x != width)
1943 return kViewEga;
1944 }
1945
1946 return kViewAmiga;
1947 }
1948 }
1949 }
1950
1951 // this may happen if there are serious system issues (or trying to add a broken game)
1952 warning("resMan: Couldn't find any views");
1953 return kViewUnknown;
1954}
1955
1956void ResourceManager::detectSciVersion() {
1957 // We use the view compression to set a preliminary s_sciVersion for the sake of getResourceInfo
1958 // Pretend we have a SCI0 game
1959 s_sciVersion = SCI_VERSION_0_EARLY;
1960 bool oldDecompressors = true;
1961
1962 ResourceCompression viewCompression;
1963#ifdef ENABLE_SCI32
1964 viewCompression = getViewCompression();
1965#else
1966 if (_volVersion == kResVersionSci32) {
1967 // SCI32 support isn't built in, thus view detection will fail
1968 viewCompression = kCompUnknown;
1969 } else {
1970 viewCompression = getViewCompression();
1971 }
1972#endif
1973
1974 if (viewCompression != kCompLZW) {
1975 // If it's a different compression type from kCompLZW, the game is probably
1976 // SCI_VERSION_1_EGA or later. If the views are uncompressed, it is
1977 // likely not an early disk game.
1978 s_sciVersion = SCI_VERSION_1_EGA;
1979 oldDecompressors = false;
1980 }
1981
1982 // Set view type
1983 if (viewCompression == kCompDCL
1984 || _volVersion == kResVersionSci11 // pq4demo
1985 || _volVersion == kResVersionSci11Mac
1986#ifdef ENABLE_SCI32
1987 || viewCompression == kCompSTACpack
1988 || _volVersion == kResVersionSci32 // kq7
1989#endif
1990 ) {
1991 // SCI1.1 VGA views
1992 _viewType = kViewVga11;
1993 } else {
1994#ifdef ENABLE_SCI32
1995 // Otherwise we detect it from a view
1996 _viewType = detectViewType();
1997#else
1998 if (_volVersion == kResVersionSci32 && viewCompression == kCompUnknown) {
1999 // A SCI32 game, but SCI32 support is disabled. Force the view type
2000 // to kViewVga11, as we can't read from the game's resource files
2001 _viewType = kViewVga11;
2002 } else {
2003 _viewType = detectViewType();
2004 }
2005#endif
2006 }
2007
2008 if (_volVersion == kResVersionSci11Mac) {
2009 // SCI32 doesn't have the resource.cfg file, so we can figure out
2010 // which of the games are SCI1.1.
2011 // TODO: Decide between SCI2 and SCI2.1
2012 if (Common::File::exists("resource.cfg"))
2013 s_sciVersion = SCI_VERSION_1_1;
2014 else if (Common::File::exists("Patches"))
2015 s_sciVersion = SCI_VERSION_2_1;
2016 else
2017 s_sciVersion = SCI_VERSION_2;
2018 return;
2019 }
2020
2021 // Handle SCI32 versions here
2022 if (_volVersion == kResVersionSci32) {
2023 // SCI2.1/3 and SCI1 Late resource maps are the same, except that
2024 // SCI1 Late resource maps have the resource types or'd with
2025 // 0x80. We differentiate between SCI2 and SCI2.1/3 based on that.
2026 // TODO: Differentiate between SCI2.1 and SCI3
2027 if (_mapVersion == kResVersionSci1Late) {
2028 s_sciVersion = SCI_VERSION_2;
2029 return;
2030 } else {
2031 s_sciVersion = SCI_VERSION_2_1;
2032 return;
2033 }
2034 }
2035
2036 // Check for transitive SCI1/SCI1.1 games, like PQ1 here
2037 // If the game has any heap file (here we check for heap file 0), then
2038 // it definitely uses a SCI1.1 kernel
2039 if (testResource(ResourceId(kResourceTypeHeap, 0))) {
2040 s_sciVersion = SCI_VERSION_1_1;
2041 return;
2042 }
2043
2044 switch (_mapVersion) {
2045 case kResVersionSci0Sci1Early:
2046 if (_viewType == kViewVga) {
2047 // VGA
2048 s_sciVersion = SCI_VERSION_1_EARLY;
2049 return;
2050 }
2051
2052 // EGA
2053 if (hasOldScriptHeader()) {
2054 s_sciVersion = SCI_VERSION_0_EARLY;
2055 return;
2056 }
2057
2058 if (hasSci0Voc999()) {
2059 s_sciVersion = SCI_VERSION_0_LATE;
2060 return;
2061 }
2062
2063 if (oldDecompressors) {
2064 // It's either SCI_VERSION_0_LATE or SCI_VERSION_01
2065
2066 // We first check for SCI1 vocab.999
2067 if (testResource(ResourceId(kResourceTypeVocab, 999))) {
2068 s_sciVersion = SCI_VERSION_01;
2069 return;
2070 }
2071
2072 // If vocab.999 is missing, we try vocab.900
2073 if (testResource(ResourceId(kResourceTypeVocab, 900))) {
2074 if (hasSci1Voc900()) {
2075 s_sciVersion = SCI_VERSION_01;
2076 return;
2077 } else {
2078 s_sciVersion = SCI_VERSION_0_LATE;
2079 return;
2080 }
2081 }
2082
2083 error("Failed to accurately determine SCI version");
2084 // No parser, we assume SCI_VERSION_01.
2085 s_sciVersion = SCI_VERSION_01;
2086 return;
2087 }
2088
2089 // New decompressors. It's either SCI_VERSION_1_EGA or SCI_VERSION_1_EARLY.
2090 if (hasSci1Voc900()) {
2091 s_sciVersion = SCI_VERSION_1_EGA;
2092 return;
2093 }
2094
2095 // SCI_VERSION_1_EARLY EGA versions lack the parser vocab
2096 s_sciVersion = SCI_VERSION_1_EARLY;
2097 return;
2098 case kResVersionSci1Middle:
2099 s_sciVersion = SCI_VERSION_1_MIDDLE;
2100 return;
2101 case kResVersionSci1Late:
2102 if (_volVersion == kResVersionSci11) {
2103 s_sciVersion = SCI_VERSION_1_1;
2104 return;
2105 }
2106 // FIXME: this is really difficult, lsl1 spanish has map/vol sci1late
2107 // and the only current detection difference is movecounttype which
2108 // is increment here, but ignore for all the regular sci1late games
2109 // the problem is, we dont have access to that detection till later
2110 // so maybe (part of?) that detection should get moved in here
2111 if (g_sci && (g_sci->getGameId() == GID_LSL1) && (g_sci->getLanguage() == Common::ES_ESP)) {
2112 s_sciVersion = SCI_VERSION_1_MIDDLE;
2113 return;
2114 }
2115 s_sciVersion = SCI_VERSION_1_LATE;
2116 return;
2117 case kResVersionSci11:
2118 s_sciVersion = SCI_VERSION_1_1;
2119 return;
2120 default:
2121 s_sciVersion = SCI_VERSION_NONE;
2122 error("detectSciVersion(): Unable to detect the game's SCI version");
2123 }
2124}
2125
2126bool ResourceManager::detectHires() {
2127 // SCI 1.1 and prior is never hires
2128 if (getSciVersion() <= SCI_VERSION_1_1)
2129 return false;
2130
2131#ifdef ENABLE_SCI32
2132 for (int i = 0; i < 32768; i++) {
2133 Resource *res = findResource(ResourceId(kResourceTypePic, i), 0);
2134
2135 if (res) {
2136 if (READ_LE_UINT16(res->data) == 0x0e) {
2137 // SCI32 picture
2138 uint16 width = READ_LE_UINT16(res->data + 10);
2139 uint16 height = READ_LE_UINT16(res->data + 12);
2140 // Surely lowres (e.g. QFG4CD)
2141 if ((width == 320) && ((height == 190) || (height == 200)))
2142 return false;
2143 // Surely hires
2144 if ((width >= 600) || (height >= 400))
2145 return true;
2146 }
2147 }
2148 }
2149
2150 // We haven't been able to find hires content
2151
2152 return false;
2153#else
2154 error("no sci32 support");
2155#endif
2156}
2157
2158bool ResourceManager::detectFontExtended() {
2159
2160 Resource *res = findResource(ResourceId(kResourceTypeFont, 0), 0);
2161 if (res) {
2162 if (res->size >= 4) {
2163 uint16 numChars = READ_LE_UINT16(res->data + 2);
2164 if (numChars > 0x80)
2165 return true;
2166 }
2167 }
2168 return false;
2169}
2170
2171// detects, if SCI1.1 game uses palette merging or copying - this is supposed to only get used on SCI1.1 games
2172bool ResourceManager::detectForPaletteMergingForSci11() {
2173 // Load palette 999 (default palette)
2174 Resource *res = findResource(ResourceId(kResourceTypePalette, 999), false);
2175
2176 if ((res) && (res->size > 30)) {
2177 byte *data = res->data;
2178 // Old palette format used in palette resource? -> it's merging
2179 if ((data[0] == 0 && data[1] == 1) || (data[0] == 0 && data[1] == 0 && READ_LE_UINT16(data + 29) == 0))
2180 return true;
2181 return false;
2182 }
2183 return false;
2184}
2185
2186// is called on SCI0EARLY games to make sure that sound resources are in fact also SCI0EARLY
2187bool ResourceManager::detectEarlySound() {
2188 Resource *res = findResource(ResourceId(kResourceTypeSound, 1), 0);
2189 if (res) {
2190 if (res->size >= 0x22) {
2191 if (READ_LE_UINT16(res->data + 0x1f) == 0) // channel 15 voice count + play mask is 0 in SCI0LATE
2192 if (res->data[0x21] == 0) // last byte right before actual data is 0 as well
2193 return false;
2194 }
2195 }
2196 return true;
2197}
2198
2199// Functions below are based on PD code by Brian Provinciano (SCI Studio)
2200bool ResourceManager::hasOldScriptHeader() {
2201 Resource *res = findResource(ResourceId(kResourceTypeScript, 0), 0);
2202
2203 if (!res) {
2204 error("resMan: Failed to find script.000");
2205 return false;
2206 }
2207
2208 uint offset = 2;
2209 const int objTypes = 17;
2210
2211 while (offset < res->size) {
2212 uint16 objType = READ_LE_UINT16(res->data + offset);
2213
2214 if (!objType) {
2215 offset += 2;
2216 // We should be at the end of the resource now
2217 return offset == res->size;
2218 }
2219
2220 if (objType >= objTypes) {
2221 // Invalid objType
2222 return false;
2223 }
2224
2225 int skip = READ_LE_UINT16(res->data + offset + 2);
2226
2227 if (skip < 2) {
2228 // Invalid size
2229 return false;
2230 }
2231
2232 offset += skip;
2233 }
2234
2235 return false;
2236}
2237
2238bool ResourceManager::hasSci0Voc999() {
2239 Resource *res = findResource(ResourceId(kResourceTypeVocab, 999), 0);
2240
2241 if (!res) {
2242 // No vocab present, possibly a demo version
2243 return false;
2244 }
2245
2246 if (res->size < 2)
2247 return false;
2248
2249 uint16 count = READ_LE_UINT16(res->data);
2250
2251 // Make sure there's enough room for the pointers
2252 if (res->size < (uint)count * 2)
2253 return false;
2254
2255 // Iterate over all pointers
2256 for (uint i = 0; i < count; i++) {
2257 // Offset to string
2258 uint16 offset = READ_LE_UINT16(res->data + 2 + count * 2);
2259
2260 // Look for end of string
2261 do {
2262 if (offset >= res->size) {
2263 // Out of bounds
2264 return false;
2265 }
2266 } while (res->data[offset++]);
2267 }
2268
2269 return true;
2270}
2271
2272bool ResourceManager::hasSci1Voc900() {
2273 Resource *res = findResource(ResourceId(kResourceTypeVocab, 900), 0);
2274
2275 if (!res )
2276 return false;
2277
2278 if (res->size < 0x1fe)
2279 return false;
2280
2281 uint16 offset = 0x1fe;
2282
2283 while (offset < res->size) {
2284 offset++;
2285 do {
2286 if (offset >= res->size) {
2287 // Out of bounds;
2288 return false;
2289 }
2290 } while (res->data[offset++]);
2291 offset += 3;
2292 }
2293
2294 return offset == res->size;
2295}
2296
2297// Same function as Script::findBlock(). Slight code
2298// duplication here, but this has been done to keep the resource
2299// manager independent from the rest of the engine
2300static byte *findSci0ExportsBlock(byte *buffer) {
2301 byte *buf = buffer;
2302 bool oldScriptHeader = (getSciVersion() == SCI_VERSION_0_EARLY);
2303
2304 if (oldScriptHeader)
2305 buf += 2;
2306
2307 do {
2308 int seekerType = READ_LE_UINT16(buf);
2309
2310 if (seekerType == 0)
2311 break;
2312 if (seekerType == 7) // exports
2313 return buf;
2314
2315 int seekerSize = READ_LE_UINT16(buf + 2);
2316 assert(seekerSize > 0);
2317 buf += seekerSize;
2318 } while (1);
2319
2320 return NULL;
2321}
2322
2323reg_t ResourceManager::findGameObject(bool addSci11ScriptOffset) {
2324 Resource *script = findResource(ResourceId(kResourceTypeScript, 0), false);
2325
2326 if (!script)
2327 return NULL_REG;
2328
2329 byte *offsetPtr = 0;
2330
2331 if (getSciVersion() < SCI_VERSION_1_1) {
2332 byte *buf = (getSciVersion() == SCI_VERSION_0_EARLY) ? script->data + 2 : script->data;
2333
2334 // Check if the first block is the exports block (in most cases, it is)
2335 bool exportsIsFirst = (READ_LE_UINT16(buf + 4) == 7);
2336 if (exportsIsFirst) {
2337 offsetPtr = buf + 4 + 2;
2338 } else {
2339 offsetPtr = findSci0ExportsBlock(script->data);
2340 if (!offsetPtr)
2341 error("Unable to find exports block from script 0");
2342 offsetPtr += 4 + 2;
2343 }
2344 } else {
2345 offsetPtr = script->data + 4 + 2 + 2;
2346 }
2347
2348 int16 offset = !isSci11Mac() ? READ_LE_UINT16(offsetPtr) : READ_BE_UINT16(offsetPtr);
2349
2350 // In SCI1.1 and newer, the heap is appended at the end of the script,
2351 // so adjust the offset accordingly
2352 if (getSciVersion() >= SCI_VERSION_1_1 && addSci11ScriptOffset) {
2353 offset += script->size;
2354
2355 // Ensure that the start of the heap is word-aligned - same as in Script::init()
2356 if (script->size & 2)
2357 offset++;
2358 }
2359
2360 return make_reg(1, offset);
2361}
2362
2363Common::String ResourceManager::findSierraGameId() {
2364 // In SCI0-SCI1, the heap is embedded in the script. In SCI1.1+, it's separated
2365 Resource *heap = 0;
2366 int nameSelector = 3;
2367
2368 if (getSciVersion() < SCI_VERSION_1_1) {
2369 heap = findResource(ResourceId(kResourceTypeScript, 0), false);
2370 } else {
2371 heap = findResource(ResourceId(kResourceTypeHeap, 0), false);
2372 nameSelector += 5;
2373 }
2374
2375 if (!heap)
2376 return "";
2377
2378 int16 gameObjectOffset = findGameObject(false).offset;
2379
2380 if (!gameObjectOffset)
2381 return "";
2382
2383 // Seek to the name selector of the first export
2384 byte *seeker = heap->data + READ_UINT16(heap->data + gameObjectOffset + nameSelector * 2);
2385 Common::String sierraId;
2386 sierraId += (const char *)seeker;
2387
2388 return sierraId;
2389}
2390
2391const Common::String &Resource::getResourceLocation() const {
2392 return _source->getLocationName();
2393}
2394
2395} // End of namespace Sci