mirror of
https://github.com/Xevion/easy7zip.git
synced 2025-12-06 07:14:55 -06:00
549 lines
13 KiB
C++
549 lines
13 KiB
C++
// SparseHandler.cpp
|
|
|
|
#include "StdAfx.h"
|
|
|
|
#include "../../../C/CpuArch.h"
|
|
|
|
#include "../../Common/ComTry.h"
|
|
|
|
#include "../../Windows/PropVariantUtils.h"
|
|
|
|
#include "../Common/RegisterArc.h"
|
|
#include "../Common/StreamUtils.h"
|
|
|
|
#include "HandlerCont.h"
|
|
|
|
#define Get16(p) GetUi16(p)
|
|
#define Get32(p) GetUi32(p)
|
|
|
|
#define G16(_offs_, dest) dest = Get16(p + (_offs_));
|
|
#define G32(_offs_, dest) dest = Get32(p + (_offs_));
|
|
|
|
using namespace NWindows;
|
|
|
|
namespace NArchive {
|
|
namespace NSparse {
|
|
|
|
// libsparse and simg2img
|
|
|
|
struct CHeader
|
|
{
|
|
// UInt32 magic; /* 0xed26ff3a */
|
|
// UInt16 major_version; /* (0x1) - reject images with higher major versions */
|
|
// UInt16 minor_version; /* (0x0) - allow images with higer minor versions */
|
|
UInt16 file_hdr_sz; /* 28 bytes for first revision of the file format */
|
|
UInt16 chunk_hdr_sz; /* 12 bytes for first revision of the file format */
|
|
UInt32 BlockSize; /* block size in bytes, must be a multiple of 4 (4096) */
|
|
UInt32 NumBlocks; /* total blocks in the non-sparse output image */
|
|
UInt32 NumChunks; /* total chunks in the sparse input image */
|
|
// UInt32 image_checksum; /* CRC32 checksum of the original data, counting "don't care" as 0. */
|
|
|
|
void Parse(const Byte *p)
|
|
{
|
|
// G16 (4, major_version);
|
|
// G16 (6, minor_version);
|
|
G16 (8, file_hdr_sz);
|
|
G16 (10, chunk_hdr_sz);
|
|
G32 (12, BlockSize);
|
|
G32 (16, NumBlocks);
|
|
G32 (20, NumChunks);
|
|
// G32 (24, image_checksum);
|
|
}
|
|
};
|
|
|
|
// #define SPARSE_HEADER_MAGIC 0xed26ff3a
|
|
|
|
#define CHUNK_TYPE_RAW 0xCAC1
|
|
#define CHUNK_TYPE_FILL 0xCAC2
|
|
#define CHUNK_TYPE_DONT_CARE 0xCAC3
|
|
#define CHUNK_TYPE_CRC32 0xCAC4
|
|
|
|
#define MY__CHUNK_TYPE_FILL 0
|
|
#define MY__CHUNK_TYPE_DONT_CARE 1
|
|
#define MY__CHUNK_TYPE_RAW__START 2
|
|
|
|
static const char * const g_Methods[] =
|
|
{
|
|
"RAW"
|
|
, "FILL"
|
|
, "SPARSE" // "DONT_CARE"
|
|
, "CRC32"
|
|
};
|
|
|
|
static const unsigned kFillSize = 4;
|
|
|
|
struct CChunk
|
|
{
|
|
UInt32 VirtBlock;
|
|
Byte Fill [kFillSize];
|
|
UInt64 PhyOffset;
|
|
};
|
|
|
|
static const Byte k_Signature[] = { 0x3a, 0xff, 0x26, 0xed, 1, 0 };
|
|
|
|
|
|
class CHandler: public CHandlerImg
|
|
{
|
|
CRecordVector<CChunk> Chunks;
|
|
UInt64 _virtSize_fromChunks;
|
|
unsigned _blockSizeLog;
|
|
UInt32 _chunkIndexPrev;
|
|
|
|
UInt64 _packSizeProcessed;
|
|
UInt64 _phySize;
|
|
UInt32 _methodFlags;
|
|
bool _isArc;
|
|
bool _headersError;
|
|
bool _unexpectedEnd;
|
|
// bool _unsupported;
|
|
UInt32 NumChunks; // from header
|
|
|
|
HRESULT Seek2(UInt64 offset)
|
|
{
|
|
_posInArc = offset;
|
|
return Stream->Seek(offset, STREAM_SEEK_SET, NULL);
|
|
}
|
|
|
|
void InitSeekPositions()
|
|
{
|
|
/* (_virtPos) and (_posInArc) is used only in Read() (that calls ReadPhy()).
|
|
So we must reset these variables before first call of Read() */
|
|
Reset_VirtPos();
|
|
Reset_PosInArc();
|
|
_chunkIndexPrev = 0;
|
|
_packSizeProcessed = 0;
|
|
}
|
|
|
|
// virtual functions
|
|
bool Init_PackSizeProcessed()
|
|
{
|
|
_packSizeProcessed = 0;
|
|
return true;
|
|
}
|
|
bool Get_PackSizeProcessed(UInt64 &size)
|
|
{
|
|
size = _packSizeProcessed;
|
|
return true;
|
|
}
|
|
|
|
HRESULT Open2(IInStream *stream, IArchiveOpenCallback *openCallback);
|
|
HRESULT ReadPhy(UInt64 offset, void *data, UInt32 size, UInt32 &processed);
|
|
|
|
public:
|
|
INTERFACE_IInArchive_Img(;)
|
|
|
|
STDMETHOD(GetStream)(UInt32 index, ISequentialInStream **stream);
|
|
STDMETHOD(Read)(void *data, UInt32 size, UInt32 *processedSize);
|
|
};
|
|
|
|
|
|
|
|
static const Byte kProps[] =
|
|
{
|
|
kpidSize,
|
|
kpidPackSize
|
|
};
|
|
|
|
static const Byte kArcProps[] =
|
|
{
|
|
kpidClusterSize,
|
|
kpidNumBlocks,
|
|
kpidMethod
|
|
};
|
|
|
|
IMP_IInArchive_Props
|
|
IMP_IInArchive_ArcProps
|
|
|
|
STDMETHODIMP CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value)
|
|
{
|
|
COM_TRY_BEGIN
|
|
NCOM::CPropVariant prop;
|
|
|
|
switch (propID)
|
|
{
|
|
case kpidMainSubfile: prop = (UInt32)0; break;
|
|
case kpidClusterSize: prop = (UInt32)((UInt32)1 << _blockSizeLog); break;
|
|
case kpidNumBlocks: prop = (UInt32)NumChunks; break;
|
|
case kpidPhySize: if (_phySize != 0) prop = _phySize; break;
|
|
|
|
case kpidMethod:
|
|
{
|
|
FLAGS_TO_PROP(g_Methods, _methodFlags, prop);
|
|
break;
|
|
}
|
|
|
|
case kpidErrorFlags:
|
|
{
|
|
UInt32 v = 0;
|
|
if (!_isArc) v |= kpv_ErrorFlags_IsNotArc;
|
|
if (_headersError) v |= kpv_ErrorFlags_HeadersError;
|
|
if (_unexpectedEnd) v |= kpv_ErrorFlags_UnexpectedEnd;
|
|
// if (_unsupported) v |= kpv_ErrorFlags_UnsupportedMethod;
|
|
if (!Stream && v == 0 && _isArc)
|
|
v = kpv_ErrorFlags_HeadersError;
|
|
if (v != 0)
|
|
prop = v;
|
|
break;
|
|
}
|
|
}
|
|
|
|
prop.Detach(value);
|
|
return S_OK;
|
|
COM_TRY_END
|
|
}
|
|
|
|
|
|
STDMETHODIMP CHandler::GetProperty(UInt32 /* index */, PROPID propID, PROPVARIANT *value)
|
|
{
|
|
COM_TRY_BEGIN
|
|
NCOM::CPropVariant prop;
|
|
|
|
switch (propID)
|
|
{
|
|
case kpidSize: prop = _size; break;
|
|
case kpidPackSize: prop = _phySize; break;
|
|
case kpidExtension: prop = (_imgExt ? _imgExt : "img"); break;
|
|
}
|
|
|
|
prop.Detach(value);
|
|
return S_OK;
|
|
COM_TRY_END
|
|
}
|
|
|
|
|
|
static unsigned GetLogSize(UInt32 size)
|
|
{
|
|
unsigned k;
|
|
for (k = 0; k < 32; k++)
|
|
if (((UInt32)1 << k) == size)
|
|
return k;
|
|
return k;
|
|
}
|
|
|
|
|
|
HRESULT CHandler::Open2(IInStream *stream, IArchiveOpenCallback *openCallback)
|
|
{
|
|
const unsigned kHeaderSize = 28;
|
|
const unsigned kChunkHeaderSize = 12;
|
|
CHeader h;
|
|
{
|
|
Byte buf[kHeaderSize];
|
|
RINOK(ReadStream_FALSE(stream, buf, kHeaderSize));
|
|
if (memcmp(buf, k_Signature, 6) != 0)
|
|
return S_FALSE;
|
|
h.Parse(buf);
|
|
}
|
|
|
|
if (h.file_hdr_sz != kHeaderSize ||
|
|
h.chunk_hdr_sz != kChunkHeaderSize)
|
|
return S_FALSE;
|
|
|
|
NumChunks = h.NumChunks;
|
|
|
|
const unsigned logSize = GetLogSize(h.BlockSize);
|
|
if (logSize < 2 || logSize >= 32)
|
|
return S_FALSE;
|
|
_blockSizeLog = logSize;
|
|
|
|
_size = (UInt64)h.NumBlocks << logSize;
|
|
|
|
if (h.NumChunks >= (UInt32)(Int32)-2) // it's our limit
|
|
return S_FALSE;
|
|
|
|
_isArc = true;
|
|
Chunks.Reserve(h.NumChunks + 1);
|
|
UInt64 offset = kHeaderSize;
|
|
UInt32 virtBlock = 0;
|
|
UInt32 i;
|
|
|
|
for (i = 0; i < h.NumChunks; i++)
|
|
{
|
|
{
|
|
const UInt32 mask = ((UInt32)1 << 16) - 1;
|
|
if ((i & mask) == mask && openCallback)
|
|
{
|
|
RINOK(openCallback->SetCompleted(NULL, &offset));
|
|
}
|
|
}
|
|
Byte buf[kChunkHeaderSize];
|
|
{
|
|
size_t processed = kChunkHeaderSize;
|
|
RINOK(ReadStream(stream, buf, &processed));
|
|
if (kChunkHeaderSize != processed)
|
|
{
|
|
offset += kChunkHeaderSize;
|
|
break;
|
|
}
|
|
}
|
|
const UInt32 type = Get32(&buf[0]);
|
|
const UInt32 numBlocks = Get32(&buf[4]);
|
|
UInt32 size = Get32(&buf[8]);
|
|
|
|
if (type < CHUNK_TYPE_RAW ||
|
|
type > CHUNK_TYPE_CRC32)
|
|
return S_FALSE;
|
|
if (size < kChunkHeaderSize)
|
|
return S_FALSE;
|
|
CChunk c;
|
|
c.PhyOffset = offset + kChunkHeaderSize;
|
|
c.VirtBlock = virtBlock;
|
|
offset += size;
|
|
size -= kChunkHeaderSize;
|
|
_methodFlags |= ((UInt32)1 << (type - CHUNK_TYPE_RAW));
|
|
|
|
if (numBlocks > h.NumBlocks - virtBlock)
|
|
return S_FALSE;
|
|
|
|
if (type == CHUNK_TYPE_CRC32)
|
|
{
|
|
// crc chunk must be last chunk (i == h.NumChunks -1);
|
|
if (size != kFillSize || numBlocks != 0)
|
|
return S_FALSE;
|
|
{
|
|
size_t processed = kFillSize;
|
|
RINOK(ReadStream(stream, c.Fill, &processed));
|
|
if (kFillSize != processed)
|
|
break;
|
|
}
|
|
continue;
|
|
}
|
|
// else
|
|
{
|
|
if (numBlocks == 0)
|
|
return S_FALSE;
|
|
|
|
if (type == CHUNK_TYPE_DONT_CARE)
|
|
{
|
|
if (size != 0)
|
|
return S_FALSE;
|
|
c.PhyOffset = MY__CHUNK_TYPE_DONT_CARE;
|
|
}
|
|
else if (type == CHUNK_TYPE_FILL)
|
|
{
|
|
if (size != kFillSize)
|
|
return S_FALSE;
|
|
c.PhyOffset = MY__CHUNK_TYPE_FILL;
|
|
size_t processed = kFillSize;
|
|
RINOK(ReadStream(stream, c.Fill, &processed));
|
|
if (kFillSize != processed)
|
|
break;
|
|
}
|
|
else if (type == CHUNK_TYPE_RAW)
|
|
{
|
|
/* Here we require (size == virtSize).
|
|
Probably original decoder also requires it.
|
|
But maybe size of last chunk can be non-aligned with blockSize ? */
|
|
const UInt32 virtSize = (numBlocks << _blockSizeLog);
|
|
if (size != virtSize || numBlocks != (virtSize >> _blockSizeLog))
|
|
return S_FALSE;
|
|
}
|
|
else
|
|
return S_FALSE;
|
|
|
|
virtBlock += numBlocks;
|
|
Chunks.AddInReserved(c);
|
|
if (type == CHUNK_TYPE_RAW)
|
|
RINOK(stream->Seek(offset, STREAM_SEEK_SET, NULL));
|
|
}
|
|
}
|
|
|
|
if (i != h.NumChunks)
|
|
_unexpectedEnd = true;
|
|
else if (virtBlock != h.NumBlocks)
|
|
_headersError = true;
|
|
|
|
_phySize = offset;
|
|
|
|
{
|
|
CChunk c;
|
|
c.VirtBlock = virtBlock;
|
|
c.PhyOffset = offset;
|
|
Chunks.AddInReserved(c);
|
|
}
|
|
_virtSize_fromChunks = (UInt64)virtBlock << _blockSizeLog;
|
|
|
|
Stream = stream;
|
|
return S_OK;
|
|
}
|
|
|
|
|
|
STDMETHODIMP CHandler::Close()
|
|
{
|
|
Chunks.Clear();
|
|
_isArc = false;
|
|
_virtSize_fromChunks = 0;
|
|
// _unsupported = false;
|
|
_headersError = false;
|
|
_unexpectedEnd = false;
|
|
_phySize = 0;
|
|
_methodFlags = 0;
|
|
|
|
_chunkIndexPrev = 0;
|
|
_packSizeProcessed = 0;
|
|
|
|
// CHandlerImg:
|
|
Clear_HandlerImg_Vars();
|
|
Stream.Release();
|
|
return S_OK;
|
|
}
|
|
|
|
|
|
STDMETHODIMP CHandler::GetStream(UInt32 /* index */, ISequentialInStream **stream)
|
|
{
|
|
COM_TRY_BEGIN
|
|
*stream = NULL;
|
|
if (Chunks.Size() < 1)
|
|
return S_FALSE;
|
|
if (Chunks.Size() < 2 && _virtSize_fromChunks != 0)
|
|
return S_FALSE;
|
|
// if (_unsupported) return S_FALSE;
|
|
InitSeekPositions();
|
|
CMyComPtr<ISequentialInStream> streamTemp = this;
|
|
*stream = streamTemp.Detach();
|
|
return S_OK;
|
|
COM_TRY_END
|
|
}
|
|
|
|
|
|
|
|
HRESULT CHandler::ReadPhy(UInt64 offset, void *data, UInt32 size, UInt32 &processed)
|
|
{
|
|
processed = 0;
|
|
if (offset > _phySize || offset + size > _phySize)
|
|
{
|
|
// we don't expect these cases, if (_phySize) was set correctly.
|
|
return S_FALSE;
|
|
}
|
|
if (offset != _posInArc)
|
|
{
|
|
const HRESULT res = Seek2(offset);
|
|
if (res != S_OK)
|
|
{
|
|
Reset_PosInArc(); // we don't trust seek_pos in case of error
|
|
return res;
|
|
}
|
|
}
|
|
{
|
|
size_t size2 = size;
|
|
const HRESULT res = ReadStream(Stream, data, &size2);
|
|
processed = (UInt32)size2;
|
|
_packSizeProcessed += size2;
|
|
_posInArc += size2;
|
|
if (res != S_OK)
|
|
Reset_PosInArc(); // we don't trust seek_pos in case of reading error
|
|
return res;
|
|
}
|
|
}
|
|
|
|
|
|
STDMETHODIMP CHandler::Read(void *data, UInt32 size, UInt32 *processedSize)
|
|
{
|
|
if (processedSize)
|
|
*processedSize = 0;
|
|
// const unsigned kLimit = (1 << 16) + 1; if (size > kLimit) size = kLimit; // for debug
|
|
if (_virtPos >= _virtSize_fromChunks)
|
|
return S_OK;
|
|
{
|
|
const UInt64 rem = _virtSize_fromChunks - _virtPos;
|
|
if (size > rem)
|
|
size = (UInt32)rem;
|
|
if (size == 0)
|
|
return S_OK;
|
|
}
|
|
|
|
UInt32 chunkIndex = _chunkIndexPrev;
|
|
if (chunkIndex + 1 >= Chunks.Size())
|
|
return S_FALSE;
|
|
{
|
|
const UInt32 blockIndex = (UInt32)(_virtPos >> _blockSizeLog);
|
|
if (blockIndex < Chunks[chunkIndex ].VirtBlock ||
|
|
blockIndex >= Chunks[chunkIndex + 1].VirtBlock)
|
|
{
|
|
unsigned left = 0, right = Chunks.Size() - 1;
|
|
for (;;)
|
|
{
|
|
const unsigned mid = (unsigned)(((size_t)left + (size_t)right) / 2);
|
|
if (mid == left)
|
|
break;
|
|
if (blockIndex < Chunks[mid].VirtBlock)
|
|
right = mid;
|
|
else
|
|
left = mid;
|
|
}
|
|
chunkIndex = left;
|
|
_chunkIndexPrev = chunkIndex;
|
|
}
|
|
}
|
|
|
|
const CChunk &c = Chunks[chunkIndex];
|
|
const UInt64 offset = _virtPos - ((UInt64)c.VirtBlock << _blockSizeLog);
|
|
{
|
|
const UInt32 numBlocks = Chunks[chunkIndex + 1].VirtBlock - c.VirtBlock;
|
|
const UInt64 rem = ((UInt64)numBlocks << _blockSizeLog) - offset;
|
|
if (size > rem)
|
|
size = (UInt32)rem;
|
|
}
|
|
|
|
const UInt64 phyOffset = c.PhyOffset;
|
|
|
|
if (phyOffset >= MY__CHUNK_TYPE_RAW__START)
|
|
{
|
|
UInt32 processed = 0;
|
|
const HRESULT res = ReadPhy(phyOffset + offset, data, size, processed);
|
|
if (processedSize)
|
|
*processedSize = processed;
|
|
_virtPos += processed;
|
|
return res;
|
|
}
|
|
|
|
unsigned b = 0;
|
|
|
|
if (phyOffset == MY__CHUNK_TYPE_FILL)
|
|
{
|
|
const Byte b0 = c.Fill [0];
|
|
const Byte b1 = c.Fill [1];
|
|
const Byte b2 = c.Fill [2];
|
|
const Byte b3 = c.Fill [3];
|
|
if (b0 != b1 ||
|
|
b0 != b2 ||
|
|
b0 != b3)
|
|
{
|
|
if (processedSize)
|
|
*processedSize = size;
|
|
_virtPos += size;
|
|
Byte *dest = (Byte *)data;
|
|
while (size >= 4)
|
|
{
|
|
dest[0] = b0;
|
|
dest[1] = b1;
|
|
dest[2] = b2;
|
|
dest[3] = b3;
|
|
dest += 4;
|
|
size -= 4;
|
|
}
|
|
if (size > 0) dest[0] = b0;
|
|
if (size > 1) dest[1] = b1;
|
|
if (size > 2) dest[2] = b2;
|
|
return S_OK;
|
|
}
|
|
b = b0;
|
|
}
|
|
else if (phyOffset != MY__CHUNK_TYPE_DONT_CARE)
|
|
return S_FALSE;
|
|
|
|
memset(data, b, size);
|
|
_virtPos += size;
|
|
if (processedSize)
|
|
*processedSize = size;
|
|
return S_OK;
|
|
}
|
|
|
|
REGISTER_ARC_I(
|
|
"Sparse", "simg img", NULL, 0xc2,
|
|
k_Signature,
|
|
0,
|
|
0,
|
|
NULL)
|
|
|
|
}}
|