Initial commit
This commit is contained in:
979
common/compression/gentee_installer.cpp
Normal file
979
common/compression/gentee_installer.cpp
Normal 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
|
||||
Reference in New Issue
Block a user