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.cpp248
1 files changed, 181 insertions, 67 deletions
diff --git a/engines/sci/resource.cpp b/engines/sci/resource.cpp
index 10740a8b7b..48278e35a7 100644
--- a/engines/sci/resource.cpp
+++ b/engines/sci/resource.cpp
@@ -26,6 +26,9 @@
#include "common/fs.h"
#include "common/macresman.h"
#include "common/textconsole.h"
+#ifdef ENABLE_SCI32
+#include "common/memstream.h"
+#endif
#include "sci/resource.h"
#include "sci/resource_intern.h"
@@ -82,8 +85,12 @@ const char *getSciVersionDesc(SciVersion version) {
return "SCI1.1";
case SCI_VERSION_2:
return "SCI2";
- case SCI_VERSION_2_1:
- return "SCI2.1";
+ case SCI_VERSION_2_1_EARLY:
+ return "Early SCI2.1";
+ case SCI_VERSION_2_1_MIDDLE:
+ return "Middle SCI2.1";
+ case SCI_VERSION_2_1_LATE:
+ return "Late SCI2.1";
case SCI_VERSION_3:
return "SCI3";
default:
@@ -217,6 +224,12 @@ void Resource::writeToStream(Common::WriteStream *stream) const {
stream->write(data, size);
}
+#ifdef ENABLE_SCI32
+Common::SeekableReadStream *Resource::makeStream() const {
+ return new Common::MemoryReadStream(data, size, DisposeAfterUse::NO);
+}
+#endif
+
uint32 Resource::getAudioCompressionType() const {
return _source->getAudioCompressionType();
}
@@ -225,7 +238,6 @@ uint32 AudioVolumeResourceSource::getAudioCompressionType() const {
return _audioCompressionType;
}
-
ResourceSource::ResourceSource(ResSourceType type, const Common::String &name, int volNum, const Common::FSNode *resFile)
: _sourceType(type), _name(name), _volumeNumber(volNum), _resourceFile(resFile) {
_scanned = false;
@@ -303,7 +315,7 @@ bool Resource::loadPatch(Common::SeekableReadStream *file) {
error("Can't allocate %d bytes needed for loading %s", res->size + res->_headerSize, res->_id.toString().c_str());
}
- unsigned int really_read;
+ uint32 really_read;
if (res->_headerSize > 0) {
really_read = file->read(res->_header, res->_headerSize);
if (really_read != res->_headerSize)
@@ -561,12 +573,11 @@ Resource *ResourceManager::testResource(ResourceId id) {
}
int ResourceManager::addAppropriateSources() {
- Common::ArchiveMemberList files;
-
if (Common::File::exists("resource.map")) {
// SCI0-SCI2 file naming scheme
ResourceSource *map = addExternalMap("resource.map");
+ Common::ArchiveMemberList files;
SearchMan.listMatchingMembers(files, "resource.0??");
for (Common::ArchiveMemberList::const_iterator x = files.begin(); x != files.end(); ++x) {
@@ -583,20 +594,20 @@ int ResourceManager::addAppropriateSources() {
#endif
} else if (Common::MacResManager::exists("Data1")) {
// Mac SCI1.1+ file naming scheme
- SearchMan.listMatchingMembers(files, "Data?*");
+ Common::StringArray files;
+ Common::MacResManager::listFiles(files, "Data?");
- for (Common::ArchiveMemberList::const_iterator x = files.begin(); x != files.end(); ++x) {
- Common::String filename = (*x)->getName();
- addSource(new MacResourceForkResourceSource(filename, atoi(filename.c_str() + 4)));
+ for (Common::StringArray::const_iterator x = files.begin(); x != files.end(); ++x) {
+ addSource(new MacResourceForkResourceSource(*x, atoi(x->c_str() + 4)));
}
#ifdef ENABLE_SCI32
// There can also be a "Patches" resource fork with patches
- if (Common::File::exists("Patches"))
+ if (Common::MacResManager::exists("Patches"))
addSource(new MacResourceForkResourceSource("Patches", 100));
} else {
// SCI2.1-SCI3 file naming scheme
- Common::ArchiveMemberList mapFiles;
+ Common::ArchiveMemberList mapFiles, files;
SearchMan.listMatchingMembers(mapFiles, "resmap.0??");
SearchMan.listMatchingMembers(files, "ressci.0??");
@@ -861,6 +872,7 @@ ResourceManager::ResourceManager() {
}
void ResourceManager::init() {
+ _maxMemoryLRU = 256 * 1024; // 256KiB
_memoryLocked = 0;
_memoryLRU = 0;
_LRU.clear();
@@ -914,6 +926,14 @@ void ResourceManager::init() {
debugC(1, kDebugLevelResMan, "resMan: Detected %s", getSciVersionDesc(getSciVersion()));
+ // Resources in SCI32 games are significantly larger than SCI16
+ // games and can cause immediate exhaustion of the LRU resource
+ // cache, leading to constant decompression of picture resources
+ // and making the renderer very slow.
+ if (getSciVersion() >= SCI_VERSION_2) {
+ _maxMemoryLRU = 2048 * 1024; // 2MiB
+ }
+
switch (_viewType) {
case kViewEga:
debugC(1, kDebugLevelResMan, "resMan: Detected EGA graphic resources");
@@ -931,35 +951,14 @@ void ResourceManager::init() {
debugC(1, kDebugLevelResMan, "resMan: Detected SCI1.1 VGA graphic resources");
break;
default:
-#ifdef ENABLE_SCI32
- error("resMan: Couldn't determine view type");
-#else
- if (getSciVersion() >= SCI_VERSION_2) {
- // SCI support isn't built in, thus the view type won't be determined for
- // SCI2+ games. This will be handled further up, so throw no error here
- } else {
- error("resMan: Couldn't determine view type");
- }
-#endif
+ // Throw a warning, but do not error out here, because this is called from the
+ // fallback detector, and the user could be pointing to a folder with a non-SCI
+ // game, but with SCI-like file names (e.g. Pinball Creep)
+ warning("resMan: Couldn't determine view type");
+ break;
}
}
-void ResourceManager::initForDetection() {
- assert(!g_sci);
-
- _memoryLocked = 0;
- _memoryLRU = 0;
- _LRU.clear();
- _resMap.clear();
- _audioMapSCI1 = NULL;
-
- _mapVersion = detectMapVersion();
- _volVersion = detectVolVersion();
-
- scanNewSources();
- detectSciVersion();
-}
-
ResourceManager::~ResourceManager() {
// freeing resources
ResourceMap::iterator itr = _resMap.begin();
@@ -994,9 +993,9 @@ void ResourceManager::addToLRU(Resource *res) {
_LRU.push_front(res);
_memoryLRU += res->size;
#if SCI_VERBOSE_RESMAN
- debug("Adding %s.%03d (%d bytes) to lru control: %d bytes total",
- getResourceTypeName(res->type), res->number, res->size,
- mgr->_memoryLRU);
+ debug("Adding %s (%d bytes) to lru control: %d bytes total",
+ res->_id.toString().c_str(), res->size,
+ _memoryLRU);
#endif
res->_status = kResStatusEnqueued;
}
@@ -1019,13 +1018,13 @@ void ResourceManager::printLRU() {
}
void ResourceManager::freeOldResources() {
- while (MAX_MEMORY < _memoryLRU) {
+ while (_maxMemoryLRU < _memoryLRU) {
assert(!_LRU.empty());
Resource *goner = *_LRU.reverse_begin();
removeFromLRU(goner);
goner->unalloc();
#ifdef SCI_VERBOSE_RESMAN
- debug("resMan-debug: LRU: Freeing %s.%03d (%d bytes)", getResourceTypeName(goner->type), goner->number, goner->size);
+ debug("resMan-debug: LRU: Freeing %s (%d bytes)", goner->_id.toString().c_str(), goner->size);
#endif
}
}
@@ -1052,7 +1051,13 @@ Resource *ResourceManager::findResource(ResourceId id, bool lock) {
if (retval->_status == kResStatusNoMalloc)
loadResource(retval);
else if (retval->_status == kResStatusEnqueued)
+ // The resource is removed from its current position
+ // in the LRU list because it has been requested
+ // again. Below, it will either be locked, or it
+ // will be added back to the LRU list at the 'most
+ // recent' position.
removeFromLRU(retval);
+
// Unless an error occurred, the resource is now either
// locked or allocated, but never queued or freed.
@@ -1361,6 +1366,8 @@ void ResourceManager::processPatch(ResourceSource *source, ResourceType resource
Common::File *file = new Common::File();
if (!file->open(source->getLocationName())) {
warning("ResourceManager::processPatch(): failed to open %s", source->getLocationName().c_str());
+ delete source;
+ delete file;
return;
}
fileStream = file;
@@ -1369,6 +1376,8 @@ void ResourceManager::processPatch(ResourceSource *source, ResourceType resource
int fsize = fileStream->size();
if (fsize < 3) {
debug("Patching %s failed - file too small", source->getLocationName().c_str());
+ delete source;
+ delete fileStream;
return;
}
@@ -1379,6 +1388,7 @@ void ResourceManager::processPatch(ResourceSource *source, ResourceType resource
if (patchType != checkForType) {
debug("Patching %s failed - resource type mismatch", source->getLocationName().c_str());
+ delete source;
return;
}
@@ -1403,6 +1413,7 @@ void ResourceManager::processPatch(ResourceSource *source, ResourceType resource
if (patchDataOffset + 2 >= fsize) {
debug("Patching %s failed - patch starting at offset %d can't be in file of size %d",
source->getLocationName().c_str(), patchDataOffset + 2, fsize);
+ delete source;
return;
}
@@ -1450,13 +1461,18 @@ void ResourceManager::readResourcePatchesBase36() {
files.clear();
// audio36 resources start with a @, A, or B
- // sync36 resources start with a #
+ // sync36 resources start with a #, S, or T
if (i == kResourceTypeAudio36) {
SearchMan.listMatchingMembers(files, "@???????.???");
SearchMan.listMatchingMembers(files, "A???????.???");
SearchMan.listMatchingMembers(files, "B???????.???");
- } else
+ } else {
SearchMan.listMatchingMembers(files, "#???????.???");
+#ifdef ENABLE_SCI32
+ SearchMan.listMatchingMembers(files, "S???????.???");
+ SearchMan.listMatchingMembers(files, "T???????.???");
+#endif
+ }
for (Common::ArchiveMemberList::const_iterator x = files.begin(); x != files.end(); ++x) {
name = (*x)->getName();
@@ -2121,6 +2137,79 @@ ViewType ResourceManager::detectViewType() {
return kViewUnknown;
}
+// to detect selector "wordFail" in LE vocab resource
+static const byte detectSci21EarlySignature[] = {
+ 10, // size of signature
+ 0x08, 0x00, 'w', 'o', 'r', 'd', 'F', 'a', 'i', 'l'
+};
+
+// to detect selector "wordFail" in BE vocab resource (SCI2.1 Early)
+static const byte detectSci21EarlyBESignature[] = {
+ 10, // size of signature
+ 0x00, 0x08, 'w', 'o', 'r', 'd', 'F', 'a', 'i', 'l'
+};
+
+// to detect new kString calling to detect SCI2.1 Late
+static const byte detectSci21NewStringSignature[] = {
+ 8, // size of signature
+ 0x78, // push1
+ 0x78, // push1
+ 0x39, 0x09, // pushi 09
+ 0x59, 0x01, // rest 01
+ 0x43, 0x5c, // callk String
+};
+
+bool ResourceManager::checkResourceDataForSignature(Resource *resource, const byte *signature) {
+ byte signatureSize = *signature;
+ const byte *resourceData = resource->data;
+
+ signature++; // skip over size byte
+ if (signatureSize < 4)
+ error("resource signature is too small, internal error");
+ if (signatureSize > resource->size)
+ return false;
+
+ const uint32 signatureDWord = *((const uint32 *)signature);
+ signature += 4; signatureSize -= 4;
+
+ const uint32 searchLimit = resource->size - signatureSize + 1;
+ uint32 DWordOffset = 0;
+ while (DWordOffset < searchLimit) {
+ if (signatureDWord == READ_UINT32(resourceData + DWordOffset)) {
+ // magic DWORD found, check if the rest matches as well
+ uint32 offset = DWordOffset + 4;
+ uint32 signaturePos = 0;
+ while (signaturePos < signatureSize) {
+ if (resourceData[offset] != signature[signaturePos])
+ break;
+ offset++;
+ signaturePos++;
+ }
+ if (signaturePos >= signatureSize)
+ return true; // signature found
+ }
+ DWordOffset++;
+ }
+ return false;
+}
+
+bool ResourceManager::checkResourceForSignatures(ResourceType resourceType, uint16 resourceNr, const byte *signature1, const byte *signature2) {
+ Resource *resource = findResource(ResourceId(resourceType, resourceNr), false);
+
+ if (resource) {
+ // resource found and loaded, check for signatures
+ if (signature1) {
+ if (checkResourceDataForSignature(resource, signature1))
+ return true;
+ }
+ if (signature2) {
+ if (checkResourceDataForSignature(resource, signature2))
+ return true;
+ }
+ }
+ return false;
+}
+
void ResourceManager::detectSciVersion() {
// We use the view compression to set a preliminary s_sciVersion for the sake of getResourceInfo
// Pretend we have a SCI0 game
@@ -2180,31 +2269,52 @@ void ResourceManager::detectSciVersion() {
// no Mac SCI2 games. Yes, that means that GK1 Mac is SCI2.1 and not SCI2.
// TODO: Decide between SCI2.1 and SCI3
- if (res)
- s_sciVersion = SCI_VERSION_2_1;
- else
+ if (res) {
+ s_sciVersion = SCI_VERSION_2_1_EARLY; // we check for SCI2.1 specifics a bit later
+ } else {
s_sciVersion = SCI_VERSION_1_1;
- return;
+ return;
+ }
}
// Handle SCI32 versions here
- if (_volVersion >= kResVersionSci2) {
- Common::List<ResourceId> heaps = listResources(kResourceTypeHeap);
- bool hasHeapResources = !heaps.empty();
-
- // SCI2.1/3 and SCI1 Late resource maps are the same, except that
- // SCI1 Late resource maps have the resource types or'd with
- // 0x80. We differentiate between SCI2 and SCI2.1/3 based on that.
- if (_mapVersion == kResVersionSci1Late) {
- s_sciVersion = SCI_VERSION_2;
- return;
- } else if (hasHeapResources) {
- s_sciVersion = SCI_VERSION_2_1;
+ if (s_sciVersion != SCI_VERSION_2_1_EARLY) {
+ if (_volVersion >= kResVersionSci2) {
+ Common::List<ResourceId> heaps = listResources(kResourceTypeHeap);
+ bool hasHeapResources = !heaps.empty();
+
+ // SCI2.1/3 and SCI1 Late resource maps are the same, except that
+ // SCI1 Late resource maps have the resource types or'd with
+ // 0x80. We differentiate between SCI2 and SCI2.1/3 based on that.
+ if (_mapVersion == kResVersionSci1Late) {
+ s_sciVersion = SCI_VERSION_2;
+ return;
+ } else if (hasHeapResources) {
+ s_sciVersion = SCI_VERSION_2_1_EARLY; // exact SCI2.1 version is checked a bit later
+ } else {
+ s_sciVersion = SCI_VERSION_3;
+ return;
+ }
+ }
+ }
+
+ if (s_sciVersion == SCI_VERSION_2_1_EARLY) {
+ // we only know that it's SCI2.1, not which exact version it is
+
+ // check, if selector "wordFail" inside vocab 997 exists, if it does it's SCI2.1 Early
+ if ((checkResourceForSignatures(kResourceTypeVocab, 997, detectSci21EarlySignature, detectSci21EarlyBESignature))) {
+ // found -> it is SCI2.1 early
return;
- } else {
- s_sciVersion = SCI_VERSION_3;
+ }
+
+ s_sciVersion = SCI_VERSION_2_1_MIDDLE;
+ if (checkResourceForSignatures(kResourceTypeScript, 64918, detectSci21NewStringSignature, nullptr)) {
+ // new kString call detected, it's SCI2.1 late
+ // TODO: this call seems to be different on Mac
+ s_sciVersion = SCI_VERSION_2_1_LATE;
return;
}
+ return;
}
// Check for transitive SCI1/SCI1.1 games, like PQ1 here
@@ -2376,7 +2486,9 @@ bool ResourceManager::hasOldScriptHeader() {
Resource *res = findResource(ResourceId(kResourceTypeScript, 0), 0);
if (!res) {
- error("resMan: Failed to find script.000");
+ // Script 0 missing -> corrupted / non-SCI resource files.
+ // Don't error out here, because this might have been called
+ // from the fallback detector
return false;
}
@@ -2537,7 +2649,7 @@ reg_t ResourceManager::findGameObject(bool addSci11ScriptOffset) {
int16 offset = !isSci11Mac() ? READ_LE_UINT16(offsetPtr) : READ_BE_UINT16(offsetPtr);
return make_reg(1, offset);
- } else if (getSciVersion() >= SCI_VERSION_1_1 && getSciVersion() <= SCI_VERSION_2_1) {
+ } else if (getSciVersion() >= SCI_VERSION_1_1 && getSciVersion() <= SCI_VERSION_2_1_LATE) {
offsetPtr = script->data + 4 + 2 + 2;
// In SCI1.1 - SCI2.1, the heap is appended at the end of the script,
@@ -2565,7 +2677,7 @@ Common::String ResourceManager::findSierraGameId() {
if (getSciVersion() < SCI_VERSION_1_1) {
heap = findResource(ResourceId(kResourceTypeScript, 0), false);
- } else if (getSciVersion() >= SCI_VERSION_1_1 && getSciVersion() <= SCI_VERSION_2_1) {
+ } else if (getSciVersion() >= SCI_VERSION_1_1 && getSciVersion() <= SCI_VERSION_2_1_LATE) {
heap = findResource(ResourceId(kResourceTypeHeap, 0), false);
nameSelector += 5;
} else if (getSciVersion() == SCI_VERSION_3) {
@@ -2581,7 +2693,9 @@ Common::String ResourceManager::findSierraGameId() {
return "";
// Seek to the name selector of the first export
- byte *seeker = heap->data + READ_UINT16(heap->data + gameObjectOffset + nameSelector * 2);
+ byte *offsetPtr = heap->data + gameObjectOffset + nameSelector * 2;
+ uint16 offset = !isSci11Mac() ? READ_LE_UINT16(offsetPtr) : READ_BE_UINT16(offsetPtr);
+ byte *seeker = heap->data + offset;
Common::String sierraId;
sierraId += (const char *)seeker;