CCompressedStream.cpp 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199
  1. /*
  2. * CCompressedStream.cpp, part of VCMI engine
  3. *
  4. * Authors: listed in file AUTHORS in main folder
  5. *
  6. * License: GNU General Public License v2.0 or later
  7. * Full text of license available in license.txt file, in main folder
  8. *
  9. */
  10. #include "StdInc.h"
  11. #include "CCompressedStream.h"
  12. #include <zlib.h>
  13. VCMI_LIB_NAMESPACE_BEGIN
  14. static const int inflateBlockSize = 10000;
  15. CBufferedStream::CBufferedStream():
  16. position(0),
  17. endOfFileReached(false)
  18. {
  19. }
  20. si64 CBufferedStream::read(ui8 * data, si64 size)
  21. {
  22. ensureSize(position + size);
  23. auto * start = buffer.data() + position;
  24. si64 toRead = std::min<si64>(size, buffer.size() - position);
  25. std::copy(start, start + toRead, data);
  26. position += toRead;
  27. return size;
  28. }
  29. si64 CBufferedStream::seek(si64 position)
  30. {
  31. ensureSize(position);
  32. this->position = std::min<si64>(position, buffer.size());
  33. return this->position;
  34. }
  35. si64 CBufferedStream::tell()
  36. {
  37. return position;
  38. }
  39. si64 CBufferedStream::skip(si64 delta)
  40. {
  41. return seek(position + delta) - delta;
  42. }
  43. si64 CBufferedStream::getSize()
  44. {
  45. si64 prevPos = tell();
  46. seek(std::numeric_limits<si64>::max());
  47. si64 size = tell();
  48. seek(prevPos);
  49. return size;
  50. }
  51. void CBufferedStream::ensureSize(si64 size)
  52. {
  53. while(static_cast<si64>(buffer.size()) < size && !endOfFileReached)
  54. {
  55. si64 initialSize = buffer.size();
  56. si64 need = size - buffer.size();
  57. // to avoid large number of calls at start
  58. // this is often used to load h3m map headers, most of which are ~300 bytes in size
  59. si64 currentStep = std::min<si64>(need, std::max<si64>(initialSize, si64{512}));
  60. buffer.resize(initialSize + currentStep);
  61. si64 readSize = readMore(buffer.data() + initialSize, currentStep);
  62. if (readSize != currentStep)
  63. {
  64. endOfFileReached = true;
  65. buffer.resize(initialSize + readSize);
  66. buffer.shrink_to_fit();
  67. return;
  68. }
  69. }
  70. }
  71. void CBufferedStream::reset()
  72. {
  73. buffer.clear();
  74. position = 0;
  75. endOfFileReached = false;
  76. }
  77. CCompressedStream::CCompressedStream(std::unique_ptr<CInputStream> stream, bool gzip, size_t decompressedSize):
  78. gzipStream(std::move(stream)),
  79. compressedBuffer(inflateBlockSize)
  80. {
  81. assert(gzipStream);
  82. // Allocate inflate state
  83. inflateState = std::make_unique<z_stream>();
  84. inflateState->zalloc = Z_NULL;
  85. inflateState->zfree = Z_NULL;
  86. inflateState->opaque = Z_NULL;
  87. inflateState->avail_in = 0;
  88. inflateState->next_in = Z_NULL;
  89. int wbits = 15;
  90. if (gzip)
  91. wbits += 16;
  92. int ret = inflateInit2(inflateState.get(), wbits);
  93. if (ret != Z_OK)
  94. throw std::runtime_error("Failed to initialize inflate!\n");
  95. }
  96. CCompressedStream::~CCompressedStream()
  97. {
  98. inflateEnd(inflateState.get());
  99. }
  100. si64 CCompressedStream::readMore(ui8 *data, si64 size)
  101. {
  102. if (inflateState == nullptr)
  103. return 0; //file already decompressed
  104. bool fileEnded = false; //end of file reached
  105. bool endLoop = false;
  106. int decompressed = inflateState->total_out;
  107. inflateState->avail_out = static_cast<uInt>(size);
  108. inflateState->next_out = data;
  109. do
  110. {
  111. if (inflateState->avail_in == 0)
  112. {
  113. if (gzipStream == nullptr)
  114. throw std::runtime_error("Potentially truncated gzip file");
  115. //inflate ran out of available data or was not initialized yet
  116. // get new input data and update state accordingly
  117. si64 availSize = gzipStream->read(compressedBuffer.data(), compressedBuffer.size());
  118. if (availSize != compressedBuffer.size())
  119. gzipStream.reset();
  120. inflateState->avail_in = static_cast<uInt>(availSize);
  121. inflateState->next_in = compressedBuffer.data();
  122. }
  123. int ret = inflate(inflateState.get(), Z_NO_FLUSH);
  124. if (inflateState->avail_in == 0 && gzipStream == nullptr)
  125. fileEnded = true;
  126. switch (ret)
  127. {
  128. case Z_OK: //input data ended/ output buffer full
  129. endLoop = false;
  130. break;
  131. case Z_STREAM_END: // stream ended. Note that campaign files consist from multiple such streams
  132. endLoop = true;
  133. break;
  134. case Z_BUF_ERROR: // output buffer full. Can be triggered?
  135. endLoop = true;
  136. break;
  137. default:
  138. if (inflateState->msg == nullptr)
  139. throw DecompressionException("Error code " + std::to_string(ret));
  140. else
  141. throw DecompressionException(inflateState->msg);
  142. }
  143. }
  144. while (!endLoop && inflateState->avail_out != 0 );
  145. decompressed = inflateState->total_out - decompressed;
  146. // Clean up and return
  147. if (fileEnded)
  148. {
  149. inflateEnd(inflateState.get());
  150. inflateState.reset();
  151. }
  152. return decompressed;
  153. }
  154. bool CCompressedStream::getNextBlock()
  155. {
  156. if (!inflateState)
  157. return false;
  158. if (inflateReset(inflateState.get()) < 0)
  159. return false;
  160. reset();
  161. return true;
  162. }
  163. VCMI_LIB_NAMESPACE_END