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

sev- noreply at scummvm.org
Sun Apr 9 11:02:35 UTC 2023


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

Summary:
9fb08caa4e COMMON: Upgrade InstallShield archive code
6efa793a25 COMMON: Fix InstallShield v5 file list reading
ba11cf87cd COMMON: Fix incorrectly marked split files in InstallShield cabinets
d99d325747 COMMON: Detect obfuscated and invalid files in InstallShield cabs


Commit: 9fb08caa4ed26ffd7761665279075a82097a6acd
    https://github.com/scummvm/scummvm/commit/9fb08caa4ed26ffd7761665279075a82097a6acd
Author: Kaloyan Chehlarski (strahy at outlook.com)
Date: 2023-04-09T13:02:30+02:00

Commit Message:
COMMON: Upgrade InstallShield archive code

Added support for files split across volumes, and for multi-volume
v5 archives to the InstallShield cabfile code.

Changed paths:
    common/compression/installshield_cab.cpp


diff --git a/common/compression/installshield_cab.cpp b/common/compression/installshield_cab.cpp
index f23f1da7da9..337158f5f80 100644
--- a/common/compression/installshield_cab.cpp
+++ b/common/compression/installshield_cab.cpp
@@ -69,6 +69,8 @@ public:
 	SeekableReadStream *createReadStreamForMember(const Path &path) const override;
 
 private:
+	enum Flags { kSplit = 1, kCompressed = 4 };
+
 	struct FileEntry {
 		uint32 uncompressedSize;
 		uint32 compressedSize;
@@ -77,10 +79,28 @@ private:
 		uint16 volume;
 	};
 
+	struct VolumeHeader {
+		int version;
+		uint32 cabDescriptorOffset;
+
+		uint32 dataOffset;
+		uint32 firstFileIndex;
+		uint32 lastFileIndex;
+		uint32 firstFileOffset;
+		uint32 firstFileSizeUncompressed;
+		uint32 firstFileSizeCompressed;
+		uint32 lastFileOffset;
+		uint32 lastFileSizeUncompressed;
+		uint32 lastFileSizeCompressed;
+	};
+
 	int _version;
 	typedef HashMap<String, FileEntry, IgnoreCase_Hash, IgnoreCase_EqualTo> FileMap;
 	FileMap _map;
 	String _baseName;
+	Common::Array<VolumeHeader> _volumeHeaders;
+
+	static bool readVolumeHeader(SeekableReadStream *volumeStream, VolumeHeader &inVolumeHeader);
 
 	String getHeaderName() const;
 	String getVolumeName(uint volume) const;
@@ -93,117 +113,141 @@ 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));
+	bool hasHeaderFile = false;
+	uint fileIndex = 0;
+	ScopedPtr<SeekableReadStream> file;
+
+	for (uint i = 1; hasHeaderFile == false; ++i) {
+		// First, try to open a header (.hdr) file
+		file.reset(SearchMan.createReadStreamForMember(getHeaderName()));
+		if (file) {
+			hasHeaderFile = true;
+		} else {
+			// No header file is present, so we have to iterate through
+			// all .cab volumes to get the file list
+			file.reset(SearchMan.createReadStreamForMember(getVolumeName(i)));
+			hasHeaderFile = false;
+		}
 
-	if (!header) {
-		close();
-		return false;
-	}
+		if (!file) {
+			if (i == 1) {
+				close();
+				return false;
+			}
+			return true;
+		}
 
-	// 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;
-	}
+		_volumeHeaders.push_back(VolumeHeader());
+		VolumeHeader &volumeHeader = _volumeHeaders.back();
+		if (!readVolumeHeader(file.get(), volumeHeader)) {
+			close();
+			return false;
+		}
 
-	// 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
+		_version = volumeHeader.version;
+
+		file->seek(volumeHeader.cabDescriptorOffset);
+
+		file->skip(12);
+		uint32 fileTableOffset = file->readUint32LE();
+		file->skip(4);
+		uint32 fileTableSize = file->readUint32LE();
+		uint32 fileTableSize2 = file->readUint32LE();
+		uint32 directoryCount = file->readUint32LE();
+		file->skip(8);
+		uint32 fileCount = file->readUint32LE();
+
+		if (fileTableSize != fileTableSize2)
+			warning("file table sizes do not match");
+
+		// We're ignoring file groups and components since we
+		// should not need them. Moving on to the files...
+
+		if (_version >= 6) {
+			uint32 fileTableOffset2 = file->readUint32LE();
+
+			for (uint32 j = 0; j < fileCount; j++) {
+				file->seek(volumeHeader.cabDescriptorOffset + fileTableOffset + fileTableOffset2 + j * 0x57);
+				FileEntry entry;
+				entry.flags = file->readUint16LE();
+				entry.uncompressedSize = file->readUint32LE();
+				file->skip(4);
+				entry.compressedSize = file->readUint32LE();
+				file->skip(4);
+				entry.offset = file->readUint32LE();
+				file->skip(36);
+				uint32 nameOffset = file->readUint32LE();
+				/* uint32 directoryIndex = */ file->readUint16LE();
+				file->skip(12);
+				/* entry.linkPrev = */ file->readUint32LE();
+				/* entry.linkNext = */ file->readUint32LE();
+				/* entry.linkFlags = */ file->readByte();
+				entry.volume = file->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
+				file->seek(volumeHeader.cabDescriptorOffset + fileTableOffset + nameOffset);
+				String fileName = file->readString();
+				_map[fileName] = entry;
+			}
+		} else {
+			file->seek(volumeHeader.cabDescriptorOffset + fileTableOffset);
+			uint32 fileTableCount = directoryCount + fileCount;
+			Array<uint32> fileTableOffsets;
+			fileTableOffsets.resize(fileTableCount);
+			for (uint32 j = 0; j < fileTableCount; j++)
+				fileTableOffsets[j] = file->readUint32LE();
+
+			for (uint32 j = directoryCount; j < fileCount + directoryCount; j++) {
+				file->seek(volumeHeader.cabDescriptorOffset + fileTableOffset + fileTableOffsets[j]);
+				uint32 nameOffset = file->readUint32LE();
+				/* uint32 directoryIndex = */ file->readUint32LE();
+
+				// First read in data needed by us to get at the file data
+				FileEntry entry;
+				entry.flags = file->readUint16LE();
+				entry.uncompressedSize = file->readUint32LE();
+				entry.compressedSize = file->readUint32LE();
+				file->skip(20);
+				entry.offset = file->readUint32LE();
+				entry.volume = i;
+
+				// Then let's get the string
+				file->seek(volumeHeader.cabDescriptorOffset + fileTableOffset + nameOffset);
+				String fileName = file->readString();
+
+				// Check if the file is split across volumes
+				if (fileIndex == volumeHeader.lastFileIndex &&
+					entry.compressedSize != volumeHeader.lastFileSizeCompressed) {
+					
+					entry.flags |= kSplit;
+				}
+
+				++fileIndex;
+
+				if (!_map.contains(fileName))
+					_map[fileName] = entry;
+			}
+		}
+	}
 
-	uint32 magicBytes = header->readUint32LE();
-	int shift = magicBytes >> 24;
-	_version = shift == 1 ? (magicBytes >> 12) & 0xf : (magicBytes & 0xffff) / 100;
-	_version = (_version == 0) ? 5 : _version;
+	uint volume = 1;
+	if (hasHeaderFile) {
+		// Remove the header file header from the array
+		_volumeHeaders.clear();
 
-	if (_version < 5 || _version > 13) {
-		warning("Unsupported CAB version %d, magic bytes %08x", _version, magicBytes);
-		close();
-		return false;
-	}
+		// Load the actual volume headers into the array
+		for (;;) {
+			file.reset(SearchMan.createReadStreamForMember(getVolumeName(volume++)));
+			if (!file.get()) {
+				break;
+			}
 
-	/* uint32 volumeInfo = */ header->readUint32LE();
-	uint32 cabDescriptorOffset = header->readUint32LE();
-	/* uint32 cabDescriptorSize = */ header->readUint32LE();
-
-	header->seek(cabDescriptorOffset);
-
-	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");
-
-	// We're ignoring file groups and components since we
-	// should not need them. Moving on to the files...
-
-	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;
+			_volumeHeaders.push_back(VolumeHeader());
+			readVolumeHeader(file.get(), _volumeHeaders.back());
 		}
 	}
 
@@ -213,6 +257,7 @@ bool InstallShieldCabinet::open(const String &baseName) {
 void InstallShieldCabinet::close() {
 	_baseName.clear();
 	_map.clear();
+    _volumeHeaders.clear();
 	_version = 0;
 }
 
@@ -240,25 +285,57 @@ SeekableReadStream *InstallShieldCabinet::createReadStreamForMember(const Path &
 
 	const FileEntry &entry = _map[name];
 
-	ScopedPtr<SeekableReadStream> stream(SearchMan.createReadStreamForMember(getVolumeName((entry.volume == 0) ? 1 : entry.volume)));
+	ScopedPtr<SeekableReadStream> stream(SearchMan.createReadStreamForMember(getVolumeName((entry.volume))));
 	if (!stream) {
 		warning("Failed to open volume for file '%s'", name.c_str());
 		return nullptr;
 	}
 
-	if (!(entry.flags & 0x04)) {
-		// Uncompressed
-		// Return a substream
-		return new SeekableSubReadStream(stream.release(), entry.offset, entry.offset + entry.uncompressedSize, DisposeAfterUse::YES);
+	byte *src = nullptr;
+	if (entry.flags & kSplit) {
+		// File is split across volumes
+		src = (byte *)malloc(entry.compressedSize);
+		uint bytesRead = 0;
+		uint volume = entry.volume;
+
+		// Read the first part of the split file
+		stream->seek(entry.offset);
+		stream->read(src, _volumeHeaders[volume - 1].lastFileSizeCompressed);
+		bytesRead += _volumeHeaders[volume - 1].lastFileSizeCompressed;
+
+		// 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 (!stream.get()) {
+				warning("Failed to read split file %s", name.c_str());
+				free(src);
+				return nullptr;
+			}
+			stream->seek(_volumeHeaders[volume - 1].firstFileOffset);
+			stream->read(src + bytesRead, _volumeHeaders[volume - 1].firstFileSizeCompressed);
+			bytesRead += _volumeHeaders[volume - 1].firstFileSizeCompressed;
+		}
 	}
 
-	stream->seek(entry.offset);
+	// Uncompressed file
+	if (!(entry.flags & kCompressed)) {
+		if (src == nullptr) {
+			// File not split, return a substream
+			return new SeekableSubReadStream(stream.release(), entry.offset, entry.offset + entry.uncompressedSize, DisposeAfterUse::YES);
+		} else {
+			// File split, return the assembled data
+			return new MemoryReadStream(src, entry.uncompressedSize, DisposeAfterUse::YES);
+		}		
+	}
 
 #ifdef USE_ZLIB
-	byte *src = (byte *)malloc(entry.compressedSize);
 	byte *dst = (byte *)malloc(entry.uncompressedSize);
 
-	stream->read(src, entry.compressedSize);
+	if (!src) {
+		src = (byte *)malloc(entry.compressedSize);
+		stream->seek(entry.offset);
+		stream->read(src, entry.compressedSize);
+	}
 
 	bool result = inflateZlibInstallShield(dst, entry.uncompressedSize, src, entry.compressedSize);
 	free(src);
@@ -272,10 +349,72 @@ SeekableReadStream *InstallShieldCabinet::createReadStreamForMember(const Path &
 	return new MemoryReadStream(dst, entry.uncompressedSize, DisposeAfterUse::YES);
 #else
 	warning("zlib required to extract compressed CAB file '%s'", name.c_str());
+	free(src);
 	return 0;
 #endif
 }
 
+bool InstallShieldCabinet::readVolumeHeader(SeekableReadStream *volumeStream, InstallShieldCabinet::VolumeHeader &inVolumeHeader) {
+	// Check for the cab signature
+	volumeStream->seek(0);
+	uint32 signature = volumeStream->readUint32LE();
+	if (signature != 0x28635349) {
+		warning("InstallShieldCabinet signature doesn't match: expecting %x but got %x", 0x28635349, signature);
+		return false;
+	}
+
+	// We support cabinet versions 5 - 13, but do not deobfuscate obfuscated files
+
+	uint32 magicBytes = volumeStream->readUint32LE();
+	int shift = magicBytes >> 24;
+	inVolumeHeader.version = shift == 1 ? (magicBytes >> 12) & 0xf : (magicBytes & 0xffff) / 100;
+	inVolumeHeader.version = (inVolumeHeader.version == 0) ? 5 : inVolumeHeader.version;
+
+	if (inVolumeHeader.version < 5 || inVolumeHeader.version > 13) {
+		warning("Unsupported CAB version %d, magic bytes %08x", inVolumeHeader.version, magicBytes);
+		return false;
+	}
+
+	/* uint32 volumeInfo = */ volumeStream->readUint32LE();
+	inVolumeHeader.cabDescriptorOffset = volumeStream->readUint32LE();
+	/* uint32 cabDescriptorSize = */ volumeStream->readUint32LE();
+
+	// Read the version-specific part of the header
+	if (inVolumeHeader.version == 5) {
+		inVolumeHeader.dataOffset = volumeStream->readUint32LE();
+		inVolumeHeader.firstFileIndex = volumeStream->readUint32LE();
+		inVolumeHeader.lastFileIndex = volumeStream->readUint32LE();
+		inVolumeHeader.firstFileOffset = volumeStream->readUint32LE();
+		inVolumeHeader.firstFileSizeUncompressed = volumeStream->readUint32LE();
+		inVolumeHeader.firstFileSizeCompressed = volumeStream->readUint32LE();
+		inVolumeHeader.lastFileOffset = volumeStream->readUint32LE();
+		inVolumeHeader.lastFileSizeUncompressed = volumeStream->readUint32LE();
+		inVolumeHeader.lastFileSizeCompressed = volumeStream->readUint32LE();
+
+		if (inVolumeHeader.lastFileOffset == 0)
+			inVolumeHeader.lastFileOffset = UINT_MAX;
+	} else {
+		inVolumeHeader.dataOffset = volumeStream->readUint32LE();
+		volumeStream->skip(4);
+		inVolumeHeader.firstFileIndex = volumeStream->readUint32LE();
+		inVolumeHeader.lastFileIndex = volumeStream->readUint32LE();
+		inVolumeHeader.firstFileOffset = volumeStream->readUint32LE();
+		volumeStream->skip(4);
+		inVolumeHeader.firstFileSizeUncompressed = volumeStream->readUint32LE();
+		volumeStream->skip(4);
+		inVolumeHeader.firstFileSizeCompressed = volumeStream->readUint32LE();
+		volumeStream->skip(4);
+		inVolumeHeader.lastFileOffset = volumeStream->readUint32LE();
+		volumeStream->skip(4);
+		inVolumeHeader.lastFileSizeUncompressed = volumeStream->readUint32LE();
+		volumeStream->skip(4);
+		inVolumeHeader.lastFileSizeCompressed = volumeStream->readUint32LE();
+		volumeStream->skip(4);
+	}
+
+	return true;
+}
+
 String InstallShieldCabinet::getHeaderName() const {
 	return _baseName + "1.hdr";
 }


Commit: 6efa793a25472e3d9009de82421cb5f34dfc3e32
    https://github.com/scummvm/scummvm/commit/6efa793a25472e3d9009de82421cb5f34dfc3e32
Author: Kaloyan Chehlarski (strahy at outlook.com)
Date: 2023-04-09T13:02:30+02:00

Commit Message:
COMMON: Fix InstallShield v5 file list reading

Fixed the InstallShield archive code to correctly load the file list in
multi-cabinet v5 archives.

Changed paths:
    common/compression/installshield_cab.cpp


diff --git a/common/compression/installshield_cab.cpp b/common/compression/installshield_cab.cpp
index 337158f5f80..0a28bfdd81b 100644
--- a/common/compression/installshield_cab.cpp
+++ b/common/compression/installshield_cab.cpp
@@ -113,141 +113,140 @@ bool InstallShieldCabinet::open(const String &baseName) {
 	// Store the base name so we can generate file names
 	_baseName = baseName;
 
-	bool hasHeaderFile = false;
 	uint fileIndex = 0;
 	ScopedPtr<SeekableReadStream> file;
 
-	for (uint i = 1; hasHeaderFile == false; ++i) {
-		// First, try to open a header (.hdr) file
-		file.reset(SearchMan.createReadStreamForMember(getHeaderName()));
-		if (file) {
-			hasHeaderFile = true;
-		} else {
-			// No header file is present, so we have to iterate through
-			// all .cab volumes to get the file list
-			file.reset(SearchMan.createReadStreamForMember(getVolumeName(i)));
-			hasHeaderFile = false;
-		}
-
-		if (!file) {
-			if (i == 1) {
-				close();
-				return false;
-			}
-			return true;
+	// First, open all the .cab files and read their headers
+	uint volume = 1;
+	for (;;) {
+		file.reset(SearchMan.createReadStreamForMember(getVolumeName(volume++)));
+		if (!file.get()) {
+			break;
 		}
 
 		_volumeHeaders.push_back(VolumeHeader());
-		VolumeHeader &volumeHeader = _volumeHeaders.back();
-		if (!readVolumeHeader(file.get(), volumeHeader)) {
-			close();
-			return false;
-		}
+		readVolumeHeader(file.get(), _volumeHeaders.back());
+	}
 
-		_version = volumeHeader.version;
-
-		file->seek(volumeHeader.cabDescriptorOffset);
-
-		file->skip(12);
-		uint32 fileTableOffset = file->readUint32LE();
-		file->skip(4);
-		uint32 fileTableSize = file->readUint32LE();
-		uint32 fileTableSize2 = file->readUint32LE();
-		uint32 directoryCount = file->readUint32LE();
-		file->skip(8);
-		uint32 fileCount = file->readUint32LE();
-
-		if (fileTableSize != fileTableSize2)
-			warning("file table sizes do not match");
-
-		// We're ignoring file groups and components since we
-		// should not need them. Moving on to the files...
-
-		if (_version >= 6) {
-			uint32 fileTableOffset2 = file->readUint32LE();
-
-			for (uint32 j = 0; j < fileCount; j++) {
-				file->seek(volumeHeader.cabDescriptorOffset + fileTableOffset + fileTableOffset2 + j * 0x57);
-				FileEntry entry;
-				entry.flags = file->readUint16LE();
-				entry.uncompressedSize = file->readUint32LE();
-				file->skip(4);
-				entry.compressedSize = file->readUint32LE();
-				file->skip(4);
-				entry.offset = file->readUint32LE();
-				file->skip(36);
-				uint32 nameOffset = file->readUint32LE();
-				/* uint32 directoryIndex = */ file->readUint16LE();
-				file->skip(12);
-				/* entry.linkPrev = */ file->readUint32LE();
-				/* entry.linkNext = */ file->readUint32LE();
-				/* entry.linkFlags = */ file->readByte();
-				entry.volume = file->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
-				file->seek(volumeHeader.cabDescriptorOffset + fileTableOffset + nameOffset);
-				String fileName = file->readString();
-				_map[fileName] = entry;
-			}
-		} else {
-			file->seek(volumeHeader.cabDescriptorOffset + fileTableOffset);
-			uint32 fileTableCount = directoryCount + fileCount;
-			Array<uint32> fileTableOffsets;
-			fileTableOffsets.resize(fileTableCount);
-			for (uint32 j = 0; j < fileTableCount; j++)
-				fileTableOffsets[j] = file->readUint32LE();
-
-			for (uint32 j = directoryCount; j < fileCount + directoryCount; j++) {
-				file->seek(volumeHeader.cabDescriptorOffset + fileTableOffset + fileTableOffsets[j]);
-				uint32 nameOffset = file->readUint32LE();
-				/* uint32 directoryIndex = */ file->readUint32LE();
-
-				// First read in data needed by us to get at the file data
-				FileEntry entry;
-				entry.flags = file->readUint16LE();
-				entry.uncompressedSize = file->readUint32LE();
-				entry.compressedSize = file->readUint32LE();
-				file->skip(20);
-				entry.offset = file->readUint32LE();
-				entry.volume = i;
-
-				// Then let's get the string
-				file->seek(volumeHeader.cabDescriptorOffset + fileTableOffset + nameOffset);
-				String fileName = file->readString();
-
-				// Check if the file is split across volumes
-				if (fileIndex == volumeHeader.lastFileIndex &&
-					entry.compressedSize != volumeHeader.lastFileSizeCompressed) {
-					
-					entry.flags |= kSplit;
-				}
+	// 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 (!file) {
+		close();
+		return false;
+	}
 
-				++fileIndex;
+	VolumeHeader headerHeader;
+	if (!readVolumeHeader(file.get(), headerHeader)) {
+		close();
+		return false;
+	}
 
-				if (!_map.contains(fileName))
-					_map[fileName] = entry;
-			}
+	_version = headerHeader.version;
+
+	file->seek(headerHeader.cabDescriptorOffset);
+
+	file->skip(12);
+	uint32 fileTableOffset = file->readUint32LE();
+	file->skip(4);
+	uint32 fileTableSize = file->readUint32LE();
+	uint32 fileTableSize2 = file->readUint32LE();
+	uint32 directoryCount = file->readUint32LE();
+	file->skip(8);
+	uint32 fileCount = file->readUint32LE();
+
+	if (fileTableSize != fileTableSize2)
+		warning("file table sizes do not match");
+
+	// We're ignoring file groups and components since we
+	// should not need them. Moving on to the files...
+
+	if (_version >= 6) {
+		uint32 fileTableOffset2 = file->readUint32LE();
+
+		for (uint32 j = 0; j < fileCount; j++) {
+			file->seek(headerHeader.cabDescriptorOffset + fileTableOffset + fileTableOffset2 + j * 0x57);
+			FileEntry entry;
+			entry.flags = file->readUint16LE();
+			entry.uncompressedSize = file->readUint32LE();
+			file->skip(4);
+			entry.compressedSize = file->readUint32LE();
+			file->skip(4);
+			entry.offset = file->readUint32LE();
+			file->skip(36);
+			uint32 nameOffset = file->readUint32LE();
+			/* uint32 directoryIndex = */ file->readUint16LE();
+			file->skip(12);
+			/* entry.linkPrev = */ file->readUint32LE();
+			/* entry.linkNext = */ file->readUint32LE();
+			/* entry.linkFlags = */ file->readByte();
+			entry.volume = file->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
+			file->seek(headerHeader.cabDescriptorOffset + fileTableOffset + nameOffset);
+			String fileName = file->readString();
+			_map[fileName] = entry;
 		}
-	}
+	} else {
+		file->seek(headerHeader.cabDescriptorOffset + fileTableOffset);
+		uint32 fileTableCount = directoryCount + fileCount;
+		Array<uint32> fileTableOffsets;
+		fileTableOffsets.resize(fileTableCount);
+		for (uint32 j = 0; j < fileTableCount; j++)
+			fileTableOffsets[j] = file->readUint32LE();
+
+		for (uint32 j = directoryCount; j < fileCount + directoryCount; j++) {
+			file->seek(headerHeader.cabDescriptorOffset + fileTableOffset + fileTableOffsets[j]);
+			uint32 nameOffset = file->readUint32LE();
+			/* uint32 directoryIndex = */ file->readUint32LE();
+
+			// First read in data needed by us to get at the file data
+			FileEntry entry;
+			entry.flags = file->readUint16LE();
+			entry.uncompressedSize = file->readUint32LE();
+			entry.compressedSize = file->readUint32LE();
+			file->skip(20);
+			entry.offset = file->readUint32LE();
+			entry.volume = 0;
+
+			for (uint i = 1; i < _volumeHeaders.size() + 1; ++i) {
+				// Check which volume the file is in
+				VolumeHeader &volumeHeader = _volumeHeaders[i - 1];
+				if (fileIndex >= volumeHeader.firstFileIndex && fileIndex <= volumeHeader.lastFileIndex) {
+					entry.volume = i;
+
+					// Check if the file is split across volumes
+					if (fileIndex == volumeHeader.lastFileIndex &&
+						entry.compressedSize != headerHeader.lastFileSizeCompressed) {
+						
+						entry.flags |= kSplit;
+					}
+
+					break;
+				}
+			}
 
-	uint volume = 1;
-	if (hasHeaderFile) {
-		// Remove the header file header from the array
-		_volumeHeaders.clear();
-
-		// Load the actual volume headers into the array
-		for (;;) {
-			file.reset(SearchMan.createReadStreamForMember(getVolumeName(volume++)));
-			if (!file.get()) {
-				break;
+			// Then let's get the string
+			file->seek(headerHeader.cabDescriptorOffset + fileTableOffset + nameOffset);
+			String fileName = file->readString();
+
+			if (entry.volume == 0) {
+				warning("Couldn't find the volume for file %s", fileName.c_str());
+				close();
+				return false;
 			}
 
-			_volumeHeaders.push_back(VolumeHeader());
-			readVolumeHeader(file.get(), _volumeHeaders.back());
+			++fileIndex;
+
+			_map[fileName] = entry;
 		}
 	}
 
@@ -382,6 +381,7 @@ bool InstallShieldCabinet::readVolumeHeader(SeekableReadStream *volumeStream, In
 	// Read the version-specific part of the header
 	if (inVolumeHeader.version == 5) {
 		inVolumeHeader.dataOffset = volumeStream->readUint32LE();
+		volumeStream->skip(4);
 		inVolumeHeader.firstFileIndex = volumeStream->readUint32LE();
 		inVolumeHeader.lastFileIndex = volumeStream->readUint32LE();
 		inVolumeHeader.firstFileOffset = volumeStream->readUint32LE();
@@ -390,9 +390,6 @@ bool InstallShieldCabinet::readVolumeHeader(SeekableReadStream *volumeStream, In
 		inVolumeHeader.lastFileOffset = volumeStream->readUint32LE();
 		inVolumeHeader.lastFileSizeUncompressed = volumeStream->readUint32LE();
 		inVolumeHeader.lastFileSizeCompressed = volumeStream->readUint32LE();
-
-		if (inVolumeHeader.lastFileOffset == 0)
-			inVolumeHeader.lastFileOffset = UINT_MAX;
 	} else {
 		inVolumeHeader.dataOffset = volumeStream->readUint32LE();
 		volumeStream->skip(4);


Commit: ba11cf87cde296cfb182e07ac8e8faf9f0be91b5
    https://github.com/scummvm/scummvm/commit/ba11cf87cde296cfb182e07ac8e8faf9f0be91b5
Author: Kaloyan Chehlarski (strahy at outlook.com)
Date: 2023-04-09T13:02:30+02:00

Commit Message:
COMMON: Fix incorrectly marked split files in InstallShield cabinets

Changed paths:
    common/compression/installshield_cab.cpp


diff --git a/common/compression/installshield_cab.cpp b/common/compression/installshield_cab.cpp
index 0a28bfdd81b..dfa37b6d7dd 100644
--- a/common/compression/installshield_cab.cpp
+++ b/common/compression/installshield_cab.cpp
@@ -225,7 +225,8 @@ bool InstallShieldCabinet::open(const String &baseName) {
 
 					// Check if the file is split across volumes
 					if (fileIndex == volumeHeader.lastFileIndex &&
-						entry.compressedSize != headerHeader.lastFileSizeCompressed) {
+						entry.compressedSize != headerHeader.lastFileSizeCompressed &&
+						headerHeader.lastFileSizeCompressed != 0) {
 						
 						entry.flags |= kSplit;
 					}


Commit: d99d325747c8590581f7c822fd182b925b5ac6f1
    https://github.com/scummvm/scummvm/commit/d99d325747c8590581f7c822fd182b925b5ac6f1
Author: Kaloyan Chehlarski (strahy at outlook.com)
Date: 2023-04-09T13:02:30+02:00

Commit Message:
COMMON: Detect obfuscated and invalid files in InstallShield cabs

Files marked as invalid are now ignored in all IS versions. Trying to
extract an obfuscated file now results in a warning instead of just failing.

Changed paths:
    common/compression/installshield_cab.cpp


diff --git a/common/compression/installshield_cab.cpp b/common/compression/installshield_cab.cpp
index dfa37b6d7dd..b377d5d3d33 100644
--- a/common/compression/installshield_cab.cpp
+++ b/common/compression/installshield_cab.cpp
@@ -69,7 +69,7 @@ public:
 	SeekableReadStream *createReadStreamForMember(const Path &path) const override;
 
 private:
-	enum Flags { kSplit = 1, kCompressed = 4 };
+	enum Flags { kSplit = 1, kObfuscated = 2, kCompressed = 4, kInvalid = 8 };
 
 	struct FileEntry {
 		uint32 uncompressedSize;
@@ -187,7 +187,7 @@ bool InstallShieldCabinet::open(const String &baseName) {
 			entry.volume = file->readUint16LE();
 
 			// Make sure the entry has a name and data inside the cab
-			if (nameOffset == 0 || entry.offset == 0 || (entry.flags & 8))
+			if (nameOffset == 0 || entry.offset == 0 || (entry.flags & kInvalid))
 				continue;
 
 			// Then let's get the string
@@ -217,6 +217,10 @@ bool InstallShieldCabinet::open(const String &baseName) {
 			entry.offset = file->readUint32LE();
 			entry.volume = 0;
 
+			// Make sure the entry has a name and data inside the cab
+			if (nameOffset == 0 || entry.offset == 0 || (entry.flags & kInvalid))
+				continue;
+
 			for (uint i = 1; i < _volumeHeaders.size() + 1; ++i) {
 				// Check which volume the file is in
 				VolumeHeader &volumeHeader = _volumeHeaders[i - 1];
@@ -285,6 +289,11 @@ SeekableReadStream *InstallShieldCabinet::createReadStreamForMember(const Path &
 
 	const FileEntry &entry = _map[name];
 
+    if (entry.flags & kObfuscated) {
+        warning("Cannot extract obfuscated file %s", name.c_str());
+        return nullptr;
+    }
+
 	ScopedPtr<SeekableReadStream> stream(SearchMan.createReadStreamForMember(getVolumeName((entry.volume))));
 	if (!stream) {
 		warning("Failed to open volume for file '%s'", name.c_str());




More information about the Scummvm-git-logs mailing list