CCompressedStream.cpp 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191
  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. static const int inflateBlockSize = 10000;
  14. CBufferedStream::CBufferedStream():
  15. position(0),
  16. endOfFileReached(false)
  17. {
  18. }
  19. si64 CBufferedStream::read(ui8 * data, si64 size)
  20. {
  21. ensureSize(position + size);
  22. auto start = buffer.data() + position;
  23. si64 toRead = std::min<si64>(size, buffer.size() - position);
  24. std::copy(start, start + toRead, data);
  25. position += toRead;
  26. return size;
  27. }
  28. si64 CBufferedStream::seek(si64 position)
  29. {
  30. ensureSize(position);
  31. this->position = std::min<si64>(position, buffer.size());
  32. return this->position;
  33. }
  34. si64 CBufferedStream::tell()
  35. {
  36. return position;
  37. }
  38. si64 CBufferedStream::skip(si64 delta)
  39. {
  40. return seek(position + delta) - delta;
  41. }
  42. si64 CBufferedStream::getSize()
  43. {
  44. si64 prevPos = tell();
  45. seek(std::numeric_limits<si64>::max());
  46. si64 size = tell();
  47. seek(prevPos);
  48. return size;
  49. }
  50. void CBufferedStream::ensureSize(si64 size)
  51. {
  52. while (buffer.size() < size && !endOfFileReached)
  53. {
  54. si64 initialSize = buffer.size();
  55. si64 currentStep = std::min<si64>(size, buffer.size());
  56. vstd::amax(currentStep, 1024); // to avoid large number of calls at start
  57. buffer.resize(initialSize + currentStep);
  58. si64 readSize = readMore(buffer.data() + initialSize, currentStep);
  59. if (readSize != currentStep)
  60. {
  61. endOfFileReached = true;
  62. buffer.resize(initialSize + readSize);
  63. buffer.shrink_to_fit();
  64. return;
  65. }
  66. }
  67. }
  68. void CBufferedStream::reset()
  69. {
  70. buffer.clear();
  71. position = 0;
  72. endOfFileReached = false;
  73. }
  74. CCompressedStream::CCompressedStream(std::unique_ptr<CInputStream> stream, bool gzip, size_t decompressedSize):
  75. gzipStream(std::move(stream)),
  76. compressedBuffer(inflateBlockSize)
  77. {
  78. assert(gzipStream);
  79. // Allocate inflate state
  80. inflateState = new z_stream();
  81. inflateState->zalloc = Z_NULL;
  82. inflateState->zfree = Z_NULL;
  83. inflateState->opaque = Z_NULL;
  84. inflateState->avail_in = 0;
  85. inflateState->next_in = Z_NULL;
  86. int wbits = 15;
  87. if (gzip)
  88. wbits += 16;
  89. int ret = inflateInit2(inflateState, wbits);
  90. if (ret != Z_OK)
  91. throw std::runtime_error("Failed to initialize inflate!\n");
  92. }
  93. CCompressedStream::~CCompressedStream()
  94. {
  95. inflateEnd(inflateState);
  96. vstd::clear_pointer(inflateState);
  97. }
  98. si64 CCompressedStream::readMore(ui8 *data, si64 size)
  99. {
  100. if (inflateState == nullptr)
  101. return 0; //file already decompressed
  102. bool fileEnded = false; //end of file reached
  103. bool endLoop = false;
  104. int decompressed = inflateState->total_out;
  105. inflateState->avail_out = size;
  106. inflateState->next_out = data;
  107. do
  108. {
  109. if (inflateState->avail_in == 0)
  110. {
  111. //inflate ran out of available data or was not initialized yet
  112. // get new input data and update state accordingly
  113. si64 availSize = gzipStream->read(compressedBuffer.data(), compressedBuffer.size());
  114. if (availSize != compressedBuffer.size())
  115. gzipStream.reset();
  116. inflateState->avail_in = availSize;
  117. inflateState->next_in = compressedBuffer.data();
  118. }
  119. int ret = inflate(inflateState, Z_NO_FLUSH);
  120. if (inflateState->avail_in == 0 && gzipStream == nullptr)
  121. fileEnded = true;
  122. switch (ret)
  123. {
  124. case Z_OK: //input data ended/ output buffer full
  125. endLoop = false;
  126. break;
  127. case Z_STREAM_END: // stream ended. Note that campaign files consist from multiple such streams
  128. endLoop = true;
  129. break;
  130. case Z_BUF_ERROR: // output buffer full. Can be triggered?
  131. endLoop = true;
  132. break;
  133. default:
  134. if (inflateState->msg == nullptr)
  135. throw std::runtime_error("Decompression error. Return code was " + boost::lexical_cast<std::string>(ret));
  136. else
  137. throw std::runtime_error(std::string("Decompression error: ") + inflateState->msg);
  138. }
  139. }
  140. while (endLoop == false && inflateState->avail_out != 0 );
  141. decompressed = inflateState->total_out - decompressed;
  142. // Clean up and return
  143. if (fileEnded)
  144. {
  145. inflateEnd(inflateState);
  146. vstd::clear_pointer(inflateState);
  147. }
  148. return decompressed;
  149. }
  150. bool CCompressedStream::getNextBlock()
  151. {
  152. if (!inflateState)
  153. return false;
  154. if (inflateReset(inflateState) < 0)
  155. return false;
  156. reset();
  157. return true;
  158. }