[Scummvm-git-logs] scummvm master -> 486b139d105e57a46bddade493c8f800bf9faab9

sev- noreply at scummvm.org
Thu Nov 2 17:20:51 UTC 2023


This automated email contains information about 9 new commits which have been
pushed to the 'scummvm' repo located at https://github.com/scummvm/scummvm .

Summary:
3829b41235 COMMON: Allow creating InstallShield archive with FSNode
22553cb924 AD: Implement scanning inside InstallShield archives
71e445db5a NANCY: Upgrade detection entries to new system
f77eca7c80 AD: Implement archive caching during detection
c598e60e5e AD: Clear archive cache when starting game
1a08bcc08f ARCHIVE: Allow creating InstallShieldV3 archives with FSNode
ad682243c4 AD: Support reading inside InstallShieldV3 archives
f7fe15ed42 AD: Add sanity checks for entries with archives
486b139d10 AD: Clean up detection prefix generation functions


Commit: 3829b412358d3f99fb9784c34c5f7a03aaf30bd3
    https://github.com/scummvm/scummvm/commit/3829b412358d3f99fb9784c34c5f7a03aaf30bd3
Author: Kaloyan Chehlarski (strahy at outlook.com)
Date: 2023-11-02T18:20:44+01:00

Commit Message:
COMMON: Allow creating InstallShield archive with FSNode

This is needed for cases where SearchMan is not applicable
(e.g. during detection)

Changed paths:
    common/compression/installshield_cab.cpp
    common/compression/installshield_cab.h


diff --git a/common/compression/installshield_cab.cpp b/common/compression/installshield_cab.cpp
index e9be8e430fc..fc7d7a59d36 100644
--- a/common/compression/installshield_cab.cpp
+++ b/common/compression/installshield_cab.cpp
@@ -50,6 +50,7 @@
 #include "common/substream.h"
 #include "common/ptr.h"
 #include "common/compression/deflate.h"
+#include "common/file.h"
 
 namespace Common {
 
@@ -86,7 +87,7 @@ class InstallShieldCabinet : public Archive {
 public:
 	InstallShieldCabinet();
 
-	bool open(const String &baseName);
+	bool open(const String *baseName, const FSNode *node);
 	void close();
 
 	// Archive API implementation
@@ -126,6 +127,7 @@ private:
 	FileMap _map;
 	String _baseName;
 	Common::Array<VolumeHeader> _volumeHeaders;
+    bool _useSearchMan;
 
 	static bool readVolumeHeader(SeekableReadStream *volumeStream, VolumeHeader &inVolumeHeader);
 
@@ -133,12 +135,24 @@ private:
 	String getVolumeName(uint volume) const;
 };
 
-InstallShieldCabinet::InstallShieldCabinet() : _version(0) {
+InstallShieldCabinet::InstallShieldCabinet() : _version(0), _useSearchMan(false) {
 }
 
-bool InstallShieldCabinet::open(const String &baseName) {
-	// Store the base name so we can generate file names
-	_baseName = baseName;
+bool InstallShieldCabinet::open(const String *baseName, const FSNode *node) {
+    // Store the base name so we can generate file names
+    if (baseName) {
+        _baseName = *baseName;
+        _useSearchMan = true;
+    } else if (node) {
+        _baseName = node->getPath();
+        _useSearchMan = false;
+    } else {
+        return false;
+    }
+
+    if (_baseName.hasSuffix(".cab") || _baseName.hasSuffix(".hdr")) {
+        _baseName.erase(_baseName.size() - 5, String::npos);
+    }
 
 	uint fileIndex = 0;
 	ScopedPtr<SeekableReadStream> file;
@@ -146,21 +160,38 @@ bool InstallShieldCabinet::open(const String &baseName) {
 	// First, open all the .cab files and read their headers
 	uint volume = 1;
 	for (;;) {
-		file.reset(SearchMan.createReadStreamForMember(getVolumeName(volume++)));
-		if (!file.get()) {
-			break;
-		}
+        if (_useSearchMan) {
+            file.reset(SearchMan.createReadStreamForMember(getVolumeName(volume++)));
+            if (!file.get()) {
+                break;
+            }
+        } else {
+            file.reset(new Common::File());
+            if (!((Common::File *)file.get())->open(Common::FSNode(getVolumeName(volume++)))) {
+                break;
+            }
+        }
 
 		_volumeHeaders.push_back(VolumeHeader());
 		readVolumeHeader(file.get(), _volumeHeaders.back());
 	}
 
 	// Try to open a header (.hdr) file to get the file list
-	file.reset(SearchMan.createReadStreamForMember(getHeaderName()));
-	if (!file) {
-		// No header file is present, file list is in first .cab file
-		file.reset(SearchMan.createReadStreamForMember(getVolumeName(1)));
-	}
+    if (_useSearchMan) {
+        file.reset(SearchMan.createReadStreamForMember(getHeaderName()));
+        if (!file) {
+            // No header file is present, file list is in first .cab file
+            file.reset(SearchMan.createReadStreamForMember(getVolumeName(1)));
+        }
+    } else {
+        file.reset(new Common::File());
+        if (!((Common::File *)file.get())->open(Common::FSNode(getHeaderName()))) {
+            // No header file is present, file list is in first .cab file
+            if (!((Common::File *)file.get())->open(Common::FSNode(getVolumeName(1)))) {
+                file.reset(nullptr);
+            }
+        }
+    }
 
 	if (!file) {
 		close();
@@ -320,7 +351,16 @@ SeekableReadStream *InstallShieldCabinet::createReadStreamForMember(const Path &
         return nullptr;
     }
 
-	ScopedPtr<SeekableReadStream> stream(SearchMan.createReadStreamForMember(getVolumeName((entry.volume))));
+	ScopedPtr<SeekableReadStream> stream;
+    if (_useSearchMan) {
+        stream.reset(SearchMan.createReadStreamForMember(getVolumeName((entry.volume))));
+    } else {
+        stream.reset(new Common::File());
+        if (!((Common::File *)stream.get())->open(Common::FSNode(getVolumeName((entry.volume))))) {
+            stream.reset(nullptr);
+        }
+    }
+
 	if (!stream) {
 		warning("Failed to open volume for file '%s'", name.c_str());
 		return nullptr;
@@ -340,7 +380,14 @@ SeekableReadStream *InstallShieldCabinet::createReadStreamForMember(const Path &
 
 		// Then, iterate through the next volumes until we've read all the data for the file
 		while (bytesRead < entry.compressedSize) {
-			stream.reset(SearchMan.createReadStreamForMember(getVolumeName((++volume))));
+            if (_useSearchMan) {
+                stream.reset(SearchMan.createReadStreamForMember(getVolumeName((++volume))));
+            } else {
+                if (!((Common::File *)stream.get())->open(Common::FSNode(getVolumeName((++volume))))) {
+                    stream.reset(nullptr);
+                }
+            }
+			
 			if (!stream.get()) {
 				warning("Failed to read split file %s", name.c_str());
 				free(src);
@@ -457,7 +504,17 @@ String InstallShieldCabinet::getVolumeName(uint volume) const {
 
 Archive *makeInstallShieldArchive(const String &baseName) {
 	InstallShieldCabinet *cab = new InstallShieldCabinet();
-	if (!cab->open(baseName)) {
+	if (!cab->open(&baseName, nullptr)) {
+		delete cab;
+		return nullptr;
+	}
+
+	return cab;
+}
+
+Archive *makeInstallShieldArchive(const FSNode &baseName) {
+	InstallShieldCabinet *cab = new InstallShieldCabinet();
+	if (!cab->open(nullptr, &baseName)) {
 		delete cab;
 		return nullptr;
 	}
diff --git a/common/compression/installshield_cab.h b/common/compression/installshield_cab.h
index 997484058f2..1115429f67d 100644
--- a/common/compression/installshield_cab.h
+++ b/common/compression/installshield_cab.h
@@ -26,6 +26,8 @@
 
 namespace Common {
 
+class FSNode;
+
 /**
  * @defgroup common_installshield InstallShield
  * @ingroup common
@@ -43,12 +45,22 @@ class SeekableReadStream;
  * This factory method creates an Archive instance corresponding to the content
  * of the single- or multi-file InstallShield cabinet with the given base name
  *
- * May return 0 in case of a failure.
+ * May return nullptr in case of a failure.
  * 
  * @param baseName The base filename, e.g. the "data" in "data1.cab"
  */
 Archive *makeInstallShieldArchive(const Common::String &baseName);
 
+/**
+ * This factory method creates an Archive instance corresponding to the content
+ * of the single- or multi-file InstallShield cabinet with the given base name
+ *
+ * May return nullptr in case of a failure.
+ * 
+ * @param baseName The base filename, e.g. the "data" in "data1.cab"
+ */
+Archive *makeInstallShieldArchive(const Common::FSNode &baseName);
+
 /** @} */
 
 } // End of namespace Common


Commit: 22553cb9241df8113993118f6eafaa709c95b836
    https://github.com/scummvm/scummvm/commit/22553cb9241df8113993118f6eafaa709c95b836
Author: Kaloyan Chehlarski (strahy at outlook.com)
Date: 2023-11-02T18:20:44+01:00

Commit Message:
AD: Implement scanning inside InstallShield archives

Added the A: prefix to detection md5 sums, which is used to
signify that the desired file is embedded within an archive.
Added support for similar prefixes in filenames, which should
look like the following example:

is:data1.cab:file.dat

Where "is" is the identifier for InstallShield archives,
"data1.cab" is the name of the archive container file, and
"file.dat" is the name of the file whose md5 sum will actually
be calculated.

Changed paths:
    engines/advancedDetector.cpp
    engines/game.cpp
    engines/game.h


diff --git a/engines/advancedDetector.cpp b/engines/advancedDetector.cpp
index b156bcc6fb8..bd7418d54ee 100644
--- a/engines/advancedDetector.cpp
+++ b/engines/advancedDetector.cpp
@@ -32,6 +32,7 @@
 #include "common/textconsole.h"
 #include "common/tokenizer.h"
 #include "common/translation.h"
+#include "common/compression/installshield_cab.h"
 #include "gui/EventRecorder.h"
 #include "gui/gui-manager.h"
 #include "gui/message.h"
@@ -532,6 +533,8 @@ static MD5Properties gameFileToMD5Props(const ADGameFileDescription *fileEntry,
 			case 't':
 				ret = (MD5Properties)(ret | kMD5Tail);
 				break;
+			case 'A':
+				ret = (MD5Properties)(ret | kMD5Archive);
 			}
 		return ret;
 	}
@@ -548,27 +551,52 @@ static MD5Properties gameFileToMD5Props(const ADGameFileDescription *fileEntry,
 }
 
 const char *md5PropToGameFile(MD5Properties flags) {
-	switch (flags & kMD5MacMask)
-	case kMD5MacDataFork: {
-		if (flags & kMD5Tail)
-			return "dt";
-		return "d";
-
-	case kMD5MacResOrDataFork:
-		if (flags & kMD5Tail)
-			return "mt";
-		return "m";
-
-	case kMD5MacResFork:
-		if (flags & kMD5Tail)
-			return "rt";
-		return "r";
-
-	case kMD5Tail:
-		return "t";
-
-	default:
-		return "";
+	if (flags & kMD5Archive) {
+		switch (flags & kMD5MacMask)
+		case kMD5MacDataFork: {
+			if (flags & kMD5Tail)
+				return "dtA";
+			return "dA";
+
+		case kMD5MacResOrDataFork:
+			if (flags & kMD5Tail)
+				return "mtA";
+			return "mA";
+
+		case kMD5MacResFork:
+			if (flags & kMD5Tail)
+				return "rtA";
+			return "rA";
+
+		case kMD5Tail:
+			return "tA";
+
+		default:
+			return "A";
+		}
+	} else {
+		switch (flags & kMD5MacMask)
+		case kMD5MacDataFork: {
+			if (flags & kMD5Tail)
+				return "dt";
+			return "d";
+
+		case kMD5MacResOrDataFork:
+			if (flags & kMD5Tail)
+				return "mt";
+			return "m";
+
+		case kMD5MacResFork:
+			if (flags & kMD5Tail)
+				return "rt";
+			return "r";
+
+		case kMD5Tail:
+			return "t";
+
+		default:
+			return "";
+		}
 	}
 }
 
@@ -638,21 +666,50 @@ static bool getFilePropertiesIntern(uint md5Bytes, const AdvancedMetaEngine::Fil
 			return false;
 	}
 
-	if (!allFiles.contains(fname))
-		return false;
+	Common::ScopedPtr<Common::SeekableReadStream> testFile;
+
+	if (md5prop & kMD5Archive) {
+		// The desired file is inside an archive
 
-	Common::File testFile;
+		// First, check the type of archive
+		Common::StringTokenizer tok(fname, ":");
+		Common::String archiveType = tok.nextToken();
+
+		if (archiveType.equals("is")) {
+			// InstallShield (v4 and up)
+			Common::String archiveName = tok.nextToken();
+
+			if (!allFiles.contains(archiveName))
+				return false;
+			
+			Common::ScopedPtr<Common::Archive> archive(Common::makeInstallShieldArchive(allFiles[archiveName]));
+			if (!archive)
+				return false;
+			
+			testFile.reset(archive->createReadStreamForMember(tok.nextToken()));
+			if (!testFile) {
+				return false;
+			}
+		} else {
+			debugC(3, kDebugGlobalDetection, "WARNING: Archive type string '%s' not recognized", archiveType.c_str());
+			return false;
+		}
+	} else {
+		if (!allFiles.contains(fname))
+			return false;
 
-	if (!testFile.open(allFiles[fname]))
-		return false;
+		testFile.reset(new Common::File());
+		if (!((Common::File *)testFile.get())->open(allFiles[fname]))
+			return false;
+	}
 
 	if (md5prop & kMD5Tail) {
-		if (testFile.size() > md5Bytes)
-			testFile.seek(-(int64)md5Bytes, SEEK_END);
+		if (testFile->size() > md5Bytes)
+			testFile->seek(-(int64)md5Bytes, SEEK_END);
 	}
 
-	fileProps.size = testFile.size();
-	fileProps.md5 = Common::computeStreamMD5AsString(testFile, md5Bytes);
+	fileProps.size = testFile->size();
+	fileProps.md5 = Common::computeStreamMD5AsString(*testFile.get(), md5Bytes);
 	fileProps.md5prop = (MD5Properties) (md5prop & kMD5Tail);
 	return true;
 }
diff --git a/engines/game.cpp b/engines/game.cpp
index 6b0df5275bc..52e97938a73 100644
--- a/engines/game.cpp
+++ b/engines/game.cpp
@@ -166,32 +166,61 @@ Common::U32String DetectionResults::generateUnknownGameReport(bool translate, ui
 }
 
 const char *md5PropToCachePrefix(MD5Properties flags) {
-	switch (flags & kMD5MacMask) {
-	case kMD5MacDataFork: {
-		if (flags & kMD5Tail)
-			return "dt";
-		return "d";
-	}
-
-	case kMD5MacResOrDataFork: {
-		if (flags & kMD5Tail)
-			return "mt";
-		return "m";
-	}
-
-	case kMD5MacResFork: {
-		if (flags & kMD5Tail)
-			return "rt";
-		return "r";
-	}
-
-	default: {
-		if (flags & kMD5Tail)
-			return "ft";
-
-		return "f";
-	}
-	}
+    if (flags & kMD5Archive) {
+        switch (flags & kMD5MacMask) {
+        case kMD5MacDataFork: {
+            if (flags & kMD5Tail)
+                return "dtA";
+            return "dA";
+        }
+
+        case kMD5MacResOrDataFork: {
+            if (flags & kMD5Tail)
+                return "mtA";
+            return "mA";
+        }
+
+        case kMD5MacResFork: {
+            if (flags & kMD5Tail)
+                return "rtA";
+            return "rA";
+        }
+
+        default: {
+            if (flags & kMD5Tail)
+                return "ftA";
+
+            return "fA";
+        }
+        }
+    } else {
+        switch (flags & kMD5MacMask) {
+        case kMD5MacDataFork: {
+            if (flags & kMD5Tail)
+                return "dt";
+            return "d";
+        }
+
+        case kMD5MacResOrDataFork: {
+            if (flags & kMD5Tail)
+                return "mt";
+            return "m";
+        }
+
+        case kMD5MacResFork: {
+            if (flags & kMD5Tail)
+                return "rt";
+            return "r";
+        }
+
+        default: {
+            if (flags & kMD5Tail)
+                return "ft";
+
+            return "f";
+        }
+        }
+    }
 }
 
 Common::U32String generateUnknownGameReport(const DetectedGames &detectedGames, bool translate, bool fullPath, uint32 wordwrapAt) {
diff --git a/engines/game.h b/engines/game.h
index 65b3ce14f37..3951ada2072 100644
--- a/engines/game.h
+++ b/engines/game.h
@@ -103,12 +103,13 @@ enum GameSupportLevel {
  */
 
 enum MD5Properties {
-	kMD5Head		 = 0 << 1,	// the MD5 is calculated from the head, default
-	kMD5Tail		 = 1 << 1,	// the MD5 is calculated from the tail
+	kMD5Head		         = 0 << 1,	// the MD5 is calculated from the head, default
+	kMD5Tail		         = 1 << 1,	// the MD5 is calculated from the tail
 	kMD5MacResFork           = 1 << 2,	// the MD5 is calculated from the Mac Resource fork (no fall back) (head or tail)
 	kMD5MacDataFork	         = 1 << 3,	// the MD5 is calculated from the Mac Data fork (head or tail)
 	kMD5MacResOrDataFork     = kMD5MacResFork | kMD5MacDataFork,	// the MD5 is calculated from the Mac Resource fork falling back to data fork (head or tail). Deprecated.
 	kMD5MacMask              = kMD5MacResFork | kMD5MacDataFork,    // Mask for mac type
+	kMD5Archive              = 1 << 4,	// the desired file is inside an archive
 };
 
 const char *md5PropToCachePrefix(MD5Properties val);
@@ -141,11 +142,11 @@ struct DetectedGame {
 	DetectedGame();
 	DetectedGame(const Common::String &engine, const PlainGameDescriptor &pgd);
 	DetectedGame(const Common::String &engine, const Common::String &id,
-	               const Common::String &description,
-	               Common::Language language = Common::UNK_LANG,
-	               Common::Platform platform = Common::kPlatformUnknown,
-	               const Common::String &extra = Common::String(),
-	               bool unsupported = false);
+				   const Common::String &description,
+				   Common::Language language = Common::UNK_LANG,
+				   Common::Platform platform = Common::kPlatformUnknown,
+				   const Common::String &extra = Common::String(),
+				   bool unsupported = false);
 
 	void setGUIOptions(const Common::String &options);
 	void appendGUIOptions(const Common::String &str);


Commit: 71e445db5ac6d4b5224414767fac65fefeb0967c
    https://github.com/scummvm/scummvm/commit/71e445db5ac6d4b5224414767fac65fefeb0967c
Author: Kaloyan Chehlarski (strahy at outlook.com)
Date: 2023-11-02T18:20:44+01:00

Commit Message:
NANCY: Upgrade detection entries to new system

Detection entries for compressed game variants now have
an additional file entry for ciftree.dat, which uses the newly
introduced feature for scanning inside archives. The few entries
which I don't own have been temporarily disabled.
Also, a couple new compressed entries have been added; these
were previously put on hold for after the scanning feature
was implemented.

Changed paths:
    engines/nancy/detection.cpp


diff --git a/engines/nancy/detection.cpp b/engines/nancy/detection.cpp
index 610ca4803d7..938946abdc9 100644
--- a/engines/nancy/detection.cpp
+++ b/engines/nancy/detection.cpp
@@ -96,6 +96,8 @@ static const Nancy::NancyGameDescription gameDescriptions[] = {
 				{ "data1.hdr", 0, "39b33ad649d3e7261508d3c6907f237f", 139814 },
 				{ "data1.cab", 0, "f900861c47b0cb88191f5c6189db6cb1", 1916153 },
 				{ "data2.cab", 0, "9c652edb9846a721839cb7e1dcc94a3e", 462008320 },
+				{ "data3.cab", 0, "3053edb46a2574e118c4d4347a25a949", 626903743 },
+				{ "is:data1.cab:ciftree.dat", 0, "A:e1cd21841ab1b83a0ea0755ce0254cbc", 4480956 },
 				AD_LISTEND
 			},
 			Common::RU_RUS,
@@ -135,6 +137,7 @@ static const Nancy::NancyGameDescription gameDescriptions[] = {
 				{ "data1.cab", 0, "8139908ecba23f6cf58711ebd59c5b8b", 3854339 },
 				{ "data2.cab", 0, "99926a30ced5af845220a96d3b657498", 459982848 },
 				{ "data3.cab", 0, "39d396865ab10f908d55eb2ec733cb45", 60604580 },
+				{ "is:data1.cab:ciftree.dat", 0, "A:c19f4a1193b58939ab1a7e314e9a550e", 7756789 },
 			},
 			Common::RU_RUS,
 			Common::kPlatformWindows,
@@ -165,6 +168,24 @@ static const Nancy::NancyGameDescription gameDescriptions[] = {
 		},
 		Nancy::kGameTypeNancy3
 	},
+	{ // MD5 by fracturehill
+		{
+			"nancy3", nullptr,
+			{
+				{ "data1.hdr", 0, "f05167f20c11972f17e6abf7db0034b3", 207514 },
+				{ "data1.cab", 0, "cb5d147affbd1f2f70b436c166d35e5b", 1595638 },
+				{ "data2.cab", 0, "1ea22dabe4a5cff022dac123577188e7", 137304516 },
+				{ "is:data1.cab:ciftree.dat", 0, "A:6b379f9d8edfb2d439062122e08f785c", 16161115 },
+				AD_LISTEND
+			},
+			Common::EN_ANY,
+			Common::kPlatformWindows,
+			ADGF_UNSTABLE | ADGF_DROPPLATFORM | Nancy::GF_COMPRESSED,
+			GUIO2(GAMEOPTION_PLAYER_SPEECH, GAMEOPTION_CHARACTER_SPEECH)
+		},
+		Nancy::kGameTypeNancy3
+	},
+	/* Temporarily disabled; needs ciftree.dat
 	{ // MD5 by waltervn
 		{
 			"nancy3", nullptr,
@@ -180,7 +201,7 @@ static const Nancy::NancyGameDescription gameDescriptions[] = {
 			GUIO2(GAMEOPTION_PLAYER_SPEECH, GAMEOPTION_CHARACTER_SPEECH)
 		},
 		Nancy::kGameTypeNancy3
-	},
+	},*/
 	{ // MD5 by fracturehill
 		{
 			"nancy3", nullptr,
@@ -196,9 +217,10 @@ static const Nancy::NancyGameDescription gameDescriptions[] = {
 		{
 			"nancy3", nullptr,
 			{
-  				{ "data1.hdr", 0, "9da72fec24e1ca4f8f6b563bbdab3276", 237686 },
-				{ "data1.cab", 0, "a7a259e45ae643aed63fa958531cc318", 3473219 },
-  				{ "data2.cab", 0, "cb709fba73605814f9dda823b1cfaf85", 433625036 },
+				{ "data1.hdr", 0, "9da72fec24e1ca4f8f6b563bbdab3276", 237686 },
+  				{ "data1.cab", 0, "a7a259e45ae643aed63fa958531cc318", 3473219 },
+				{ "data2.cab", 0, "cb709fba73605814f9dda823b1cfaf85", 433625036 },
+				{ "is:data1.cab:ciftree.dat", 0, "A:6b379f9d8edfb2d439062122e08f785c", 16161148 },
 				AD_LISTEND
 			},
 			Common::RU_RUS,
@@ -219,24 +241,30 @@ static const Nancy::NancyGameDescription gameDescriptions[] = {
 		},
 		Nancy::kGameTypeNancy4
 	},
-	{ // MD5 by fracturehill
+	{ // Steam version
 		{
 			"nancy4", nullptr,
-			AD_ENTRY1s("ciftree.dat", "8645fad8c3fb8c0ee13e7a0a75902782", 9714463),
-			Common::RU_RUS,
+			AD_ENTRY1s("ciftree.dat", "3ad55cd8f9a3b010a19de44ff4ce7edf", 8786300),
+			Common::EN_ANY,
 			Common::kPlatformWindows,
 			ADGF_UNSTABLE | ADGF_DROPPLATFORM,
 			GUIO2(GAMEOPTION_PLAYER_SPEECH, GAMEOPTION_CHARACTER_SPEECH)
 		},
 		Nancy::kGameTypeNancy4
 	},
-	{ // Steam version
+	{ // MD5 by fracturehill
 		{
 			"nancy4", nullptr,
-			AD_ENTRY1s("ciftree.dat", "3ad55cd8f9a3b010a19de44ff4ce7edf", 8786300),
+			{
+				{ "data1.hdr", 0, "1cfe79efba356d1b5545f9b0cca99f31", 256336 },
+				{ "data1.cab", 0, "31ad655a6de1f16c8990b94f4f094cc2", 1598815 },
+				{ "data2.cab", 0, "9e134ed2dd0ce262b1c93bc91ce67d95", 59466427 },
+				{ "is:data1.cab:ciftree.dat", 0, "A:3ad55cd8f9a3b010a19de44ff4ce7edf", 8786300 },
+				AD_LISTEND
+			},
 			Common::EN_ANY,
 			Common::kPlatformWindows,
-			ADGF_UNSTABLE | ADGF_DROPPLATFORM,
+			ADGF_UNSTABLE | ADGF_DROPPLATFORM | Nancy::GF_COMPRESSED,
 			GUIO2(GAMEOPTION_PLAYER_SPEECH, GAMEOPTION_CHARACTER_SPEECH)
 		},
 		Nancy::kGameTypeNancy4
@@ -248,6 +276,7 @@ static const Nancy::NancyGameDescription gameDescriptions[] = {
 				{ "data1.hdr", 0, "fa4e7a1c411053557169a7731f287012", 263443 },
 				{ "data1.cab", 0, "8f689f92fcca443d6a03faa5de7e2f1c", 1568756 },
 				{ "data2.cab", 0, "5525aa428041f3f1421a6fb5d1b8dba1", 140518758 },
+				{ "is:data1.cab:ciftree.dat", 0, "A:e9d45f7db453b0d8f37d202fc979537c", 8742289 },
 				AD_LISTEND
 			},
 			Common::EN_ANY,
@@ -257,6 +286,7 @@ static const Nancy::NancyGameDescription gameDescriptions[] = {
 		},
 		Nancy::kGameTypeNancy4
 	},
+	/* Temporarily disabled: needs ciftree.dat
 	{ // MD5 by eriktorbjorn
 		{
 			"nancy4", nullptr,
@@ -272,15 +302,27 @@ static const Nancy::NancyGameDescription gameDescriptions[] = {
 			GUIO2(GAMEOPTION_PLAYER_SPEECH, GAMEOPTION_CHARACTER_SPEECH)
 		},
 		Nancy::kGameTypeNancy4
+	},*/
+	{ // MD5 by fracturehill
+		{
+			"nancy4", nullptr,
+			AD_ENTRY1s("ciftree.dat", "a1090497f5fefce17494804bd1d624e1", 9991442),
+			Common::RU_RUS,
+			Common::kPlatformWindows,
+			ADGF_UNSTABLE | ADGF_DROPPLATFORM,
+			GUIO2(GAMEOPTION_PLAYER_SPEECH, GAMEOPTION_CHARACTER_SPEECH)
+		},
+		Nancy::kGameTypeNancy4
 	},
 	{ // MD5 by fracturehill
 		{
 			"nancy4", nullptr,
 			{
-  				{ "data1.hdr", 0, "229ab8e318a0fd0f0db366d854be2a20", 277512 },
-				{ "data1.cab", 0, "156a26646f48e73c578373694cfd632d", 3466411 },
-  				{ "data2.cab", 0, "3db8fcd5414be1b704bef52d300a7fc1", 460324864 },
-  				{ "data3.cab", 0, "8e40909e6946cd45aa949e3db1b970ac", 127118355 },
+				{ "data1.hdr", 0, "694d11d7ebdf2c96cd395f4baf938c10", 256535 },
+  				{ "data1.cab", 0, "9c9c9c60c5344e877e033e54564c6e6e", 4892300 },
+				{ "data2.cab", 0, "05ef8fac76a227f829479719f40522b5", 458917888 },
+				{ "data3.cab", 0, "9b266564726664befe1770754150f5d8", 65795720 },
+				{ "is:data1.cab:ciftree.dat", 0, "A:a1090497f5fefce17494804bd1d624e1", 9991442 },
 				AD_LISTEND
 			},
 			Common::RU_RUS,
@@ -308,6 +350,7 @@ static const Nancy::NancyGameDescription gameDescriptions[] = {
 				{ "data1.hdr", 0, "261105fba2a1226eedb090c2ce79fd35", 284091 },
 				{ "data1.cab", 0, "7d27bb947ef7305831f1faaf1512a598", 1446301 },
 				{ "data2.cab", 0, "00719c86cab733c1094b27079ce030f3", 145857935 },
+				{ "is:data1.cab:ciftree.dat", 0, "A:21fa81f322595c3100d8d58d100852d5", 8187692 },
 				AD_LISTEND
 			},
 			Common::EN_ANY,
@@ -317,6 +360,24 @@ static const Nancy::NancyGameDescription gameDescriptions[] = {
 		},
 		Nancy::kGameTypeNancy5
 	},
+	{ // MD5 by fracturehill
+		{
+			"nancy5", nullptr,
+			{
+				{ "data1.hdr", 0, "0db3fb5bc002eb875eebb872969a22ca", 278505 },
+				{ "data1.cab", 0, "b5d2d218ded5683b5ca2eafcdc1ed76e", 1720654 },
+				{ "data2.cab", 0, "d379a879fb23b3013f78537927ac6cfe", 548761463 },
+  				{ "is:data1.cab:ciftree.dat", 0, "A:21fa81f322595c3100d8d58d100852d5", 8187692 },
+				AD_LISTEND
+			},
+			Common::EN_ANY,
+			Common::kPlatformWindows,
+			ADGF_UNSTABLE | ADGF_DROPPLATFORM | Nancy::GF_COMPRESSED,
+			GUIO2(GAMEOPTION_PLAYER_SPEECH, GAMEOPTION_CHARACTER_SPEECH)
+		},
+		Nancy::kGameTypeNancy5
+	},
+	/* Temporarily disabled; needs ciftree.dat
 	{ // MD5 by clone2727
 		{
 			"nancy5", nullptr,
@@ -332,17 +393,30 @@ static const Nancy::NancyGameDescription gameDescriptions[] = {
 			GUIO2(GAMEOPTION_PLAYER_SPEECH, GAMEOPTION_CHARACTER_SPEECH)
 		},
 		Nancy::kGameTypeNancy5
+	},*/
+	{ // MD5 by fracturehill
+		{
+			"nancy5", nullptr,
+			AD_ENTRY1s("ciftree.dat", "8645fad8c3fb8c0ee13e7a0a75902782", 9714463),
+			Common::RU_RUS,
+			Common::kPlatformWindows,
+			ADGF_UNSTABLE | ADGF_DROPPLATFORM,
+			GUIO2(GAMEOPTION_PLAYER_SPEECH, GAMEOPTION_CHARACTER_SPEECH)
+		},
+		Nancy::kGameTypeNancy5
 	},
 	{ // MD5 by fracturehill
 		{
 			"nancy5", nullptr,
 			{
-				{ "data1.hdr", 0, "0db3fb5bc002eb875eebb872969a22ca", 278505 },
-				{ "data1.cab", 0, "b5d2d218ded5683b5ca2eafcdc1ed76e", 1720654 },
-				{ "data2.cab", 0, "d379a879fb23b3013f78537927ac6cfe", 548761463 },
+				{ "data1.hdr", 0, "229ab8e318a0fd0f0db366d854be2a20", 277512 },
+				{ "data1.cab", 0, "156a26646f48e73c578373694cfd632d", 3466411 },
+				{ "data2.cab", 0, "3db8fcd5414be1b704bef52d300a7fc1", 460324864 },
+				{ "data3.cab", 0, "8e40909e6946cd45aa949e3db1b970ac", 127118355 },
+				{ "is:data1.cab:ciftree.dat", 0, "A:8645fad8c3fb8c0ee13e7a0a75902782", 9714463 },
 				AD_LISTEND
 			},
-			Common::EN_ANY,
+			Common::RU_RUS,
 			Common::kPlatformWindows,
 			ADGF_UNSTABLE | ADGF_DROPPLATFORM | Nancy::GF_COMPRESSED,
 			GUIO2(GAMEOPTION_PLAYER_SPEECH, GAMEOPTION_CHARACTER_SPEECH)
@@ -360,6 +434,7 @@ static const Nancy::NancyGameDescription gameDescriptions[] = {
 		},
 		Nancy::kGameTypeNancy6
 	},
+	/* Temporarily disabled; needs ciftree.dat
 	{ // MD5 by eriktorbjorn
 		{
 			"nancy6", nullptr,
@@ -375,8 +450,53 @@ static const Nancy::NancyGameDescription gameDescriptions[] = {
 			GUIO1(GAMEOPTION_AUTO_MOVE)
 		},
 		Nancy::kGameTypeNancy6
+	},*/
+	{ // MD5 by fracturehill
+		{
+			"nancy6", nullptr,
+			{
+				{ "data1.hdr", 0, "fe9ccf3ac298dfdba50c27971eb17758", 275738 },
+				{ "data1.cab", 0, "44379edf93cc7132e69800558c84c1a5", 4470232 },
+				{ "data2.cab", 0, "e0552258607ac7d8ed89890768b705ae", 404327022 },
+				{ "ciftree.dat", 0, "a97b848651fdcf38f5cad7092d98e4a1", 28888006 }, // ciftree is outside of cabfiles
+				AD_LISTEND
+			},
+			Common::EN_ANY,
+			Common::kPlatformWindows,
+			ADGF_UNSTABLE | ADGF_DROPPLATFORM | Nancy::GF_COMPRESSED,
+			GUIO1(GAMEOPTION_AUTO_MOVE)
+		},
+		Nancy::kGameTypeNancy6
 	},
-	{
+	{ // MD5 by fracturehill
+		{
+			"nancy6", nullptr,
+			AD_ENTRY1s("ciftree.dat", "e1a4db8cbac3de50d8e808f06d673b36", 29643931),
+			Common::RU_RUS,
+			Common::kPlatformWindows,
+			ADGF_UNSTABLE | ADGF_DROPPLATFORM,
+			GUIO1(GAMEOPTION_AUTO_MOVE)
+		},
+		Nancy::kGameTypeNancy6
+	},
+	{ // MD5 by fracturehill
+		{
+			"nancy6", nullptr,
+			{
+				{ "data1.hdr", 0, "4a95296bcc54c1376661496d17f2eeb1", 276630 },
+				{ "data1.cab", 0, "67f1f05f7ddfae3f63fc2f208e7b6e5d", 6634156 },
+				{ "data2.cab", 0, "299c8165f5f7dacfcb6e625ffeac47b4", 457154560 },
+				{ "is:data1.cab:ciftree.dat", 0, "A:e1a4db8cbac3de50d8e808f06d673b36", 29643931 },
+				AD_LISTEND
+			},
+			Common::RU_RUS,
+			Common::kPlatformWindows,
+			ADGF_UNSTABLE | ADGF_DROPPLATFORM | Nancy::GF_COMPRESSED,
+			GUIO1(GAMEOPTION_AUTO_MOVE)
+		},
+		Nancy::kGameTypeNancy6
+	},
+	{ // MD5 by fracturehill
 		{
 			"nancy7", nullptr,
 			AD_ENTRY1s("ciftree.dat", "e49e6f56a47c363e2651bd19a70ff557", 55835579),
@@ -394,16 +514,45 @@ static const Nancy::NancyGameDescription gameDescriptions[] = {
 				{ "data1.hdr", 0, "f58175e4647e635e96d73dde49deb871", 335485 },
 				{ "data1.cab", 0, "6b48a626a8c6c12c9d7f68ee6c80212a", 4693602 },
 				{ "data2.cab", 0, "0aec46d4a59ea0279228ab25bfb3fcd5", 144613827 },
+				{ "is:data1.cab:ciftree.dat", 0, "A:e49e6f56a47c363e2651bd19a70ff557", 55835579 },
 				AD_LISTEND
 			},
 			Common::EN_ANY,
 			Common::kPlatformWindows,
 			ADGF_UNSTABLE | ADGF_DROPPLATFORM | Nancy::GF_COMPRESSED,
-			GUIO0()
+			GUIO1(GAMEOPTION_AUTO_MOVE)
 		},
 		Nancy::kGameTypeNancy7
 	},
-	{
+	{ // MD5 by fracturehill
+		{
+			"nancy7", nullptr,
+			AD_ENTRY1s("ciftree.dat", "a2001796b82a88d36693d087b15526e1", 56580014),
+			Common::RU_RUS,
+			Common::kPlatformWindows,
+			ADGF_UNSTABLE | ADGF_DROPPLATFORM,
+			GUIO1(GAMEOPTION_AUTO_MOVE)
+		},
+		Nancy::kGameTypeNancy7
+	},
+	{ // MD5 by fracturehill
+		{
+			"nancy7", nullptr,
+			{
+				{ "data1.hdr", 0, "32708e36d0250cfd031a82b7534aebb9", 327394 },
+				{ "data1.cab", 0, "41681a27e35123a5edfcf45a58101295", 4805355 },
+				{ "data2.cab", 0, "4e5644ac2e0d523f5aaf343d115889a7", 460975235 },
+				{ "is:data1.cab:ciftree.dat", 0, "A:a2001796b82a88d36693d087b15526e1", 56580014 },
+				AD_LISTEND
+			},
+			Common::RU_RUS,
+			Common::kPlatformWindows,
+			ADGF_UNSTABLE | ADGF_DROPPLATFORM | Nancy::GF_COMPRESSED,
+			GUIO1(GAMEOPTION_AUTO_MOVE)
+		},
+		Nancy::kGameTypeNancy7
+	},
+	{ // MD5 by fracturehill
 		{
 			"nancy8", nullptr,
 			AD_ENTRY1s("ciftree.dat", "d85192a942a207017ebf0a19207ac698", 19498032),
@@ -414,7 +563,52 @@ static const Nancy::NancyGameDescription gameDescriptions[] = {
 		},
 		Nancy::kGameTypeNancy8
 	},
-	{
+	{ // MD5 by fracturehill
+		{
+			"nancy8", nullptr,
+			{
+				{ "data1.hdr", 0, "463f68bd7b09bafcfed40c7875b775cc", 318378 },
+				{ "data1.cab", 0, "6dd3805a1e2f5dd4c81726acf77e3869", 5451563 },
+				{ "data2.cab", 0, "14dde9855a10ed9ee6622aea53477ba6", 159239822 },
+				{ "is:data1.cab:ciftree.dat", 0, "A:d85192a942a207017ebf0a19207ac698", 19498032 },
+				AD_LISTEND
+			},
+			Common::EN_ANY,
+			Common::kPlatformWindows,
+			ADGF_UNSTABLE | ADGF_DROPPLATFORM | Nancy::GF_COMPRESSED,
+			GUIO1(GAMEOPTION_AUTO_MOVE)
+		},
+		Nancy::kGameTypeNancy8
+	},
+	{ // MD5 by fracturehill
+		{
+			"nancy8", nullptr,
+			AD_ENTRY1s("ciftree.dat", "9ed40f0bcc1d9e3e38e127bfbc6797d7", 20358011),
+			Common::RU_RUS,
+			Common::kPlatformWindows,
+			ADGF_UNSTABLE | ADGF_DROPPLATFORM,
+			GUIO1(GAMEOPTION_AUTO_MOVE)
+		},
+		Nancy::kGameTypeNancy8
+	},
+	{ // MD5 by fracturehill
+		{
+			"nancy8", nullptr,
+			{
+				{ "data1.cab", 0, "d9cc1694249e2084ccf5afa2a2398f80", 3297523 },
+				{ "data1.hdr", 0, "e2440f9e2ce02bc1aace6a376c9a9939", 303244 },
+				{ "data2.cab", 0, "448974e37758bd91f81f92f508cbffd5", 379253931 },
+				{ "is:data1.cab:ciftree.dat", 0, "A:9ed40f0bcc1d9e3e38e127bfbc6797d7", 20358011 },
+				AD_LISTEND
+			},
+			Common::RU_RUS,
+			Common::kPlatformWindows,
+			ADGF_UNSTABLE | ADGF_DROPPLATFORM | Nancy::GF_COMPRESSED,
+			GUIO1(GAMEOPTION_AUTO_MOVE)
+		},
+		Nancy::kGameTypeNancy8
+	},
+	{ // MD5 by fracturehill
 		{
 			"nancy9", nullptr,
 			AD_ENTRY1s("ciftree.dat", "3a756e09631f4a2c8f48bf316e77b5d5", 26610718),
@@ -425,6 +619,23 @@ static const Nancy::NancyGameDescription gameDescriptions[] = {
 		},
 		Nancy::kGameTypeNancy9
 	},
+	{ // MD5 by fracturehill
+		{
+			"nancy9", nullptr,
+			{
+				{ "data1.hdr", 0, "6dcc79a737b5275d431087b36fb81c88", 403996 },
+				{ "data1.cab", 0, "1b1c1067b46ead0771485948bcfd8320", 6874331 },
+				{ "data2.cab", 0, "d0c4a054d38de8dc85e9fde9667fff31", 115877879 },
+				{ "ciftree.dat", 0, "3a756e09631f4a2c8f48bf316e77b5d5", 26610718 }, // ciftree is outside of cabfiles
+				AD_LISTEND
+			},
+			Common::EN_ANY,
+			Common::kPlatformWindows,
+			ADGF_UNSTABLE | ADGF_DROPPLATFORM | Nancy::GF_COMPRESSED,
+			GUIO1(GAMEOPTION_AUTO_MOVE)
+		},
+		Nancy::kGameTypeNancy9
+	},
 
 	// Do not delete
 	{ AD_TABLE_END_MARKER, Nancy::kGameTypeNone }


Commit: f77eca7c80b1557608c568fb23d42a94833bb68e
    https://github.com/scummvm/scummvm/commit/f77eca7c80b1557608c568fb23d42a94833bb68e
Author: Kaloyan Chehlarski (strahy at outlook.com)
Date: 2023-11-02T18:20:44+01:00

Commit Message:
AD: Implement archive caching during detection

Renamed MD5CacheManager to AdvancedDetectorCacheManager,
and added facilities for storing open archives inside it. This
way an archive that was opened by an AdvancedDetector
will be kept in memory until the end of the detection, so
other entries/engines that will look inside it won't have
to reopen it and reread its data every time.

Changed paths:
    base/plugins.cpp
    engines/advancedDetector.cpp
    engines/advancedDetector.h
    gui/launcher.cpp


diff --git a/base/plugins.cpp b/base/plugins.cpp
index d3b44e577c4..48fcb3f3b40 100644
--- a/base/plugins.cpp
+++ b/base/plugins.cpp
@@ -724,7 +724,7 @@ DetectionResults EngineManager::detectGames(const Common::FSList &fslist, uint32
 	plugins = getPlugins(PLUGIN_TYPE_ENGINE_DETECTION);
 
 	// Clear md5 cache before each detection starts, just in case.
-	MD5Man.clear();
+	ADCacheMan.clear();
 
 	// Iterate over all known games and for each check if it might be
 	// the game in the presented directory.
@@ -741,6 +741,9 @@ DetectionResults EngineManager::detectGames(const Common::FSList &fslist, uint32
 		}
 	}
 
+	// Close all archives that were opened during detection
+	ADCacheMan.clearArchives();
+
 	return DetectionResults(candidates);
 }
 
@@ -942,7 +945,7 @@ void EngineManager::upgradeTargetForEngineId(const Common::String &target) const
 		// set debug flags before call detectGames
 		DebugMan.addAllDebugChannels(metaEngine.getDebugChannels());
 		// Clear md5 cache before detection starts
-		MD5Man.clear();
+		ADCacheMan.clear();
 		DetectedGames candidates = metaEngine.detectGames(files);
 		if (candidates.empty()) {
 			warning("No games supported by the engine '%s' were found in path '%s' when upgrading target '%s'",
diff --git a/engines/advancedDetector.cpp b/engines/advancedDetector.cpp
index bd7418d54ee..b8ebaf51dbf 100644
--- a/engines/advancedDetector.cpp
+++ b/engines/advancedDetector.cpp
@@ -380,7 +380,7 @@ Common::Error AdvancedMetaEngineDetection::createInstance(OSystem *syst, Engine
 	composeFileHashMap(allFiles, files, (_maxScanDepth == 0 ? 1 : _maxScanDepth));
 
 	// Clear md5 cache before each detection starts, just in case.
-	MD5Man.clear();
+	ADCacheMan.clear();
 
 	// Run the detector on this
 	ADDetectedGames matches = detectGame(files.begin()->getParent(), allFiles, language, platform, extra);
@@ -514,7 +514,7 @@ void AdvancedMetaEngineDetection::composeFileHashMap(FileMap &allFiles, const Co
 /* Singleton Cache Storage for MD5 */
 
 namespace Common {
-	DECLARE_SINGLETON(MD5CacheManager);
+	DECLARE_SINGLETON(AdvancedDetectorCacheManager);
 }
 
 
@@ -609,17 +609,17 @@ bool AdvancedMetaEngineDetection::getFileProperties(const FileMap &allFiles, MD5
 		hashname += ':';
 		hashname += Common::String::format("%d", _md5Bytes);
 
-	if (MD5Man.contains(hashname)) {
-		fileProps.md5 = MD5Man.getMD5(hashname);
-		fileProps.size = MD5Man.getSize(hashname);
+	if (ADCacheMan.containsMD5(hashname)) {
+		fileProps.md5 = ADCacheMan.getMD5(hashname);
+		fileProps.size = ADCacheMan.getSize(hashname);
 		return true;
 	}
 
 	bool res = getFilePropertiesIntern(_md5Bytes, allFiles, md5prop, fname, fileProps);
 
 	if (res) {
-		MD5Man.setMD5(hashname, fileProps.md5);
-		MD5Man.setSize(hashname, fileProps.size);
+		ADCacheMan.setMD5(hashname, fileProps.md5);
+		ADCacheMan.setSize(hashname, fileProps.size);
 	}
 
 	return res;
@@ -682,9 +682,13 @@ static bool getFilePropertiesIntern(uint md5Bytes, const AdvancedMetaEngine::Fil
 			if (!allFiles.contains(archiveName))
 				return false;
 			
-			Common::ScopedPtr<Common::Archive> archive(Common::makeInstallShieldArchive(allFiles[archiveName]));
-			if (!archive)
-				return false;
+			Common::Archive *archive = ADCacheMan.getArchive(allFiles[archiveName]);
+			if (!archive) {
+				archive = Common::makeInstallShieldArchive(allFiles[archiveName]);
+				ADCacheMan.addArchive(allFiles[archiveName], archive);
+				if (!archive)
+					return false;
+			}
 			
 			testFile.reset(archive->createReadStreamForMember(tok.nextToken()));
 			if (!testFile) {
diff --git a/engines/advancedDetector.h b/engines/advancedDetector.h
index c639f94be15..673c1393d87 100644
--- a/engines/advancedDetector.h
+++ b/engines/advancedDetector.h
@@ -571,9 +571,9 @@ protected:
 };
 
 /**
- * Singleton Cache Storage for Computed MD5s
+ * Singleton Cache Storage for Computed MD5s and Open Archives
  */
-class MD5CacheManager : public Common::Singleton<MD5CacheManager> {
+class AdvancedDetectorCacheManager : public Common::Singleton<AdvancedDetectorCacheManager> {
 public:
 	void setMD5(Common::String fname, Common::String md5) {
 		md5HashMap.setVal(fname, md5);
@@ -591,29 +591,57 @@ public:
 		return sizeHashMap.getVal(fname);
 	}
 
-	bool contains(Common::String fname) {
+	bool containsMD5(Common::String fname) {
 		return (md5HashMap.contains(fname) && sizeHashMap.contains(fname));
 	}
 
-	MD5CacheManager() {
+	void addArchive(const Common::FSNode &node, Common::Archive *archivePtr) {
+		if (!archivePtr)
+			return;
+
+		Common::String filename = node.getPath();
+		
+		if (archiveHashMap.contains(filename)) {
+			delete archiveHashMap[filename];
+		}
+		
+		archiveHashMap.setVal(filename, archivePtr);
+	}
+
+	Common::Archive *getArchive(const Common::FSNode &node) {
+		Common::Archive *ret = nullptr;
+		return archiveHashMap.tryGetVal(node.getPath(), ret) ? ret : nullptr;
+	}
+
+	AdvancedDetectorCacheManager() {
 		clear();
 	}
 
+	void clearArchives() {
+		for (auto &entry : archiveHashMap) {
+			delete entry._value;
+		}
+		archiveHashMap.clear(true);
+	}
+
 	void clear() {
 		md5HashMap.clear(true);
 		sizeHashMap.clear(true);
+		clearArchives();
 	}
 
 private:
-	friend class Common::Singleton<MD5CacheManager>;
+	friend class Common::Singleton<AdvancedDetectorCacheManager>;
 
 	typedef Common::HashMap<Common::String, Common::String, Common::IgnoreCase_Hash, Common::IgnoreCase_EqualTo> FileHashMap;
 	typedef Common::HashMap<Common::String, int64, Common::IgnoreCase_Hash, Common::IgnoreCase_EqualTo> SizeHashMap;
+	typedef Common::HashMap<Common::String, Common::Archive *, Common::IgnoreCase_Hash, Common::IgnoreCase_EqualTo> ArchiveHashMap;
 	FileHashMap md5HashMap;
 	SizeHashMap sizeHashMap;
+	ArchiveHashMap archiveHashMap;
 };
 
 /** Convenience shortcut for accessing the MD5CacheManager. */
-#define MD5Man MD5CacheManager::instance()
+#define ADCacheMan AdvancedDetectorCacheManager::instance()
 /** @} */
 #endif
diff --git a/gui/launcher.cpp b/gui/launcher.cpp
index 3956b674e42..f94b3afef7d 100644
--- a/gui/launcher.cpp
+++ b/gui/launcher.cpp
@@ -424,7 +424,7 @@ void LauncherDialog::massAddGame() {
 	MessageDialog alert(_("Do you really want to run the mass game detector? "
 						  "This could potentially add a huge number of games."), _("Yes"), _("No"));
 	if (alert.runModal() == GUI::kMessageOK && _browser->runModal() > 0) {
-		MD5Man.clear();
+		ADCacheMan.clear();
 		MassAddDialog massAddDlg(_browser->getResult());
 
 		massAddDlg.runModal();


Commit: c598e60e5ef6069ef4e59f29d553e98f67ff1648
    https://github.com/scummvm/scummvm/commit/c598e60e5ef6069ef4e59f29d553e98f67ff1648
Author: Kaloyan Chehlarski (strahy at outlook.com)
Date: 2023-11-02T18:20:44+01:00

Commit Message:
AD: Clear archive cache when starting game

This ensures we don't keep potentially expensive and
otherwise useless objects when a game is running

Changed paths:
    engines/advancedDetector.cpp


diff --git a/engines/advancedDetector.cpp b/engines/advancedDetector.cpp
index b8ebaf51dbf..e033c861440 100644
--- a/engines/advancedDetector.cpp
+++ b/engines/advancedDetector.cpp
@@ -413,6 +413,9 @@ Common::Error AdvancedMetaEngineDetection::createInstance(OSystem *syst, Engine
 
 	DetectedGame gameDescriptor = toDetectedGame(agdDesc);
 
+	// Detection is done, no need to keep archives in memory anymore
+	ADCacheMan.clearArchives();
+
 	// If the GUI options were updated, we catch this here and update them in the users config
 	// file transparently.
 	ConfMan.setAndFlush("guioptions", gameDescriptor.getGUIOptions());


Commit: 1a08bcc08f31a51315f93c3621984dc85382d2f2
    https://github.com/scummvm/scummvm/commit/1a08bcc08f31a51315f93c3621984dc85382d2f2
Author: Kaloyan Chehlarski (strahy at outlook.com)
Date: 2023-11-02T18:20:44+01:00

Commit Message:
ARCHIVE: Allow creating InstallShieldV3 archives with FSNode

Changed paths:
    common/compression/installshieldv3_archive.cpp
    common/compression/installshieldv3_archive.h


diff --git a/common/compression/installshieldv3_archive.cpp b/common/compression/installshieldv3_archive.cpp
index 983b48686d5..2d085b602fb 100644
--- a/common/compression/installshieldv3_archive.cpp
+++ b/common/compression/installshieldv3_archive.cpp
@@ -42,6 +42,65 @@ bool InstallShieldV3::open(const Common::String &filename) {
 	if (!_stream)
 		return false;
 
+	return read();
+}
+
+bool InstallShieldV3::open(const Common::FSNode &node) {
+	close();
+
+	_stream = node.createReadStream();
+
+	if (!_stream)
+		return false;
+
+	return read();
+}
+
+void InstallShieldV3::close() {
+	delete _stream; _stream = nullptr;
+	_map.clear();
+}
+
+bool InstallShieldV3::hasFile(const Common::Path &path) const {
+	Common::String name = path.toString();
+	return _map.contains(name);
+}
+
+int InstallShieldV3::listMembers(Common::ArchiveMemberList &list) const {
+	for (FileMap::const_iterator it = _map.begin(); it != _map.end(); it++)
+		list.push_back(getMember(it->_key));
+
+	return _map.size();
+}
+
+const Common::ArchiveMemberPtr InstallShieldV3::getMember(const Common::Path &path) const {
+	Common::String name = path.toString();
+	return Common::ArchiveMemberPtr(new Common::GenericArchiveMember(name, *this));
+}
+
+Common::SeekableReadStream *InstallShieldV3::createReadStreamForMember(const Common::Path &path) const {
+	Common::String name = path.toString();
+	// Make sure "/" is converted to "\"
+	while (name.contains("/"))
+		Common::replace(name, "/", "\\");
+
+	if (!_stream || !_map.contains(name))
+		return nullptr;
+
+	const FileEntry &entry = _map[name];
+
+	// Seek to our offset and then send it off to the decompressor
+	_stream->seek(entry.offset);
+	return Common::decompressDCL(_stream, entry.compressedSize, entry.uncompressedSize);
+}
+
+char InstallShieldV3::getPathSeparator() const {
+	return '\\';
+}
+
+bool InstallShieldV3::read() {
+	assert(_stream);
+
 	// Check for the magic uint32
 	// No idea what it means, but it's how "file" recognizes them
 	if (_stream->readUint32BE() != 0x135D658C) {
@@ -112,49 +171,8 @@ bool InstallShieldV3::open(const Common::String &filename) {
 					entry.offset, entry.compressedSize, entry.uncompressedSize);
 		}
 	}
-	return true;
-}
-
-void InstallShieldV3::close() {
-	delete _stream; _stream = nullptr;
-	_map.clear();
-}
-
-bool InstallShieldV3::hasFile(const Common::Path &path) const {
-	Common::String name = path.toString();
-	return _map.contains(name);
-}
-
-int InstallShieldV3::listMembers(Common::ArchiveMemberList &list) const {
-	for (FileMap::const_iterator it = _map.begin(); it != _map.end(); it++)
-		list.push_back(getMember(it->_key));
-
-	return _map.size();
-}
 
-const Common::ArchiveMemberPtr InstallShieldV3::getMember(const Common::Path &path) const {
-	Common::String name = path.toString();
-	return Common::ArchiveMemberPtr(new Common::GenericArchiveMember(name, *this));
-}
-
-Common::SeekableReadStream *InstallShieldV3::createReadStreamForMember(const Common::Path &path) const {
-	Common::String name = path.toString();
-	// Make sure "/" is converted to "\"
-	while (name.contains("/"))
-		Common::replace(name, "/", "\\");
-
-	if (!_stream || !_map.contains(name))
-		return nullptr;
-
-	const FileEntry &entry = _map[name];
-
-	// Seek to our offset and then send it off to the decompressor
-	_stream->seek(entry.offset);
-	return Common::decompressDCL(_stream, entry.compressedSize, entry.uncompressedSize);
-}
-
-char InstallShieldV3::getPathSeparator() const {
-	return '\\';
+	return true;
 }
 
 } // End of namespace Common
diff --git a/common/compression/installshieldv3_archive.h b/common/compression/installshieldv3_archive.h
index ee5ccc0e65a..31e2d437f09 100644
--- a/common/compression/installshieldv3_archive.h
+++ b/common/compression/installshieldv3_archive.h
@@ -38,6 +38,7 @@ public:
 	~InstallShieldV3() override;
 
 	bool open(const Common::String &filename);
+	bool open(const Common::FSNode &node);
 	void close();
 	bool isOpen() const { return _stream != nullptr; }
 
@@ -55,6 +56,8 @@ private:
 		uint32 offset;
 	};
 
+	bool read();
+
 	Common::SeekableReadStream *_stream;
 
 	typedef Common::HashMap<Common::String, FileEntry, Common::IgnoreCase_Hash, Common::IgnoreCase_EqualTo> FileMap;


Commit: ad682243c4852e0201ae6b9c2b876a5cda15936b
    https://github.com/scummvm/scummvm/commit/ad682243c4852e0201ae6b9c2b876a5cda15936b
Author: Kaloyan Chehlarski (strahy at outlook.com)
Date: 2023-11-02T18:20:44+01:00

Commit Message:
AD: Support reading inside InstallShieldV3 archives

InstallShield V3 archives are a completely different format
and have their own Archive subclass; this commit allows
AdvancedDetector to look inside them. Also moved around
some of the relevant logic and added more comments to
make the code more readable.

Changed paths:
    engines/advancedDetector.cpp


diff --git a/engines/advancedDetector.cpp b/engines/advancedDetector.cpp
index e033c861440..0e2b837c2c7 100644
--- a/engines/advancedDetector.cpp
+++ b/engines/advancedDetector.cpp
@@ -33,6 +33,7 @@
 #include "common/tokenizer.h"
 #include "common/translation.h"
 #include "common/compression/installshield_cab.h"
+#include "common/compression/installshieldv3_archive.h"
 #include "gui/EventRecorder.h"
 #include "gui/gui-manager.h"
 #include "gui/message.h"
@@ -674,31 +675,45 @@ static bool getFilePropertiesIntern(uint md5Bytes, const AdvancedMetaEngine::Fil
 	if (md5prop & kMD5Archive) {
 		// The desired file is inside an archive
 
-		// First, check the type of archive
+		// First, split the file string
 		Common::StringTokenizer tok(fname, ":");
 		Common::String archiveType = tok.nextToken();
+		Common::String archiveName = tok.nextToken();
+		Common::String fileName = tok.nextToken();
 
-		if (archiveType.equals("is")) {
-			// InstallShield (v4 and up)
-			Common::String archiveName = tok.nextToken();
+		if (!allFiles.contains(archiveName))
+			return false;
 
-			if (!allFiles.contains(archiveName))
-				return false;
-			
-			Common::Archive *archive = ADCacheMan.getArchive(allFiles[archiveName]);
-			if (!archive) {
+		// Check if archive has already been opened and is stored in cache
+		Common::Archive *archive = ADCacheMan.getArchive(allFiles[archiveName]);
+
+		if (!archive) {
+			// Archive not in cache. Find the appropriate type based on the type string,
+			// open the archive, and add it to the cache 
+			if (archiveType.equals("is")) {
+				// InstallShield (v4 and up)
 				archive = Common::makeInstallShieldArchive(allFiles[archiveName]);
 				ADCacheMan.addArchive(allFiles[archiveName], archive);
 				if (!archive)
 					return false;
-			}
-			
-			testFile.reset(archive->createReadStreamForMember(tok.nextToken()));
-			if (!testFile) {
+			} else if (archiveType.equals("is3")) {
+				// InstallShield v3
+				archive = new Common::InstallShieldV3();
+				if (((Common::InstallShieldV3 *)archive)->open(allFiles[archiveName])) {
+					ADCacheMan.addArchive(allFiles[archiveName], archive);
+				} else {
+					delete archive;
+					return false;
+				}
+			} else {
+				debugC(3, kDebugGlobalDetection, "WARNING: Archive type string '%s' not recognized", archiveType.c_str());
 				return false;
 			}
-		} else {
-			debugC(3, kDebugGlobalDetection, "WARNING: Archive type string '%s' not recognized", archiveType.c_str());
+		}
+
+		// Look for file with matching name inside the archive
+		testFile.reset(archive->createReadStreamForMember(fileName));
+		if (!testFile) {
 			return false;
 		}
 	} else {


Commit: f7fe15ed427a91fe92508fa601200df661f838c4
    https://github.com/scummvm/scummvm/commit/f7fe15ed427a91fe92508fa601200df661f838c4
Author: Kaloyan Chehlarski (strahy at outlook.com)
Date: 2023-11-02T18:20:44+01:00

Commit Message:
AD: Add sanity checks for entries with archives

AdvancedMetaEngineDetection::detectClashes() now
performs a sanity check for entries with archive members,
making sure their filename schema contains exactly three
elements.

Changed paths:
    engines/advancedDetector.cpp


diff --git a/engines/advancedDetector.cpp b/engines/advancedDetector.cpp
index 0e2b837c2c7..e25cee6588d 100644
--- a/engines/advancedDetector.cpp
+++ b/engines/advancedDetector.cpp
@@ -1185,6 +1185,30 @@ void AdvancedMetaEngineDetection::detectClashes() const {
 		} else {
 			idsMap[g->gameId]++;
 		}
+
+		// Perform sanity checks for entries with files inside archives
+		for (const ADGameFileDescription &fileDesc : ((const ADGameDescription *)descPtr)->filesDescriptions) {
+			if (fileDesc.fileName == nullptr && fileDesc.md5 == nullptr) {
+				break;
+			}
+
+			if (fileDesc.md5 && fileDesc.md5[0] == 'A' && fileDesc.md5[1] == ':') {
+				Common::StringTokenizer tok(fileDesc.fileName, ":");
+				uint numTokens = 0;
+
+				while (!tok.empty()) {
+					if (tok.nextToken().empty()) {
+						break;
+					}
+					++numTokens;
+				}
+
+				// We need exactly three tokens: <archive type> : <archive name> : <file name>
+				if (numTokens != 3) {
+					debug(0, "WARNING: Detection entry '%s' for gameId '%s' in engine '%s' is invalid", fileDesc.fileName, g->gameId, getName());
+				}
+			}
+		}
 	}
 
 	for (auto &k : idsMap) {


Commit: 486b139d105e57a46bddade493c8f800bf9faab9
    https://github.com/scummvm/scummvm/commit/486b139d105e57a46bddade493c8f800bf9faab9
Author: Kaloyan Chehlarski (strahy at outlook.com)
Date: 2023-11-02T18:20:44+01:00

Commit Message:
AD: Clean up detection prefix generation functions

Cleaned up the functions md5PropToGameFile() and
md5PropToCachePrefix() so they no longer work through
a tree of switches. Both functions have also been changed to
return a Common::String.

Changed paths:
    engines/advancedDetector.cpp
    engines/game.cpp
    engines/game.h


diff --git a/engines/advancedDetector.cpp b/engines/advancedDetector.cpp
index e25cee6588d..18a629d5243 100644
--- a/engines/advancedDetector.cpp
+++ b/engines/advancedDetector.cpp
@@ -554,54 +554,33 @@ static MD5Properties gameFileToMD5Props(const ADGameFileDescription *fileEntry,
 	return ret;
 }
 
-const char *md5PropToGameFile(MD5Properties flags) {
-	if (flags & kMD5Archive) {
-		switch (flags & kMD5MacMask)
-		case kMD5MacDataFork: {
-			if (flags & kMD5Tail)
-				return "dtA";
-			return "dA";
-
-		case kMD5MacResOrDataFork:
-			if (flags & kMD5Tail)
-				return "mtA";
-			return "mA";
-
-		case kMD5MacResFork:
-			if (flags & kMD5Tail)
-				return "rtA";
-			return "rA";
-
-		case kMD5Tail:
-			return "tA";
-
-		default:
-			return "A";
-		}
-	} else {
-		switch (flags & kMD5MacMask)
-		case kMD5MacDataFork: {
-			if (flags & kMD5Tail)
-				return "dt";
-			return "d";
-
-		case kMD5MacResOrDataFork:
-			if (flags & kMD5Tail)
-				return "mt";
-			return "m";
-
-		case kMD5MacResFork:
-			if (flags & kMD5Tail)
-				return "rt";
-			return "r";
-
-		case kMD5Tail:
-			return "t";
-
-		default:
-			return "";
-		}
-	}
+Common::String md5PropToGameFile(MD5Properties flags) {
+	Common::String res;
+
+	switch (flags & kMD5MacMask) {
+	case kMD5MacDataFork: 
+		res = "d";
+		break;
+
+	case kMD5MacResOrDataFork:
+		res = "m";
+		break;
+
+	case kMD5MacResFork:
+		res = "r";
+		break;
+
+	default:
+		break;
+    }
+
+	if (flags & kMD5Tail)
+		res += "t";
+
+	if (flags & kMD5Archive)
+		res += "A";
+
+	return res;
 }
 
 static bool getFilePropertiesIntern(uint md5Bytes, const AdvancedMetaEngine::FileMap &allFiles, MD5Properties md5prop, const Common::String &fname, FileProperties &fileProps);
diff --git a/engines/game.cpp b/engines/game.cpp
index 52e97938a73..be6bd6163c2 100644
--- a/engines/game.cpp
+++ b/engines/game.cpp
@@ -165,62 +165,36 @@ Common::U32String DetectionResults::generateUnknownGameReport(bool translate, ui
 	return ::generateUnknownGameReport(_detectedGames, translate, false, wordwrapAt);
 }
 
-const char *md5PropToCachePrefix(MD5Properties flags) {
-    if (flags & kMD5Archive) {
-        switch (flags & kMD5MacMask) {
-        case kMD5MacDataFork: {
-            if (flags & kMD5Tail)
-                return "dtA";
-            return "dA";
-        }
-
-        case kMD5MacResOrDataFork: {
-            if (flags & kMD5Tail)
-                return "mtA";
-            return "mA";
-        }
-
-        case kMD5MacResFork: {
-            if (flags & kMD5Tail)
-                return "rtA";
-            return "rA";
-        }
-
-        default: {
-            if (flags & kMD5Tail)
-                return "ftA";
-
-            return "fA";
-        }
-        }
-    } else {
-        switch (flags & kMD5MacMask) {
-        case kMD5MacDataFork: {
-            if (flags & kMD5Tail)
-                return "dt";
-            return "d";
-        }
-
-        case kMD5MacResOrDataFork: {
-            if (flags & kMD5Tail)
-                return "mt";
-            return "m";
-        }
-
-        case kMD5MacResFork: {
-            if (flags & kMD5Tail)
-                return "rt";
-            return "r";
-        }
-
-        default: {
-            if (flags & kMD5Tail)
-                return "ft";
-
-            return "f";
-        }
-        }
-    }
+Common::String md5PropToCachePrefix(MD5Properties flags) {
+	Common::String res;
+
+	if (flags & kMD5Tail) {
+		res += 't';
+	} else {
+		res += 'f';
+	}
+
+	switch (flags & kMD5MacMask) {
+	case kMD5MacDataFork: 
+		res += 'd';
+		break;
+
+	case kMD5MacResOrDataFork:
+		res += 'm';
+		break;
+
+	case kMD5MacResFork:
+		res += 'r';
+		break;
+
+	default:
+		break;
+	}
+
+	if (flags & kMD5Archive)
+		res += 'A';
+
+	return res;
 }
 
 Common::U32String generateUnknownGameReport(const DetectedGames &detectedGames, bool translate, bool fullPath, uint32 wordwrapAt) {
@@ -269,7 +243,7 @@ Common::U32String generateUnknownGameReport(const DetectedGames &detectedGames,
 
 		// Consolidate matched files across all engines and detection entries
 		for (FilePropertiesMap::const_iterator it = game.matchedFiles.begin(); it != game.matchedFiles.end(); it++) {
-			Common::String key = Common::String::format("%s:%s", md5PropToCachePrefix(it->_value.md5prop), it->_key.c_str());
+			Common::String key = Common::String::format("%s:%s", md5PropToCachePrefix(it->_value.md5prop).c_str(), it->_key.c_str());
 			matchedFiles.setVal(key, it->_value);
 		}
 	}
diff --git a/engines/game.h b/engines/game.h
index 3951ada2072..aa3a91cdf40 100644
--- a/engines/game.h
+++ b/engines/game.h
@@ -112,7 +112,7 @@ enum MD5Properties {
 	kMD5Archive              = 1 << 4,	// the desired file is inside an archive
 };
 
-const char *md5PropToCachePrefix(MD5Properties val);
+Common::String md5PropToCachePrefix(MD5Properties val);
 
 /**
  * A record describing the properties of a file. Used on the existing




More information about the Scummvm-git-logs mailing list