mirror of
https://github.com/holub/mame
synced 2025-06-07 13:23:50 +03:00
837 lines
26 KiB
C++
837 lines
26 KiB
C++
// license:BSD-3-Clause
|
|
// copyright-holders:Aaron Giles, Vas Crabb
|
|
/***************************************************************************
|
|
|
|
unzip.c
|
|
|
|
Functions to manipulate data within ZIP files.
|
|
|
|
***************************************************************************/
|
|
|
|
#include "unzip.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
|
|
***************************************************************************/
|
|
|
|
/* offsets in end of central directory structure */
|
|
#define ZIPESIG 0x00
|
|
#define ZIPEDSK 0x04
|
|
#define ZIPECEN 0x06
|
|
#define ZIPENUM 0x08
|
|
#define ZIPECENN 0x0a
|
|
#define ZIPECSZ 0x0c
|
|
#define ZIPEOFST 0x10
|
|
#define ZIPECOML 0x14
|
|
#define ZIPECOM 0x16
|
|
|
|
/* offsets in central directory entry structure */
|
|
#define ZIPCENSIG 0x00
|
|
#define ZIPCVER 0x04
|
|
#define ZIPCOS 0x05
|
|
#define ZIPCVXT 0x06
|
|
#define ZIPCEXOS 0x07
|
|
#define ZIPCFLG 0x08
|
|
#define ZIPCMTHD 0x0a
|
|
#define ZIPCTIM 0x0c
|
|
#define ZIPCDAT 0x0e
|
|
#define ZIPCCRC 0x10
|
|
#define ZIPCSIZ 0x14
|
|
#define ZIPCUNC 0x18
|
|
#define ZIPCFNL 0x1c
|
|
#define ZIPCXTL 0x1e
|
|
#define ZIPCCML 0x20
|
|
#define ZIPDSK 0x22
|
|
#define ZIPINT 0x24
|
|
#define ZIPEXT 0x26
|
|
#define ZIPOFST 0x2a
|
|
#define ZIPCFN 0x2e
|
|
|
|
/* offsets in local file header structure */
|
|
#define ZIPLOCSIG 0x00
|
|
#define ZIPVER 0x04
|
|
#define ZIPGENFLG 0x06
|
|
#define ZIPMTHD 0x08
|
|
#define ZIPTIME 0x0a
|
|
#define ZIPDATE 0x0c
|
|
#define ZIPCRC 0x0e
|
|
#define ZIPSIZE 0x12
|
|
#define ZIPUNCMP 0x16
|
|
#define ZIPFNLN 0x1a
|
|
#define ZIPXTRALN 0x1c
|
|
#define ZIPNAME 0x1e
|
|
|
|
|
|
|
|
/***************************************************************************
|
|
TYPE DEFINITIONS
|
|
***************************************************************************/
|
|
|
|
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);
|
|
}
|
|
|
|
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()) { }
|
|
}
|
|
|
|
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;
|
|
};
|
|
|
|
|
|
|
|
/***************************************************************************
|
|
INLINE FUNCTIONS
|
|
***************************************************************************/
|
|
|
|
/**
|
|
* @fn static inline UINT16 read_word(UINT8 *buf)
|
|
*
|
|
* @brief Reads a word.
|
|
*
|
|
* @param [in,out] buf If non-null, the buffer.
|
|
*
|
|
* @return The word.
|
|
*/
|
|
|
|
inline std::uint16_t read_word(std::uint8_t const *buf)
|
|
{
|
|
return (buf[1] << 8) | buf[0];
|
|
}
|
|
|
|
/**
|
|
* @fn static inline UINT32 read_dword(UINT8 *buf)
|
|
*
|
|
* @brief Reads a double word.
|
|
*
|
|
* @param [in,out] buf If non-null, the buffer.
|
|
*
|
|
* @return The double word.
|
|
*/
|
|
|
|
inline std::uint32_t read_dword(std::uint8_t const *buf)
|
|
{
|
|
return (buf[3] << 24) | (buf[2] << 16) | (buf[1] << 8) | buf[0];
|
|
}
|
|
|
|
|
|
|
|
/***************************************************************************
|
|
GLOBAL VARIABLES
|
|
***************************************************************************/
|
|
|
|
/** @brief The zip cache[ zip cache size]. */
|
|
std::array<zip_file_impl::ptr, zip_file_impl::CACHE_SIZE> zip_file_impl::s_cache;
|
|
std::mutex zip_file_impl::s_cache_mutex;
|
|
|
|
|
|
|
|
/*-------------------------------------------------
|
|
zip_file_close - close a ZIP file and add it
|
|
to the cache
|
|
-------------------------------------------------*/
|
|
|
|
/**
|
|
* @fn void zip_file_close(zip_file *zip)
|
|
*
|
|
* @brief Zip file close.
|
|
*
|
|
* @param [in,out] zip If non-null, the zip.
|
|
*/
|
|
|
|
void zip_file_impl::close(ptr &&zip)
|
|
{
|
|
if (!zip) return;
|
|
|
|
// close the open files
|
|
zip->m_file.reset();
|
|
|
|
// 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 == s_cache.size())
|
|
s_cache[--cachenum].reset();
|
|
|
|
// 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
|
|
***************************************************************************/
|
|
|
|
/*-------------------------------------------------
|
|
zip_file_first_entry - return the first entry
|
|
in the ZIP
|
|
-------------------------------------------------*/
|
|
|
|
/*-------------------------------------------------
|
|
zip_file_next_entry - return the next entry
|
|
in the ZIP
|
|
-------------------------------------------------*/
|
|
|
|
int zip_file_impl::search(std::uint32_t search_crc, const std::string &search_filename, bool matchcrc, bool matchname)
|
|
{
|
|
// if we're at or past the end, we're done
|
|
std::string filename;
|
|
while ((m_cd_pos + ZIPCFN) <= m_ecd.cd_size)
|
|
{
|
|
// 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()));
|
|
|
|
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;
|
|
}
|
|
|
|
|
|
/*-------------------------------------------------
|
|
zip_file_decompress - decompress a file
|
|
from a ZIP into the target buffer
|
|
-------------------------------------------------*/
|
|
|
|
/**
|
|
* @fn zip_error zip_file_decompress(zip_file *zip, void *buffer, UINT32 length)
|
|
*
|
|
* @brief Zip file decompress.
|
|
*
|
|
* @param [in,out] zip If non-null, the zip.
|
|
* @param [in,out] buffer If non-null, the buffer.
|
|
* @param length The length.
|
|
*
|
|
* @return A zip_error.
|
|
*/
|
|
|
|
archive_file::error zip_file_impl::decompress(void *buffer, std::uint32_t length)
|
|
{
|
|
archive_file::error ziperr;
|
|
std::uint64_t offset;
|
|
|
|
// 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 (m_header.start_disk_number != m_ecd.disk_number)
|
|
return archive_file::error::UNSUPPORTED;
|
|
|
|
// get the compressed data offset
|
|
ziperr = get_compressed_data_offset(offset);
|
|
if (ziperr != archive_file::error::NONE)
|
|
return ziperr;
|
|
|
|
// handle compression types
|
|
switch (m_header.compression)
|
|
{
|
|
case 0:
|
|
ziperr = decompress_data_type_0(offset, buffer, length);
|
|
break;
|
|
|
|
case 8:
|
|
ziperr = decompress_data_type_8(offset, buffer, length);
|
|
break;
|
|
|
|
default:
|
|
ziperr = archive_file::error::UNSUPPORTED;
|
|
break;
|
|
}
|
|
return ziperr;
|
|
}
|
|
|
|
|
|
|
|
/***************************************************************************
|
|
ZIP FILE PARSING
|
|
***************************************************************************/
|
|
|
|
/*-------------------------------------------------
|
|
read_ecd - read the ECD data
|
|
-------------------------------------------------*/
|
|
|
|
/**
|
|
* @fn static zip_error read_ecd(zip_file *zip)
|
|
*
|
|
* @brief Reads an ecd.
|
|
*
|
|
* @param [in,out] zip If non-null, the zip.
|
|
*
|
|
* @return The ecd.
|
|
*/
|
|
|
|
archive_file::error zip_file_impl::read_ecd()
|
|
{
|
|
// make sure the file handle is open
|
|
auto const ziperr = reopen();
|
|
if (ziperr != archive_file::error::NONE)
|
|
return ziperr;
|
|
|
|
// we may need multiple tries
|
|
std::uint32_t buflen = 1024;
|
|
while (buflen < 65536)
|
|
{
|
|
// 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 (offset >= 0)
|
|
{
|
|
// 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(&m_ecd.raw[0], &m_ecd.raw[offset], m_ecd.rawlength);
|
|
m_ecd.raw[m_ecd.rawlength] = 0;
|
|
|
|
/* extract ecd info */
|
|
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
|
|
if (buflen < m_length)
|
|
buflen *= 2;
|
|
else
|
|
return archive_file::error::BAD_SIGNATURE;
|
|
}
|
|
return archive_file::error::OUT_OF_MEMORY;
|
|
}
|
|
|
|
|
|
/*-------------------------------------------------
|
|
get_compressed_data_offset - return the
|
|
offset of the compressed data
|
|
-------------------------------------------------*/
|
|
|
|
/**
|
|
* @fn static zip_error get_compressed_data_offset(zip_file *zip, UINT64 *offset)
|
|
*
|
|
* @brief Gets compressed data offset.
|
|
*
|
|
* @param [in,out] zip If non-null, the zip.
|
|
* @param [in,out] offset If non-null, the offset.
|
|
*
|
|
* @return The compressed data offset.
|
|
*/
|
|
|
|
archive_file::error zip_file_impl::get_compressed_data_offset(std::uint64_t &offset)
|
|
{
|
|
// 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
|
|
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 = m_header.local_header_offset + ZIPNAME;
|
|
offset += read_word(&m_buffer[ZIPFNLN]);
|
|
offset += read_word(&m_buffer[ZIPXTRALN]);
|
|
|
|
return archive_file::error::NONE;
|
|
}
|
|
|
|
|
|
|
|
/***************************************************************************
|
|
DECOMPRESSION INTERFACES
|
|
***************************************************************************/
|
|
|
|
/*-------------------------------------------------
|
|
decompress_data_type_0 - "decompress"
|
|
type 0 data (which is uncompressed)
|
|
-------------------------------------------------*/
|
|
|
|
/**
|
|
* @fn static zip_error decompress_data_type_0(zip_file *zip, UINT64 offset, void *buffer, UINT32 length)
|
|
*
|
|
* @brief Decompress the data type 0.
|
|
*
|
|
* @param [in,out] zip If non-null, the zip.
|
|
* @param offset The offset.
|
|
* @param [in,out] buffer If non-null, the buffer.
|
|
* @param length The length.
|
|
*
|
|
* @return A zip_error.
|
|
*/
|
|
|
|
archive_file::error zip_file_impl::decompress_data_type_0(std::uint64_t offset, void *buffer, std::uint32_t length)
|
|
{
|
|
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 archive_file::error::NONE;
|
|
}
|
|
|
|
|
|
/*-------------------------------------------------
|
|
decompress_data_type_8 - decompress
|
|
type 8 data (which is deflated)
|
|
-------------------------------------------------*/
|
|
|
|
/**
|
|
* @fn static zip_error decompress_data_type_8(zip_file *zip, UINT64 offset, void *buffer, UINT32 length)
|
|
*
|
|
* @brief Decompress the data type 8.
|
|
*
|
|
* @param [in,out] zip If non-null, the zip.
|
|
* @param offset The offset.
|
|
* @param [in,out] buffer If non-null, the buffer.
|
|
* @param length The length.
|
|
*
|
|
* @return A zip_error.
|
|
*/
|
|
|
|
archive_file::error zip_file_impl::decompress_data_type_8(std::uint64_t offset, void *buffer, std::uint32_t length)
|
|
{
|
|
std::uint32_t input_remaining = m_header.compressed_length;
|
|
int zerr;
|
|
|
|
// 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
|
|
zerr = inflateInit2(&stream, -MAX_WBITS);
|
|
if (zerr != Z_OK)
|
|
return archive_file::error::DECOMPRESS_ERROR;
|
|
|
|
// loop until we're done
|
|
while (1)
|
|
{
|
|
// 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 archive_file::error::FILE_ERROR;
|
|
}
|
|
offset += read_length;
|
|
|
|
// if we read nothing, but still have data left, the file is truncated
|
|
if (read_length == 0 && input_remaining > 0)
|
|
{
|
|
inflateEnd(&stream);
|
|
return archive_file::error::FILE_TRUNCATED;
|
|
}
|
|
|
|
// 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
|
|
if (input_remaining == 0)
|
|
stream.avail_in++;
|
|
|
|
// now inflate
|
|
zerr = inflate(&stream, Z_NO_FLUSH);
|
|
if (zerr == Z_STREAM_END)
|
|
break;
|
|
if (zerr != Z_OK)
|
|
{
|
|
inflateEnd(&stream);
|
|
return archive_file::error::DECOMPRESS_ERROR;
|
|
}
|
|
}
|
|
|
|
// finish decompression
|
|
zerr = inflateEnd(&stream);
|
|
if (zerr != Z_OK)
|
|
return archive_file::error::DECOMPRESS_ERROR;
|
|
|
|
/* if anything looks funny, report an error */
|
|
if (stream.avail_out > 0 || input_remaining > 0)
|
|
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;
|
|
}
|
|
|
|
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
|