diff --git a/common/compression/gentee_installer.cpp b/common/compression/gentee_installer.cpp index 749e77fa488..7060ea074ca 100644 --- a/common/compression/gentee_installer.cpp +++ b/common/compression/gentee_installer.cpp @@ -629,7 +629,8 @@ public: explicit PackageArchive(Common::SeekableReadStream *stream); ~PackageArchive(); - bool load(const char *prefix); + bool loadPAK(const char *prefix); + bool loadFromEXE(const char *prefix); bool hasFile(const Common::Path &path) const override; int listMembers(Common::ArchiveMemberList &list) const override; @@ -657,26 +658,75 @@ PackageArchive::~PackageArchive() { delete _stream; } -bool PackageArchive::load(const char *prefix) { +bool PackageArchive::loadFromEXE(const char *prefix) { + // Gentee Installer executables have up to three components: + // + // - The loader, which is a small loader about 7.5kb in size that contains + // the decompression function. The PAK file name is embedded at 0x3a0 + // and the DLL payload locator is embedded at 0x3f0. + // + // - The DLL payload, which is a compressed DLL that gets decompressed to + // a file named ginstall.dll and then loaded and used to execute most of + // the installer interpreter logic. + // + // - Possibly an embedded PAK file. + + if (_stream->size() < 1024) { + warning("GenteeInstaller::PackageArchive::loadFromEXE: Couldn't read pak file size declaration"); + return false; + } + + if (!_stream->seek(1024 - 16, SEEK_SET)) { + warning("GenteeInstaller::PackageArchive::loadFromEXE: Couldn't seek to ginstall.dll payload locator"); + return false; + } + + byte payloadLocatorBytes[12]; + + if (_stream->read(payloadLocatorBytes, 12) != 12) { + warning("GenteeInstaller::PackageArchive::loadFromEXE: Couldn't read ginstall.dll payload locator"); + return false; + } + + uint32 payloadLocation = READ_LE_UINT32(payloadLocatorBytes + 0); + uint32 payloadSizeCompressed = READ_LE_UINT32(payloadLocatorBytes + 4); + //uint32 payloadSizeUncompressed = READ_LE_UINT32(payloadLocatorBytes + 8); + + int64 endOfPayload = static_cast(payloadLocation) + static_cast(payloadSizeCompressed); + + if (endOfPayload >= _stream->size()) { + warning("GenteeInstaller::PackageArchive::loadFromEXE: Couldn't locate embedded PAK"); + return false; + } + + if (!_stream->seek(endOfPayload, SEEK_SET)) { + warning("GenteeInstaller::PackageArchive::loadFromEXE: Couldn't seek to embedded PAK"); + return false; + } + + return loadPAK(prefix); +} + +bool PackageArchive::loadPAK(const char *prefix) { byte pakFileEOFBytes[4]; int64 pakFileStartPos = _stream->pos(); int64 maxPakFileSize = _stream->size() - pakFileStartPos; if (_stream->read(pakFileEOFBytes, 4) != 4) { - warning("GenteeInstaller::PackageArchive::load: Couldn't read pak file size declaration"); + warning("GenteeInstaller::PackageArchive::loadPAK: Couldn't read pak file size declaration"); return false; } uint32 pakFileEOF = READ_LE_UINT32(pakFileEOFBytes); if (static_cast(pakFileEOF) > _stream->size()) { - warning("GenteeInstaller::PackageArchive::load: Pak file size was larger than would be possible"); + warning("GenteeInstaller::PackageArchive::loadPAK: Pak file size was larger than would be possible"); return false; } if (static_cast(pakFileEOF) < pakFileStartPos) { - warning("GenteeInstaller::PackageArchive::load: Pak file EOF preceded the start position"); + warning("GenteeInstaller::PackageArchive::loadPAK: Pak file EOF preceded the start position"); return false; } @@ -685,14 +735,14 @@ bool PackageArchive::load(const char *prefix) { if (pakFileStartPos != 0 || maxPakFileSize != pakFileSize) { _stream = new Common::SeekableSubReadStream(_stream, pakFileStartPos, static_cast(pakFileStartPos) + pakFileSize, DisposeAfterUse::YES); if (!_stream->seek(4)) { - warning("GenteeInstaller::PackageArchive::load: Couldn't reset pak position"); + warning("GenteeInstaller::PackageArchive::loadPAK: Couldn't reset pak position"); return false; } } byte pakFileHeader[16]; if (_stream->read(pakFileHeader, 16) != 16) { - warning("GenteeInstaller::PackageArchive::load: Couldn't load pak header"); + warning("GenteeInstaller::PackageArchive::loadPAK: Couldn't load pak header"); return false; } @@ -706,7 +756,7 @@ bool PackageArchive::load(const char *prefix) { return false; if (firstChunk.size() < 3) { - warning("GenteeInstaller::PackageArchive::load: First chunk is malformed"); + warning("GenteeInstaller::PackageArchive::loadPAK: First chunk is malformed"); return false; } @@ -721,7 +771,7 @@ bool PackageArchive::load(const char *prefix) { return false; if (commandletChunk.size() < 3) { - warning("GenteeInstaller::PackageArchive::load: Commandlet was malformed"); + warning("GenteeInstaller::PackageArchive::loadPAK: Commandlet was malformed"); return false; } @@ -730,7 +780,7 @@ bool PackageArchive::load(const char *prefix) { if (commandletCode == 0x87f4) { // Unpack file commandlet if (commandletChunk.size() < 36 || commandletChunk.back() != 0) { - warning("GenteeInstaller::PackageArchive::load: File commandlet was malformed"); + warning("GenteeInstaller::PackageArchive::loadPAK: File commandlet was malformed"); return false; } @@ -749,7 +799,7 @@ bool PackageArchive::load(const char *prefix) { byte decompressedSizeBytes[4]; if (_stream->read(decompressedSizeBytes, 4) != 4) { - warning("GenteeInstaller::PackageArchive::load: Decompressed file size was malformed"); + warning("GenteeInstaller::PackageArchive::loadPAK: Decompressed file size was malformed"); return false; } @@ -766,7 +816,7 @@ bool PackageArchive::load(const char *prefix) { amountToSkip = kSkipBufSize; if (fileCtx->decompressBytes(skipBuf, amountToSkip) != amountToSkip) { - warning("GenteeInstaller::PackageArchive::load: Couldn't decompress file data to skip it"); + warning("GenteeInstaller::PackageArchive::loadPAK: Couldn't decompress file data to skip it"); return false; } @@ -777,7 +827,7 @@ bool PackageArchive::load(const char *prefix) { } else { decompressedSize = READ_LE_UINT32(&commandletChunk[7]); if (!_stream->skip(decompressedSize)) { - warning("GenteeInstaller::PackageArchive::load: Failed to skip uncompressed file data"); + warning("GenteeInstaller::PackageArchive::loadPAK: Failed to skip uncompressed file data"); return false; } dataEnd = _stream->pos(); @@ -904,7 +954,7 @@ Common::Mutex *ThreadSafePackageArchive::getGuardMutex() { -Common::Archive *createGenteeInstallerArchive(Common::SeekableReadStream *stream, const char *prefixToRemove, bool threadSafe) { +Common::Archive *createGenteeInstallerArchive(Common::SeekableReadStream *stream, const char *prefixToRemove, bool embedded, bool threadSafe) { if (!prefixToRemove) prefixToRemove = ""; @@ -915,7 +965,9 @@ Common::Archive *createGenteeInstallerArchive(Common::SeekableReadStream *stream else archive = new GenteeInstaller::PackageArchive(stream); - if (!archive->load(prefixToRemove)) { + bool loaded = embedded ? archive->loadFromEXE(prefixToRemove) : archive->loadPAK(prefixToRemove); + + if (!loaded) { delete archive; return nullptr; } diff --git a/common/compression/gentee_installer.h b/common/compression/gentee_installer.h index 80d9e79f714..6310defae3e 100644 --- a/common/compression/gentee_installer.h +++ b/common/compression/gentee_installer.h @@ -52,13 +52,16 @@ namespace Common { * other compression methods like PPMd. This version uses LZ77 with adaptive Huffman coding. * * @param stream Data stream to load + * @param embedded If true, load an embedded package from an installer executable, otherwise load + * a stand-alone package file. * @param prefixToRemove Specifies the prefix of extract directives to include, and removes the prefix - * If you pass an empty string or null, all directives will be included + * If you pass an empty string or null, all directives will be included. + * File path separators will be normalized to '/' before checking the prefix. * @param threadSafe If true, all read operations will be wrapped in a mutex-guarded substream * * @return The Gentee Installer package archive */ -Common::Archive *createGenteeInstallerArchive(Common::SeekableReadStream *stream, const char *prefixToRemove, bool threadSafe); +Common::Archive *createGenteeInstallerArchive(Common::SeekableReadStream *stream, const char *prefixToRemove, bool embedded, bool threadSafe); } // End of namespace Common