diff options
Diffstat (limited to 'src/lib/util/unzip.cpp')
-rw-r--r-- | src/lib/util/unzip.cpp | 922 |
1 files changed, 494 insertions, 428 deletions
diff --git a/src/lib/util/unzip.cpp b/src/lib/util/unzip.cpp index 876d8ae90c5..2751f83ffb6 100644 --- a/src/lib/util/unzip.cpp +++ b/src/lib/util/unzip.cpp @@ -1,5 +1,5 @@ // license:BSD-3-Clause -// copyright-holders:Aaron Giles +// copyright-holders:Aaron Giles, Vas Crabb /*************************************************************************** unzip.c @@ -8,22 +8,30 @@ ***************************************************************************/ -#include "osdcore.h" #include "unzip.h" -#include <ctype.h> -#include <stdlib.h> -#include <zlib.h> +#include "corestr.h" +#include "osdcore.h" + +#include <algorithm> +#include <array> +#include <cassert> +#include <cstring> +#include <cstdlib> +#include <mutex> +#include <utility> +#include <vector> + +#include <zlib.h> +namespace util { +namespace { /*************************************************************************** CONSTANTS ***************************************************************************/ -/* number of open files to cache */ -#define ZIP_CACHE_SIZE 8 - /* offsets in end of central directory structure */ #define ZIPESIG 0x00 #define ZIPEDSK 0x04 @@ -67,30 +75,226 @@ #define ZIPCRC 0x0e #define ZIPSIZE 0x12 #define ZIPUNCMP 0x16 +#define ZIPFNLN 0x1a +#define ZIPXTRALN 0x1c +#define ZIPNAME 0x1e -/** - * @def ZIPFNLN - * - * @brief A macro that defines zipfnln. - */ -#define ZIPFNLN 0x1a -/** - * @def ZIPXTRALN - * - * @brief A macro that defines zipxtraln. - */ +/*************************************************************************** + TYPE DEFINITIONS +***************************************************************************/ -#define ZIPXTRALN 0x1c +class zip_file_impl +{ +public: + typedef std::unique_ptr<zip_file_impl> ptr; + + zip_file_impl(const std::string &filename) + : m_filename(filename) + , m_file() + , m_length(0) + , m_ecd() + , m_cd() + , m_cd_pos(0) + , m_header() + , m_curr_is_dir(false) + , m_curr_name() + , m_buffer() + { + std::memset(&m_header, 0, sizeof(m_header)); + std::fill(m_buffer.begin(), m_buffer.end(), 0); + } -/** - * @def ZIPNAME - * - * @brief A macro that defines zipname. - */ + static ptr find_cached(const std::string &filename) + { + std::lock_guard<std::mutex> guard(s_cache_mutex); + for (std::size_t cachenum = 0; cachenum < s_cache.size(); cachenum++) + { + // if we have a valid entry and it matches our filename, use it and remove from the cache + if (s_cache[cachenum] && (filename == s_cache[cachenum]->m_filename)) + { + ptr result; + std::swap(s_cache[cachenum], result); + return result; + } + } + return ptr(); + } + static void close(ptr &&zip); + static void cache_clear() + { + // clear call cache entries + std::lock_guard<std::mutex> guard(s_cache_mutex); + for (std::size_t cachenum = 0; cachenum < s_cache.size(); s_cache[cachenum++].reset()) { } + } -#define ZIPNAME 0x1e + archive_file::error initialize() + { + // read ecd data + auto const ziperr = read_ecd(); + if (ziperr != archive_file::error::NONE) + return ziperr; + + // verify that we can work with this zipfile (no disk spanning allowed) + if ((m_ecd.disk_number != m_ecd.cd_start_disk_number) || (m_ecd.cd_disk_entries != m_ecd.cd_total_entries)) + return archive_file::error::UNSUPPORTED; + + // allocate memory for the central directory + try { m_cd.resize(m_ecd.cd_size + 1); } + catch (...) { return archive_file::error::OUT_OF_MEMORY; } + + // read the central directory + std::uint32_t read_length; + auto const filerr = m_file->read(&m_cd[0], m_ecd.cd_start_disk_offset, m_ecd.cd_size, read_length); + if ((filerr != osd_file::error::NONE) || (read_length != m_ecd.cd_size)) + return (filerr == osd_file::error::NONE) ? archive_file::error::FILE_TRUNCATED : archive_file::error::FILE_ERROR; + + return archive_file::error::NONE; + } + + int first_file() + { + m_cd_pos = 0; + return search(0, std::string(), false, false); + } + int next_file() + { + return search(0, std::string(), false, false); + } + int search(std::uint32_t crc) + { + m_cd_pos = 0; + return search(crc, std::string(), true, false); + } + int search(const std::string &filename) + { + m_cd_pos = 0; + return search(0, filename, false, true); + } + int search(std::uint32_t crc, const std::string &filename) + { + m_cd_pos = 0; + return search(crc, filename, true, true); + } + + bool current_is_directory() const { return m_curr_is_dir; } + const std::string ¤t_name() const { return m_curr_name; } + std::uint64_t current_uncompressed_length() const { return m_header.uncompressed_length; } + std::uint32_t current_crc() const { return m_header.crc; } + + archive_file::error decompress(void *buffer, std::uint32_t length); + +private: + zip_file_impl(const zip_file_impl &) = delete; + zip_file_impl(zip_file_impl &&) = delete; + zip_file_impl &operator=(const zip_file_impl &) = delete; + zip_file_impl &operator=(zip_file_impl &&) = delete; + + int search(std::uint32_t search_crc, const std::string &search_filename, bool matchcrc, bool matchname); + + archive_file::error reopen() + { + if (!m_file) + { + auto const filerr = osd_file::open(m_filename, OPEN_FLAG_READ, m_file, m_length); + if (filerr != osd_file::error::NONE) + return archive_file::error::FILE_ERROR; + } + return archive_file::error::NONE; + } + + // ZIP file parsing + archive_file::error read_ecd(); + archive_file::error get_compressed_data_offset(std::uint64_t &offset); + + // decompression interfaces + archive_file::error decompress_data_type_0(std::uint64_t offset, void *buffer, std::uint32_t length); + archive_file::error decompress_data_type_8(std::uint64_t offset, void *buffer, std::uint32_t length); + + struct file_header + { + std::uint32_t signature; // central file header signature + std::uint16_t version_created; // version made by + std::uint16_t version_needed; // version needed to extract + std::uint16_t bit_flag; // general purpose bit flag + std::uint16_t compression; // compression method + std::uint16_t file_time; // last mod file time + std::uint16_t file_date; // last mod file date + std::uint32_t crc; // crc-32 + std::uint32_t compressed_length; // compressed size + std::uint32_t uncompressed_length; // uncompressed size + std::uint16_t filename_length; // filename length + std::uint16_t extra_field_length; // extra field length + std::uint16_t file_comment_length; // file comment length + std::uint16_t start_disk_number; // disk number start + std::uint16_t internal_attributes; // internal file attributes + std::uint32_t external_attributes; // external file attributes + std::uint32_t local_header_offset; // relative offset of local header + const char * filename; // filename + }; + + // contains extracted end of central directory information + struct ecd + { + std::uint32_t signature; // end of central dir signature + std::uint16_t disk_number; // number of this disk + std::uint16_t cd_start_disk_number; // number of the disk with the start of the central directory + std::uint16_t cd_disk_entries; // total number of entries in the central directory on this disk + std::uint16_t cd_total_entries; // total number of entries in the central directory + std::uint32_t cd_size; // size of the central directory + std::uint32_t cd_start_disk_offset; // offset of start of central directory with respect to the starting disk number + std::uint16_t comment_length; // .ZIP file comment length + const char * comment; // .ZIP file comment + + std::unique_ptr<std::uint8_t []> raw; // pointer to the raw data + std::uint32_t rawlength; // length of the raw data + }; + + static constexpr std::size_t DECOMPRESS_BUFSIZE = 16384; + static constexpr std::size_t CACHE_SIZE = 8; // number of open files to cache + static std::array<ptr, CACHE_SIZE> s_cache; + static std::mutex s_cache_mutex; + + const std::string m_filename; // copy of ZIP filename (for caching) + osd_file::ptr m_file; // OSD file handle + std::uint64_t m_length; // length of zip file + + ecd m_ecd; // end of central directory + + std::vector<std::uint8_t> m_cd; // central directory raw data + std::uint32_t m_cd_pos; // position in central directory + file_header m_header; // current file header + bool m_curr_is_dir; // current file is directory + std::string m_curr_name; // current file name + + std::array<std::uint8_t, DECOMPRESS_BUFSIZE> m_buffer; // buffer for decompression +}; + + +class zip_file_wrapper : public archive_file +{ +public: + zip_file_wrapper(zip_file_impl::ptr &&impl) : m_impl(std::move(impl)) { assert(m_impl); } + virtual ~zip_file_wrapper() { zip_file_impl::close(std::move(m_impl)); } + + virtual int first_file() override { return m_impl->first_file(); } + virtual int next_file() override { return m_impl->next_file(); } + + virtual int search(std::uint32_t crc) override { return m_impl->search(crc); } + virtual int search(const std::string &filename) override { return m_impl->search(filename); } + virtual int search(std::uint32_t crc, const std::string &filename) override { return m_impl->search(crc, filename); } + + virtual bool current_is_directory() const override { return m_impl->current_is_directory(); } + virtual const std::string ¤t_name() const override { return m_impl->current_name(); } + virtual std::uint64_t current_uncompressed_length() const override { return m_impl->current_uncompressed_length(); } + virtual std::uint32_t current_crc() const override { return m_impl->current_crc(); } + + virtual error decompress(void *buffer, std::uint32_t length) override { return m_impl->decompress(buffer, length); } + +private: + zip_file_impl::ptr m_impl; +}; @@ -108,7 +312,7 @@ * @return The word. */ -static inline UINT16 read_word(UINT8 *buf) +inline std::uint16_t read_word(std::uint8_t const *buf) { return (buf[1] << 8) | buf[0]; } @@ -123,7 +327,7 @@ static inline UINT16 read_word(UINT8 *buf) * @return The double word. */ -static inline UINT32 read_dword(UINT8 *buf) +inline std::uint32_t read_dword(std::uint8_t const *buf) { return (buf[3] << 24) | (buf[2] << 16) | (buf[1] << 8) | buf[0]; } @@ -135,130 +339,9 @@ static inline UINT32 read_dword(UINT8 *buf) ***************************************************************************/ /** @brief The zip cache[ zip cache size]. */ -static zip_file *zip_cache[ZIP_CACHE_SIZE]; - - - -/*************************************************************************** - FUNCTION PROTOTYPES -***************************************************************************/ - -/* cache management */ -static void free_zip_file(zip_file *zip); - -/* ZIP file parsing */ -static zip_error read_ecd(zip_file *zip); -static zip_error get_compressed_data_offset(zip_file *zip, UINT64 *offset); - -/* decompression interfaces */ -static zip_error decompress_data_type_0(zip_file *zip, UINT64 offset, void *buffer, UINT32 length); -static zip_error decompress_data_type_8(zip_file *zip, UINT64 offset, void *buffer, UINT32 length); - - - -/*************************************************************************** - ZIP FILE ACCESS -***************************************************************************/ - -/*------------------------------------------------- - zip_file_open - opens a ZIP file for reading --------------------------------------------------*/ - -/** - * @fn zip_error zip_file_open(const char *filename, zip_file **zip) - * - * @brief Queries if a given zip file open. - * - * @param filename Filename of the file. - * @param [in,out] zip If non-null, the zip. - * - * @return A zip_error. - */ - -zip_error zip_file_open(const char *filename, zip_file **zip) -{ - zip_error ziperr = ZIPERR_NONE; - file_error filerr; - UINT32 read_length; - zip_file *newzip; - char *string; - int cachenum; - - /* ensure we start with a NULL result */ - *zip = nullptr; - - /* see if we are in the cache, and reopen if so */ - for (cachenum = 0; cachenum < ARRAY_LENGTH(zip_cache); cachenum++) - { - zip_file *cached = zip_cache[cachenum]; - - /* if we have a valid entry and it matches our filename, use it and remove from the cache */ - if (cached != nullptr && cached->filename != nullptr && strcmp(filename, cached->filename) == 0) - { - *zip = cached; - zip_cache[cachenum] = nullptr; - return ZIPERR_NONE; - } - } +std::array<zip_file_impl::ptr, zip_file_impl::CACHE_SIZE> zip_file_impl::s_cache; +std::mutex zip_file_impl::s_cache_mutex; - /* allocate memory for the zip_file structure */ - newzip = (zip_file *)malloc(sizeof(*newzip)); - if (newzip == nullptr) - return ZIPERR_OUT_OF_MEMORY; - memset(newzip, 0, sizeof(*newzip)); - - /* open the file */ - filerr = osd_open(filename, OPEN_FLAG_READ, &newzip->file, &newzip->length); - if (filerr != FILERR_NONE) - { - ziperr = ZIPERR_FILE_ERROR; - goto error; - } - - /* read ecd data */ - ziperr = read_ecd(newzip); - if (ziperr != ZIPERR_NONE) - goto error; - - /* verify that we can work with this zipfile (no disk spanning allowed) */ - if (newzip->ecd.disk_number != newzip->ecd.cd_start_disk_number || newzip->ecd.cd_disk_entries != newzip->ecd.cd_total_entries) - { - ziperr = ZIPERR_UNSUPPORTED; - goto error; - } - - /* allocate memory for the central directory */ - newzip->cd = (UINT8 *)malloc(newzip->ecd.cd_size + 1); - if (newzip->cd == nullptr) - { - ziperr = ZIPERR_OUT_OF_MEMORY; - goto error; - } - - /* read the central directory */ - filerr = osd_read(newzip->file, newzip->cd, newzip->ecd.cd_start_disk_offset, newzip->ecd.cd_size, &read_length); - if (filerr != FILERR_NONE || read_length != newzip->ecd.cd_size) - { - ziperr = (filerr == FILERR_NONE) ? ZIPERR_FILE_TRUNCATED : ZIPERR_FILE_ERROR; - goto error; - } - - /* make a copy of the filename for caching purposes */ - string = (char *)malloc(strlen(filename) + 1); - if (string == nullptr) - { - ziperr = ZIPERR_OUT_OF_MEMORY; - goto error; - } - strcpy(string, filename); - newzip->filename = string; - *zip = newzip; - return ZIPERR_NONE; - -error: - free_zip_file(newzip); - return ziperr; -} /*------------------------------------------------- @@ -274,57 +357,31 @@ error: * @param [in,out] zip If non-null, the zip. */ -void zip_file_close(zip_file *zip) +void zip_file_impl::close(ptr &&zip) { - int cachenum; + if (!zip) return; - /* close the open files */ - if (zip->file != nullptr) - osd_close(zip->file); - zip->file = nullptr; + // close the open files + zip->m_file.reset(); - /* find the first NULL entry in the cache */ - for (cachenum = 0; cachenum < ARRAY_LENGTH(zip_cache); cachenum++) - if (zip_cache[cachenum] == nullptr) + // find the first NULL entry in the cache + std::lock_guard<std::mutex> guard(s_cache_mutex); + std::size_t cachenum; + for (cachenum = 0; cachenum < s_cache.size(); cachenum++) + if (!s_cache[cachenum]) break; - /* if no room left in the cache, free the bottommost entry */ - if (cachenum == ARRAY_LENGTH(zip_cache)) - free_zip_file(zip_cache[--cachenum]); - - /* move everyone else down and place us at the top */ - if (cachenum != 0) - memmove(&zip_cache[1], &zip_cache[0], cachenum * sizeof(zip_cache[0])); - zip_cache[0] = zip; -} - - -/*------------------------------------------------- - zip_file_cache_clear - clear the ZIP file - cache and free all memory --------------------------------------------------*/ - -/** - * @fn void zip_file_cache_clear(void) - * - * @brief Zip file cache clear. - */ + // if no room left in the cache, free the bottommost entry + if (cachenum == s_cache.size()) + s_cache[--cachenum].reset(); -void zip_file_cache_clear(void) -{ - int cachenum; - - /* clear call cache entries */ - for (cachenum = 0; cachenum < ARRAY_LENGTH(zip_cache); cachenum++) - if (zip_cache[cachenum] != nullptr) - { - free_zip_file(zip_cache[cachenum]); - zip_cache[cachenum] = nullptr; - } + // move everyone else down and place us at the top + for ( ; cachenum > 0; cachenum--) + s_cache[cachenum] = std::move(s_cache[cachenum - 1]); + s_cache[0] = std::move(zip); } - /*************************************************************************** CONTAINED FILE ACCESS ***************************************************************************/ @@ -334,88 +391,63 @@ void zip_file_cache_clear(void) in the ZIP -------------------------------------------------*/ -/** - * @fn const zip_file_header *zip_file_first_file(zip_file *zip) - * - * @brief Zip file first file. - * - * @param [in,out] zip If non-null, the zip. - * - * @return null if it fails, else a zip_file_header*. - */ - -const zip_file_header *zip_file_first_file(zip_file *zip) -{ - /* reset the position and go from there */ - zip->cd_pos = 0; - return zip_file_next_file(zip); -} - - /*------------------------------------------------- zip_file_next_entry - return the next entry in the ZIP -------------------------------------------------*/ -/** - * @fn const zip_file_header *zip_file_next_file(zip_file *zip) - * - * @brief Zip file next file. - * - * @param [in,out] zip If non-null, the zip. - * - * @return null if it fails, else a zip_file_header*. - */ - -const zip_file_header *zip_file_next_file(zip_file *zip) +int zip_file_impl::search(std::uint32_t search_crc, const std::string &search_filename, bool matchcrc, bool matchname) { - /* fix up any modified data */ - if (zip->header.raw != nullptr) + // if we're at or past the end, we're done + std::string filename; + while ((m_cd_pos + ZIPCFN) <= m_ecd.cd_size) { - zip->header.raw[ZIPCFN + zip->header.filename_length] = zip->header.saved; - zip->header.raw = nullptr; - } + // extract file header info + std::uint8_t const *const raw = &m_cd[0] + m_cd_pos; + m_header.signature = read_dword(raw + ZIPCENSIG); + m_header.version_created = read_word (raw + ZIPCVER); + m_header.version_needed = read_word (raw + ZIPCVXT); + m_header.bit_flag = read_word (raw + ZIPCFLG); + m_header.compression = read_word (raw + ZIPCMTHD); + m_header.file_time = read_word (raw + ZIPCTIM); + m_header.file_date = read_word (raw + ZIPCDAT); + m_header.crc = read_dword(raw + ZIPCCRC); + m_header.compressed_length = read_dword(raw + ZIPCSIZ); + m_header.uncompressed_length = read_dword(raw + ZIPCUNC); + m_header.filename_length = read_word (raw + ZIPCFNL); + m_header.extra_field_length = read_word (raw + ZIPCXTL); + m_header.file_comment_length = read_word (raw + ZIPCCML); + m_header.start_disk_number = read_word (raw + ZIPDSK); + m_header.internal_attributes = read_word (raw + ZIPINT); + m_header.external_attributes = read_dword(raw + ZIPEXT); + m_header.local_header_offset = read_dword(raw + ZIPOFST); + m_header.filename = reinterpret_cast<const char *>(raw + ZIPCFN); + + // make sure we have enough data + std::uint32_t const rawlength = ZIPCFN + m_header.filename_length + m_header.extra_field_length + m_header.file_comment_length; + if ((m_cd_pos + rawlength) > m_ecd.cd_size) + break; + + // advance the position + m_cd_pos += rawlength; + + // copy the filename filename + bool const is_dir((m_header.filename_length > 0) && (m_header.filename[m_header.filename_length - 1] == '/')); + filename.assign(m_header.filename, m_header.filename_length - (is_dir ? 1 : 0)); + + // check to see if it matches query + bool const crcmatch(search_crc == m_header.crc); + const bool namematch(!core_stricmp(search_filename.c_str(), filename.c_str())); - /* if we're at or past the end, we're done */ - if (zip->cd_pos >= zip->ecd.cd_size) - return nullptr; - - /* extract file header info */ - zip->header.raw = zip->cd + zip->cd_pos; - zip->header.rawlength = ZIPCFN; - zip->header.signature = read_dword(zip->header.raw + ZIPCENSIG); - zip->header.version_created = read_word (zip->header.raw + ZIPCVER); - zip->header.version_needed = read_word (zip->header.raw + ZIPCVXT); - zip->header.bit_flag = read_word (zip->header.raw + ZIPCFLG); - zip->header.compression = read_word (zip->header.raw + ZIPCMTHD); - zip->header.file_time = read_word (zip->header.raw + ZIPCTIM); - zip->header.file_date = read_word (zip->header.raw + ZIPCDAT); - zip->header.crc = read_dword(zip->header.raw + ZIPCCRC); - zip->header.compressed_length = read_dword(zip->header.raw + ZIPCSIZ); - zip->header.uncompressed_length = read_dword(zip->header.raw + ZIPCUNC); - zip->header.filename_length = read_word (zip->header.raw + ZIPCFNL); - zip->header.extra_field_length = read_word (zip->header.raw + ZIPCXTL); - zip->header.file_comment_length = read_word (zip->header.raw + ZIPCCML); - zip->header.start_disk_number = read_word (zip->header.raw + ZIPDSK); - zip->header.internal_attributes = read_word (zip->header.raw + ZIPINT); - zip->header.external_attributes = read_dword(zip->header.raw + ZIPEXT); - zip->header.local_header_offset = read_dword(zip->header.raw + ZIPOFST); - zip->header.filename = (char *)zip->header.raw + ZIPCFN; - - /* make sure we have enough data */ - zip->header.rawlength += zip->header.filename_length; - zip->header.rawlength += zip->header.extra_field_length; - zip->header.rawlength += zip->header.file_comment_length; - if (zip->cd_pos + zip->header.rawlength > zip->ecd.cd_size) - return nullptr; - - /* NULL terminate the filename */ - zip->header.saved = zip->header.raw[ZIPCFN + zip->header.filename_length]; - zip->header.raw[ZIPCFN + zip->header.filename_length] = 0; - - /* advance the position */ - zip->cd_pos += zip->header.rawlength; - return &zip->header; + bool const found = ((!matchcrc && !matchname) || !is_dir) && (!matchcrc || crcmatch) && (!matchname || namematch); + if (found) + { + m_curr_is_dir = is_dir; + m_curr_name = std::move(filename); + return 0; + } + } + return -1; } @@ -436,38 +468,38 @@ const zip_file_header *zip_file_next_file(zip_file *zip) * @return A zip_error. */ -zip_error zip_file_decompress(zip_file *zip, void *buffer, UINT32 length) +archive_file::error zip_file_impl::decompress(void *buffer, std::uint32_t length) { - zip_error ziperr; - UINT64 offset; + archive_file::error ziperr; + std::uint64_t offset; - /* if we don't have enough buffer, error */ - if (length < zip->header.uncompressed_length) - return ZIPERR_BUFFER_TOO_SMALL; + // if we don't have enough buffer, error + if (length < m_header.uncompressed_length) + return archive_file::error::BUFFER_TOO_SMALL; - /* make sure the info in the header aligns with what we know */ - if (zip->header.start_disk_number != zip->ecd.disk_number) - return ZIPERR_UNSUPPORTED; + // make sure the info in the header aligns with what we know + if (m_header.start_disk_number != m_ecd.disk_number) + return archive_file::error::UNSUPPORTED; - /* get the compressed data offset */ - ziperr = get_compressed_data_offset(zip, &offset); - if (ziperr != ZIPERR_NONE) + // get the compressed data offset + ziperr = get_compressed_data_offset(offset); + if (ziperr != archive_file::error::NONE) return ziperr; - /* handle compression types */ - switch (zip->header.compression) + // handle compression types + switch (m_header.compression) { - case 0: - ziperr = decompress_data_type_0(zip, offset, buffer, length); - break; + case 0: + ziperr = decompress_data_type_0(offset, buffer, length); + break; - case 8: - ziperr = decompress_data_type_8(zip, offset, buffer, length); - break; + case 8: + ziperr = decompress_data_type_8(offset, buffer, length); + break; - default: - ziperr = ZIPERR_UNSUPPORTED; - break; + default: + ziperr = archive_file::error::UNSUPPORTED; + break; } return ziperr; } @@ -475,41 +507,6 @@ zip_error zip_file_decompress(zip_file *zip, void *buffer, UINT32 length) /*************************************************************************** - CACHE MANAGEMENT -***************************************************************************/ - -/*------------------------------------------------- - free_zip_file - free all the data for a - zip_file --------------------------------------------------*/ - -/** - * @fn static void free_zip_file(zip_file *zip) - * - * @brief Free zip file. - * - * @param [in,out] zip If non-null, the zip. - */ - -static void free_zip_file(zip_file *zip) -{ - if (zip != nullptr) - { - if (zip->file != nullptr) - osd_close(zip->file); - if (zip->filename != nullptr) - free((void *)zip->filename); - if (zip->ecd.raw != nullptr) - free(zip->ecd.raw); - if (zip->cd != nullptr) - free(zip->cd); - free(zip); - } -} - - - -/*************************************************************************** ZIP FILE PARSING ***************************************************************************/ @@ -527,72 +524,69 @@ static void free_zip_file(zip_file *zip) * @return The ecd. */ -static zip_error read_ecd(zip_file *zip) +archive_file::error zip_file_impl::read_ecd() { - UINT32 buflen = 1024; - UINT8 *buffer; + // make sure the file handle is open + auto const ziperr = reopen(); + if (ziperr != archive_file::error::NONE) + return ziperr; - /* we may need multiple tries */ + // we may need multiple tries + std::uint32_t buflen = 1024; while (buflen < 65536) { - file_error error; - UINT32 read_length; - INT32 offset; - - /* max out the buffer length at the size of the file */ - if (buflen > zip->length) - buflen = zip->length; - - /* allocate buffer */ - buffer = (UINT8 *)malloc(buflen + 1); - if (buffer == nullptr) - return ZIPERR_OUT_OF_MEMORY; - - /* read in one buffers' worth of data */ - error = osd_read(zip->file, buffer, zip->length - buflen, buflen, &read_length); - if (error != FILERR_NONE || read_length != buflen) - { - free(buffer); - return ZIPERR_FILE_ERROR; - } - - /* find the ECD signature */ + // max out the buffer length at the size of the file + if (buflen > m_length) + buflen = m_length; + + // allocate buffer + std::unique_ptr<std::uint8_t []> buffer; + try { buffer.reset(new std::uint8_t[buflen + 1]); } + catch (...) { return archive_file::error::OUT_OF_MEMORY; } + + // read in one buffers' worth of data + std::uint32_t read_length; + auto const error = m_file->read(&buffer[0], m_length - buflen, buflen, read_length); + if (error != osd_file::error::NONE || read_length != buflen) + return archive_file::error::FILE_ERROR; + + // find the ECD signature + std::int32_t offset; for (offset = buflen - 22; offset >= 0; offset--) if (buffer[offset + 0] == 'P' && buffer[offset + 1] == 'K' && buffer[offset + 2] == 0x05 && buffer[offset + 3] == 0x06) break; - /* if we found it, fill out the data */ + // if we found it, fill out the data if (offset >= 0) { - /* reuse the buffer as our ECD buffer */ - zip->ecd.raw = buffer; - zip->ecd.rawlength = buflen - offset; + // reuse the buffer as our ECD buffer + m_ecd.raw = std::move(buffer); + m_ecd.rawlength = buflen - offset; /* append a NULL terminator to the comment */ - memmove(&buffer[0], &buffer[offset], zip->ecd.rawlength); - zip->ecd.raw[zip->ecd.rawlength] = 0; + memmove(&m_ecd.raw[0], &m_ecd.raw[offset], m_ecd.rawlength); + m_ecd.raw[m_ecd.rawlength] = 0; /* extract ecd info */ - zip->ecd.signature = read_dword(zip->ecd.raw + ZIPESIG); - zip->ecd.disk_number = read_word (zip->ecd.raw + ZIPEDSK); - zip->ecd.cd_start_disk_number = read_word (zip->ecd.raw + ZIPECEN); - zip->ecd.cd_disk_entries = read_word (zip->ecd.raw + ZIPENUM); - zip->ecd.cd_total_entries = read_word (zip->ecd.raw + ZIPECENN); - zip->ecd.cd_size = read_dword(zip->ecd.raw + ZIPECSZ); - zip->ecd.cd_start_disk_offset = read_dword(zip->ecd.raw + ZIPEOFST); - zip->ecd.comment_length = read_word (zip->ecd.raw + ZIPECOML); - zip->ecd.comment = (const char *)(zip->ecd.raw + ZIPECOM); - return ZIPERR_NONE; + m_ecd.signature = read_dword(&m_ecd.raw[ZIPESIG]); + m_ecd.disk_number = read_word (&m_ecd.raw[ZIPEDSK]); + m_ecd.cd_start_disk_number = read_word (&m_ecd.raw[ZIPECEN]); + m_ecd.cd_disk_entries = read_word (&m_ecd.raw[ZIPENUM]); + m_ecd.cd_total_entries = read_word (&m_ecd.raw[ZIPECENN]); + m_ecd.cd_size = read_dword(&m_ecd.raw[ZIPECSZ]); + m_ecd.cd_start_disk_offset = read_dword(&m_ecd.raw[ZIPEOFST]); + m_ecd.comment_length = read_word (&m_ecd.raw[ZIPECOML]); + m_ecd.comment = reinterpret_cast<const char *>(&m_ecd.raw[ZIPECOM]); + return archive_file::error::NONE; } - /* didn't find it; free this buffer and expand our search */ - free(buffer); - if (buflen < zip->length) + // didn't find it; free this buffer and expand our search + if (buflen < m_length) buflen *= 2; else - return ZIPERR_BAD_SIGNATURE; + return archive_file::error::BAD_SIGNATURE; } - return ZIPERR_OUT_OF_MEMORY; + return archive_file::error::OUT_OF_MEMORY; } @@ -612,30 +606,25 @@ static zip_error read_ecd(zip_file *zip) * @return The compressed data offset. */ -static zip_error get_compressed_data_offset(zip_file *zip, UINT64 *offset) +archive_file::error zip_file_impl::get_compressed_data_offset(std::uint64_t &offset) { - file_error error; - UINT32 read_length; - - /* make sure the file handle is open */ - if (zip->file == nullptr) - { - int filerr = osd_open(zip->filename, OPEN_FLAG_READ, &zip->file, &zip->length); - if (filerr != FILERR_NONE) - return ZIPERR_FILE_ERROR; - } + // make sure the file handle is open + auto const ziperr = reopen(); + if (ziperr != archive_file::error::NONE) + return ziperr; - /* now go read the fixed-sized part of the local file header */ - error = osd_read(zip->file, zip->buffer, zip->header.local_header_offset, ZIPNAME, &read_length); - if (error != FILERR_NONE || read_length != ZIPNAME) - return (error == FILERR_NONE) ? ZIPERR_FILE_TRUNCATED : ZIPERR_FILE_ERROR; + // now go read the fixed-sized part of the local file header + std::uint32_t read_length; + auto const error = m_file->read(&m_buffer[0], m_header.local_header_offset, ZIPNAME, read_length); + if (error != osd_file::error::NONE || read_length != ZIPNAME) + return (error == osd_file::error::NONE) ? archive_file::error::FILE_TRUNCATED : archive_file::error::FILE_ERROR; /* compute the final offset */ - *offset = zip->header.local_header_offset + ZIPNAME; - *offset += read_word(zip->buffer + ZIPFNLN); - *offset += read_word(zip->buffer + ZIPXTRALN); + offset = m_header.local_header_offset + ZIPNAME; + offset += read_word(&m_buffer[ZIPFNLN]); + offset += read_word(&m_buffer[ZIPXTRALN]); - return ZIPERR_NONE; + return archive_file::error::NONE; } @@ -662,19 +651,18 @@ static zip_error get_compressed_data_offset(zip_file *zip, UINT64 *offset) * @return A zip_error. */ -static zip_error decompress_data_type_0(zip_file *zip, UINT64 offset, void *buffer, UINT32 length) +archive_file::error zip_file_impl::decompress_data_type_0(std::uint64_t offset, void *buffer, std::uint32_t length) { - file_error filerr; - UINT32 read_length; - - /* the data is uncompressed; just read it */ - filerr = osd_read(zip->file, buffer, offset, zip->header.compressed_length, &read_length); - if (filerr != FILERR_NONE) - return ZIPERR_FILE_ERROR; - else if (read_length != zip->header.compressed_length) - return ZIPERR_FILE_TRUNCATED; + std::uint32_t read_length; + + // the data is uncompressed; just read it + auto const filerr = m_file->read(buffer, offset, m_header.compressed_length, read_length); + if (filerr != osd_file::error::NONE) + return archive_file::error::FILE_ERROR; + else if (read_length != m_header.compressed_length) + return archive_file::error::FILE_TRUNCATED; else - return ZIPERR_NONE; + return archive_file::error::NONE; } @@ -696,75 +684,153 @@ static zip_error decompress_data_type_0(zip_file *zip, UINT64 offset, void *buff * @return A zip_error. */ -static zip_error decompress_data_type_8(zip_file *zip, UINT64 offset, void *buffer, UINT32 length) +archive_file::error zip_file_impl::decompress_data_type_8(std::uint64_t offset, void *buffer, std::uint32_t length) { - UINT32 input_remaining = zip->header.compressed_length; - UINT32 read_length; - z_stream stream; - int filerr; + std::uint32_t input_remaining = m_header.compressed_length; int zerr; - /* make sure we don't need a newer mechanism */ - if (zip->header.version_needed > 0x14) - return ZIPERR_UNSUPPORTED; + // make sure we don't need a newer mechanism + if (m_header.version_needed > 0x14) + return archive_file::error::UNSUPPORTED; /* reset the stream */ + z_stream stream; memset(&stream, 0, sizeof(stream)); stream.next_out = (Bytef *)buffer; stream.avail_out = length; - /* initialize the decompressor */ + // initialize the decompressor zerr = inflateInit2(&stream, -MAX_WBITS); if (zerr != Z_OK) - return ZIPERR_DECOMPRESS_ERROR; + return archive_file::error::DECOMPRESS_ERROR; - /* loop until we're done */ + // loop until we're done while (1) { - /* read in the next chunk of data */ - filerr = osd_read(zip->file, zip->buffer, offset, MIN(input_remaining, sizeof(zip->buffer)), &read_length); - if (filerr != FILERR_NONE) + // read in the next chunk of data + std::uint32_t read_length; + auto const filerr = m_file->read(&m_buffer[0], offset, (std::min<std::uint32_t>)(input_remaining, m_buffer.size()), read_length); + if (filerr != osd_file::error::NONE) { inflateEnd(&stream); - return ZIPERR_FILE_ERROR; + return archive_file::error::FILE_ERROR; } offset += read_length; - /* if we read nothing, but still have data left, the file is truncated */ + // if we read nothing, but still have data left, the file is truncated if (read_length == 0 && input_remaining > 0) { inflateEnd(&stream); - return ZIPERR_FILE_TRUNCATED; + return archive_file::error::FILE_TRUNCATED; } - /* fill out the input data */ - stream.next_in = zip->buffer; + // fill out the input data + stream.next_in = &m_buffer[0]; stream.avail_in = read_length; input_remaining -= read_length; - /* add a dummy byte at end of compressed data */ + // add a dummy byte at end of compressed data if (input_remaining == 0) stream.avail_in++; - /* now inflate */ + // now inflate zerr = inflate(&stream, Z_NO_FLUSH); if (zerr == Z_STREAM_END) break; if (zerr != Z_OK) { inflateEnd(&stream); - return ZIPERR_DECOMPRESS_ERROR; + return archive_file::error::DECOMPRESS_ERROR; } } - /* finish decompression */ + // finish decompression zerr = inflateEnd(&stream); if (zerr != Z_OK) - return ZIPERR_DECOMPRESS_ERROR; + return archive_file::error::DECOMPRESS_ERROR; /* if anything looks funny, report an error */ if (stream.avail_out > 0 || input_remaining > 0) - return ZIPERR_DECOMPRESS_ERROR; + return archive_file::error::DECOMPRESS_ERROR; + + return archive_file::error::NONE; +} + +} // anonymous namespace + + + +/*************************************************************************** + un7z.cpp TRAMPOLINES +***************************************************************************/ + +void m7z_file_cache_clear(); + + + +/*************************************************************************** + ZIP FILE ACCESS +***************************************************************************/ + +/*------------------------------------------------- + zip_file_open - opens a ZIP file for reading +-------------------------------------------------*/ + +/** + * @fn zip_error zip_file_open(const char *filename, zip_file **zip) + * + * @brief Queries if a given zip file open. + * + * @param filename Filename of the file. + * @param [in,out] zip If non-null, the zip. + * + * @return A zip_error. + */ + +archive_file::error archive_file::open_zip(const std::string &filename, ptr &result) +{ + // ensure we start with a NULL result + result.reset(); + + // see if we are in the cache, and reopen if so + zip_file_impl::ptr newimpl(zip_file_impl::find_cached(filename)); + + if (!newimpl) + { + // allocate memory for the zip_file structure + try { newimpl = std::make_unique<zip_file_impl>(filename); } + catch (...) { return error::OUT_OF_MEMORY; } + auto const ziperr = newimpl->initialize(); + if (ziperr != error::NONE) return ziperr; + } - return ZIPERR_NONE; + try + { + result = std::make_unique<zip_file_wrapper>(std::move(newimpl)); + return error::NONE; + } + catch (...) + { + zip_file_impl::close(std::move(newimpl)); + return error::OUT_OF_MEMORY; + } } + + +/*------------------------------------------------- + zip_file_cache_clear - clear the ZIP file + cache and free all memory +-------------------------------------------------*/ + +void archive_file::cache_clear() +{ + zip_file_impl::cache_clear(); + m7z_file_cache_clear(); +} + + +archive_file::~archive_file() +{ +} + +} // namespace util |