diff options
Diffstat (limited to 'engines/sci/resource.cpp')
-rw-r--r-- | engines/sci/resource.cpp | 248 |
1 files changed, 181 insertions, 67 deletions
diff --git a/engines/sci/resource.cpp b/engines/sci/resource.cpp index 10740a8b7b..48278e35a7 100644 --- a/engines/sci/resource.cpp +++ b/engines/sci/resource.cpp @@ -26,6 +26,9 @@ #include "common/fs.h" #include "common/macresman.h" #include "common/textconsole.h" +#ifdef ENABLE_SCI32 +#include "common/memstream.h" +#endif #include "sci/resource.h" #include "sci/resource_intern.h" @@ -82,8 +85,12 @@ const char *getSciVersionDesc(SciVersion version) { return "SCI1.1"; case SCI_VERSION_2: return "SCI2"; - case SCI_VERSION_2_1: - return "SCI2.1"; + case SCI_VERSION_2_1_EARLY: + return "Early SCI2.1"; + case SCI_VERSION_2_1_MIDDLE: + return "Middle SCI2.1"; + case SCI_VERSION_2_1_LATE: + return "Late SCI2.1"; case SCI_VERSION_3: return "SCI3"; default: @@ -217,6 +224,12 @@ void Resource::writeToStream(Common::WriteStream *stream) const { stream->write(data, size); } +#ifdef ENABLE_SCI32 +Common::SeekableReadStream *Resource::makeStream() const { + return new Common::MemoryReadStream(data, size, DisposeAfterUse::NO); +} +#endif + uint32 Resource::getAudioCompressionType() const { return _source->getAudioCompressionType(); } @@ -225,7 +238,6 @@ uint32 AudioVolumeResourceSource::getAudioCompressionType() const { return _audioCompressionType; } - ResourceSource::ResourceSource(ResSourceType type, const Common::String &name, int volNum, const Common::FSNode *resFile) : _sourceType(type), _name(name), _volumeNumber(volNum), _resourceFile(resFile) { _scanned = false; @@ -303,7 +315,7 @@ bool Resource::loadPatch(Common::SeekableReadStream *file) { error("Can't allocate %d bytes needed for loading %s", res->size + res->_headerSize, res->_id.toString().c_str()); } - unsigned int really_read; + uint32 really_read; if (res->_headerSize > 0) { really_read = file->read(res->_header, res->_headerSize); if (really_read != res->_headerSize) @@ -561,12 +573,11 @@ Resource *ResourceManager::testResource(ResourceId id) { } int ResourceManager::addAppropriateSources() { - Common::ArchiveMemberList files; - if (Common::File::exists("resource.map")) { // SCI0-SCI2 file naming scheme ResourceSource *map = addExternalMap("resource.map"); + Common::ArchiveMemberList files; SearchMan.listMatchingMembers(files, "resource.0??"); for (Common::ArchiveMemberList::const_iterator x = files.begin(); x != files.end(); ++x) { @@ -583,20 +594,20 @@ int ResourceManager::addAppropriateSources() { #endif } else if (Common::MacResManager::exists("Data1")) { // Mac SCI1.1+ file naming scheme - SearchMan.listMatchingMembers(files, "Data?*"); + Common::StringArray files; + Common::MacResManager::listFiles(files, "Data?"); - for (Common::ArchiveMemberList::const_iterator x = files.begin(); x != files.end(); ++x) { - Common::String filename = (*x)->getName(); - addSource(new MacResourceForkResourceSource(filename, atoi(filename.c_str() + 4))); + for (Common::StringArray::const_iterator x = files.begin(); x != files.end(); ++x) { + addSource(new MacResourceForkResourceSource(*x, atoi(x->c_str() + 4))); } #ifdef ENABLE_SCI32 // There can also be a "Patches" resource fork with patches - if (Common::File::exists("Patches")) + if (Common::MacResManager::exists("Patches")) addSource(new MacResourceForkResourceSource("Patches", 100)); } else { // SCI2.1-SCI3 file naming scheme - Common::ArchiveMemberList mapFiles; + Common::ArchiveMemberList mapFiles, files; SearchMan.listMatchingMembers(mapFiles, "resmap.0??"); SearchMan.listMatchingMembers(files, "ressci.0??"); @@ -861,6 +872,7 @@ ResourceManager::ResourceManager() { } void ResourceManager::init() { + _maxMemoryLRU = 256 * 1024; // 256KiB _memoryLocked = 0; _memoryLRU = 0; _LRU.clear(); @@ -914,6 +926,14 @@ void ResourceManager::init() { debugC(1, kDebugLevelResMan, "resMan: Detected %s", getSciVersionDesc(getSciVersion())); + // Resources in SCI32 games are significantly larger than SCI16 + // games and can cause immediate exhaustion of the LRU resource + // cache, leading to constant decompression of picture resources + // and making the renderer very slow. + if (getSciVersion() >= SCI_VERSION_2) { + _maxMemoryLRU = 2048 * 1024; // 2MiB + } + switch (_viewType) { case kViewEga: debugC(1, kDebugLevelResMan, "resMan: Detected EGA graphic resources"); @@ -931,35 +951,14 @@ void ResourceManager::init() { debugC(1, kDebugLevelResMan, "resMan: Detected SCI1.1 VGA graphic resources"); break; default: -#ifdef ENABLE_SCI32 - error("resMan: Couldn't determine view type"); -#else - if (getSciVersion() >= SCI_VERSION_2) { - // SCI support isn't built in, thus the view type won't be determined for - // SCI2+ games. This will be handled further up, so throw no error here - } else { - error("resMan: Couldn't determine view type"); - } -#endif + // Throw a warning, but do not error out here, because this is called from the + // fallback detector, and the user could be pointing to a folder with a non-SCI + // game, but with SCI-like file names (e.g. Pinball Creep) + warning("resMan: Couldn't determine view type"); + break; } } -void ResourceManager::initForDetection() { - assert(!g_sci); - - _memoryLocked = 0; - _memoryLRU = 0; - _LRU.clear(); - _resMap.clear(); - _audioMapSCI1 = NULL; - - _mapVersion = detectMapVersion(); - _volVersion = detectVolVersion(); - - scanNewSources(); - detectSciVersion(); -} - ResourceManager::~ResourceManager() { // freeing resources ResourceMap::iterator itr = _resMap.begin(); @@ -994,9 +993,9 @@ void ResourceManager::addToLRU(Resource *res) { _LRU.push_front(res); _memoryLRU += res->size; #if SCI_VERBOSE_RESMAN - debug("Adding %s.%03d (%d bytes) to lru control: %d bytes total", - getResourceTypeName(res->type), res->number, res->size, - mgr->_memoryLRU); + debug("Adding %s (%d bytes) to lru control: %d bytes total", + res->_id.toString().c_str(), res->size, + _memoryLRU); #endif res->_status = kResStatusEnqueued; } @@ -1019,13 +1018,13 @@ void ResourceManager::printLRU() { } void ResourceManager::freeOldResources() { - while (MAX_MEMORY < _memoryLRU) { + while (_maxMemoryLRU < _memoryLRU) { assert(!_LRU.empty()); Resource *goner = *_LRU.reverse_begin(); removeFromLRU(goner); goner->unalloc(); #ifdef SCI_VERBOSE_RESMAN - debug("resMan-debug: LRU: Freeing %s.%03d (%d bytes)", getResourceTypeName(goner->type), goner->number, goner->size); + debug("resMan-debug: LRU: Freeing %s (%d bytes)", goner->_id.toString().c_str(), goner->size); #endif } } @@ -1052,7 +1051,13 @@ Resource *ResourceManager::findResource(ResourceId id, bool lock) { if (retval->_status == kResStatusNoMalloc) loadResource(retval); else if (retval->_status == kResStatusEnqueued) + // The resource is removed from its current position + // in the LRU list because it has been requested + // again. Below, it will either be locked, or it + // will be added back to the LRU list at the 'most + // recent' position. removeFromLRU(retval); + // Unless an error occurred, the resource is now either // locked or allocated, but never queued or freed. @@ -1361,6 +1366,8 @@ void ResourceManager::processPatch(ResourceSource *source, ResourceType resource Common::File *file = new Common::File(); if (!file->open(source->getLocationName())) { warning("ResourceManager::processPatch(): failed to open %s", source->getLocationName().c_str()); + delete source; + delete file; return; } fileStream = file; @@ -1369,6 +1376,8 @@ void ResourceManager::processPatch(ResourceSource *source, ResourceType resource int fsize = fileStream->size(); if (fsize < 3) { debug("Patching %s failed - file too small", source->getLocationName().c_str()); + delete source; + delete fileStream; return; } @@ -1379,6 +1388,7 @@ void ResourceManager::processPatch(ResourceSource *source, ResourceType resource if (patchType != checkForType) { debug("Patching %s failed - resource type mismatch", source->getLocationName().c_str()); + delete source; return; } @@ -1403,6 +1413,7 @@ void ResourceManager::processPatch(ResourceSource *source, ResourceType resource if (patchDataOffset + 2 >= fsize) { debug("Patching %s failed - patch starting at offset %d can't be in file of size %d", source->getLocationName().c_str(), patchDataOffset + 2, fsize); + delete source; return; } @@ -1450,13 +1461,18 @@ void ResourceManager::readResourcePatchesBase36() { files.clear(); // audio36 resources start with a @, A, or B - // sync36 resources start with a # + // sync36 resources start with a #, S, or T if (i == kResourceTypeAudio36) { SearchMan.listMatchingMembers(files, "@???????.???"); SearchMan.listMatchingMembers(files, "A???????.???"); SearchMan.listMatchingMembers(files, "B???????.???"); - } else + } else { SearchMan.listMatchingMembers(files, "#???????.???"); +#ifdef ENABLE_SCI32 + SearchMan.listMatchingMembers(files, "S???????.???"); + SearchMan.listMatchingMembers(files, "T???????.???"); +#endif + } for (Common::ArchiveMemberList::const_iterator x = files.begin(); x != files.end(); ++x) { name = (*x)->getName(); @@ -2121,6 +2137,79 @@ ViewType ResourceManager::detectViewType() { return kViewUnknown; } +// to detect selector "wordFail" in LE vocab resource +static const byte detectSci21EarlySignature[] = { + 10, // size of signature + 0x08, 0x00, 'w', 'o', 'r', 'd', 'F', 'a', 'i', 'l' +}; + +// to detect selector "wordFail" in BE vocab resource (SCI2.1 Early) +static const byte detectSci21EarlyBESignature[] = { + 10, // size of signature + 0x00, 0x08, 'w', 'o', 'r', 'd', 'F', 'a', 'i', 'l' +}; + +// to detect new kString calling to detect SCI2.1 Late +static const byte detectSci21NewStringSignature[] = { + 8, // size of signature + 0x78, // push1 + 0x78, // push1 + 0x39, 0x09, // pushi 09 + 0x59, 0x01, // rest 01 + 0x43, 0x5c, // callk String +}; + +bool ResourceManager::checkResourceDataForSignature(Resource *resource, const byte *signature) { + byte signatureSize = *signature; + const byte *resourceData = resource->data; + + signature++; // skip over size byte + if (signatureSize < 4) + error("resource signature is too small, internal error"); + if (signatureSize > resource->size) + return false; + + const uint32 signatureDWord = *((const uint32 *)signature); + signature += 4; signatureSize -= 4; + + const uint32 searchLimit = resource->size - signatureSize + 1; + uint32 DWordOffset = 0; + while (DWordOffset < searchLimit) { + if (signatureDWord == READ_UINT32(resourceData + DWordOffset)) { + // magic DWORD found, check if the rest matches as well + uint32 offset = DWordOffset + 4; + uint32 signaturePos = 0; + while (signaturePos < signatureSize) { + if (resourceData[offset] != signature[signaturePos]) + break; + offset++; + signaturePos++; + } + if (signaturePos >= signatureSize) + return true; // signature found + } + DWordOffset++; + } + return false; +} + +bool ResourceManager::checkResourceForSignatures(ResourceType resourceType, uint16 resourceNr, const byte *signature1, const byte *signature2) { + Resource *resource = findResource(ResourceId(resourceType, resourceNr), false); + + if (resource) { + // resource found and loaded, check for signatures + if (signature1) { + if (checkResourceDataForSignature(resource, signature1)) + return true; + } + if (signature2) { + if (checkResourceDataForSignature(resource, signature2)) + return true; + } + } + return false; +} + void ResourceManager::detectSciVersion() { // We use the view compression to set a preliminary s_sciVersion for the sake of getResourceInfo // Pretend we have a SCI0 game @@ -2180,31 +2269,52 @@ void ResourceManager::detectSciVersion() { // no Mac SCI2 games. Yes, that means that GK1 Mac is SCI2.1 and not SCI2. // TODO: Decide between SCI2.1 and SCI3 - if (res) - s_sciVersion = SCI_VERSION_2_1; - else + if (res) { + s_sciVersion = SCI_VERSION_2_1_EARLY; // we check for SCI2.1 specifics a bit later + } else { s_sciVersion = SCI_VERSION_1_1; - return; + return; + } } // Handle SCI32 versions here - if (_volVersion >= kResVersionSci2) { - Common::List<ResourceId> heaps = listResources(kResourceTypeHeap); - bool hasHeapResources = !heaps.empty(); - - // SCI2.1/3 and SCI1 Late resource maps are the same, except that - // SCI1 Late resource maps have the resource types or'd with - // 0x80. We differentiate between SCI2 and SCI2.1/3 based on that. - if (_mapVersion == kResVersionSci1Late) { - s_sciVersion = SCI_VERSION_2; - return; - } else if (hasHeapResources) { - s_sciVersion = SCI_VERSION_2_1; + if (s_sciVersion != SCI_VERSION_2_1_EARLY) { + if (_volVersion >= kResVersionSci2) { + Common::List<ResourceId> heaps = listResources(kResourceTypeHeap); + bool hasHeapResources = !heaps.empty(); + + // SCI2.1/3 and SCI1 Late resource maps are the same, except that + // SCI1 Late resource maps have the resource types or'd with + // 0x80. We differentiate between SCI2 and SCI2.1/3 based on that. + if (_mapVersion == kResVersionSci1Late) { + s_sciVersion = SCI_VERSION_2; + return; + } else if (hasHeapResources) { + s_sciVersion = SCI_VERSION_2_1_EARLY; // exact SCI2.1 version is checked a bit later + } else { + s_sciVersion = SCI_VERSION_3; + return; + } + } + } + + if (s_sciVersion == SCI_VERSION_2_1_EARLY) { + // we only know that it's SCI2.1, not which exact version it is + + // check, if selector "wordFail" inside vocab 997 exists, if it does it's SCI2.1 Early + if ((checkResourceForSignatures(kResourceTypeVocab, 997, detectSci21EarlySignature, detectSci21EarlyBESignature))) { + // found -> it is SCI2.1 early return; - } else { - s_sciVersion = SCI_VERSION_3; + } + + s_sciVersion = SCI_VERSION_2_1_MIDDLE; + if (checkResourceForSignatures(kResourceTypeScript, 64918, detectSci21NewStringSignature, nullptr)) { + // new kString call detected, it's SCI2.1 late + // TODO: this call seems to be different on Mac + s_sciVersion = SCI_VERSION_2_1_LATE; return; } + return; } // Check for transitive SCI1/SCI1.1 games, like PQ1 here @@ -2376,7 +2486,9 @@ bool ResourceManager::hasOldScriptHeader() { Resource *res = findResource(ResourceId(kResourceTypeScript, 0), 0); if (!res) { - error("resMan: Failed to find script.000"); + // Script 0 missing -> corrupted / non-SCI resource files. + // Don't error out here, because this might have been called + // from the fallback detector return false; } @@ -2537,7 +2649,7 @@ reg_t ResourceManager::findGameObject(bool addSci11ScriptOffset) { int16 offset = !isSci11Mac() ? READ_LE_UINT16(offsetPtr) : READ_BE_UINT16(offsetPtr); return make_reg(1, offset); - } else if (getSciVersion() >= SCI_VERSION_1_1 && getSciVersion() <= SCI_VERSION_2_1) { + } else if (getSciVersion() >= SCI_VERSION_1_1 && getSciVersion() <= SCI_VERSION_2_1_LATE) { offsetPtr = script->data + 4 + 2 + 2; // In SCI1.1 - SCI2.1, the heap is appended at the end of the script, @@ -2565,7 +2677,7 @@ Common::String ResourceManager::findSierraGameId() { if (getSciVersion() < SCI_VERSION_1_1) { heap = findResource(ResourceId(kResourceTypeScript, 0), false); - } else if (getSciVersion() >= SCI_VERSION_1_1 && getSciVersion() <= SCI_VERSION_2_1) { + } else if (getSciVersion() >= SCI_VERSION_1_1 && getSciVersion() <= SCI_VERSION_2_1_LATE) { heap = findResource(ResourceId(kResourceTypeHeap, 0), false); nameSelector += 5; } else if (getSciVersion() == SCI_VERSION_3) { @@ -2581,7 +2693,9 @@ Common::String ResourceManager::findSierraGameId() { return ""; // Seek to the name selector of the first export - byte *seeker = heap->data + READ_UINT16(heap->data + gameObjectOffset + nameSelector * 2); + byte *offsetPtr = heap->data + gameObjectOffset + nameSelector * 2; + uint16 offset = !isSci11Mac() ? READ_LE_UINT16(offsetPtr) : READ_BE_UINT16(offsetPtr); + byte *seeker = heap->data + offset; Common::String sierraId; sierraId += (const char *)seeker; |