CZipLoader.cpp 6.3 KB

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