Resource ResourceLoader::get(const ResourceId &id) { LOG_TRACE(m_log, '\'' << id.toString() << '\''); Resource result; // Find existing resources with the identifier if (m_pCache) { result = m_pCache->find(id); } // Haven't found a resource if (!result) { // Try to retrieve the resource result = ResourceLoaderBase::get(id); // Cache the retrieved resource if (m_pCache && result) m_pCache->add(result); } return result; }
void ResourceManager::removeAudioResource(ResourceId resId) { // Remove resource, unless it was loaded from a patch if (_resMap.contains(resId)) { Resource *res = _resMap.getVal(resId); if (res->_source->getSourceType() == kSourceAudioVolume) { if (res->_status == kResStatusLocked) { warning("Failed to remove resource %s (still in use)", resId.toString().c_str()); } else { if (res->_status == kResStatusEnqueued) removeFromLRU(res); _resMap.erase(resId); delete res; } } } }
int ResourceManager::readAudioMapSCI11(IntMapResourceSource *map) { #ifndef ENABLE_SCI32 // SCI32 support is not built in. Check if this is a SCI32 game // and if it is abort here. if (_volVersion >= kResVersionSci2) return SCI_ERROR_RESMAP_NOT_FOUND; #endif uint32 offset = 0; const ResourceId mapResId(kResourceTypeMap, map->_mapNumber); Resource *mapRes = _resMap.getVal(mapResId, nullptr); if (!mapRes) { warning("Failed to open %s", mapResId.toString().c_str()); return SCI_ERROR_RESMAP_NOT_FOUND; } // Here, we allocate audio maps ourselves instead of using findResource to // do this for us. This is in order to prevent the map resources from // getting into the LRU cache. These resources must be read and then // deallocated in games with multi-disc audio in order to read the audio // maps from every CD, and LRU eviction freaks out if an unallocated // resource ends up in the LRU list. It is also not necessary for these // resources to be cached in the LRU at all, since they are only used upon // game startup to populate _resMap. assert(mapRes->_status == kResStatusNoMalloc); loadResource(mapRes); if (!mapRes->data()) { warning("Failed to read data for %s", mapResId.toString().c_str()); return SCI_ERROR_RESMAP_NOT_FOUND; } ResourceSource *src = findVolume(map, map->_volumeNumber); if (!src) { warning("Failed to find volume for %s", mapResId.toString().c_str()); return SCI_ERROR_NO_RESOURCE_FILES_FOUND; } Common::SeekableReadStream *fileStream = getVolumeFile(src); if (!fileStream) { warning("Failed to open file stream for %s", src->getLocationName().c_str()); return SCI_ERROR_NO_RESOURCE_FILES_FOUND; } const uint32 srcSize = fileStream->size(); disposeVolumeFileStream(fileStream, src); SciSpan<const byte>::const_iterator ptr = mapRes->cbegin(); uint32 entrySize = 0; if (_volVersion >= kResVersionSci2) { // The heuristic size detection is incompatible with at least Torin RU, // which is fine because it is not needed for SCI32 entrySize = 11; } else { // Heuristic to detect entry size for (int i = mapRes->size() - 1; i >= 0; --i) { if (ptr[i] == 0xff) entrySize++; else break; } } if (map->_mapNumber == 65535) { while (ptr != mapRes->cend()) { uint16 n = ptr.getUint16LE(); ptr += 2; if (n == 0xffff) break; if (entrySize == 6) { offset = ptr.getUint32LE(); ptr += 4; } else { offset += ptr.getUint24LE(); ptr += 3; } addResource(ResourceId(kResourceTypeAudio, n), src, offset, 0, map->getLocationName()); } } else if (map->_mapNumber == 0 && entrySize == 10 && ptr[3] == 0) { // QFG3 demo format // ptr[3] would be 'seq' in the normal format and cannot possibly be 0 while (ptr != mapRes->cend()) { uint16 n = ptr.getUint16BE(); ptr += 2; if (n == 0xffff) break; offset = ptr.getUint32LE(); ptr += 4; uint32 size = ptr.getUint32LE(); ptr += 4; addResource(ResourceId(kResourceTypeAudio, n), src, offset, size, map->getLocationName()); } } else if (map->_mapNumber == 0 && entrySize == 8 && (ptr + 2).getUint16LE() == 0xffff) { // LB2 Floppy/Mother Goose SCI1.1 format Common::SeekableReadStream *stream = getVolumeFile(src); while (ptr != mapRes->cend()) { uint16 n = ptr.getUint16LE(); ptr += 4; if (n == 0xffff) break; const ResourceId audioResId(kResourceTypeAudio, n); offset = ptr.getUint32LE(); ptr += 4; uint32 size; if (src->getAudioCompressionType() == 0) { // The size is not stored in the map and the entries have no order. // We need to dig into the audio resource in the volume to get the size. stream->seek(offset + 1); byte headerSize = stream->readByte(); if (headerSize != 11 && headerSize != 12) { error("Unexpected header size in %s: should be 11 or 12, got %d", audioResId.toString().c_str(), headerSize); } stream->skip(7); size = stream->readUint32LE() + headerSize + 2; } else { size = 0; } addResource(audioResId, src, offset, size, map->getLocationName()); } disposeVolumeFileStream(stream, src); } else { // EQ1CD & SQ4CD are "early" games; KQ6CD and all SCI32 are "late" games const bool isEarly = (entrySize != 11); if (!isEarly) { offset = ptr.getUint32LE(); ptr += 4; } enum { kRaveFlag = 0x40, kSyncFlag = 0x80, kEndOfMapFlag = 0xFF }; while (ptr != mapRes->cend()) { uint32 n = ptr.getUint32BE(); uint32 syncSize = 0; ptr += 4; // Checking the entire tuple breaks Torin RU and is not how SSCI // works if ((n & kEndOfMapFlag) == kEndOfMapFlag) { const uint32 bytesLeft = mapRes->cend() - ptr; if (bytesLeft >= entrySize) { warning("End of %s reached, but %u entries remain", mapResId.toString().c_str(), bytesLeft / entrySize); } break; } // GK1CD has a message whose audio36 resource has the wrong tuple and never plays. // The message tuple is 420 2 32 0 1 but the audio36 tuple is 420 2 32 3 1. bug #10819 if (g_sci->getGameId() == GID_GK1 && g_sci->isCD() && map->_mapNumber == 420 && n == 0x02200301) { n = 0x02200001; } // QFG4CD has a message whose audio36 resource has the wrong tuple and never plays. // The message tuple is 510 23 1 0 1 but the audio36 tuple is 510 199 1 0 1. bug #10848 if (g_sci->getGameId() == GID_QFG4 && g_sci->isCD() && map->_mapNumber == 510 && n == 0xc7010001) { n = 0x17010001; } // QFG4CD has an orphaned audio36 resource that additionally has the wrong tuple. // The audio36 tuple is 520 2 59 0 3. The message would be 520 2 59 0 2. bug #10849 // We restore the missing message in message.cpp. if (g_sci->getGameId() == GID_QFG4 && g_sci->isCD() && map->_mapNumber == 520 && n == 0x023b0003) { n = 0x023b0002; } if (isEarly) { offset = ptr.getUint32LE(); ptr += 4; } else { offset += ptr.getUint24LE(); ptr += 3; } if (isEarly || (n & kSyncFlag)) { syncSize = ptr.getUint16LE(); ptr += 2; // FIXME: The sync36 resource seems to be two bytes too big in KQ6CD // (bytes taken from the RAVE resource right after it) if (syncSize > 0) { // TODO: Add a mechanism to handle cases with missing resources like the ones below // // LB2CD is missing the sync resource for message 1885 1 6 30 2 but it's a duplicate // of 1885 1 6 16 2 which does have a sync resource so use that for both. bug #9956 if (g_sci->getGameId() == GID_LAURABOW2 && map->_mapNumber == 1885 && n == 0x01061002) { addResource(ResourceId(kResourceTypeSync36, map->_mapNumber, 0x01061e02), src, offset, syncSize, map->getLocationName()); } addResource(ResourceId(kResourceTypeSync36, map->_mapNumber, n & 0xffffff3f), src, offset, syncSize, map->getLocationName()); } } // Checking for this 0x40 flag breaks at least Laura Bow 2 CD 1.1 // map 448 if (g_sci->getGameId() == GID_KQ6 && (n & kRaveFlag)) { // This seems to define the size of raw lipsync data (at least // in KQ6 CD Windows). uint32 kq6HiresSyncSize = ptr.getUint16LE(); ptr += 2; if (kq6HiresSyncSize > 0) { // Rave resources do not have separate entries in the audio // map (their data was just appended to sync resources), so // we have to use the sync resource offset first and then // adjust the offset & size later, otherwise offset // validation will fail for compressed volumes (since the // relocation table in a compressed volume only contains // offsets that existed in the original audio map) Resource *res = addResource(ResourceId(kResourceTypeRave, map->_mapNumber, n & 0xffffff3f), src, offset, syncSize + kq6HiresSyncSize, map->getLocationName()); res->_fileOffset += syncSize; res->_size -= syncSize; syncSize += kq6HiresSyncSize; } } const ResourceId id(kResourceTypeAudio36, map->_mapNumber, n & 0xffffff3f); // Map 405 on CD 1 of the US release of PQ:SWAT 1.000 is broken // and points to garbage in the RESOURCE.AUD. The affected audio36 // assets seem to be able to load successfully from one of the later // CDs, so just ignore the map on this disc if (g_sci->getGameId() == GID_PQSWAT && g_sci->getLanguage() == Common::EN_ANY && map->_volumeNumber == 1 && map->_mapNumber == 405) { continue; } if (g_sci->getGameId() == GID_GK2) { // At least version 1.00 of the US release, and the German // release, of GK2 have multiple invalid audio36 map entries on // CD 6 if (map->_volumeNumber == 6 && offset + syncSize >= srcSize) { bool skip; switch (g_sci->getLanguage()) { case Common::EN_ANY: skip = (map->_mapNumber == 22 || map->_mapNumber == 160); break; case Common::DE_DEU: skip = (map->_mapNumber == 22); break; default: skip = false; } if (skip) { continue; } } // Map 2020 on CD 1 of the German release of GK2 is invalid. // This content does not appear to ever be used by the game (it // does not even exist in the US release), and there is a // correct copy of it on CD 6, so just ignore the bad copy on // CD 1 if (g_sci->getLanguage() == Common::DE_DEU && map->_volumeNumber == 1 && map->_mapNumber == 2020) { continue; } } // Map 800 and 4176 contain content that was cut from the game. The // French version of the game includes map files from the US // release, but the audio resources are French so the maps don't // match. Since the content was never used, just ignore these maps // everywhere if (g_sci->getGameId() == GID_PHANTASMAGORIA2 && (map->_mapNumber == 800 || map->_mapNumber == 4176)) { continue; } addResource(id, src, offset + syncSize, 0, map->getLocationName()); } } mapRes->unalloc(); return 0; }
reg_t kLock(EngineState *s, int argc, reg_t *argv) { // NOTE: In SSCI, kLock uses a boolean lock flag, not a lock counter. // ScummVM's current counter-based implementation should be better than SSCI // at dealing with game scripts that unintentionally lock & unlock the same // resource multiple times (e.g. through recursion), but it will introduce // memory bugs (resource leaks lasting until the engine is restarted, or // destruction of kernel locks that lead to a use-after-free) that are // masked by ResourceManager's LRU cache if scripts rely on kLock being // idempotent like it was in SSCI. // // Like SSCI, resource locks are not persisted in save games in ScummVM // until GK2, so it is also possible that kLock bugs will appear only after // restoring a save game. // // See also kUnLoad. ResourceType type = g_sci->getResMan()->convertResType(argv[0].toUint16()); if (type == kResourceTypeSound && getSciVersion() >= SCI_VERSION_1_1) { type = g_sci->_soundCmd->getSoundResourceType(argv[1].toUint16()); } const ResourceId id(type, argv[1].toUint16()); const bool lock = argc > 2 ? argv[2].toUint16() : true; #ifdef ENABLE_SCI32 // SSCI GK2+SCI3 also saves lock states for View, Pic, and Sync resources, // but so far it seems like audio resources are the only ones that actually // need to be handled if (g_sci->_features->hasSci3Audio() && type == kResourceTypeAudio) { g_sci->_audio32->lockResource(id, lock); return s->r_acc; } #endif if (getSciVersion() == SCI_VERSION_1_1 && (type == kResourceTypeAudio36 || type == kResourceTypeSync36)) { return s->r_acc; } if (lock) { g_sci->getResMan()->findResource(id, true); } else { if (getSciVersion() < SCI_VERSION_2 && id.getNumber() == 0xFFFF) { // Unlock all resources of the requested type Common::List<ResourceId> resources = g_sci->getResMan()->listResources(type); Common::List<ResourceId>::iterator itr; for (itr = resources.begin(); itr != resources.end(); ++itr) { Resource *res = g_sci->getResMan()->testResource(*itr); if (res->isLocked()) g_sci->getResMan()->unlockResource(res); } } else { Resource *which = g_sci->getResMan()->findResource(id, false); if (which) g_sci->getResMan()->unlockResource(which); else { if (id.getType() == kResourceTypeInvalid) warning("[resMan] Attempt to unlock resource %i of invalid type %i", id.getNumber(), argv[0].toUint16()); else // Happens in CD games (e.g. LSL6CD) with the message // resource. It isn't fatal, and it's usually caused // by leftover scripts. debugC(kDebugLevelResMan, "[resMan] Attempt to unlock non-existent resource %s", id.toString().c_str()); } } } return s->r_acc; }
reg_t kDoSync(EngineState *s, int funct_nr, int argc, reg_t *argv) { switch (argv[0].toUint16()) { case kSciAudioSyncStart: { ResourceId id; if (s->_sound._syncResource) { s->resmgr->unlockResource(s->_sound._syncResource); s->_sound._syncResource = NULL; } // Load sound sync resource and lock it if (argc == 3) { id = ResourceId(kResourceTypeSync, argv[2].toUint16()); } else if (argc == 7) { id = ResourceId(kResourceTypeSync36, argv[2].toUint16(), argv[3].toUint16(), argv[4].toUint16(), argv[5].toUint16(), argv[6].toUint16()); } else { warning("kDoSync: Start called with an unknown number of parameters (%d)", argc); return s->r_acc; } s->_sound._syncResource = s->resmgr->findResource(id, 1); if (s->_sound._syncResource) { PUT_SEL32V(argv[1], syncCue, 0); s->_sound._syncOffset = 0; } else { warning("DoSync: failed to find resource %s", id.toString().c_str()); // Notify the scripts to stop sound sync PUT_SEL32V(argv[1], syncCue, -1); } break; } case kSciAudioSyncNext: { Resource *res = s->_sound._syncResource; if (res && (s->_sound._syncOffset < res->size - 1)) { int16 syncCue = -1; int16 syncTime = (int16)READ_LE_UINT16(res->data + s->_sound._syncOffset); s->_sound._syncOffset += 2; if ((syncTime != -1) && (s->_sound._syncOffset < res->size - 1)) { syncCue = (int16)READ_LE_UINT16(res->data + s->_sound._syncOffset); s->_sound._syncOffset += 2; } PUT_SEL32V(argv[1], syncTime, syncTime); PUT_SEL32V(argv[1], syncCue, syncCue); } break; } case kSciAudioSyncStop: if (s->_sound._syncResource) { s->resmgr->unlockResource(s->_sound._syncResource); s->_sound._syncResource = NULL; } break; default: warning("DoSync: Unhandled subfunction %d", argv[0].toUint16()); } return s->r_acc; }
reg_t kLock(EngineState *s, int argc, reg_t *argv) { int state = argc > 2 ? argv[2].toUint16() : 1; ResourceType type = g_sci->getResMan()->convertResType(argv[0].toUint16()); ResourceId id = ResourceId(type, argv[1].toUint16()); Resource *which; switch (state) { case 1 : g_sci->getResMan()->findResource(id, 1); break; case 0 : if (id.getNumber() == 0xFFFF) { // Unlock all resources of the requested type Common::List<ResourceId> *resources = g_sci->getResMan()->listResources(type); Common::List<ResourceId>::iterator itr = resources->begin(); while (itr != resources->end()) { Resource *res = g_sci->getResMan()->testResource(*itr); if (res->isLocked()) g_sci->getResMan()->unlockResource(res); ++itr; } delete resources; } else { which = g_sci->getResMan()->findResource(id, 0); if (which) g_sci->getResMan()->unlockResource(which); else { if (id.getType() == kResourceTypeInvalid) warning("[resMan] Attempt to unlock resource %i of invalid type %i", id.getNumber(), argv[0].toUint16()); else // Happens in CD games (e.g. LSL6CD) with the message // resource. It isn't fatal, and it's usually caused // by leftover scripts. debugC(kDebugLevelResMan, "[resMan] Attempt to unlock non-existant resource %s", id.toString().c_str()); } } break; } return s->r_acc; }