mirror of
https://github.com/Xevion/easy7zip.git
synced 2025-12-06 15:14:59 -06:00
641 lines
16 KiB
C++
Executable File
641 lines
16 KiB
C++
Executable File
// WimHandler.cpp
|
|
|
|
#include "StdAfx.h"
|
|
|
|
#include "../../../../C/CpuArch.h"
|
|
|
|
#include "Common/ComTry.h"
|
|
#include "Common/IntToString.h"
|
|
#include "Common/StringToInt.h"
|
|
#include "Common/UTFConvert.h"
|
|
|
|
#include "Windows/PropVariant.h"
|
|
|
|
#include "../../Common/ProgressUtils.h"
|
|
#include "../../Common/StreamUtils.h"
|
|
|
|
#include "WimHandler.h"
|
|
|
|
#define Get16(p) GetUi16(p)
|
|
#define Get32(p) GetUi32(p)
|
|
#define Get64(p) GetUi64(p)
|
|
|
|
using namespace NWindows;
|
|
|
|
namespace NArchive {
|
|
namespace NWim {
|
|
|
|
#define WIM_DETAILS
|
|
|
|
static STATPROPSTG kProps[] =
|
|
{
|
|
{ NULL, kpidPath, VT_BSTR},
|
|
{ NULL, kpidIsDir, VT_BOOL},
|
|
{ NULL, kpidSize, VT_UI8},
|
|
{ NULL, kpidPackSize, VT_UI8},
|
|
{ NULL, kpidMTime, VT_FILETIME},
|
|
{ NULL, kpidCTime, VT_FILETIME},
|
|
{ NULL, kpidATime, VT_FILETIME},
|
|
{ NULL, kpidAttrib, VT_UI4},
|
|
{ NULL, kpidMethod, VT_BSTR},
|
|
{ NULL, kpidShortName, VT_BSTR}
|
|
|
|
#ifdef WIM_DETAILS
|
|
, { NULL, kpidVolume, VT_UI4}
|
|
, { NULL, kpidOffset, VT_UI8}
|
|
, { NULL, kpidLinks, VT_UI4}
|
|
#endif
|
|
};
|
|
|
|
static STATPROPSTG kArcProps[] =
|
|
{
|
|
{ NULL, kpidSize, VT_UI8},
|
|
{ NULL, kpidPackSize, VT_UI8},
|
|
{ NULL, kpidMethod, VT_BSTR},
|
|
{ NULL, kpidCTime, VT_FILETIME},
|
|
{ NULL, kpidMTime, VT_FILETIME},
|
|
{ NULL, kpidComment, VT_BSTR},
|
|
{ NULL, kpidIsVolume, VT_BOOL},
|
|
{ NULL, kpidVolume, VT_UI4},
|
|
{ NULL, kpidNumVolumes, VT_UI4}
|
|
};
|
|
|
|
static bool ParseNumber64(const AString &s, UInt64 &res)
|
|
{
|
|
const char *end;
|
|
if (s.Left(2) == "0x")
|
|
{
|
|
if (s.Length() == 2)
|
|
return false;
|
|
res = ConvertHexStringToUInt64((const char *)s + 2, &end);
|
|
}
|
|
else
|
|
{
|
|
if (s.IsEmpty())
|
|
return false;
|
|
res = ConvertStringToUInt64(s, &end);
|
|
}
|
|
return *end == 0;
|
|
}
|
|
|
|
static bool ParseNumber32(const AString &s, UInt32 &res)
|
|
{
|
|
UInt64 res64;
|
|
if (!ParseNumber64(s, res64) || res64 >= ((UInt64)1 << 32))
|
|
return false;
|
|
res = (UInt32)res64;
|
|
return true;
|
|
}
|
|
|
|
bool ParseTime(const CXmlItem &item, FILETIME &ft, const char *tag)
|
|
{
|
|
int index = item.FindSubTag(tag);
|
|
if (index >= 0)
|
|
{
|
|
const CXmlItem &timeItem = item.SubItems[index];
|
|
UInt32 low = 0, high = 0;
|
|
if (ParseNumber32(timeItem.GetSubStringForTag("LOWPART"), low) &&
|
|
ParseNumber32(timeItem.GetSubStringForTag("HIGHPART"), high))
|
|
{
|
|
ft.dwLowDateTime = low;
|
|
ft.dwHighDateTime = high;
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void CImageInfo::Parse(const CXmlItem &item)
|
|
{
|
|
CTimeDefined = ParseTime(item, CTime, "CREATIONTIME");
|
|
MTimeDefined = ParseTime(item, MTime, "LASTMODIFICATIONTIME");
|
|
NameDefined = ConvertUTF8ToUnicode(item.GetSubStringForTag("NAME"), Name);
|
|
// IndexDefined = ParseNumber32(item.GetPropertyValue("INDEX"), Index);
|
|
}
|
|
|
|
void CXml::ToUnicode(UString &s)
|
|
{
|
|
size_t size = Data.GetCapacity();
|
|
if (size < 2 || (size & 1) != 0 || size > (1 << 24))
|
|
return;
|
|
const Byte *p = Data;
|
|
if (Get16(p) != 0xFEFF)
|
|
return;
|
|
wchar_t *chars = s.GetBuffer((int)size / 2);
|
|
for (size_t i = 2; i < size; i += 2)
|
|
*chars++ = (wchar_t)Get16(p + i);
|
|
*chars = 0;
|
|
s.ReleaseBuffer();
|
|
}
|
|
|
|
void CXml::Parse()
|
|
{
|
|
UString s;
|
|
ToUnicode(s);
|
|
AString utf;
|
|
if (!ConvertUnicodeToUTF8(s, utf))
|
|
return;
|
|
::CXml xml;
|
|
if (!xml.Parse(utf))
|
|
return;
|
|
if (xml.Root.Name != "WIM")
|
|
return;
|
|
|
|
for (int i = 0; i < xml.Root.SubItems.Size(); i++)
|
|
{
|
|
const CXmlItem &item = xml.Root.SubItems[i];
|
|
if (item.IsTagged("IMAGE"))
|
|
{
|
|
CImageInfo imageInfo;
|
|
imageInfo.Parse(item);
|
|
Images.Add(imageInfo);
|
|
}
|
|
}
|
|
}
|
|
|
|
static const char *kMethodLZX = "LZX";
|
|
static const char *kMethodXpress = "XPress";
|
|
static const char *kMethodCopy = "Copy";
|
|
|
|
IMP_IInArchive_Props
|
|
IMP_IInArchive_ArcProps
|
|
|
|
STDMETHODIMP CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value)
|
|
{
|
|
COM_TRY_BEGIN
|
|
NWindows::NCOM::CPropVariant prop;
|
|
|
|
const CImageInfo *image = NULL;
|
|
if (_xmls.Size() == 1)
|
|
{
|
|
const CXml &xml = _xmls[0];
|
|
if (xml.Images.Size() == 1)
|
|
image = &xml.Images[0];
|
|
}
|
|
|
|
switch(propID)
|
|
{
|
|
case kpidSize: prop = _db.GetUnpackSize(); break;
|
|
case kpidPackSize: prop = _db.GetPackSize(); break;
|
|
|
|
case kpidCTime:
|
|
if (_xmls.Size() == 1)
|
|
{
|
|
const CXml &xml = _xmls[0];
|
|
int index = -1;
|
|
for (int i = 0; i < xml.Images.Size(); i++)
|
|
{
|
|
const CImageInfo &image = xml.Images[i];
|
|
if (image.CTimeDefined)
|
|
if (index < 0 || ::CompareFileTime(&image.CTime, &xml.Images[index].CTime) < 0)
|
|
index = i;
|
|
}
|
|
if (index >= 0)
|
|
prop = xml.Images[index].CTime;
|
|
}
|
|
break;
|
|
|
|
case kpidMTime:
|
|
if (_xmls.Size() == 1)
|
|
{
|
|
const CXml &xml = _xmls[0];
|
|
int index = -1;
|
|
for (int i = 0; i < xml.Images.Size(); i++)
|
|
{
|
|
const CImageInfo &image = xml.Images[i];
|
|
if (image.MTimeDefined)
|
|
if (index < 0 || ::CompareFileTime(&image.MTime, &xml.Images[index].MTime) > 0)
|
|
index = i;
|
|
}
|
|
if (index >= 0)
|
|
prop = xml.Images[index].MTime;
|
|
}
|
|
break;
|
|
|
|
case kpidComment:
|
|
if (image != NULL)
|
|
{
|
|
if (_xmlInComments)
|
|
{
|
|
UString s;
|
|
_xmls[0].ToUnicode(s);
|
|
prop = s;
|
|
}
|
|
else if (image->NameDefined)
|
|
prop = image->Name;
|
|
}
|
|
break;
|
|
|
|
case kpidIsVolume:
|
|
if (_xmls.Size() > 0)
|
|
{
|
|
UInt16 volIndex = _xmls[0].VolIndex;
|
|
if (volIndex < _volumes.Size())
|
|
prop = (_volumes[volIndex].Header.NumParts > 1);
|
|
}
|
|
break;
|
|
case kpidVolume:
|
|
if (_xmls.Size() > 0)
|
|
{
|
|
UInt16 volIndex = _xmls[0].VolIndex;
|
|
if (volIndex < _volumes.Size())
|
|
prop = (UInt32)_volumes[volIndex].Header.PartNumber;
|
|
}
|
|
break;
|
|
case kpidNumVolumes: if (_volumes.Size() > 0) prop = (UInt32)(_volumes.Size() - 1); break;
|
|
case kpidMethod:
|
|
{
|
|
bool lzx = false, xpress = false, copy = false;
|
|
for (int i = 0; i < _xmls.Size(); i++)
|
|
{
|
|
const CHeader &header = _volumes[_xmls[i].VolIndex].Header;
|
|
if (header.IsCompressed())
|
|
if (header.IsLzxMode())
|
|
lzx = true;
|
|
else
|
|
xpress = true;
|
|
else
|
|
copy = true;
|
|
}
|
|
AString res;
|
|
if (lzx)
|
|
res = kMethodLZX;
|
|
if (xpress)
|
|
{
|
|
if (!res.IsEmpty())
|
|
res += ' ';
|
|
res += kMethodXpress;
|
|
}
|
|
if (copy)
|
|
{
|
|
if (!res.IsEmpty())
|
|
res += ' ';
|
|
res += kMethodCopy;
|
|
}
|
|
prop = res;
|
|
}
|
|
}
|
|
prop.Detach(value);
|
|
return S_OK;
|
|
COM_TRY_END
|
|
}
|
|
|
|
STDMETHODIMP CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *value)
|
|
{
|
|
COM_TRY_BEGIN
|
|
NWindows::NCOM::CPropVariant prop;
|
|
if (index < (UInt32)_db.SortedItems.Size())
|
|
{
|
|
int realIndex = _db.SortedItems[index];
|
|
const CItem &item = _db.Items[realIndex];
|
|
const CStreamInfo *si = NULL;
|
|
const CVolume *vol = NULL;
|
|
if (item.StreamIndex >= 0)
|
|
{
|
|
si = &_db.Streams[item.StreamIndex];
|
|
vol = &_volumes[si->PartNumber];
|
|
}
|
|
|
|
switch(propID)
|
|
{
|
|
case kpidPath:
|
|
if (item.HasMetadata)
|
|
prop = _db.GetItemPath(realIndex);
|
|
else
|
|
{
|
|
char sz[32];
|
|
ConvertUInt64ToString(item.StreamIndex, sz);
|
|
AString s = sz;
|
|
while (s.Length() < _nameLenForStreams)
|
|
s = '0' + s;
|
|
/*
|
|
if (si->Resource.IsFree())
|
|
prefix = "[Free]";
|
|
*/
|
|
s = "[Files]" STRING_PATH_SEPARATOR + s;
|
|
prop = s;
|
|
}
|
|
break;
|
|
case kpidShortName: if (item.HasMetadata) prop = item.ShortName; break;
|
|
|
|
case kpidIsDir: prop = item.IsDir(); break;
|
|
case kpidAttrib: if (item.HasMetadata) prop = item.Attrib; break;
|
|
case kpidCTime: if (item.HasMetadata) prop = item.CTime; break;
|
|
case kpidATime: if (item.HasMetadata) prop = item.ATime; break;
|
|
case kpidMTime: if (item.HasMetadata) prop = item.MTime; break;
|
|
case kpidPackSize: prop = si ? si->Resource.PackSize : (UInt64)0; break;
|
|
case kpidSize: prop = si ? si->Resource.UnpackSize : (UInt64)0; break;
|
|
case kpidMethod: if (si) prop = si->Resource.IsCompressed() ?
|
|
(vol->Header.IsLzxMode() ? kMethodLZX : kMethodXpress) : kMethodCopy; break;
|
|
#ifdef WIM_DETAILS
|
|
case kpidVolume: if (si) prop = (UInt32)si->PartNumber; break;
|
|
case kpidOffset: if (si) prop = (UInt64)si->Resource.Offset; break;
|
|
case kpidLinks: prop = si ? (UInt32)si->RefCount : (UInt32)0; break;
|
|
#endif
|
|
}
|
|
}
|
|
else
|
|
{
|
|
index -= _db.SortedItems.Size();
|
|
{
|
|
switch(propID)
|
|
{
|
|
case kpidPath:
|
|
{
|
|
char sz[32];
|
|
ConvertUInt64ToString(_xmls[index].VolIndex, sz);
|
|
prop = (AString)"[" + (AString)sz + "].xml";
|
|
break;
|
|
}
|
|
case kpidIsDir: prop = false; break;
|
|
case kpidPackSize:
|
|
case kpidSize: prop = (UInt64)_xmls[index].Data.GetCapacity(); break;
|
|
case kpidMethod: prop = kMethodCopy; break;
|
|
}
|
|
}
|
|
}
|
|
prop.Detach(value);
|
|
return S_OK;
|
|
COM_TRY_END
|
|
}
|
|
|
|
class CVolumeName
|
|
{
|
|
// UInt32 _volIndex;
|
|
UString _before;
|
|
UString _after;
|
|
public:
|
|
CVolumeName() {};
|
|
|
|
void InitName(const UString &name)
|
|
{
|
|
// _volIndex = 1;
|
|
int dotPos = name.ReverseFind('.');
|
|
if (dotPos < 0)
|
|
dotPos = name.Length();
|
|
_before = name.Left(dotPos);
|
|
_after = name.Mid(dotPos);
|
|
}
|
|
|
|
UString GetNextName(UInt32 index)
|
|
{
|
|
wchar_t s[32];
|
|
ConvertUInt64ToString((index), s);
|
|
return _before + (UString)s + _after;
|
|
}
|
|
};
|
|
|
|
STDMETHODIMP CHandler::Open(IInStream *inStream,
|
|
const UInt64 * /* maxCheckStartPosition */,
|
|
IArchiveOpenCallback *openArchiveCallback)
|
|
{
|
|
COM_TRY_BEGIN
|
|
Close();
|
|
try
|
|
{
|
|
CMyComPtr<IArchiveOpenVolumeCallback> openVolumeCallback;
|
|
|
|
CVolumeName seqName;
|
|
if (openArchiveCallback != NULL)
|
|
openArchiveCallback->QueryInterface(IID_IArchiveOpenVolumeCallback, (void **)&openVolumeCallback);
|
|
|
|
UInt32 numVolumes = 1;
|
|
int firstVolumeIndex = -1;
|
|
for (UInt32 i = 1; i <= numVolumes; i++)
|
|
{
|
|
CMyComPtr<IInStream> curStream;
|
|
if (i != 1)
|
|
{
|
|
UString fullName = seqName.GetNextName(i);
|
|
HRESULT result = openVolumeCallback->GetStream(fullName, &curStream);
|
|
if (result == S_FALSE)
|
|
continue;
|
|
if (result != S_OK)
|
|
return result;
|
|
if (!curStream)
|
|
break;
|
|
}
|
|
else
|
|
curStream = inStream;
|
|
CHeader header;
|
|
HRESULT res = NWim::ReadHeader(curStream, header);
|
|
if (res != S_OK)
|
|
{
|
|
if (i == 1)
|
|
return res;
|
|
if (res == S_FALSE)
|
|
continue;
|
|
return res;
|
|
}
|
|
if (firstVolumeIndex >= 0)
|
|
if (!header.AreFromOnArchive(_volumes[firstVolumeIndex].Header))
|
|
break;
|
|
if (_volumes.Size() > header.PartNumber && _volumes[header.PartNumber].Stream)
|
|
break;
|
|
CXml xml;
|
|
xml.VolIndex = header.PartNumber;
|
|
res = _db.Open(curStream, header, xml.Data, openArchiveCallback);
|
|
if (res != S_OK)
|
|
{
|
|
if (i == 1)
|
|
return res;
|
|
if (res == S_FALSE)
|
|
continue;
|
|
return res;
|
|
}
|
|
|
|
while (_volumes.Size() <= header.PartNumber)
|
|
_volumes.Add(CVolume());
|
|
CVolume &volume = _volumes[header.PartNumber];
|
|
volume.Header = header;
|
|
volume.Stream = curStream;
|
|
|
|
firstVolumeIndex = header.PartNumber;
|
|
|
|
bool needAddXml = true;
|
|
if (_xmls.Size() != 0)
|
|
if (xml.Data == _xmls[0].Data)
|
|
needAddXml = false;
|
|
if (needAddXml)
|
|
{
|
|
xml.Parse();
|
|
_xmls.Add(xml);
|
|
}
|
|
|
|
if (i == 1)
|
|
{
|
|
if (header.PartNumber != 1)
|
|
break;
|
|
if (!openVolumeCallback)
|
|
break;
|
|
numVolumes = header.NumParts;
|
|
{
|
|
NCOM::CPropVariant prop;
|
|
RINOK(openVolumeCallback->GetProperty(kpidName, &prop));
|
|
if (prop.vt != VT_BSTR)
|
|
break;
|
|
seqName.InitName(prop.bstrVal);
|
|
}
|
|
}
|
|
}
|
|
|
|
_db.DetectPathMode();
|
|
RINOK(_db.Sort(_db.SkipRoot));
|
|
|
|
wchar_t sz[32];
|
|
ConvertUInt64ToString(_db.Streams.Size(), sz);
|
|
_nameLenForStreams = MyStringLen(sz);
|
|
|
|
_xmlInComments = (_xmls.Size() == 1 && !_db.ShowImageNumber);
|
|
}
|
|
catch(...)
|
|
{
|
|
return S_FALSE;
|
|
}
|
|
return S_OK;
|
|
COM_TRY_END
|
|
}
|
|
|
|
STDMETHODIMP CHandler::Close()
|
|
{
|
|
_db.Clear();
|
|
_volumes.Clear();
|
|
_xmls.Clear();
|
|
_nameLenForStreams = 0;
|
|
return S_OK;
|
|
}
|
|
|
|
STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems,
|
|
Int32 testMode, IArchiveExtractCallback *extractCallback)
|
|
{
|
|
COM_TRY_BEGIN
|
|
bool allFilesMode = (numItems == (UInt32)-1);
|
|
|
|
if (allFilesMode)
|
|
numItems = _db.SortedItems.Size() + _xmls.Size();
|
|
if (numItems == 0)
|
|
return S_OK;
|
|
|
|
UInt32 i;
|
|
UInt64 totalSize = 0;
|
|
for (i = 0; i < numItems; i++)
|
|
{
|
|
UInt32 index = allFilesMode ? i : indices[i];
|
|
if (index < (UInt32)_db.SortedItems.Size())
|
|
{
|
|
int streamIndex = _db.Items[_db.SortedItems[index]].StreamIndex;
|
|
if (streamIndex >= 0)
|
|
{
|
|
const CStreamInfo &si = _db.Streams[streamIndex];
|
|
totalSize += si.Resource.UnpackSize;
|
|
}
|
|
}
|
|
else
|
|
totalSize += _xmls[index - (UInt32)_db.SortedItems.Size()].Data.GetCapacity();
|
|
}
|
|
|
|
RINOK(extractCallback->SetTotal(totalSize));
|
|
|
|
UInt64 currentTotalPacked = 0;
|
|
UInt64 currentTotalUnPacked = 0;
|
|
UInt64 currentItemUnPacked, currentItemPacked;
|
|
|
|
int prevSuccessStreamIndex = -1;
|
|
|
|
CUnpacker unpacker;
|
|
|
|
CLocalProgress *lps = new CLocalProgress;
|
|
CMyComPtr<ICompressProgressInfo> progress = lps;
|
|
lps->Init(extractCallback, false);
|
|
|
|
for (i = 0; i < numItems; currentTotalUnPacked += currentItemUnPacked,
|
|
currentTotalPacked += currentItemPacked)
|
|
{
|
|
currentItemUnPacked = 0;
|
|
currentItemPacked = 0;
|
|
|
|
lps->InSize = currentTotalPacked;
|
|
lps->OutSize = currentTotalUnPacked;
|
|
|
|
RINOK(lps->SetCur());
|
|
UInt32 index = allFilesMode ? i : indices[i];
|
|
i++;
|
|
Int32 askMode = testMode ?
|
|
NExtract::NAskMode::kTest :
|
|
NExtract::NAskMode::kExtract;
|
|
|
|
CMyComPtr<ISequentialOutStream> realOutStream;
|
|
RINOK(extractCallback->GetStream(index, &realOutStream, askMode));
|
|
if (index >= (UInt32)_db.SortedItems.Size())
|
|
{
|
|
if (!testMode && !realOutStream)
|
|
continue;
|
|
RINOK(extractCallback->PrepareOperation(askMode));
|
|
const CByteBuffer &data = _xmls[index - (UInt32)_db.SortedItems.Size()].Data;
|
|
currentItemUnPacked = data.GetCapacity();
|
|
if (realOutStream)
|
|
{
|
|
RINOK(WriteStream(realOutStream, (const Byte *)data, data.GetCapacity()));
|
|
realOutStream.Release();
|
|
}
|
|
RINOK(extractCallback->SetOperationResult(NExtract::NOperationResult::kOK));
|
|
continue;
|
|
}
|
|
|
|
const CItem &item = _db.Items[_db.SortedItems[index]];
|
|
int streamIndex = item.StreamIndex;
|
|
if (streamIndex < 0)
|
|
{
|
|
if (!testMode && !realOutStream)
|
|
continue;
|
|
RINOK(extractCallback->PrepareOperation(askMode));
|
|
realOutStream.Release();
|
|
RINOK(extractCallback->SetOperationResult(item.HasStream() ?
|
|
NExtract::NOperationResult::kDataError :
|
|
NExtract::NOperationResult::kOK));
|
|
continue;
|
|
}
|
|
|
|
const CStreamInfo &si = _db.Streams[streamIndex];
|
|
currentItemUnPacked = si.Resource.UnpackSize;
|
|
currentItemPacked = si.Resource.PackSize;
|
|
|
|
if (!testMode && !realOutStream)
|
|
continue;
|
|
RINOK(extractCallback->PrepareOperation(askMode));
|
|
Int32 opRes = NExtract::NOperationResult::kOK;
|
|
if (streamIndex != prevSuccessStreamIndex || realOutStream)
|
|
{
|
|
Byte digest[20];
|
|
const CVolume &vol = _volumes[si.PartNumber];
|
|
HRESULT res = unpacker.Unpack(vol.Stream, si.Resource, vol.Header.IsLzxMode(),
|
|
realOutStream, progress, digest);
|
|
if (res == S_OK)
|
|
{
|
|
if (memcmp(digest, si.Hash, kHashSize) == 0)
|
|
prevSuccessStreamIndex = streamIndex;
|
|
else
|
|
opRes = NExtract::NOperationResult::kCRCError;
|
|
}
|
|
else if (res == S_FALSE)
|
|
opRes = NExtract::NOperationResult::kDataError;
|
|
else
|
|
return res;
|
|
}
|
|
realOutStream.Release();
|
|
RINOK(extractCallback->SetOperationResult(opRes));
|
|
}
|
|
return S_OK;
|
|
COM_TRY_END
|
|
}
|
|
|
|
STDMETHODIMP CHandler::GetNumberOfItems(UInt32 *numItems)
|
|
{
|
|
*numItems = _db.SortedItems.Size();
|
|
if (!_xmlInComments)
|
|
*numItems += _xmls.Size();
|
|
return S_OK;
|
|
}
|
|
|
|
}}
|