Skip to content

Commit

Permalink
COMMON: Move InstallShield code to common
Browse files Browse the repository at this point in the history
The code also now works for both data compressed with sync bytes and without
  • Loading branch information
Matthew Hoops committed May 28, 2012
1 parent f7e515a commit ab45e72
Show file tree
Hide file tree
Showing 7 changed files with 149 additions and 78 deletions.
126 changes: 59 additions & 67 deletions engines/agos/installshield_cab.cpp → common/installshield_cab.cpp
Expand Up @@ -43,27 +43,25 @@
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.

#include "agos/installshield_cab.h"

#include "common/archive.h"
#include "common/debug.h"
#include "common/file.h"
#include "common/hash-str.h"
#include "common/installshield_cab.h"
#include "common/memstream.h"
#include "common/zlib.h"

namespace AGOS {

class InstallShieldCabinet : public Common::Archive {
Common::String _installShieldFilename;
namespace Common {

class InstallShieldCabinet : public Archive {
public:
InstallShieldCabinet(const Common::String &filename);
InstallShieldCabinet(SeekableReadStream *stream, DisposeAfterUse::Flag disposeAfterUse);
~InstallShieldCabinet();

// Common::Archive API implementation
bool hasFile(const Common::String &name) const;
int listMembers(Common::ArchiveMemberList &list) const;
const Common::ArchiveMemberPtr getMember(const Common::String &name) const;
Common::SeekableReadStream *createReadStreamForMember(const Common::String &name) const;
// Archive API implementation
bool hasFile(const String &name) const;
int listMembers(ArchiveMemberList &list) const;
const ArchiveMemberPtr getMember(const String &name) const;
SeekableReadStream *createReadStreamForMember(const String &name) const;

private:
struct FileEntry {
Expand All @@ -73,131 +71,125 @@ class InstallShieldCabinet : public Common::Archive {
uint16 flags;
};

typedef Common::HashMap<Common::String, FileEntry, Common::IgnoreCase_Hash, Common::IgnoreCase_EqualTo> FileMap;
typedef HashMap<String, FileEntry, IgnoreCase_Hash, IgnoreCase_EqualTo> FileMap;
FileMap _map;
Common::SeekableReadStream *_stream;
DisposeAfterUse::Flag _disposeAfterUse;
};

InstallShieldCabinet::~InstallShieldCabinet() {
_map.clear();
}

InstallShieldCabinet::InstallShieldCabinet(const Common::String &filename) : _installShieldFilename(filename) {
Common::File installShieldFile;

if (!installShieldFile.open(_installShieldFilename)) {
warning("InstallShieldCabinet::InstallShieldCabinet(): Could not find the archive file %s", _installShieldFilename.c_str());
return;
}
if (_disposeAfterUse == DisposeAfterUse::YES)
delete _stream;
}

InstallShieldCabinet::InstallShieldCabinet(SeekableReadStream *stream, DisposeAfterUse::Flag disposeAfterUse) : _stream(stream), _disposeAfterUse(disposeAfterUse) {
// Note that we only support a limited subset of cabinet files
// Only single cabinet files and ones without data shared between
// cabinets.

// Check for the magic uint32
if (installShieldFile.readUint32LE() != 0x28635349) {
if (_stream->readUint32LE() != 0x28635349) {
warning("InstallShieldCabinet::InstallShieldCabinet(): Magic ID doesn't match");
return;
}

uint32 version = installShieldFile.readUint32LE();
uint32 version = _stream->readUint32LE();

if (version != 0x01000004) {
warning("Unsupported CAB version %08x", version);
return;
}

/* uint32 volumeInfo = */ installShieldFile.readUint32LE();
uint32 cabDescriptorOffset = installShieldFile.readUint32LE();
/* uint32 cabDescriptorSize = */ installShieldFile.readUint32LE();
/* uint32 volumeInfo = */ _stream->readUint32LE();
uint32 cabDescriptorOffset = _stream->readUint32LE();
/* uint32 cabDescriptorSize = */ _stream->readUint32LE();

installShieldFile.seek(cabDescriptorOffset);
_stream->seek(cabDescriptorOffset);

installShieldFile.skip(12);
uint32 fileTableOffset = installShieldFile.readUint32LE();
installShieldFile.skip(4);
uint32 fileTableSize = installShieldFile.readUint32LE();
uint32 fileTableSize2 = installShieldFile.readUint32LE();
uint32 directoryCount = installShieldFile.readUint32LE();
installShieldFile.skip(8);
uint32 fileCount = installShieldFile.readUint32LE();
_stream->skip(12);
uint32 fileTableOffset = _stream->readUint32LE();
_stream->skip(4);
uint32 fileTableSize = _stream->readUint32LE();
uint32 fileTableSize2 = _stream->readUint32LE();
uint32 directoryCount = _stream->readUint32LE();
_stream->skip(8);
uint32 fileCount = _stream->readUint32LE();

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...

installShieldFile.seek(cabDescriptorOffset + fileTableOffset);
_stream->seek(cabDescriptorOffset + fileTableOffset);
uint32 fileTableCount = directoryCount + fileCount;
uint32 *fileTableOffsets = new uint32[fileTableCount];
for (uint32 i = 0; i < fileTableCount; i++)
fileTableOffsets[i] = installShieldFile.readUint32LE();
fileTableOffsets[i] = _stream->readUint32LE();

for (uint32 i = directoryCount; i < fileCount + directoryCount; i++) {
installShieldFile.seek(cabDescriptorOffset + fileTableOffset + fileTableOffsets[i]);
uint32 nameOffset = installShieldFile.readUint32LE();
/* uint32 directoryIndex = */ installShieldFile.readUint32LE();
_stream->seek(cabDescriptorOffset + fileTableOffset + fileTableOffsets[i]);
uint32 nameOffset = _stream->readUint32LE();
/* uint32 directoryIndex = */ _stream->readUint32LE();

// First read in data needed by us to get at the file data
FileEntry entry;
entry.flags = installShieldFile.readUint16LE();
entry.uncompressedSize = installShieldFile.readUint32LE();
entry.compressedSize = installShieldFile.readUint32LE();
installShieldFile.skip(20);
entry.offset = installShieldFile.readUint32LE();
entry.flags = _stream->readUint16LE();
entry.uncompressedSize = _stream->readUint32LE();
entry.compressedSize = _stream->readUint32LE();
_stream->skip(20);
entry.offset = _stream->readUint32LE();

// Then let's get the string
installShieldFile.seek(cabDescriptorOffset + fileTableOffset + nameOffset);
Common::String fileName;
_stream->seek(cabDescriptorOffset + fileTableOffset + nameOffset);
String fileName;

char c = installShieldFile.readByte();
char c = _stream->readByte();
while (c) {
fileName += c;
c = installShieldFile.readByte();
c = _stream->readByte();
}
_map[fileName] = entry;
}

delete[] fileTableOffsets;
}

bool InstallShieldCabinet::hasFile(const Common::String &name) const {
bool InstallShieldCabinet::hasFile(const String &name) const {
return _map.contains(name);
}

int InstallShieldCabinet::listMembers(Common::ArchiveMemberList &list) const {
int InstallShieldCabinet::listMembers(ArchiveMemberList &list) const {
for (FileMap::const_iterator it = _map.begin(); it != _map.end(); it++)
list.push_back(getMember(it->_key));

return _map.size();
}

const Common::ArchiveMemberPtr InstallShieldCabinet::getMember(const Common::String &name) const {
return Common::ArchiveMemberPtr(new Common::GenericArchiveMember(name, this));
const ArchiveMemberPtr InstallShieldCabinet::getMember(const String &name) const {
return ArchiveMemberPtr(new GenericArchiveMember(name, this));
}

Common::SeekableReadStream *InstallShieldCabinet::createReadStreamForMember(const Common::String &name) const {
SeekableReadStream *InstallShieldCabinet::createReadStreamForMember(const String &name) const {
if (!_map.contains(name))
return 0;

const FileEntry &entry = _map[name];

Common::File archiveFile;
archiveFile.open(_installShieldFilename);
archiveFile.seek(entry.offset);
_stream->seek(entry.offset);

if (!(entry.flags & 0x04)) {
// Not compressed
return archiveFile.readStream(entry.uncompressedSize);
}
if (!(entry.flags & 0x04)) // Not compressed
return _stream->readStream(entry.uncompressedSize);

#ifdef USE_ZLIB
byte *src = (byte *)malloc(entry.compressedSize);
byte *dst = (byte *)malloc(entry.uncompressedSize);

archiveFile.read(src, entry.compressedSize);
_stream->read(src, entry.compressedSize);

bool result = Common::inflateZlibHeaderless(dst, entry.uncompressedSize, src, entry.compressedSize);
bool result = inflateZlibInstallShield(dst, entry.uncompressedSize, src, entry.compressedSize);
free(src);

if (!result) {
Expand All @@ -206,15 +198,15 @@ Common::SeekableReadStream *InstallShieldCabinet::createReadStreamForMember(cons
return 0;
}

return new Common::MemoryReadStream(dst, entry.uncompressedSize, DisposeAfterUse::YES);
return new MemoryReadStream(dst, entry.uncompressedSize, DisposeAfterUse::YES);
#else
warning("zlib required to extract compressed CAB file '%s'", name.c_str());
return 0;
#endif
}

Common::Archive *makeInstallShieldArchive(const Common::String &name) {
return new InstallShieldCabinet(name);
Archive *makeInstallShieldArchive(SeekableReadStream *stream, DisposeAfterUse::Flag disposeAfterUse) {
return new InstallShieldCabinet(stream, disposeAfterUse);
}

} // End of namespace AGOS
18 changes: 10 additions & 8 deletions engines/agos/installshield_cab.h → common/installshield_cab.h
Expand Up @@ -20,22 +20,24 @@
*
*/

#include "common/archive.h"
#include "common/str.h"
#ifndef COMMON_INSTALLSHIELD_CAB_H
#define COMMON_INSTALLSHIELD_CAB_H

#ifndef AGOS_INSTALLSHIELD_CAB_H
#define AGOS_INSTALLSHIELD_CAB_H
#include "common/types.h"

namespace AGOS {
namespace Common {

class Archive;
class SeekableReadStream;

/**
* This factory method creates an Archive instance corresponding to the content
* of the InstallShield compressed file with the given name.
* of the InstallShield compressed stream.
*
* May return 0 in case of a failure.
*/
Common::Archive *makeInstallShieldArchive(const Common::String &name);
Archive *makeInstallShieldArchive(SeekableReadStream *stream, DisposeAfterUse::Flag disposeAfterUse = DisposeAfterUse::YES);

} // End of namespace AGOS
} // End of namespace Common

#endif
1 change: 1 addition & 0 deletions common/module.mk
Expand Up @@ -16,6 +16,7 @@ MODULE_OBJS := \
gui_options.o \
hashmap.o \
iff_container.o \
installshield_cab.o \
language.o \
localization.o \
macresman.o \
Expand Down
54 changes: 54 additions & 0 deletions common/zlib.cpp
Expand Up @@ -85,6 +85,60 @@ bool inflateZlibHeaderless(byte *dst, uint dstLen, const byte *src, uint srcLen,
return true;
}

enum {
kTempBufSize = 65536
};

bool inflateZlibInstallShield(byte *dst, uint dstLen, const byte *src, uint srcLen) {
if (!dst || !dstLen || !src || !srcLen)
return false;

// See if we have sync bytes. If so, just use our function for that.
if (srcLen >= 4 && READ_BE_UINT32(src + srcLen - 4) == 0xFFFF)
return inflateZlibHeaderless(dst, dstLen, src, srcLen);

// Otherwise, we have some custom code we get to use here.

byte *temp = (byte *)malloc(kTempBufSize);

uint32 bytesRead = 0, bytesProcessed = 0;
while (bytesRead < srcLen) {
uint16 chunkSize = READ_LE_UINT16(src + bytesRead);
bytesRead += 2;

// Initialize zlib
z_stream stream;
stream.next_in = const_cast<byte *>(src + bytesRead);
stream.avail_in = chunkSize;
stream.next_out = temp;
stream.avail_out = kTempBufSize;
stream.zalloc = Z_NULL;
stream.zfree = Z_NULL;
stream.opaque = Z_NULL;

// Negative MAX_WBITS tells zlib there's no zlib header
int err = inflateInit2(&stream, -MAX_WBITS);
if (err != Z_OK)
return false;

err = inflate(&stream, Z_FINISH);
if (err != Z_OK && err != Z_STREAM_END) {
inflateEnd(&stream);
free(temp);
return false;
}

memcpy(dst + bytesProcessed, temp, stream.total_out);
bytesProcessed += stream.total_out;

inflateEnd(&stream);
bytesRead += chunkSize;
}

free(temp);
return true;
}

/**
* A simple wrapper class which can be used to wrap around an arbitrary
* other SeekableReadStream and will then provide on-the-fly decompression support.
Expand Down
19 changes: 19 additions & 0 deletions common/zlib.h
Expand Up @@ -77,6 +77,25 @@ bool uncompress(byte *dst, unsigned long *dstLen, const byte *src, unsigned long
*/
bool inflateZlibHeaderless(byte *dst, uint dstLen, const byte *src, uint srcLen, const byte *dict = 0, uint dictLen = 0);

/**
* Wrapper around zlib's inflate functions. This function will call the
* necessary inflate functions to uncompress data compressed for InstallShield
* cabinet files.
*
* Decompresses the src buffer into the dst buffer.
* srcLen is the byte length of the source buffer, dstLen is the byte
* length of the output buffer.
* It decompress as much data as possible, up to dstLen bytes.
*
* @param dst the buffer to store into.
* @param dstLen the size of the destination buffer.
* @param src the data to be decompressed.
* @param dstLen the size of the compressed data.
*
* @return true on success (Z_OK or Z_STREAM_END), false otherwise.
*/
bool inflateZlibInstallShield(byte *dst, uint dstLen, const byte *src, uint srcLen);

#endif

/**
Expand Down
1 change: 0 additions & 1 deletion engines/agos/module.mk
Expand Up @@ -51,7 +51,6 @@ ifdef ENABLE_AGOS2
MODULE_OBJS += \
animation.o \
feeble.o \
installshield_cab.o \
oracle.o \
script_dp.o \
script_ff.o \
Expand Down
8 changes: 6 additions & 2 deletions engines/agos/res.cpp
Expand Up @@ -23,6 +23,8 @@
// Resource file routines for Simon1/Simon2


#include "common/archive.h"
#include "common/installshield_cab.h"
#include "common/file.h"
#include "common/memstream.h"
#include "common/textconsole.h"
Expand All @@ -31,7 +33,6 @@
#include "agos/agos.h"
#include "agos/intern.h"
#include "agos/sound.h"
#include "agos/installshield_cab.h"

#include "common/zlib.h"

Expand All @@ -43,7 +44,10 @@ ArchiveMan::ArchiveMan() {

#ifdef ENABLE_AGOS2
void ArchiveMan::registerArchive(const Common::String &filename, int priority) {
add(filename, makeInstallShieldArchive(filename), priority);
Common::SeekableReadStream *stream = SearchMan.createReadStreamForMember(filename);

if (stream)
add(filename, makeInstallShieldArchive(stream, DisposeAfterUse::YES), priority);
}
#endif

Expand Down

0 comments on commit ab45e72

Please sign in to comment.