[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