winamp/Src/external_dependencies/openmpt-trunk/unarchiver/unzip.cpp
2024-09-24 14:54:57 +02:00

315 lines
6.9 KiB
C++

/*
* unzip.cpp
* ---------
* Purpose: Implementation file for extracting modules from .zip archives, making use of MiniZip (from the zlib contrib package)
* Notes : (currently none)
* Authors: OpenMPT Devs
* The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
*/
#include "stdafx.h"
#include "../common/FileReader.h"
#include "unzip.h"
#include "../common/misc_util.h"
#include <algorithm>
#include <vector>
#if defined(MPT_WITH_ZLIB) && defined(MPT_WITH_MINIZIP)
#include <contrib/minizip/unzip.h>
#elif defined(MPT_WITH_MINIZ)
#include <miniz/miniz.h>
#endif
OPENMPT_NAMESPACE_BEGIN
#if defined(MPT_WITH_ZLIB) && defined(MPT_WITH_MINIZIP)
// Low-level file abstractions for in-memory file handling
struct ZipFileAbstraction
{
static voidpf ZCALLBACK fopen64_mem(voidpf opaque, const void *, int mode)
{
FileReader &file = *static_cast<FileReader *>(opaque);
if((mode & ZLIB_FILEFUNC_MODE_READWRITEFILTER) == ZLIB_FILEFUNC_MODE_WRITE)
{
return nullptr;
} else
{
file.Rewind();
return opaque;
}
}
static uLong ZCALLBACK fread_mem(voidpf opaque, voidpf, void *buf, uLong size)
{
FileReader &file = *static_cast<FileReader *>(opaque);
return mpt::saturate_cast<uLong>(file.ReadRaw(mpt::span(mpt::void_cast<std::byte*>(buf), size)).size());
}
static uLong ZCALLBACK fwrite_mem(voidpf, voidpf, const void *, uLong)
{
return 0;
}
static ZPOS64_T ZCALLBACK ftell64_mem(voidpf opaque, voidpf)
{
FileReader &file = *static_cast<FileReader *>(opaque);
return file.GetPosition();
}
static long ZCALLBACK fseek64_mem(voidpf opaque, voidpf, ZPOS64_T offset, int origin)
{
FileReader &file = *static_cast<FileReader *>(opaque);
ZPOS64_T destination = 0;
switch(origin)
{
case ZLIB_FILEFUNC_SEEK_CUR:
destination = static_cast<ZPOS64_T>(file.GetPosition()) + offset;
break;
case ZLIB_FILEFUNC_SEEK_END:
destination = static_cast<ZPOS64_T>(file.GetLength()) + offset;
break;
case ZLIB_FILEFUNC_SEEK_SET:
destination = offset;
break;
default:
return -1;
}
if(!mpt::in_range<FileReader::off_t>(destination))
{
return 1;
}
return (file.Seek(static_cast<FileReader::off_t>(destination)) ? 0 : 1);
}
static int ZCALLBACK fclose_mem(voidpf, voidpf)
{
return 0;
}
static int ZCALLBACK ferror_mem(voidpf, voidpf)
{
return 0;
}
};
CZipArchive::CZipArchive(FileReader &file)
: ArchiveBase(file)
, zipFile(nullptr)
{
zlib_filefunc64_def functions =
{
ZipFileAbstraction::fopen64_mem,
ZipFileAbstraction::fread_mem,
ZipFileAbstraction::fwrite_mem,
ZipFileAbstraction::ftell64_mem,
ZipFileAbstraction::fseek64_mem,
ZipFileAbstraction::fclose_mem,
ZipFileAbstraction::ferror_mem,
&inFile
};
zipFile = unzOpen2_64(nullptr, &functions);
if(zipFile == nullptr)
{
return;
}
// read comment
{
unz_global_info info;
if(unzGetGlobalInfo(zipFile, &info) == UNZ_OK)
{
if(info.size_comment > 0)
{
if(info.size_comment < Util::MaxValueOfType(info.size_comment))
{
info.size_comment++;
}
std::vector<char> commentData(info.size_comment);
if(unzGetGlobalComment(zipFile, commentData.data(), info.size_comment) >= 0)
{
commentData[info.size_comment - 1] = '\0';
comment = mpt::ToUnicode(mpt::IsUTF8(commentData.data()) ? mpt::Charset::UTF8 : mpt::Charset::CP437, commentData.data());
}
}
}
}
// read contents
unz_file_pos curFile;
int status = unzGoToFirstFile(zipFile);
unzGetFilePos(zipFile, &curFile);
while(status == UNZ_OK)
{
ArchiveFileInfo fileinfo;
fileinfo.type = ArchiveFileType::Normal;
unz_file_info info;
char name[256];
unzGetCurrentFileInfo(zipFile, &info, name, sizeof(name), nullptr, 0, nullptr, 0);
fileinfo.name = mpt::PathString::FromUnicode(mpt::ToUnicode((info.flag & (1<<11)) ? mpt::Charset::UTF8 : mpt::Charset::CP437, std::string(name)));
fileinfo.size = info.uncompressed_size;
unzGetFilePos(zipFile, &curFile);
fileinfo.cookie1 = curFile.pos_in_zip_directory;
fileinfo.cookie2 = curFile.num_of_file;
contents.push_back(fileinfo);
status = unzGoToNextFile(zipFile);
}
}
CZipArchive::~CZipArchive()
{
unzClose(zipFile);
}
bool CZipArchive::ExtractFile(std::size_t index)
{
if(index >= contents.size())
{
return false;
}
data.clear();
unz_file_pos bestFile;
unz_file_info info;
bestFile.pos_in_zip_directory = static_cast<uLong>(contents[index].cookie1);
bestFile.num_of_file = static_cast<uLong>(contents[index].cookie2);
if(unzGoToFilePos(zipFile, &bestFile) == UNZ_OK && unzOpenCurrentFile(zipFile) == UNZ_OK)
{
unzGetCurrentFileInfo(zipFile, &info, nullptr, 0, nullptr, 0, nullptr, 0);
try
{
data.resize(info.uncompressed_size);
} catch(...)
{
unzCloseCurrentFile(zipFile);
return false;
}
unzReadCurrentFile(zipFile, data.data(), info.uncompressed_size);
unzCloseCurrentFile(zipFile);
return true;
}
return false;
}
#elif defined(MPT_WITH_MINIZ)
CZipArchive::CZipArchive(FileReader &file) : ArchiveBase(file)
{
zipFile = new mz_zip_archive();
mz_zip_archive *zip = static_cast<mz_zip_archive*>(zipFile);
(*zip) = {};
const auto fileData = file.GetRawData();
if(!mz_zip_reader_init_mem(zip, fileData.data(), fileData.size(), 0))
{
delete zip;
zip = nullptr;
zipFile = nullptr;
}
if(!zip)
{
return;
}
for(mz_uint i = 0; i < mz_zip_reader_get_num_files(zip); ++i)
{
ArchiveFileInfo info;
info.type = ArchiveFileType::Invalid;
mz_zip_archive_file_stat stat = {};
if(mz_zip_reader_file_stat(zip, i, &stat))
{
info.type = ArchiveFileType::Normal;
info.name = mpt::PathString::FromUnicode(mpt::ToUnicode((stat.m_bit_flag & (1<<11)) ? mpt::Charset::UTF8 : mpt::Charset::CP437, stat.m_filename));
info.size = stat.m_uncomp_size;
}
if(mz_zip_reader_is_file_a_directory(zip, i))
{
info.type = ArchiveFileType::Special;
} else if(mz_zip_reader_is_file_encrypted(zip, i))
{
info.type = ArchiveFileType::Special;
}
contents.push_back(info);
}
}
CZipArchive::~CZipArchive()
{
mz_zip_archive *zip = static_cast<mz_zip_archive*>(zipFile);
if(zip)
{
mz_zip_reader_end(zip);
delete zip;
zipFile = nullptr;
}
}
bool CZipArchive::ExtractFile(std::size_t index)
{
mz_zip_archive *zip = static_cast<mz_zip_archive*>(zipFile);
if(index >= contents.size())
{
return false;
}
mz_uint bestFile = index;
mz_zip_archive_file_stat stat = {};
mz_zip_reader_file_stat(zip, bestFile, &stat);
if(stat.m_uncomp_size >= std::numeric_limits<std::size_t>::max())
{
return false;
}
try
{
data.resize(static_cast<std::size_t>(stat.m_uncomp_size));
} catch(...)
{
return false;
}
if(!mz_zip_reader_extract_to_mem(zip, bestFile, data.data(), static_cast<std::size_t>(stat.m_uncomp_size), 0))
{
return false;
}
comment = mpt::ToUnicode(mpt::Charset::CP437, std::string(stat.m_comment, stat.m_comment + stat.m_comment_size));
return true;
}
#endif // MPT_WITH_ZLIB || MPT_WITH_MINIZ
OPENMPT_NAMESPACE_END