aboutsummaryrefslogtreecommitdiff
path: root/engines/sci/resource.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'engines/sci/resource.cpp')
-rw-r--r--engines/sci/resource.cpp894
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