2025-12-03 22:00:29 +11:00
|
|
|
#pragma once
|
2025-12-30 15:09:30 +10:00
|
|
|
#include <SdFat.h>
|
2025-12-08 00:39:17 +11:00
|
|
|
|
2025-12-03 22:00:29 +11:00
|
|
|
#include <string>
|
2025-12-29 20:17:29 +10:00
|
|
|
#include <unordered_map>
|
2026-01-21 19:55:24 -08:00
|
|
|
#include <vector>
|
2025-12-08 00:39:17 +11:00
|
|
|
|
2025-12-03 22:00:29 +11:00
|
|
|
class ZipFile {
|
2025-12-29 20:17:29 +10:00
|
|
|
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;
|
|
|
|
|
};
|
|
|
|
|
|
2026-01-21 19:55:24 -08:00
|
|
|
// 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;
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-29 20:17:29 +10:00
|
|
|
private:
|
|
|
|
|
const std::string& filePath;
|
2025-12-30 15:09:30 +10:00
|
|
|
FsFile file;
|
2025-12-29 20:17:29 +10:00
|
|
|
ZipDetails zipDetails = {0, 0, false};
|
|
|
|
|
std::unordered_map<std::string, FileStatSlim> fileStatSlimCache;
|
|
|
|
|
|
2026-01-20 23:35:54 -08:00
|
|
|
// Cursor for sequential central-dir scanning optimization
|
|
|
|
|
uint32_t lastCentralDirPos = 0;
|
|
|
|
|
bool lastCentralDirPosValid = false;
|
|
|
|
|
|
2025-12-29 20:17:29 +10:00
|
|
|
bool loadFileStatSlim(const char* filename, FileStatSlim* fileStat);
|
|
|
|
|
long getDataOffset(const FileStatSlim& fileStat);
|
|
|
|
|
bool loadZipDetails();
|
2025-12-03 22:00:29 +11:00
|
|
|
|
|
|
|
|
public:
|
2025-12-29 20:17:29 +10:00
|
|
|
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);
|
2026-01-21 19:55:24 -08:00
|
|
|
// 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::vector<SizeTarget>& targets, std::vector<uint32_t>& sizes);
|
2025-12-29 20:17:29 +10:00
|
|
|
// 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);
|
|
|
|
|
bool readFileToStream(const char* filename, Print& out, size_t chunkSize);
|
2025-12-03 22:00:29 +11:00
|
|
|
};
|