CZipLoader.cpp 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227
  1. /*
  2. * CZipLoader.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 "CZipLoader.h"
  12. #include "../ScopeGuard.h"
  13. VCMI_LIB_NAMESPACE_BEGIN
  14. CZipStream::CZipStream(const std::shared_ptr<CIOApi> & api, const boost::filesystem::path & archive, unz64_file_pos filepos)
  15. {
  16. zlib_filefunc64_def zlibApi;
  17. zlibApi = api->getApiStructure();
  18. file = unzOpen2_64(archive.c_str(), &zlibApi);
  19. unzGoToFilePos64(file, &filepos);
  20. unzOpenCurrentFile(file);
  21. }
  22. CZipStream::~CZipStream()
  23. {
  24. unzCloseCurrentFile(file);
  25. unzClose(file);
  26. }
  27. si64 CZipStream::readMore(ui8 * data, si64 size)
  28. {
  29. return unzReadCurrentFile(file, data, static_cast<unsigned int>(size));
  30. }
  31. si64 CZipStream::getSize()
  32. {
  33. unz_file_info64 info;
  34. unzGetCurrentFileInfo64 (file, &info, nullptr, 0, nullptr, 0, nullptr, 0);
  35. return info.uncompressed_size;
  36. }
  37. ui32 CZipStream::calculateCRC32()
  38. {
  39. unz_file_info64 info;
  40. unzGetCurrentFileInfo64 (file, &info, nullptr, 0, nullptr, 0, nullptr, 0);
  41. return info.crc;
  42. }
  43. ///CZipLoader
  44. CZipLoader::CZipLoader(const std::string & mountPoint, const boost::filesystem::path & archive, std::shared_ptr<CIOApi> api):
  45. ioApi(std::move(api)),
  46. zlibApi(ioApi->getApiStructure()),
  47. archiveName(archive),
  48. mountPoint(mountPoint),
  49. files(listFiles(mountPoint, archive))
  50. {
  51. logGlobal->trace("Zip archive loaded, %d files found", files.size());
  52. }
  53. std::unordered_map<ResourceID, unz64_file_pos> CZipLoader::listFiles(const std::string & mountPoint, const boost::filesystem::path & archive)
  54. {
  55. std::unordered_map<ResourceID, unz64_file_pos> ret;
  56. unzFile file = unzOpen2_64(archive.c_str(), &zlibApi);
  57. if(file == nullptr)
  58. logGlobal->error("%s failed to open", archive.string());
  59. if (unzGoToFirstFile(file) == UNZ_OK)
  60. {
  61. do
  62. {
  63. unz_file_info64 info;
  64. std::vector<char> filename;
  65. // Fill unz_file_info structure with current file info
  66. unzGetCurrentFileInfo64 (file, &info, nullptr, 0, nullptr, 0, nullptr, 0);
  67. filename.resize(info.size_filename);
  68. // Get name of current file. Contrary to docs "info" parameter can't be null
  69. unzGetCurrentFileInfo64(file, &info, filename.data(), static_cast<uLong>(filename.size()), nullptr, 0, nullptr, 0);
  70. std::string filenameString(filename.data(), filename.size());
  71. unzGetFilePos64(file, &ret[ResourceID(mountPoint + filenameString)]);
  72. }
  73. while (unzGoToNextFile(file) == UNZ_OK);
  74. }
  75. unzClose(file);
  76. return ret;
  77. }
  78. std::unique_ptr<CInputStream> CZipLoader::load(const ResourceID & resourceName) const
  79. {
  80. return std::unique_ptr<CInputStream>(new CZipStream(ioApi, archiveName, files.at(resourceName)));
  81. }
  82. bool CZipLoader::existsResource(const ResourceID & resourceName) const
  83. {
  84. return files.count(resourceName) != 0;
  85. }
  86. std::string CZipLoader::getMountPoint() const
  87. {
  88. return mountPoint;
  89. }
  90. std::unordered_set<ResourceID> CZipLoader::getFilteredFiles(std::function<bool(const ResourceID &)> filter) const
  91. {
  92. std::unordered_set<ResourceID> foundID;
  93. for(const auto & file : files)
  94. {
  95. if (filter(file.first))
  96. foundID.insert(file.first);
  97. }
  98. return foundID;
  99. }
  100. /// extracts currently selected file from zip into stream "where"
  101. static bool extractCurrent(unzFile file, std::ostream & where)
  102. {
  103. std::array<char, 8 * 1024> buffer{};
  104. unzOpenCurrentFile(file);
  105. while(true)
  106. {
  107. int readSize = unzReadCurrentFile(file, buffer.data(), static_cast<unsigned int>(buffer.size()));
  108. if (readSize < 0) // error
  109. break;
  110. if (readSize == 0) // end-of-file. Also performs CRC check
  111. return unzCloseCurrentFile(file) == UNZ_OK;
  112. if (readSize > 0) // successful read
  113. {
  114. where.write(buffer.data(), readSize);
  115. if (!where.good())
  116. break;
  117. }
  118. }
  119. // extraction failed. Close file and exit
  120. unzCloseCurrentFile(file);
  121. return false;
  122. }
  123. std::vector<std::string> ZipArchive::listFiles(const boost::filesystem::path & filename)
  124. {
  125. std::vector<std::string> ret;
  126. CDefaultIOApi zipAPI;
  127. auto zipStructure = zipAPI.getApiStructure();
  128. unzFile file = unzOpen2_64(filename.c_str(), &zipStructure);
  129. if (unzGoToFirstFile(file) == UNZ_OK)
  130. {
  131. do
  132. {
  133. unz_file_info64 info;
  134. std::vector<char> zipFilename;
  135. unzGetCurrentFileInfo64 (file, &info, nullptr, 0, nullptr, 0, nullptr, 0);
  136. zipFilename.resize(info.size_filename);
  137. // Get name of current file. Contrary to docs "info" parameter can't be null
  138. unzGetCurrentFileInfo64(file, &info, zipFilename.data(), static_cast<uLong>(zipFilename.size()), nullptr, 0, nullptr, 0);
  139. ret.emplace_back(zipFilename.data(), zipFilename.size());
  140. }
  141. while (unzGoToNextFile(file) == UNZ_OK);
  142. }
  143. unzClose(file);
  144. return ret;
  145. }
  146. bool ZipArchive::extract(const boost::filesystem::path & from, const boost::filesystem::path & where)
  147. {
  148. // Note: may not be fast enough for large archives (should NOT happen with mods)
  149. // because locating each file by name may be slow. Unlikely slower than decompression though
  150. return extract(from, where, listFiles(from));
  151. }
  152. bool ZipArchive::extract(const boost::filesystem::path & from, const boost::filesystem::path & where, const std::vector<std::string> & what)
  153. {
  154. CDefaultIOApi zipAPI;
  155. auto zipStructure = zipAPI.getApiStructure();
  156. unzFile archive = unzOpen2_64(from.c_str(), &zipStructure);
  157. auto onExit = vstd::makeScopeGuard([&]()
  158. {
  159. unzClose(archive);
  160. });
  161. for (const std::string & file : what)
  162. {
  163. if (unzLocateFile(archive, file.c_str(), 1) != UNZ_OK)
  164. return false;
  165. const boost::filesystem::path fullName = where / file;
  166. const boost::filesystem::path fullPath = fullName.parent_path();
  167. boost::filesystem::create_directories(fullPath);
  168. // directory. No file to extract
  169. // TODO: better way to detect directory? Probably check return value of unzOpenCurrentFile?
  170. if (boost::algorithm::ends_with(file, "/"))
  171. continue;
  172. std::fstream destFile(fullName.c_str(), std::ios::out | std::ios::binary);
  173. if (!destFile.good())
  174. return false;
  175. if (!extractCurrent(archive, destFile))
  176. return false;
  177. }
  178. return true;
  179. }
  180. VCMI_LIB_NAMESPACE_END