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.
This commit is contained in:
Colin Snover 2017-05-10 00:33:43 -05:00
parent 53b0b82e87
commit dd13c3be43
3 changed files with 74 additions and 94 deletions

View file

@ -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) { void ResourceManager::addResource(ResourceId resId, ResourceSource *src, uint32 offset, uint32 size, const Common::String &sourceMapLocation) {
// Adding new resource only if it does not exist // Adding new resource only if it does not exist
if (_resMap.contains(resId) == false) { if (_resMap.contains(resId) == false) {
Common::SeekableReadStream *volumeFile = getVolumeFile(src); updateResource(resId, src, offset, size, sourceMapLocation);
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;
}
} }
} }
@ -2048,6 +2035,13 @@ Resource *ResourceManager::updateResource(ResourceId resId, ResourceSource *src,
error("Could not open %s for reading", src->getLocationName().c_str()); error("Could not open %s for reading", src->getLocationName().c_str());
} }
AudioVolumeResourceSource *avSrc = dynamic_cast<AudioVolumeResourceSource *>(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 (validateResource(resId, sourceMapLocation, src->getLocationName(), offset, size, volumeFile->size())) {
if (res == nullptr) { if (res == nullptr) {
res = new Resource(this, resId); res = new Resource(this, resId);

View file

@ -36,7 +36,6 @@ AudioVolumeResourceSource::AudioVolumeResourceSource(ResourceManager *resMan, co
: VolumeResourceSource(name, map, volNum, kSourceAudioVolume) { : VolumeResourceSource(name, map, volNum, kSourceAudioVolume) {
_audioCompressionType = 0; _audioCompressionType = 0;
_audioCompressionOffsetMapping = NULL;
/* /*
* Check if this audio volume got compressed by our tool. If that is the * Check if this audio volume got compressed by our tool. If that is the
@ -49,36 +48,37 @@ AudioVolumeResourceSource::AudioVolumeResourceSource(ResourceManager *resMan, co
return; return;
fileStream->seek(0, SEEK_SET); fileStream->seek(0, SEEK_SET);
uint32 compressionType = fileStream->readUint32BE(); const uint32 compressionType = fileStream->readUint32BE();
switch (compressionType) { switch (compressionType) {
case MKTAG('M','P','3',' '): case MKTAG('M','P','3',' '):
case MKTAG('O','G','G',' '): case MKTAG('O','G','G',' '):
case MKTAG('F','L','A','C'): case MKTAG('F','L','A','C'):
// Detected a compressed audio volume
_audioCompressionType = compressionType; _audioCompressionType = compressionType;
// Now read the whole offset mapping table for later usage const uint32 numEntries = fileStream->readUint32LE();
int32 recordCount = fileStream->readUint32LE(); if (!numEntries) {
if (!recordCount) error("Compressed audio volume %s has no relocation table entries", name.c_str());
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();
} }
// Put ending zero
*offsetMapping++ = 0; CompressedTableEntry *lastEntry = nullptr;
*offsetMapping++ = fileStream->size(); 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) if (_resourceFile)
delete fileStream; delete fileStream;
} }
AudioVolumeResourceSource::~AudioVolumeResourceSource() {
delete[] _audioCompressionOffsetMapping;
}
bool Resource::loadFromWaveFile(Common::SeekableReadStream *file) { bool Resource::loadFromWaveFile(Common::SeekableReadStream *file) {
byte *ptr = new byte[_size]; byte *ptr = new byte[_size];
_data = ptr; _data = ptr;
@ -315,7 +315,7 @@ int ResourceManager::readAudioMapSCI11(IntMapResourceSource *map) {
Common::SeekableReadStream *fileStream = getVolumeFile(src); Common::SeekableReadStream *fileStream = getVolumeFile(src);
if (!fileStream) { 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; return SCI_ERROR_NO_RESOURCE_FILES_FOUND;
} }
@ -383,6 +383,8 @@ int ResourceManager::readAudioMapSCI11(IntMapResourceSource *map) {
offset = ptr.getUint32LE(); offset = ptr.getUint32LE();
ptr += 4; ptr += 4;
uint32 size;
if (src->getAudioCompressionType() == 0) {
// The size is not stored in the map and the entries have no order. // 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. // We need to dig into the audio resource in the volume to get the size.
stream->seek(offset + 1); stream->seek(offset + 1);
@ -392,8 +394,10 @@ int ResourceManager::readAudioMapSCI11(IntMapResourceSource *map) {
} }
stream->skip(7); stream->skip(7);
uint32 size = stream->readUint32LE() + headerSize + 2; size = stream->readUint32LE() + headerSize + 2;
} else {
size = 0;
}
addResource(audioResId, src, offset, size, map->getLocationName()); addResource(audioResId, src, offset, size, map->getLocationName());
} }
} else { } else {
@ -406,7 +410,7 @@ int ResourceManager::readAudioMapSCI11(IntMapResourceSource *map) {
while (ptr != mapRes->cend()) { while (ptr != mapRes->cend()) {
uint32 n = ptr.getUint32BE(); uint32 n = ptr.getUint32BE();
int syncSize = 0; uint32 syncSize = 0;
ptr += 4; ptr += 4;
if (n == 0xffffffff) if (n == 0xffffffff)
@ -436,7 +440,7 @@ int ResourceManager::readAudioMapSCI11(IntMapResourceSource *map) {
if (g_sci->getGameId() == GID_KQ6 && (n & 0x40)) { if (g_sci->getGameId() == GID_KQ6 && (n & 0x40)) {
// This seems to define the size of raw lipsync data (at least // This seems to define the size of raw lipsync data (at least
// in KQ6 CD Windows). // in KQ6 CD Windows).
int kq6HiresSyncSize = ptr.getUint16LE(); uint32 kq6HiresSyncSize = ptr.getUint16LE();
ptr += 2; ptr += 2;
if (kq6HiresSyncSize > 0) { if (kq6HiresSyncSize > 0) {
@ -924,52 +928,16 @@ void AudioVolumeResourceSource::loadResource(ResourceManager *resMan, Resource *
if (!fileStream) if (!fileStream)
return; 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); fileStream->seek(res->_fileOffset, SEEK_SET);
}
if (getSciVersion() < SCI_VERSION_1_1) // 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); res->loadFromAudioVolumeSCI1(fileStream);
else else
res->loadFromAudioVolumeSCI11(fileStream); res->loadFromAudioVolumeSCI11(fileStream);

View file

@ -144,17 +144,35 @@ public:
class AudioVolumeResourceSource : public VolumeResourceSource { class AudioVolumeResourceSource : public VolumeResourceSource {
protected: protected:
struct CompressedTableEntry {
uint32 offset;
uint32 size;
};
uint32 _audioCompressionType; uint32 _audioCompressionType;
int32 *_audioCompressionOffsetMapping; Common::HashMap<uint32, CompressedTableEntry> _compressedOffsets;
public: public:
AudioVolumeResourceSource(ResourceManager *resMan, const Common::String &name, ResourceSource *map, int volNum); AudioVolumeResourceSource(ResourceManager *resMan, const Common::String &name, ResourceSource *map, int volNum);
virtual ~AudioVolumeResourceSource();
virtual void loadResource(ResourceManager *resMan, Resource *res); virtual void loadResource(ResourceManager *resMan, Resource *res);
virtual uint32 getAudioCompressionType() const; 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 { class ExtAudioMapResourceSource : public ResourceSource {