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:
parent
53b0b82e87
commit
dd13c3be43
3 changed files with 74 additions and 94 deletions
|
@ -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<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 (res == nullptr) {
|
||||
res = new Resource(this, resId);
|
||||
|
|
|
@ -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,6 +383,8 @@ int ResourceManager::readAudioMapSCI11(IntMapResourceSource *map) {
|
|||
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);
|
||||
|
@ -392,8 +394,10 @@ int ResourceManager::readAudioMapSCI11(IntMapResourceSource *map) {
|
|||
}
|
||||
|
||||
stream->skip(7);
|
||||
uint32 size = stream->readUint32LE() + headerSize + 2;
|
||||
|
||||
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)
|
||||
|
||||
// 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);
|
||||
|
|
|
@ -144,17 +144,35 @@ public:
|
|||
|
||||
class AudioVolumeResourceSource : public VolumeResourceSource {
|
||||
protected:
|
||||
struct CompressedTableEntry {
|
||||
uint32 offset;
|
||||
uint32 size;
|
||||
};
|
||||
|
||||
uint32 _audioCompressionType;
|
||||
int32 *_audioCompressionOffsetMapping;
|
||||
Common::HashMap<uint32, CompressedTableEntry> _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 {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue