[Scummvm-git-logs] scummvm master -> 19a3f6ade71a810dbdf1d5fe5b329911830744ac
elasota
noreply at scummvm.org
Tue Nov 29 23:59:49 UTC 2022
This automated email contains information about 2 new commits which have been
pushed to the 'scummvm' repo located at https://github.com/scummvm/scummvm .
Summary:
d8058f94f3 MTROPOLIS: VISE 3 archive improvements.
19a3f6ade7 MTROPOLIS: Split off VISE archive handling.
Commit: d8058f94f3fc1a54024d063bfa9af3d9ff0f069b
https://github.com/scummvm/scummvm/commit/d8058f94f3fc1a54024d063bfa9af3d9ff0f069b
Author: elasota (ejlasota at gmail.com)
Date: 2022-11-29T18:58:13-05:00
Commit Message:
MTROPOLIS: VISE 3 archive improvements.
Changed paths:
engines/mtropolis/boot.cpp
diff --git a/engines/mtropolis/boot.cpp b/engines/mtropolis/boot.cpp
index da6dd243479..bb370ee8d58 100644
--- a/engines/mtropolis/boot.cpp
+++ b/engines/mtropolis/boot.cpp
@@ -425,12 +425,28 @@ private:
uint32 uncompressedResSize;
uint32 positionInArchive;
- Common::String fileName;
+ uint16 containingDirectory;
+
+ Common::String name;
+ Common::String fullPath;
+ };
+
+ struct VISE3DirectoryDesc {
+ uint16 containingDirectory;
+
+ Common::String name;
+ Common::String fullPath;
};
class VISE3ArchiveMember : public Common::ArchiveMember {
public:
- VISE3ArchiveMember(Common::SeekableReadStream *archiveStream, const VISE3FileDesc *fileDesc, bool isResFork);
+ enum SubstreamType {
+ kSubstreamTypeData,
+ kSubstreamTypeResource,
+ kSubstreamTypeFinderInfo,
+ };
+
+ VISE3ArchiveMember(Common::SeekableReadStream *archiveStream, const VISE3FileDesc *fileDesc, SubstreamType substreamType);
Common::SeekableReadStream *createReadStream() const override;
Common::String getName() const override;
@@ -438,7 +454,7 @@ private:
private:
Common::SeekableReadStream *_archiveStream;
const VISE3FileDesc *_fileDesc;
- bool _isResFork;
+ SubstreamType _substreamType;
};
class VISE3Archive : public Common::Archive {
@@ -453,19 +469,36 @@ private:
Common::SeekableReadStream *createReadStreamForMember(const Common::Path &path) const override;
private:
- bool getFileDescIndex(const Common::Path &path, uint &anOutIndex, bool &anOutIsResFork) const;
+ bool getFileDescIndex(const Common::Path &path, uint &outIndex, VISE3ArchiveMember::SubstreamType &outSubstreamType) const;
Common::SeekableReadStream *_archiveStream;
Common::Array<VISE3FileDesc> _fileDescs;
+ Common::Array<VISE3DirectoryDesc> _directoryDescs;
};
bool _isMac;
};
-SPQRGameDataHandler::VISE3ArchiveMember::VISE3ArchiveMember(Common::SeekableReadStream *archiveStream, const SPQRGameDataHandler::VISE3FileDesc *fileDesc, bool isResFork) : _archiveStream(archiveStream), _fileDesc(fileDesc), _isResFork(isResFork) {
+SPQRGameDataHandler::VISE3ArchiveMember::VISE3ArchiveMember(Common::SeekableReadStream *archiveStream, const SPQRGameDataHandler::VISE3FileDesc *fileDesc, SubstreamType substreamType)
+ : _archiveStream(archiveStream), _fileDesc(fileDesc), _substreamType(substreamType) {
}
Common::SeekableReadStream *SPQRGameDataHandler::VISE3ArchiveMember::createReadStream() const {
+ if (_substreamType == kSubstreamTypeFinderInfo) {
+ Common::MacFinderInfoData *finfoData = static_cast<Common::MacFinderInfoData*>(malloc(sizeof(Common::MacFinderInfoData)));
+
+ if (!finfoData)
+ return nullptr;
+
+ Common::MacFinderInfo finfo;
+ memcpy(finfo.type, _fileDesc->type, 4);
+ memcpy(finfo.creator, _fileDesc->creator, 4);
+
+ *finfoData = finfo.toData();
+
+ return new Common::MemoryReadStream(reinterpret_cast<const byte *>(finfoData), sizeof(Common::MacFinderInfoData), DisposeAfterUse::YES);
+ }
+
static const uint8 vl3DeobfuscationTable[] = {
0x6a, 0xb7, 0x36, 0xec, 0x15, 0xd9, 0xc8, 0x73, 0xe8, 0x38, 0x9a, 0xdf, 0x21, 0x25, 0xd0, 0xcc,
0xfd, 0xdc, 0x16, 0xd7, 0xe3, 0x43, 0x05, 0xc5, 0x8f, 0x48, 0xda, 0xf2, 0x3f, 0x10, 0x23, 0x6c,
@@ -485,11 +518,13 @@ Common::SeekableReadStream *SPQRGameDataHandler::VISE3ArchiveMember::createReadS
0x86, 0xdd, 0x5f, 0x42, 0xd3, 0x02, 0x61, 0x95, 0x0c, 0x5c, 0xa5, 0xcd, 0xc0, 0x07, 0xe2, 0xf3,
};
- uint32 uncompressedSize = _isResFork ? _fileDesc->uncompressedResSize : _fileDesc->uncompressedDataSize;
- uint32 compressedSize = _isResFork ? _fileDesc->compressedResSize : _fileDesc->compressedDataSize;
+ const bool isResFork = (_substreamType == kSubstreamTypeResource);
+
+ uint32 uncompressedSize = isResFork ? _fileDesc->uncompressedResSize : _fileDesc->uncompressedDataSize;
+ uint32 compressedSize = isResFork ? _fileDesc->compressedResSize : _fileDesc->compressedDataSize;
uint32 filePosition = _fileDesc->positionInArchive;
- if (_isResFork)
+ if (isResFork)
filePosition += _fileDesc->compressedDataSize;
if (uncompressedSize == 0)
@@ -517,6 +552,15 @@ Common::SeekableReadStream *SPQRGameDataHandler::VISE3ArchiveMember::createReadS
if (!decompressedData)
return nullptr;
+
+ // WARNING/TODO: Based on reverse engineering of the "Dcmp" resource from the installer, which contains the decompression code,
+ // the bitstream format is usually just deflate, however there is one difference: Stored blocks are flushed to a 2-byte boundary
+ // instead of 1-byte boundary, because the decompressor reads 2 bytes at a time. This doesn't usually matter because stored
+ // blocks are very rare in practice on compressible data, and small files with only 1 compressible block are already 2-byte
+ // aligned on the first block.
+ //
+ // If this turns out to be significant, then this will need to be updated to pass information to the deflate decompressor to
+ // handle the non-standard behavior.
if (!Common::inflateZlibHeaderless(decompressedData, uncompressedSize, &compressedData[0], compressedSize)) {
free(decompressedData);
return nullptr;
@@ -526,10 +570,12 @@ Common::SeekableReadStream *SPQRGameDataHandler::VISE3ArchiveMember::createReadS
}
Common::String SPQRGameDataHandler::VISE3ArchiveMember::getName() const {
- if (_isResFork)
- return _fileDesc->fileName + ".rsrc";
+ if (_substreamType == kSubstreamTypeFinderInfo)
+ return _fileDesc->fullPath + ".finf";
+ else if (_substreamType == kSubstreamTypeResource)
+ return _fileDesc->fullPath + ".rsrc";
else
- return _fileDesc->fileName;
+ return _fileDesc->fullPath;
}
SPQRGameDataHandler::VISE3FileDesc::VISE3FileDesc() : type{ 0, 0, 0, 0 }, creator{ 0, 0, 0, 0 }, compressedDataSize(0), uncompressedDataSize(0), compressedResSize(0), uncompressedResSize(0), positionInArchive(0) {
@@ -542,6 +588,15 @@ SPQRGameDataHandler::VISE3Archive::VISE3Archive(Common::SeekableReadStream *arch
uint32 catalogPosition = READ_BE_UINT32(vl3Header + 36);
+ uint32 archiveVersion = READ_BE_UINT32(vl3Header + 16);
+
+ if (archiveVersion == 0x80010202)
+ debug(3, "Detected VISE 3 archive as 3.5 Lite");
+ else if (archiveVersion == 0x80010300)
+ debug(3, "Detected VISE 3 archive as 3.6 Lite");
+ else
+ error("Unrecognized VISE 3 archive version");
+
if (!archiveStream->seek(catalogPosition))
error("Failed to seek to VISE 3 catalog");
@@ -561,8 +616,23 @@ SPQRGameDataHandler::VISE3Archive::VISE3Archive(Common::SeekableReadStream *arch
if (archiveStream->read(directoryData, 78) != 78)
error("Failed to read VISE 3 directory");
+ // 3.6 Lite archives have 6 extra bytes for something
+ if (archiveVersion >= 0x80010300)
+ archiveStream->seek(6, SEEK_CUR);
+
+ VISE3DirectoryDesc desc;
+ desc.containingDirectory = READ_BE_UINT16(directoryData + 68);
+
uint8 nameLength = directoryData[76];
- archiveStream->seek(nameLength, SEEK_CUR);
+
+ if (nameLength > 0) {
+ char fileNameChars[256];
+ if (archiveStream->read(fileNameChars, nameLength) != nameLength)
+ error("Failed to read VISE 3 directory name");
+ desc.name = Common::String(fileNameChars, nameLength);
+ }
+
+ _directoryDescs.push_back(desc);
} else if (entryMagic[0] == 'F') {
uint8 fileData[120];
if (archiveStream->read(fileData, 120) != 120)
@@ -575,6 +645,7 @@ SPQRGameDataHandler::VISE3Archive::VISE3Archive(Common::SeekableReadStream *arch
desc.uncompressedDataSize = READ_BE_UINT32(fileData + 68);
desc.compressedResSize = READ_BE_UINT32(fileData + 72);
desc.uncompressedResSize = READ_BE_UINT32(fileData + 76);
+ desc.containingDirectory = READ_BE_UINT16(fileData + 92);
desc.positionInArchive = READ_BE_UINT32(fileData + 96);
uint8 nameLength = fileData[118];
@@ -583,7 +654,7 @@ SPQRGameDataHandler::VISE3Archive::VISE3Archive(Common::SeekableReadStream *arch
char fileNameChars[256];
if (archiveStream->read(fileNameChars, nameLength) != nameLength)
error("Failed to read VISE 3 file name");
- desc.fileName = Common::String(fileNameChars, nameLength);
+ desc.name = Common::String(fileNameChars, nameLength);
}
_fileDescs.push_back(desc);
@@ -591,12 +662,35 @@ SPQRGameDataHandler::VISE3Archive::VISE3Archive(Common::SeekableReadStream *arch
error("Unknown VISE 3 catalog entry item type");
}
}
+
+ // Generate full paths
+ for (VISE3DirectoryDesc &dirDesc : _directoryDescs) {
+ if (dirDesc.containingDirectory == 0)
+ dirDesc.fullPath = dirDesc.name;
+ else {
+ if (dirDesc.containingDirectory > _directoryDescs.size())
+ error("VISE 3 containing directory index was invalid");
+
+ dirDesc.fullPath = _directoryDescs[dirDesc.containingDirectory - 1].fullPath + ":" + dirDesc.name;
+ }
+ }
+
+ for (VISE3FileDesc &fileDesc : _fileDescs) {
+ if (fileDesc.containingDirectory == 0)
+ fileDesc.fullPath = fileDesc.name;
+ else {
+ if (fileDesc.containingDirectory > _directoryDescs.size())
+ error("VISE 3 containing directory index was invalid");
+
+ fileDesc.fullPath = _directoryDescs[fileDesc.containingDirectory - 1].fullPath + ":" + fileDesc.name;
+ }
+ }
}
const SPQRGameDataHandler::VISE3FileDesc *SPQRGameDataHandler::VISE3Archive::getFileDesc(const Common::Path &path) const {
Common::String convertedPath = path.toString(':');
for (const VISE3FileDesc &desc : _fileDescs) {
- if (desc.fileName == convertedPath)
+ if (desc.fullPath == convertedPath)
return &desc;
}
@@ -605,8 +699,8 @@ const SPQRGameDataHandler::VISE3FileDesc *SPQRGameDataHandler::VISE3Archive::get
bool SPQRGameDataHandler::VISE3Archive::hasFile(const Common::Path &path) const {
uint index = 0;
- bool isResFork = false;
- return getFileDescIndex(path, index, isResFork);
+ VISE3ArchiveMember::SubstreamType substreamType = VISE3ArchiveMember::SubstreamType::kSubstreamTypeData;
+ return getFileDescIndex(path, index, substreamType);
}
int SPQRGameDataHandler::VISE3Archive::listMembers(Common::ArchiveMemberList &list) const {
@@ -615,24 +709,27 @@ int SPQRGameDataHandler::VISE3Archive::listMembers(Common::ArchiveMemberList &li
const VISE3FileDesc &desc = _fileDescs[fileIndex];
if (desc.uncompressedDataSize) {
- list.push_back(Common::ArchiveMemberPtr(new VISE3ArchiveMember(_archiveStream, &desc, false)));
+ list.push_back(Common::ArchiveMemberPtr(new VISE3ArchiveMember(_archiveStream, &desc, VISE3ArchiveMember::kSubstreamTypeData)));
numMembers++;
}
if (desc.uncompressedResSize) {
- list.push_back(Common::ArchiveMemberPtr(new VISE3ArchiveMember(_archiveStream, &desc, true)));
+ list.push_back(Common::ArchiveMemberPtr(new VISE3ArchiveMember(_archiveStream, &desc, VISE3ArchiveMember::kSubstreamTypeResource)));
numMembers++;
}
+
+ list.push_back(Common::ArchiveMemberPtr(new VISE3ArchiveMember(nullptr, &desc, VISE3ArchiveMember::kSubstreamTypeFinderInfo)));
+ numMembers++;
}
return numMembers;
}
const Common::ArchiveMemberPtr SPQRGameDataHandler::VISE3Archive::getMember(const Common::Path &path) const {
uint descIndex = 0;
- bool isResFork = false;
- if (!getFileDescIndex(path, descIndex, isResFork))
+ VISE3ArchiveMember::SubstreamType substreamType = VISE3ArchiveMember::SubstreamType::kSubstreamTypeData;
+ if (!getFileDescIndex(path, descIndex, substreamType))
return nullptr;
- return Common::ArchiveMemberPtr(new VISE3ArchiveMember(_archiveStream, &_fileDescs[descIndex], isResFork));
+ return Common::ArchiveMemberPtr(new VISE3ArchiveMember(_archiveStream, &_fileDescs[descIndex], substreamType));
}
Common::SeekableReadStream *SPQRGameDataHandler::VISE3Archive::createReadStreamForMember(const Common::Path &path) const {
@@ -643,23 +740,28 @@ Common::SeekableReadStream *SPQRGameDataHandler::VISE3Archive::createReadStreamF
return archiveMember->createReadStream();
}
-bool SPQRGameDataHandler::VISE3Archive::getFileDescIndex(const Common::Path &path, uint &anOutIndex, bool &anOutIsResFork) const {
+bool SPQRGameDataHandler::VISE3Archive::getFileDescIndex(const Common::Path &path, uint &outIndex, VISE3ArchiveMember::SubstreamType &outSubstreamType) const {
Common::String convertedPath = path.toString(':');
- bool isResFork = false;
+ VISE3ArchiveMember::SubstreamType substreamType = VISE3ArchiveMember::SubstreamType::kSubstreamTypeData;
if (convertedPath.hasSuffix(".rsrc")) {
- isResFork = true;
+ substreamType = VISE3ArchiveMember::SubstreamType::kSubstreamTypeResource;
+ convertedPath = convertedPath.substr(0, convertedPath.size() - 5);
+ } else if (convertedPath.hasSuffix(".finf")) {
+ substreamType = VISE3ArchiveMember::SubstreamType::kSubstreamTypeFinderInfo;
convertedPath = convertedPath.substr(0, convertedPath.size() - 5);
}
for (uint descIndex = 0; descIndex < _fileDescs.size(); descIndex++) {
const VISE3FileDesc &desc = _fileDescs[descIndex];
- if (desc.fileName == convertedPath) {
- if ((isResFork ? desc.uncompressedResSize : desc.uncompressedDataSize) == 0)
+ if (desc.fullPath == convertedPath) {
+ if (substreamType == VISE3ArchiveMember::SubstreamType::kSubstreamTypeData && desc.uncompressedDataSize == 0)
+ return false;
+ if (substreamType == VISE3ArchiveMember::SubstreamType::kSubstreamTypeResource && desc.uncompressedResSize == 0)
return false;
- anOutIsResFork = isResFork;
- anOutIndex = descIndex;
+ outSubstreamType = substreamType;
+ outIndex = descIndex;
return true;
}
}
@@ -681,11 +783,11 @@ void SPQRGameDataHandler::addPlugIns(ProjectDescription &projectDesc, const Comm
void SPQRGameDataHandler::unpackAdditionalFiles(Common::Array<Common::SharedPtr<ProjectPersistentResource> > &persistentResources, Common::Array<FileIdentification> &files) {
if (_isMac) {
const MacVISE3InstallerUnpackRequest unpackRequests[] = {
- {"Basic.rPP", false, true, MTFT_EXTENSION},
- {"Extras.rPP", false, true, MTFT_EXTENSION},
- {"mCursors.cPP", false, true, MTFT_EXTENSION},
- {"SPQR PPC Start", false, true, MTFT_PLAYER},
- {"Data File SPQR", true, false, MTFT_MAIN},
+ {"SPQR:Resource:Basic.rPP", false, true, MTFT_EXTENSION},
+ {"SPQR:Resource:Extras.rPP", false, true, MTFT_EXTENSION},
+ {"SPQR:Resource:mCursors.cPP", false, true, MTFT_EXTENSION},
+ {"SPQR:SPQR PPC Start", false, true, MTFT_PLAYER},
+ {"SPQR:Data File SPQR", true, false, MTFT_MAIN},
};
Common::SharedPtr<Common::MacResManager> installerResMan(new Common::MacResManager());
@@ -709,7 +811,7 @@ void SPQRGameDataHandler::unpackAdditionalFiles(Common::Array<Common::SharedPtr<
error("Couldn't find file '%s' in VISE 3 archive", request.fileName);
FileIdentification ident;
- ident.fileName = fileDesc->fileName;
+ ident.fileName = fileDesc->fullPath;
ident.macCreator.value = MKTAG(fileDesc->creator[0], fileDesc->creator[1], fileDesc->creator[2], fileDesc->creator[3]);
ident.macType.value = MKTAG(fileDesc->type[0], fileDesc->type[1], fileDesc->type[2], fileDesc->type[3]);
ident.category = request.fileType;
Commit: 19a3f6ade71a810dbdf1d5fe5b329911830744ac
https://github.com/scummvm/scummvm/commit/19a3f6ade71a810dbdf1d5fe5b329911830744ac
Author: elasota (ejlasota at gmail.com)
Date: 2022-11-29T18:58:13-05:00
Commit Message:
MTROPOLIS: Split off VISE archive handling.
Changed paths:
A engines/mtropolis/vise.cpp
A engines/mtropolis/vise.h
engines/mtropolis/boot.cpp
engines/mtropolis/module.mk
diff --git a/engines/mtropolis/boot.cpp b/engines/mtropolis/boot.cpp
index bb370ee8d58..5462fdbaf26 100644
--- a/engines/mtropolis/boot.cpp
+++ b/engines/mtropolis/boot.cpp
@@ -41,6 +41,8 @@
#include "mtropolis/plugin/standard.h"
#include "mtropolis/plugins.h"
+#include "mtropolis/vise.h"
+
namespace MTropolis {
namespace Boot {
@@ -414,361 +416,9 @@ public:
void unpackAdditionalFiles(Common::Array<Common::SharedPtr<ProjectPersistentResource> > &persistentResources, Common::Array<FileIdentification> &files) override;
private:
- struct VISE3FileDesc {
- VISE3FileDesc();
-
- char type[4];
- char creator[4];
- uint32 compressedDataSize;
- uint32 uncompressedDataSize;
- uint32 compressedResSize;
- uint32 uncompressedResSize;
- uint32 positionInArchive;
-
- uint16 containingDirectory;
-
- Common::String name;
- Common::String fullPath;
- };
-
- struct VISE3DirectoryDesc {
- uint16 containingDirectory;
-
- Common::String name;
- Common::String fullPath;
- };
-
- class VISE3ArchiveMember : public Common::ArchiveMember {
- public:
- enum SubstreamType {
- kSubstreamTypeData,
- kSubstreamTypeResource,
- kSubstreamTypeFinderInfo,
- };
-
- VISE3ArchiveMember(Common::SeekableReadStream *archiveStream, const VISE3FileDesc *fileDesc, SubstreamType substreamType);
-
- Common::SeekableReadStream *createReadStream() const override;
- Common::String getName() const override;
-
- private:
- Common::SeekableReadStream *_archiveStream;
- const VISE3FileDesc *_fileDesc;
- SubstreamType _substreamType;
- };
-
- class VISE3Archive : public Common::Archive {
- public:
- explicit VISE3Archive(Common::SeekableReadStream *archiveStream);
-
- const VISE3FileDesc *getFileDesc(const Common::Path &path) const;
-
- bool hasFile(const Common::Path &path) const override;
- int listMembers(Common::ArchiveMemberList &list) const override;
- const Common::ArchiveMemberPtr getMember(const Common::Path &path) const override;
- Common::SeekableReadStream *createReadStreamForMember(const Common::Path &path) const override;
-
- private:
- bool getFileDescIndex(const Common::Path &path, uint &outIndex, VISE3ArchiveMember::SubstreamType &outSubstreamType) const;
-
- Common::SeekableReadStream *_archiveStream;
- Common::Array<VISE3FileDesc> _fileDescs;
- Common::Array<VISE3DirectoryDesc> _directoryDescs;
- };
-
bool _isMac;
};
-SPQRGameDataHandler::VISE3ArchiveMember::VISE3ArchiveMember(Common::SeekableReadStream *archiveStream, const SPQRGameDataHandler::VISE3FileDesc *fileDesc, SubstreamType substreamType)
- : _archiveStream(archiveStream), _fileDesc(fileDesc), _substreamType(substreamType) {
-}
-
-Common::SeekableReadStream *SPQRGameDataHandler::VISE3ArchiveMember::createReadStream() const {
- if (_substreamType == kSubstreamTypeFinderInfo) {
- Common::MacFinderInfoData *finfoData = static_cast<Common::MacFinderInfoData*>(malloc(sizeof(Common::MacFinderInfoData)));
-
- if (!finfoData)
- return nullptr;
-
- Common::MacFinderInfo finfo;
- memcpy(finfo.type, _fileDesc->type, 4);
- memcpy(finfo.creator, _fileDesc->creator, 4);
-
- *finfoData = finfo.toData();
-
- return new Common::MemoryReadStream(reinterpret_cast<const byte *>(finfoData), sizeof(Common::MacFinderInfoData), DisposeAfterUse::YES);
- }
-
- static const uint8 vl3DeobfuscationTable[] = {
- 0x6a, 0xb7, 0x36, 0xec, 0x15, 0xd9, 0xc8, 0x73, 0xe8, 0x38, 0x9a, 0xdf, 0x21, 0x25, 0xd0, 0xcc,
- 0xfd, 0xdc, 0x16, 0xd7, 0xe3, 0x43, 0x05, 0xc5, 0x8f, 0x48, 0xda, 0xf2, 0x3f, 0x10, 0x23, 0x6c,
- 0x77, 0x7c, 0xf9, 0xa0, 0xa3, 0xe9, 0xed, 0x46, 0x8b, 0xd8, 0xac, 0x54, 0xce, 0x2d, 0x19, 0x5e,
- 0x6d, 0x7d, 0x87, 0x5d, 0xfa, 0x5b, 0x9b, 0xe0, 0xc7, 0xee, 0x9f, 0x52, 0xa9, 0xb9, 0x0a, 0xd1,
- 0xfe, 0x78, 0x76, 0x4a, 0x3d, 0x44, 0x5a, 0x96, 0x90, 0x1f, 0x26, 0x9d, 0x58, 0x1b, 0x8e, 0x57,
- 0x59, 0xc3, 0x0b, 0x6b, 0xfc, 0x1d, 0xe6, 0xa2, 0x7f, 0x92, 0x4f, 0x40, 0xb4, 0x06, 0x72, 0x4d,
- 0xf4, 0x34, 0xaa, 0xd2, 0x49, 0xad, 0xef, 0x22, 0x1a, 0xb5, 0xba, 0xbf, 0x29, 0x68, 0x89, 0x93,
- 0x3e, 0x32, 0x04, 0xf5, 0xde, 0xe1, 0x6f, 0xfb, 0x67, 0xe4, 0x7e, 0x08, 0xaf, 0xf0, 0xab, 0x41,
- 0x82, 0xea, 0x50, 0x0f, 0x2a, 0xc6, 0x35, 0xb3, 0xa8, 0xca, 0xe5, 0x4c, 0x45, 0x8a, 0x97, 0xae,
- 0xd6, 0x66, 0x27, 0x53, 0xc9, 0x1c, 0x3c, 0x03, 0x99, 0xc1, 0x09, 0x2e, 0x69, 0x37, 0x8d, 0x2f,
- 0x60, 0xc2, 0xa6, 0x18, 0x4e, 0x7a, 0xb8, 0xcf, 0xa7, 0x3a, 0x17, 0xd5, 0x9e, 0xf1, 0x84, 0x51,
- 0x0d, 0xa4, 0x64, 0xc4, 0x1e, 0xb1, 0x30, 0x98, 0xbb, 0x79, 0x01, 0xf6, 0x62, 0x0e, 0xb2, 0x63,
- 0x91, 0xcb, 0xff, 0x80, 0x71, 0xe7, 0xd4, 0x00, 0xdb, 0x75, 0x2c, 0xbd, 0x39, 0x33, 0x94, 0xbc,
- 0x8c, 0x3b, 0xb6, 0x20, 0x85, 0x24, 0x88, 0x2b, 0x70, 0x83, 0x6e, 0x7b, 0x9c, 0xbe, 0x14, 0x47,
- 0x65, 0x4b, 0x56, 0x81, 0xf8, 0x12, 0x11, 0x28, 0xeb, 0x55, 0x74, 0xa1, 0x31, 0xf7, 0xb0, 0x13,
- 0x86, 0xdd, 0x5f, 0x42, 0xd3, 0x02, 0x61, 0x95, 0x0c, 0x5c, 0xa5, 0xcd, 0xc0, 0x07, 0xe2, 0xf3,
- };
-
- const bool isResFork = (_substreamType == kSubstreamTypeResource);
-
- uint32 uncompressedSize = isResFork ? _fileDesc->uncompressedResSize : _fileDesc->uncompressedDataSize;
- uint32 compressedSize = isResFork ? _fileDesc->compressedResSize : _fileDesc->compressedDataSize;
- uint32 filePosition = _fileDesc->positionInArchive;
-
- if (isResFork)
- filePosition += _fileDesc->compressedDataSize;
-
- if (uncompressedSize == 0)
- return nullptr;
-
- Common::Array<byte> compressedData;
- compressedData.resize(compressedSize);
-
- _archiveStream->seek(filePosition, SEEK_SET);
- if (_archiveStream->read(&compressedData[0], compressedSize) != compressedSize)
- return nullptr;
-
- // Undo byte swapping
- for (uint i = 1; i < compressedSize; i += 2) {
- byte temp = compressedData[i];
- compressedData[i] = compressedData[i - 1];
- compressedData[i - 1] = temp;
- }
-
- // Undo obfuscation
- for (byte &b : compressedData)
- b = vl3DeobfuscationTable[b];
-
- byte *decompressedData = static_cast<byte *>(malloc(uncompressedSize));
- if (!decompressedData)
- return nullptr;
-
-
- // WARNING/TODO: Based on reverse engineering of the "Dcmp" resource from the installer, which contains the decompression code,
- // the bitstream format is usually just deflate, however there is one difference: Stored blocks are flushed to a 2-byte boundary
- // instead of 1-byte boundary, because the decompressor reads 2 bytes at a time. This doesn't usually matter because stored
- // blocks are very rare in practice on compressible data, and small files with only 1 compressible block are already 2-byte
- // aligned on the first block.
- //
- // If this turns out to be significant, then this will need to be updated to pass information to the deflate decompressor to
- // handle the non-standard behavior.
- if (!Common::inflateZlibHeaderless(decompressedData, uncompressedSize, &compressedData[0], compressedSize)) {
- free(decompressedData);
- return nullptr;
- }
-
- return new Common::MemoryReadStream(decompressedData, uncompressedSize, DisposeAfterUse::YES);
-}
-
-Common::String SPQRGameDataHandler::VISE3ArchiveMember::getName() const {
- if (_substreamType == kSubstreamTypeFinderInfo)
- return _fileDesc->fullPath + ".finf";
- else if (_substreamType == kSubstreamTypeResource)
- return _fileDesc->fullPath + ".rsrc";
- else
- return _fileDesc->fullPath;
-}
-
-SPQRGameDataHandler::VISE3FileDesc::VISE3FileDesc() : type{ 0, 0, 0, 0 }, creator{ 0, 0, 0, 0 }, compressedDataSize(0), uncompressedDataSize(0), compressedResSize(0), uncompressedResSize(0), positionInArchive(0) {
-}
-
-SPQRGameDataHandler::VISE3Archive::VISE3Archive(Common::SeekableReadStream *archiveStream) : _archiveStream(archiveStream) {
- uint8 vl3Header[44];
- if (archiveStream->read(vl3Header, 44) != 44 || memcmp(vl3Header, "SVCT", 4))
- error("Failed to read VISE 3 header");
-
- uint32 catalogPosition = READ_BE_UINT32(vl3Header + 36);
-
- uint32 archiveVersion = READ_BE_UINT32(vl3Header + 16);
-
- if (archiveVersion == 0x80010202)
- debug(3, "Detected VISE 3 archive as 3.5 Lite");
- else if (archiveVersion == 0x80010300)
- debug(3, "Detected VISE 3 archive as 3.6 Lite");
- else
- error("Unrecognized VISE 3 archive version");
-
- if (!archiveStream->seek(catalogPosition))
- error("Failed to seek to VISE 3 catalog");
-
- uint8 vl3Catalog[20];
- if (archiveStream->read(vl3Catalog, 20) != 20 || memcmp(vl3Catalog, "CVCT", 4))
- error("Failed to read VISE 3 catalog");
-
- uint16 numEntries = READ_BE_UINT16(vl3Catalog + 16);
-
- for (uint16 i = 0; i < numEntries; i++) {
- uint8 entryMagic[4];
- if (archiveStream->read(entryMagic, 4) != 4 || memcmp(entryMagic + 1, "VCT", 3))
- error("Failed to read VISE 3 catalog item");
-
- if (entryMagic[0] == 'D') {
- uint8 directoryData[78];
- if (archiveStream->read(directoryData, 78) != 78)
- error("Failed to read VISE 3 directory");
-
- // 3.6 Lite archives have 6 extra bytes for something
- if (archiveVersion >= 0x80010300)
- archiveStream->seek(6, SEEK_CUR);
-
- VISE3DirectoryDesc desc;
- desc.containingDirectory = READ_BE_UINT16(directoryData + 68);
-
- uint8 nameLength = directoryData[76];
-
- if (nameLength > 0) {
- char fileNameChars[256];
- if (archiveStream->read(fileNameChars, nameLength) != nameLength)
- error("Failed to read VISE 3 directory name");
- desc.name = Common::String(fileNameChars, nameLength);
- }
-
- _directoryDescs.push_back(desc);
- } else if (entryMagic[0] == 'F') {
- uint8 fileData[120];
- if (archiveStream->read(fileData, 120) != 120)
- error("Failed to read VISE 3 file");
-
- VISE3FileDesc desc;
- memcpy(desc.type, fileData + 40, 4);
- memcpy(desc.creator, fileData + 44, 4);
- desc.compressedDataSize = READ_BE_UINT32(fileData + 64);
- desc.uncompressedDataSize = READ_BE_UINT32(fileData + 68);
- desc.compressedResSize = READ_BE_UINT32(fileData + 72);
- desc.uncompressedResSize = READ_BE_UINT32(fileData + 76);
- desc.containingDirectory = READ_BE_UINT16(fileData + 92);
- desc.positionInArchive = READ_BE_UINT32(fileData + 96);
-
- uint8 nameLength = fileData[118];
-
- if (nameLength > 0) {
- char fileNameChars[256];
- if (archiveStream->read(fileNameChars, nameLength) != nameLength)
- error("Failed to read VISE 3 file name");
- desc.name = Common::String(fileNameChars, nameLength);
- }
-
- _fileDescs.push_back(desc);
- } else {
- error("Unknown VISE 3 catalog entry item type");
- }
- }
-
- // Generate full paths
- for (VISE3DirectoryDesc &dirDesc : _directoryDescs) {
- if (dirDesc.containingDirectory == 0)
- dirDesc.fullPath = dirDesc.name;
- else {
- if (dirDesc.containingDirectory > _directoryDescs.size())
- error("VISE 3 containing directory index was invalid");
-
- dirDesc.fullPath = _directoryDescs[dirDesc.containingDirectory - 1].fullPath + ":" + dirDesc.name;
- }
- }
-
- for (VISE3FileDesc &fileDesc : _fileDescs) {
- if (fileDesc.containingDirectory == 0)
- fileDesc.fullPath = fileDesc.name;
- else {
- if (fileDesc.containingDirectory > _directoryDescs.size())
- error("VISE 3 containing directory index was invalid");
-
- fileDesc.fullPath = _directoryDescs[fileDesc.containingDirectory - 1].fullPath + ":" + fileDesc.name;
- }
- }
-}
-
-const SPQRGameDataHandler::VISE3FileDesc *SPQRGameDataHandler::VISE3Archive::getFileDesc(const Common::Path &path) const {
- Common::String convertedPath = path.toString(':');
- for (const VISE3FileDesc &desc : _fileDescs) {
- if (desc.fullPath == convertedPath)
- return &desc;
- }
-
- return nullptr;
-}
-
-bool SPQRGameDataHandler::VISE3Archive::hasFile(const Common::Path &path) const {
- uint index = 0;
- VISE3ArchiveMember::SubstreamType substreamType = VISE3ArchiveMember::SubstreamType::kSubstreamTypeData;
- return getFileDescIndex(path, index, substreamType);
-}
-
-int SPQRGameDataHandler::VISE3Archive::listMembers(Common::ArchiveMemberList &list) const {
- int numMembers = 0;
- for (uint fileIndex = 0; fileIndex < _fileDescs.size(); fileIndex++) {
- const VISE3FileDesc &desc = _fileDescs[fileIndex];
-
- if (desc.uncompressedDataSize) {
- list.push_back(Common::ArchiveMemberPtr(new VISE3ArchiveMember(_archiveStream, &desc, VISE3ArchiveMember::kSubstreamTypeData)));
- numMembers++;
- }
- if (desc.uncompressedResSize) {
- list.push_back(Common::ArchiveMemberPtr(new VISE3ArchiveMember(_archiveStream, &desc, VISE3ArchiveMember::kSubstreamTypeResource)));
- numMembers++;
- }
-
- list.push_back(Common::ArchiveMemberPtr(new VISE3ArchiveMember(nullptr, &desc, VISE3ArchiveMember::kSubstreamTypeFinderInfo)));
- numMembers++;
- }
- return numMembers;
-}
-
-const Common::ArchiveMemberPtr SPQRGameDataHandler::VISE3Archive::getMember(const Common::Path &path) const {
- uint descIndex = 0;
- VISE3ArchiveMember::SubstreamType substreamType = VISE3ArchiveMember::SubstreamType::kSubstreamTypeData;
- if (!getFileDescIndex(path, descIndex, substreamType))
- return nullptr;
-
- return Common::ArchiveMemberPtr(new VISE3ArchiveMember(_archiveStream, &_fileDescs[descIndex], substreamType));
-}
-
-Common::SeekableReadStream *SPQRGameDataHandler::VISE3Archive::createReadStreamForMember(const Common::Path &path) const {
- Common::ArchiveMemberPtr archiveMember = getMember(path);
- if (!archiveMember)
- return nullptr;
-
- return archiveMember->createReadStream();
-}
-
-bool SPQRGameDataHandler::VISE3Archive::getFileDescIndex(const Common::Path &path, uint &outIndex, VISE3ArchiveMember::SubstreamType &outSubstreamType) const {
- Common::String convertedPath = path.toString(':');
- VISE3ArchiveMember::SubstreamType substreamType = VISE3ArchiveMember::SubstreamType::kSubstreamTypeData;
- if (convertedPath.hasSuffix(".rsrc")) {
- substreamType = VISE3ArchiveMember::SubstreamType::kSubstreamTypeResource;
- convertedPath = convertedPath.substr(0, convertedPath.size() - 5);
- } else if (convertedPath.hasSuffix(".finf")) {
- substreamType = VISE3ArchiveMember::SubstreamType::kSubstreamTypeFinderInfo;
- convertedPath = convertedPath.substr(0, convertedPath.size() - 5);
- }
-
- for (uint descIndex = 0; descIndex < _fileDescs.size(); descIndex++) {
- const VISE3FileDesc &desc = _fileDescs[descIndex];
-
- if (desc.fullPath == convertedPath) {
- if (substreamType == VISE3ArchiveMember::SubstreamType::kSubstreamTypeData && desc.uncompressedDataSize == 0)
- return false;
- if (substreamType == VISE3ArchiveMember::SubstreamType::kSubstreamTypeResource && desc.uncompressedResSize == 0)
- return false;
-
- outSubstreamType = substreamType;
- outIndex = descIndex;
- return true;
- }
- }
-
- return false;
-}
-
SPQRGameDataHandler::SPQRGameDataHandler(const Game &game, const MTropolisGameDescription &gameDesc) : GameDataHandler(game, gameDesc), _isMac(gameDesc.desc.platform == Common::kPlatformMacintosh) {
}
@@ -800,32 +450,31 @@ void SPQRGameDataHandler::unpackAdditionalFiles(Common::Array<Common::SharedPtr<
Common::SharedPtr<Common::SeekableReadStream> installerDataForkStream(installerResMan->getDataFork());
- VISE3Archive archive(installerDataForkStream.get());
+ Common::ScopedPtr<Common::Archive> archive(createMacVISEArchive(installerDataForkStream.get()));
debug(1, "Unpacking files...");
for (const MacVISE3InstallerUnpackRequest &request : unpackRequests) {
- const VISE3FileDesc *fileDesc = archive.getFileDesc(request.fileName);
-
- if (!fileDesc)
- error("Couldn't find file '%s' in VISE 3 archive", request.fileName);
+ Common::MacFinderInfo finfo;
+ if (!Common::MacResManager::getFileFinderInfo(request.fileName, *archive, finfo))
+ error("Couldn't get Finder info for file '%s'", request.fileName);
FileIdentification ident;
- ident.fileName = fileDesc->fullPath;
- ident.macCreator.value = MKTAG(fileDesc->creator[0], fileDesc->creator[1], fileDesc->creator[2], fileDesc->creator[3]);
- ident.macType.value = MKTAG(fileDesc->type[0], fileDesc->type[1], fileDesc->type[2], fileDesc->type[3]);
+ ident.fileName = request.fileName;
+ ident.macCreator.value = MKTAG(finfo.creator[0], finfo.creator[1], finfo.creator[2], finfo.creator[3]);
+ ident.macType.value = MKTAG(finfo.type[0], finfo.type[1], finfo.type[2], finfo.type[3]);
ident.category = request.fileType;
if (request.extractResources) {
Common::SharedPtr<Common::MacResManager> resMan(new Common::MacResManager());
- if (!resMan->open(request.fileName, archive))
+ if (!resMan->open(request.fileName, *archive))
error("Failed to open Mac res manager for file '%s'", request.fileName);
ident.resMan = resMan;
}
if (request.extractData)
- ident.stream.reset(archive.createReadStreamForMember(request.fileName));
+ ident.stream.reset(archive->createReadStreamForMember(request.fileName));
files.push_back(ident);
}
diff --git a/engines/mtropolis/module.mk b/engines/mtropolis/module.mk
index 1d45e895c6b..1e62a22e58c 100644
--- a/engines/mtropolis/module.mk
+++ b/engines/mtropolis/module.mk
@@ -28,6 +28,7 @@ MODULE_OBJS = \
runtime.o \
saveload.o \
subtitles.o \
+ vise.o \
vthread.o
# This module can be built as a plugin
diff --git a/engines/mtropolis/vise.cpp b/engines/mtropolis/vise.cpp
new file mode 100644
index 00000000000..39ebd0beee1
--- /dev/null
+++ b/engines/mtropolis/vise.cpp
@@ -0,0 +1,415 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "common/macresman.h"
+#include "common/memstream.h"
+#include "common/zlib.h"
+
+#include "mtropolis/vise.h"
+
+namespace MTropolis {
+
+class MacVISEArchive : public Common::Archive {
+private:
+ struct FileDesc {
+ FileDesc();
+
+ byte type[4];
+ byte creator[4];
+ uint32 compressedDataSize;
+ uint32 uncompressedDataSize;
+ uint32 compressedResSize;
+ uint32 uncompressedResSize;
+ uint32 positionInArchive;
+
+ uint16 containingDirectory;
+
+ Common::String name;
+ Common::String fullPath;
+ };
+
+ struct DirectoryDesc {
+ uint16 containingDirectory;
+
+ Common::String name;
+ Common::String fullPath;
+ };
+
+ class ArchiveMember : public Common::ArchiveMember {
+ public:
+ enum SubstreamType {
+ kSubstreamTypeData,
+ kSubstreamTypeResource,
+ kSubstreamTypeFinderInfo,
+ };
+
+ ArchiveMember(Common::SeekableReadStream *archiveStream, const FileDesc *fileDesc, SubstreamType substreamType);
+
+ Common::SeekableReadStream *createReadStream() const override;
+ Common::String getName() const override;
+
+ private:
+ Common::SeekableReadStream *_archiveStream;
+ const FileDesc *_fileDesc;
+ SubstreamType _substreamType;
+ };
+
+public:
+ explicit MacVISEArchive(Common::SeekableReadStream *archiveStream);
+
+ bool loadCatalog();
+
+ const FileDesc *getFileDesc(const Common::Path &path) const;
+
+ bool hasFile(const Common::Path &path) const override;
+ int listMembers(Common::ArchiveMemberList &list) const override;
+ const Common::ArchiveMemberPtr getMember(const Common::Path &path) const override;
+ Common::SeekableReadStream *createReadStreamForMember(const Common::Path &path) const override;
+
+private:
+ bool getFileDescIndex(const Common::Path &path, uint &outIndex, ArchiveMember::SubstreamType &outSubstreamType) const;
+
+ Common::SeekableReadStream *_archiveStream;
+ Common::Array<FileDesc> _fileDescs;
+ Common::Array<DirectoryDesc> _directoryDescs;
+};
+
+MacVISEArchive::ArchiveMember::ArchiveMember(Common::SeekableReadStream *archiveStream, const FileDesc *fileDesc, SubstreamType substreamType)
+ : _archiveStream(archiveStream), _fileDesc(fileDesc), _substreamType(substreamType) {
+}
+
+Common::SeekableReadStream *MacVISEArchive::ArchiveMember::createReadStream() const {
+ if (_substreamType == kSubstreamTypeFinderInfo) {
+ Common::MacFinderInfoData *finfoData = static_cast<Common::MacFinderInfoData*>(malloc(sizeof(Common::MacFinderInfoData)));
+
+ if (!finfoData)
+ return nullptr;
+
+ Common::MacFinderInfo finfo;
+ memcpy(finfo.type, _fileDesc->type, 4);
+ memcpy(finfo.creator, _fileDesc->creator, 4);
+
+ *finfoData = finfo.toData();
+
+ return new Common::MemoryReadStream(reinterpret_cast<const byte *>(finfoData), sizeof(Common::MacFinderInfoData), DisposeAfterUse::YES);
+ }
+
+ static const uint8 vl3DeobfuscationTable[] = {
+ 0x6a, 0xb7, 0x36, 0xec, 0x15, 0xd9, 0xc8, 0x73, 0xe8, 0x38, 0x9a, 0xdf, 0x21, 0x25, 0xd0, 0xcc,
+ 0xfd, 0xdc, 0x16, 0xd7, 0xe3, 0x43, 0x05, 0xc5, 0x8f, 0x48, 0xda, 0xf2, 0x3f, 0x10, 0x23, 0x6c,
+ 0x77, 0x7c, 0xf9, 0xa0, 0xa3, 0xe9, 0xed, 0x46, 0x8b, 0xd8, 0xac, 0x54, 0xce, 0x2d, 0x19, 0x5e,
+ 0x6d, 0x7d, 0x87, 0x5d, 0xfa, 0x5b, 0x9b, 0xe0, 0xc7, 0xee, 0x9f, 0x52, 0xa9, 0xb9, 0x0a, 0xd1,
+ 0xfe, 0x78, 0x76, 0x4a, 0x3d, 0x44, 0x5a, 0x96, 0x90, 0x1f, 0x26, 0x9d, 0x58, 0x1b, 0x8e, 0x57,
+ 0x59, 0xc3, 0x0b, 0x6b, 0xfc, 0x1d, 0xe6, 0xa2, 0x7f, 0x92, 0x4f, 0x40, 0xb4, 0x06, 0x72, 0x4d,
+ 0xf4, 0x34, 0xaa, 0xd2, 0x49, 0xad, 0xef, 0x22, 0x1a, 0xb5, 0xba, 0xbf, 0x29, 0x68, 0x89, 0x93,
+ 0x3e, 0x32, 0x04, 0xf5, 0xde, 0xe1, 0x6f, 0xfb, 0x67, 0xe4, 0x7e, 0x08, 0xaf, 0xf0, 0xab, 0x41,
+ 0x82, 0xea, 0x50, 0x0f, 0x2a, 0xc6, 0x35, 0xb3, 0xa8, 0xca, 0xe5, 0x4c, 0x45, 0x8a, 0x97, 0xae,
+ 0xd6, 0x66, 0x27, 0x53, 0xc9, 0x1c, 0x3c, 0x03, 0x99, 0xc1, 0x09, 0x2e, 0x69, 0x37, 0x8d, 0x2f,
+ 0x60, 0xc2, 0xa6, 0x18, 0x4e, 0x7a, 0xb8, 0xcf, 0xa7, 0x3a, 0x17, 0xd5, 0x9e, 0xf1, 0x84, 0x51,
+ 0x0d, 0xa4, 0x64, 0xc4, 0x1e, 0xb1, 0x30, 0x98, 0xbb, 0x79, 0x01, 0xf6, 0x62, 0x0e, 0xb2, 0x63,
+ 0x91, 0xcb, 0xff, 0x80, 0x71, 0xe7, 0xd4, 0x00, 0xdb, 0x75, 0x2c, 0xbd, 0x39, 0x33, 0x94, 0xbc,
+ 0x8c, 0x3b, 0xb6, 0x20, 0x85, 0x24, 0x88, 0x2b, 0x70, 0x83, 0x6e, 0x7b, 0x9c, 0xbe, 0x14, 0x47,
+ 0x65, 0x4b, 0x56, 0x81, 0xf8, 0x12, 0x11, 0x28, 0xeb, 0x55, 0x74, 0xa1, 0x31, 0xf7, 0xb0, 0x13,
+ 0x86, 0xdd, 0x5f, 0x42, 0xd3, 0x02, 0x61, 0x95, 0x0c, 0x5c, 0xa5, 0xcd, 0xc0, 0x07, 0xe2, 0xf3,
+ };
+
+ const bool isResFork = (_substreamType == kSubstreamTypeResource);
+
+ uint32 uncompressedSize = isResFork ? _fileDesc->uncompressedResSize : _fileDesc->uncompressedDataSize;
+ uint32 compressedSize = isResFork ? _fileDesc->compressedResSize : _fileDesc->compressedDataSize;
+ uint32 filePosition = _fileDesc->positionInArchive;
+
+ if (isResFork)
+ filePosition += _fileDesc->compressedDataSize;
+
+ if (uncompressedSize == 0)
+ return nullptr;
+
+ Common::Array<byte> compressedData;
+ compressedData.resize(compressedSize);
+
+ _archiveStream->seek(filePosition, SEEK_SET);
+ if (_archiveStream->read(&compressedData[0], compressedSize) != compressedSize)
+ return nullptr;
+
+ // Undo byte swapping
+ for (uint i = 1; i < compressedSize; i += 2) {
+ byte temp = compressedData[i];
+ compressedData[i] = compressedData[i - 1];
+ compressedData[i - 1] = temp;
+ }
+
+ // Undo obfuscation
+ for (byte &b : compressedData)
+ b = vl3DeobfuscationTable[b];
+
+ byte *decompressedData = static_cast<byte *>(malloc(uncompressedSize));
+ if (!decompressedData)
+ return nullptr;
+
+
+ // WARNING/TODO: Based on reverse engineering of the "Dcmp" resource from the installer, which contains the decompression code,
+ // the bitstream format is usually just deflate, however there is one difference: Stored blocks are flushed to a 2-byte boundary
+ // instead of 1-byte boundary, because the decompressor reads 2 bytes at a time. This doesn't usually matter because stored
+ // blocks are very rare in practice on compressible data, and small files with only 1 compressible block are already 2-byte
+ // aligned on the first block.
+ //
+ // If this turns out to be significant, then this will need to be updated to pass information to the deflate decompressor to
+ // handle the non-standard behavior.
+ if (!Common::inflateZlibHeaderless(decompressedData, uncompressedSize, &compressedData[0], compressedSize)) {
+ free(decompressedData);
+ return nullptr;
+ }
+
+ return new Common::MemoryReadStream(decompressedData, uncompressedSize, DisposeAfterUse::YES);
+}
+
+Common::String MacVISEArchive::ArchiveMember::getName() const {
+ if (_substreamType == kSubstreamTypeFinderInfo)
+ return _fileDesc->fullPath + ".finf";
+ else if (_substreamType == kSubstreamTypeResource)
+ return _fileDesc->fullPath + ".rsrc";
+ else
+ return _fileDesc->fullPath;
+}
+
+MacVISEArchive::FileDesc::FileDesc() : type{0, 0, 0, 0}, creator{0, 0, 0, 0}, compressedDataSize(0), uncompressedDataSize(0), compressedResSize(0), uncompressedResSize(0), positionInArchive(0) {
+}
+
+MacVISEArchive::MacVISEArchive(Common::SeekableReadStream *archiveStream) : _archiveStream(archiveStream) {
+}
+
+bool MacVISEArchive::loadCatalog() {
+ uint8 vl3Header[44];
+ if (_archiveStream->read(vl3Header, 44) != 44 || memcmp(vl3Header, "SVCT", 4)) {
+ debug(1, "Failed to read VISE header");
+ return false;
+ }
+
+ uint32 catalogPosition = READ_BE_UINT32(vl3Header + 36);
+
+ uint32 archiveVersion = READ_BE_UINT32(vl3Header + 16);
+
+ if (archiveVersion == 0x80010202)
+ debug(3, "Detected VISE archive as 3.5 Lite");
+ else if (archiveVersion == 0x80010300)
+ debug(3, "Detected VISE archive as 3.6 Lite");
+ else {
+ debug(1, "Unrecognized VISE archive version");
+ return false;
+ }
+
+ if (!_archiveStream->seek(catalogPosition))
+ error("Failed to seek to VISE catalog");
+
+ uint8 vl3Catalog[20];
+ if (_archiveStream->read(vl3Catalog, 20) != 20 || memcmp(vl3Catalog, "CVCT", 4)) {
+ debug(1, "Failed to read VISE catalog");
+ return false;
+ }
+
+ uint16 numEntries = READ_BE_UINT16(vl3Catalog + 16);
+
+ for (uint16 i = 0; i < numEntries; i++) {
+ uint8 entryMagic[4];
+ if (_archiveStream->read(entryMagic, 4) != 4 || memcmp(entryMagic + 1, "VCT", 3)) {
+ debug(1, "Failed to read VISE catalog item");
+ return false;
+ }
+
+ if (entryMagic[0] == 'D') {
+ uint8 directoryData[78];
+ if (_archiveStream->read(directoryData, 78) != 78) {
+ debug(1, "Failed to read VISE directory");
+ return false;
+ }
+
+ // 3.6 Lite archives have an additional 6 bytes before the name in directory entries
+ if (archiveVersion == 0x80010300)
+ _archiveStream->seek(6, SEEK_CUR);
+
+ DirectoryDesc desc;
+ desc.containingDirectory = READ_BE_UINT16(directoryData + 68);
+
+ uint8 nameLength = directoryData[76];
+
+ if (nameLength > 0) {
+ char fileNameChars[256];
+ if (_archiveStream->read(fileNameChars, nameLength) != nameLength) {
+ debug(1, "Failed to read VISE directory name");
+ return false;
+ }
+ desc.name = Common::String(fileNameChars, nameLength);
+ }
+
+ _directoryDescs.push_back(desc);
+ } else if (entryMagic[0] == 'F') {
+ uint8 fileData[120];
+ if (_archiveStream->read(fileData, 120) != 120) {
+ debug(1, "Failed to read VISE file");
+ return false;
+ }
+
+ FileDesc desc;
+ memcpy(desc.type, fileData + 40, 4);
+ memcpy(desc.creator, fileData + 44, 4);
+ desc.compressedDataSize = READ_BE_UINT32(fileData + 64);
+ desc.uncompressedDataSize = READ_BE_UINT32(fileData + 68);
+ desc.compressedResSize = READ_BE_UINT32(fileData + 72);
+ desc.uncompressedResSize = READ_BE_UINT32(fileData + 76);
+ desc.containingDirectory = READ_BE_UINT16(fileData + 92);
+ desc.positionInArchive = READ_BE_UINT32(fileData + 96);
+
+ uint8 nameLength = fileData[118];
+
+ if (nameLength > 0) {
+ char fileNameChars[256];
+ if (_archiveStream->read(fileNameChars, nameLength) != nameLength) {
+ debug(1, "Failed to read VISE file name");
+ return false;
+ }
+ desc.name = Common::String(fileNameChars, nameLength);
+ }
+
+ _fileDescs.push_back(desc);
+ } else {
+ debug(1, "Unknown VISE catalog entry item type");
+ return false;
+ }
+ }
+
+ // Generate full paths
+ for (DirectoryDesc &dirDesc : _directoryDescs) {
+ if (dirDesc.containingDirectory == 0)
+ dirDesc.fullPath = dirDesc.name;
+ else {
+ if (dirDesc.containingDirectory > _directoryDescs.size())
+ error("VISE 3 containing directory index was invalid");
+
+ dirDesc.fullPath = _directoryDescs[dirDesc.containingDirectory - 1].fullPath + ":" + dirDesc.name;
+ }
+ }
+
+ for (FileDesc &fileDesc : _fileDescs) {
+ if (fileDesc.containingDirectory == 0)
+ fileDesc.fullPath = fileDesc.name;
+ else {
+ if (fileDesc.containingDirectory > _directoryDescs.size())
+ error("VISE 3 containing directory index was invalid");
+
+ fileDesc.fullPath = _directoryDescs[fileDesc.containingDirectory - 1].fullPath + ":" + fileDesc.name;
+ }
+ }
+}
+
+const MacVISEArchive::FileDesc *MacVISEArchive::getFileDesc(const Common::Path &path) const {
+ Common::String convertedPath = path.toString(':');
+ for (const FileDesc &desc : _fileDescs) {
+ if (desc.fullPath == convertedPath)
+ return &desc;
+ }
+
+ return nullptr;
+}
+
+bool MacVISEArchive::hasFile(const Common::Path &path) const {
+ uint index = 0;
+ ArchiveMember::SubstreamType substreamType = ArchiveMember::kSubstreamTypeData;
+ return getFileDescIndex(path, index, substreamType);
+}
+
+int MacVISEArchive::listMembers(Common::ArchiveMemberList &list) const {
+ int numMembers = 0;
+ for (uint fileIndex = 0; fileIndex < _fileDescs.size(); fileIndex++) {
+ const FileDesc &desc = _fileDescs[fileIndex];
+
+ if (desc.uncompressedDataSize) {
+ list.push_back(Common::ArchiveMemberPtr(new ArchiveMember(_archiveStream, &desc, ArchiveMember::kSubstreamTypeData)));
+ numMembers++;
+ }
+ if (desc.uncompressedResSize) {
+ list.push_back(Common::ArchiveMemberPtr(new ArchiveMember(_archiveStream, &desc, ArchiveMember::kSubstreamTypeResource)));
+ numMembers++;
+ }
+
+ list.push_back(Common::ArchiveMemberPtr(new ArchiveMember(nullptr, &desc, ArchiveMember::kSubstreamTypeFinderInfo)));
+ numMembers++;
+ }
+ return numMembers;
+}
+
+const Common::ArchiveMemberPtr MacVISEArchive::getMember(const Common::Path &path) const {
+ uint descIndex = 0;
+ ArchiveMember::SubstreamType substreamType = ArchiveMember::kSubstreamTypeData;
+ if (!getFileDescIndex(path, descIndex, substreamType))
+ return nullptr;
+
+ return Common::ArchiveMemberPtr(new ArchiveMember(_archiveStream, &_fileDescs[descIndex], substreamType));
+}
+
+Common::SeekableReadStream *MacVISEArchive::createReadStreamForMember(const Common::Path &path) const {
+ Common::ArchiveMemberPtr archiveMember = getMember(path);
+ if (!archiveMember)
+ return nullptr;
+
+ return archiveMember->createReadStream();
+}
+
+bool MacVISEArchive::getFileDescIndex(const Common::Path &path, uint &outIndex, ArchiveMember::SubstreamType &outSubstreamType) const {
+ Common::String convertedPath = path.toString(':');
+ ArchiveMember::SubstreamType substreamType = ArchiveMember::kSubstreamTypeData;
+ if (convertedPath.hasSuffix(".rsrc")) {
+ substreamType = ArchiveMember::kSubstreamTypeResource;
+ convertedPath = convertedPath.substr(0, convertedPath.size() - 5);
+ } else if (convertedPath.hasSuffix(".finf")) {
+ substreamType = ArchiveMember::kSubstreamTypeFinderInfo;
+ convertedPath = convertedPath.substr(0, convertedPath.size() - 5);
+ }
+
+ for (uint descIndex = 0; descIndex < _fileDescs.size(); descIndex++) {
+ const FileDesc &desc = _fileDescs[descIndex];
+
+ if (desc.fullPath == convertedPath) {
+ if (substreamType == ArchiveMember::SubstreamType::kSubstreamTypeData && desc.uncompressedDataSize == 0)
+ return false;
+ if (substreamType == ArchiveMember::SubstreamType::kSubstreamTypeResource && desc.uncompressedResSize == 0)
+ return false;
+
+ outSubstreamType = substreamType;
+ outIndex = descIndex;
+ return true;
+ }
+ }
+
+ return false;
+}
+
+Common::Archive *createMacVISEArchive(Common::SeekableReadStream *stream) {
+ MacVISEArchive *archive = new MacVISEArchive(stream);
+ if (!archive->loadCatalog()) {
+ delete archive;
+ return nullptr;
+ }
+
+ return archive;
+}
+
+} // End of namespace MTropolis
diff --git a/engines/mtropolis/vise.h b/engines/mtropolis/vise.h
new file mode 100644
index 00000000000..13ea4a41132
--- /dev/null
+++ b/engines/mtropolis/vise.h
@@ -0,0 +1,37 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+/**
+ * @file
+ * Installer VISE decompressor used in engines:
+ * - mtropolis
+ */
+
+#include "common/array.h"
+#include "common/str.h"
+#include "common/archive.h"
+
+namespace MTropolis {
+
+// Loads a Macintosh Installer VISE archive from a stream
+Common::Archive *createMacVISEArchive(Common::SeekableReadStream *stream);
+
+} // End of namespace MTropolis
More information about the Scummvm-git-logs
mailing list