/* * CZipLoader.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 "CZipLoader.h" #include "../ScopeGuard.h" VCMI_LIB_NAMESPACE_BEGIN CZipStream::CZipStream(const std::shared_ptr & api, const boost::filesystem::path & archive, unz64_file_pos filepos) { zlib_filefunc64_def zlibApi; zlibApi = api->getApiStructure(); file = unzOpen2_64(archive.c_str(), &zlibApi); unzGoToFilePos64(file, &filepos); unzOpenCurrentFile(file); } CZipStream::~CZipStream() { unzCloseCurrentFile(file); unzClose(file); } si64 CZipStream::readMore(ui8 * data, si64 size) { return unzReadCurrentFile(file, data, static_cast(size)); } si64 CZipStream::getSize() { unz_file_info64 info; unzGetCurrentFileInfo64 (file, &info, nullptr, 0, nullptr, 0, nullptr, 0); return info.uncompressed_size; } ui32 CZipStream::calculateCRC32() { unz_file_info64 info; unzGetCurrentFileInfo64 (file, &info, nullptr, 0, nullptr, 0, nullptr, 0); return info.crc; } ///CZipLoader CZipLoader::CZipLoader(const std::string & mountPoint, const boost::filesystem::path & archive, std::shared_ptr api): ioApi(std::move(api)), zlibApi(ioApi->getApiStructure()), archiveName(archive), mountPoint(mountPoint), files(listFiles(mountPoint, archive)) { logGlobal->trace("Zip archive loaded, %d files found", files.size()); } std::unordered_map CZipLoader::listFiles(const std::string & mountPoint, const boost::filesystem::path & archive) { std::unordered_map ret; unzFile file = unzOpen2_64(archive.c_str(), &zlibApi); if(file == nullptr) logGlobal->error("%s failed to open", archive.string()); if (unzGoToFirstFile(file) == UNZ_OK) { do { unz_file_info64 info; std::vector filename; // Fill unz_file_info structure with current file info unzGetCurrentFileInfo64 (file, &info, nullptr, 0, nullptr, 0, nullptr, 0); filename.resize(info.size_filename); // Get name of current file. Contrary to docs "info" parameter can't be null unzGetCurrentFileInfo64(file, &info, filename.data(), static_cast(filename.size()), nullptr, 0, nullptr, 0); std::string filenameString(filename.data(), filename.size()); unzGetFilePos64(file, &ret[ResourcePath(mountPoint + filenameString)]); } while (unzGoToNextFile(file) == UNZ_OK); } unzClose(file); return ret; } std::unique_ptr CZipLoader::load(const ResourcePath & resourceName) const { return std::unique_ptr(new CZipStream(ioApi, archiveName, files.at(resourceName))); } bool CZipLoader::existsResource(const ResourcePath & resourceName) const { return files.count(resourceName) != 0; } std::string CZipLoader::getMountPoint() const { return mountPoint; } std::unordered_set CZipLoader::getFilteredFiles(std::function filter) const { std::unordered_set foundID; for(const auto & file : files) { if (filter(file.first)) foundID.insert(file.first); } return foundID; } /// extracts currently selected file from zip into stream "where" static bool extractCurrent(unzFile file, std::ostream & where) { std::array buffer{}; unzOpenCurrentFile(file); while(true) { int readSize = unzReadCurrentFile(file, buffer.data(), static_cast(buffer.size())); if (readSize < 0) // error break; if (readSize == 0) // end-of-file. Also performs CRC check return unzCloseCurrentFile(file) == UNZ_OK; if (readSize > 0) // successful read { where.write(buffer.data(), readSize); if (!where.good()) break; } } // extraction failed. Close file and exit unzCloseCurrentFile(file); return false; } std::vector ZipArchive::listFiles(const boost::filesystem::path & filename) { std::vector ret; CDefaultIOApi zipAPI; auto zipStructure = zipAPI.getApiStructure(); unzFile file = unzOpen2_64(filename.c_str(), &zipStructure); if (file == nullptr) { logGlobal->error("Failed to open file '%s'! Unable to list files!", filename.string()); return {}; } int result = unzGoToFirstFile(file); if (result == UNZ_OK) { do { unz_file_info64 info; std::vector zipFilename; unzGetCurrentFileInfo64 (file, &info, nullptr, 0, nullptr, 0, nullptr, 0); zipFilename.resize(info.size_filename); // Get name of current file. Contrary to docs "info" parameter can't be null unzGetCurrentFileInfo64(file, &info, zipFilename.data(), static_cast(zipFilename.size()), nullptr, 0, nullptr, 0); ret.emplace_back(zipFilename.data(), zipFilename.size()); result = unzGoToNextFile(file); } while (result == UNZ_OK); if (result != UNZ_OK && result != UNZ_END_OF_LIST_OF_FILE) { logGlobal->error("Failed to list file from '%s'! Error code %d", filename.string(), result); } } else { logGlobal->error("Failed to list files from '%s'! Error code %d", filename.string(), result); } unzClose(file); return ret; } bool ZipArchive::extract(const boost::filesystem::path & from, const boost::filesystem::path & where) { // Note: may not be fast enough for large archives (should NOT happen with mods) // because locating each file by name may be slow. Unlikely slower than decompression though return extract(from, where, listFiles(from)); } bool ZipArchive::extract(const boost::filesystem::path & from, const boost::filesystem::path & where, const std::vector & what) { CDefaultIOApi zipAPI; auto zipStructure = zipAPI.getApiStructure(); unzFile archive = unzOpen2_64(from.c_str(), &zipStructure); auto onExit = vstd::makeScopeGuard([&]() { unzClose(archive); }); for (const std::string & file : what) { if (unzLocateFile(archive, file.c_str(), 1) != UNZ_OK) return false; const boost::filesystem::path fullName = where / file; const boost::filesystem::path fullPath = fullName.parent_path(); boost::filesystem::create_directories(fullPath); // directory. No file to extract // TODO: better way to detect directory? Probably check return value of unzOpenCurrentFile? if (boost::algorithm::ends_with(file, "/")) continue; std::fstream destFile(fullName.c_str(), std::ios::out | std::ios::binary); if (!destFile.good()) return false; if (!extractCurrent(archive, destFile)) return false; } return true; } VCMI_LIB_NAMESPACE_END