[Scummvm-git-logs] scummvm master -> cba24194e2cd7890e9d771df759220a4b5feab34

bluegr bluegr at gmail.com
Sat May 15 20:03:23 UTC 2021


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

Summary:
a8103e2eb5 COMMON: Add support for multi-file InstallShield cabinets
3f5ea7bbb7 COMMON: Add another file version to InstallShield cabinet code
cba24194e2 COMMON: Fix InstallShield cabinet version detection


Commit: a8103e2eb52c8f65af190d71dbb8500302b56022
    https://github.com/scummvm/scummvm/commit/a8103e2eb52c8f65af190d71dbb8500302b56022
Author: Kaloyan Chehlarski (strahy at outlook.com)
Date: 2021-05-15T23:03:19+03:00

Commit Message:
COMMON: Add support for multi-file InstallShield cabinets

Added support for InstallShield v6 cabinets, which can be made up of
multiple files. The interface for creating an Archive instance now takes
the base filename (e.g. the "data" in "data1.cab") for all cabinets,
including single-file ones.

Co-Authored-By: clone2727 <236052+clone2727 at users.noreply.github.com>
Co-Authored-By: Walter van Niftrik <615114+waltervn at users.noreply.github.com>

Changed paths:
    common/installshield_cab.cpp
    common/installshield_cab.h
    engines/agos/metaengine.cpp
    engines/nancy/nancy.cpp
    engines/tony/tony.cpp


diff --git a/common/installshield_cab.cpp b/common/installshield_cab.cpp
index b953952ea2..8dce48ba94 100644
--- a/common/installshield_cab.cpp
+++ b/common/installshield_cab.cpp
@@ -48,20 +48,26 @@
 #include "common/hash-str.h"
 #include "common/installshield_cab.h"
 #include "common/memstream.h"
+#include "common/substream.h"
+#include "common/ptr.h"
 #include "common/zlib.h"
 
 namespace Common {
 
+namespace {
+
 class InstallShieldCabinet : public Archive {
 public:
-	InstallShieldCabinet(SeekableReadStream *stream, DisposeAfterUse::Flag disposeAfterUse);
-	~InstallShieldCabinet();
+	InstallShieldCabinet();
+
+	bool open(const String &baseName);
+	void close();
 
 	// Archive API implementation
-	bool hasFile(const String &name) const;
-	int listMembers(ArchiveMemberList &list) const;
-	const ArchiveMemberPtr getMember(const String &name) const;
-	SeekableReadStream *createReadStreamForMember(const String &name) const;
+	virtual bool hasFile(const String &name) const;
+	virtual int listMembers(ArchiveMemberList &list) const;
+	virtual const ArchiveMemberPtr getMember(const String &name) const;
+	virtual SeekableReadStream *createReadStreamForMember(const String &name) const;
 
 private:
 	struct FileEntry {
@@ -69,54 +75,74 @@ private:
 		uint32 compressedSize;
 		uint32 offset;
 		uint16 flags;
+		uint16 volume;
 	};
 
+	int _version;
 	typedef HashMap<String, FileEntry, IgnoreCase_Hash, IgnoreCase_EqualTo> FileMap;
 	FileMap _map;
-	Common::SeekableReadStream *_stream;
-	DisposeAfterUse::Flag _disposeAfterUse;
-};
+	String _baseName;
 
-InstallShieldCabinet::~InstallShieldCabinet() {
-	_map.clear();
+	String getHeaderName() const;
+	String getVolumeName(uint volume) const;
+};
 
-	if (_disposeAfterUse == DisposeAfterUse::YES)
-		delete _stream;
+InstallShieldCabinet::InstallShieldCabinet() : _version(0) {
 }
 
-InstallShieldCabinet::InstallShieldCabinet(SeekableReadStream *stream, DisposeAfterUse::Flag disposeAfterUse) : _stream(stream), _disposeAfterUse(disposeAfterUse) {
-	// Note that we only support a limited subset of cabinet files
-	// Only single cabinet files and ones without data shared between
-	// cabinets.
+bool InstallShieldCabinet::open(const String &baseName) {
+	// Store the base name so we can generate file names
+	_baseName = baseName;
+
+	// Try to find the file
+	SeekableReadStream *header = SearchMan.createReadStreamForMember(getHeaderName());
+	if (!header)
+		header = SearchMan.createReadStreamForMember(getVolumeName(1));
+
+	if (!header) {
+		close();
+		return false;
+	}
+
+	// Note that we only support v5 and v6 cabinet files
 
 	// Check for the magic uint32
-	uint32 magic = _stream->readUint32LE();
+	uint32 magic = header->readUint32LE();
 	if (magic != 0x28635349) {
 		warning("InstallShieldCabinet::InstallShieldCabinet(): Magic ID doesn't match: expecting %x but got %x", 0x28635349, magic);
-		return;
+		close();
+		return false;
 	}
 
-	uint32 version = _stream->readUint32LE();
+	uint32 version = header->readUint32LE();
 
-	if (version != 0x01000004) {
+	switch(version) {
+	case 0x01000004:
+		_version = 5;
+		break;
+	case 0x0100600c:
+		_version = 6;
+		break;
+	default:
 		warning("Unsupported CAB version %08x", version);
-		return;
+		close();
+		return false;
 	}
 
-	/* uint32 volumeInfo = */ _stream->readUint32LE();
-	uint32 cabDescriptorOffset = _stream->readUint32LE();
-	/* uint32 cabDescriptorSize = */ _stream->readUint32LE();
+	/* uint32 volumeInfo = */ header->readUint32LE();
+	uint32 cabDescriptorOffset = header->readUint32LE();
+	/* uint32 cabDescriptorSize = */ header->readUint32LE();
 
-	_stream->seek(cabDescriptorOffset);
+	header->seek(cabDescriptorOffset);
 
-	_stream->skip(12);
-	uint32 fileTableOffset = _stream->readUint32LE();
-	_stream->skip(4);
-	uint32 fileTableSize = _stream->readUint32LE();
-	uint32 fileTableSize2 = _stream->readUint32LE();
-	uint32 directoryCount = _stream->readUint32LE();
-	_stream->skip(8);
-	uint32 fileCount = _stream->readUint32LE();
+	header->skip(12);
+	uint32 fileTableOffset = header->readUint32LE();
+	header->skip(4);
+	uint32 fileTableSize = header->readUint32LE();
+	uint32 fileTableSize2 = header->readUint32LE();
+	uint32 directoryCount = header->readUint32LE();
+	header->skip(8);
+	uint32 fileCount = header->readUint32LE();
 
 	if (fileTableSize != fileTableSize2)
 		warning("file table sizes do not match");
@@ -124,38 +150,72 @@ InstallShieldCabinet::InstallShieldCabinet(SeekableReadStream *stream, DisposeAf
 	// We're ignoring file groups and components since we
 	// should not need them. Moving on to the files...
 
-	_stream->seek(cabDescriptorOffset + fileTableOffset);
-	uint32 fileTableCount = directoryCount + fileCount;
-	uint32 *fileTableOffsets = new uint32[fileTableCount];
-	for (uint32 i = 0; i < fileTableCount; i++)
-		fileTableOffsets[i] = _stream->readUint32LE();
-
-	for (uint32 i = directoryCount; i < fileCount + directoryCount; i++) {
-		_stream->seek(cabDescriptorOffset + fileTableOffset + fileTableOffsets[i]);
-		uint32 nameOffset = _stream->readUint32LE();
-		/* uint32 directoryIndex = */ _stream->readUint32LE();
-
-		// First read in data needed by us to get at the file data
-		FileEntry entry;
-		entry.flags = _stream->readUint16LE();
-		entry.uncompressedSize = _stream->readUint32LE();
-		entry.compressedSize = _stream->readUint32LE();
-		_stream->skip(20);
-		entry.offset = _stream->readUint32LE();
-
-		// Then let's get the string
-		_stream->seek(cabDescriptorOffset + fileTableOffset + nameOffset);
-		String fileName;
-
-		char c = _stream->readByte();
-		while (c) {
-			fileName += c;
-			c = _stream->readByte();
+	if (_version >= 6) {
+		uint32 fileTableOffset2 = header->readUint32LE();
+
+		for (uint32 i = 0; i < fileCount; i++) {
+			header->seek(cabDescriptorOffset + fileTableOffset + fileTableOffset2 + i * 0x57);
+			FileEntry entry;
+			entry.flags = header->readUint16LE();
+			entry.uncompressedSize = header->readUint32LE();
+			header->skip(4);
+			entry.compressedSize = header->readUint32LE();
+			header->skip(4);
+			entry.offset = header->readUint32LE();
+			header->skip(36);
+			uint32 nameOffset = header->readUint32LE();
+			/* uint32 directoryIndex = */ header->readUint16LE();
+			header->skip(12);
+			/* entry.linkPrev = */ header->readUint32LE();
+			/* entry.linkNext = */ header->readUint32LE();
+			/* entry.linkFlags = */ header->readByte();
+			entry.volume = header->readUint16LE();
+
+			// Make sure the entry has a name and data inside the cab
+			if (nameOffset == 0 || entry.offset == 0 || (entry.flags & 8))
+				continue;
+
+			// Then let's get the string
+			header->seek(cabDescriptorOffset + fileTableOffset + nameOffset);
+			String fileName = header->readString();
+			_map[fileName] = entry;
+		}
+	} else {
+		header->seek(cabDescriptorOffset + fileTableOffset);
+		uint32 fileTableCount = directoryCount + fileCount;
+		Array<uint32> fileTableOffsets;
+		fileTableOffsets.resize(fileTableCount);
+		for (uint32 i = 0; i < fileTableCount; i++)
+			fileTableOffsets[i] = header->readUint32LE();
+
+		for (uint32 i = directoryCount; i < fileCount + directoryCount; i++) {
+			header->seek(cabDescriptorOffset + fileTableOffset + fileTableOffsets[i]);
+			uint32 nameOffset = header->readUint32LE();
+			/* uint32 directoryIndex = */ header->readUint32LE();
+
+			// First read in data needed by us to get at the file data
+			FileEntry entry;
+			entry.flags = header->readUint16LE();
+			entry.uncompressedSize = header->readUint32LE();
+			entry.compressedSize = header->readUint32LE();
+			header->skip(20);
+			entry.offset = header->readUint32LE();
+			entry.volume = 0;
+
+			// Then let's get the string
+			header->seek(cabDescriptorOffset + fileTableOffset + nameOffset);
+			String fileName = header->readString();
+			_map[fileName] = entry;
 		}
-		_map[fileName] = entry;
 	}
 
-	delete[] fileTableOffsets;
+	return true;
+}
+
+void InstallShieldCabinet::close() {
+	_baseName.clear();
+	_map.clear();
+	_version = 0;
 }
 
 bool InstallShieldCabinet::hasFile(const String &name) const {
@@ -179,16 +239,25 @@ SeekableReadStream *InstallShieldCabinet::createReadStreamForMember(const String
 
 	const FileEntry &entry = _map[name];
 
-	_stream->seek(entry.offset);
+	ScopedPtr<SeekableReadStream> stream(SearchMan.createReadStreamForMember(getVolumeName((entry.volume == 0) ? 1 : entry.volume)));
+	if (!stream) {
+		warning("Failed to open volume for file '%s'", name.c_str());
+		return 0;
+	}
+
+	if (!(entry.flags & 0x04)) {
+		// Uncompressed
+		// Return a substream
+		return new SeekableSubReadStream(stream.release(), entry.offset, entry.offset + entry.uncompressedSize, DisposeAfterUse::YES);
+	}
 
-	if (!(entry.flags & 0x04)) // Not compressed
-		return _stream->readStream(entry.uncompressedSize);
+	stream->seek(entry.offset);
 
 #ifdef USE_ZLIB
 	byte *src = (byte *)malloc(entry.compressedSize);
 	byte *dst = (byte *)malloc(entry.uncompressedSize);
 
-	_stream->read(src, entry.compressedSize);
+	stream->read(src, entry.compressedSize);
 
 	bool result = inflateZlibInstallShield(dst, entry.uncompressedSize, src, entry.compressedSize);
 	free(src);
@@ -206,8 +275,24 @@ SeekableReadStream *InstallShieldCabinet::createReadStreamForMember(const String
 #endif
 }
 
-Archive *makeInstallShieldArchive(SeekableReadStream *stream, DisposeAfterUse::Flag disposeAfterUse) {
-	return new InstallShieldCabinet(stream, disposeAfterUse);
+String InstallShieldCabinet::getHeaderName() const {
+	return _baseName + "1.hdr";
+}
+
+String InstallShieldCabinet::getVolumeName(uint volume) const {
+	return String::format("%s%d.cab", _baseName.c_str(), volume);
+}
+
+} // End of anonymous namespace
+
+Archive *makeInstallShieldArchive(const String &baseName) {
+	InstallShieldCabinet *cab = new InstallShieldCabinet();
+	if (!cab->open(baseName)) {
+		delete cab;
+		return 0;
+	}
+
+	return cab;
 }
 
-} // End of namespace AGOS
+} // End of namespace Common
diff --git a/common/installshield_cab.h b/common/installshield_cab.h
index 8cdba32ca0..42d30ef0ad 100644
--- a/common/installshield_cab.h
+++ b/common/installshield_cab.h
@@ -42,11 +42,13 @@ class SeekableReadStream;
 
 /**
  * This factory method creates an Archive instance corresponding to the content
- * of the InstallShield compressed stream.
+ * of the single- or multi-file InstallShield cabinet with the given base name
  *
  * May return 0 in case of a failure.
+ * 
+ * @param baseName The base filename, e.g. the "data" in "data1.cab"
  */
-Archive *makeInstallShieldArchive(SeekableReadStream *stream, DisposeAfterUse::Flag disposeAfterUse = DisposeAfterUse::YES);
+Archive *makeInstallShieldArchive(const Common::String &baseName);
 
 /** @} */
 
diff --git a/engines/agos/metaengine.cpp b/engines/agos/metaengine.cpp
index 803167cd17..67b5b99e05 100644
--- a/engines/agos/metaengine.cpp
+++ b/engines/agos/metaengine.cpp
@@ -207,10 +207,10 @@ void AGOSEngine::loadArchives() {
 				continue;
 
 			if (!SearchMan.hasArchive(ag->fileName)) {
-				Common::SeekableReadStream *stream = SearchMan.createReadStreamForMember(ag->fileName);
-
-				if (stream)
-					SearchMan.add(ag->fileName, Common::makeInstallShieldArchive(stream, DisposeAfterUse::YES), ag->fileType);
+				// Assumes the cabinet file is named data1.cab
+				Common::Archive *cabinet = Common::makeInstallShieldArchive("data");
+				if (cabinet)
+					SearchMan.add(ag->fileName, cabinet);
 			}
 		}
 	}
diff --git a/engines/nancy/nancy.cpp b/engines/nancy/nancy.cpp
index 02fc2cd266..fc1563c8ae 100644
--- a/engines/nancy/nancy.cpp
+++ b/engines/nancy/nancy.cpp
@@ -315,13 +315,9 @@ void NancyEngine::bootGameEngine() {
 	ConfMan.registerDefault("second_chance", false);
 
 	// Load archive
-	Common::SeekableReadStream *stream = SearchMan.createReadStreamForMember("data1.cab");
-	if (stream) {
-		Common::Archive *cab = Common::makeInstallShieldArchive(stream);
-
-		if (cab) {
-			SearchMan.add("data1.hdr", cab);
-		}
+	Common::Archive *cabinet = Common::makeInstallShieldArchive("data");
+	if (cabinet) {
+		SearchMan.add("data1.cab", cabinet);
 	}
 
 	_resource = new ResourceManager();
diff --git a/engines/tony/tony.cpp b/engines/tony/tony.cpp
index cd60740599..5c0490c7de 100644
--- a/engines/tony/tony.cpp
+++ b/engines/tony/tony.cpp
@@ -120,13 +120,9 @@ Common::ErrorCode TonyEngine::init() {
 		return Common::kUnknownError;
 
 	if (isCompressed()) {
-		Common::SeekableReadStream *stream = SearchMan.createReadStreamForMember("data1.cab");
-		if (!stream)
-			error("Failed to open data1.cab");
-
-		Common::Archive *cabinet = Common::makeInstallShieldArchive(stream);
+		Common::Archive *cabinet = Common::makeInstallShieldArchive("data");
 		if (!cabinet)
-			error("Failed to parse data1.cab");
+			error("Failed to open the InstallShield cabinet");
 
 		SearchMan.add("data1.cab", cabinet);
 	}


Commit: 3f5ea7bbb7728c48d7f4b930e64baba6523a1722
    https://github.com/scummvm/scummvm/commit/3f5ea7bbb7728c48d7f4b930e64baba6523a1722
Author: Kaloyan Chehlarski (strahy at outlook.com)
Date: 2021-05-15T23:03:19+03:00

Commit Message:
COMMON: Add another file version to InstallShield cabinet code

Added a previously unknown version id to the InstallShield code.

Changed paths:
    common/installshield_cab.cpp


diff --git a/common/installshield_cab.cpp b/common/installshield_cab.cpp
index 8dce48ba94..a8e122d0bd 100644
--- a/common/installshield_cab.cpp
+++ b/common/installshield_cab.cpp
@@ -123,6 +123,9 @@ bool InstallShieldCabinet::open(const String &baseName) {
 	case 0x0100600c:
 		_version = 6;
 		break;
+	case 0x020004B0: // Found in some Russian variants of Nancy Drew games. Possibly a malformed header
+		_version = 6;
+		break;
 	default:
 		warning("Unsupported CAB version %08x", version);
 		close();


Commit: cba24194e2cd7890e9d771df759220a4b5feab34
    https://github.com/scummvm/scummvm/commit/cba24194e2cd7890e9d771df759220a4b5feab34
Author: Kaloyan Chehlarski (strahy at outlook.com)
Date: 2021-05-15T23:03:19+03:00

Commit Message:
COMMON: Fix InstallShield cabinet version detection

The file version of InstallShield cabinets is now detected correctly.

Changed paths:
    common/installshield_cab.cpp


diff --git a/common/installshield_cab.cpp b/common/installshield_cab.cpp
index a8e122d0bd..8c5754f53a 100644
--- a/common/installshield_cab.cpp
+++ b/common/installshield_cab.cpp
@@ -104,30 +104,26 @@ bool InstallShieldCabinet::open(const String &baseName) {
 		return false;
 	}
 
-	// Note that we only support v5 and v6 cabinet files
-
-	// Check for the magic uint32
-	uint32 magic = header->readUint32LE();
-	if (magic != 0x28635349) {
-		warning("InstallShieldCabinet::InstallShieldCabinet(): Magic ID doesn't match: expecting %x but got %x", 0x28635349, magic);
+	// Check for the cab signature
+	uint32 signature = header->readUint32LE();
+	if (signature != 0x28635349) {
+		warning("InstallShieldCabinet::InstallShieldCabinet(): Signature doesn't match: expecting %x but got %x", 0x28635349, signature);
 		close();
 		return false;
 	}
 
-	uint32 version = header->readUint32LE();
-
-	switch(version) {
-	case 0x01000004:
-		_version = 5;
-		break;
-	case 0x0100600c:
-		_version = 6;
-		break;
-	case 0x020004B0: // Found in some Russian variants of Nancy Drew games. Possibly a malformed header
-		_version = 6;
-		break;
-	default:
-		warning("Unsupported CAB version %08x", version);
+	// We support cabinet versions 5 - 13, with some exceptions:
+	// - obfuscated files are not deobfuscated
+	// - no support for files split across volumes
+	// - single-volume v5 cabinets only
+
+	uint32 magicBytes = header->readUint32LE();
+	int shift = magicBytes >> 24;
+	_version = shift == 1 ? (magicBytes >> 12) & 0xf : (magicBytes & 0xffff) / 100;
+	_version = (_version == 0) ? 5 : _version;
+
+	if (_version < 5 || _version > 13) {
+		warning("Unsupported CAB version %d, magic bytes %08x", _version, magicBytes);
 		close();
 		return false;
 	}




More information about the Scummvm-git-logs mailing list