From dd13c3be43b2566d7ee6449be7918a86428bb4da Mon Sep 17 00:00:00 2001 From: Colin Snover Date: Wed, 10 May 2017 00:33:43 -0500 Subject: SCI: Fix support for ScummVM compressed audio volumes The runtime code for this had previously relied on hot patching volume file offsets at the moment that a resource was loaded, instead of correcting file offsets when reading audio maps. The code added for sanity checking audio volumes started to report warnings because the offsets being received were for the original uncompressed audio volume, which (naturally) is larger than the compressed audio volume. This commit also deduplicates code between addResource and updateResource, and tweaks a validation-related error message for improved clarity. Fixes Trac#9764. --- engines/sci/resource.cpp | 22 +++----- engines/sci/resource_audio.cpp | 124 +++++++++++++++-------------------------- engines/sci/resource_intern.h | 24 +++++++- 3 files changed, 75 insertions(+), 95 deletions(-) (limited to 'engines/sci') diff --git a/engines/sci/resource.cpp b/engines/sci/resource.cpp index 91cceb5968..2f34f8f433 100644 --- a/engines/sci/resource.cpp +++ b/engines/sci/resource.cpp @@ -2013,20 +2013,7 @@ bool ResourceManager::validateResource(const ResourceId &resourceId, const Commo void ResourceManager::addResource(ResourceId resId, ResourceSource *src, uint32 offset, uint32 size, const Common::String &sourceMapLocation) { // Adding new resource only if it does not exist if (_resMap.contains(resId) == false) { - Common::SeekableReadStream *volumeFile = getVolumeFile(src); - if (volumeFile == nullptr) { - error("Could not open %s for reading", src->getLocationName().c_str()); - } - - if (validateResource(resId, sourceMapLocation, src->getLocationName(), offset, size, volumeFile->size())) { - Resource *res = new Resource(this, resId); - _resMap.setVal(resId, res); - res->_source = src; - res->_fileOffset = offset; - res->_size = size; - } else { - _hasBadResources = true; - } + updateResource(resId, src, offset, size, sourceMapLocation); } } @@ -2048,6 +2035,13 @@ Resource *ResourceManager::updateResource(ResourceId resId, ResourceSource *src, error("Could not open %s for reading", src->getLocationName().c_str()); } + AudioVolumeResourceSource *avSrc = dynamic_cast(src); + if (avSrc != nullptr && !avSrc->relocateMapOffset(offset, size)) { + warning("Compressed volume %s does not contain a valid entry for %s (map offset %u)", src->getLocationName().c_str(), resId.toString().c_str(), offset); + _hasBadResources = true; + return res; + } + if (validateResource(resId, sourceMapLocation, src->getLocationName(), offset, size, volumeFile->size())) { if (res == nullptr) { res = new Resource(this, resId); diff --git a/engines/sci/resource_audio.cpp b/engines/sci/resource_audio.cpp index a5501cdb84..2cd157a631 100644 --- a/engines/sci/resource_audio.cpp +++ b/engines/sci/resource_audio.cpp @@ -36,7 +36,6 @@ AudioVolumeResourceSource::AudioVolumeResourceSource(ResourceManager *resMan, co : VolumeResourceSource(name, map, volNum, kSourceAudioVolume) { _audioCompressionType = 0; - _audioCompressionOffsetMapping = NULL; /* * Check if this audio volume got compressed by our tool. If that is the @@ -49,36 +48,37 @@ AudioVolumeResourceSource::AudioVolumeResourceSource(ResourceManager *resMan, co return; fileStream->seek(0, SEEK_SET); - uint32 compressionType = fileStream->readUint32BE(); + const uint32 compressionType = fileStream->readUint32BE(); switch (compressionType) { case MKTAG('M','P','3',' '): case MKTAG('O','G','G',' '): case MKTAG('F','L','A','C'): - // Detected a compressed audio volume _audioCompressionType = compressionType; - // Now read the whole offset mapping table for later usage - int32 recordCount = fileStream->readUint32LE(); - if (!recordCount) - error("compressed audio volume doesn't contain any entries"); - int32 *offsetMapping = new int32[(recordCount + 1) * 2]; - _audioCompressionOffsetMapping = offsetMapping; - for (int recordNo = 0; recordNo < recordCount; recordNo++) { - *offsetMapping++ = fileStream->readUint32LE(); - *offsetMapping++ = fileStream->readUint32LE(); + const uint32 numEntries = fileStream->readUint32LE(); + if (!numEntries) { + error("Compressed audio volume %s has no relocation table entries", name.c_str()); } - // Put ending zero - *offsetMapping++ = 0; - *offsetMapping++ = fileStream->size(); + + CompressedTableEntry *lastEntry = nullptr; + for (uint i = 0; i < numEntries; ++i) { + CompressedTableEntry nextEntry; + const uint32 sourceOffset = fileStream->readUint32LE(); + nextEntry.offset = fileStream->readUint32LE(); + if (lastEntry != nullptr) { + lastEntry->size = nextEntry.offset - lastEntry->offset; + } + + _compressedOffsets.setVal(sourceOffset, nextEntry); + lastEntry = &_compressedOffsets.getVal(sourceOffset); + } + + lastEntry->size = fileStream->size() - lastEntry->offset; } if (_resourceFile) delete fileStream; } -AudioVolumeResourceSource::~AudioVolumeResourceSource() { - delete[] _audioCompressionOffsetMapping; -} - bool Resource::loadFromWaveFile(Common::SeekableReadStream *file) { byte *ptr = new byte[_size]; _data = ptr; @@ -315,7 +315,7 @@ int ResourceManager::readAudioMapSCI11(IntMapResourceSource *map) { Common::SeekableReadStream *fileStream = getVolumeFile(src); if (!fileStream) { - warning("Failed to open file stream for %s", mapResId.toString().c_str()); + warning("Failed to open file stream for %s", src->getLocationName().c_str()); return SCI_ERROR_NO_RESOURCE_FILES_FOUND; } @@ -383,17 +383,21 @@ int ResourceManager::readAudioMapSCI11(IntMapResourceSource *map) { offset = ptr.getUint32LE(); ptr += 4; - // 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); - uint32 size = stream->readUint32LE() + headerSize + 2; + 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()); } } else { @@ -406,7 +410,7 @@ int ResourceManager::readAudioMapSCI11(IntMapResourceSource *map) { while (ptr != mapRes->cend()) { uint32 n = ptr.getUint32BE(); - int syncSize = 0; + uint32 syncSize = 0; ptr += 4; if (n == 0xffffffff) @@ -436,7 +440,7 @@ int ResourceManager::readAudioMapSCI11(IntMapResourceSource *map) { if (g_sci->getGameId() == GID_KQ6 && (n & 0x40)) { // This seems to define the size of raw lipsync data (at least // in KQ6 CD Windows). - int kq6HiresSyncSize = ptr.getUint16LE(); + uint32 kq6HiresSyncSize = ptr.getUint16LE(); ptr += 2; if (kq6HiresSyncSize > 0) { @@ -924,52 +928,16 @@ void AudioVolumeResourceSource::loadResource(ResourceManager *resMan, Resource * if (!fileStream) return; - if (_audioCompressionType) { - // this file is compressed, so lookup our offset in the offset-translation table and get the new offset - // also calculate the compressed size by using the next offset - int32 *mappingTable = _audioCompressionOffsetMapping; - int32 compressedOffset = 0; - - do { - if (*mappingTable == res->_fileOffset) { - mappingTable++; - compressedOffset = *mappingTable; - // Go to next compressed offset and use that to calculate size of compressed sample - switch (res->getType()) { - case kResourceTypeSync: - case kResourceTypeSync36: - case kResourceTypeRave: - // we should already have a (valid) size - break; - default: - mappingTable += 2; - res->_size = *mappingTable - compressedOffset; - } - break; - } - mappingTable += 2; - } while (*mappingTable); - - if (!compressedOffset) - error("could not translate offset to compressed offset in audio volume"); - fileStream->seek(compressedOffset, SEEK_SET); - - switch (res->getType()) { - case kResourceTypeAudio: - case kResourceTypeAudio36: - // Directly read the stream, compressed audio wont have resource type id and header size for SCI1.1 - res->loadFromAudioVolumeSCI1(fileStream); - if (_resourceFile) - delete fileStream; - return; - default: - break; - } - } else { - // original file, directly seek to given offset and get SCI1/SCI1.1 audio resource - fileStream->seek(res->_fileOffset, SEEK_SET); - } - if (getSciVersion() < SCI_VERSION_1_1) + fileStream->seek(res->_fileOffset, SEEK_SET); + + // For compressed audio, using loadFromAudioVolumeSCI1 is a hack to bypass + // the resource type checking in loadFromAudioVolumeSCI11 (since + // loadFromAudioVolumeSCI1 does nothing more than read raw data) + if (_audioCompressionType != 0 && + (res->getType() == kResourceTypeAudio || + res->getType() == kResourceTypeAudio36)) { + res->loadFromAudioVolumeSCI1(fileStream); + } else if (getSciVersion() < SCI_VERSION_1_1) res->loadFromAudioVolumeSCI1(fileStream); else res->loadFromAudioVolumeSCI11(fileStream); diff --git a/engines/sci/resource_intern.h b/engines/sci/resource_intern.h index f198eddeb3..ea3e05661f 100644 --- a/engines/sci/resource_intern.h +++ b/engines/sci/resource_intern.h @@ -144,17 +144,35 @@ public: class AudioVolumeResourceSource : public VolumeResourceSource { protected: + struct CompressedTableEntry { + uint32 offset; + uint32 size; + }; + uint32 _audioCompressionType; - int32 *_audioCompressionOffsetMapping; + Common::HashMap _compressedOffsets; public: AudioVolumeResourceSource(ResourceManager *resMan, const Common::String &name, ResourceSource *map, int volNum); - virtual ~AudioVolumeResourceSource(); - virtual void loadResource(ResourceManager *resMan, Resource *res); virtual uint32 getAudioCompressionType() const; + + bool relocateMapOffset(uint32 &offset, uint32 &size) const { + if (_audioCompressionType == 0) { + return true; + } + + if (!_compressedOffsets.contains(offset)) { + return false; + } + + const CompressedTableEntry &entry = _compressedOffsets.getVal(offset); + offset = entry.offset; + size = entry.size; + return true; + } }; class ExtAudioMapResourceSource : public ResourceSource { -- cgit v1.2.3