1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200
|
/*
* 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
|