/* * 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 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(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(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::max()); si64 size = tell(); seek(prevPos); return size; } void CBufferedStream::ensureSize(si64 size) { while(static_cast(buffer.size()) < size && !endOfFileReached) { si64 initialSize = buffer.size(); si64 currentStep = std::min(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 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(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(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