280 lines
5.4 KiB
C++
280 lines
5.4 KiB
C++
|
/*
|
||
|
* unrar.cpp
|
||
|
* ---------
|
||
|
* Purpose: Implementation file for extracting modules from .rar archives
|
||
|
* 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 "unrar.h"
|
||
|
|
||
|
#ifdef MPT_WITH_UNRAR
|
||
|
|
||
|
#include "../common/mptFileIO.h"
|
||
|
|
||
|
#if MPT_OS_WINDOWS
|
||
|
#include <windows.h>
|
||
|
#else // !MPT_OS_WINDOWS
|
||
|
#ifdef _UNIX
|
||
|
#define MPT_UNRAR_UNIX_WAS_DEFINED
|
||
|
#else
|
||
|
#define _UNIX
|
||
|
#endif
|
||
|
#endif // MPT_OS_WINDOWS
|
||
|
|
||
|
#include "unrar/dll.hpp"
|
||
|
|
||
|
#if !MPT_OS_WINDOWS
|
||
|
#ifndef MPT_UNRAR_UNIX_WAS_DEFINED
|
||
|
#undef _UNIX
|
||
|
#undef MPT_UNRAR_UNIX_WAS_DEFINED
|
||
|
#endif
|
||
|
#endif // !MPT_OS_WINDOWS
|
||
|
|
||
|
#endif // MPT_WITH_UNRAR
|
||
|
|
||
|
|
||
|
OPENMPT_NAMESPACE_BEGIN
|
||
|
|
||
|
|
||
|
#ifdef MPT_WITH_UNRAR
|
||
|
|
||
|
|
||
|
struct RARHandle // RAII
|
||
|
{
|
||
|
HANDLE rar = nullptr;
|
||
|
explicit RARHandle(HANDLE rar_) : rar(rar_) { return; }
|
||
|
RARHandle(const RARHandle &) = delete;
|
||
|
~RARHandle() { if(rar) RARCloseArchive(rar); }
|
||
|
|
||
|
operator HANDLE () const { return rar; }
|
||
|
};
|
||
|
|
||
|
|
||
|
static int CALLBACK RARCallback(unsigned int msg, LPARAM userData, LPARAM p1, LPARAM p2)
|
||
|
{
|
||
|
int result = 0;
|
||
|
CRarArchive *that = reinterpret_cast<CRarArchive *>(userData);
|
||
|
switch(msg)
|
||
|
{
|
||
|
case UCM_PROCESSDATA:
|
||
|
// Receive extracted data
|
||
|
that->RARCallbackProcessData(reinterpret_cast<const char *>(p1), p2);
|
||
|
result = 1;
|
||
|
break;
|
||
|
default:
|
||
|
// No support for passwords or volumes
|
||
|
result = -1;
|
||
|
break;
|
||
|
}
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
|
||
|
void CRarArchive::RARCallbackProcessData(const char * buf, std::size_t size)
|
||
|
{
|
||
|
if(!captureCurrentFile)
|
||
|
{
|
||
|
return;
|
||
|
}
|
||
|
mpt::append(data, buf, buf + size);
|
||
|
}
|
||
|
|
||
|
|
||
|
CRarArchive::CRarArchive(FileReader &file)
|
||
|
: ArchiveBase(file)
|
||
|
{
|
||
|
// NOTE:
|
||
|
// We open the archive twice, once for listing the contents in the
|
||
|
// constructor and once for actual decompression in ExtractFile.
|
||
|
// For solid archives, listing the contents via RAR_OM_LIST is way faster.
|
||
|
// The overhead of opening twice for non-solid archives is negligable if the
|
||
|
// archive does not contain a lot of files (and archives with large amount of
|
||
|
// files are pretty useless for OpenMPT anyway).
|
||
|
|
||
|
// Early reject files with no Rar! magic
|
||
|
// so that we do not have to instantiate OnDiskFileWrapper.
|
||
|
inFile.Rewind();
|
||
|
if(!inFile.ReadMagic("Rar!\x1A"))
|
||
|
{
|
||
|
return;
|
||
|
}
|
||
|
inFile.Rewind();
|
||
|
|
||
|
diskFile = std::make_unique<OnDiskFileWrapper>(inFile);
|
||
|
if(!diskFile->IsValid())
|
||
|
{
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
std::wstring ArcName = diskFile->GetFilename().ToWide();
|
||
|
std::vector<wchar_t> ArcNameBuf(ArcName.c_str(), ArcName.c_str() + ArcName.length() + 1);
|
||
|
std::vector<wchar_t> CmtBuf(65536);
|
||
|
RAROpenArchiveDataEx ArchiveData = {};
|
||
|
ArchiveData.OpenMode = RAR_OM_LIST;
|
||
|
ArchiveData.ArcNameW = ArcNameBuf.data();
|
||
|
ArchiveData.CmtBufW = CmtBuf.data();
|
||
|
ArchiveData.CmtBufSize = static_cast<unsigned int>(CmtBuf.size());
|
||
|
RARHandle rar(RAROpenArchiveEx(&ArchiveData));
|
||
|
if(!rar)
|
||
|
{
|
||
|
Reset();
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
switch(ArchiveData.CmtState)
|
||
|
{
|
||
|
case 1:
|
||
|
if(ArchiveData.CmtSize)
|
||
|
{
|
||
|
comment = mpt::ToUnicode(std::wstring(ArchiveData.CmtBufW, ArchiveData.CmtBufW + ArchiveData.CmtSize - 1));
|
||
|
break;
|
||
|
}
|
||
|
[[fallthrough]];
|
||
|
case 0:
|
||
|
comment = mpt::ustring();
|
||
|
break;
|
||
|
default:
|
||
|
Reset();
|
||
|
return;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
bool eof = false;
|
||
|
int RARResult = 0;
|
||
|
while(!eof)
|
||
|
{
|
||
|
RARHeaderDataEx HeaderData = {};
|
||
|
RARResult = RARReadHeaderEx(rar, &HeaderData);
|
||
|
switch(RARResult)
|
||
|
{
|
||
|
case ERAR_SUCCESS:
|
||
|
break;
|
||
|
case ERAR_END_ARCHIVE:
|
||
|
eof = true;
|
||
|
continue;
|
||
|
break;
|
||
|
default:
|
||
|
Reset();
|
||
|
return;
|
||
|
break;
|
||
|
}
|
||
|
ArchiveFileInfo fileInfo;
|
||
|
fileInfo.name = mpt::PathString::FromWide(HeaderData.FileNameW);
|
||
|
fileInfo.type = ArchiveFileType::Normal;
|
||
|
fileInfo.size = HeaderData.UnpSize;
|
||
|
contents.push_back(fileInfo);
|
||
|
RARResult = RARProcessFileW(rar, RAR_SKIP, NULL, NULL);
|
||
|
switch(RARResult)
|
||
|
{
|
||
|
case ERAR_SUCCESS:
|
||
|
break;
|
||
|
default:
|
||
|
Reset();
|
||
|
return;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
|
||
|
CRarArchive::~CRarArchive()
|
||
|
{
|
||
|
}
|
||
|
|
||
|
|
||
|
bool CRarArchive::ExtractFile(std::size_t index)
|
||
|
{
|
||
|
|
||
|
if(!diskFile || !diskFile->IsValid())
|
||
|
{
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
if(index >= contents.size())
|
||
|
{
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
std::wstring ArcName = diskFile->GetFilename().ToWide();
|
||
|
std::vector<wchar_t> ArcNameBuf(ArcName.c_str(), ArcName.c_str() + ArcName.length() + 1);
|
||
|
RAROpenArchiveDataEx ArchiveData = {};
|
||
|
ArchiveData.OpenMode = RAR_OM_EXTRACT;
|
||
|
ArchiveData.ArcNameW = ArcNameBuf.data();
|
||
|
ArchiveData.Callback = RARCallback;
|
||
|
ArchiveData.UserData = reinterpret_cast<LPARAM>(this);
|
||
|
RARHandle rar(RAROpenArchiveEx(&ArchiveData));
|
||
|
if(!rar)
|
||
|
{
|
||
|
ResetFile();
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
std::size_t i = 0;
|
||
|
int RARResult = 0;
|
||
|
bool eof = false;
|
||
|
while(!eof)
|
||
|
{
|
||
|
RARHeaderDataEx HeaderData = {};
|
||
|
RARResult = RARReadHeaderEx(rar, &HeaderData);
|
||
|
switch(RARResult)
|
||
|
{
|
||
|
case ERAR_SUCCESS:
|
||
|
break;
|
||
|
case ERAR_END_ARCHIVE:
|
||
|
eof = true;
|
||
|
continue;
|
||
|
break;
|
||
|
default:
|
||
|
ResetFile();
|
||
|
return false;
|
||
|
break;
|
||
|
}
|
||
|
captureCurrentFile = (i == index);
|
||
|
RARResult = RARProcessFileW(rar, captureCurrentFile ? RAR_TEST : RAR_SKIP, NULL, NULL);
|
||
|
switch(RARResult)
|
||
|
{
|
||
|
case ERAR_SUCCESS:
|
||
|
break;
|
||
|
default:
|
||
|
ResetFile();
|
||
|
return false;
|
||
|
break;
|
||
|
}
|
||
|
if(captureCurrentFile)
|
||
|
{ // done
|
||
|
return true;
|
||
|
}
|
||
|
captureCurrentFile = false;
|
||
|
++i;
|
||
|
}
|
||
|
|
||
|
return false;
|
||
|
|
||
|
}
|
||
|
|
||
|
|
||
|
void CRarArchive::Reset()
|
||
|
{
|
||
|
captureCurrentFile = false;
|
||
|
comment = mpt::ustring();
|
||
|
contents = std::vector<ArchiveFileInfo>();
|
||
|
data = std::vector<char>();
|
||
|
}
|
||
|
|
||
|
|
||
|
void CRarArchive::ResetFile()
|
||
|
{
|
||
|
captureCurrentFile = false;
|
||
|
data = std::vector<char>();
|
||
|
}
|
||
|
|
||
|
|
||
|
#endif // MPT_WITH_UNRAR
|
||
|
|
||
|
|
||
|
OPENMPT_NAMESPACE_END
|