Initial commit

This commit is contained in:
2026-02-02 04:50:13 +01:00
commit 5b11698731
22592 changed files with 7677434 additions and 0 deletions

View File

@@ -0,0 +1,632 @@
/* 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/array.h"
#include "common/compression/clickteam.h"
#include "common/compression/deflate.h"
#include "common/debug.h"
#include "common/ptr.h"
#include "common/substream.h"
#include "common/memstream.h"
#define FLAG_COMPRESSED 1
namespace Common {
ClickteamInstaller::ClickteamFileDescriptor::ClickteamFileDescriptor(const ClickteamTag& contentsTag, uint32 off)
: _fileDataOffset(0), _fileDescriptorOffset(0), _compressedSize(0), _uncompressedSize(0), _isReferenceMissing(false) {
switch (contentsTag._tagId) {
case (uint16)ClickteamTagId::FILE_LIST: {
uint32 stringsOffset = 36;
byte *tag = contentsTag._contents + off;
uint32 lmax = contentsTag._size - off;
if (lmax < 0x24)
return;
uint32 ls = READ_LE_UINT32(tag), l;
if (ls < 0x24)
return;
l = MIN((uint32)ls, lmax);
uint16 flags = READ_LE_UINT32(tag+22);
if (flags & 6)
stringsOffset += 0x12;
if (flags & 8)
stringsOffset += 0x18;
if (stringsOffset >= l) {
return;
}
_fileDataOffset = READ_LE_UINT32(tag + 6);
_compressedSize = READ_LE_UINT32(tag + 10);
_uncompressedSize = READ_LE_UINT32(tag + 14);
_expectedCRC = READ_LE_UINT32(tag + 18);
char *strings = (char *)tag + stringsOffset;
char *p;
for (p = strings; p < (char*)tag + lmax && *p; p++);
_fileName = Common::Path(Common::String(strings, p - strings), Common::Path::kNoSeparator);
_fileDescriptorOffset = off;
_supported = true;
_isPatchFile = false;
_crcIsXorred = true;
break;
}
case (uint16)ClickteamTagId::FILE_PATCHING_LIST: {
uint32 stringsOffset = 0x36;
byte *tag = contentsTag._contents + off;
uint32 lmax = contentsTag._size - off;
if (lmax < 33)
return;
uint32 ls = READ_LE_UINT32(tag);
if (ls < 33)
return;
byte type = tag[7];
if (type != 0) {
_supported = false;
_fileName.clear();
_fileDataOffset = 0;
_fileDescriptorOffset = off;
_compressedSize = 0;
_uncompressedSize = 0;
_expectedCRC = 0;
_isPatchFile = false;
_crcIsXorred = false;
return;
}
// Layout:
// 0-3: tag size
// 4-6: ???
// 7: operation
// 8: file type
// 9: ???
// a-d: uncompressed size
// e-11: unxorred uncompressed CRC
// 12-15: number of original files entries
// 16-17: pointer to original files entries
// 18-1d: ???
// 1e-36: 3 blocks of 8 bytes relating to some timestamps..
// Original files entries. Array of:
// 0-3: original CRC
// 4-7: file size before patching
// 8-b: patch data offset
// c-f: patch size
_expectedCRC = READ_LE_UINT32(tag + 0xe);
int numPatchEntries = READ_LE_UINT16(tag + 0x12);
byte *blockb = tag + READ_LE_UINT16(tag + 0x16);
_uncompressedSize = READ_LE_UINT32(tag + 0xa);
char *strings = (char *)tag + stringsOffset;
char *p;
for (p = strings; p < (char*)tag + lmax && *p; p++);
_fileName = Common::Path(Common::String(strings, p - strings), Common::Path::kNoSeparator);
_fileDescriptorOffset = off;
_compressedSize = 0;
_fileDataOffset = 0;
_supported = true;
_isPatchFile = !(tag[8] & 2);
if (!_isPatchFile && numPatchEntries > 0) {
_compressedSize = READ_LE_UINT32(blockb + 0xc);
_fileDataOffset = READ_LE_UINT32(blockb + 0x8);
}
_patchEntries.resize(numPatchEntries);
for (int i = 0; i < numPatchEntries; i++) {
_patchEntries[i]._originalCRC = READ_LE_UINT32(blockb + 0x10 * i);
_patchEntries[i]._originalSize = READ_LE_UINT32(blockb + 0x10 * i + 4);
_patchEntries[i]._patchDataOffset = READ_LE_UINT32(blockb + 0x10 * i + 8);
_patchEntries[i]._patchSize = READ_LE_UINT32(blockb + 0x10 * i + 12);
}
_crcIsXorred = false;
break;
}
}
}
ClickteamInstaller::ClickteamTag* ClickteamInstaller::getTag(ClickteamTagId tagId) const {
return _tags.getValOrDefault((uint16) tagId).get();
}
namespace {
uint32 computeCRC(byte *buf, uint32 sz, uint32 previous) {
uint32 cur = previous;
byte *ptr = buf;
uint32 i;
for (i = 0; i < (sz & ~3); i += 4, ptr += 4)
cur = READ_LE_UINT32(ptr) + (cur >> 31) + (cur << 1);
for (; i < sz; i++, ptr++)
cur = (*ptr) + (cur >> 31) + (cur << 1);
return cur;
}
bool checkStubAndComputeCRC1(Common::SeekableReadStream *stream, uint32 &crc) {
static const byte BLOCK1_MAGIC_START[] = { 0x77, 0x77, 0x49, 0x4e, 0x53, 0x53 };
static const byte BLOCK1_MAGIC_END[] = { 0x77, 0x77, 0x49, 0x4e, 0x53, 0x45 };
static const byte STUB_SIZE_MAGIC[] = { 0x77, 0x77, 0x67, 0x54, 0x29, 0x48 };
static const uint32 MAX_SEARCH_RANGE = 0x16000; // So far, if needed increase
uint32 blockSearchRange = MIN<uint32>(MAX_SEARCH_RANGE, stream->size());
if (blockSearchRange <= sizeof(STUB_SIZE_MAGIC) + 4 + sizeof(BLOCK1_MAGIC_START) + sizeof(BLOCK1_MAGIC_END)) {
return false;
}
byte *stub = new byte[blockSearchRange];
stream->seek(0);
stream->read(stub, blockSearchRange);
byte *block1start = nullptr;
byte *block1end = nullptr;
byte *stubSizePtr = nullptr;
byte *ptr;
for (ptr = stub; ptr < stub + blockSearchRange - sizeof(STUB_SIZE_MAGIC) - 3; ptr++) {
if (memcmp(ptr, STUB_SIZE_MAGIC, sizeof(STUB_SIZE_MAGIC)) == 0)
stubSizePtr = ptr;
if (block1start && memcmp(ptr, BLOCK1_MAGIC_END, sizeof(BLOCK1_MAGIC_END)) == 0)
block1end = ptr;
if (memcmp(ptr, BLOCK1_MAGIC_START, sizeof(BLOCK1_MAGIC_START)) == 0)
block1start = ptr;
if (block1start && block1end && stubSizePtr)
break;
}
if (!block1start || !block1end || !stubSizePtr) {
delete[] stub;
return false;
}
uint32 stubSize = READ_LE_UINT32(stubSizePtr + sizeof(STUB_SIZE_MAGIC));
crc = computeCRC(block1start, block1end - block1start, 0);
delete[] stub;
stream->seek(stubSize);
return true;
}
int32 signExtendAndOffset(uint32 val, int bit, uint32 offset) {
if (val & (1 << bit)) {
return (val | (0xffffffff << bit)) - offset;
}
return val + offset;
}
void applyClickteamPatch(Common::WriteStream *outStream, Common::SeekableReadStream *refStream,
Common::SeekableReadStream *patchStream, Common::SeekableReadStream *literalsStream) {
uint32 referenceBaseOffset = 0;
while (!patchStream->eos() && !outStream->err()) {
uint32 referenceReadSize = 0;
byte patchByte = patchStream->readByte();
if (patchByte & 0x80) {
uint32 litteralReadSize = (patchByte >> 5) & 3;
if (litteralReadSize == 0)
litteralReadSize = ((patchByte & 0x1f) + 1);
else
referenceReadSize = (patchByte & 0x1f) + 2;
byte *buf = new byte[litteralReadSize]; // optimize this
literalsStream->read(buf, litteralReadSize);
outStream->write(buf,litteralReadSize);
delete[] buf;
} else if (patchByte == 0)
referenceReadSize = patchStream->readUint16LE() + 0x81;
else
referenceReadSize = patchByte + 1;
if (referenceReadSize != 0) {
int referenceOffsetDelta;
patchByte = patchStream->readByte();
if (patchByte & 0x80) {
if ((patchByte & 0x40) == 0)
referenceOffsetDelta = signExtendAndOffset(patchByte & 0x3f, 5, 0);
else {
referenceOffsetDelta = ((patchByte & 0x3f) << 16) | patchStream->readUint16BE();
if (referenceOffsetDelta == 0x100000)
referenceOffsetDelta = patchStream->readSint32BE();
else
referenceOffsetDelta = signExtendAndOffset(referenceOffsetDelta, 21, 0x4020);
}
} else
referenceOffsetDelta = signExtendAndOffset(((patchByte & 0x7f) << 8) | patchStream->readByte(), 14, 0x20);
uint32 referenseOffset = referenceOffsetDelta + referenceBaseOffset;
byte *buf = new byte[referenceReadSize]; // optimize this
if (referenseOffset < refStream->size()) {
refStream->seek(referenseOffset);
refStream->read(buf, referenceReadSize);
} else {
memset(buf, 0, referenceReadSize);
}
// TODO: Handle zero-out blocks. We never encountered any so far
outStream->write(buf, referenceReadSize);
delete[] buf;
referenceBaseOffset += referenceOffsetDelta + referenceReadSize;
}
}
}
bool readBlockHeader(Common::SeekableReadStream *stream, uint32 &compressedSize, uint32 &uncompressedSize, bool &isCompressed) {
byte codec = stream->readByte();
if (codec > 7) {
warning("Unknown block codec %d", codec);
return false;
}
if (codec & 2)
uncompressedSize = stream->readUint32LE();
else
uncompressedSize = stream->readUint16LE();
switch (codec & 5) {
case 5:
compressedSize = stream->readUint32LE();
break;
case 1:
compressedSize = stream->readUint16LE();
break;
case 0:
compressedSize = uncompressedSize;
break;
default:
warning("Unknown block codec %d", codec);
return false;
}
isCompressed = codec & 1;
return true;
}
} // end of anonymous namespace
struct TagHead {
uint16 id;
uint16 flags;
uint32 compressedLen;
};
int ClickteamInstaller::findPatchIdx(const ClickteamFileDescriptor &desc, Common::SeekableReadStream *refStream,
const Common::Path &fileName,
uint32 crcXor, bool doWarn) {
bool hasMatching = refStream->size() == desc._uncompressedSize; // Maybe already patched?
for (uint i = 0; !hasMatching && i < desc._patchEntries.size(); i++)
if (desc._patchEntries[i]._originalSize == refStream->size()) {
hasMatching = true;
break;
}
if (!hasMatching) {
if (doWarn)
warning("Couldn't find matching patch entry for file %s size %d", fileName.toString().c_str(), (int)refStream->size());
return -1;
}
uint32 crcOriginal = 0;
{
byte buf[0x1000]; // Must be divisible by 4
while (!refStream->eos()) {
uint32 actual = refStream->read(buf, sizeof(buf));
crcOriginal = computeCRC(buf, actual, crcOriginal);
}
}
int patchDescIdx = -1;
for (uint i = 0; i < desc._patchEntries.size(); i++)
if (desc._patchEntries[i]._originalSize == refStream->size() && desc._patchEntries[i]._originalCRC == (crcOriginal ^ crcXor)) {
patchDescIdx = i;
break;
}
// Maybe already patched if nothing else is found?
if (patchDescIdx == -1 && refStream->size() == desc._uncompressedSize && crcOriginal == desc._expectedCRC)
return -2;
if (patchDescIdx < 0 && doWarn) {
warning("Couldn't find matching patch entry for file %s size %d and CRC 0x%x", fileName.toString().c_str(), (int)refStream->size(), crcOriginal);
}
return patchDescIdx;
}
ClickteamInstaller* ClickteamInstaller::open(const Common::FSNode &node) {
Common::SeekableReadStream *stream = node.createReadStream();
if (!stream)
return nullptr;
ClickteamInstaller *installer = open(stream, DisposeAfterUse::YES);
if (!installer) {
delete stream;
return nullptr;
}
return installer;
}
ClickteamInstaller* ClickteamInstaller::open(Common::SeekableReadStream *stream, DisposeAfterUse::Flag dispose) {
return openPatch(stream, false, true, nullptr, dispose);
}
ClickteamInstaller* ClickteamInstaller::openPatch(Common::SeekableReadStream *stream, bool verifyOriginal, bool verifyAllowSkip,
Common::Archive *reference, DisposeAfterUse::Flag dispose) {
Common::HashMap<Common::Path, ClickteamFileDescriptor, Common::Path::IgnoreCase_Hash, Common::Path::IgnoreCase_EqualTo> files;
HashMap<uint16, Common::SharedPtr<ClickteamTag>> tags;
uint32 crc_xor;
if (!checkStubAndComputeCRC1(stream, crc_xor))
return nullptr;
int64 block3_offset = -1, block3_len = 0;
while (!stream->eos()) {
TagHead tagHead;
stream->read(&tagHead, sizeof(tagHead));
uint16 tagId = FROM_LE_16(tagHead.id);
uint16 flags = FROM_LE_16(tagHead.flags);
uint32 compressedPayloadLen = FROM_LE_32(tagHead.compressedLen);
if (tagId == 0x7f7f) {
stream->skip(4);
block3_offset = stream->pos();
block3_len = stream->readUint32LE();
break;
}
if (compressedPayloadLen == 0) {
break;
}
byte *compressedPayload = new byte[compressedPayloadLen];
stream->read(compressedPayload, compressedPayloadLen);
ClickteamTag *tag;
if (flags & FLAG_COMPRESSED) {
if (compressedPayloadLen < 4) {
delete[] compressedPayload;
continue;
}
uint32 uncompressedPayloadLen = READ_LE_UINT32(compressedPayload);
byte *uncompressedPayload = new byte[uncompressedPayloadLen];
bool ret = inflateClickteam(uncompressedPayload,
uncompressedPayloadLen,
compressedPayload + 4,
compressedPayloadLen - 4);
delete[] compressedPayload;
if (!ret) {
warning("Decompression error for tag 0x%04x", tagId);
continue;
}
tag = new ClickteamTag(tagId, uncompressedPayload, uncompressedPayloadLen);
} else {
tag = new ClickteamTag(tagId, compressedPayload, compressedPayloadLen);
}
tags[tagId].reset(tag);
switch (tag->_tagId) {
case (uint16) ClickteamTagId::FILE_LIST:
case (uint16) ClickteamTagId::FILE_PATCHING_LIST: {
if (tag->_size < 4) {
return nullptr;
}
uint32 count = READ_LE_UINT32(tag->_contents);
uint32 off = 4;
for (unsigned i = 0; i < count && off + 0x24 < tag->_size; i++) {
uint32 l = READ_LE_UINT32(tag->_contents + off);
if (l < 33)
break;
ClickteamFileDescriptor desc(*tag, off);
if (desc._supported) {
// Prefer non-patches
if (!desc._isPatchFile || ! files.contains(desc._fileName))
files[desc._fileName] = desc;
}
off += l;
}
break;
}
case 0x1237: {
byte *p;
for (p = tag->_contents; p < tag->_contents + tag->_size; p++)
if (!*p)
break;
crc_xor = computeCRC(tag->_contents, p - tag->_contents, crc_xor);
break;
}
}
}
if (block3_offset <= 0 || block3_len <= 0)
return nullptr;
if (verifyOriginal && reference) {
for (auto &file : files) {
if (file._value._isPatchFile) {
Common::ScopedPtr<Common::SeekableReadStream> refStream(reference->createReadStreamForMember(file._key));
if (!refStream) {
if (verifyAllowSkip) {
file._value._isReferenceMissing = true;
continue;
}
return nullptr;
}
if (findPatchIdx(file._value, refStream.get(), file._key, crc_xor, false) == -1)
return nullptr;
}
}
}
if (!reference) {
for (auto &file : files) {
if (file._value._isPatchFile) {
file._value._isReferenceMissing = true;
}
}
}
return new ClickteamInstaller(files, tags, crc_xor, block3_offset, block3_len, stream, reference, dispose);
}
bool ClickteamInstaller::hasFile(const Path &path) const {
return _files.contains(translatePath(path));
}
int ClickteamInstaller::listMembers(ArchiveMemberList &list) const {
int members = 0;
for (const auto &file : _files) {
if (!file._value._isReferenceMissing) {
list.push_back(ArchiveMemberList::value_type(new GenericArchiveMember(file._key, *this)));
++members;
}
}
return members;
}
const ArchiveMemberPtr ClickteamInstaller::getMember(const Path &path) const {
Common::Path translated = translatePath(path);
ClickteamFileDescriptor el;
if (!_files.tryGetVal(translated, el))
return nullptr;
if (el._isReferenceMissing)
return nullptr;
return Common::SharedPtr<Common::ArchiveMember>(new GenericArchiveMember(el._fileName, *this));
}
Common::SharedArchiveContents ClickteamInstaller::readContentsForPath(const Common::Path &translated) const {
ClickteamFileDescriptor desc;
byte *uncompressedBuffer = nullptr;
if (!_files.tryGetVal(translated, desc))
return Common::SharedArchiveContents();
if (desc._isReferenceMissing)
return Common::SharedArchiveContents();
if (desc._isPatchFile) {
Common::ScopedPtr<Common::SeekableReadStream> refStream(_reference->createReadStreamForMemberNext(translated, this));
if (!refStream) {
warning("Couldn't open reference file for %s. Skipping", translated.toString('\\').c_str());
return Common::SharedArchiveContents();
}
int patchDescIdx = findPatchIdx(desc, refStream.get(), translated, _crcXor, true);
if (patchDescIdx == -1 || patchDescIdx < -2)
return Common::SharedArchiveContents();
refStream->seek(0);
// Already patched
if (patchDescIdx == -2) {
return Common::SharedArchiveContents::bypass(refStream.release());
}
uint32 patchDataOffset = _block3Offset + desc._patchEntries[patchDescIdx]._patchDataOffset;
_stream->seek(patchDataOffset);
uint32 patchCompressedSize, patchUncompressedSize;
bool patchIsCompressed;
if (!readBlockHeader(_stream.get(), patchCompressedSize, patchUncompressedSize, patchIsCompressed))
return Common::SharedArchiveContents();
uint32 patchStart = _stream->pos();
_stream->skip(patchCompressedSize);
uint32 literalsCompressedSize, literalsUncompressedSize;
bool literalsIsCompressed;
if (!readBlockHeader(_stream.get(), literalsCompressedSize, literalsUncompressedSize, literalsIsCompressed)) {
return Common::SharedArchiveContents();
}
uint32 literalsStart = _stream->pos();
Common::ScopedPtr<Common::SeekableReadStream> uncompressedPatchStream, uncompressedLiteralsStream;
if (patchIsCompressed) {
Common::SeekableReadStream *compressedPatchStream = new Common::SafeSeekableSubReadStream(
_stream.get(), patchStart, patchStart + patchCompressedSize);
if (!compressedPatchStream) {
warning("Decompression error");
return Common::SharedArchiveContents();
}
uncompressedPatchStream.reset(wrapClickteamReadStream(compressedPatchStream, DisposeAfterUse::YES, patchUncompressedSize));
} else {
uncompressedPatchStream.reset(new Common::SafeSeekableSubReadStream(
_stream.get(), patchStart, patchStart + patchCompressedSize));
}
if (!uncompressedPatchStream) {
warning("Decompression error");
return Common::SharedArchiveContents();
}
if (literalsIsCompressed) {
Common::SeekableReadStream *compressedLiteralsStream = new Common::SafeSeekableSubReadStream(
_stream.get(), literalsStart, literalsStart + literalsCompressedSize);
if (!compressedLiteralsStream) {
warning("Decompression error");
return Common::SharedArchiveContents();
}
uncompressedLiteralsStream.reset(wrapClickteamReadStream(compressedLiteralsStream, DisposeAfterUse::YES, literalsUncompressedSize));
} else {
uncompressedLiteralsStream.reset(new Common::SafeSeekableSubReadStream(
_stream.get(), literalsStart, literalsStart + literalsCompressedSize));
}
if (!uncompressedLiteralsStream) {
warning("Decompression error");
return Common::SharedArchiveContents();
}
uncompressedBuffer = new byte[desc._uncompressedSize];
Common::MemoryWriteStream outStream(uncompressedBuffer, desc._uncompressedSize);
applyClickteamPatch(&outStream, refStream.get(), uncompressedPatchStream.get(), uncompressedLiteralsStream.get());
} else {
Common::SeekableReadStream *subStream = new Common::SeekableSubReadStream(_stream.get(), _block3Offset + desc._fileDataOffset,
_block3Offset + desc._fileDataOffset + desc._compressedSize);
if (!subStream) {
warning("Decompression error");
return Common::SharedArchiveContents();
}
Common::ScopedPtr<Common::SeekableReadStream> uncStream(wrapClickteamReadStream(subStream, DisposeAfterUse::YES, desc._uncompressedSize));
if (!uncStream) {
warning("Decompression error");
return Common::SharedArchiveContents();
}
uncompressedBuffer = new byte[desc._uncompressedSize];
int64 ret = uncStream->read(uncompressedBuffer, desc._uncompressedSize);
if (ret < 0 || ret < desc._uncompressedSize) {
warning ("Decompression error");
delete[] uncompressedBuffer;
return Common::SharedArchiveContents();
}
}
if (desc._expectedCRC != 0 || !desc._fileName.equalsIgnoreCase("Uninstal.exe")) {
uint32 expectedCrc = desc._crcIsXorred ? desc._expectedCRC ^ _crcXor : desc._expectedCRC;
uint32 actualCrc = computeCRC(uncompressedBuffer, desc._uncompressedSize, 0);
if (actualCrc != expectedCrc) {
warning("CRC mismatch for %s: expected=%08x (obfuscated %08x), actual=%08x (back %08x)", desc._fileName.toString().c_str(), expectedCrc, desc._expectedCRC, actualCrc, actualCrc ^ _crcXor);
delete[] uncompressedBuffer;
return Common::SharedArchiveContents();
}
}
// TODO: Make it configurable to use a uncompressing substream instead
return Common::SharedArchiveContents(uncompressedBuffer, desc._uncompressedSize);
}
}

View File

@@ -0,0 +1,120 @@
/* 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/>.
*
*/
#ifndef COMMON_CLICKTEAM_H
#define COMMON_CLICKTEAM_H
#include "common/archive.h"
#include "common/fs.h"
#include "common/ptr.h"
#include "common/stream.h"
#include "common/hashmap.h"
#include "common/hash-str.h"
namespace Common {
class ClickteamInstaller : public MemcachingCaseInsensitiveArchive {
public:
enum class ClickteamTagId : uint16 {
BANNER_IMAGE = 0x1235,
FILE_LIST = 0x123a,
STRINGS = 0x123e,
UNINSTALLER = 0x123f,
FILE_PATCHING_LIST = 0x1242,
};
class ClickteamTag : Common::NonCopyable {
protected:
ClickteamTag(uint16 tagId, byte *contents, uint32 size) : _tagId(tagId), _contents(contents), _size(size) {
}
friend class ClickteamInstaller;
public:
uint16 _tagId;
byte *_contents;
uint32 _size;
~ClickteamTag() {
delete[] _contents;
}
};
bool hasFile(const Common::Path &path) const override;
int listMembers(Common::ArchiveMemberList&) const override;
const ArchiveMemberPtr getMember(const Common::Path &path) const override;
Common::SharedArchiveContents readContentsForPath(const Common::Path &translated) const override;
ClickteamTag* getTag(ClickteamTagId tagId) const;
static ClickteamInstaller* openPatch(Common::SeekableReadStream *stream, bool verifyOriginal = true, bool verifyAllowSkip = true,
Common::Archive *reference = &SearchMan, DisposeAfterUse::Flag dispose = DisposeAfterUse::NO);
static ClickteamInstaller* open(Common::SeekableReadStream *stream, DisposeAfterUse::Flag dispose = DisposeAfterUse::NO);
static ClickteamInstaller* open(const Common::FSNode &node);
private:
struct ClickteamPatchDescriptor {
uint32 _originalCRC;
uint32 _originalSize;
uint32 _patchDataOffset;
uint32 _patchSize;
};
class ClickteamFileDescriptor {
private:
Common::Path _fileName;
// Offset of the file contents relative to the beginning of block3
uint32 _fileDataOffset;
// Offset of file descriptor
uint32 _fileDescriptorOffset;
uint32 _compressedSize;
uint32 _uncompressedSize;
uint32 _expectedCRC;
bool _supported;
bool _isPatchFile;
bool _crcIsXorred;
bool _isReferenceMissing;
//uint16 _field1c;
Common::Array<ClickteamPatchDescriptor> _patchEntries;
ClickteamFileDescriptor(const ClickteamTag& contentsTag, uint32 off);
friend class ClickteamInstaller;
public:
// It's public for hashmap
ClickteamFileDescriptor() : _fileDataOffset(0), _fileDescriptorOffset(0), _compressedSize(0), _uncompressedSize(0) {}
};
ClickteamInstaller(const Common::HashMap<Common::Path, ClickteamFileDescriptor, Common::Path::IgnoreCase_Hash, Common::Path::IgnoreCase_EqualTo> &files,
const Common::HashMap<uint16, Common::SharedPtr<ClickteamTag>> &tags,
uint32 crcXor, uint32 block3Offset, uint32 block3Size, Common::SeekableReadStream *stream,
Common::Archive *reference,
DisposeAfterUse::Flag dispose)
: _files(files), _tags(tags), _crcXor(crcXor), _block3Offset(block3Offset), /*_block3Size(block3Size), */_stream(stream, dispose),
_reference(reference) {
}
static int findPatchIdx(const ClickteamFileDescriptor &desc, Common::SeekableReadStream *refStream, const Common::Path &fileName,
uint32 crcXor, bool doWarn);
Common::HashMap<Common::Path, ClickteamFileDescriptor, Common::Path::IgnoreCase_Hash, Common::Path::IgnoreCase_EqualTo> _files;
Common::HashMap<uint16, Common::SharedPtr<ClickteamTag>> _tags;
Common::DisposablePtr<Common::SeekableReadStream> _stream;
uint32 _crcXor, _block3Offset/*, _block3Size*/;
Common::Archive *_reference;
};
}
#endif

521
common/compression/dcl.cpp Normal file
View File

@@ -0,0 +1,521 @@
/* 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/compression/dcl.h"
#include "common/debug.h"
#include "common/memstream.h"
#include "common/stream.h"
#include "common/textconsole.h"
namespace Common {
class DecompressorDCL {
public:
bool unpack(SeekableReadStream *sourceStream, WriteStream *targetStream, uint32 targetSize, bool targetFixedSize);
protected:
/**
* Initialize decompressor.
* @param sourceStream source stream to read from
* @param targetStream target memory stream to write to
*/
void init(SeekableReadStream *sourceStream, WriteStream *targetStream, uint32 targetSize, bool targetFixedSize);
/**
* Get a number of bits from _src stream, starting with the least
* significant unread bit of the current four byte block.
* @param n number of bits to get
* @return n-bits number
*/
uint32 getBitsLSB(int n);
/**
* Get one byte from _src stream.
* @return byte
*/
byte getByteLSB();
void fetchBitsLSB();
/**
* Write one byte into _dest stream
* @param b byte to put
*/
void putByte(byte b);
int huffman_lookup(const int *tree);
uint32 _dwBits; ///< bits buffer
byte _nBits; ///< number of unread bits in _dwBits
uint32 _sourceSize; ///< size of the source stream
uint32 _targetSize; ///< size of the target stream (if fixed)
bool _targetFixedSize; ///< if target stream is fixed size or dynamic size
uint32 _bytesRead; ///< number of bytes read from _sourceStream
uint32 _bytesWritten; ///< number of bytes written to _targetStream
SeekableReadStream *_sourceStream;
WriteStream *_targetStream;
};
void DecompressorDCL::init(SeekableReadStream *sourceStream, WriteStream *targetStream, uint32 targetSize, bool targetFixedSize) {
_sourceStream = sourceStream;
_targetStream = targetStream;
_sourceSize = sourceStream->size();
_targetSize = targetSize;
_targetFixedSize = targetFixedSize;
_nBits = 0;
_bytesRead = _bytesWritten = 0;
_dwBits = 0;
}
void DecompressorDCL::fetchBitsLSB() {
while (_nBits <= 24) {
_dwBits |= ((uint32)_sourceStream->readByte()) << _nBits;
_nBits += 8;
_bytesRead++;
}
}
uint32 DecompressorDCL::getBitsLSB(int n) {
// Fetching more data to buffer if needed
if (_nBits < n)
fetchBitsLSB();
uint32 ret = (_dwBits & ~(~0UL << n));
_dwBits >>= n;
_nBits -= n;
return ret;
}
byte DecompressorDCL::getByteLSB() {
return getBitsLSB(8);
}
void DecompressorDCL::putByte(byte b) {
_targetStream->writeByte(b);
_bytesWritten++;
}
#define HUFFMAN_LEAF 0x40000000
// Branch node
#define BN(pos, left, right) ((left << 12) | (right)),
// Leaf node
#define LN(pos, value) ((value) | HUFFMAN_LEAF),
static const int length_tree[] = {
BN(0, 1, 2)
BN(1, 3, 4) BN(2, 5, 6)
BN(3, 7, 8) BN(4, 9, 10) BN(5, 11, 12) LN(6, 1)
BN(7, 13, 14) BN(8, 15, 16) BN(9, 17, 18) LN(10, 3) LN(11, 2) LN(12, 0)
BN(13, 19, 20) BN(14, 21, 22) BN(15, 23, 24) LN(16, 6) LN(17, 5) LN(18, 4)
BN(19, 25, 26) BN(20, 27, 28) LN(21, 10) LN(22, 9) LN(23, 8) LN(24, 7)
BN(25, 29, 30) LN(26, 13) LN(27, 12) LN(28, 11)
LN(29, 15) LN(30, 14)
0 // We need something witout a comma at the end
};
static const int distance_tree[] = {
BN(0, 1, 2)
BN(1, 3, 4) BN(2, 5, 6)
//
BN(3, 7, 8) BN(4, 9, 10) BN(5, 11, 12) LN(6, 0)
BN(7, 13, 14) BN(8, 15, 16) BN(9, 17, 18) BN(10, 19, 20)
BN(11, 21, 22) BN(12, 23, 24)
//
BN(13, 25, 26) BN(14, 27, 28) BN(15, 29, 30) BN(16, 31, 32)
BN(17, 33, 34) BN(18, 35, 36) BN(19, 37, 38) BN(20, 39, 40)
BN(21, 41, 42) BN(22, 43, 44) LN(23, 2) LN(24, 1)
//
BN(25, 45, 46) BN(26, 47, 48) BN(27, 49, 50) BN(28, 51, 52)
BN(29, 53, 54) BN(30, 55, 56) BN(31, 57, 58) BN(32, 59, 60)
BN(33, 61, 62) BN(34, 63, 64) BN(35, 65, 66) BN(36, 67, 68)
BN(37, 69, 70) BN(38, 71, 72) BN(39, 73, 74) BN(40, 75, 76)
LN(41, 6) LN(42, 5) LN(43, 4) LN(44, 3)
//
BN(45, 77, 78) BN(46, 79, 80) BN(47, 81, 82) BN(48, 83, 84)
BN(49, 85, 86) BN(50, 87, 88) BN(51, 89, 90) BN(52, 91, 92)
BN(53, 93, 94) BN(54, 95, 96) BN(55, 97, 98) BN(56, 99, 100)
BN(57, 101, 102) BN(58, 103, 104) BN(59, 105, 106) BN(60, 107, 108)
BN(61, 109, 110) LN(62, 21) LN(63, 20) LN(64, 19)
LN(65, 18) LN(66, 17) LN(67, 16) LN(68, 15)
LN(69, 14) LN(70, 13) LN(71, 12) LN(72, 11)
LN(73, 10) LN(74, 9) LN(75, 8) LN(76, 7)
//
BN(77, 111, 112) BN(78, 113, 114) BN(79, 115, 116) BN(80, 117, 118)
BN(81, 119, 120) BN(82, 121, 122) BN(83, 123, 124) BN(84, 125, 126)
LN(85, 47) LN(86, 46) LN(87, 45) LN(88, 44)
LN(89, 43) LN(90, 42) LN(91, 41) LN(92, 40)
LN(93, 39) LN(94, 38) LN(95, 37) LN(96, 36)
LN(97, 35) LN(98, 34) LN(99, 33) LN(100, 32)
LN(101, 31) LN(102, 30) LN(103, 29) LN(104, 28)
LN(105, 27) LN(106, 26) LN(107, 25) LN(108, 24)
LN(109, 23) LN(110, 22) LN(111, 63) LN(112, 62)
LN(113, 61) LN(114, 60) LN(115, 59) LN(116, 58)
LN(117, 57) LN(118, 56) LN(119, 55) LN(120, 54)
LN(121, 53) LN(122, 52) LN(123, 51) LN(124, 50)
LN(125, 49) LN(126, 48)
0 // We need something witout a comma at the end
};
static const int ascii_tree[] = {
BN(0, 1, 2) BN(1, 3, 4) BN(2, 5, 6) BN(3, 7, 8)
BN(4, 9, 10) BN(5, 11, 12) BN(6, 13, 14) BN(7, 15, 16)
BN(8, 17, 18) BN(9, 19, 20) BN(10, 21, 22) BN(11, 23, 24)
BN(12, 25, 26) BN(13, 27, 28) BN(14, 29, 30) BN(15, 31, 32)
BN(16, 33, 34) BN(17, 35, 36) BN(18, 37, 38) BN(19, 39, 40)
BN(20, 41, 42) BN(21, 43, 44) BN(22, 45, 46) BN(23, 47, 48)
BN(24, 49, 50) BN(25, 51, 52) BN(26, 53, 54) BN(27, 55, 56)
BN(28, 57, 58) BN(29, 59, 60) LN(30, 32)
//
BN(31, 61, 62) BN(32, 63, 64) BN(33, 65, 66) BN(34, 67, 68)
BN(35, 69, 70) BN(36, 71, 72) BN(37, 73, 74) BN(38, 75, 76)
BN(39, 77, 78) BN(40, 79, 80) BN(41, 81, 82) BN(42, 83, 84)
BN(43, 85, 86) BN(44, 87, 88) BN(45, 89, 90) BN(46, 91, 92)
BN(47, 93, 94) BN(48, 95, 96) BN(49, 97, 98) LN(50, 117)
LN(51, 116) LN(52, 115) LN(53, 114) LN(54, 111)
LN(55, 110) LN(56, 108) LN(57, 105) LN(58, 101)
LN(59, 97) LN(60, 69)
//
BN(61, 99, 100) BN(62, 101, 102) BN(63, 103, 104) BN(64, 105, 106)
BN(65, 107, 108) BN(66, 109, 110) BN(67, 111, 112) BN(68, 113, 114)
BN(69, 115, 116) BN(70, 117, 118) BN(71, 119, 120) BN(72, 121, 122)
BN(73, 123, 124) BN(74, 125, 126) BN(75, 127, 128) BN(76, 129, 130)
BN(77, 131, 132) BN(78, 133, 134) LN(79, 112) LN(80, 109)
LN(81, 104) LN(82, 103) LN(83, 102) LN(84, 100)
LN(85, 99) LN(86, 98) LN(87, 84) LN(88, 83)
LN(89, 82) LN(90, 79) LN(91, 78) LN(92, 76)
LN(93, 73) LN(94, 68) LN(95, 67) LN(96, 65)
LN(97, 49) LN(98, 45)
//
BN(99, 135, 136) BN(100, 137, 138) BN(101, 139, 140) BN(102, 141, 142)
BN(103, 143, 144) BN(104, 145, 146) BN(105, 147, 148) BN(106, 149, 150)
BN(107, 151, 152) BN(108, 153, 154) BN(109, 155, 156) BN(110, 157, 158)
BN(111, 159, 160) BN(112, 161, 162) BN(113, 163, 164) LN(114, 119)
LN(115, 107) LN(116, 85) LN(117, 80) LN(118, 77)
LN(119, 70) LN(120, 66) LN(121, 61) LN(122, 56)
LN(123, 55) LN(124, 53) LN(125, 52) LN(126, 51)
LN(127, 50) LN(128, 48) LN(129, 46) LN(130, 44)
LN(131, 41) LN(132, 40) LN(133, 13) LN(134, 10)
//
BN(135, 165, 166) BN(136, 167, 168) BN(137, 169, 170) BN(138, 171, 172)
BN(139, 173, 174) BN(140, 175, 176) BN(141, 177, 178) BN(142, 179, 180)
BN(143, 181, 182) BN(144, 183, 184) BN(145, 185, 186) BN(146, 187, 188)
BN(147, 189, 190) BN(148, 191, 192) LN(149, 121) LN(150, 120)
LN(151, 118) LN(152, 95) LN(153, 91) LN(154, 87)
LN(155, 72) LN(156, 71) LN(157, 58) LN(158, 57)
LN(159, 54) LN(160, 47) LN(161, 42) LN(162, 39)
LN(163, 34) LN(164, 9)
//
BN(165, 193, 194) BN(166, 195, 196) BN(167, 197, 198) BN(168, 199, 200)
BN(169, 201, 202) BN(170, 203, 204) BN(171, 205, 206) BN(172, 207, 208)
BN(173, 209, 210) BN(174, 211, 212) BN(175, 213, 214) BN(176, 215, 216)
BN(177, 217, 218) BN(178, 219, 220) BN(179, 221, 222) BN(180, 223, 224)
BN(181, 225, 226) BN(182, 227, 228) BN(183, 229, 230) BN(184, 231, 232)
BN(185, 233, 234) LN(186, 93) LN(187, 89) LN(188, 88)
LN(189, 86) LN(190, 75) LN(191, 62) LN(192, 43)
//
BN(193, 235, 236) BN(194, 237, 238) BN(195, 239, 240) BN(196, 241, 242)
BN(197, 243, 244) BN(198, 245, 246) BN(199, 247, 248) BN(200, 249, 250)
BN(201, 251, 252) BN(202, 253, 254) BN(203, 255, 256) BN(204, 257, 258)
BN(205, 259, 260) BN(206, 261, 262) BN(207, 263, 264) BN(208, 265, 266)
BN(209, 267, 268) BN(210, 269, 270) BN(211, 271, 272) BN(212, 273, 274)
BN(213, 275, 276) BN(214, 277, 278) BN(215, 279, 280) BN(216, 281, 282)
BN(217, 283, 284) BN(218, 285, 286) BN(219, 287, 288) BN(220, 289, 290)
BN(221, 291, 292) BN(222, 293, 294) BN(223, 295, 296) BN(224, 297, 298)
BN(225, 299, 300) BN(226, 301, 302) BN(227, 303, 304) BN(228, 305, 306)
BN(229, 307, 308) LN(230, 122) LN(231, 113) LN(232, 38)
LN(233, 36) LN(234, 33)
//
BN(235, 309, 310) BN(236, 311, 312) BN(237, 313, 314) BN(238, 315, 316)
BN(239, 317, 318) BN(240, 319, 320) BN(241, 321, 322) BN(242, 323, 324)
BN(243, 325, 326) BN(244, 327, 328) BN(245, 329, 330) BN(246, 331, 332)
BN(247, 333, 334) BN(248, 335, 336) BN(249, 337, 338) BN(250, 339, 340)
BN(251, 341, 342) BN(252, 343, 344) BN(253, 345, 346) BN(254, 347, 348)
BN(255, 349, 350) BN(256, 351, 352) BN(257, 353, 354) BN(258, 355, 356)
BN(259, 357, 358) BN(260, 359, 360) BN(261, 361, 362) BN(262, 363, 364)
BN(263, 365, 366) BN(264, 367, 368) BN(265, 369, 370) BN(266, 371, 372)
BN(267, 373, 374) BN(268, 375, 376) BN(269, 377, 378) BN(270, 379, 380)
BN(271, 381, 382) BN(272, 383, 384) BN(273, 385, 386) BN(274, 387, 388)
BN(275, 389, 390) BN(276, 391, 392) BN(277, 393, 394) BN(278, 395, 396)
BN(279, 397, 398) BN(280, 399, 400) BN(281, 401, 402) BN(282, 403, 404)
BN(283, 405, 406) BN(284, 407, 408) BN(285, 409, 410) BN(286, 411, 412)
BN(287, 413, 414) BN(288, 415, 416) BN(289, 417, 418) BN(290, 419, 420)
BN(291, 421, 422) BN(292, 423, 424) BN(293, 425, 426) BN(294, 427, 428)
BN(295, 429, 430) BN(296, 431, 432) BN(297, 433, 434) BN(298, 435, 436)
LN(299, 124) LN(300, 123) LN(301, 106) LN(302, 92)
LN(303, 90) LN(304, 81) LN(305, 74) LN(306, 63)
LN(307, 60) LN(308, 0)
//
BN(309, 437, 438) BN(310, 439, 440) BN(311, 441, 442) BN(312, 443, 444)
BN(313, 445, 446) BN(314, 447, 448) BN(315, 449, 450) BN(316, 451, 452)
BN(317, 453, 454) BN(318, 455, 456) BN(319, 457, 458) BN(320, 459, 460)
BN(321, 461, 462) BN(322, 463, 464) BN(323, 465, 466) BN(324, 467, 468)
BN(325, 469, 470) BN(326, 471, 472) BN(327, 473, 474) BN(328, 475, 476)
BN(329, 477, 478) BN(330, 479, 480) BN(331, 481, 482) BN(332, 483, 484)
BN(333, 485, 486) BN(334, 487, 488) BN(335, 489, 490) BN(336, 491, 492)
BN(337, 493, 494) BN(338, 495, 496) BN(339, 497, 498) BN(340, 499, 500)
BN(341, 501, 502) BN(342, 503, 504) BN(343, 505, 506) BN(344, 507, 508)
BN(345, 509, 510) LN(346, 244) LN(347, 243) LN(348, 242)
LN(349, 238) LN(350, 233) LN(351, 229) LN(352, 225)
LN(353, 223) LN(354, 222) LN(355, 221) LN(356, 220)
LN(357, 219) LN(358, 218) LN(359, 217) LN(360, 216)
LN(361, 215) LN(362, 214) LN(363, 213) LN(364, 212)
LN(365, 211) LN(366, 210) LN(367, 209) LN(368, 208)
LN(369, 207) LN(370, 206) LN(371, 205) LN(372, 204)
LN(373, 203) LN(374, 202) LN(375, 201) LN(376, 200)
LN(377, 199) LN(378, 198) LN(379, 197) LN(380, 196)
LN(381, 195) LN(382, 194) LN(383, 193) LN(384, 192)
LN(385, 191) LN(386, 190) LN(387, 189) LN(388, 188)
LN(389, 187) LN(390, 186) LN(391, 185) LN(392, 184)
LN(393, 183) LN(394, 182) LN(395, 181) LN(396, 180)
LN(397, 179) LN(398, 178) LN(399, 177) LN(400, 176)
LN(401, 127) LN(402, 126) LN(403, 125) LN(404, 96)
LN(405, 94) LN(406, 64) LN(407, 59) LN(408, 37)
LN(409, 35) LN(410, 31) LN(411, 30) LN(412, 29)
LN(413, 28) LN(414, 27) LN(415, 25) LN(416, 24)
LN(417, 23) LN(418, 22) LN(419, 21) LN(420, 20)
LN(421, 19) LN(422, 18) LN(423, 17) LN(424, 16)
LN(425, 15) LN(426, 14) LN(427, 12) LN(428, 11)
LN(429, 8) LN(430, 7) LN(431, 6) LN(432, 5)
LN(433, 4) LN(434, 3) LN(435, 2) LN(436, 1)
LN(437, 255) LN(438, 254) LN(439, 253) LN(440, 252)
LN(441, 251) LN(442, 250) LN(443, 249) LN(444, 248)
LN(445, 247) LN(446, 246) LN(447, 245) LN(448, 241)
LN(449, 240) LN(450, 239) LN(451, 237) LN(452, 236)
LN(453, 235) LN(454, 234) LN(455, 232) LN(456, 231)
LN(457, 230) LN(458, 228) LN(459, 227) LN(460, 226)
LN(461, 224) LN(462, 175) LN(463, 174) LN(464, 173)
LN(465, 172) LN(466, 171) LN(467, 170) LN(468, 169)
LN(469, 168) LN(470, 167) LN(471, 166) LN(472, 165)
LN(473, 164) LN(474, 163) LN(475, 162) LN(476, 161)
LN(477, 160) LN(478, 159) LN(479, 158) LN(480, 157)
LN(481, 156) LN(482, 155) LN(483, 154) LN(484, 153)
LN(485, 152) LN(486, 151) LN(487, 150) LN(488, 149)
LN(489, 148) LN(490, 147) LN(491, 146) LN(492, 145)
LN(493, 144) LN(494, 143) LN(495, 142) LN(496, 141)
LN(497, 140) LN(498, 139) LN(499, 138) LN(500, 137)
LN(501, 136) LN(502, 135) LN(503, 134) LN(504, 133)
LN(505, 132) LN(506, 131) LN(507, 130) LN(508, 129)
LN(509, 128) LN(510, 26)
};
int DecompressorDCL::huffman_lookup(const int *tree) {
int pos = 0;
while (!(tree[pos] & HUFFMAN_LEAF)) {
int bit = getBitsLSB(1);
debug(8, "[%d]:%d->", pos, bit);
pos = bit ? tree[pos] & 0xFFF : tree[pos] >> 12;
}
debug(8, "=%02x\n", tree[pos] & 0xffff);
return tree[pos] & 0xFFFF;
}
#define DCL_BINARY_MODE 0
#define DCL_ASCII_MODE 1
#define MIDI_SETUP_BUNDLE_FILE_MAXIMUM_DICTIONARY_SIZE 4096
bool DecompressorDCL::unpack(SeekableReadStream *sourceStream, WriteStream *targetStream, uint32 targetSize, bool targetFixedSize) {
byte dictionary[MIDI_SETUP_BUNDLE_FILE_MAXIMUM_DICTIONARY_SIZE];
uint16 dictionaryPos = 0;
uint16 dictionarySize = 0;
uint16 dictionaryMask = 0;
int value;
uint16 tokenOffset = 0;
uint16 tokenLength = 0;
init(sourceStream, targetStream, targetSize, targetFixedSize);
byte mode = getByteLSB();
byte dictionaryType = getByteLSB();
if (mode != DCL_BINARY_MODE && mode != DCL_ASCII_MODE) {
warning("DCL-IMPLODE: Error: Encountered mode %02x, expected 00 or 01", mode);
return false;
}
// TODO: original code supported 3 as well???
// Was this an accident or on purpose? And the original code did just give out a warning
// and didn't error out at all
switch (dictionaryType) {
case 4:
dictionarySize = 1024;
break;
case 5:
dictionarySize = 2048;
break;
case 6:
dictionarySize = 4096;
break;
default:
warning("DCL-IMPLODE: Error: unsupported dictionary type %02x", dictionaryType);
return false;
}
dictionaryMask = dictionarySize - 1;
while ((!targetFixedSize) || (_bytesWritten < _targetSize)) {
if (getBitsLSB(1)) { // (length,distance) pair
value = huffman_lookup(length_tree);
if (value < 8)
tokenLength = value + 2;
else
tokenLength = 8 + (1 << (value - 7)) + getBitsLSB(value - 7);
if (tokenLength == 519)
break; // End of stream signal
debug(8, " | ");
value = huffman_lookup(distance_tree);
if (tokenLength == 2)
tokenOffset = (value << 2) | getBitsLSB(2);
else
tokenOffset = (value << dictionaryType) | getBitsLSB(dictionaryType);
tokenOffset++;
debug(8, "\nCOPY(%d from %d)\n", tokenLength, tokenOffset);
if (_targetFixedSize) {
if (tokenLength + _bytesWritten > _targetSize) {
warning("DCL-IMPLODE Error: Write out of bounds while copying %d bytes (declared unpacked size is %d bytes, current is %d + %d bytes)",
tokenLength, _targetSize, _bytesWritten, tokenLength);
return false;
}
}
if (_bytesWritten < tokenOffset) {
warning("DCL-IMPLODE Error: Attempt to copy from before beginning of input stream (declared unpacked size is %d bytes, current is %d bytes)",
_targetSize, _bytesWritten);
return false;
}
uint16 dictionaryBaseIndex = (dictionaryPos - tokenOffset) & dictionaryMask;
uint16 dictionaryIndex = dictionaryBaseIndex;
uint16 dictionaryNextIndex = dictionaryPos;
while (tokenLength) {
// Write byte from dictionary
putByte(dictionary[dictionaryIndex]);
debug(9, "\33[32;31m%02x\33[37;37m ", dictionary[dictionaryIndex]);
dictionary[dictionaryNextIndex] = dictionary[dictionaryIndex];
dictionaryNextIndex = (dictionaryNextIndex + 1) & dictionaryMask;
dictionaryIndex = (dictionaryIndex + 1) & dictionaryMask;
if (dictionaryIndex == dictionaryPos)
dictionaryIndex = dictionaryBaseIndex;
if (dictionaryNextIndex == dictionarySize)
dictionaryNextIndex = 0;
tokenLength--;
}
dictionaryPos = dictionaryNextIndex;
debug(9, "\n");
} else { // Copy byte verbatim
value = (mode == DCL_ASCII_MODE) ? huffman_lookup(ascii_tree) : getByteLSB();
putByte(value);
// Also remember it inside dictionary
dictionary[dictionaryPos] = value;
dictionaryPos++;
if (dictionaryPos >= dictionarySize)
dictionaryPos = 0;
debug(9, "\33[32;31m%02x \33[37;37m", value);
}
}
if (_targetFixedSize) {
if (_bytesWritten != _targetSize)
warning("DCL-IMPLODE Error: Inconsistent bytes written (%d) and target buffer size (%d)", _bytesWritten, _targetSize);
return _bytesWritten == _targetSize;
}
return true; // For targets featuring dynamic size we always succeed
}
bool decompressDCL(ReadStream *src, byte *dest, uint32 packedSize, uint32 unpackedSize) {
bool success = false;
DecompressorDCL dcl;
if (!src || !dest)
return false;
byte *sourceBufferPtr = (byte *)malloc(packedSize);
if (!sourceBufferPtr)
return false;
// Read source into memory
src->read(sourceBufferPtr, packedSize);
Common::MemoryReadStream *sourceStream = new MemoryReadStream(sourceBufferPtr, packedSize, DisposeAfterUse::YES);
Common::MemoryWriteStream *targetStream = new MemoryWriteStream(dest, unpackedSize);
success = dcl.unpack(sourceStream, targetStream, unpackedSize, true);
delete sourceStream;
delete targetStream;
return success;
}
SeekableReadStream *decompressDCL(SeekableReadStream *sourceStream, uint32 packedSize, uint32 unpackedSize) {
bool success = false;
byte *targetPtr = nullptr;
Common::MemoryWriteStream *targetStream;
DecompressorDCL dcl;
targetPtr = (byte *)malloc(unpackedSize);
if (!targetPtr)
return nullptr;
targetStream = new MemoryWriteStream(targetPtr, unpackedSize);
success = dcl.unpack(sourceStream, targetStream, unpackedSize, true);
delete targetStream;
if (!success) {
free(targetPtr);
return nullptr;
}
return new MemoryReadStream(targetPtr, unpackedSize, DisposeAfterUse::YES);
}
// This one figures out the unpacked size by itself
// Needed for at least Simon 2, because the unpacked size is not stored anywhere
SeekableReadStream *decompressDCL(SeekableReadStream *sourceStream) {
Common::MemoryWriteStreamDynamic *targetStream;
DecompressorDCL dcl;
targetStream = new MemoryWriteStreamDynamic(DisposeAfterUse::NO);
if (dcl.unpack(sourceStream, targetStream, 0, false)) {
byte *targetPtr = targetStream->getData();
uint32 unpackedSize = targetStream->size();
delete targetStream;
return new MemoryReadStream(targetPtr, unpackedSize, DisposeAfterUse::YES);
}
delete targetStream;
return nullptr;
}
} // End of namespace Common

78
common/compression/dcl.h Normal file
View File

@@ -0,0 +1,78 @@
/* 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/>.
*
*/
#ifndef COMMON_DCL_H
#define COMMON_DCL_H
#include "common/scummsys.h"
namespace Common {
/**
* @defgroup common_dcl Data compression library
* @ingroup common
*
* @brief PKWARE data compression library (DCL).
*
* @details PKWARE DCL ("explode") ("PKWARE data compression library") decompressor used in engines:
* - AGOS (exclusively for Simon 2 setup.shr file)
* - Mohawk
* - Neverhood
* - SCI
* - MADS (exclusively for the Rex Nebular installer)
* @{
*/
class ReadStream;
class SeekableReadStream;
/**
* Decompress a PKWARE DCL compressed stream.
*
* @return Returns true if successful.
*/
bool decompressDCL(ReadStream *sourceStream, byte *dest, uint32 packedSize, uint32 unpackedSize);
/**
* @overload
*
* Decompress a PKWARE DCL compressed stream.
*
* @return Returns a valid pointer if successful or 0 otherwise.
*/
SeekableReadStream *decompressDCL(SeekableReadStream *sourceStream, uint32 packedSize, uint32 unpackedSize);
/**
* @overload
*
* Decompress a PKWARE DCL compressed stream.
*
* This method is meant for cases, where the unpacked size is not known.
*
* @return Returns a valid pointer if successful or 0 otherwise.
*/
SeekableReadStream *decompressDCL(SeekableReadStream *sourceStream);
/** @} */
} // End of namespace Common
#endif

View File

@@ -0,0 +1,187 @@
/* 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/>.
*
*/
#ifndef COMMON_ZLIB_H
#define COMMON_ZLIB_H
#include "common/scummsys.h"
#include "common/types.h"
namespace Common {
/**
* @defgroup common_deflate deflate
* @ingroup common
*
* @brief API for deflate based operations.
*
* @{
*/
class SeekableReadStream;
class WriteStream;
/**
* Thin wrapper around zlib's uncompress() function. This wrapper makes
* it possible to uncompress data in engines without being forced to link
* them against zlib, thus simplifying the build system.
*
* Taken from the zlib manual:
* Decompresses the src buffer into the dst buffer.
* srcLen is the byte length of the source buffer. Upon entry, dstLen is the
* total size of the destination buffer, which must be large enough to hold
* the entire uncompressed data. Upon exit, dstLen is the actual size of the
* compressed buffer.
*
* @param dst the buffer to store into.
* @param dstLen a pointer to the size of the destination buffer.
* @param src the data to be decompressed.
* @param srcLen the size of the compressed data.
*
* @return true on success (i.e. Z_OK), false otherwise.
*/
bool inflateZlib(byte *dst, unsigned long *dstLen, const byte *src, unsigned long srcLen);
static inline bool inflateZlib(byte *dst, unsigned long dstLen, const byte *src, unsigned long srcLen) {
return inflateZlib(dst, &dstLen, src, srcLen);
}
/**
* Wrapper around zlib's inflate functions. This function will call the
* necessary inflate functions to uncompress data compressed with deflate
* but *not* with the standard zlib header.
*
* 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.
* If a dictionary is provided through the dict buffer, uses it to initializes
* the internal decompression dictionary, before the decompression takes place.
*
* @param dst the buffer to store into.
* @param dstLen the size of the destination buffer.
* @param src the data to be decompressed.
* @param srcLen the size of the compressed data.
* @param dict (optional) a decompress dictionary.
* @param dictLen (optional) the size of the dictionary.
* Mandatory if dict is not 0.
*
* @return true on success (Z_OK or Z_STREAM_END), false otherwise.
*/
bool inflateZlibHeaderless(byte *dst, uint *dstLen, const byte *src, uint srcLen, const byte *dict = nullptr, uint dictLen = 0);
static inline bool inflateZlibHeaderless(byte *dst, uint dstLen, const byte *src, uint srcLen, const byte *dict = nullptr, uint dictLen = 0) {
return inflateZlibHeaderless(dst, &dstLen, src, srcLen, dict, dictLen);
}
/**
* Wrapper around zlib's inflate functions. This function will call the
* modified inflate functions to uncompress data compressed for Clickteam
* Installer 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 srcLen the size of the compressed data.
*
* @return true on success (Z_OK or Z_STREAM_END), false otherwise.
*/
bool inflateClickteam(byte *dst, uint *dstLen, const byte *src, uint srcLen);
static inline bool inflateClickteam(byte *dst, uint dstLen, const byte *src, uint srcLen) {
return inflateClickteam(dst, &dstLen, src, srcLen);
}
/**
* Take an arbitrary SeekableReadStream and wrap it in a custom stream which
* provides transparent on-the-fly decompression. Assumes the data it
* retrieves from the wrapped stream to be either uncompressed or in gzip
* format. In the former case, the original stream is returned unmodified
* (and in particular, not wrapped). In the latter case the stream is
* returned wrapped, unless there is no ZLIB support, then NULL is returned
* and the old stream is destroyed.
*
* Certain GZip-formats don't supply an easily readable length, if you
* still need the length carried along with the stream, and you know
* the decompressed length at wrap-time, then it can be supplied as knownSize
* here. knownSize will be ignored if the GZip-stream DOES include a length.
* The created stream also becomes responsible for freeing the passed stream.
*
* It is safe to call this with a NULL parameter (in this case, NULL is
* returned).
*
* @param toBeWrapped the stream to be wrapped (if it is in gzip-format)
* @param knownSize a supplied length of the uncompressed data (if not available directly)
*/
SeekableReadStream *wrapCompressedReadStream(SeekableReadStream *toBeWrapped,
DisposeAfterUse::Flag disposeParent = DisposeAfterUse::YES, uint64 knownSize = 0);
/**
* Take an arbitrary SeekableReadStream and wrap it in a custom stream which
* provides transparent on-the-fly decompression. Assumes the data it
* retrieves from the wrapped stream is compressed with deflate algorithm.
*
* It is safe to call this with a NULL parameter (in this case, NULL is
* returned).
*
* @param toBeWrapped the stream to be wrapped (if it is in gzip-format)
* @param knownSize a supplied length of the uncompressed data (if not available directly)
*/
SeekableReadStream *wrapDeflateReadStream(SeekableReadStream *toBeWrapped,
DisposeAfterUse::Flag disposeParent = DisposeAfterUse::YES, uint64 knownSize = 0,
const byte *dict = nullptr, uint dictLen = 0);
/**
* Take an arbitrary SeekableReadStream and wrap it in a custom stream which
* provides transparent on-the-fly decompression. Assumes the data it
* retrieves from the wrapped stream is compressed with the Clickteam deflate
* variant algorithm.
* The stream is returned wrapped, unless there is no ZLIB support,
* then NULL is returned and the old stream is destroyed.
*
* It is safe to call this with a NULL parameter (in this case, NULL is
* returned).
*
* @param toBeWrapped the stream to be wrapped (if it is in gzip-format)
* @param knownSize a supplied length of the compressed data (if not available directly)
*/
SeekableReadStream *wrapClickteamReadStream(SeekableReadStream *toBeWrapped,
DisposeAfterUse::Flag disposeParent = DisposeAfterUse::YES, uint64 knownSize = 0);
/**
* Take an arbitrary WriteStream and wrap it in a custom stream which provides
* transparent on-the-fly compression. The compressed data is written in the
* gzip format, unless ZLIB support has been disabled, in which case the given
* stream is returned unmodified (and in particular, not wrapped).
* The created stream also becomes responsible for freeing the passed stream.
*
* It is safe to call this with a NULL parameter (in this case, NULL is
* returned).
*/
WriteStream *wrapCompressedWriteStream(WriteStream *toBeWrapped);
/** @} */
} // End of namespace Common
#endif

View File

@@ -0,0 +1,979 @@
/* 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/debug.h"
#include "common/endian.h"
#include "common/stream.h"
#include "common/bufferedstream.h"
#include "common/substream.h"
#include "common/compression/gentee_installer.h"
namespace Common {
namespace GenteeInstaller {
struct HuffmanTreeNode {
HuffmanTreeNode();
HuffmanTreeNode *_parent;
HuffmanTreeNode *_children[2];
// NOTE: This must be signed! The decoder uses a broken normalization algorithm which halves all frequency
// counts (instead of only halving leaf counts and recomputing parent nodes), which causes parent nodes
// to desync if both children have odd frequencies. This can cause the rebalancing algorithm to assign
// negative numbers to frequencies, which in turn affects other comparisons through the decoder.
//
// We need to accurately replicate this bug to get the correct decode behavior.
int32 _freq;
uint16 _symbol;
};
HuffmanTreeNode::HuffmanTreeNode() : _parent(nullptr), _children{nullptr, nullptr}, _freq(0), _symbol(0) {
}
class HuffmanTree {
public:
HuffmanTreeNode *_treeRoot;
HuffmanTreeNode *_nodes;
void incrementFreqCount(uint16 symbol);
protected:
void initTree(HuffmanTreeNode *nodes, uint16 numLeafs);
private:
void buildTree();
static void rebalanceTree(HuffmanTreeNode *entry, HuffmanTreeNode *entryParentSibling);
uint32 _numNodes;
int32 _maxFreq;
uint16 _numLeafs;
};
void HuffmanTree::buildTree() {
HuffmanTreeNode *entries = _nodes;
uint32 numResultingNodes = _numLeafs * 2 - 1;
for (uint32 i = 0; i < numResultingNodes; i++)
entries[i]._parent = nullptr;
for (uint32 nextNode = _numLeafs; nextNode < numResultingNodes; nextNode++) {
HuffmanTreeNode *lowest = nullptr;
HuffmanTreeNode *secondLowest = nullptr;
for (uint32 ci = 0; ci < nextNode; ci++) {
HuffmanTreeNode *candidate = entries + ci;
if (candidate->_parent == nullptr) {
if (lowest == nullptr)
lowest = candidate;
else {
if (candidate->_freq < lowest->_freq) {
secondLowest = lowest;
lowest = candidate;
} else if (secondLowest == nullptr || candidate->_freq < secondLowest->_freq)
secondLowest = candidate;
}
}
}
HuffmanTreeNode *newEntry = entries + nextNode;
newEntry->_freq = lowest->_freq + secondLowest->_freq;
newEntry->_symbol = nextNode;
newEntry->_children[0] = lowest;
newEntry->_children[1] = secondLowest;
lowest->_parent = newEntry;
secondLowest->_parent = newEntry;
}
_numNodes = numResultingNodes;
_treeRoot = entries + numResultingNodes - 1;
}
void HuffmanTree::incrementFreqCount(uint16 symbol) {
for (HuffmanTreeNode *checkEntry = _nodes + symbol; checkEntry; checkEntry = checkEntry->_parent) {
HuffmanTreeNode *entryParent = checkEntry->_parent;
if (checkEntry->_parent) {
HuffmanTreeNode *parentOfParent = entryParent->_parent;
if (parentOfParent) {
HuffmanTreeNode *entryParentSibling = parentOfParent->_children[0];
if (entryParent == entryParentSibling)
entryParentSibling = parentOfParent->_children[1];
if (entryParentSibling->_freq <= checkEntry->_freq)
rebalanceTree(checkEntry, entryParentSibling);
}
}
checkEntry->_freq = checkEntry->_freq + 1;
}
if (_maxFreq <= _treeRoot->_freq) {
HuffmanTreeNode *entries = _nodes;
for (uint i = 0; i < _numNodes; i++)
entries[i]._freq >>= 1;
}
}
void HuffmanTree::rebalanceTree(HuffmanTreeNode *entry, HuffmanTreeNode *entryParentSibling) {
for (uint pass = 0; pass < 2; pass++) {
HuffmanTreeNode *entryParent = entry->_parent;
HuffmanTreeNode *entryParentChild0 = entryParent->_children[0];
HuffmanTreeNode *entrySibling = entryParentChild0;
if (entry == entryParentChild0)
entrySibling = entryParent->_children[1];
HuffmanTreeNode *entryParentSiblingHighestFreqChild = entryParentSibling->_children[0];
if (entryParentSiblingHighestFreqChild && entryParentSiblingHighestFreqChild->_freq <= entryParentSibling->_children[1]->_freq)
entryParentSiblingHighestFreqChild = entryParentSibling->_children[1];
if (entry == entryParentChild0)
entryParent->_children[0] = entryParentSibling;
else
entryParent->_children[1] = entryParentSibling;
HuffmanTreeNode *entryParentParent = entryParent->_parent;
if (entryParentParent->_children[0] == entryParentSibling)
entryParentParent->_children[0] = entry;
else
entryParentParent->_children[1] = entry;
entry->_parent = entryParentParent;
entryParentSibling->_parent = entryParent;
if (pass > 0 || entryParentSiblingHighestFreqChild == nullptr || entryParentSiblingHighestFreqChild->_freq <= entrySibling->_freq)
break;
entryParentSibling->_freq = entryParentSibling->_freq + entrySibling->_freq - entryParentSiblingHighestFreqChild->_freq;
entry = entryParentSiblingHighestFreqChild;
entryParentSibling = entrySibling;
}
}
void HuffmanTree::initTree(HuffmanTreeNode *nodes, uint16 numLeafs) {
_numLeafs = numLeafs;
_nodes = nodes;
_maxFreq = 512;
for (uint16 i = 0; i < numLeafs; i++) {
HuffmanTreeNode *entry = &nodes[i];
entry->_symbol = i;
entry->_freq = i + 1;
}
buildTree();
}
template<uint TNumLeafs>
class HuffmanTreePresized : public HuffmanTree {
public:
HuffmanTreePresized();
void reset();
private:
HuffmanTreeNode _preallocNodes[TNumLeafs * 2];
};
template<uint TNumLeafs>
HuffmanTreePresized<TNumLeafs>::HuffmanTreePresized() {
reset();
}
template<uint TNumLeafs>
void HuffmanTreePresized<TNumLeafs>::reset() {
initTree(_preallocNodes, TNumLeafs);
}
class DecompressorState
{
public:
explicit DecompressorState(Common::ReadStream *inputStream);
void resetEverything();
void resetBitstream();
uint decompressBytes(void *dest, uint size);
private:
static const uint kWindowSize = 32767;
static const uint kMatchHistorySize = 4;
static const uint kNumMatchVLCs = 30;
HuffmanTreePresized<274> _codeTree;
HuffmanTreePresized<34> _offsetTree;
HuffmanTreePresized<237> _lengthTree;
uint32 _matchOffsetHistory[kMatchHistorySize];
uint16 _windowOffset;
byte _window[kWindowSize];
Common::ReadStream *_inputStream;
static const byte g_matchVLCLengths[kNumMatchVLCs];
uint16 _matchVLCOffsets[kNumMatchVLCs];
private:
uint16 decodeDynamicHuffmanValue(HuffmanTree *tree);
void recordMatchOffset(uint matchOffset);
byte readBit();
uint16 readBits(uint16 numBits);
void emitByte(void *dest, byte b);
uint _matchReadPos;
uint _matchRemaining;
byte _bitstreamByte;
byte _bitstreamBitsRemaining;
bool _failed;
};
DecompressorState::DecompressorState(Common::ReadStream *inputStream)
: _inputStream(inputStream), _bitstreamBitsRemaining(0), _bitstreamByte(0), _matchRemaining(0), _matchReadPos(0), _failed(false), _matchOffsetHistory{0, 0, 0, 0}, _windowOffset(0) {
resetEverything();
}
void DecompressorState::resetEverything() {
resetBitstream();
for (byte &b : _window)
b = 0;
_windowOffset = 0;
uint16 nextValue = 0;
for (int i = 0; i < 30; i++) {
_matchVLCOffsets[i] = nextValue;
nextValue = nextValue + (1 << (g_matchVLCLengths[i] & 0x1f));
}
for (uint32 i = 0; i < 4; i++)
_matchOffsetHistory[i] = i;
_codeTree.reset();
_offsetTree.reset();
_lengthTree.reset();
_failed = false;
}
void DecompressorState::resetBitstream() {
_bitstreamBitsRemaining = 0;
_bitstreamByte = 0;
}
uint DecompressorState::decompressBytes(void *dest, uint size) {
uint remaining = size;
while (remaining > 0) {
if (_failed)
break;
if (_matchRemaining > 0) {
if (_matchReadPos == kWindowSize)
_matchReadPos = 0;
emitByte(dest, _window[_matchReadPos]);
dest = static_cast<byte *>(dest) + 1;
remaining--;
_matchRemaining--;
_matchReadPos++;
continue;
} else {
uint16 code = decodeDynamicHuffmanValue(&_codeTree);
if (code < 256) {
if (_failed)
break;
emitByte(dest, static_cast<byte>(code));
dest = static_cast<byte *>(dest) + 1;
remaining--;
} else {
uint matchLength = 0;
if (code > 272)
matchLength = decodeDynamicHuffmanValue(&_lengthTree) + 20;
else
matchLength = code - 253;
code = decodeDynamicHuffmanValue(&_offsetTree);
uint matchOffset = 0;
if (code < 30) {
// Coded offset
matchOffset = readBits(g_matchVLCLengths[code]) + _matchVLCOffsets[code];
} else {
// Historic offset
matchOffset = _matchOffsetHistory[code - 30];
}
uint backDistance = matchLength + matchOffset;
_matchReadPos = _windowOffset;
while (_matchReadPos < backDistance)
_matchReadPos += kWindowSize;
_matchReadPos -= backDistance;
_matchRemaining = matchLength;
recordMatchOffset(matchOffset);
}
}
}
return size - remaining;
}
uint16 DecompressorState::decodeDynamicHuffmanValue(HuffmanTree *tree) {
HuffmanTreeNode *node = tree->_treeRoot;
while (node->_children[0] != nullptr)
node = node->_children[readBit()];
tree->incrementFreqCount(node->_symbol);
return node->_symbol;
}
byte DecompressorState::readBit() {
if (_bitstreamBitsRemaining == 0) {
if (_failed)
return 0;
if (!_inputStream->read(&_bitstreamByte, 1)) {
_failed = true;
return 0;
}
_bitstreamBitsRemaining = 7;
} else
_bitstreamBitsRemaining--;
byte bit = _bitstreamByte >> 7;
_bitstreamByte <<= 1;
return bit;
}
uint16 DecompressorState::readBits(uint16 numBits) {
uint16 result = 0;
uint16 bitToInsert = 1;
while (numBits > 0) {
if (readBit())
result |= bitToInsert;
bitToInsert <<= 1;
numBits--;
}
return result;
}
void DecompressorState::emitByte(void *dest, byte b) {
*static_cast<byte *>(dest) = b;
_window[_windowOffset] = b;
_windowOffset++;
if (_windowOffset == kWindowSize)
_windowOffset = 0;
}
void DecompressorState::recordMatchOffset(uint matchOffset) {
uint expungeIndex = kMatchHistorySize - 1;
for (uint i = 0; i < kMatchHistorySize - 1u; i++) {
if (_matchOffsetHistory[i] == matchOffset) {
expungeIndex = i;
break;
}
}
if (expungeIndex == 0)
return;
for (uint i = 0; i < expungeIndex; i++)
_matchOffsetHistory[expungeIndex - i] = _matchOffsetHistory[expungeIndex - i - 1];
_matchOffsetHistory[0] = matchOffset;
}
const byte DecompressorState::g_matchVLCLengths[DecompressorState::kNumMatchVLCs] = {
0, 1, 1, 2, 2,
2, 3, 3, 3, 3,
4, 4, 5, 5, 6,
6, 7, 7, 8, 8,
9, 9, 10, 10, 11,
11, 12, 12, 13, 13
};
class DecompressingStream : public Common::SeekableReadStream {
public:
DecompressingStream(Common::SeekableReadStream *baseStream, uint32 decompressedSize);
~DecompressingStream();
int64 pos() const override;
int64 size() const override;
bool seek(int64 offset, int whence) override;
bool skip(uint32 offset) override;
bool eos() const override;
uint32 read(void *dataPtr, uint32 dataSize) override;
bool err() const override;
void clearErr() override;
private:
bool rewind();
DecompressorState _decomp;
Common::SeekableReadStream *_baseStream;
uint32 _pos;
uint32 _decompressedSize;
bool _eosFlag;
bool _errFlag;
};
DecompressingStream::DecompressingStream(Common::SeekableReadStream *baseStream, uint32 decompressedSize)
: _baseStream(baseStream), _decompressedSize(decompressedSize), _pos(0), _eosFlag(false), _errFlag(false), _decomp(baseStream) {
}
DecompressingStream::~DecompressingStream() {
delete _baseStream;
}
int64 DecompressingStream::pos() const {
return _pos;
}
int64 DecompressingStream::size() const {
return _decompressedSize;
}
bool DecompressingStream::seek(int64 offset, int whence) {
switch (whence) {
case SEEK_SET:
if (offset == _pos)
return true;
if (offset < 0)
return false;
if (offset == 0)
return rewind();
if (offset > static_cast<int64>(_decompressedSize))
return false;
if (offset == static_cast<int64>(_decompressedSize)) {
// Just set position to EOF
_pos = _decompressedSize;
return true;
}
if (offset < static_cast<int64>(_pos)) {
if (!rewind())
return false;
}
return skip(static_cast<uint32>(offset) - _pos);
case SEEK_END:
return seek(static_cast<int64>(_decompressedSize) + offset, SEEK_SET);
case SEEK_CUR:
return seek(static_cast<int64>(_pos) + offset, SEEK_SET);
default:
return false;
}
}
bool DecompressingStream::skip(uint32 offset) {
const uint kSkipBufSize = 1024;
byte skipBuf[kSkipBufSize];
while (offset > 0) {
uint32 skipAmount = kSkipBufSize;
if (skipAmount > offset)
skipAmount = offset;
uint32 amountRead = read(skipBuf, skipAmount);
if (amountRead != skipAmount)
return false;
offset -= amountRead;
}
return true;
}
bool DecompressingStream::eos() const {
return _eosFlag;
}
uint32 DecompressingStream::read(void *dataPtr, uint32 dataSize) {
if (_errFlag)
return 0;
uint32 bytesAvailable = _decompressedSize - _pos;
if (dataSize > bytesAvailable) {
_eosFlag = true;
dataSize = bytesAvailable;
}
if (dataSize == 0)
return 0;
uint32 numBytesDecompressed = _decomp.decompressBytes(dataPtr, dataSize);
if (numBytesDecompressed < dataSize)
_errFlag = true;
_pos += numBytesDecompressed;
return numBytesDecompressed;
}
bool DecompressingStream::err() const {
return _errFlag;
}
void DecompressingStream::clearErr() {
_errFlag = false;
_eosFlag = false;
_baseStream->clearErr();
}
bool DecompressingStream::rewind() {
if (!_baseStream->seek(0)) {
_errFlag = true;
return false;
}
_decomp.resetEverything();
_pos = 0;
return true;
}
class ArchiveItem : public Common::ArchiveMember {
public:
ArchiveItem(Common::SeekableReadStream *stream, Common::Mutex *guardMutex, const Common::Path &path, int64 filePos, uint compressedSize, uint decompressedSize, bool isCompressed);
Common::SeekableReadStream *createReadStream() const override;
Common::SeekableReadStream *createReadStreamForAltStream(Common::AltStreamType altStreamType) const override;
Common::String getName() const override { return getFileName(); }
Common::Path getPathInArchive() const override { return _path; }
Common::String getFileName() const override { return _path.getLastComponent().toString(); }
private:
Common::SeekableReadStream *_stream;
Common::Mutex *_guardMutex;
Common::Path _path;
int64 _filePos;
uint _compressedSize;
uint _decompressedSize;
bool _isCompressed;
};
ArchiveItem::ArchiveItem(Common::SeekableReadStream *stream, Common::Mutex *guardMutex, const Common::Path &path, int64 filePos, uint compressedSize, uint decompressedSize, bool isCompressed)
: _stream(stream), _guardMutex(guardMutex), _path(path), _filePos(filePos), _compressedSize(compressedSize), _decompressedSize(decompressedSize), _isCompressed(isCompressed) {
}
Common::SeekableReadStream *ArchiveItem::createReadStream() const {
Common::SeekableReadStream *sliceSubstream = nullptr;
if (_guardMutex)
sliceSubstream = new Common::SafeMutexedSeekableSubReadStream(_stream, static_cast<uint32>(_filePos), static_cast<uint32>(_filePos) + _compressedSize, DisposeAfterUse::NO, *_guardMutex);
else
sliceSubstream = new Common::SeekableSubReadStream(_stream, static_cast<uint32>(_filePos), static_cast<uint32>(_filePos) + _compressedSize, DisposeAfterUse::NO);
// Add buffering since seekable substreams can be extremely slow!
sliceSubstream = Common::wrapBufferedSeekableReadStream(sliceSubstream, 4096, DisposeAfterUse::YES);
if (_isCompressed)
return new DecompressingStream(sliceSubstream, _decompressedSize);
else
return sliceSubstream;
}
Common::SeekableReadStream *ArchiveItem::createReadStreamForAltStream(Common::AltStreamType altStreamType) const {
return nullptr;
}
class PackageArchive : public Common::Archive {
public:
explicit PackageArchive(Common::SeekableReadStream *stream);
~PackageArchive();
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;
int listMatchingMembers(Common::ArchiveMemberList &list, const Common::Path &pattern, bool matchPathComponents) const override;
const Common::ArchiveMemberPtr getMember(const Common::Path &path) const override;
Common::SeekableReadStream *createReadStreamForMember(const Common::Path &path) const override;
protected:
virtual Common::Mutex *getGuardMutex();
private:
bool decodeDataChunk(DecompressorState *decompState, Common::Array<byte> &data);
static Common::String normalizePath(const Common::Path &path);
Common::Array<Common::SharedPtr<ArchiveItem> > _items;
Common::HashMap<Common::Path, uint, Common::Path::IgnoreCase_Hash, Common::Path::IgnoreCase_EqualTo> _pathToItemIndex;
Common::SeekableReadStream *_stream;
};
PackageArchive::PackageArchive(Common::SeekableReadStream *stream) : _stream(stream) {
}
PackageArchive::~PackageArchive() {
delete _stream;
}
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<int64>(payloadLocation) + static_cast<int64>(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::loadPAK: Couldn't read pak file size declaration");
return false;
}
uint32 pakFileEOF = READ_LE_UINT32(pakFileEOFBytes);
if (static_cast<int64>(pakFileEOF) > _stream->size()) {
warning("GenteeInstaller::PackageArchive::loadPAK: Pak file size was larger than would be possible");
return false;
}
if (static_cast<int64>(pakFileEOF) < pakFileStartPos) {
warning("GenteeInstaller::PackageArchive::loadPAK: Pak file EOF preceded the start position");
return false;
}
uint32 pakFileSize = static_cast<uint32>(static_cast<int64>(pakFileEOF) - pakFileStartPos);
if (pakFileStartPos != 0 || maxPakFileSize != pakFileSize) {
_stream = new Common::SeekableSubReadStream(_stream, pakFileStartPos, static_cast<uint32>(pakFileStartPos) + pakFileSize, DisposeAfterUse::YES);
if (!_stream->seek(4)) {
warning("GenteeInstaller::PackageArchive::loadPAK: Couldn't reset pak position");
return false;
}
}
byte pakFileHeader[16];
if (_stream->read(pakFileHeader, 16) != 16) {
warning("GenteeInstaller::PackageArchive::loadPAK: Couldn't load pak header");
return false;
}
Common::ScopedPtr<DecompressorState> cmdContextPtr(new DecompressorState(_stream));
DecompressorState *cmdContext = cmdContextPtr.get();
Common::Array<byte> firstChunk;
if (!decodeDataChunk(cmdContext, firstChunk))
return false;
if (firstChunk.size() < 3) {
warning("GenteeInstaller::PackageArchive::loadPAK: First chunk is malformed");
return false;
}
bool allFilesAreStored = (firstChunk[0] != 0);
// The second chunk appears to not be a commandlet either, should figure out what it is
while (_stream->pos() < _stream->size()) {
Common::Array<byte> commandletChunk;
if (!decodeDataChunk(cmdContext, commandletChunk))
return false;
if (commandletChunk.size() < 3) {
warning("GenteeInstaller::PackageArchive::loadPAK: Commandlet was malformed");
return false;
}
uint16 commandletCode = READ_LE_UINT16(&commandletChunk[0]);
if (commandletCode == 0x87f4) {
// Unpack file commandlet
if (commandletChunk.size() < 36 || commandletChunk.back() != 0) {
warning("GenteeInstaller::PackageArchive::loadPAK: File commandlet was malformed");
return false;
}
Common::String fileName = Common::String(reinterpret_cast<const char *>(&commandletChunk[34]));
bool isCompressed = (!allFilesAreStored) && (commandletChunk[28] != 0);
int64 dataStart = _stream->pos();
int64 dataEnd = dataStart;
uint32 decompressedSize = 0;
if (isCompressed) {
// Extremely annoying: The compressed data size isn't stored, so we must decompress the entire file
// to find the next commandlet
Common::ScopedPtr<DecompressorState> fileCtx(new DecompressorState(_stream));
byte decompressedSizeBytes[4];
if (_stream->read(decompressedSizeBytes, 4) != 4) {
warning("GenteeInstaller::PackageArchive::loadPAK: Decompressed file size was malformed");
return false;
}
decompressedSize = READ_LE_UINT32(decompressedSizeBytes);
dataStart += 4;
const uint kSkipBufSize = 1024;
byte skipBuf[1024];
uint32 skipRemaining = decompressedSize;
while (skipRemaining > 0) {
uint32 amountToSkip = skipRemaining;
if (amountToSkip > kSkipBufSize)
amountToSkip = kSkipBufSize;
if (fileCtx->decompressBytes(skipBuf, amountToSkip) != amountToSkip) {
warning("GenteeInstaller::PackageArchive::loadPAK: Couldn't decompress file data to skip it");
return false;
}
skipRemaining -= amountToSkip;
}
dataEnd = _stream->pos();
} else {
decompressedSize = READ_LE_UINT32(&commandletChunk[7]);
if (!_stream->skip(decompressedSize)) {
warning("GenteeInstaller::PackageArchive::loadPAK: Failed to skip uncompressed file data");
return false;
}
dataEnd = _stream->pos();
}
debug(3, "GenteeInstaller: Detected %s item '%s' size %u at pos %u .. %u", (isCompressed ? "compressed" : "stored"), fileName.c_str(), static_cast<uint>(decompressedSize), static_cast<uint>(dataStart), static_cast<uint>(dataEnd));
fileName.replace('\\', '/');
if (fileName.hasPrefix(prefix)) {
fileName = fileName.substr(strlen(prefix));
Common::Path path(fileName, '/');
Common::SharedPtr<ArchiveItem> item(new ArchiveItem(_stream, getGuardMutex(), path, dataStart, static_cast<uint>(dataEnd - dataStart), decompressedSize, isCompressed));
_pathToItemIndex[path] = _items.size();
_items.push_back(item);
}
}
}
return true;
}
bool PackageArchive::decodeDataChunk(DecompressorState *decompState, Common::Array<byte> &commandletData) {
byte sizeBytes[4];
if (_stream->read(sizeBytes, 4) != 4) {
warning("GenteeInstaller::PackageArchive::load: Couldn't read commandlet size");
return false;
}
uint32 size = READ_LE_UINT32(sizeBytes);
if (size > 4 * 1024 * 1024) {
warning("GenteeInstaller::PackageArchive::load: Commandlet was abnormally large, possibly corrupt data or a decompression bug");
return false;
}
commandletData.resize(size);
if (size > 0) {
decompState->resetBitstream();
if (decompState->decompressBytes(&commandletData[0], size) != size) {
warning("GenteeInstaller::PackageArchive::load: Commandlet packet decompression failed");
return false;
}
}
return true;
}
Common::String PackageArchive::normalizePath(const Common::Path &path) {
Common::String normalizedPath = path.toString();
normalizedPath.toLowercase();
return normalizedPath;
}
bool PackageArchive::hasFile(const Common::Path &path) const {
return _pathToItemIndex.find(path) != _pathToItemIndex.end();
}
int PackageArchive::listMembers(Common::ArchiveMemberList &list) const {
for (const Common::SharedPtr<ArchiveItem> &item : _items)
list.push_back(item.staticCast<Common::ArchiveMember>());
return _items.size();
}
int PackageArchive::listMatchingMembers(Common::ArchiveMemberList &list, const Common::Path &pattern, bool matchPathComponents) const {
int matches = 0;
const char *wildcardExclusions = matchPathComponents ? NULL : "/";
for (const Common::SharedPtr<ArchiveItem> &item : _items) {
if (normalizePath(item->getPathInArchive()).matchString(normalizePath(pattern), false, wildcardExclusions)) {
list.push_back(item.staticCast<Common::ArchiveMember>());
matches++;
}
}
return matches;
}
const Common::ArchiveMemberPtr PackageArchive::getMember(const Common::Path &path) const {
Common::HashMap<Common::Path, uint, Common::Path::IgnoreCase_Hash, Common::Path::IgnoreCase_EqualTo>::const_iterator it = _pathToItemIndex.find(path);
if (it == _pathToItemIndex.end())
return nullptr;
return _items[it->_value].staticCast<Common::ArchiveMember>();
}
Common::SeekableReadStream *PackageArchive::createReadStreamForMember(const Common::Path &path) const {
const Common::ArchiveMemberPtr member = getMember(path);
if (!member)
return nullptr;
return member->createReadStream();
}
Common::Mutex *PackageArchive::getGuardMutex() {
return nullptr;
}
class ThreadSafePackageArchive : public PackageArchive {
public:
explicit ThreadSafePackageArchive(Common::SeekableReadStream *stream);
protected:
Common::Mutex *getGuardMutex() override;
private:
Common::Mutex _guardMutex;
};
ThreadSafePackageArchive::ThreadSafePackageArchive(Common::SeekableReadStream *stream) : PackageArchive(stream) {
}
Common::Mutex *ThreadSafePackageArchive::getGuardMutex() {
return &_guardMutex;
}
} // End of namespace GenteeInstaller
Common::Archive *createGenteeInstallerArchive(Common::SeekableReadStream *stream, const char *prefixToRemove, bool embedded, bool threadSafe) {
if (!prefixToRemove)
prefixToRemove = "";
GenteeInstaller::PackageArchive *archive = nullptr;
if (threadSafe)
archive = new GenteeInstaller::ThreadSafePackageArchive(stream);
else
archive = new GenteeInstaller::PackageArchive(stream);
bool loaded = embedded ? archive->loadFromEXE(prefixToRemove) : archive->loadPAK(prefixToRemove);
if (!loaded) {
delete archive;
return nullptr;
}
return archive;
}
} // End of namespace Common

View File

@@ -0,0 +1,68 @@
/* 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/>.
*
*/
#ifndef COMMON_GENTEE_INSTALLER_H
#define COMMON_GENTEE_INSTALLER_H
#include "common/archive.h"
/**
* @file
* Gentee Installer decompressor used in engines:
* - vcruise
*/
namespace Common {
/** NOTE for future implementation:
* A Gentee Installer setup executable can be deployed with a package file (disk1.pak, etc.) or the
* data can be embedded in the setup executable. If it's deployed with the executable, then you
* must read 12 bytes from offset 1008 in the executable file. Of that, the first 4 bytes is the
* position of the embedded ginstall.dll compressed data, the next 4 are the compressed size of
* ginstall.dll, and the next 4 are the decompressed size of ginstall.dll.
*
* From that, compute the end position of the DLL data from the DLL position + the compressed size.
* If that position is less than the end of the file, then the package is embedded in the setup
* executable starting at that location.
*/
/**
* Loads a Gentee Installer package.
*
* This product appears to have been renamed to CreateInstall in later versions, which also adopted
* 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.
* 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 embedded, bool threadSafe);
} // End of namespace Common
#endif

1434
common/compression/gzio.cpp Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,175 @@
/* 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/>.
*
*/
// Based on xoreos' Huffman code
#ifndef COMMON_HUFFMAN_H
#define COMMON_HUFFMAN_H
#include "common/array.h"
#include "common/list.h"
#include "common/types.h"
namespace Common {
/**
* @defgroup common_huffmann Huffman bit stream decoding
* @ingroup common
*
* @brief API for operations related to Huffman bit stream decoding.
*
* @details Used in engines:
* - SCUMM
*
* @{
*/
inline uint32 REVERSEBITS(uint32 x) {
x = (((x & ~0x55555555) >> 1) | ((x & 0x55555555) << 1));
x = (((x & ~0x33333333) >> 2) | ((x & 0x33333333) << 2));
x = (((x & ~0x0F0F0F0F) >> 4) | ((x & 0x0F0F0F0F) << 4));
x = (((x & ~0x00FF00FF) >> 8) | ((x & 0x00FF00FF) << 8));
return((x >> 16) | (x << 16));
}
/**
* Huffman bit stream decoding.
*
*/
template<class BITSTREAM>
class Huffman {
public:
/** Construct a Huffman decoder.
*
* @param maxLength Maximal code length. If 0, it is searched for.
* @param codeCount Number of codes.
* @param codes The actual codes.
* @param lengths Lengths of the individual codes.
* @param symbols The symbols. If 0, assume they are identical to the code indices.
*/
Huffman(uint8 maxLength, uint32 codeCount, const uint32 *codes, const uint8 *lengths, const uint32 *symbols = nullptr);
/** Return the next symbol in the bit stream. */
uint32 getSymbol(BITSTREAM &bits) const;
private:
struct Symbol {
uint32 code;
uint32 symbol;
Symbol(uint32 c, uint32 s) : code(c), symbol(s) {}
};
typedef List<Symbol> CodeList;
typedef Array<CodeList> CodeLists;
/** Lists of codes and their symbols, sorted by code length. */
CodeLists _codes;
/** Prefix lookup table used to speed up the decoding of short codes. */
struct PrefixEntry {
uint32 symbol;
uint8 length;
PrefixEntry() : length(0xFF) {}
};
static const uint8 _prefixTableBits = 8;
PrefixEntry _prefixTable[1 << _prefixTableBits];
};
template <class BITSTREAM>
Huffman<BITSTREAM>::Huffman(uint8 maxLength, uint32 codeCount, const uint32 *codes, const uint8 *lengths, const uint32 *symbols) {
assert(codeCount > 0);
assert(codes);
assert(lengths);
if (maxLength == 0)
for (uint32 i = 0; i < codeCount; i++)
maxLength = MAX(maxLength, lengths[i]);
assert(maxLength <= 32);
// Codes that do not fit in the prefix table are stored in the _codes array.
_codes.resize(MAX(maxLength - _prefixTableBits, 0));
for (uint i = 0; i < codeCount; i++) {
uint8 length = lengths[i];
// The symbol. If none was specified, assume it is identical to the code index.
uint32 symbol = symbols ? symbols[i] : i;
if (length <= _prefixTableBits) {
// Short codes go in the prefix lookup table. Set all the entries in the table
// with an index starting with the code to the symbol value.
uint32 startIndex;
if (BITSTREAM::isMSB2LSB()) {
startIndex = codes[i] << (_prefixTableBits - length);
} else {
startIndex = REVERSEBITS(codes[i]) >> (32 - _prefixTableBits);
}
uint32 endIndex = startIndex | ((1 << (_prefixTableBits - length)) - 1);
for (uint32 j = startIndex; j <= endIndex; j++) {
uint32 index = BITSTREAM::isMSB2LSB() ? j : REVERSEBITS(j) >> (32 - _prefixTableBits);
_prefixTable[index].symbol = symbol;
_prefixTable[index].length = length;
}
} else {
// Put the code and symbol into the correct list for the length.
_codes[lengths[i] - 1 - _prefixTableBits].push_back(Symbol(codes[i], symbol));
}
}
}
template <class BITSTREAM>
uint32 Huffman<BITSTREAM>::getSymbol(BITSTREAM &bits) const {
uint32 code = bits.peekBits(_prefixTableBits);
uint8 length = _prefixTable[code].length;
if (length != 0xFF) {
bits.skip(length);
return _prefixTable[code].symbol;
} else {
bits.skip(_prefixTableBits);
for (uint32 i = 0; i < _codes.size(); i++) {
bits.addBit(code, i + _prefixTableBits);
for (typename CodeList::const_iterator cCode = _codes[i].begin(); cCode != _codes[i].end(); ++cCode)
if (code == cCode->code)
return cCode->symbol;
}
}
error("Unknown Huffman code");
return 0;
}
/** @} */
} // End of namespace Common
#endif // COMMON_HUFFMAN_H

View File

@@ -0,0 +1,536 @@
/* 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/>.
*
*/
// The following code is based on unshield
// Original copyright:
// Copyright (c) 2003 David Eriksson <twogood@users.sourceforge.net>
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of
// this software and associated documentation files (the "Software"), to deal in
// the Software without restriction, including without limitation the rights to
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
// of the Software, and to permit persons to whom the Software is furnished to do
// so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
#include "common/archive.h"
#include "common/debug.h"
#include "common/hash-str.h"
#include "common/compression/installshield_cab.h"
#include "common/memstream.h"
#include "common/substream.h"
#include "common/ptr.h"
#include "common/compression/deflate.h"
#include "common/file.h"
namespace Common {
namespace {
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.
uint32 bytesRead = 0, bytesProcessed = 0;
while (dstLen > 0 && bytesRead < srcLen) {
uint16 chunkSize = READ_LE_UINT16(src + bytesRead);
bytesRead += 2;
uint zlibLen = dstLen;
if (!inflateZlibHeaderless(dst + bytesProcessed, &zlibLen, src + bytesRead, chunkSize)) {
return false;
}
bytesProcessed += zlibLen;
dstLen -= zlibLen;
bytesRead += chunkSize;
}
return true;
}
class InstallShieldCabinet : public Archive {
public:
InstallShieldCabinet();
bool open(const Path *baseName, Common::Archive *archive, const FSNode *node);
void close();
// Archive API implementation
bool hasFile(const Path &path) const override;
int listMembers(ArchiveMemberList &list) const override;
const ArchiveMemberPtr getMember(const Path &path) const override;
SeekableReadStream *createReadStreamForMember(const Path &path) const override;
private:
enum Flags { kSplit = 1, kObfuscated = 2, kCompressed = 4, kInvalid = 8 };
struct FileEntry {
uint32 uncompressedSize;
uint32 compressedSize;
uint32 offset;
uint16 flags;
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<Path, FileEntry, Path::IgnoreCase_Hash, Path::IgnoreCase_EqualTo> FileMap;
FileMap _map;
Path _baseName;
Common::Array<VolumeHeader> _volumeHeaders;
Common::Archive *_archive;
static bool readVolumeHeader(SeekableReadStream *volumeStream, VolumeHeader &inVolumeHeader);
Path getHeaderName() const;
Path getVolumeName(uint volume) const;
};
InstallShieldCabinet::InstallShieldCabinet() : _version(0), _archive(nullptr) {
}
bool InstallShieldCabinet::open(const Path *baseName, Common::Archive *archive, const FSNode *node) {
// Store the base name so we can generate file names
if (baseName) {
_baseName = *baseName;
_archive = archive;
} else if (node) {
_baseName = node->getPath();
_archive = nullptr;
} else {
return false;
}
String strippedName = _baseName.baseName();
if (strippedName.hasSuffix(".cab") || strippedName.hasSuffix(".hdr")) {
strippedName.erase(strippedName.size() - 5, String::npos);
_baseName = _baseName.getParent().appendComponent(strippedName);
}
uint fileIndex = 0;
ScopedPtr<SeekableReadStream> file;
// First, open all the .cab files and read their headers
uint volume = 1;
for (;;) {
if (_archive) {
file.reset(_archive->createReadStreamForMember(getVolumeName(volume++)));
if (!file.get()) {
break;
}
} else {
file.reset(new Common::File());
if (!((Common::File *)file.get())->open(Common::FSNode(getVolumeName(volume++)))) {
break;
}
}
_volumeHeaders.push_back(VolumeHeader());
readVolumeHeader(file.get(), _volumeHeaders.back());
}
// Try to open a header (.hdr) file to get the file list
if (_archive) {
file.reset(_archive->createReadStreamForMember(getHeaderName()));
if (!file) {
// No header file is present, file list is in first .cab file
file.reset(_archive->createReadStreamForMember(getVolumeName(1)));
}
} else {
file.reset(new Common::File());
if (!((Common::File *)file.get())->open(Common::FSNode(getHeaderName()))) {
// No header file is present, file list is in first .cab file
if (!((Common::File *)file.get())->open(Common::FSNode(getVolumeName(1)))) {
file.reset(nullptr);
}
}
}
if (!file) {
close();
return false;
}
VolumeHeader headerHeader;
if (!readVolumeHeader(file.get(), headerHeader)) {
close();
return false;
}
_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 & kInvalid))
continue;
// Then let's get the string
file->seek(headerHeader.cabDescriptorOffset + fileTableOffset + nameOffset);
Path fileName(file->readString(), '\\');
// Entries can appear in multiple volumes (sometimes erroneously).
// We keep the one with the lowest volume ID
if (!_map.contains(fileName) || _map[fileName].volume > entry.volume)
_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;
// 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];
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 &&
headerHeader.lastFileSizeCompressed != 0) {
entry.flags |= kSplit;
}
break;
}
}
// Then let's get the string
file->seek(headerHeader.cabDescriptorOffset + fileTableOffset + nameOffset);
Path fileName(file->readString(), '\\');
if (entry.volume == 0) {
warning("Couldn't find the volume for file %s", fileName.toString('\\').c_str());
close();
return false;
}
++fileIndex;
// Entries can appear in multiple volumes (sometimes erroneously).
// We keep the one with the lowest volume ID
if (!_map.contains(fileName) || _map[fileName].volume > entry.volume)
_map[fileName] = entry;
}
}
return true;
}
void InstallShieldCabinet::close() {
_baseName.clear();
_map.clear();
_volumeHeaders.clear();
_version = 0;
}
bool InstallShieldCabinet::hasFile(const Path &path) const {
return _map.contains(path);
}
int InstallShieldCabinet::listMembers(ArchiveMemberList &list) const {
for (const auto &file : _map)
list.push_back(getMember(file._key));
return _map.size();
}
const ArchiveMemberPtr InstallShieldCabinet::getMember(const Path &path) const {
return ArchiveMemberPtr(new GenericArchiveMember(path, *this));
}
SeekableReadStream *InstallShieldCabinet::createReadStreamForMember(const Path &path) const {
if (!_map.contains(path))
return nullptr;
const FileEntry &entry = _map[path];
if (entry.flags & kObfuscated) {
warning("Cannot extract obfuscated file %s", path.toString().c_str());
return nullptr;
}
ScopedPtr<SeekableReadStream> stream;
if (_archive) {
stream.reset(_archive->createReadStreamForMember(getVolumeName((entry.volume))));
} else {
stream.reset(new Common::File());
if (!((Common::File *)stream.get())->open(Common::FSNode(getVolumeName((entry.volume))))) {
stream.reset(nullptr);
}
}
if (!stream) {
warning("Failed to open volume for file '%s'", path.toString().c_str());
return nullptr;
}
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) {
if (_archive) {
stream.reset(_archive->createReadStreamForMember(getVolumeName((++volume))));
} else {
if (!((Common::File *)stream.get())->open(Common::FSNode(getVolumeName((++volume))))) {
stream.reset(nullptr);
}
}
if (!stream.get()) {
warning("Failed to read split file %s", path.toString().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;
}
}
// 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);
}
}
byte *dst = (byte *)malloc(entry.uncompressedSize);
if (!src) {
src = (byte *)malloc(entry.compressedSize);
stream->seek(entry.offset);
stream->read(src, entry.compressedSize);
}
// Entries with size 0 are valid, and do not need to be inflated
if (entry.compressedSize != 0) {
if (!inflateZlibInstallShield(dst, entry.uncompressedSize, src, entry.compressedSize)) {
warning("failed to inflate CAB file '%s'", path.toString().c_str());
free(dst);
free(src);
return nullptr;
}
}
free(src);
return new MemoryReadStream(dst, entry.uncompressedSize, DisposeAfterUse::YES);
}
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();
volumeStream->skip(4);
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();
} 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;
}
Path InstallShieldCabinet::getHeaderName() const {
return _baseName.append("1.hdr");
}
Path InstallShieldCabinet::getVolumeName(uint volume) const {
return _baseName.append(String::format("%d.cab", volume));
}
} // End of anonymous namespace
Archive *makeInstallShieldArchive(const Path &baseName) {
return makeInstallShieldArchive(baseName, SearchMan);
}
Archive *makeInstallShieldArchive(const Common::Path &baseName, Common::Archive &archive) {
InstallShieldCabinet *cab = new InstallShieldCabinet();
if (!cab->open(&baseName, &archive, nullptr)) {
delete cab;
return nullptr;
}
return cab;
}
Archive *makeInstallShieldArchive(const FSNode &baseName) {
InstallShieldCabinet *cab = new InstallShieldCabinet();
if (!cab->open(nullptr, nullptr, &baseName)) {
delete cab;
return nullptr;
}
return cab;
}
} // End of namespace Common

View File

@@ -0,0 +1,79 @@
/* 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/>.
*
*/
#ifndef COMMON_INSTALLSHIELD_CAB_H
#define COMMON_INSTALLSHIELD_CAB_H
#include "common/types.h"
namespace Common {
class FSNode;
/**
* @defgroup common_installshield InstallShield
* @ingroup common
*
* @brief API for managing the InstallShield.
*
*
* @{
*/
class Archive;
class SeekableReadStream;
/**
* This factory method creates an Archive instance corresponding to the content
* of the single- or multi-file InstallShield cabinet with the given base name
*
* May return nullptr in case of a failure.
*
* @param baseName The base filename, e.g. the "data" in "data1.cab"
*/
Archive *makeInstallShieldArchive(const Common::Path &baseName);
/**
* This factory method creates an Archive instance corresponding to the content
* of the single- or multi-file InstallShield cabinet with the given base name
* in a specified archive.
*
* May return nullptr in case of a failure.
*
* @param baseName The base filename, e.g. the "data" in "data1.cab"
*/
Archive *makeInstallShieldArchive(const Common::Path &baseName, Common::Archive &archive);
/**
* This factory method creates an Archive instance corresponding to the content
* of the single- or multi-file InstallShield cabinet with the given base name
*
* May return nullptr in case of a failure.
*
* @param baseName The base filename, e.g. the "data" in "data1.cab"
*/
Archive *makeInstallShieldArchive(const Common::FSNode &baseName);
/** @} */
} // End of namespace Common
#endif

View File

@@ -0,0 +1,183 @@
/* 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/compression/installshieldv3_archive.h"
#include "common/compression/dcl.h"
#include "common/debug.h"
namespace Common {
InstallShieldV3::InstallShieldV3() : Common::Archive() {
_stream = nullptr;
}
InstallShieldV3::~InstallShieldV3() {
close();
}
bool InstallShieldV3::open(const Common::Path &filename) {
close();
_stream = SearchMan.createReadStreamForMember(filename);
if (!_stream)
return false;
return read();
}
bool InstallShieldV3::open(const Common::FSNode &node) {
close();
_stream = node.createReadStream();
if (!_stream)
return false;
return read();
}
bool InstallShieldV3::open(Common::SeekableReadStream *stream) {
close();
if (stream == nullptr)
return false;
_stream = stream;
return read();
}
void InstallShieldV3::close() {
delete _stream;
_stream = nullptr;
_map.clear();
}
bool InstallShieldV3::hasFile(const Common::Path &path) const {
return _map.contains(path);
}
int InstallShieldV3::listMembers(Common::ArchiveMemberList &list) const {
for (const auto &file : _map)
list.push_back(getMember(file._key));
return _map.size();
}
const Common::ArchiveMemberPtr InstallShieldV3::getMember(const Common::Path &path) const {
return Common::ArchiveMemberPtr(new Common::GenericArchiveMember(path, *this));
}
Common::SeekableReadStream *InstallShieldV3::createReadStreamForMember(const Common::Path &path) const {
if (!_stream || !_map.contains(path))
return nullptr;
const FileEntry &entry = _map[path];
// Seek to our offset and then send it off to the decompressor
_stream->seek(entry.offset);
return Common::decompressDCL(_stream, entry.compressedSize, entry.uncompressedSize);
}
char InstallShieldV3::getPathSeparator() const {
return '\\';
}
bool InstallShieldV3::read() {
assert(_stream);
// Check for the magic uint32
// No idea what it means, but it's how "file" recognizes them
if (_stream->readUint32BE() != 0x135D658C) {
close();
return false;
}
// Let's pull some relevant data from the header
_stream->seek(41);
uint32 directoryTableOffset = _stream->readUint32LE();
/*uint32 directoryTableSize =*/ _stream->readUint32LE();
uint16 directoryCount = _stream->readUint16LE();
/*uint32 fileTableOffset =*/ _stream->readUint32LE();
/*uint32 fileTableSize =*/ _stream->readUint32LE();
Common::Array<Common::String> dirNames;
Common::Array<int> dirSizes;
// We need to have at least one directory in order for the archive to be valid
if (directoryCount == 0) {
close();
return false;
}
// Get the number of files from every directory
_stream->seek(directoryTableOffset);
for (uint32 i = 0; i < directoryCount; i++) {
uint16 fileCount = _stream->readUint16LE();
uint16 chunkSize = _stream->readUint16LE();
byte nameLength = _stream->readUint16LE();
Common::String name;
while (nameLength--)
name += _stream->readByte();
dirNames.push_back(name);
dirSizes.push_back(fileCount);
debug(2, "Directory = %s, file count = %d", name.c_str(), fileCount);
_stream->skip(chunkSize - name.size() - 6);
}
// Following the directory table is the file table with files stored recursively
// by directory
for (int i = 0; i < directoryCount; i++) {
for (int j = 0; j < dirSizes[i]; j++) {
FileEntry entry;
_stream->skip(3); // Unknown
entry.uncompressedSize = _stream->readUint32LE();
entry.compressedSize = _stream->readUint32LE();
entry.offset = _stream->readUint32LE();
_stream->skip(14); // Unknown
byte nameLength = _stream->readByte();
Common::String name;
while (nameLength--)
name += _stream->readByte();
_stream->skip(13); // Unknown
if (!dirNames[i].empty())
name = dirNames[i] + "\\" + name;
_map[Path(name, '\\')] = entry;
debug(3, "Found file '%s' at 0x%08x (Comp: 0x%08x, Uncomp: 0x%08x)", name.c_str(),
entry.offset, entry.compressedSize, entry.uncompressedSize);
}
}
return true;
}
} // End of namespace Common

View File

@@ -0,0 +1,70 @@
/* 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/>.
*
*/
#ifndef COMMON_INSTALLSHIELDV3_ARCHIVE_H
#define COMMON_INSTALLSHIELDV3_ARCHIVE_H
#include "common/archive.h"
#include "common/scummsys.h"
#include "common/endian.h"
#include "common/file.h"
#include "common/hash-str.h"
#include "common/hashmap.h"
#include "common/str.h"
namespace Common {
class InstallShieldV3 : public Common::Archive {
public:
InstallShieldV3();
~InstallShieldV3() override;
bool open(const Common::Path &filename);
bool open(const Common::FSNode &node);
bool open(Common::SeekableReadStream *stream);
void close();
bool isOpen() const { return _stream != nullptr; }
// Common::Archive API implementation
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;
char getPathSeparator() const override;
private:
struct FileEntry {
uint32 uncompressedSize;
uint32 compressedSize;
uint32 offset;
};
bool read();
Common::SeekableReadStream *_stream;
typedef Common::HashMap<Path, FileEntry, Path::IgnoreCase_Hash, Path::IgnoreCase_EqualTo> FileMap;
FileMap _map;
};
} // End of namespace Common
#endif

View File

@@ -0,0 +1,24 @@
MODULE := common/compression
MODULE_OBJS := \
clickteam.o \
dcl.o \
gentee_installer.o \
gzio.o \
installshield_cab.o \
installshieldv3_archive.o \
powerpacker.o \
rnc_deco.o \
stuffit.o \
unarj.o \
unzip.o \
vise.o
ifdef USE_ZLIB
MODULE_OBJS += \
zlib.o
endif
# Include common rules
include $(srcdir)/rules.mk

View File

@@ -0,0 +1,178 @@
/* 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/compression/powerpacker.h"
#include "common/memstream.h"
#include "common/debug.h"
#include "common/textconsole.h"
namespace Common {
/* the decoder presented here is taken from pplib by Stuart Caie. The
* following statement comes from the original source.
*
* pplib 1.0: a simple PowerPacker decompression and decryption library
* placed in the Public Domain on 2003-09-18 by Stuart Caie.
*/
#define PP_READ_BITS(nbits, var) do { \
bit_cnt = (nbits); (var) = 0; \
while (bits_left < bit_cnt) { \
if (buf < src) return 0; \
bit_buffer |= *--buf << bits_left; \
bits_left += 8; \
} \
bits_left -= bit_cnt; \
while (bit_cnt--) { \
(var) = ((var) << 1) | (bit_buffer & 1); \
bit_buffer >>= 1; \
} \
} while (0)
#define PP_BYTE_OUT(byte) do { \
if (out <= dest) return 0; \
*--out = (byte); written++; \
} while (0)
int PowerPackerStream::ppDecrunchBuffer(const byte *src, byte *dest, uint32 src_len, uint32 dest_len) {
const byte *buf, *off_lens;
byte *out, *dest_end, bits_left = 0, bit_cnt;
uint32 bit_buffer = 0, x, todo, offbits, offset, written = 0;
if (!src || !dest) return 0;
/* set up input and output pointers */
off_lens = src; src = &src[4];
buf = &src[src_len];
out = dest_end = &dest[dest_len];
/* skip the first few bits */
PP_READ_BITS(src[src_len + 3], x);
/* while there are input bits left */
while (written < dest_len) {
PP_READ_BITS(1, x);
if (x == 0) {
/* bit==0: literal, then match. bit==1: just match */
todo = 1; do { PP_READ_BITS(2, x); todo += x; } while (x == 3);
while (todo--) { PP_READ_BITS(8, x); PP_BYTE_OUT(x); }
/* should we end decoding on a literal, break out of the main loop */
if (written == dest_len) break;
}
/* match: read 2 bits for initial offset bitlength / match length */
PP_READ_BITS(2, x);
offbits = off_lens[x];
todo = x+2;
if (x == 3) {
PP_READ_BITS(1, x);
if (x == 0) offbits = 7;
PP_READ_BITS(offbits, offset);
do { PP_READ_BITS(3, x); todo += x; } while (x == 7);
}
else {
PP_READ_BITS(offbits, offset);
}
if (&out[offset] > dest_end) return 0; /* match_overflow */
while (todo--) { x = out[offset]; PP_BYTE_OUT(x); }
}
/* all output bytes written without error */
return 1;
}
uint16 PowerPackerStream::getCrunchType(uint32 signature) {
byte eff = 0;
switch (signature) {
case 0x50503230: /* PP20 */
case 0x5041434b: /* PACK, non-standard header used in amiga floppy ITE. */
eff = 4;
break;
case 0x50504C53: /* PPLS */
error("PPLS crunched files are not supported");
#if 0
eff = 8;
break;
#endif
case 0x50583230: /* PX20 */
error("PX20 crunched files are not supported");
#if 0
eff = 6;
break;
#endif
default:
eff = 0;
}
return eff;
}
PowerPackerStream::PowerPackerStream(Common::SeekableReadStream &stream) {
_dispose = false;
uint32 signature = stream.readUint32BE();
if (getCrunchType(signature) == 0) {
stream.seek(0, SEEK_SET);
_stream = &stream;
return;
}
stream.seek(-4, SEEK_END);
uint32 decrlen = stream.readUint32BE() >> 8;
byte *dest = (byte *)malloc(decrlen);
uint32 crlen = stream.size() - 4;
byte *src = (byte *)malloc(crlen);
stream.seek(4, SEEK_SET);
stream.read(src, crlen);
ppDecrunchBuffer(src, dest, crlen-8, decrlen);
free(src);
_stream = new Common::MemoryReadStream(dest, decrlen, DisposeAfterUse::YES);
_dispose = true;
}
byte *PowerPackerStream::unpackBuffer(const byte *input, uint32 input_len, uint32 &output_len) {
if (input_len < 8) {
output_len = 0;
return nullptr;
}
uint32 signature = READ_BE_UINT32(input);
if (getCrunchType(signature) == 0) {
output_len = 0;
return nullptr;
}
output_len = READ_BE_UINT32(input + input_len - 4) >> 8;
byte *dest = new byte[output_len];
ppDecrunchBuffer(input + 4, dest, input_len - 12, output_len);
return dest;
}
}

View File

@@ -0,0 +1,69 @@
/* 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/>.
*
*/
#ifndef COMMON_POWERPACKER_H
#define COMMON_POWERPACKER_H
#include "common/stream.h"
namespace Common {
class PowerPackerStream : public Common::SeekableReadStream {
SeekableReadStream *_stream;
bool _dispose;
private:
static int ppDecrunchBuffer(const byte *src, byte *dest, uint32 src_len, uint32 dest_len);
static uint16 getCrunchType(uint32 signature);
public:
PowerPackerStream(Common::SeekableReadStream &stream);
static byte *unpackBuffer(const byte *input, uint32 input_len, uint32 &output_len);
~PowerPackerStream() override {
if (_dispose) delete _stream;
}
int64 size() const override {
return _stream->size();
}
int64 pos() const override {
return _stream->pos();
}
bool eos() const override {
return _stream->eos();
}
bool seek(int64 offs, int whence = SEEK_SET) override {
return _stream->seek(offs, whence);
}
uint32 read(void *dataPtr, uint32 dataSize) override {
return _stream->read(dataPtr, dataSize);
}
};
} // End of namespace Common
#endif

View File

@@ -0,0 +1,431 @@
/* 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/endian.h"
#include "common/compression/rnc_deco.h"
namespace Common {
//return codes
#define NOT_PACKED 0
#define PACKED_CRC -1
#define UNPACKED_CRC -2
//other defines
#define TABLE_SIZE (16 * 8)
#define MIN_LENGTH 2
#define HEADER_LEN 18
RncDecoder::RncDecoder() {
initCrc();
_bitBuffl = 0;
_bitBuffh = 0;
_bitCount = 0;
_srcPtr = nullptr;
_dstPtr = nullptr;
_inputByteLeft = 0;
}
RncDecoder::~RncDecoder() { }
void RncDecoder::initCrc() {
uint16 cnt = 0;
uint16 tmp1 = 0;
uint16 tmp2 = 0;
for (tmp2 = 0; tmp2 < 0x100; tmp2++) {
tmp1 = tmp2;
for (cnt = 8; cnt > 0; cnt--) {
if (tmp1 % 2) {
tmp1 >>= 1;
tmp1 ^= 0x0a001;
} else
tmp1 >>= 1;
}
_crcTable[tmp2] = tmp1;
}
}
//calculate 16 bit crc of a block of memory
uint16 RncDecoder::crcBlock(const uint8 *block, uint32 size) {
uint16 crc = 0;
uint8 *crcTable8 = (uint8 *)_crcTable; //make a uint8* to crc_table
uint8 tmp;
uint32 i;
for (i = 0; i < size; i++) {
tmp = *block++;
crc ^= tmp;
tmp = (uint8)((crc >> 8) & 0x00FF);
crc &= 0x00FF;
crc = *(uint16 *)&crcTable8[crc << 1];
crc ^= tmp;
}
return crc;
}
uint16 RncDecoder::inputBits(uint8 amount) {
// Although we are manipulating 16 bits values, use 32 bits variables
// This avoids triggering UB when shifting an amount of 0 or 16.
uint32 newBitBuffh = _bitBuffh;
uint32 newBitBuffl = _bitBuffl;
int16 newBitCount = _bitCount;
uint32 remBits;
uint16 returnVal;
returnVal = ((1 << amount) - 1) & newBitBuffl;
newBitCount -= amount;
if (newBitCount < 0) {
newBitCount += amount;
remBits = (newBitBuffh << (16 - newBitCount)) & 0xffff;
newBitBuffh >>= newBitCount;
newBitBuffl >>= newBitCount;
newBitBuffl |= remBits;
_srcPtr += 2;
// added some more check here to prevent reading in the buffer
// if there are no bytes anymore.
_inputByteLeft -= 2;
if (_inputByteLeft <= 0)
newBitBuffh = 0;
else if (_inputByteLeft == 1)
newBitBuffh = *_srcPtr;
else
newBitBuffh = READ_LE_UINT16(_srcPtr);
amount -= newBitCount;
newBitCount = 16 - amount;
}
remBits = (newBitBuffh << (16 - amount)) & 0xffff;
_bitBuffh = newBitBuffh >> amount;
_bitBuffl = (newBitBuffl >> amount) | remBits;
_bitCount = (uint8)newBitCount;
return returnVal;
}
void RncDecoder::makeHufftable(uint16 *table) {
uint16 bitLength, i, j;
uint16 numCodes = inputBits(5);
if (!numCodes)
return;
uint8 huffLength[16];
for (i = 0; i < numCodes; i++)
huffLength[i] = (uint8)(inputBits(4) & 0x00FF);
uint16 huffCode = 0;
for (bitLength = 1; bitLength < 17; bitLength++) {
for (i = 0; i < numCodes; i++) {
if (huffLength[i] == bitLength) {
*table++ = (1 << bitLength) - 1;
uint16 b = huffCode >> (16 - bitLength);
uint16 a = 0;
for (j = 0; j < bitLength; j++)
a |= ((b >> j) & 1) << (bitLength - j - 1);
*table++ = a;
*(table + 0x1e) = (huffLength[i] << 8) | (i & 0x00FF);
huffCode += 1 << (16 - bitLength);
}
}
}
}
uint16 RncDecoder::inputValue(uint16 *table) {
uint16 valOne, valTwo, value = _bitBuffl;
do {
valTwo = (*table++) & value;
valOne = *table++;
} while (valOne != valTwo);
value = *(table + 0x1e);
inputBits((uint8)((value>>8) & 0x00FF));
value &= 0x00FF;
if (value >= 2) {
value--;
valOne = inputBits((uint8)value & 0x00FF);
valOne |= (1 << value);
value = valOne;
}
return value;
}
int RncDecoder::getbit() {
if (_bitCount == 0) {
_bitBuffl = *_srcPtr++;
_bitCount = 8;
}
byte temp = (_bitBuffl & 0x80) >> 7;
_bitBuffl <<= 1;
_bitCount--;
return temp;
}
int32 RncDecoder::unpackM1(const void *input, uint inputSize, void *output) {
uint8 *outputLow, *outputHigh;
const uint8 *inputHigh, *inputptr = (const uint8 *)input;
uint32 unpackLen = 0;
uint32 packLen = 0;
uint16 counts = 0;
uint16 crcUnpacked = 0;
uint16 crcPacked = 0;
_inputByteLeft = inputSize;
_bitBuffl = 0;
_bitBuffh = 0;
_bitCount = 0;
//Check for "RNC "
if (READ_BE_UINT32(inputptr) != kRnc1Signature)
return NOT_PACKED;
inputptr += 4;
// read unpacked/packed file length
unpackLen = READ_BE_UINT32(inputptr); inputptr += 4;
packLen = READ_BE_UINT32(inputptr); inputptr += 4;
uint8 blocks = *(inputptr + 5);
//read CRC's
crcUnpacked = READ_BE_UINT16(inputptr); inputptr += 2;
crcPacked = READ_BE_UINT16(inputptr); inputptr += 2;
inputptr = (inputptr + HEADER_LEN - 16);
if (crcBlock(inputptr, packLen) != crcPacked)
return PACKED_CRC;
inputptr = (((const uint8 *)input) + HEADER_LEN);
_srcPtr = inputptr;
inputHigh = ((const uint8 *)input) + packLen + HEADER_LEN;
outputLow = (uint8 *)output;
outputHigh = *(((const uint8 *)input) + 16) + unpackLen + outputLow;
if (! ((inputHigh <= outputLow) || (outputHigh <= inputHigh)) ) {
_srcPtr = inputHigh;
_dstPtr = outputHigh;
memcpy((_dstPtr-packLen), (_srcPtr-packLen), packLen);
_srcPtr = (_dstPtr-packLen);
}
_inputByteLeft -= HEADER_LEN;
_dstPtr = (uint8 *)output;
_bitCount = 0;
_bitBuffl = READ_LE_UINT16(_srcPtr);
inputBits(2);
do {
makeHufftable(_rawTable);
makeHufftable(_posTable);
makeHufftable(_lenTable);
counts = inputBits(16);
do {
uint32 inputLength = inputValue(_rawTable);
uint32 inputOffset;
if (inputLength) {
if (_inputByteLeft < (int32) inputLength || inputLength > 0xff000000) {
return NOT_PACKED;
}
memcpy(_dstPtr, _srcPtr, inputLength); //memcpy is allowed here
_dstPtr += inputLength;
_srcPtr += inputLength;
_inputByteLeft -= inputLength;
uint16 a;
if (_inputByteLeft <= 0)
a = 0;
else if (_inputByteLeft == 1)
a = *_srcPtr;
else
a = READ_LE_UINT16(_srcPtr);
uint16 b;
if (_inputByteLeft <= 2)
b = 0;
else if (_inputByteLeft == 3)
b = *(_srcPtr + 2);
else
b = READ_LE_UINT16(_srcPtr + 2);
_bitBuffl &= ((1 << _bitCount) - 1);
_bitBuffl |= (a << _bitCount);
_bitBuffh = (a >> (16 - _bitCount)) | (b << _bitCount);
}
if (counts > 1) {
inputOffset = inputValue(_posTable) + 1;
inputLength = inputValue(_lenTable) + MIN_LENGTH;
// Don't use memcpy here! because input and output overlap.
uint8 *tmpPtr = (_dstPtr-inputOffset);
while (inputLength--)
*_dstPtr++ = *tmpPtr++;
}
} while (--counts);
} while (--blocks);
if (crcBlock((uint8 *)output, unpackLen) != crcUnpacked)
return UNPACKED_CRC;
// all is done..return the amount of unpacked bytes
return unpackLen;
}
int32 RncDecoder::unpackM2(const void *input, void *output) {
const uint8 *inputptr = (const uint8 *)input;
uint32 unpackLen = 0;
uint32 packLen = 0;
uint16 crcUnpacked = 0;
uint16 crcPacked = 0;
_bitBuffl = 0;
_bitCount = 0;
// Check for "RNC "
if (READ_BE_UINT32(inputptr) != kRnc2Signature)
return NOT_PACKED;
inputptr += 4;
// Read unpacked/packed file length
unpackLen = READ_BE_UINT32(inputptr);
inputptr += 4;
packLen = READ_BE_UINT32(inputptr);
inputptr += 4;
// Read CRCs
crcUnpacked = READ_BE_UINT16(inputptr);
inputptr += 2;
crcPacked = READ_BE_UINT16(inputptr);
inputptr += 2;
inputptr = (inputptr + HEADER_LEN - 16);
if (crcBlock(inputptr, packLen) != crcPacked)
return PACKED_CRC;
inputptr = (((const uint8 *)input) + HEADER_LEN);
_srcPtr = inputptr;
_dstPtr = (uint8 *)output;
uint16 ofs, len;
byte ofs_hi, ofs_lo;
len = 0;
ofs_hi = 0;
ofs_lo = 0;
getbit();
getbit();
while (1) {
bool loadVal = false;
while (getbit() == 0)
*_dstPtr++ = *_srcPtr++;
len = 2;
ofs_hi = 0;
if (getbit() == 0) {
len = (len << 1) | getbit();
if (getbit() == 1) {
len--;
len = (len << 1) | getbit();
if (len == 9) {
len = 4;
while (len--)
ofs_hi = (ofs_hi << 1) | getbit();
len = (ofs_hi + 3) * 4;
while (len--)
*_dstPtr++ = *_srcPtr++;
continue;
}
}
loadVal = true;
} else {
if (getbit() == 1) {
len++;
if (getbit() == 1) {
len = *_srcPtr++;
if (len == 0) {
if (getbit() == 1)
continue;
else
break;
}
len += 8;
}
loadVal = true;
} else {
loadVal = false;
}
}
if (loadVal) {
if (getbit() == 1) {
ofs_hi = (ofs_hi << 1) | getbit();
if (getbit() == 1) {
ofs_hi = ((ofs_hi << 1) | getbit()) | 4;
if (getbit() == 0)
ofs_hi = (ofs_hi << 1) | getbit();
} else if (ofs_hi == 0) {
ofs_hi = 2 | getbit();
}
}
}
ofs_lo = *_srcPtr++;
ofs = (ofs_hi << 8) | ofs_lo;
while (len--) {
*_dstPtr = *(byte *)(_dstPtr - ofs - 1);
_dstPtr++;
}
}
if (crcBlock((uint8 *)output, unpackLen) != crcUnpacked)
return UNPACKED_CRC;
// all is done..return the amount of unpacked bytes
return unpackLen;
}
} // End of namespace Common

View File

@@ -0,0 +1,66 @@
/* 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/>.
*
*/
#ifndef COMMON_RNC_DECO_H
#define COMMON_RNC_DECO_H
namespace Common {
// Decoder for RNC (Rob Norton Computing) ProPack compression.
class RncDecoder {
protected:
uint16 _rawTable[64];
uint16 _posTable[64];
uint16 _lenTable[64];
uint16 _crcTable[256];
uint16 _bitBuffl;
uint16 _bitBuffh;
uint8 _bitCount;
const uint8 *_srcPtr;
uint8 *_dstPtr;
int32 _inputByteLeft;
public:
RncDecoder();
~RncDecoder();
int32 unpackM1(const void *input, uint inputSize, void *output);
int32 unpackM2(const void *input, void *output);
static const uint32 kRnc1Signature = 0x524E4301; // "RNC\001"
static const uint32 kRnc2Signature = 0x524E4302; // "RNC\002"
protected:
void initCrc();
uint16 crcBlock(const uint8 *block, uint32 size);
uint16 inputBits(uint8 amount);
void makeHufftable(uint16 *table);
uint16 inputValue(uint16 *table);
int getbit();
};
} // End of namespace Common
#endif

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,65 @@
/* 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
* StuffIt decompressor used in engines:
* - grim
* - groovie
* - kyra
* - mtropolis
*/
#ifndef COMMON_STUFFIT_H
#define COMMON_STUFFIT_H
namespace Common {
/**
* @defgroup common_stuffit StuffIt decompressor
* @ingroup common
*
* @brief API related to StuffIt archive files.
*
* @{
*/
class Archive;
class Path;
class SeekableReadStream;
/**
* This factory method creates an Archive instance corresponding to the content
* of the StuffIt compressed file with the given name.
*
* @param fileName The file name to load
* @param flattenTree If true, removes the directory prefixes from all file paths
*
* @return The StuffIt archive
*/
Archive *createStuffItArchive(const Path &fileName, bool flattenTree = false);
Archive *createStuffItArchive(SeekableReadStream *stream, bool flattenTree = false);
/** @} */
} // End of namespace Common
#endif

View File

@@ -0,0 +1,869 @@
/* 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/>.
*
*/
//
// This file is heavily based on the arj code available under the GPL
// from <https://arj.sourceforge.net>, version 3.10.22.
#include "common/scummsys.h"
#include "common/archive.h"
#include "common/debug.h"
#include "common/compression/unarj.h"
#include "common/file.h"
#include "common/hash-str.h"
#include "common/memstream.h"
#include "common/bufferedstream.h"
#include "common/textconsole.h"
namespace Common {
#define ARJ_UCHAR_MAX 255
#define ARJ_CHAR_BIT 8
#define ARJ_COMMENT_MAX 2048
#define ARJ_FILENAME_MAX 512
#define ARJ_CODE_BIT 16
#define ARJ_THRESHOLD 3
#define ARJ_DICSIZ 26624
#define ARJ_FDICSIZ ARJ_DICSIZ
#define ARJ_MAXDICBIT 16
#define ARJ_MAXMATCH 256
#define ARJ_NC (ARJ_UCHAR_MAX + ARJ_MAXMATCH + 2 - ARJ_THRESHOLD)
#define ARJ_NP (ARJ_MAXDICBIT + 1)
#define ARJ_NT (ARJ_CODE_BIT + 3)
#if ARJ_NT > ARJ_NP
#define ARJ_NPT ARJ_NT
#else
#define ARJ_NPT ARJ_NP
#endif
#define ARJ_CTABLESIZE 4096
#define ARJ_PTABLESIZE 256
// these struct represents a file inside an Arj archive
struct ArjHeader {
int32 pos;
uint16 id;
uint16 headerSize;
//
byte firstHdrSize;
byte nbr;
byte xNbr;
byte hostOs;
byte flags;
byte method;
byte fileType;
byte pad;
uint32 timeStamp;
int32 compSize;
int32 origSize;
uint32 fileCRC;
uint16 entryPos;
uint16 fileMode;
uint16 hostData;
char filename[ARJ_FILENAME_MAX];
char comment[ARJ_COMMENT_MAX];
uint32 headerCrc;
};
static int32 findHeader(SeekableReadStream &stream);
static ArjHeader *readHeader(SeekableReadStream &stream);
class ArjDecoder {
public:
ArjDecoder(const ArjHeader *hdr) {
_compsize = hdr->compSize;
_compressed = nullptr;
_outstream = nullptr;
_bitbuf = 0;
_bytebuf = 0;
_bitcount = 0;
_blocksize = 0;
}
~ArjDecoder() {
delete _compressed;
delete _outstream;
}
void decode(int32 origsize);
void decode_f(int32 origsize);
ReadStream *_compressed;
MemoryWriteStream *_outstream;
//protected:
uint16 _bitbuf;
uint16 _bytebuf;
int32 _compsize;
int _bitcount;
void init_getbits();
void fillbuf(int n);
uint16 getbits(int n);
void make_table(int nchar, byte *bitlen, int tablebits, uint16 *table, int tablesize);
void read_pt_len(int nn, int nbit, int i_special);
void read_c_len(void);
uint16 decode_c(void);
uint16 decode_p(void);
void decode_start(void);
int16 decode_ptr(void);
int16 decode_len(void);
private:
byte _ntext[ARJ_FDICSIZ];
uint16 _left[2 * ARJ_NC - 1];
uint16 _right[2 * ARJ_NC - 1];
byte _c_len[ARJ_NC];
byte _pt_len[ARJ_NPT];
uint16 _c_table[ARJ_CTABLESIZE];
uint16 _pt_table[ARJ_PTABLESIZE];
uint16 _blocksize;
};
#define HEADER_ID 0xEA60
#define HEADER_ID_HI 0xEA
#define HEADER_ID_LO 0x60
#define FIRST_HDR_SIZE 30
#define HEADERSIZE_MAX (FIRST_HDR_SIZE + 10 + ARJ_FILENAME_MAX + ARJ_COMMENT_MAX)
#define CRC_MASK 0xFFFFFFFFL
#define HSLIMIT_ARJ 524288L
#define CBIT 9
#define PBIT 5
#define TBIT 5
// Source for CRC32::init, CRC32::checksum : crc32.c
class CRC32 {
static uint32 _table[256];
static bool _initialized;
private:
static void init() {
const uint32 poly = 0xEDB88320;
int i, j;
uint32 r;
for (i = 0; i < 256; i++) {
r = i;
for (j = 0; j < 8; j++)
if (r & 1)
r = (r >> 1) ^ poly;
else
r >>= 1;
_table[i] = r;
}
_initialized = true;
}
public:
static uint32 checksum(byte *data, int len) {
if (!_initialized) {
init();
}
uint32 CRC = 0xFFFFFFFF;
int i;
for (i = 0; i < len; i++)
CRC = (CRC >> 8) ^ _table[(CRC ^ data[i]) & 0xFF];
return CRC ^ 0xFFFFFFFF;
}
};
bool CRC32::_initialized = false;
uint32 CRC32::_table[256];
// Source for findHeader and readHeader: arj_arcv.c
int32 findHeader(SeekableReadStream &stream) {
long end_pos, tmp_pos;
int id;
byte header[HEADERSIZE_MAX];
uint32 crc;
uint16 basic_hdr_size;
tmp_pos = stream.pos();
stream.seek(0L, SEEK_END);
end_pos = stream.pos() - 2;
if (end_pos >= tmp_pos + HSLIMIT_ARJ)
end_pos = tmp_pos + HSLIMIT_ARJ;
while (tmp_pos < end_pos) {
stream.seek(tmp_pos, SEEK_SET);
id = stream.readByte();
while (tmp_pos < end_pos) {
if (id == HEADER_ID_LO) {
if ((id = stream.readByte()) == HEADER_ID_HI)
break;
} else
id = stream.readByte();
tmp_pos++;
}
if (tmp_pos >= end_pos)
return -1;
if ((basic_hdr_size = stream.readUint16LE()) <= HEADERSIZE_MAX) {
stream.read(header, basic_hdr_size);
crc = CRC32::checksum(header, basic_hdr_size);
if (crc == stream.readUint32LE()) {
stream.seek(tmp_pos, SEEK_SET);
return tmp_pos;
}
}
tmp_pos++;
}
return -1;
}
ArjHeader *readHeader(SeekableReadStream &stream) {
ArjHeader header;
ArjHeader *head;
byte headData[HEADERSIZE_MAX];
// Strictly check the header ID
header.id = stream.readUint16LE();
if (header.id != HEADER_ID) {
warning("ArjFile::readHeader(): Bad header ID (%x)", header.id);
return nullptr;
}
header.headerSize = stream.readUint16LE();
if (header.headerSize == 0)
return nullptr; // end of archive
if (header.headerSize > HEADERSIZE_MAX) {
warning("ArjFile::readHeader(): Bad header");
return nullptr;
}
int rSize = stream.read(headData, header.headerSize);
MemoryReadStream readS(headData, rSize);
header.headerCrc = stream.readUint32LE();
if (CRC32::checksum(headData, header.headerSize) != header.headerCrc) {
warning("ArjFile::readHeader(): Bad header CRC");
return nullptr;
}
header.firstHdrSize = readS.readByte();
header.nbr = readS.readByte();
header.xNbr = readS.readByte();
header.hostOs = readS.readByte();
header.flags = readS.readByte();
header.method = readS.readByte();
header.fileType = readS.readByte();
(void)readS.readByte(); // password_modifier
header.timeStamp = readS.readUint32LE();
header.compSize = readS.readSint32LE();
header.origSize = readS.readSint32LE();
header.fileCRC = readS.readUint32LE();
header.entryPos = readS.readUint16LE();
header.fileMode = readS.readUint16LE();
header.hostData = readS.readUint16LE();
// static int check_file_size()
if (header.origSize < 0 || header.compSize < 0) {
warning("ArjFile::readHeader(): Wrong file size");
return nullptr;
}
strlcpy(header.filename, (const char *)&headData[header.firstHdrSize], ARJ_FILENAME_MAX);
strlcpy(header.comment, (const char *)&headData[header.firstHdrSize + strlen(header.filename) + 1], ARJ_COMMENT_MAX);
// Process extended headers, if any
uint16 extHeaderSize;
while ((extHeaderSize = stream.readUint16LE()) != 0)
stream.seek((long)(extHeaderSize + 4), SEEK_CUR);
header.pos = stream.pos();
head = new ArjHeader(header);
return head;
}
// Source for init_getbits: arj_file.c (decode_start_stub)
void ArjDecoder::init_getbits() {
_bitbuf = 0;
_bytebuf = 0;
_bitcount = 0;
fillbuf(ARJ_CHAR_BIT * 2);
}
// Source for fillbuf, getbits: decode.c
void ArjDecoder::fillbuf(int n) {
while (_bitcount < n) {
_bitbuf = (_bitbuf << _bitcount) | (_bytebuf >> (8 - _bitcount));
n -= _bitcount;
if (_compsize > 0) {
_compsize--;
_bytebuf = _compressed->readByte();
} else {
_bytebuf = 0;
}
_bitcount = 8;
}
_bitcount -= n;
_bitbuf = ( _bitbuf << n) | (_bytebuf >> (8-n));
_bytebuf <<= n;
}
// Reads a series of bits into the input buffer */
uint16 ArjDecoder::getbits(int n) {
uint16 rc;
rc = _bitbuf >> (ARJ_CODE_BIT - n);
fillbuf(n);
return rc;
}
// Huffman decode routines
// Source: decode.c
// Creates a table for decoding
void ArjDecoder::make_table(int nchar, byte *bitlen, int tablebits, uint16 *table, int tablesize) {
uint16 count[17], weight[17], start[18];
uint16 *p;
uint i, k, len, ch, jutbits, avail, nextcode, mask;
for (i = 1; i <= 16; i++)
count[i] = 0;
for (i = 0; (int)i < nchar; i++)
count[bitlen[i]]++;
start[1] = 0;
for (i = 1; i <= 16; i++)
start[i + 1] = start[i] + (count[i] << (16 - i));
if (start[17] != (uint16) (1 << 16))
error("ArjDecoder::make_table(): bad file data");
jutbits = 16 - tablebits;
for (i = 1; (int)i <= tablebits; i++) {
start[i] >>= jutbits;
weight[i] = 1 << (tablebits - i);
}
while (i <= 16) {
weight[i] = 1 << (16 - i);
i++;
}
i = start[tablebits + 1] >> jutbits;
if (i != (uint16) (1 << 16)) {
k = 1 << tablebits;
while (i != k)
table[i++] = 0;
}
avail = nchar;
mask = 1 << (15 - tablebits);
for (ch = 0; (int)ch < nchar; ch++) {
if ((len = bitlen[ch]) == 0)
continue;
k = start[len];
nextcode = k + weight[len];
if ((int)len <= tablebits) {
if (nextcode > (uint)tablesize)
error("ArjDecoder::make_table(): bad file data");
for (i = start[len]; i < nextcode; i++)
table[i] = ch;
} else {
p = &table[k >> jutbits];
i = len - tablebits;
while (i != 0) {
if (*p == 0) {
_right[avail] = _left[avail] = 0;
*p = avail;
avail++;
}
if (k & mask)
p = &_right[*p];
else
p = &_left[*p];
k <<= 1;
i--;
}
*p = ch;
}
start[len] = nextcode;
}
}
// Reads length of data pending
void ArjDecoder::read_pt_len(int nn, int nbit, int i_special) {
int i, n;
int16 c;
uint16 mask;
n = getbits(nbit);
if (n == 0) {
c = getbits(nbit);
for (i = 0; i < nn; i++)
_pt_len[i] = 0;
for (i = 0; i < 256; i++)
_pt_table[i] = c;
} else {
i = 0;
while (i < n) {
c = _bitbuf >> 13;
if (c == 7) {
mask = 1 << 12;
while (mask & _bitbuf) {
mask >>= 1;
c++;
}
}
fillbuf((c < 7) ? 3 : (int)(c - 3));
_pt_len[i++] = (byte)c;
if (i == i_special) {
c = getbits(2);
while (--c >= 0)
_pt_len[i++] = 0;
}
}
while (i < nn)
_pt_len[i++] = 0;
make_table(nn, _pt_len, 8, _pt_table, ARJ_PTABLESIZE);
}
}
// Reads a character table
void ArjDecoder::read_c_len() {
int16 i, c, n;
uint16 mask;
n = getbits(CBIT);
if (n == 0) {
c = getbits(CBIT);
for (i = 0; i < ARJ_NC; i++)
_c_len[i] = 0;
for (i = 0; i < ARJ_CTABLESIZE; i++)
_c_table[i] = c;
} else {
i = 0;
while (i < n) {
c = _pt_table[_bitbuf >> (8)];
if (c >= ARJ_NT) {
mask = 1 << 7;
do {
if (_bitbuf & mask)
c = _right[c];
else
c = _left[c];
mask >>= 1;
} while (c >= ARJ_NT);
}
fillbuf((int)(_pt_len[c]));
if (c <= 2) {
if (c == 0)
c = 1;
else if (c == 1) {
c = getbits(4);
c += 3;
} else {
c = getbits(CBIT);
c += 20;
}
while (--c >= 0)
_c_len[i++] = 0;
}
else
_c_len[i++] = (byte)(c - 2);
}
while (i < ARJ_NC)
_c_len[i++] = 0;
make_table(ARJ_NC, _c_len, 12, _c_table, ARJ_CTABLESIZE);
}
}
// Decodes a single character
uint16 ArjDecoder::decode_c() {
uint16 j, mask;
if (_blocksize == 0) {
_blocksize = getbits(ARJ_CODE_BIT);
read_pt_len(ARJ_NT, TBIT, 3);
read_c_len();
read_pt_len(ARJ_NP, PBIT, -1);
}
_blocksize--;
j = _c_table[_bitbuf >> 4];
if (j >= ARJ_NC) {
mask = 1 << 3;
do {
if (_bitbuf & mask)
j = _right[j];
else
j = _left[j];
mask >>= 1;
} while (j >= ARJ_NC);
}
fillbuf((int)(_c_len[j]));
return j;
}
// Decodes a control character
uint16 ArjDecoder::decode_p() {
uint16 j, mask;
j = _pt_table[_bitbuf >> 8];
if (j >= ARJ_NP) {
mask = 1 << 7;
do {
if (_bitbuf & mask)
j = _right[j];
else
j = _left[j];
mask >>= 1;
} while (j >= ARJ_NP);
}
fillbuf((int)(_pt_len[j]));
if (j != 0) {
j--;
j = (1 << j) + getbits((int)j);
}
return j;
}
// Initializes memory for decoding
void ArjDecoder::decode_start() {
_blocksize = 0;
init_getbits();
}
// Decodes the entire file
void ArjDecoder::decode(int32 origsize) {
int16 i;
int16 r;
int16 c;
int16 j;
int32 count;
decode_start();
count = origsize;
r = 0;
while (count > 0) {
if ((c = decode_c()) <= ARJ_UCHAR_MAX) {
_ntext[r] = (byte) c;
count--;
if (++r >= ARJ_DICSIZ) {
r = 0;
_outstream->write(_ntext, ARJ_DICSIZ);
}
} else {
j = c - (ARJ_UCHAR_MAX + 1 - ARJ_THRESHOLD);
count -= j;
i = r - decode_p() - 1;
if (i < 0)
i += ARJ_DICSIZ;
if (r > i && r < ARJ_DICSIZ - ARJ_MAXMATCH - 1) {
while (--j >= 0)
_ntext[r++] = _ntext[i++];
} else {
while (--j >= 0) {
_ntext[r] = _ntext[i];
if (++r >= ARJ_DICSIZ) {
r = 0;
_outstream->write(_ntext, ARJ_DICSIZ);
}
if (++i >= ARJ_DICSIZ)
i = 0;
}
}
}
}
if (r > 0)
_outstream->write(_ntext, r);
}
// Backward pointer decoding
int16 ArjDecoder::decode_ptr() {
int16 c = 0;
int16 width;
int16 plus;
int16 pwr;
plus = 0;
pwr = 1 << 9;
for (width = 9; width < 13; width++) {
c = getbits(1);
if (c == 0)
break;
plus += pwr;
pwr <<= 1;
}
if (width != 0)
c = getbits(width);
c += plus;
return c;
}
// Reference length decoding
int16 ArjDecoder::decode_len() {
int16 c = 0;
int16 width;
int16 plus;
int16 pwr;
plus = 0;
pwr = 1;
for (width = 0; width < 7; width++) {
c = getbits(1);
if (c == 0)
break;
plus += pwr;
pwr <<= 1;
}
if (width != 0)
c = getbits(width);
c += plus;
return c;
}
// Decodes the entire file, using method 4
void ArjDecoder::decode_f(int32 origsize) {
int16 i;
int16 j;
int16 c;
int16 r;
uint32 ncount;
init_getbits();
ncount = 0;
r = 0;
while (ncount < (uint32)origsize) {
c = decode_len();
if (c == 0) {
ncount++;
_ntext[r] = (byte)getbits(8);
if (++r >= ARJ_FDICSIZ) {
r = 0;
_outstream->write(_ntext, ARJ_FDICSIZ);
}
} else {
j = c - 1 + ARJ_THRESHOLD;
ncount += j;
if ((i = r - decode_ptr() - 1) < 0)
i += ARJ_FDICSIZ;
while (j-- > 0) {
_ntext[r] = _ntext[i];
if (++r >= ARJ_FDICSIZ) {
r = 0;
_outstream->write(_ntext, ARJ_FDICSIZ);
}
if (++i >= ARJ_FDICSIZ)
i = 0;
}
}
}
if (r != 0)
_outstream->write(_ntext, r);
}
#pragma mark ArjArchive implementation
struct ArjFileChunk {
ArjHeader* _header;
uint _volume;
ArjFileChunk(ArjHeader* header, uint volume) : _header(header), _volume(volume) {}
};
typedef HashMap<Path, Array<ArjFileChunk>, Path::IgnoreCase_Hash, Path::IgnoreCase_EqualTo> ArjHeadersMap;
class ArjArchive : public MemcachingCaseInsensitiveArchive {
ArjHeadersMap _headers;
Array<Path> _arjFilenames;
bool _flattenTree;
public:
ArjArchive(const Array<Path> &names, bool flattenTree);
virtual ~ArjArchive();
// Archive implementation
bool hasFile(const Path &path) const override;
int listMembers(ArchiveMemberList &list) const override;
const ArchiveMemberPtr getMember(const Path &path) const override;
Common::SharedArchiveContents readContentsForPath(const Common::Path &translated) const override;
Common::Path translatePath(const Common::Path &path) const override {
return _flattenTree ? path.getLastComponent() : path;
}
};
ArjArchive::~ArjArchive() {
debug(0, "ArjArchive Destructor Called");
for (auto &header : _headers) {
for (uint i = 0; i < header._value.size(); i++)
delete header._value[i]._header;
}
}
ArjArchive::ArjArchive(const Array<Path> &filenames, bool flattenTree) : _arjFilenames(filenames), _flattenTree(flattenTree) {
for (uint i = 0; i < _arjFilenames.size(); i++) {
File arjFile;
if (!arjFile.open(_arjFilenames[i])) {
warning("ArjArchive::ArjArchive(): Could not find the archive file");
return;
}
int32 firstHeaderOffset = findHeader(arjFile);
if (firstHeaderOffset < 0) {
warning("ArjArchive::ArjArchive(): Could not find a valid header");
return;
}
ArjHeader *header = nullptr;
arjFile.seek(firstHeaderOffset, SEEK_SET);
if ((header = readHeader(arjFile)) == nullptr)
return;
delete header;
while ((header = readHeader(arjFile)) != nullptr) {
const char *name = header->filename;
if (_flattenTree) {
for (const char *p = header->filename; *p; p++) {
if (*p == '\\' || *p == '/') {
name = p + 1;
}
}
} else {
for (char *p = header->filename; *p; p++) {
if (*p == '\\')
*p = '/';
}
}
_headers[Path(name)].push_back(ArjFileChunk(header, i));
arjFile.seek(header->compSize, SEEK_CUR);
}
}
debug(0, "ArjArchive::ArjArchive(%d volume(s) starting with %s): Located %d files", filenames.size(), filenames.empty() ? "" : filenames[0].toString(Common::Path::kNativeSeparator).c_str(), _headers.size());
}
bool ArjArchive::hasFile(const Path &path) const {
return _headers.contains(path);
}
int ArjArchive::listMembers(ArchiveMemberList &list) const {
int matches = 0;
for (const auto &header : _headers) {
list.push_back(ArchiveMemberList::value_type(new GenericArchiveMember(Path(header._value[0]._header->filename), *this)));
matches++;
}
return matches;
}
const ArchiveMemberPtr ArjArchive::getMember(const Path &path) const {
if (!hasFile(path))
return ArchiveMemberPtr();
return ArchiveMemberPtr(new GenericArchiveMember(path, *this));
}
Common::SharedArchiveContents ArjArchive::readContentsForPath(const Common::Path &path) const {
if (!_headers.contains(path)) {
return Common::SharedArchiveContents();
}
const Array <ArjFileChunk>& hdrs = _headers[path];
uint64 uncompressedSize = 0;
uint totalChunks;
for (totalChunks = 0; totalChunks < hdrs.size(); totalChunks++) {
uncompressedSize += hdrs[totalChunks]._header->origSize;
if (!(hdrs[totalChunks]._header->flags & 0x4)) {
totalChunks++;
break;
}
}
// Prevent overflows
if (uncompressedSize > 0x70000000)
return Common::SharedArchiveContents();
// TODO: It would be good if ArjFile could decompress files in a streaming
// mode, so it would not need to pre-allocate the entire output.
byte *uncompressedData = new byte[uncompressedSize];
uint32 uncompressedPtr = 0;
if (!uncompressedData) {
warning("ArjArchive: Failed to allocate %d bytes", (uint32)uncompressedSize);
return Common::SharedArchiveContents();
}
for (uint chunk = 0; chunk < totalChunks; chunk++) {
File archiveFile;
ArjHeader *hdr = hdrs[chunk]._header;
archiveFile.open(_arjFilenames[hdrs[chunk]._volume]);
archiveFile.seek(hdr->pos, SEEK_SET);
if (hdr->method == 0) { // store
int32 len = archiveFile.read(uncompressedData + uncompressedPtr, hdr->origSize);
assert(len == hdr->origSize);
(void)len;
} else {
ArjDecoder *decoder = new ArjDecoder(hdr);
// TODO: It might not be appropriate to use this wrapper inside ArjFile.
// If reading from archiveFile directly is too slow to be usable,
// maybe the filesystem code should instead wrap its files
// in a BufferedReadStream.
decoder->_compressed = wrapBufferedReadStream(&archiveFile, 4096, DisposeAfterUse::NO);
decoder->_outstream = new MemoryWriteStream(uncompressedData + uncompressedPtr, hdr->origSize);
if (hdr->method == 1 || hdr->method == 2 || hdr->method == 3)
decoder->decode(hdr->origSize);
else if (hdr->method == 4)
decoder->decode_f(hdr->origSize);
delete decoder;
}
uncompressedPtr += hdr->origSize;
}
return Common::SharedArchiveContents(uncompressedData, uncompressedSize);
}
Archive *makeArjArchive(const Path &name, bool flattenTree) {
return new ArjArchive({name}, flattenTree);
}
Archive *makeArjArchive(const Array<Path> &names, bool flattenTree) {
return new ArjArchive(names, flattenTree);
}
} // End of namespace Common

View File

@@ -0,0 +1,63 @@
/* 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
* ARJ decompressor used in engines:
* - drascula
*/
#ifndef COMMON_UNARJ_H
#define COMMON_UNARJ_H
#include "common/str.h"
namespace Common {
/**
* @defgroup common_unarj ARJ decompressor
* @ingroup common
*
* @brief API related to ARJ archive files.
*
* @{
*/
class Archive;
/**
* This factory method creates an Archive instance corresponding to the content
* of the ARJ compressed file with the given name.
*
* May return 0 in case of a failure.
*/
Archive *makeArjArchive(const Path &name, bool flattenTree = false);
/**
* Similar to previous but for multi-volume archives
*/
Archive *makeArjArchive(const Array<Path> &names, bool flattenTree = false);
/** @} */
} // End of namespace Common
#endif

1144
common/compression/unzip.cpp Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,72 @@
/* 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/>.
*
*/
#ifndef COMMON_UNZIP_H
#define COMMON_UNZIP_H
#include "common/str.h"
namespace Common {
/**
* @defgroup common_unzip ZIP decompressor
* @ingroup common
*
* @brief API related to ZIP archive files.
*
* @{
*/
class Archive;
class FSNode;
class SeekableReadStream;
/**
* This factory method creates an Archive instance corresponding to the content
* of the ZIP compressed file with the given name.
*
* May return 0 in case of a failure.
*/
Archive *makeZipArchive(const Path &name, bool flattenTree = false);
/**
* This factory method creates an Archive instance corresponding to the content
* of the ZIP compressed file with the given name.
*
* May return 0 in case of a failure.
*/
Archive *makeZipArchive(const FSNode &node, bool flattenTree = false);
/**
* This factory method creates an Archive instance corresponding to the content
* of the given ZIP compressed datastream.
* This takes ownership of the stream, in particular, it is deleted when the
* ZipArchive is deleted.
*
* May return 0 in case of a failure. In this case stream will still be deleted.
*/
Archive *makeZipArchive(SeekableReadStream *stream, bool flattenTree = false);
/** @} */
} // End of namespace Common
#endif

434
common/compression/vise.cpp Normal file
View File

@@ -0,0 +1,434 @@
/* 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/compression/vise.h"
#include "common/macresman.h"
#include "common/memstream.h"
#include "common/compression/deflate.h"
// Installer VISE archive loader.
//
// Supported versions:
//
// Macintosh:
// Installer VISE 3.5 Lite
// Installer VISE 3.6 Lite
//
// Windows:
// None
namespace Common {
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::Path fullPath;
};
struct DirectoryDesc {
uint16 containingDirectory;
Common::String name;
Common::Path fullPath;
};
class ArchiveMember : public Common::ArchiveMember {
public:
ArchiveMember(Common::SeekableReadStream *archiveStream, const FileDesc *fileDesc);
Common::SeekableReadStream *createReadStream() const override;
Common::SeekableReadStream *createReadStreamForAltStream(Common::AltStreamType altStreamType) const override;
Common::String getName() const override;
Common::Path getPathInArchive() const override;
Common::String getFileName() const override;
bool isInMacArchive() const override;
private:
Common::SeekableReadStream *createReadStreamForDataStream(bool isResFork) const;
Common::SeekableReadStream *_archiveStream;
const FileDesc *_fileDesc;
};
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;
Common::SeekableReadStream *createReadStreamForMemberAltStream(const Common::Path &path, Common::AltStreamType altStreamType) const override;
char getPathSeparator() const override;
private:
bool getFileDescIndex(const Common::Path &path, uint &outIndex) const;
Common::SeekableReadStream *_archiveStream;
Common::Array<FileDesc> _fileDescs;
Common::Array<DirectoryDesc> _directoryDescs;
};
MacVISEArchive::ArchiveMember::ArchiveMember(Common::SeekableReadStream *archiveStream, const FileDesc *fileDesc)
: _archiveStream(archiveStream), _fileDesc(fileDesc) {
}
Common::SeekableReadStream *MacVISEArchive::ArchiveMember::createReadStream() const {
return createReadStreamForDataStream(false);
}
Common::SeekableReadStream *MacVISEArchive::ArchiveMember::createReadStreamForAltStream(Common::AltStreamType altStreamType) const {
if (altStreamType == AltStreamType::MacFinderInfo) {
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);
}
if (altStreamType == AltStreamType::MacResourceFork)
return createReadStreamForDataStream(true);
return nullptr;
}
Common::SeekableReadStream *MacVISEArchive::ArchiveMember::createReadStreamForDataStream(bool isResFork) const {
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,
};
uint32 uncompressedSize = isResFork ? _fileDesc->uncompressedResSize : _fileDesc->uncompressedDataSize;
uint32 compressedSize = isResFork ? _fileDesc->compressedResSize : _fileDesc->compressedDataSize;
uint32 filePosition = _fileDesc->positionInArchive;
if (uncompressedSize == 0 && !isResFork) {
// Always return a stream for the data fork, even if it's empty
return new Common::MemoryReadStream(nullptr, 0, DisposeAfterUse::NO);
}
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 {
return getPathInArchive().getLastComponent().toString(':');
}
Common::Path MacVISEArchive::ArchiveMember::getPathInArchive() const {
return _fileDesc->fullPath;
}
Common::String MacVISEArchive::ArchiveMember::getFileName() const {
return _fileDesc->name;
}
bool MacVISEArchive::ArchiveMember::isInMacArchive() const {
return true;
}
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.appendComponent(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.appendComponent(fileDesc.name);
}
}
return true;
}
const MacVISEArchive::FileDesc *MacVISEArchive::getFileDesc(const Common::Path &path) const {
for (const FileDesc &desc : _fileDescs) {
if (desc.fullPath == path)
return &desc;
}
return nullptr;
}
bool MacVISEArchive::hasFile(const Common::Path &path) const {
uint index = 0;
return getFileDescIndex(path, index);
}
int MacVISEArchive::listMembers(Common::ArchiveMemberList &list) const {
int numMembers = 0;
for (uint fileIndex = 0; fileIndex < _fileDescs.size(); fileIndex++) {
const FileDesc &desc = _fileDescs[fileIndex];
list.push_back(Common::ArchiveMemberPtr(new ArchiveMember(_archiveStream, &desc)));
numMembers++;
}
return numMembers;
}
const Common::ArchiveMemberPtr MacVISEArchive::getMember(const Common::Path &path) const {
uint descIndex = 0;
if (!getFileDescIndex(path, descIndex))
return nullptr;
return Common::ArchiveMemberPtr(new ArchiveMember(_archiveStream, &_fileDescs[descIndex]));
}
Common::SeekableReadStream *MacVISEArchive::createReadStreamForMember(const Common::Path &path) const {
Common::ArchiveMemberPtr archiveMember = getMember(path);
if (!archiveMember)
return nullptr;
return archiveMember->createReadStream();
}
Common::SeekableReadStream *MacVISEArchive::createReadStreamForMemberAltStream(const Common::Path &path, Common::AltStreamType altStreamType) const {
Common::ArchiveMemberPtr archiveMember = getMember(path);
if (!archiveMember)
return nullptr;
return archiveMember->createReadStreamForAltStream(altStreamType);
}
char MacVISEArchive::getPathSeparator() const {
return ':';
}
bool MacVISEArchive::getFileDescIndex(const Common::Path &path, uint &outIndex) const {
for (uint descIndex = 0; descIndex < _fileDescs.size(); descIndex++) {
const FileDesc &desc = _fileDescs[descIndex];
if (desc.fullPath == path) {
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 Common

35
common/compression/vise.h Normal file
View File

@@ -0,0 +1,35 @@
/* 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/archive.h"
namespace Common {
// Loads a Macintosh Installer VISE archive from a stream
Common::Archive *createMacVISEArchive(Common::SeekableReadStream *stream);
} // End of namespace Common

431
common/compression/zlib.cpp Normal file
View File

@@ -0,0 +1,431 @@
/* 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/>.
*
*/
#ifdef __MORPHOS__
#define _NO_PPCINLINE
#include <zlib.h>
#undef _NO_PPCINLINE
#else
#include <zlib.h>
#endif
#if ZLIB_VERNUM < 0x1204
#error Version 1.2.0.4 or newer of zlib is required for this code
#endif
#include "common/compression/deflate.h"
#include "common/ptr.h"
#include "common/util.h"
#include "common/stream.h"
#include "common/debug.h"
#include "common/textconsole.h"
namespace Common {
bool inflateZlib(byte *dst, unsigned long *dstLen, const byte *src, unsigned long srcLen) {
return Z_OK == uncompress(dst, dstLen, src, srcLen);
}
bool inflateZlibHeaderless(byte *dst, uint *dstLen, const byte *src, uint srcLen, const byte *dict, uint dictLen) {
if (!dst || !dstLen || !*dstLen || !src || !srcLen)
return false;
// Initialize zlib
z_stream stream;
stream.next_in = const_cast<byte *>(src);
stream.avail_in = srcLen;
stream.next_out = dst;
stream.avail_out = *dstLen;
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;
// Set the dictionary, if provided
if (dict != nullptr) {
err = inflateSetDictionary(&stream, const_cast<byte *>(dict), dictLen);
if (err != Z_OK) {
inflateEnd(&stream);
return false;
}
}
err = inflate(&stream, Z_SYNC_FLUSH);
if (err != Z_OK && err != Z_STREAM_END) {
inflateEnd(&stream);
return false;
}
inflateEnd(&stream);
*dstLen = *dstLen - stream.avail_out;
return true;
}
#ifndef RELEASE_BUILD
static bool _shownBackwardSeekingWarning = false;
#endif
/**
* A simple wrapper class which can be used to wrap around an arbitrary
* other SeekableReadStream and will then provide on-the-fly decompression support.
* Assumes the compressed data to be in gzip format.
*/
class GZipReadStream : public SeekableReadStream {
protected:
enum {
BUFSIZE = 16384 // 1 << MAX_WBITS
};
byte _buf[BUFSIZE];
DisposablePtr<SeekableReadStream> _wrapped;
z_stream _stream;
int _zlibErr;
uint64 _parentPos;
uint32 _pos;
uint32 _origSize;
bool _eos;
public:
GZipReadStream(SeekableReadStream *w, DisposeAfterUse::Flag disposeParent, uint32 knownSize) : _wrapped(w, disposeParent), _stream() {
assert(w != nullptr);
_parentPos = w->pos();
// Verify file header is correct
uint16 header = w->readUint16BE();
assert(header == 0x1F8B ||
((header & 0x0F00) == 0x0800 && header % 31 == 0));
if (header == 0x1F8B) {
// Retrieve the original file size
w->seek(-4, SEEK_END);
_origSize = w->readUint32LE();
} else {
// Original size not available in zlib format
// use an otherwise known size if supplied.
_origSize = knownSize;
}
w->seek(_parentPos, SEEK_SET);
_pos = 0;
_eos = false;
// Adding 32 to windowBits indicates to zlib that it is supposed to
// automatically detect whether gzip or zlib headers are used for
// the compressed file. This feature was added in zlib 1.2.0.4,
// released 10 August 2003.
// Note: This is *crucial* for savegame compatibility, do *not* remove!
_zlibErr = inflateInit2(&_stream, MAX_WBITS + 32);
if (_zlibErr != Z_OK)
return;
// Setup input buffer
_stream.next_in = _buf;
_stream.avail_in = 0;
}
GZipReadStream(SeekableReadStream *w, DisposeAfterUse::Flag disposeParent, uint32 knownSize, const byte *dict, uint dictLen) : _wrapped(w, disposeParent), _stream() {
assert(w != nullptr);
_parentPos = w->pos();
// This is headerless deflate
// Original size not available
// use an otherwise known size if supplied.
_origSize = knownSize;
_pos = 0;
_eos = false;
_zlibErr = inflateInit2(&_stream, -MAX_WBITS);
if (_zlibErr != Z_OK)
return;
// Set the dictionary, if provided
if (dict != nullptr && dictLen > 0) {
_zlibErr = inflateSetDictionary(&_stream, const_cast<byte *>(dict), dictLen);
if (_zlibErr != Z_OK)
return;
}
// Setup input buffer
_stream.next_in = _buf;
_stream.avail_in = 0;
}
~GZipReadStream() {
inflateEnd(&_stream);
}
bool err() const override { return (_zlibErr != Z_OK) && (_zlibErr != Z_STREAM_END); }
void clearErr() override {
// only reset _eos; I/O errors are not recoverable
_eos = false;
}
uint32 read(void *dataPtr, uint32 dataSize) override {
_stream.next_out = (byte *)dataPtr;
_stream.avail_out = dataSize;
// Keep going while we get no error
while (_zlibErr == Z_OK && _stream.avail_out) {
if (_stream.avail_in == 0 && !_wrapped->eos()) {
// If we are out of input data: Read more data, if available.
_stream.next_in = _buf;
_stream.avail_in = _wrapped->read(_buf, BUFSIZE);
}
_zlibErr = inflate(&_stream, Z_NO_FLUSH);
}
// Update the position counter
_pos += dataSize - _stream.avail_out;
if (_zlibErr == Z_STREAM_END && _stream.avail_out > 0)
_eos = true;
return dataSize - _stream.avail_out;
}
bool eos() const override {
return _eos;
}
int64 pos() const override {
return _pos;
}
int64 size() const override {
return _origSize;
}
bool seek(int64 offset, int whence = SEEK_SET) override {
int32 newPos = 0;
switch (whence) {
default:
// fallthrough intended
case SEEK_SET:
newPos = offset;
break;
case SEEK_CUR:
newPos = _pos + offset;
break;
case SEEK_END:
// NOTE: This can be an expensive operation (see below).
newPos = size() + offset;
break;
}
assert(newPos >= 0);
if ((uint32)newPos < _pos) {
// To search backward, we have to restart the whole decompression
// from the start of the file. A rather wasteful operation, best
// to avoid it. :/
#ifndef RELEASE_BUILD
if (!_shownBackwardSeekingWarning) {
// We only throw this warning once per stream, to avoid
// getting the console swarmed with warnings when consecutive
// seeks are made.
debug(1, "Backward seeking in GZipReadStream detected");
_shownBackwardSeekingWarning = true;
}
#endif
_pos = 0;
_wrapped->seek(_parentPos, SEEK_SET);
_zlibErr = inflateReset(&_stream);
if (_zlibErr != Z_OK)
return false; // FIXME: STREAM REWRITE
_stream.next_in = _buf;
_stream.avail_in = 0;
}
offset = newPos - _pos;
// Skip the given amount of data (very inefficient if one tries to skip
// huge amounts of data, but usually client code will only skip a few
// bytes, so this should be fine.
byte tmpBuf[1024];
while (!err() && offset > 0) {
offset -= read(tmpBuf, MIN((int64)sizeof(tmpBuf), offset));
}
_eos = false;
return true; // FIXME: STREAM REWRITE
}
};
/**
* A simple wrapper class which can be used to wrap around an arbitrary
* other WriteStream and will then provide on-the-fly compression support.
* The compressed data is written in the gzip format.
*/
class GZipWriteStream : public WriteStream {
protected:
enum {
BUFSIZE = 16384 // 1 << MAX_WBITS
};
byte _buf[BUFSIZE];
ScopedPtr<WriteStream> _wrapped;
z_stream _stream;
int _zlibErr;
uint32 _pos;
void processData(int flushType) {
// This function is called by both write() and finalize().
while (_zlibErr == Z_OK && (_stream.avail_in || flushType == Z_FINISH)) {
if (_stream.avail_out == 0) {
if (_wrapped->write(_buf, BUFSIZE) != BUFSIZE) {
_zlibErr = Z_ERRNO;
break;
}
_stream.next_out = _buf;
_stream.avail_out = BUFSIZE;
}
_zlibErr = deflate(&_stream, flushType);
}
}
public:
GZipWriteStream(WriteStream *w) : _wrapped(w), _stream(), _pos(0) {
assert(w != nullptr);
// Adding 16 to windowBits indicates to zlib that it is supposed to
// write gzip headers. This feature was added in zlib 1.2.0.4,
// released 10 August 2003.
// Note: This is *crucial* for savegame compatibility, do *not* remove!
_zlibErr = deflateInit2(&_stream,
Z_DEFAULT_COMPRESSION,
Z_DEFLATED,
MAX_WBITS + 16,
8,
Z_DEFAULT_STRATEGY);
assert(_zlibErr == Z_OK);
_stream.next_out = _buf;
_stream.avail_out = BUFSIZE;
_stream.avail_in = 0;
_stream.next_in = nullptr;
}
~GZipWriteStream() {
finalize();
deflateEnd(&_stream);
}
bool err() const override {
// CHECKME: does Z_STREAM_END make sense here?
return (_zlibErr != Z_OK && _zlibErr != Z_STREAM_END) || _wrapped->err();
}
void clearErr() override {
// Note: we don't reset the _zlibErr here, as it is not
// clear in general how
_wrapped->clearErr();
}
void finalize() override {
if (_zlibErr != Z_OK)
return;
// Process whatever remaining data there is.
processData(Z_FINISH);
// Since processData only writes out blocks of size BUFSIZE,
// we may have to flush some stragglers.
uint remainder = BUFSIZE - _stream.avail_out;
if (remainder > 0) {
if (_wrapped->write(_buf, remainder) != remainder) {
_zlibErr = Z_ERRNO;
}
}
// Finalize the wrapped savefile, too
_wrapped->finalize();
}
uint32 write(const void *dataPtr, uint32 dataSize) override {
if (err())
return 0;
// Hook in the new data ...
// Note: We need to make a const_cast here, as zlib is not aware
// of the const keyword.
_stream.next_in = const_cast<byte *>((const byte *)dataPtr);
_stream.avail_in = dataSize;
// ... and flush it to disk
processData(Z_NO_FLUSH);
_pos += dataSize - _stream.avail_in;
return dataSize - _stream.avail_in;
}
int64 pos() const override { return _pos; }
};
SeekableReadStream *wrapCompressedReadStream(SeekableReadStream *toBeWrapped, DisposeAfterUse::Flag disposeParent, uint64 knownSize) {
if (!toBeWrapped) {
return nullptr;
}
if (toBeWrapped->eos() || toBeWrapped->err() || toBeWrapped->size() < 2) {
if (disposeParent == DisposeAfterUse::YES) {
delete toBeWrapped;
}
return nullptr;
}
uint16 header = toBeWrapped->readUint16BE();
bool isCompressed = (header == 0x1F8B ||
((header & 0x0F00) == 0x0800 &&
header % 31 == 0));
toBeWrapped->seek(-2, SEEK_CUR);
if (isCompressed) {
return new GZipReadStream(toBeWrapped, disposeParent, knownSize);
}
return toBeWrapped;
}
SeekableReadStream *wrapDeflateReadStream(SeekableReadStream *toBeWrapped, DisposeAfterUse::Flag disposeParent, uint64 knownSize, const byte *dict, uint dictLen) {
if (!toBeWrapped) {
return nullptr;
}
if (toBeWrapped->eos() || toBeWrapped->err()) {
if (disposeParent == DisposeAfterUse::YES) {
delete toBeWrapped;
}
return nullptr;
}
return new GZipReadStream(toBeWrapped, disposeParent, knownSize, dict, dictLen);
}
WriteStream *wrapCompressedWriteStream(WriteStream *toBeWrapped) {
if (!toBeWrapped)
return nullptr;
return new GZipWriteStream(toBeWrapped);
}
} // End of namespace Common