123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259 |
- /*
- * 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"
- #include "../texts/TextOperations.h"
- VCMI_LIB_NAMESPACE_BEGIN
- CZipStream::CZipStream(const std::shared_ptr<CIOApi> & 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<unsigned int>(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<CIOApi> 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<ResourcePath, unz64_file_pos> CZipLoader::listFiles(const std::string & mountPoint, const boost::filesystem::path & archive)
- {
- std::unordered_map<ResourcePath, unz64_file_pos> 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<char> 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<uLong>(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<CInputStream> CZipLoader::load(const ResourcePath & resourceName) const
- {
- return std::unique_ptr<CInputStream>(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<ResourcePath> CZipLoader::getFilteredFiles(std::function<bool(const ResourcePath &)> filter) const
- {
- std::unordered_set<ResourcePath> foundID;
- for(const auto & file : files)
- {
- if (filter(file.first))
- foundID.insert(file.first);
- }
- return foundID;
- }
- std::string CZipLoader::getFullFileURI(const ResourcePath& resourceName) const
- {
- auto relativePath = TextOperations::Utf8TofilesystemPath(resourceName.getName());
- auto path = boost::filesystem::canonical(archiveName) / relativePath;
- return TextOperations::filesystemPathToUtf8(path);
- }
- std::time_t CZipLoader::getLastWriteTime(const ResourcePath& resourceName) const
- {
- auto path = boost::filesystem::canonical(archiveName);
- return boost::filesystem::last_write_time(path);
- }
- /// extracts currently selected file from zip into stream "where"
- static bool extractCurrent(unzFile file, std::ostream & where)
- {
- std::array<char, 8 * 1024> buffer{};
- unzOpenCurrentFile(file);
- while(true)
- {
- int readSize = unzReadCurrentFile(file, buffer.data(), static_cast<unsigned int>(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<std::string> ZipArchive::listFiles()
- {
- std::vector<std::string> ret;
- int result = unzGoToFirstFile(archive);
- if (result == UNZ_OK)
- {
- do
- {
- unz_file_info64 info;
- std::vector<char> zipFilename;
- unzGetCurrentFileInfo64 (archive, &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(archive, &info, zipFilename.data(), static_cast<uLong>(zipFilename.size()), nullptr, 0, nullptr, 0);
- ret.emplace_back(zipFilename.data(), zipFilename.size());
- result = unzGoToNextFile(archive);
- }
- while (result == UNZ_OK);
- }
- return ret;
- }
- ZipArchive::ZipArchive(const boost::filesystem::path & from)
- {
- CDefaultIOApi zipAPI;
- #if MINIZIP_NEEDS_32BIT_FUNCS
- auto zipStructure = zipAPI.getApiStructure32();
- archive = unzOpen2(from.c_str(), &zipStructure);
- #else
- auto zipStructure = zipAPI.getApiStructure();
- archive = unzOpen2_64(from.c_str(), &zipStructure);
- #endif
- if (archive == nullptr)
- throw std::runtime_error("Failed to open file '" + from.string());
- }
- ZipArchive::~ZipArchive()
- {
- unzClose(archive);
- }
- bool ZipArchive::extract(const boost::filesystem::path & where, const std::vector<std::string> & what)
- {
- for (const std::string & file : what)
- if (!extract(where, file))
- return false;
- return true;
- }
- bool ZipArchive::extract(const boost::filesystem::path & where, const std::string & file)
- {
- 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, "/"))
- return true;
- std::fstream destFile(fullName.c_str(), std::ios::out | std::ios::binary);
- if (!destFile.good())
- {
- #ifdef VCMI_WINDOWS
- if (fullName.size() < 260)
- logGlobal->error("Failed to open file '%s'", fullName.c_str());
- else
- logGlobal->error("Failed to open file with long path '%s' (%d characters)", fullName.c_str(), fullName.size());
- #else
- logGlobal->error("Failed to open file '%s'", fullName.c_str());
- #endif
- return false;
- }
- if (!extractCurrent(archive, destFile))
- return false;
- return true;
- }
- VCMI_LIB_NAMESPACE_END
|