Files
RMG/Source/RMG-Core/Archive.cpp
T

575 lines
17 KiB
C++

/*
* Rosalie's Mupen GUI - https://github.com/Rosalie241/RMG
* Copyright (C) 2020 Rosalie Wanders <rosalie@mailbox.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 3.
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include "Archive.hpp"
#include "String.hpp"
#include "Error.hpp"
#include <fstream>
#include <cstring>
// lzma includes
#include "../3rdParty/lzma/7zTypes.h"
#include "../3rdParty/lzma/7z.h"
#include "../3rdParty/lzma/7zAlloc.h"
#include "../3rdParty/lzma/7zBuf.h"
#include "../3rdParty/lzma/7zCrc.h"
#include "../3rdParty/lzma/7zFile.h"
#include "../3rdParty/lzma/7zVersion.h"
// minizip includes
#include <unzip.h>
#include <zlib.h>
//
// Local Defines
//
#define UNZIP_READ_SIZE 67108860 /* 64 MiB */
//
// Local Functions
//
static voidpf zlib_filefunc_open(voidpf opaque, const void* filename, int mode)
{
std::filesystem::path path = *(std::filesystem::path*)filename;
// we need a static filestream
static std::ifstream fileStream;
// attempt to open file
fileStream.open(path, std::ios::binary);
if (!fileStream.is_open())
{
return nullptr;
}
return (voidpf)&fileStream;
}
static uLong zlib_filefunc_read(voidpf opaque, voidpf stream, void* buf, uLong size)
{
std::ifstream* fileStream = (std::ifstream*)stream;
fileStream->read((char*)buf, size);
return fileStream->fail() ? fileStream->gcount() : size;
}
static ZPOS64_T zlib_filefunc_tell(voidpf opaque, voidpf stream)
{
std::ifstream* fileStream = (std::ifstream*)stream;
return fileStream->tellg();
}
static long zlib_filefunc_seek(voidpf opaque, voidpf stream, ZPOS64_T offset, int origin)
{
std::ifstream* fileStream = (std::ifstream*)stream;
std::ios_base::seekdir seekOrigin;
switch (origin)
{
default:
return 0;
case ZLIB_FILEFUNC_SEEK_CUR:
seekOrigin = std::ios::cur;
break;
case ZLIB_FILEFUNC_SEEK_END:
seekOrigin = std::ios::end;
break;
case ZLIB_FILEFUNC_SEEK_SET:
seekOrigin = std::ios::beg;
break;
}
fileStream->seekg(offset, seekOrigin);
return fileStream->fail() ? -1 : 0;
}
static int zlib_filefunc_close(voidpf opaque, voidpf stream)
{
std::ifstream* fileStream = (std::ifstream*)stream;
fileStream->close();
return fileStream->fail() ? -1 : 0;
}
static int zlib_filefunc_testerror(voidpf opaque, voidpf stream)
{
return errno;
}
//
// Exported Functions
//
bool CoreReadZipFile(std::filesystem::path file, std::filesystem::path& extractedFileName, bool& isDisk, std::vector<char>& outBuffer)
{
std::string error;
std::fstream fileStream;
unzFile zipFile;
unz_global_info64 zipInfo;
zlib_filefunc64_def filefuncs;
filefuncs.zopen64_file = zlib_filefunc_open;
filefuncs.zread_file = zlib_filefunc_read;
filefuncs.zwrite_file = nullptr;
filefuncs.ztell64_file = zlib_filefunc_tell;
filefuncs.zseek64_file = zlib_filefunc_seek;
filefuncs.zclose_file = zlib_filefunc_close;
filefuncs.zerror_file = zlib_filefunc_testerror;
filefuncs.opaque = nullptr;
zipFile = unzOpen2_64((const void*)&file, &filefuncs);
if (zipFile == nullptr)
{
if (errno == 0)
{
error = "CoreReadZipFile: unzOpen Failed!";
}
else
{
error = "CoreReadZipFile: unzOpen Failed: ";
error += "failed to open file: ";
error += strerror(errno);
error += " (";
error += std::to_string(errno);
error += ")";
}
CoreSetError(error);
return false;
}
if (unzGetGlobalInfo64(zipFile, &zipInfo) != UNZ_OK)
{
error = "CoreReadZipFile: unzGetGlobalInfo Failed!";
CoreSetError(error);
return false;
}
for (uint64_t i = 0; i < zipInfo.number_entry; i++)
{
unz_file_info fileInfo;
char fileName[PATH_MAX];
// if we can't retrieve file info,
// skip the file
if (unzGetCurrentFileInfo(zipFile, &fileInfo, fileName, PATH_MAX, nullptr, 0, nullptr, 0) != UNZ_OK)
{
continue;
}
// make sure file has supported file format,
// if it does, read it in memory
std::filesystem::path fileNamePath;
// Windows sometimes throws an exception when assigning a string to a path
// due to being unable to convert the character sequence.
// so we have to catch the exception and do nothing
try
{
fileNamePath = fileName;
}
catch (...)
{
// ignore exception
}
std::string fileExtension = fileNamePath.has_extension() ? fileNamePath.extension().string() : "";
fileExtension = CoreLowerString(fileExtension);
if (fileExtension == ".z64" ||
fileExtension == ".v64" ||
fileExtension == ".n64" ||
fileExtension == ".ndd" ||
fileExtension == ".d64")
{
std::vector<char> buffer(UNZIP_READ_SIZE);
int bytes_read = 0;
outBuffer.reserve(UNZIP_READ_SIZE);
if (unzOpenCurrentFile(zipFile) != UNZ_OK)
{
error = "CoreReadZipFile Failed: unzOpenCurrentFile Failed!";
CoreSetError(error);
return false;
}
do
{
bytes_read = unzReadCurrentFile(zipFile, buffer.data(), UNZIP_READ_SIZE);
if (bytes_read < 0)
{
unzCloseCurrentFile(zipFile);
unzClose(zipFile);
error = "CoreReadZipFile Failed: unzReadCurrentFile Failed: ";
error += std::to_string(bytes_read);
CoreSetError(error);
return false;
}
else if (bytes_read > 0)
{
outBuffer.insert(outBuffer.end(), buffer.begin(), std::next(buffer.begin(), bytes_read));
}
} while (bytes_read > 0);
extractedFileName = fileNamePath;
isDisk = (fileExtension == ".ndd" || fileExtension == ".d64");
unzCloseCurrentFile(zipFile);
unzClose(zipFile);
return true;
}
// break when we've iterated over all entries
if ((i + 1) >= zipInfo.number_entry)
{
break;
}
// move to next file
if (unzGoToNextFile(zipFile) != UNZ_OK)
{
unzClose(zipFile);
error = "CoreReadZipFile Failed: unzGoToNextFile Failed!";
CoreSetError(error);
return false;
}
}
error = "CoreReadZipFile Failed: no valid ROMs found in zip!";
CoreSetError(error);
unzClose(zipFile);
return false;
}
bool CoreRead7zipFile(std::filesystem::path file, std::filesystem::path& extractedFileName, bool& isDisk, std::vector<char>& outBuffer)
{
std::string error;
const ISzAlloc alloc = { SzAlloc, SzFree };
const size_t bufSize = ((size_t)1 << 18);
ISzAlloc allocImp;
ISzAlloc allocTempImp;
CFileInStream archiveStream;
CLookToRead2 lookStream;
CSzArEx db;
SRes res;
// initialize allocator
allocImp = alloc;
allocTempImp = alloc;
// try to open file
#ifdef _WIN32
WRes wres = InFile_OpenW(&archiveStream.file, file.wstring().c_str());
#else
WRes wres = InFile_Open(&archiveStream.file, file.string().c_str());
#endif // _WIN32
if (wres != 0)
{
error = "CoreRead7zipFile Failed: InFile_Open Failed: ";
error += std::to_string(wres);
CoreSetError(error);
return false;
}
// create vtables for streams
FileInStream_CreateVTable(&archiveStream);
archiveStream.wres = 0;
LookToRead2_CreateVTable(&lookStream, 0);
lookStream.buf = nullptr;
// allocate memory look reader
lookStream.buf = (Byte*)ISzAlloc_Alloc(&allocImp, bufSize);
if (lookStream.buf == nullptr)
{
error = "CoreRead7zipFile Failed: ISzAlloc_Alloc Failed!";
CoreSetError(error);
return false;
}
// initialize look reader
lookStream.bufSize = bufSize;
lookStream.realStream = &archiveStream.vt;
LookToRead2_INIT(&lookStream);
// initialize CRC table
CrcGenerateTable();
// initialize archive
SzArEx_Init(&db);
// try to open file
res = SzArEx_Open(&db, &lookStream.vt, &allocImp, &allocTempImp);
if (res != SZ_OK)
{
error = "CoreRead7zipFile Failed: SzArEx_Open Failed: ";
error += std::to_string(res);
CoreSetError(error);
SzArEx_Free(&db, &allocImp);
ISzAlloc_Free(&allocImp, lookStream.buf);
return false;
}
for (uint32_t i = 0; i < db.NumFiles; i++)
{
size_t filename_size = 0;
uint16_t fileName[PATH_MAX];
// skip directories
if (SzArEx_IsDir(&db, i))
{
continue;
}
// skip when filename size exceeds our buffer size
filename_size = SzArEx_GetFileNameUtf16(&db, i, nullptr);
if (filename_size > PATH_MAX)
{
continue;
}
SzArEx_GetFileNameUtf16(&db, i, fileName);
std::filesystem::path fileNamePath;
// Windows sometimes throws an exception when assigning a string to a path
// due to being unable to convert the character sequence.
// so we have to catch the exception and do nothing
try
{
fileNamePath = (char16_t*)fileName;
}
catch (...)
{
// ignore exception
}
std::string fileExtension = fileNamePath.has_extension() ? fileNamePath.extension().string() : "";
fileExtension = CoreLowerString(fileExtension);
if (fileExtension == ".z64" ||
fileExtension == ".v64" ||
fileExtension == ".n64" ||
fileExtension == ".ndd" ||
fileExtension == ".d64")
{
uint32_t blockIndex = 0xFFFFFFFF;
uint8_t* readBuffer = nullptr;
size_t readBufferSize = 0;
size_t offset = 0;
size_t outSizeProcessed = 0;
res = SzArEx_Extract(&db, &lookStream.vt, i,
&blockIndex, &readBuffer, &readBufferSize,
&offset, &outSizeProcessed,
&allocImp, &allocTempImp);
if (res != SZ_OK)
{
error = "CoreRead7zipFile Failed: SzArEx_Extract Failed: ";
error += std::to_string(res);
CoreSetError(error);
SzArEx_Free(&db, &allocImp);
ISzAlloc_Free(&allocImp, lookStream.buf);
File_Close(&archiveStream.file);
ISzAlloc_Free(&allocImp, readBuffer);
return false;
}
outBuffer.insert(outBuffer.begin(), readBuffer, readBuffer + outSizeProcessed);
extractedFileName = fileNamePath;
isDisk = (fileExtension == ".ndd" || fileExtension == ".d64");
SzArEx_Free(&db, &allocImp);
ISzAlloc_Free(&allocImp, lookStream.buf);
File_Close(&archiveStream.file);
ISzAlloc_Free(&allocImp, readBuffer);
return true;
}
}
// cleanup
SzArEx_Free(&db, &allocImp);
ISzAlloc_Free(&allocImp, lookStream.buf);
File_Close(&archiveStream.file);
error = "CoreRead7zipFile Failed: no valid ROMs found in 7zip!";
CoreSetError(error);
return false;
}
bool CoreReadArchiveFile(std::filesystem::path file, std::filesystem::path& extractedFileName, bool& isDisk, std::vector<char>& outBuffer)
{
std::string file_extension;
file_extension = file.has_extension() ? file.extension().string() : "";
file_extension = CoreLowerString(file_extension);
if (file_extension != ".zip" && file_extension != ".7z")
{
return false;
}
if (file_extension == ".zip")
{
if (!CoreReadZipFile(file, extractedFileName, isDisk, outBuffer))
{
return false;
}
}
else
{
if (!CoreRead7zipFile(file, extractedFileName, isDisk, outBuffer))
{
return false;
}
}
return true;
}
bool CoreUnzip(std::filesystem::path file, std::filesystem::path path)
{
std::string error;
std::filesystem::path targetPath;
std::filesystem::path fileNamePath;
unzFile zipFile;
unz_global_info zipInfo;
std::ofstream outputStream;
std::vector<char> readBuffer(UNZIP_READ_SIZE);
int bytes_read = 0;
zlib_filefunc64_def filefuncs;
filefuncs.zopen64_file = zlib_filefunc_open;
filefuncs.zread_file = zlib_filefunc_read;
filefuncs.zwrite_file = nullptr;
filefuncs.ztell64_file = zlib_filefunc_tell;
filefuncs.zseek64_file = zlib_filefunc_seek;
filefuncs.zclose_file = zlib_filefunc_close;
filefuncs.zerror_file = zlib_filefunc_testerror;
filefuncs.opaque = nullptr;
zipFile = unzOpen2_64((const void*)&file, &filefuncs);
if (zipFile == nullptr)
{
error = "CoreUnzip: unzOpen Failed!";
CoreSetError(error);
return false;
}
if (unzGetGlobalInfo(zipFile, &zipInfo) != UNZ_OK)
{
error = "CoreUnzip: unzGetGlobalInfo Failed!";
CoreSetError(error);
return false;
}
for (uint64_t i = 0; i < zipInfo.number_entry; i++)
{
unz_file_info fileInfo;
char fileName[PATH_MAX];
// ensure we can retrieve the current file info
if (unzGetCurrentFileInfo(zipFile, &fileInfo, fileName, PATH_MAX, nullptr, 0, nullptr, 0) != UNZ_OK)
{
unzClose(zipFile);
error = "CoreUnzip: unzGetCurrentFileInfo Failed!";
CoreSetError(error);
return false;
}
fileNamePath = fileName;
targetPath = path;
targetPath += "/";
targetPath += fileName;
if (fileNamePath.has_filename() &&
fileNamePath.has_parent_path())
{ // create parent directories
try
{
if (!std::filesystem::is_directory(targetPath.parent_path()) &&
!std::filesystem::create_directories(targetPath.parent_path()))
{
throw std::exception();
}
}
catch (...)
{
unzClose(zipFile);
error = "CoreUnzip: std::filesystem::create_directory(";
error += targetPath.string();
error += ") Failed!";
CoreSetError(error);
return false;
}
}
if (fileNamePath.has_filename())
{ // extract file
if (unzOpenCurrentFile(zipFile) != UNZ_OK)
{
unzClose(zipFile);
error = "CoreUnzip: unzOpenCurrentFile Failed!";
CoreSetError(error);
return false;
}
outputStream.open(targetPath, std::ios::trunc | std::ios::binary);
if (!outputStream.is_open())
{
unzCloseCurrentFile(zipFile);
unzClose(zipFile);
error = "CoreUnzip: failed to open file: ";
error += targetPath.string();
CoreSetError(error);
return false;
}
do
{
bytes_read = unzReadCurrentFile(zipFile, readBuffer.data(), UNZIP_READ_SIZE);
if (bytes_read < 0)
{
unzCloseCurrentFile(zipFile);
unzClose(zipFile);
error = "CoreUnzip: unzReadCurrentFile Failed!";
CoreSetError(error);
return false;
}
else if (bytes_read > 0)
{ // write data to file
outputStream.write(readBuffer.data(), bytes_read);
}
} while (bytes_read > 0);
outputStream.close();
unzCloseCurrentFile(zipFile);
}
// break when we've iterated over all entries
if ((i + 1) >= zipInfo.number_entry)
{
break;
}
// move to next file
if (unzGoToNextFile(zipFile) != UNZ_OK)
{
unzClose(zipFile);
error = "CoreUnzip: unzGoToNextFile Failed!";
CoreSetError(error);
return false;
}
}
unzClose(zipFile);
return true;
}