mirror of
https://github.com/libretro/scummvm.git
synced 2025-01-02 23:49:40 +00:00
d11c61db14
These are flagged by GCC if -Wswitch-default is enabled.
1150 lines
34 KiB
C++
1150 lines
34 KiB
C++
/* ScummVM - Graphic Adventure Engine
|
|
*
|
|
* ScummVM is the legal property of its developers, whose names
|
|
* are too numerous to list here. Please refer to the COPYRIGHT
|
|
* file distributed with this source distribution.
|
|
*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public License
|
|
* as published by the Free Software Foundation; either version 2
|
|
* of the License, or (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
*
|
|
*/
|
|
|
|
// Resource library
|
|
|
|
#include "common/archive.h"
|
|
#include "common/file.h"
|
|
#include "common/textconsole.h"
|
|
#include "common/memstream.h"
|
|
#include "sci/resource.h"
|
|
#include "sci/resource_intern.h"
|
|
#include "sci/util.h"
|
|
|
|
namespace Sci {
|
|
|
|
AudioVolumeResourceSource::AudioVolumeResourceSource(ResourceManager *resMan, const Common::String &name, ResourceSource *map, int volNum)
|
|
: VolumeResourceSource(name, map, volNum, kSourceAudioVolume) {
|
|
|
|
_audioCompressionType = 0;
|
|
|
|
/*
|
|
* Check if this audio volume got compressed by our tool. If that is the
|
|
* case, set _audioCompressionType and read in the offset translation
|
|
* table for later usage.
|
|
*/
|
|
|
|
Common::SeekableReadStream *fileStream = getVolumeFile(resMan, nullptr);
|
|
if (!fileStream)
|
|
return;
|
|
|
|
fileStream->seek(0, SEEK_SET);
|
|
const uint32 compressionType = fileStream->readUint32BE();
|
|
switch (compressionType) {
|
|
case MKTAG('M','P','3',' '):
|
|
case MKTAG('O','G','G',' '):
|
|
case MKTAG('F','L','A','C'): {
|
|
_audioCompressionType = compressionType;
|
|
const uint32 numEntries = fileStream->readUint32LE();
|
|
if (!numEntries) {
|
|
error("Compressed audio volume %s has no relocation table entries", name.c_str());
|
|
}
|
|
|
|
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;
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
resMan->disposeVolumeFileStream(fileStream, this);
|
|
}
|
|
|
|
bool Resource::loadFromWaveFile(Common::SeekableReadStream *file) {
|
|
byte *ptr = new byte[_size];
|
|
_data = ptr;
|
|
|
|
uint32 bytesRead = file->read(ptr, _size);
|
|
if (bytesRead != _size)
|
|
error("Read %d bytes from %s but expected %u", bytesRead, _id.toString().c_str(), _size);
|
|
|
|
_status = kResStatusAllocated;
|
|
return true;
|
|
}
|
|
|
|
bool Resource::loadFromAudioVolumeSCI11(Common::SeekableReadStream *file) {
|
|
// Check for WAVE files here
|
|
uint32 riffTag = file->readUint32BE();
|
|
if (riffTag == MKTAG('R','I','F','F')) {
|
|
_size = file->readUint32LE() + 8;
|
|
file->seek(-8, SEEK_CUR);
|
|
return loadFromWaveFile(file);
|
|
}
|
|
file->seek(-4, SEEK_CUR);
|
|
|
|
// Rave-resources (King's Quest 6) don't have any header at all
|
|
if (getType() != kResourceTypeRave) {
|
|
ResourceType type = _resMan->convertResType(file->readByte());
|
|
|
|
if (((getType() == kResourceTypeAudio || getType() == kResourceTypeAudio36) && (type != kResourceTypeAudio))
|
|
|| ((getType() == kResourceTypeSync || getType() == kResourceTypeSync36) && (type != kResourceTypeSync))) {
|
|
warning("Resource type mismatch loading %s", _id.toString().c_str());
|
|
unalloc();
|
|
return false;
|
|
}
|
|
|
|
const uint8 headerSize = file->readByte();
|
|
|
|
if (type == kResourceTypeAudio) {
|
|
if (headerSize != 7 && headerSize != 11 && headerSize != 12) {
|
|
warning("Unsupported audio header size %d in %s", headerSize, _id.toString().c_str());
|
|
unalloc();
|
|
return false;
|
|
}
|
|
|
|
if (headerSize != 7) { // Size is defined already from the map
|
|
// Load sample size
|
|
file->seek(7, SEEK_CUR);
|
|
_size = file->readUint32LE() + headerSize + kResourceHeaderSize;
|
|
if (file->err() || file->eos()) {
|
|
warning("Error while reading size of %s", _id.toString().c_str());
|
|
unalloc();
|
|
return false;
|
|
}
|
|
// Adjust offset to point at the beginning of the audio file
|
|
// again
|
|
file->seek(-11, SEEK_CUR);
|
|
}
|
|
|
|
// SOL audio files are designed to require the resource header
|
|
file->seek(-2, SEEK_CUR);
|
|
}
|
|
}
|
|
return loadPatch(file);
|
|
}
|
|
|
|
bool Resource::loadFromAudioVolumeSCI1(Common::SeekableReadStream *file) {
|
|
byte *ptr = new byte[size()];
|
|
_data = ptr;
|
|
|
|
if (!ptr) {
|
|
error("Can't allocate %u bytes needed for loading %s", _size, _id.toString().c_str());
|
|
}
|
|
|
|
uint32 bytesRead = file->read(ptr, size());
|
|
if (bytesRead != size())
|
|
warning("Read %d bytes from %s but expected %u", bytesRead, _id.toString().c_str(), _size);
|
|
|
|
_status = kResStatusAllocated;
|
|
return true;
|
|
}
|
|
|
|
void ResourceManager::addNewGMPatch(SciGameId gameId) {
|
|
Common::String gmPatchFile;
|
|
|
|
switch (gameId) {
|
|
case GID_ECOQUEST:
|
|
gmPatchFile = "ECO1GM.PAT";
|
|
break;
|
|
case GID_HOYLE3:
|
|
gmPatchFile = "HOY3GM.PAT";
|
|
break;
|
|
case GID_LSL1:
|
|
gmPatchFile = "LL1_GM.PAT";
|
|
break;
|
|
case GID_LSL5:
|
|
gmPatchFile = "LL5_GM.PAT";
|
|
break;
|
|
case GID_LONGBOW:
|
|
gmPatchFile = "ROBNGM.PAT";
|
|
break;
|
|
case GID_SQ1:
|
|
gmPatchFile = "SQ1_GM.PAT";
|
|
break;
|
|
case GID_SQ4:
|
|
gmPatchFile = "SQ4_GM.PAT";
|
|
break;
|
|
case GID_FAIRYTALES:
|
|
gmPatchFile = "TALEGM.PAT";
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if (!gmPatchFile.empty() && Common::File::exists(gmPatchFile)) {
|
|
ResourceSource *psrcPatch = new PatchResourceSource(gmPatchFile);
|
|
processPatch(psrcPatch, kResourceTypePatch, 4);
|
|
}
|
|
}
|
|
|
|
void ResourceManager::processWavePatch(ResourceId resourceId, const Common::String &name) {
|
|
ResourceSource *resSrc = new WaveResourceSource(name);
|
|
Common::File file;
|
|
file.open(name);
|
|
|
|
updateResource(resourceId, resSrc, 0, file.size(), name);
|
|
_sources.push_back(resSrc);
|
|
|
|
debugC(1, kDebugLevelResMan, "Patching %s - OK", name.c_str());
|
|
}
|
|
|
|
void ResourceManager::readWaveAudioPatches() {
|
|
// Here we do check for SCI1.1+ so we can patch wav files in as audio resources
|
|
Common::ArchiveMemberList files;
|
|
SearchMan.listMatchingMembers(files, "*.wav");
|
|
|
|
for (Common::ArchiveMemberList::const_iterator x = files.begin(); x != files.end(); ++x) {
|
|
Common::String name = (*x)->getName();
|
|
|
|
if (Common::isDigit(name[0]))
|
|
processWavePatch(ResourceId(kResourceTypeAudio, atoi(name.c_str())), name);
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Early SCI1.1 65535.MAP structure (uses RESOURCE.AUD):
|
|
// =========
|
|
// 6-byte entries:
|
|
// w nEntry
|
|
// dw offset
|
|
|
|
// Late SCI1.1 65535.MAP structure (uses RESOURCE.SFX):
|
|
// =========
|
|
// 5-byte entries:
|
|
// w nEntry
|
|
// tb offset (cumulative)
|
|
|
|
// QFG3 Demo 0.MAP structure:
|
|
// =========
|
|
// 10-byte entries:
|
|
// w nEntry
|
|
// dw offset
|
|
// dw size
|
|
|
|
// LB2 Floppy/Mother Goose SCI1.1 0.MAP structure:
|
|
// =========
|
|
// 8-byte entries:
|
|
// w nEntry
|
|
// w 0xffff
|
|
// dw offset
|
|
|
|
// Early SCI1.1 MAP structure:
|
|
// ===============
|
|
// 10-byte entries:
|
|
// b noun
|
|
// b verb
|
|
// b cond
|
|
// b seq
|
|
// dw offset
|
|
// w syncSize + syncAscSize
|
|
|
|
// Late SCI1.1 MAP structure:
|
|
// ===============
|
|
// Header:
|
|
// dw baseOffset
|
|
// Followed by 7 or 11-byte entries:
|
|
// b noun
|
|
// b verb
|
|
// b cond
|
|
// b seq
|
|
// tb cOffset (cumulative offset)
|
|
// w syncSize (iff seq has bit 7 set)
|
|
// w syncAscSize (iff seq has bit 6 set)
|
|
|
|
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 && entrySize > 0) {
|
|
warning("End of %s reached, but %u entries remain", mapResId.toString().c_str(), bytesLeft / entrySize);
|
|
}
|
|
break;
|
|
}
|
|
|
|
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) {
|
|
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;
|
|
}
|
|
|
|
// AUDIOnnn.MAP contains 10-byte entries:
|
|
// Early format:
|
|
// w 5 bits resource type and 11 bits resource number
|
|
// dw 7 bits volume number and 25 bits offset
|
|
// dw size
|
|
// Later format:
|
|
// w nEntry
|
|
// dw offset+volume (as in resource.map)
|
|
// dw size
|
|
// ending with 10 0xFFs
|
|
int ResourceManager::readAudioMapSCI1(ResourceSource *map, bool unload) {
|
|
Common::File file;
|
|
|
|
if (!file.open(map->getLocationName()))
|
|
return SCI_ERROR_RESMAP_NOT_FOUND;
|
|
|
|
bool oldFormat = (file.readUint16LE() >> 11) == kResourceTypeAudio;
|
|
file.seek(0);
|
|
|
|
for (;;) {
|
|
uint16 n = file.readUint16LE();
|
|
uint32 offset = file.readUint32LE();
|
|
uint32 size = file.readUint32LE();
|
|
|
|
if (file.eos() || file.err()) {
|
|
warning("Error while reading %s", map->getLocationName().c_str());
|
|
return SCI_ERROR_RESMAP_NOT_FOUND;
|
|
}
|
|
|
|
if (n == 0xffff)
|
|
break;
|
|
|
|
byte volume_nr;
|
|
|
|
if (oldFormat) {
|
|
n &= 0x07ff; // Mask out resource type
|
|
volume_nr = offset >> 25; // most significant 7 bits
|
|
offset &= 0x01ffffff; // least significant 25 bits
|
|
} else {
|
|
volume_nr = offset >> 28; // most significant 4 bits
|
|
offset &= 0x0fffffff; // least significant 28 bits
|
|
}
|
|
|
|
ResourceSource *src = findVolume(map, volume_nr);
|
|
|
|
if (src) {
|
|
const ResourceId resId(kResourceTypeAudio, n);
|
|
|
|
if (unload)
|
|
removeAudioResource(resId);
|
|
else
|
|
addResource(resId, src, offset, size, map->getLocationName());
|
|
} else {
|
|
warning("Failed to find audio volume %i", volume_nr);
|
|
return SCI_ERROR_NO_RESOURCE_FILES_FOUND;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void ResourceManager::setAudioLanguage(int language) {
|
|
if (_audioMapSCI1) {
|
|
if (_audioMapSCI1->_volumeNumber == language) {
|
|
// This language is already loaded
|
|
return;
|
|
}
|
|
|
|
// We already have a map loaded, so we unload it first
|
|
if (readAudioMapSCI1(_audioMapSCI1, true) != SCI_ERROR_NONE) {
|
|
_hasBadResources = true;
|
|
}
|
|
|
|
// Remove all volumes that use this map from the source list
|
|
Common::List<ResourceSource *>::iterator it = _sources.begin();
|
|
while (it != _sources.end()) {
|
|
ResourceSource *src = *it;
|
|
if (src->findVolume(_audioMapSCI1, src->_volumeNumber)) {
|
|
it = _sources.erase(it);
|
|
delete src;
|
|
} else {
|
|
++it;
|
|
}
|
|
}
|
|
|
|
// Remove the map itself from the source list
|
|
_sources.remove(_audioMapSCI1);
|
|
delete _audioMapSCI1;
|
|
|
|
_audioMapSCI1 = NULL;
|
|
}
|
|
|
|
Common::String filename = Common::String::format("AUDIO%03d", language);
|
|
Common::String fullname = filename + ".MAP";
|
|
if (!Common::File::exists(fullname)) {
|
|
warning("No audio map found for language %i", language);
|
|
return;
|
|
}
|
|
|
|
_audioMapSCI1 = addSource(new ExtAudioMapResourceSource(fullname, language));
|
|
|
|
// Search for audio volumes for this language and add them to the source list
|
|
Common::ArchiveMemberList files;
|
|
SearchMan.listMatchingMembers(files, filename + ".0??");
|
|
for (Common::ArchiveMemberList::const_iterator x = files.begin(); x != files.end(); ++x) {
|
|
const Common::String name = (*x)->getName();
|
|
const char *dot = strrchr(name.c_str(), '.');
|
|
int number = atoi(dot + 1);
|
|
|
|
addSource(new AudioVolumeResourceSource(this, name, _audioMapSCI1, number));
|
|
}
|
|
|
|
scanNewSources();
|
|
}
|
|
|
|
int ResourceManager::getAudioLanguage() const {
|
|
return (_audioMapSCI1 ? _audioMapSCI1->_volumeNumber : 0);
|
|
}
|
|
|
|
bool ResourceManager::isGMTrackIncluded() {
|
|
// This check only makes sense for SCI1 and newer games
|
|
if (getSciVersion() < SCI_VERSION_1_EARLY)
|
|
return false;
|
|
|
|
// SCI2 and newer games always have GM tracks
|
|
if (getSciVersion() >= SCI_VERSION_2)
|
|
return true;
|
|
|
|
// For the leftover games, we can safely use SCI_VERSION_1_EARLY for the soundVersion
|
|
const SciVersion soundVersion = SCI_VERSION_1_EARLY;
|
|
|
|
// Read the first song and check if it has a GM track
|
|
bool result = false;
|
|
Common::List<ResourceId> resources = listResources(kResourceTypeSound, -1);
|
|
Common::sort(resources.begin(), resources.end());
|
|
Common::List<ResourceId>::iterator itr = resources.begin();
|
|
int firstSongId = itr->getNumber();
|
|
|
|
SoundResource *song1 = new SoundResource(firstSongId, this, soundVersion);
|
|
if (!song1) {
|
|
warning("ResourceManager::isGMTrackIncluded: track 1 not found");
|
|
return false;
|
|
}
|
|
|
|
SoundResource::Track *gmTrack = song1->getTrackByType(0x07);
|
|
if (gmTrack)
|
|
result = true;
|
|
|
|
delete song1;
|
|
|
|
return result;
|
|
}
|
|
|
|
SoundResource::SoundResource(uint32 resourceNr, ResourceManager *resMan, SciVersion soundVersion) : _resMan(resMan), _soundVersion(soundVersion) {
|
|
Resource *resource = _resMan->findResource(ResourceId(kResourceTypeSound, resourceNr), true);
|
|
int trackNr, channelNr;
|
|
if (!resource)
|
|
return;
|
|
|
|
_innerResource = resource;
|
|
_soundPriority = 0xFF;
|
|
|
|
Channel *channel, *sampleChannel;
|
|
|
|
if (_soundVersion <= SCI_VERSION_0_LATE) {
|
|
// SCI0 only has a header of 0x11/0x21 byte length and the actual midi track follows afterwards
|
|
_trackCount = 1;
|
|
_tracks = new Track[_trackCount];
|
|
_tracks->digitalChannelNr = -1;
|
|
_tracks->type = 0; // Not used for SCI0
|
|
_tracks->channelCount = 1;
|
|
// Digital sample data included? -> Add an additional channel
|
|
if (resource->getUint8At(0) == 2)
|
|
_tracks->channelCount++;
|
|
// header information that can be passed to the SCI0 sound driver
|
|
_tracks->header = resource->subspan(0, _soundVersion == SCI_VERSION_0_EARLY ? 0x11 : 0x21);
|
|
_tracks->channels = new Channel[_tracks->channelCount];
|
|
channel = &_tracks->channels[0];
|
|
channel->flags |= 2; // don't remap (SCI0 doesn't have remapping)
|
|
if (_soundVersion == SCI_VERSION_0_EARLY) {
|
|
channel->data = resource->subspan(0x11);
|
|
} else {
|
|
channel->data = resource->subspan(0x21);
|
|
}
|
|
if (_tracks->channelCount == 2) {
|
|
// Digital sample data included
|
|
_tracks->digitalChannelNr = 1;
|
|
sampleChannel = &_tracks->channels[1];
|
|
// we need to find 0xFC (channel terminator) within the data
|
|
SciSpan<const byte>::const_iterator it = channel->data.cbegin();
|
|
while (it != channel->data.cend() && *it != 0xfc)
|
|
it++;
|
|
// Skip any following 0xFCs as well
|
|
while (it != channel->data.cend() && *it == 0xfc)
|
|
it++;
|
|
// Now adjust channels accordingly
|
|
sampleChannel->data = channel->data.subspan(it - channel->data.cbegin());
|
|
channel->data = channel->data.subspan(0, it - channel->data.cbegin());
|
|
// Read sample header information
|
|
//Offset 14 in the header contains the frequency as a short integer. Offset 32 contains the sample length, also as a short integer.
|
|
_tracks->digitalSampleRate = sampleChannel->data.getUint16LEAt(14);
|
|
_tracks->digitalSampleSize = sampleChannel->data.getUint16LEAt(32);
|
|
_tracks->digitalSampleStart = 0;
|
|
_tracks->digitalSampleEnd = 0;
|
|
sampleChannel->data += 44; // Skip over header
|
|
}
|
|
} else if (_soundVersion >= SCI_VERSION_1_EARLY && _soundVersion <= SCI_VERSION_2_1_MIDDLE) {
|
|
SciSpan<const byte> data = *resource;
|
|
// Count # of tracks
|
|
_trackCount = 0;
|
|
while ((*data++) != 0xFF) {
|
|
_trackCount++;
|
|
while (*data != 0xFF)
|
|
data += 6;
|
|
++data;
|
|
}
|
|
_tracks = new Track[_trackCount];
|
|
data = *resource;
|
|
|
|
byte channelCount;
|
|
|
|
for (trackNr = 0; trackNr < _trackCount; trackNr++) {
|
|
// Track info starts with track type:BYTE
|
|
// Then the channel information gets appended Unknown:WORD, ChannelOffset:WORD, ChannelSize:WORD
|
|
// 0xFF:BYTE as terminator to end that track and begin with another track type
|
|
// Track type 0xFF is the marker signifying the end of the tracks
|
|
|
|
_tracks[trackNr].type = *data++;
|
|
// Counting # of channels used
|
|
SciSpan<const byte> data2 = data;
|
|
channelCount = 0;
|
|
while (*data2 != 0xFF) {
|
|
data2 += 6;
|
|
channelCount++;
|
|
_tracks[trackNr].channelCount++;
|
|
}
|
|
_tracks[trackNr].channels = new Channel[channelCount];
|
|
_tracks[trackNr].channelCount = 0;
|
|
_tracks[trackNr].digitalChannelNr = -1; // No digital sound associated
|
|
_tracks[trackNr].digitalSampleRate = 0;
|
|
_tracks[trackNr].digitalSampleSize = 0;
|
|
_tracks[trackNr].digitalSampleStart = 0;
|
|
_tracks[trackNr].digitalSampleEnd = 0;
|
|
if (_tracks[trackNr].type != 0xF0) { // Digital track marker - not supported currently
|
|
channelNr = 0;
|
|
while (channelCount--) {
|
|
channel = &_tracks[trackNr].channels[channelNr];
|
|
const uint16 dataOffset = data.getUint16LEAt(2);
|
|
|
|
if (dataOffset >= resource->size()) {
|
|
warning("Invalid offset inside sound resource %d: track %d, channel %d", resourceNr, trackNr, channelNr);
|
|
data += 6;
|
|
continue;
|
|
}
|
|
|
|
uint16 size = data.getUint16LEAt(4);
|
|
|
|
if ((uint32)dataOffset + size > resource->size()) {
|
|
warning("Invalid size inside sound resource %d: track %d, channel %d", resourceNr, trackNr, channelNr);
|
|
size = resource->size() - dataOffset;
|
|
}
|
|
|
|
channel->data = resource->subspan(dataOffset, size);
|
|
|
|
channel->curPos = 0;
|
|
channel->number = channel->data[0];
|
|
|
|
channel->poly = channel->data[1] & 0x0F;
|
|
channel->prio = channel->data[1] >> 4;
|
|
channel->time = channel->prev = 0;
|
|
channel->data += 2; // skip over header
|
|
if (channel->number == 0xFE) { // Digital channel
|
|
_tracks[trackNr].digitalChannelNr = channelNr;
|
|
_tracks[trackNr].digitalSampleRate = channel->data.getUint16LEAt(0);
|
|
_tracks[trackNr].digitalSampleSize = channel->data.getUint16LEAt(2);
|
|
_tracks[trackNr].digitalSampleStart = channel->data.getUint16LEAt(4);
|
|
_tracks[trackNr].digitalSampleEnd = channel->data.getUint16LEAt(6);
|
|
channel->data += 8; // Skip over header
|
|
channel->flags = 0;
|
|
} else {
|
|
channel->flags = channel->number >> 4;
|
|
channel->number = channel->number & 0x0F;
|
|
|
|
// 0x20 is set on rhythm channels to prevent remapping
|
|
// CHECKME: Which SCI versions need that set manually?
|
|
if (channel->number == 9)
|
|
channel->flags |= 2;
|
|
// Note: flag 1: channel start offset is 0 instead of 10
|
|
// (currently: everything 0)
|
|
// also: don't map the channel to device
|
|
// flag 2: don't remap
|
|
// flag 4: start muted
|
|
// QfG2 lacks flags 2 and 4, and uses (flags >= 1) as
|
|
// the condition for starting offset 0, without the "don't map"
|
|
}
|
|
_tracks[trackNr].channelCount++;
|
|
channelNr++;
|
|
data += 6;
|
|
}
|
|
} else {
|
|
// The first byte of the 0xF0 track's channel list is priority
|
|
_soundPriority = *data;
|
|
|
|
// Skip over digital track
|
|
data += 6;
|
|
}
|
|
++data; // Skipping 0xFF that closes channels list
|
|
}
|
|
} else {
|
|
error("SoundResource: SCI version %s is unsupported", getSciVersionDesc(_soundVersion));
|
|
}
|
|
}
|
|
|
|
SoundResource::~SoundResource() {
|
|
for (int trackNr = 0; trackNr < _trackCount; trackNr++)
|
|
delete[] _tracks[trackNr].channels;
|
|
delete[] _tracks;
|
|
|
|
_resMan->unlockResource(_innerResource);
|
|
}
|
|
|
|
#if 0
|
|
SoundResource::Track* SoundResource::getTrackByNumber(uint16 number) {
|
|
if (_soundVersion <= SCI_VERSION_0_LATE)
|
|
return &_tracks[0];
|
|
|
|
if (/*number >= 0 &&*/number < _trackCount)
|
|
return &_tracks[number];
|
|
return NULL;
|
|
}
|
|
#endif
|
|
|
|
SoundResource::Track *SoundResource::getTrackByType(byte type) {
|
|
if (_soundVersion <= SCI_VERSION_0_LATE)
|
|
return &_tracks[0];
|
|
|
|
for (int trackNr = 0; trackNr < _trackCount; trackNr++) {
|
|
if (_tracks[trackNr].type == type)
|
|
return &_tracks[trackNr];
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
SoundResource::Track *SoundResource::getDigitalTrack() {
|
|
for (int trackNr = 0; trackNr < _trackCount; trackNr++) {
|
|
if (_tracks[trackNr].digitalChannelNr != -1)
|
|
return &_tracks[trackNr];
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
// Gets the filter mask for SCI0 sound resources
|
|
int SoundResource::getChannelFilterMask(int hardwareMask, bool wantsRhythm) {
|
|
SciSpan<const byte> data = *_innerResource;
|
|
int channelMask = 0;
|
|
|
|
if (_soundVersion > SCI_VERSION_0_LATE)
|
|
return 0;
|
|
|
|
++data; // Skip over digital sample flag
|
|
|
|
for (int channelNr = 0; channelNr < 16; channelNr++) {
|
|
channelMask = channelMask >> 1;
|
|
|
|
byte flags;
|
|
|
|
if (_soundVersion == SCI_VERSION_0_EARLY) {
|
|
// Each channel is specified by a single byte
|
|
// Upper 4 bits of the byte is a voices count
|
|
// Lower 4 bits -> bit 0 set: use for AdLib
|
|
// bit 1 set: use for PCjr
|
|
// bit 2 set: use for PC speaker
|
|
// bit 3 set and bit 0 clear: control channel (15)
|
|
// bit 3 set and bit 0 set: rhythm channel (9)
|
|
// Note: control channel is dynamically assigned inside the drivers,
|
|
// but seems to be fixed at 15 in the song data.
|
|
flags = *data++;
|
|
|
|
// Get device bits
|
|
flags &= 0x7;
|
|
} else {
|
|
// Each channel is specified by 2 bytes
|
|
// 1st byte is voices count
|
|
// 2nd byte is play mask, which specifies if the channel is supposed to be played
|
|
// by the corresponding hardware
|
|
|
|
// Skip voice count
|
|
++data;
|
|
|
|
flags = *data++;
|
|
}
|
|
|
|
bool play;
|
|
switch (channelNr) {
|
|
case 15:
|
|
// Always play control channel
|
|
play = true;
|
|
break;
|
|
case 9:
|
|
// Play rhythm channel when requested
|
|
play = wantsRhythm;
|
|
break;
|
|
default:
|
|
// Otherwise check for flag
|
|
play = flags & hardwareMask;
|
|
}
|
|
|
|
if (play) {
|
|
// This Channel is supposed to be played by the hardware
|
|
channelMask |= 0x8000;
|
|
}
|
|
}
|
|
|
|
return channelMask;
|
|
}
|
|
|
|
byte SoundResource::getInitialVoiceCount(byte channel) {
|
|
if (_soundVersion > SCI_VERSION_0_LATE)
|
|
return 0; // TODO
|
|
|
|
// Skip over digital sample flag
|
|
SciSpan<const byte> data = _innerResource->subspan(1);
|
|
|
|
if (_soundVersion == SCI_VERSION_0_EARLY)
|
|
return data[channel] >> 4;
|
|
else
|
|
return data[channel * 2];
|
|
}
|
|
|
|
void WaveResourceSource::loadResource(ResourceManager *resMan, Resource *res) {
|
|
Common::SeekableReadStream *fileStream = getVolumeFile(resMan, res);
|
|
if (!fileStream)
|
|
return;
|
|
|
|
fileStream->seek(res->_fileOffset, SEEK_SET);
|
|
res->loadFromWaveFile(fileStream);
|
|
resMan->disposeVolumeFileStream(fileStream, this);
|
|
}
|
|
|
|
void AudioVolumeResourceSource::loadResource(ResourceManager *resMan, Resource *res) {
|
|
Common::SeekableReadStream *fileStream = getVolumeFile(resMan, res);
|
|
if (!fileStream)
|
|
return;
|
|
|
|
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);
|
|
|
|
resMan->disposeVolumeFileStream(fileStream, this);
|
|
}
|
|
|
|
bool ResourceManager::addAudioSources() {
|
|
#ifdef ENABLE_SCI32
|
|
// Multi-disc audio is added during addAppropriateSources for those titles
|
|
// that require it
|
|
if (_multiDiscAudio) {
|
|
return true;
|
|
}
|
|
#endif
|
|
|
|
Common::List<ResourceId> resources = listResources(kResourceTypeMap);
|
|
Common::List<ResourceId>::iterator itr;
|
|
|
|
for (itr = resources.begin(); itr != resources.end(); ++itr) {
|
|
const Resource *mapResource = _resMap.getVal(*itr);
|
|
ResourceSource *src = addSource(new IntMapResourceSource(mapResource->getResourceLocation(), 0, itr->getNumber()));
|
|
|
|
if (itr->getNumber() == 65535 && Common::File::exists("RESOURCE.SFX"))
|
|
addSource(new AudioVolumeResourceSource(this, "RESOURCE.SFX", src, 0));
|
|
else if (Common::File::exists("RESOURCE.AUD"))
|
|
addSource(new AudioVolumeResourceSource(this, "RESOURCE.AUD", src, 0));
|
|
else
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void ResourceManager::changeAudioDirectory(Common::String path) {
|
|
if (!path.empty()) {
|
|
path += "/";
|
|
}
|
|
|
|
const Common::String resAudPath = path + "RESOURCE.AUD";
|
|
|
|
if (!SearchMan.hasFile(resAudPath)) {
|
|
error("Could not find %s", resAudPath.c_str());
|
|
}
|
|
|
|
// When a IntMapResourceSource is scanned, it will not update existing
|
|
// resources. There is also no guarantee that there are exactly the same
|
|
// number of audio36/sync36/map resources in each audio directory.
|
|
// Therefore, all of these resources must be deleted before scanning.
|
|
for (ResourceMap::iterator it = _resMap.begin(); it != _resMap.end(); ++it) {
|
|
const ResourceType type = it->_key.getType();
|
|
|
|
if (type == kResourceTypeMap || type == kResourceTypeAudio36 || type == kResourceTypeSync36) {
|
|
if (type == kResourceTypeMap && it->_key.getNumber() == 65535) {
|
|
continue;
|
|
}
|
|
|
|
Resource *resource = it->_value;
|
|
if (resource) {
|
|
// If one of these resources ends up being locked here, it
|
|
// probably means Audio32 is using it and we need to stop
|
|
// playback of audio before switching directories
|
|
assert(!resource->isLocked());
|
|
|
|
if (resource->_status == kResStatusEnqueued) {
|
|
removeFromLRU(resource);
|
|
}
|
|
|
|
// A PatchResourceSource is not added to _sources and is
|
|
// automatically deleted when the corresponding Resource is
|
|
// deleted
|
|
delete resource;
|
|
}
|
|
|
|
_resMap.erase(it);
|
|
}
|
|
}
|
|
|
|
for (SourcesList::iterator it = _sources.begin(); it != _sources.end(); ) {
|
|
IntMapResourceSource *mapSource = dynamic_cast<IntMapResourceSource *>(*it);
|
|
if (mapSource && mapSource->_mapNumber != 65535) {
|
|
delete *it;
|
|
it = _sources.erase(it);
|
|
continue;
|
|
}
|
|
|
|
AudioVolumeResourceSource *volSource = dynamic_cast<AudioVolumeResourceSource *>(*it);
|
|
if (volSource && volSource->getLocationName().contains("RESOURCE.AUD")) {
|
|
delete volSource;
|
|
it = _sources.erase(it);
|
|
continue;
|
|
}
|
|
|
|
++it;
|
|
}
|
|
|
|
// # is used as the first pattern character to avoid matching non-audio maps
|
|
// like RESOURCE.MAP
|
|
Common::ArchiveMemberList mapFiles;
|
|
SearchMan.listMatchingMembers(mapFiles, path + "#*.MAP");
|
|
|
|
for (Common::ArchiveMemberList::const_iterator it = mapFiles.begin(); it != mapFiles.end(); ++it) {
|
|
const Common::ArchiveMemberPtr &file = *it;
|
|
assert(file);
|
|
|
|
const Common::String fileName = file->getName();
|
|
const int mapNo = atoi(fileName.c_str());
|
|
|
|
// Sound effects are the same across all audio directories, so ignore
|
|
// any new SFX map
|
|
if (mapNo == 65535) {
|
|
continue;
|
|
}
|
|
|
|
ResourceSource *newSource = new PatchResourceSource(path + fileName);
|
|
processPatch(newSource, kResourceTypeMap, mapNo);
|
|
Resource *mapResource = _resMap.getVal(ResourceId(kResourceTypeMap, mapNo));
|
|
assert(mapResource);
|
|
|
|
ResourceSource *audioMap = addSource(new IntMapResourceSource(mapResource->getResourceLocation(), 0, mapNo));
|
|
addSource(new AudioVolumeResourceSource(this, resAudPath, audioMap, 0));
|
|
}
|
|
|
|
scanNewSources();
|
|
}
|
|
|
|
} // End of namespace Sci
|