mirror of
https://github.com/vcmi/vcmi.git
synced 2024-12-22 22:13:35 +02:00
201 lines
4.4 KiB
C++
201 lines
4.4 KiB
C++
/*
|
|
* CCompressedStream.cpp, part of VCMI engine
|
|
*
|
|
* Authors: listed in file AUTHORS in main folder
|
|
*
|
|
* License: GNU General Public License v2.0 or later
|
|
* Full text of license available in license.txt file, in main folder
|
|
*
|
|
*/
|
|
#include "StdInc.h"
|
|
#include "CCompressedStream.h"
|
|
|
|
#include <zlib.h>
|
|
|
|
VCMI_LIB_NAMESPACE_BEGIN
|
|
|
|
static const int inflateBlockSize = 10000;
|
|
|
|
CBufferedStream::CBufferedStream():
|
|
position(0),
|
|
endOfFileReached(false)
|
|
{
|
|
}
|
|
|
|
si64 CBufferedStream::read(ui8 * data, si64 size)
|
|
{
|
|
ensureSize(position + size);
|
|
|
|
auto * start = buffer.data() + position;
|
|
si64 toRead = std::min<si64>(size, buffer.size() - position);
|
|
|
|
std::copy(start, start + toRead, data);
|
|
position += toRead;
|
|
return size;
|
|
}
|
|
|
|
si64 CBufferedStream::seek(si64 position)
|
|
{
|
|
ensureSize(position);
|
|
this->position = std::min<si64>(position, buffer.size());
|
|
return this->position;
|
|
}
|
|
|
|
si64 CBufferedStream::tell()
|
|
{
|
|
return position;
|
|
}
|
|
|
|
si64 CBufferedStream::skip(si64 delta)
|
|
{
|
|
return seek(position + delta) - delta;
|
|
}
|
|
|
|
si64 CBufferedStream::getSize()
|
|
{
|
|
si64 prevPos = tell();
|
|
seek(std::numeric_limits<si64>::max());
|
|
si64 size = tell();
|
|
seek(prevPos);
|
|
return size;
|
|
}
|
|
|
|
void CBufferedStream::ensureSize(si64 size)
|
|
{
|
|
while(static_cast<si64>(buffer.size()) < size && !endOfFileReached)
|
|
{
|
|
si64 initialSize = buffer.size();
|
|
si64 currentStep = std::min<si64>(size, buffer.size());
|
|
// to avoid large number of calls at start
|
|
// this is often used to load h3m map headers, most of which are ~300 bytes in size
|
|
vstd::amax(currentStep, 512);
|
|
|
|
buffer.resize(initialSize + currentStep);
|
|
|
|
si64 readSize = readMore(buffer.data() + initialSize, currentStep);
|
|
if (readSize != currentStep)
|
|
{
|
|
endOfFileReached = true;
|
|
buffer.resize(initialSize + readSize);
|
|
buffer.shrink_to_fit();
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
void CBufferedStream::reset()
|
|
{
|
|
buffer.clear();
|
|
position = 0;
|
|
endOfFileReached = false;
|
|
}
|
|
|
|
CCompressedStream::CCompressedStream(std::unique_ptr<CInputStream> stream, bool gzip, size_t decompressedSize):
|
|
gzipStream(std::move(stream)),
|
|
compressedBuffer(inflateBlockSize)
|
|
{
|
|
assert(gzipStream);
|
|
|
|
// Allocate inflate state
|
|
inflateState = new z_stream();
|
|
inflateState->zalloc = Z_NULL;
|
|
inflateState->zfree = Z_NULL;
|
|
inflateState->opaque = Z_NULL;
|
|
inflateState->avail_in = 0;
|
|
inflateState->next_in = Z_NULL;
|
|
|
|
int wbits = 15;
|
|
if (gzip)
|
|
wbits += 16;
|
|
|
|
int ret = inflateInit2(inflateState, wbits);
|
|
if (ret != Z_OK)
|
|
throw std::runtime_error("Failed to initialize inflate!\n");
|
|
}
|
|
|
|
CCompressedStream::~CCompressedStream()
|
|
{
|
|
inflateEnd(inflateState);
|
|
vstd::clear_pointer(inflateState);
|
|
}
|
|
|
|
si64 CCompressedStream::readMore(ui8 *data, si64 size)
|
|
{
|
|
if (inflateState == nullptr)
|
|
return 0; //file already decompressed
|
|
|
|
bool fileEnded = false; //end of file reached
|
|
bool endLoop = false;
|
|
|
|
int decompressed = inflateState->total_out;
|
|
|
|
inflateState->avail_out = static_cast<uInt>(size);
|
|
inflateState->next_out = data;
|
|
|
|
do
|
|
{
|
|
if (inflateState->avail_in == 0)
|
|
{
|
|
if (gzipStream == nullptr)
|
|
throw std::runtime_error("Potentially truncated gzip file");
|
|
|
|
//inflate ran out of available data or was not initialized yet
|
|
// get new input data and update state accordingly
|
|
si64 availSize = gzipStream->read(compressedBuffer.data(), compressedBuffer.size());
|
|
if (availSize != compressedBuffer.size())
|
|
gzipStream.reset();
|
|
|
|
inflateState->avail_in = static_cast<uInt>(availSize);
|
|
inflateState->next_in = compressedBuffer.data();
|
|
}
|
|
|
|
int ret = inflate(inflateState, Z_NO_FLUSH);
|
|
|
|
if (inflateState->avail_in == 0 && gzipStream == nullptr)
|
|
fileEnded = true;
|
|
|
|
switch (ret)
|
|
{
|
|
case Z_OK: //input data ended/ output buffer full
|
|
endLoop = false;
|
|
break;
|
|
case Z_STREAM_END: // stream ended. Note that campaign files consist from multiple such streams
|
|
endLoop = true;
|
|
break;
|
|
case Z_BUF_ERROR: // output buffer full. Can be triggered?
|
|
endLoop = true;
|
|
break;
|
|
default:
|
|
if (inflateState->msg == nullptr)
|
|
throw DecompressionException("Error code " + std::to_string(ret));
|
|
else
|
|
throw DecompressionException(inflateState->msg);
|
|
}
|
|
}
|
|
while (!endLoop && inflateState->avail_out != 0 );
|
|
|
|
decompressed = inflateState->total_out - decompressed;
|
|
|
|
// Clean up and return
|
|
if (fileEnded)
|
|
{
|
|
inflateEnd(inflateState);
|
|
vstd::clear_pointer(inflateState);
|
|
}
|
|
return decompressed;
|
|
}
|
|
|
|
bool CCompressedStream::getNextBlock()
|
|
{
|
|
if (!inflateState)
|
|
return false;
|
|
|
|
if (inflateReset(inflateState) < 0)
|
|
return false;
|
|
|
|
reset();
|
|
return true;
|
|
}
|
|
|
|
VCMI_LIB_NAMESPACE_END
|