diff options
Diffstat (limited to 'engines/sci/resource.cpp')
-rw-r--r-- | engines/sci/resource.cpp | 894 |
1 files changed, 712 insertions, 182 deletions
diff --git a/engines/sci/resource.cpp b/engines/sci/resource.cpp index 449effd737..4888dbd4cb 100644 --- a/engines/sci/resource.cpp +++ b/engines/sci/resource.cpp @@ -26,6 +26,7 @@ // Resource library #include "common/file.h" +#include "common/macresman.h" #include "sci/resource.h" #include "sci/util.h" @@ -44,6 +45,18 @@ struct resource_index_t { uint16 wSize; }; +struct ResourceSource { + ResSourceType source_type; + bool scanned; + Common::String location_name; // FIXME: Replace by FSNode ? + const Common::FSNode *resourceFile; + int volume_number; + ResourceSource *associated_map; + uint32 audioCompressionType; + int32 *audioCompressionOffsetMapping; + Common::MacResManager macResMan; +}; + ////////////////////////////////////////////////////////////////////// static SciVersion s_sciVersion = SCI_VERSION_NONE; // FIXME: Move this inside a suitable class, e.g. SciEngine @@ -183,7 +196,7 @@ ResourceSource *ResourceManager::addExternalMap(const char *file_name, int volum return newsrc; } -ResourceSource *ResourceManager::addExternalMap(const Common::FSNode *mapFile, int volume_nr) { +ResourceSource *ResourceManager::addExternalMap(const Common::FSNode *mapFile) { ResourceSource *newsrc = new ResourceSource(); newsrc->source_type = kSourceExtMap; @@ -191,7 +204,7 @@ ResourceSource *ResourceManager::addExternalMap(const Common::FSNode *mapFile, i newsrc->resourceFile = mapFile; newsrc->scanned = false; newsrc->associated_map = NULL; - newsrc->volume_number = volume_nr; + newsrc->volume_number = 0; _sources.push_back(newsrc); return newsrc; @@ -237,7 +250,6 @@ ResourceSource *ResourceManager::addPatchDir(const char *dirname) { ResourceSource *newsrc = new ResourceSource(); newsrc->source_type = kSourceDirectory; - newsrc->resourceFile = 0; newsrc->scanned = false; newsrc->location_name = dirname; @@ -258,7 +270,37 @@ ResourceSource *ResourceManager::getVolume(ResourceSource *map, int volume_nr) { // Resource manager constructors and operations -bool ResourceManager::loadPatch(Resource *res, Common::SeekableReadStream *file) { +void ResourceManager::checkIfAudioVolumeIsCompressed(ResourceSource *source) { + Common::File *file = getVolumeFile(source->location_name.c_str()); + if (!file) { + warning("Failed to open %s", source->location_name.c_str()); + return; + } + file->seek(0, SEEK_SET); + uint32 compressionType = file->readUint32BE(); + switch (compressionType) { + case MKID_BE('MP3 '): + case MKID_BE('OGG '): + case MKID_BE('FLAC'): + // Detected a compressed audio volume + source->audioCompressionType = compressionType; + // Now read the whole offset mapping table for later usage + int32 recordCount = file->readUint32LE(); + if (!recordCount) + error("compressed audio volume doesn't contain any entries!"); + int32 *offsetMapping = new int32[(recordCount + 1) * 2]; + source->audioCompressionOffsetMapping = offsetMapping; + for (int recordNo = 0; recordNo < recordCount; recordNo++) { + *offsetMapping++ = file->readUint32LE(); + *offsetMapping++ = file->readUint32LE(); + } + // Put ending zero + *offsetMapping++ = 0; + *offsetMapping++ = file->size(); + } +} + +bool ResourceManager::loadPatch(Resource *res, Common::File &file) { // We assume that the resource type matches res->type // We also assume that the current file position is right at the actual data (behind resourceid/headersize byte) @@ -273,12 +315,12 @@ bool ResourceManager::loadPatch(Resource *res, Common::SeekableReadStream *file) unsigned int really_read; if (res->_headerSize > 0) { - really_read = file->read(res->_header, res->_headerSize); + really_read = file.read(res->_header, res->_headerSize); if (really_read != res->_headerSize) error("Read %d bytes from %s but expected %d", really_read, res->_id.toString().c_str(), res->_headerSize); } - really_read = file->read(res->data, res->size); + really_read = file.read(res->data, res->size); if (really_read != res->size) error("Read %d bytes from %s but expected %d", really_read, res->_id.toString().c_str(), res->size); @@ -296,7 +338,71 @@ bool ResourceManager::loadFromPatchFile(Resource *res) { } // Skip resourceid and header size byte file.seek(2, SEEK_SET); - return loadPatch(res, &file); + return loadPatch(res, file); +} + +bool ResourceManager::loadFromWaveFile(Resource *res, Common::File &file) { + res->data = new byte[res->size]; + + uint32 really_read = file.read(res->data, res->size); + if (really_read != res->size) + error("Read %d bytes from %s but expected %d", really_read, res->_id.toString().c_str(), res->size); + + res->_status = kResStatusAllocated; + return true; +} + +bool ResourceManager::loadFromAudioVolumeSCI11(Resource *res, Common::File &file) { + // Check for WAVE files here + uint32 riffTag = file.readUint32BE(); + if (riffTag == MKID_BE('RIFF')) { + res->_headerSize = 0; + res->size = file.readUint32LE(); + file.seek(-8, SEEK_CUR); + return loadFromWaveFile(res, file); + } + file.seek(-4, SEEK_CUR); + + ResourceType type = (ResourceType)(file.readByte() & 0x7f); + if (((res->_id.type == kResourceTypeAudio || res->_id.type == kResourceTypeAudio36) && (type != kResourceTypeAudio)) + || ((res->_id.type == kResourceTypeSync || res->_id.type == kResourceTypeSync36) && (type != kResourceTypeSync))) { + warning("Resource type mismatch loading %s from %s", res->_id.toString().c_str(), file.getName()); + res->unalloc(); + return false; + } + + res->_headerSize = file.readByte(); + + if (type == kResourceTypeAudio) { + if (res->_headerSize != 11 && res->_headerSize != 12) { + warning("Unsupported audio header"); + res->unalloc(); + return false; + } + + // Load sample size + file.seek(7, SEEK_CUR); + res->size = file.readUint32LE(); + // Adjust offset to point at the header data again + file.seek(-11, SEEK_CUR); + } + + return loadPatch(res, file); +} + +bool ResourceManager::loadFromAudioVolumeSCI1(Resource *res, Common::File &file) { + res->data = new byte[res->size]; + + if (res->data == NULL) { + error("Can't allocate %d bytes needed for loading %s", res->size, res->_id.toString().c_str()); + } + + unsigned int really_read = file.read(res->data, res->size); + if (really_read != res->size) + warning("Read %d bytes from %s but expected %d", really_read, res->_id.toString().c_str(), res->size); + + res->_status = kResStatusAllocated; + return true; } Common::File *ResourceManager::getVolumeFile(const char *filename) { @@ -339,10 +445,11 @@ void ResourceManager::loadResource(Resource *res) { return; if (res->_source->source_type == kSourceMacResourceFork) { + //error("ResourceManager::loadResource(): TODO: Mac resource fork ;)"); Common::SeekableReadStream *stream = res->_source->macResMan.getResource(resTypeToMacTag(res->_id.type), res->_id.number); if (!stream) - error("Could not get Mac resource fork resource: %d %d", res->_id.type, res->_id.number); + error("Could not get Mac resource fork resource"); int error = decompress(res, stream); if (error) { @@ -353,15 +460,10 @@ void ResourceManager::loadResource(Resource *res) { return; } - Common::SeekableReadStream *fileStream; - + Common::File *file; // Either loading from volume or patch loading failed - if (res->_source->resourceFile) - fileStream = res->_source->resourceFile->createReadStream(); - else - fileStream = getVolumeFile(res->_source->location_name.c_str()); - - if (!fileStream) { + file = getVolumeFile(res->_source->location_name.c_str()); + if (!file) { warning("Failed to open %s", res->_source->location_name.c_str()); res->unalloc(); return; @@ -369,8 +471,8 @@ void ResourceManager::loadResource(Resource *res) { switch(res->_source->source_type) { case kSourceWave: - fileStream->seek(res->_fileOffset, SEEK_SET); - loadFromWaveFile(res, fileStream); + file->seek(res->_fileOffset, SEEK_SET); + loadFromWaveFile(res, *file); return; case kSourceAudioVolume: @@ -401,30 +503,30 @@ void ResourceManager::loadResource(Resource *res) { if (!compressedOffset) error("could not translate offset to compressed offset in audio volume"); - fileStream->seek(compressedOffset, SEEK_SET); + file->seek(compressedOffset, SEEK_SET); switch (res->_id.type) { case kResourceTypeAudio: case kResourceTypeAudio36: // Directly read the stream, compressed audio wont have resource type id and header size for SCI1.1 - loadFromAudioVolumeSCI1(res, fileStream); + loadFromAudioVolumeSCI1(res, *file); return; default: break; } } else { // original file, directly seek to given offset and get SCI1/SCI1.1 audio resource - fileStream->seek(res->_fileOffset, SEEK_SET); + file->seek(res->_fileOffset, SEEK_SET); } if (getSciVersion() < SCI_VERSION_1_1) - loadFromAudioVolumeSCI1(res, fileStream); + loadFromAudioVolumeSCI1(res, *file); else - loadFromAudioVolumeSCI11(res, fileStream); + loadFromAudioVolumeSCI11(res, *file); return; default: - fileStream->seek(res->_fileOffset, SEEK_SET); - int error = decompress(res, fileStream); + file->seek(res->_fileOffset, SEEK_SET); + int error = decompress(res, file); if (error) { warning("Error %d occured while reading %s from resource file: %s", error, res->_id.toString().c_str(), sci_error_types[error]); @@ -453,11 +555,11 @@ int sci0_get_compression_method(Common::ReadStream &stream) { int ResourceManager::addAppropriateSources() { Common::ArchiveMemberList files; - if (Common::File::exists("resource.map")) { + if (Common::File::exists("RESOURCE.MAP")) { // SCI0-SCI2 file naming scheme - ResourceSource *map = addExternalMap("resource.map"); + ResourceSource *map = addExternalMap("RESOURCE.MAP"); - SearchMan.listMatchingMembers(files, "resource.0??"); + SearchMan.listMatchingMembers(files, "RESOURCE.0??"); for (Common::ArchiveMemberList::const_iterator x = files.begin(); x != files.end(); ++x) { const Common::String name = (*x)->getName(); @@ -468,12 +570,12 @@ int ResourceManager::addAppropriateSources() { } #ifdef ENABLE_SCI32 // GK1CD hires content - if (Common::File::exists("alt.map") && Common::File::exists("resource.alt")) - addSource(addExternalMap("alt.map", 10), kSourceVolume, "resource.alt", 10); + if (Common::File::exists("ALT.MAP") && Common::File::exists("RESOURCE.ALT")) + addSource(addExternalMap("ALT.MAP", 10), kSourceVolume, "RESOURCE.ALT", 10); #endif } else if (Common::File::exists("Data1")) { // Mac SCI1.1+ file naming scheme - SearchMan.listMatchingMembers(files, "Data?*"); + SearchMan.listMatchingMembers(files, "Data?"); for (Common::ArchiveMemberList::const_iterator x = files.begin(); x != files.end(); ++x) { Common::String filename = (*x)->getName(); @@ -492,8 +594,8 @@ int ResourceManager::addAppropriateSources() { } else { // SCI2.1-SCI3 file naming scheme Common::ArchiveMemberList mapFiles; - SearchMan.listMatchingMembers(mapFiles, "resmap.0??"); - SearchMan.listMatchingMembers(files, "ressci.0??"); + SearchMan.listMatchingMembers(mapFiles, "RESMAP.0??"); + SearchMan.listMatchingMembers(files, "RESSCI.0??"); // We need to have the same number of maps as resource archives if (mapFiles.empty() || files.empty() || mapFiles.size() != files.size()) @@ -515,9 +617,9 @@ int ResourceManager::addAppropriateSources() { } // SCI2.1 resource patches - if (Common::File::exists("resmap.pat") && Common::File::exists("ressci.pat")) { + if (Common::File::exists("RESMAP.PAT") && Common::File::exists("RESSCI.PAT")) { // We add this resource with a map which surely won't exist - addSource(addExternalMap("resmap.pat", 100), kSourceVolume, "ressci.pat", 100); + addSource(addExternalMap("RESMAP.PAT", 100), kSourceVolume, "RESSCI.PAT", 100); } } #else @@ -526,18 +628,14 @@ int ResourceManager::addAppropriateSources() { #endif addPatchDir("."); - if (Common::File::exists("message.map")) - addSource(addExternalMap("message.map"), kSourceVolume, "resource.msg", 0); + if (Common::File::exists("MESSAGE.MAP")) + addSource(addExternalMap("MESSAGE.MAP"), kSourceVolume, "RESOURCE.MSG", 0); return 1; } int ResourceManager::addAppropriateSources(const Common::FSList &fslist) { ResourceSource *map = 0; -#ifdef ENABLE_SCI32 - ResourceSource *sci21PatchMap = 0; - const Common::FSNode *sci21PatchRes = 0; -#endif // First, find resource.map for (Common::FSList::const_iterator file = fslist.begin(); file != fslist.end(); ++file) { @@ -547,33 +645,17 @@ int ResourceManager::addAppropriateSources(const Common::FSList &fslist) { Common::String filename = file->getName(); filename.toLowercase(); - if (filename.contains("resource.map")) + // TODO: Load the SCI2.1+ maps (resmap.*) in concurrence with the volumes to + // get the proper volume numbers from the maps. + if (filename.contains("resource.map") || filename.contains("resmap.000")) { map = addExternalMap(file); - - if (filename.contains("resmap.0")) { - const char *dot = strrchr(file->getName().c_str(), '.'); - int number = atoi(dot + 1); - map = addExternalMap(file, number); + break; } - -#ifdef ENABLE_SCI32 - // SCI2.1 resource patches - if (filename.contains("resmap.pat")) - sci21PatchMap = addExternalMap(file, 100); - - if (filename.contains("ressci.pat")) - sci21PatchRes = file; -#endif } if (!map) return 0; -#ifdef ENABLE_SCI32 - if (sci21PatchMap && sci21PatchRes) - addSource(sci21PatchMap, kSourceVolume, sci21PatchRes, 100); -#endif - // Now find all the resource.0?? files for (Common::FSList::const_iterator file = fslist.begin(); file != fslist.end(); ++file) { if (file->isDirectory()) @@ -614,6 +696,36 @@ int ResourceManager::addInternalSources() { return 1; } +void ResourceManager::addNewGMPatch(const Common::String &gameId) { + Common::String gmPatchFile; + + if (gameId == "ecoquest") + gmPatchFile = "ECO1GM.PAT"; + else if (gameId == "hoyle3") + gmPatchFile = "HOY3GM.PAT"; + else if (gameId == "hoyle3") + gmPatchFile = "HOY3GM.PAT"; + else if (gameId == "lsl1sci") + gmPatchFile = "LL1_GM.PAT"; + else if (gameId == "lsl5") + gmPatchFile = "LL5_GM.PAT"; + else if (gameId == "longbow") + gmPatchFile = "ROBNGM.PAT"; + else if (gameId == "sq1sci") + gmPatchFile = "SQ1_GM.PAT"; + else if (gameId == "sq4") + gmPatchFile = "SQ4_GM.PAT"; + else if (gameId == "fairytales") + gmPatchFile = "TALEGM.PAT"; + + if (!gmPatchFile.empty() && Common::File::exists(gmPatchFile)) { + ResourceSource *psrcPatch = new ResourceSource; + psrcPatch->source_type = kSourcePatch; + psrcPatch->location_name = gmPatchFile; + processPatch(psrcPatch, kResourceTypePatch, 4); + } +} + void ResourceManager::scanNewSources() { for (Common::List<ResourceSource *>::iterator it = _sources.begin(); it != _sources.end(); ++it) { ResourceSource *source = *it; @@ -878,6 +990,7 @@ const char *ResourceManager::versionDescription(ResVersion version) const { ResourceManager::ResVersion ResourceManager::detectMapVersion() { Common::SeekableReadStream *fileStream = 0; + Common::File *file = 0; byte buff[6]; ResourceSource *rsrc= 0; @@ -888,7 +1001,7 @@ ResourceManager::ResVersion ResourceManager::detectMapVersion() { if (rsrc->resourceFile) { fileStream = rsrc->resourceFile->createReadStream(); } else { - Common::File *file = new Common::File(); + file = new Common::File(); file->open(rsrc->location_name); if (file->isOpen()) fileStream = file; @@ -968,6 +1081,7 @@ ResourceManager::ResVersion ResourceManager::detectMapVersion() { ResourceManager::ResVersion ResourceManager::detectVolVersion() { Common::SeekableReadStream *fileStream = 0; + Common::File *file = 0; ResourceSource *rsrc; for (Common::List<ResourceSource *>::iterator it = _sources.begin(); it != _sources.end(); ++it) { @@ -977,7 +1091,7 @@ ResourceManager::ResVersion ResourceManager::detectVolVersion() { if (rsrc->resourceFile) { fileStream = rsrc->resourceFile->createReadStream(); } else { - Common::File *file = new Common::File(); + file = new Common::File(); file->open(rsrc->location_name); if (file->isOpen()) fileStream = file; @@ -1067,7 +1181,7 @@ ResourceManager::ResVersion ResourceManager::detectVolVersion() { // version-agnostic patch application void ResourceManager::processPatch(ResourceSource *source, ResourceType restype, int resnumber) { - Common::SeekableReadStream *fileStream = 0; + Common::File file; Resource *newrsc; ResourceId resId = ResourceId(restype, resnumber); byte patchtype, patch_data_offset; @@ -1075,27 +1189,18 @@ void ResourceManager::processPatch(ResourceSource *source, ResourceType restype, if (resnumber == -1) return; - - if (source->resourceFile) { - fileStream = source->resourceFile->createReadStream(); - } else { - Common::File *file = new Common::File(); - if (!file->open(source->location_name)) { - warning("ResourceManager::processPatch(): failed to open %s", source->location_name.c_str()); - return; - } - fileStream = file; + if (!file.open(source->location_name)) { + warning("ResourceManager::processPatch(): failed to open %s", source->location_name.c_str()); + return; } - fsize = fileStream->size(); + fsize = file.size(); if (fsize < 3) { debug("Patching %s failed - file too small", source->location_name.c_str()); return; } - patchtype = fileStream->readByte() & 0x7F; - patch_data_offset = fileStream->readByte(); - - delete fileStream; + patchtype = file.readByte() & 0x7F; + patch_data_offset = file.readByte(); if (patchtype != restype) { debug("Patching %s failed - resource type mismatch", source->location_name.c_str()); @@ -1110,12 +1215,8 @@ void ResourceManager::processPatch(ResourceSource *source, ResourceType restype, case 1: patch_data_offset = 2; break; - case 4: - patch_data_offset = 8; - break; default: - warning("Resource patch unsupported special case %X", patch_data_offset & 0x7F); - return; + warning("Resource patch unsupported special case %X", patch_data_offset); } } @@ -1142,17 +1243,9 @@ void ResourceManager::processPatch(ResourceSource *source, ResourceType restype, void ResourceManager::readResourcePatches(ResourceSource *source) { - // Note: since some SCI1 games(KQ5 floppy, SQ4) might use SCI0 naming scheme for patch files - // this function tries to read patch file with any supported naming scheme, - // regardless of s_sciVersion value - - // Note that audio36 and sync36 use a different naming scheme, because they cannot be described - // with a single resource number, but are a result of a <number, noun, verb, cond, seq> tuple. - // Please don't be confused with the normal audio patches (*.aud) and normal sync patches (*.syn). - // audio36 patches can be seen for example in the AUD folder of GK1CD, and are like this file: - // @0CS0M00.0X1. GK1CD is the first game where these have been observed. The actual audio36 and - // sync36 resources exist in SCI1.1 as well, but the first game where external patch files for - // them have been found is GK1CD +// Note: since some SCI1 games(KQ5 floppy, SQ4) might use SCI0 naming scheme for patch files +// this function tries to read patch file with any supported naming scheme, +// regardless of s_sciVersion value Common::String mask, name; Common::ArchiveMemberList files; @@ -1162,7 +1255,6 @@ void ResourceManager::readResourcePatches(ResourceSource *source) { for (int i = kResourceTypeView; i <= kResourceTypeRobot; ++i) { // TODO: add support for audio36 and sync36 files - // Such patches were introduced in SCI2, and didn't exist in SCI0-SCI1.1 if (i == kResourceTypeAudio36 || i == kResourceTypeSync36) continue; @@ -1197,13 +1289,55 @@ void ResourceManager::readResourcePatches(ResourceSource *source) { psrcPatch = new ResourceSource; psrcPatch->source_type = kSourcePatch; psrcPatch->location_name = name; - psrcPatch->resourceFile = 0; processPatch(psrcPatch, (ResourceType)i, number); } } } } +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 (isdigit(name[0])) { + int number = atoi(name.c_str()); + ResourceSource *psrcPatch = new ResourceSource; + psrcPatch->source_type = kSourceWave; + psrcPatch->location_name = name; + psrcPatch->volume_number = 0; + psrcPatch->audioCompressionType = 0; + + ResourceId resId = ResourceId(kResourceTypeAudio, number); + + Resource *newrsc = NULL; + + // Prepare destination, if neccessary + if (_resMap.contains(resId) == false) { + newrsc = new Resource; + _resMap.setVal(resId, newrsc); + } else + newrsc = _resMap.getVal(resId); + + // Get the size of the file + Common::SeekableReadStream *stream = (*x)->createReadStream(); + uint32 fileSize = stream->size(); + delete stream; + + // Overwrite everything, because we're patching + newrsc->_id = resId; + newrsc->_status = kResStatusNoMalloc; + newrsc->_source = psrcPatch; + newrsc->size = fileSize; + newrsc->_headerSize = 0; + debugC(1, kDebugLevelResMan, "Patching %s - OK", psrcPatch->location_name.c_str()); + } + } +} + int ResourceManager::readResourceMapSCI0(ResourceSource *map) { Common::File file; Resource *res; @@ -1314,7 +1448,7 @@ int ResourceManager::readResourceMapSCI1(ResourceSource *map) { res->_id = resId; // NOTE: We add the map's volume number here to the specified volume number - // for SCI2.1 and SCI3 maps that are not resmap.000. The resmap.* files' numbers + // for SCI2.1 and SCI3 maps that are not RESMAP.000. The RESMAP.* files' numbers // need to be used in concurrence with the volume specified in the map to get // the actual resource file. res->_source = getVolume(map, volume_nr + map->volume_number); @@ -1342,11 +1476,7 @@ struct { { MKID_BE('PAL '), kResourceTypePalette }, { MKID_BE('snd '), kResourceTypeAudio }, { MKID_BE('MSG '), kResourceTypeMessage }, - { MKID_BE('HEP '), kResourceTypeHeap }, - { MKID_BE('IBIN'), kResourceTypeMacIconBarPictN }, - { MKID_BE('IBIS'), kResourceTypeMacIconBarPictS }, - { MKID_BE('PICT'), kResourceTypeMacPict }, - { MKID_BE('SYN '), kResourceTypeSync } + { MKID_BE('HEP '), kResourceTypeHeap } }; static uint32 resTypeToMacTag(ResourceType type) { @@ -1379,16 +1509,6 @@ int ResourceManager::readMacResourceFork(ResourceSource *source) { Common::MacResIDArray idArray = source->macResMan.getResIDArray(tagArray[i]); for (uint32 j = 0; j < idArray.size(); j++) { - // Get the size of the file - Common::SeekableReadStream *stream = source->macResMan.getResource(tagArray[i], idArray[j]); - - // Some IBIS resources have a size of 0, so we skip them - if (!stream) - continue; - - uint32 fileSize = stream->size(); - delete stream; - ResourceId resId = ResourceId(type, idArray[j]); Resource *newrsc = NULL; @@ -1400,6 +1520,11 @@ int ResourceManager::readMacResourceFork(ResourceSource *source) { } else newrsc = _resMap.getVal(resId); + // Get the size of the file + Common::SeekableReadStream *stream = source->macResMan.getResource(tagArray[i], idArray[j]); + uint32 fileSize = stream->size(); + delete stream; + // Overwrite everything newrsc->_id = resId; newrsc->_status = kResStatusNoMalloc; @@ -1424,6 +1549,261 @@ void ResourceManager::addResource(ResourceId resId, ResourceSource *src, uint32 } } +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->source_type == 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) + +// 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(ResourceSource *map) { + bool isEarly = true; + uint32 offset = 0; + Resource *mapRes = findResource(ResourceId(kResourceTypeMap, map->volume_number), false); + + if (!mapRes) { + warning("Failed to open %i.MAP", map->volume_number); + return SCI_ERROR_RESMAP_NOT_FOUND; + } + + ResourceSource *src = getVolume(map, 0); + + if (!src) + return SCI_ERROR_NO_RESOURCE_FILES_FOUND; + + byte *ptr = mapRes->data; + + if (map->volume_number == 65535) { + // Heuristic to detect late SCI1.1 map format + if ((mapRes->size >= 6) && (ptr[mapRes->size - 6] != 0xff)) + isEarly = false; + + while (ptr < mapRes->data + mapRes->size) { + uint16 n = READ_LE_UINT16(ptr); + ptr += 2; + + if (n == 0xffff) + break; + + if (isEarly) { + offset = READ_LE_UINT32(ptr); + ptr += 4; + } else { + offset += READ_LE_UINT24(ptr); + ptr += 3; + } + + addResource(ResourceId(kResourceTypeAudio, n), src, offset); + } + } else { + // Heuristic to detect late SCI1.1 map format + if ((mapRes->size >= 11) && (ptr[mapRes->size - 11] == 0xff)) + isEarly = false; + + if (!isEarly) { + offset = READ_LE_UINT32(ptr); + ptr += 4; + } + + while (ptr < mapRes->data + mapRes->size) { + uint32 n = READ_BE_UINT32(ptr); + int syncSize = 0; + ptr += 4; + + if (n == 0xffffffff) + break; + + if (isEarly) { + offset = READ_LE_UINT32(ptr); + ptr += 4; + } else { + offset += READ_LE_UINT24(ptr); + ptr += 3; + } + + if (isEarly || (n & 0x80)) { + syncSize = READ_LE_UINT16(ptr); + ptr += 2; + + if (syncSize > 0) + addResource(ResourceId(kResourceTypeSync36, map->volume_number, n & 0xffffff3f), src, offset, syncSize); + } + + if (n & 0x40) { + syncSize += READ_LE_UINT16(ptr); + ptr += 2; + } + + addResource(ResourceId(kResourceTypeAudio36, map->volume_number, n & 0xffffff3f), src, offset + syncSize); + } + } + + 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->location_name)) + return SCI_ERROR_RESMAP_NOT_FOUND; + + bool oldFormat = (file.readUint16LE() >> 11) == kResourceTypeAudio; + file.seek(0); + + while (1) { + uint16 n = file.readUint16LE(); + uint32 offset = file.readUint32LE(); + uint32 size = file.readUint32LE(); + + if (file.eos() || file.err()) { + warning("Error while reading %s", map->location_name.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 = getVolume(map, volume_nr); + + if (src) { + if (unload) + removeAudioResource(ResourceId(kResourceTypeAudio, n)); + else + addResource(ResourceId(kResourceTypeAudio, n), src, offset, size); + } else { + warning("Failed to find audio volume %i", volume_nr); + } + } + + return 0; +} + +void ResourceManager::setAudioLanguage(int language) { + if (_audioMapSCI1) { + if (_audioMapSCI1->volume_number == language) { + // This language is already loaded + return; + } + + // We already have a map loaded, so we unload it first + readAudioMapSCI1(_audioMapSCI1, 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->associated_map == _audioMapSCI1) { + it = _sources.erase(it); + delete src; + } else { + ++it; + } + } + + // Remove the map itself from the source list + _sources.remove(_audioMapSCI1); + delete _audioMapSCI1; + + _audioMapSCI1 = NULL; + } + + char filename[9]; + snprintf(filename, 9, "AUDIO%03d", language); + + Common::String fullname = Common::String(filename) + ".MAP"; + if (!Common::File::exists(fullname)) { + warning("No audio map found for language %i", language); + return; + } + + _audioMapSCI1 = addSource(NULL, kSourceExtAudioMap, fullname.c_str(), language); + + // Search for audio volumes for this language and add them to the source list + Common::ArchiveMemberList files; + SearchMan.listMatchingMembers(files, Common::String(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(_audioMapSCI1, kSourceAudioVolume, name.c_str(), number); + } + + scanNewSources(); +} + +int ResourceManager::getAudioLanguage() const { + return (_audioMapSCI1 ? _audioMapSCI1->volume_number : 0); +} + int ResourceManager::readResourceInfo(Resource *res, Common::SeekableReadStream *file, uint32&szPacked, ResourceCompression &compression) { // SCI0 volume format: {wResId wPacked+4 wUnpacked wCompression} = 8 bytes @@ -1949,98 +2329,248 @@ bool ResourceManager::hasSci1Voc900() { return offset == res->size; } -// Same function as Script::findBlock(). Slight code -// duplication here, but this has been done to keep the resource -// manager independent from the rest of the engine -static byte *findSci0ExportsBlock(byte *buffer) { - byte *buf = buffer; - bool oldScriptHeader = (getSciVersion() == SCI_VERSION_0_EARLY); +SoundResource::SoundResource(uint32 resNumber, ResourceManager *resMan, SciVersion soundVersion) : _resMan(resMan), _soundVersion(soundVersion) { + Resource *resource = _resMan->findResource(ResourceId(kResourceTypeSound, resNumber), true); + int trackNr, channelNr; + if (!resource) + return; - if (oldScriptHeader) - buf += 2; + _innerResource = resource; - do { - int seekerType = READ_LE_UINT16(buf); + byte *data, *data2; + byte *dataEnd; + Channel *channel, *sampleChannel; - if (seekerType == 0) - break; - if (seekerType == 7) // exports - return buf; + switch (_soundVersion) { + case SCI_VERSION_0_EARLY: + case 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->data[0] == 2) + _tracks->channelCount++; + _tracks->channels = new Channel[_tracks->channelCount]; + memset(_tracks->channels, 0, sizeof(Channel) * _tracks->channelCount); + channel = &_tracks->channels[0]; + if (_soundVersion == SCI_VERSION_0_EARLY) { + channel->data = resource->data + 0x11; + channel->size = resource->size - 0x11; + } else { + channel->data = resource->data + 0x21; + channel->size = resource->size - 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 + data = channel->data; + dataEnd = channel->data + channel->size; + while ((data < dataEnd) && (*data != 0xfc)) + data++; + // Skip any following 0xFCs as well + while ((data < dataEnd) && (*data == 0xfc)) + data++; + // Now adjust channels accordingly + sampleChannel->data = data; + sampleChannel->size = channel->size - (data - channel->data); + channel->size = data - channel->data; + // 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 = READ_LE_UINT16(sampleChannel->data + 14); + _tracks->digitalSampleSize = READ_LE_UINT16(sampleChannel->data + 32); + _tracks->digitalSampleStart = 0; + _tracks->digitalSampleEnd = 0; + sampleChannel->data += 44; // Skip over header + sampleChannel->size -= 44; + } + break; - int seekerSize = READ_LE_UINT16(buf + 2); - assert(seekerSize > 0); - buf += seekerSize; - } while (1); + case SCI_VERSION_1_EARLY: + case SCI_VERSION_1_LATE: + data = resource->data; + // Count # of tracks + _trackCount = 0; + while ((*data++) != 0xFF) { + _trackCount++; + while (*data != 0xFF) + data += 6; + data++; + } + _tracks = new Track[_trackCount]; + data = resource->data; + 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 + data2 = data; + _tracks[trackNr].channelCount = 0; + while (*data2 != 0xFF) { + data2 += 6; + _tracks[trackNr].channelCount++; + } + _tracks[trackNr].channels = new Channel[_tracks[trackNr].channelCount]; + _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 + for (channelNr = 0; channelNr < _tracks[trackNr].channelCount; channelNr++) { + channel = &_tracks[trackNr].channels[channelNr]; + channel->prio = READ_LE_UINT16(data); + channel->data = resource->data + READ_LE_UINT16(data + 2) + 2; + channel->size = READ_LE_UINT16(data + 4) - 2; // Not counting channel header + channel->number = *(channel->data - 2); + channel->poly = *(channel->data - 1); + channel->time = channel->prev = 0; + if (channel->number == 0xFE) { // Digital channel + _tracks[trackNr].digitalChannelNr = channelNr; + _tracks[trackNr].digitalSampleRate = READ_LE_UINT16(channel->data); + _tracks[trackNr].digitalSampleSize = READ_LE_UINT16(channel->data + 2); + _tracks[trackNr].digitalSampleStart = READ_LE_UINT16(channel->data + 4); + _tracks[trackNr].digitalSampleEnd = READ_LE_UINT16(channel->data + 6); + channel->data += 8; // Skip over header + channel->size -= 8; + } + data += 6; + } + } else { + // Skip over digital track + data += 6; + } + data++; // Skipping 0xFF that closes channels list + } + break; - return NULL; + default: + error("SoundResource: SCI version %d is unsupported", _soundVersion); + } } -reg_t ResourceManager::findGameObject(bool addSci11ScriptOffset) { - Resource *script = findResource(ResourceId(kResourceTypeScript, 0), false); - - if (!script) - return NULL_REG; +SoundResource::~SoundResource() { + for (int trackNr = 0; trackNr < _trackCount; trackNr++) + delete[] _tracks[trackNr].channels; + delete[] _tracks; - byte *offsetPtr = 0; + _resMan->unlockResource(_innerResource); +} - if (getSciVersion() < SCI_VERSION_1_1) { - byte *buf = (getSciVersion() == SCI_VERSION_0_EARLY) ? script->data + 2 : script->data; +#if 0 +SoundResource::Track* SoundResource::getTrackByNumber(uint16 number) { + if (_soundVersion <= SCI_VERSION_0_LATE) + return &_tracks[0]; - // Check if the first block is the exports block (in most cases, it is) - bool exportsIsFirst = (READ_LE_UINT16(buf + 4) == 7); - if (exportsIsFirst) { - offsetPtr = buf + 4 + 2; - } else { - offsetPtr = findSci0ExportsBlock(script->data); - if (!offsetPtr) - error("Unable to find exports block from script 0"); - offsetPtr += 4 + 2; - } - } else { - offsetPtr = script->data + 4 + 2 + 2; - } - - int16 offset = !isSci11Mac() ? READ_LE_UINT16(offsetPtr) : READ_BE_UINT16(offsetPtr); + if (/*number >= 0 &&*/number < _trackCount) + return &_tracks[number]; + return NULL; +} +#endif - // In SCI1.1 and newer, the heap is appended at the end of the script, - // so adjust the offset accordingly - if (getSciVersion() >= SCI_VERSION_1_1 && addSci11ScriptOffset) { - offset += script->size; +SoundResource::Track *SoundResource::getTrackByType(byte type) { + if (_soundVersion <= SCI_VERSION_0_LATE) + return &_tracks[0]; - // Ensure that the start of the heap is word-aligned - same as in Script::init() - if (script->size & 2) - offset++; + for (int trackNr = 0; trackNr < _trackCount; trackNr++) { + if (_tracks[trackNr].type == type) + return &_tracks[trackNr]; } + return NULL; +} - return make_reg(1, offset); +SoundResource::Track *SoundResource::getDigitalTrack() { + for (int trackNr = 0; trackNr < _trackCount; trackNr++) { + if (_tracks[trackNr].digitalChannelNr != -1) + return &_tracks[trackNr]; + } + return NULL; } -Common::String ResourceManager::findSierraGameId() { - // In SCI0-SCI1, the heap is embedded in the script. In SCI1.1+, it's separated - Resource *heap = 0; - int nameSelector = 3; +// Gets the filter mask for SCI0 sound resources +int SoundResource::getChannelFilterMask(int hardwareMask, bool wantsRhythm) { + byte *data = _innerResource->data; + int channelMask = 0; - if (getSciVersion() < SCI_VERSION_1_1) { - heap = findResource(ResourceId(kResourceTypeScript, 0), false); - } else { - heap = findResource(ResourceId(kResourceTypeHeap, 0), false); - nameSelector += 5; + 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; + } } - if (!heap) - return ""; + return channelMask; +} - int16 gameObjectOffset = findGameObject(false).offset; +byte SoundResource::getInitialVoiceCount(byte channel) { + byte *data = _innerResource->data; - if (!gameObjectOffset) - return ""; + if (_soundVersion > SCI_VERSION_0_LATE) + return 0; // TODO - // Seek to the name selector of the first export - byte *seeker = heap->data + READ_UINT16(heap->data + gameObjectOffset + nameSelector * 2); - Common::String sierraId; - sierraId += (const char *)seeker; + data++; // Skip over digital sample flag - return sierraId; + if (_soundVersion == SCI_VERSION_0_EARLY) + return data[channel] >> 4; + else + return data[channel * 2]; } } // End of namespace Sci |