// Tencent is pleased to support the open source community by making ncnn available. // // Copyright (C) 2021 THL A29 Limited, a Tencent company. All rights reserved. // // Licensed under the BSD 3-Clause License (the "License"); you may not use this file except // in compliance with the License. You may obtain a copy of the License at // // https://opensource.org/licenses/BSD-3-Clause // // Unless required by applicable law or agreed to in writing, software distributed // under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR // CONDITIONS OF ANY KIND, either express or implied. See the License for the // specific language governing permissions and limitations under the License. #include "storezip.h" #include #include #include #include #include namespace pnnx { // https://stackoverflow.com/questions/1537964/visual-c-equivalent-of-gccs-attribute-packed #ifdef _MSC_VER #define PACK(__Declaration__) __pragma(pack(push, 1)) __Declaration__ __pragma(pack(pop)) #else #define PACK(__Declaration__) __Declaration__ __attribute__((__packed__)) #endif PACK(struct local_file_header { uint16_t version; uint16_t flag; uint16_t compression; uint16_t last_modify_time; uint16_t last_modify_date; uint32_t crc32; uint32_t compressed_size; uint32_t uncompressed_size; uint16_t file_name_length; uint16_t extra_field_length; }); PACK(struct zip64_extended_extra_field { uint64_t uncompressed_size; uint64_t compressed_size; uint64_t lfh_offset; uint32_t disk_number; }); PACK(struct central_directory_file_header { uint16_t version_made; uint16_t version; uint16_t flag; uint16_t compression; uint16_t last_modify_time; uint16_t last_modify_date; uint32_t crc32; uint32_t compressed_size; uint32_t uncompressed_size; uint16_t file_name_length; uint16_t extra_field_length; uint16_t file_comment_length; uint16_t start_disk; uint16_t internal_file_attrs; uint32_t external_file_attrs; uint32_t lfh_offset; }); PACK(struct zip64_end_of_central_directory_record { uint64_t size_of_eocd64_m12; uint16_t version_made_by; uint16_t version_min_required; uint32_t disk_number; uint32_t start_disk; uint64_t cd_records; uint64_t total_cd_records; uint64_t cd_size; uint64_t cd_offset; }); PACK(struct zip64_end_of_central_directory_locator { uint32_t eocdr64_disk_number; uint64_t eocdr64_offset; uint32_t disk_count; }); PACK(struct end_of_central_directory_record { uint16_t disk_number; uint16_t start_disk; uint16_t cd_records; uint16_t total_cd_records; uint32_t cd_size; uint32_t cd_offset; uint16_t comment_length; }); static uint32_t CRC32_TABLE[256]; static void CRC32_TABLE_INIT() { for (int i = 0; i < 256; i++) { uint32_t c = i; for (int j = 0; j < 8; j++) { if (c & 1) c = (c >> 1) ^ 0xedb88320; else c >>= 1; } CRC32_TABLE[i] = c; } } static uint32_t CRC32(uint32_t x, unsigned char ch) { return (x >> 8) ^ CRC32_TABLE[(x ^ ch) & 0xff]; } static uint32_t CRC32_buffer(const unsigned char* data, uint64_t len) { uint32_t x = 0xffffffff; for (uint64_t i = 0; i < len; i++) x = CRC32(x, data[i]); return x ^ 0xffffffff; } StoreZipReader::StoreZipReader() { fp = 0; } StoreZipReader::~StoreZipReader() { close(); } int StoreZipReader::open(const std::string& path) { close(); fp = fopen(path.c_str(), "rb"); if (!fp) { fprintf(stderr, "open failed\n"); return -1; } while (!feof(fp)) { // peek signature uint32_t signature; int nread = fread((char*)&signature, sizeof(signature), 1, fp); if (nread != 1) break; // fprintf(stderr, "signature = %x\n", signature); if (signature == 0x04034b50) { local_file_header lfh; fread((char*)&lfh, sizeof(lfh), 1, fp); if (lfh.flag & 0x08) { fprintf(stderr, "zip file contains data descriptor, this is not supported yet\n"); return -1; } if (lfh.compression != 0 || lfh.compressed_size != lfh.uncompressed_size) { fprintf(stderr, "not stored zip file %d %d\n", lfh.compressed_size, lfh.uncompressed_size); return -1; } // file name std::string name; name.resize(lfh.file_name_length); fread((char*)name.data(), name.size(), 1, fp); uint64_t compressed_size = lfh.compressed_size; uint64_t uncompressed_size = lfh.uncompressed_size; if (compressed_size == 0xffffffff && uncompressed_size == 0xffffffff) { uint16_t extra_offset = 0; while (extra_offset < lfh.extra_field_length) { uint16_t extra_id; uint16_t extra_size; fread((char*)&extra_id, sizeof(extra_id), 1, fp); fread((char*)&extra_size, sizeof(extra_size), 1, fp); if (extra_id != 0x0001) { // skip this extra field block fseek(fp, extra_size - 4, SEEK_CUR); extra_offset += extra_size; continue; } // zip64 extra field zip64_extended_extra_field zip64_eef; fread((char*)&zip64_eef, sizeof(zip64_eef), 1, fp); compressed_size = zip64_eef.compressed_size; uncompressed_size = zip64_eef.uncompressed_size; // skip remaining extra field blocks fseek(fp, lfh.extra_field_length - extra_offset - 4 - sizeof(zip64_eef), SEEK_CUR); break; } } else { // skip extra field fseek(fp, lfh.extra_field_length, SEEK_CUR); } StoreZipMeta fm; fm.offset = ftell(fp); fm.size = compressed_size; filemetas[name] = fm; // fprintf(stderr, "%s = %d %d\n", name.c_str(), fm.offset, fm.size); fseek(fp, compressed_size, SEEK_CUR); } else if (signature == 0x02014b50) { central_directory_file_header cdfh; fread((char*)&cdfh, sizeof(cdfh), 1, fp); // skip file name fseek(fp, cdfh.file_name_length, SEEK_CUR); // skip extra field fseek(fp, cdfh.extra_field_length, SEEK_CUR); // skip file comment fseek(fp, cdfh.file_comment_length, SEEK_CUR); } else if (signature == 0x06054b50) { end_of_central_directory_record eocdr; fread((char*)&eocdr, sizeof(eocdr), 1, fp); // skip comment fseek(fp, eocdr.comment_length, SEEK_CUR); } else if (signature == 0x06064b50) { zip64_end_of_central_directory_record eocdr64; fread((char*)&eocdr64, sizeof(eocdr64), 1, fp); // skip comment fseek(fp, eocdr64.size_of_eocd64_m12 - 44, SEEK_CUR); } else if (signature == 0x07064b50) { zip64_end_of_central_directory_locator eocdl64; fread((char*)&eocdl64, sizeof(eocdl64), 1, fp); } else { fprintf(stderr, "unsupported signature %x\n", signature); return -1; } } return 0; } std::vector StoreZipReader::get_names() const { std::vector names; for (std::map::const_iterator it = filemetas.begin(); it != filemetas.end(); ++it) { names.push_back(it->first); } return names; } uint64_t StoreZipReader::get_file_size(const std::string& name) const { if (filemetas.find(name) == filemetas.end()) { fprintf(stderr, "no such file %s\n", name.c_str()); return 0; } return filemetas.at(name).size; } int StoreZipReader::read_file(const std::string& name, char* data) { if (filemetas.find(name) == filemetas.end()) { fprintf(stderr, "no such file %s\n", name.c_str()); return -1; } uint64_t offset = filemetas[name].offset; uint64_t size = filemetas[name].size; fseek(fp, offset, SEEK_SET); fread(data, size, 1, fp); return 0; } int StoreZipReader::close() { if (!fp) return 0; fclose(fp); fp = 0; return 0; } StoreZipWriter::StoreZipWriter() { fp = 0; CRC32_TABLE_INIT(); } StoreZipWriter::~StoreZipWriter() { close(); } int StoreZipWriter::open(const std::string& path) { close(); fp = fopen(path.c_str(), "wb"); if (!fp) { fprintf(stderr, "open failed\n"); return -1; } return 0; } int StoreZipWriter::write_file(const std::string& name, const char* data, uint64_t size) { long offset = ftell(fp); uint32_t signature = 0x04034b50; fwrite((char*)&signature, sizeof(signature), 1, fp); uint32_t crc32 = CRC32_buffer((const unsigned char*)data, size); local_file_header lfh; lfh.version = 0; lfh.flag = 0; lfh.compression = 0; lfh.last_modify_time = 0; lfh.last_modify_date = 0; lfh.crc32 = crc32; lfh.compressed_size = 0xffffffff; lfh.uncompressed_size = 0xffffffff; lfh.file_name_length = name.size(); // zip64 extra field zip64_extended_extra_field zip64_eef; zip64_eef.uncompressed_size = size; zip64_eef.compressed_size = size; zip64_eef.lfh_offset = 0; zip64_eef.disk_number = 0; uint16_t extra_id = 0x0001; uint16_t extra_size = sizeof(zip64_eef); lfh.extra_field_length = sizeof(extra_id) + sizeof(extra_size) + sizeof(zip64_eef); fwrite((char*)&lfh, sizeof(lfh), 1, fp); fwrite((char*)name.c_str(), name.size(), 1, fp); fwrite((char*)&extra_id, sizeof(extra_id), 1, fp); fwrite((char*)&extra_size, sizeof(extra_size), 1, fp); fwrite((char*)&zip64_eef, sizeof(zip64_eef), 1, fp); fwrite(data, size, 1, fp); StoreZipMeta szm; szm.name = name; szm.lfh_offset = offset; szm.crc32 = crc32; szm.size = size; filemetas.push_back(szm); return 0; } int StoreZipWriter::close() { if (!fp) return 0; long offset = ftell(fp); for (const StoreZipMeta& szm : filemetas) { uint32_t signature = 0x02014b50; fwrite((char*)&signature, sizeof(signature), 1, fp); central_directory_file_header cdfh; cdfh.version_made = 0; cdfh.version = 0; cdfh.flag = 0; cdfh.compression = 0; cdfh.last_modify_time = 0; cdfh.last_modify_date = 0; cdfh.crc32 = szm.crc32; cdfh.compressed_size = 0xffffffff; cdfh.uncompressed_size = 0xffffffff; cdfh.file_name_length = szm.name.size(); cdfh.file_comment_length = 0; cdfh.start_disk = 0xffff; cdfh.internal_file_attrs = 0; cdfh.external_file_attrs = 0; cdfh.lfh_offset = 0xffffffff; // zip64 extra field zip64_extended_extra_field zip64_eef; zip64_eef.uncompressed_size = szm.size; zip64_eef.compressed_size = szm.size; zip64_eef.lfh_offset = szm.lfh_offset; zip64_eef.disk_number = 0; uint16_t extra_id = 0x0001; uint16_t extra_size = sizeof(zip64_eef); cdfh.extra_field_length = sizeof(extra_id) + sizeof(extra_size) + sizeof(zip64_eef); fwrite((char*)&cdfh, sizeof(cdfh), 1, fp); fwrite((char*)szm.name.c_str(), szm.name.size(), 1, fp); fwrite((char*)&extra_id, sizeof(extra_id), 1, fp); fwrite((char*)&extra_size, sizeof(extra_size), 1, fp); fwrite((char*)&zip64_eef, sizeof(zip64_eef), 1, fp); } long offset2 = ftell(fp); { uint32_t signature = 0x06064b50; fwrite((char*)&signature, sizeof(signature), 1, fp); zip64_end_of_central_directory_record eocdr64; eocdr64.size_of_eocd64_m12 = sizeof(eocdr64) - 8; eocdr64.version_made_by = 0; eocdr64.version_min_required = 0; eocdr64.disk_number = 0; eocdr64.start_disk = 0; eocdr64.cd_records = filemetas.size(); eocdr64.total_cd_records = filemetas.size(); eocdr64.cd_size = offset2 - offset; eocdr64.cd_offset = offset; fwrite((char*)&eocdr64, sizeof(eocdr64), 1, fp); } { uint32_t signature = 0x07064b50; fwrite((char*)&signature, sizeof(signature), 1, fp); zip64_end_of_central_directory_locator eocdl64; eocdl64.eocdr64_disk_number = 0; eocdl64.eocdr64_offset = offset2; eocdl64.disk_count = 1; fwrite((char*)&eocdl64, sizeof(eocdl64), 1, fp); } { uint32_t signature = 0x06054b50; fwrite((char*)&signature, sizeof(signature), 1, fp); end_of_central_directory_record eocdr; eocdr.disk_number = 0xffff; eocdr.start_disk = 0xffff; eocdr.cd_records = 0xffff; eocdr.total_cd_records = 0xffff; eocdr.cd_size = 0xffffffff; eocdr.cd_offset = 0xffffffff; eocdr.comment_length = 0; fwrite((char*)&eocdr, sizeof(eocdr), 1, fp); } fclose(fp); fp = 0; return 0; } } // namespace pnnx #if 0 int main() { using namespace pnnx; { uint64_t len = 1*1024*1024*1024; // uint64_t len = 1*1024*1024; char* data1g = new char[len]; StoreZipWriter szw; szw.open("szw.zip"); szw.write_file("a.py", data1g, len); szw.write_file("b.param", data1g, 44); szw.write_file("c.bin", data1g, len); szw.write_file("d.txt", data1g, len); szw.write_file("e.jpg", data1g, len); szw.write_file("f.png", data1g, len); szw.close(); delete[] data1g; } { StoreZipReader sz; sz.open("szw.zip"); std::vector names = sz.get_names(); for (size_t i = 0; i < names.size(); i++) { uint64_t size = sz.get_file_size(names[i]); fprintf(stderr, "%s %lu\n", names[i].c_str(), size); } sz.close(); } return 0; } #endif