Something went wrong. Try again.
A fork of https://github.com/crosspoint-reader/crosspoint-reader
Something went wrong. Try again.
4.5 kB · 144 lines
C
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145#pragma once#include <HalStorage.h>
#include <deque>#include <string>#include <string_view>#include <unordered_map>
class ZipFile { public: struct FileStatSlim { uint16_t method; // Compression method uint32_t compressedSize; // Compressed size uint32_t uncompressedSize; // Uncompressed size uint32_t localHeaderOffset; // Offset of local file header };
struct ZipDetails { uint32_t centralDirOffset; uint16_t totalEntries; bool isSet; };
// Target for batch uncompressed size lookup (sorted by hash, then len) struct SizeTarget { uint64_t hash; // FNV-1a 64-bit hash of normalized path uint16_t len; // Length of path for collision reduction uint16_t index; // Caller's index (e.g. spine index) };
// FNV-1a 64-bit hash computed from char buffer (no std::string allocation) static uint64_t fnvHash64(const char* s, size_t len) { uint64_t hash = 14695981039346656037ull; for (size_t i = 0; i < len; i++) { hash ^= static_cast<uint8_t>(s[i]); hash *= 1099511628211ull; } return hash; }
private: const std::string& filePath; HalFile file; ZipDetails zipDetails = {0, 0, false}; std::unordered_map<std::string, FileStatSlim> fileStatSlimCache;
// Cursor for sequential central-dir scanning optimization uint32_t lastCentralDirPos = 0; bool lastCentralDirPosValid = false;
bool loadFileStatSlim(const char* filename, FileStatSlim* fileStat); long getDataOffset(const FileStatSlim& fileStat); bool loadZipDetails();
public: explicit ZipFile(const std::string& filePath) : filePath(filePath) {} ~ZipFile() = default; // Zip file can be opened and closed by hand in order to allow for quick calculation of inflated file size // It is NOT recommended to pre-open it for any kind of inflation due to memory constraints bool isOpen() const { return !!file; } bool open(); bool close(); bool loadAllFileStatSlims(); bool getInflatedFileSize(const char* filename, size_t* size); // Batch lookup: scan ZIP central dir once and fill sizes for matching targets. // targets must be sorted by (hash, len). sizes[target.index] receives uncompressedSize. // Returns number of targets matched. int fillUncompressedSizes(std::deque<SizeTarget>& targets, std::deque<uint32_t>& sizes); // Due to the memory required to run each of these, it is recommended to not preopen the zip file for multiple // These functions will open and close the zip as needed uint8_t* readFileToMemory(const char* filename, size_t* size = nullptr, bool trailingNullByte = false); // allowEarlyStop: a short write from `out` is treated as the sink asking to // stop (returns true) instead of a write failure — used by header probes // that only need the first bytes of an entry. bool readFileToStream(const char* filename, Print& out, size_t chunkSize, bool allowEarlyStop = false);
template <typename F> bool enumerateFilePaths(F&& callback) { if (!fileStatSlimCache.empty()) { for (const auto& entry : fileStatSlimCache) { callback(std::string_view{entry.first}); } return true; }
return enumerateFileEntries([&callback](std::string_view path, uint32_t, uint32_t) { callback(path); }); }
// Callback receives (path, crc32, compressedSize) for each central-directory // entry. Always scans the central directory: the slim-stat cache does not // hold CRCs. template <typename F> bool enumerateFileEntries(F&& callback) { const bool wasOpen = isOpen(); if (!wasOpen && !open()) { return false; }
if (!loadZipDetails()) { if (!wasOpen) { close(); } return false; }
file.seek(zipDetails.centralDirOffset);
uint32_t sig; char itemName[256];
while (file.available()) { file.read(&sig, 4); if (sig != 0x02014b50) { break; }
file.seekCur(12); uint32_t crc32, compressedSize; file.read(&crc32, 4); file.read(&compressedSize, 4); file.seekCur(4); uint16_t nameLen, m, k; file.read(&nameLen, 2); file.read(&m, 2); file.read(&k, 2); file.seekCur(12);
if (nameLen < sizeof(itemName)) { file.read(itemName, nameLen); itemName[nameLen] = '\0'; callback(std::string_view{itemName, nameLen}, crc32, compressedSize); } else { file.seekCur(nameLen); }
file.seekCur(m + k); }
if (!wasOpen) { close(); } return true; }};