1587 lines
40 KiB
C++
1587 lines
40 KiB
C++
// DmgHandler.cpp
|
|
|
|
#include "StdAfx.h"
|
|
|
|
#include "../../../C/CpuArch.h"
|
|
|
|
#include "../../Common/ComTry.h"
|
|
#include "../../Common/IntToString.h"
|
|
#include "../../Common/MyXml.h"
|
|
#include "../../Common/UTFConvert.h"
|
|
|
|
#include "../../Windows/PropVariant.h"
|
|
|
|
#include "../Common/LimitedStreams.h"
|
|
#include "../Common/ProgressUtils.h"
|
|
#include "../Common/RegisterArc.h"
|
|
#include "../Common/StreamObjects.h"
|
|
#include "../Common/StreamUtils.h"
|
|
|
|
#include "../Compress/BZip2Decoder.h"
|
|
#include "../Compress/CopyCoder.h"
|
|
#include "../Compress/ZlibDecoder.h"
|
|
|
|
#include "Common/OutStreamWithCRC.h"
|
|
|
|
// #define DMG_SHOW_RAW
|
|
|
|
// #include <stdio.h>
|
|
#define PRF(x) // x
|
|
|
|
#define Get16(p) GetBe16(p)
|
|
#define Get32(p) GetBe32(p)
|
|
#define Get64(p) GetBe64(p)
|
|
|
|
static const Byte k_Base64Table[256] =
|
|
{
|
|
66,77,77,77,77,77,77,77,77,65,65,77,77,65,77,77,
|
|
77,77,77,77,77,77,77,77,77,77,77,77,77,77,77,77,
|
|
65,77,77,77,77,77,77,77,77,77,77,62,77,77,77,63,
|
|
52,53,54,55,56,57,58,59,60,61,77,77,77,64,77,77,
|
|
77, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13,14,
|
|
15,16,17,18,19,20,21,22,23,24,25,77,77,77,77,77,
|
|
77,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,
|
|
41,42,43,44,45,46,47,48,49,50,51,77,77,77,77,77,
|
|
77,77,77,77,77,77,77,77,77,77,77,77,77,77,77,77,
|
|
77,77,77,77,77,77,77,77,77,77,77,77,77,77,77,77,
|
|
77,77,77,77,77,77,77,77,77,77,77,77,77,77,77,77,
|
|
77,77,77,77,77,77,77,77,77,77,77,77,77,77,77,77,
|
|
77,77,77,77,77,77,77,77,77,77,77,77,77,77,77,77,
|
|
77,77,77,77,77,77,77,77,77,77,77,77,77,77,77,77,
|
|
77,77,77,77,77,77,77,77,77,77,77,77,77,77,77,77,
|
|
77,77,77,77,77,77,77,77,77,77,77,77,77,77,77,77
|
|
};
|
|
|
|
static Byte *Base64ToBin(Byte *dest, const char *src)
|
|
{
|
|
UInt32 val = 1;
|
|
|
|
for (;;)
|
|
{
|
|
UInt32 c = k_Base64Table[(Byte)(*src++)];
|
|
|
|
if (c < 64)
|
|
{
|
|
val = (val << 6) | c;
|
|
if ((val & ((UInt32)1 << 24)) == 0)
|
|
continue;
|
|
dest[0] = (Byte)(val >> 16);
|
|
dest[1] = (Byte)(val >> 8);
|
|
dest[2] = (Byte)(val);
|
|
dest += 3;
|
|
val = 1;
|
|
continue;
|
|
}
|
|
|
|
if (c == 65) // space
|
|
continue;
|
|
|
|
if (c == 64) // '='
|
|
break;
|
|
|
|
if (c == 66 && val == 1) // end of string
|
|
return dest;
|
|
|
|
return NULL;
|
|
}
|
|
|
|
if (val < (1 << 12))
|
|
return NULL;
|
|
|
|
if (val & (1 << 18))
|
|
{
|
|
*dest++ = (Byte)(val >> 10);
|
|
*dest++ = (Byte)(val >> 2);
|
|
}
|
|
else if (k_Base64Table[(Byte)(*src++)] != 64) // '='
|
|
return NULL;
|
|
else
|
|
*dest++ = (Byte)(val >> 4);
|
|
|
|
for (;;)
|
|
{
|
|
Byte c = k_Base64Table[(Byte)(*src++)];
|
|
if (c == 65) // space
|
|
continue;
|
|
if (c == 66) // end of string
|
|
return dest;
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
|
|
namespace NArchive {
|
|
namespace NDmg {
|
|
|
|
enum
|
|
{
|
|
METHOD_ZERO_0 = 0,
|
|
METHOD_COPY = 1,
|
|
METHOD_ZERO_2 = 2, // without file CRC calculation
|
|
METHOD_ADC = 0x80000004,
|
|
METHOD_ZLIB = 0x80000005,
|
|
METHOD_BZIP2 = 0x80000006,
|
|
METHOD_COMMENT = 0x7FFFFFFE, // is used to comment "+beg" and "+end" in extra field.
|
|
METHOD_END = 0xFFFFFFFF
|
|
};
|
|
|
|
struct CBlock
|
|
{
|
|
UInt32 Type;
|
|
UInt64 UnpPos;
|
|
UInt64 UnpSize;
|
|
UInt64 PackPos;
|
|
UInt64 PackSize;
|
|
|
|
UInt64 GetNextPackOffset() const { return PackPos + PackSize; }
|
|
UInt64 GetNextUnpPos() const { return UnpPos + UnpSize; }
|
|
|
|
bool IsZeroMethod() const { return Type == METHOD_ZERO_0 || Type == METHOD_ZERO_2; }
|
|
bool ThereAreDataInBlock() const { return Type != METHOD_COMMENT && Type != METHOD_END; }
|
|
};
|
|
|
|
static const UInt32 kCheckSumType_CRC = 2;
|
|
|
|
static const size_t kChecksumSize_Max = 0x80;
|
|
|
|
struct CChecksum
|
|
{
|
|
UInt32 Type;
|
|
UInt32 NumBits;
|
|
Byte Data[kChecksumSize_Max];
|
|
|
|
bool IsCrc32() const { return Type == kCheckSumType_CRC && NumBits == 32; }
|
|
UInt32 GetCrc32() const { return Get32(Data); }
|
|
void Parse(const Byte *p);
|
|
};
|
|
|
|
void CChecksum::Parse(const Byte *p)
|
|
{
|
|
Type = Get32(p);
|
|
NumBits = Get32(p + 4);
|
|
memcpy(Data, p + 8, kChecksumSize_Max);
|
|
};
|
|
|
|
struct CFile
|
|
{
|
|
UInt64 Size;
|
|
UInt64 PackSize;
|
|
UInt64 StartPos;
|
|
AString Name;
|
|
CRecordVector<CBlock> Blocks;
|
|
CChecksum Checksum;
|
|
bool FullFileChecksum;
|
|
|
|
HRESULT Parse(const Byte *p, UInt32 size);
|
|
};
|
|
|
|
#ifdef DMG_SHOW_RAW
|
|
struct CExtraFile
|
|
{
|
|
CByteBuffer Data;
|
|
AString Name;
|
|
};
|
|
#endif
|
|
|
|
class CHandler:
|
|
public IInArchive,
|
|
public IInArchiveGetStream,
|
|
public CMyUnknownImp
|
|
{
|
|
CMyComPtr<IInStream> _inStream;
|
|
CObjectVector<CFile> _files;
|
|
bool _masterCrcError;
|
|
|
|
UInt64 _startPos;
|
|
UInt64 _phySize;
|
|
|
|
#ifdef DMG_SHOW_RAW
|
|
CObjectVector<CExtraFile> _extras;
|
|
#endif
|
|
|
|
HRESULT Open2(IInStream *stream);
|
|
HRESULT Extract(IInStream *stream);
|
|
public:
|
|
MY_UNKNOWN_IMP2(IInArchive, IInArchiveGetStream)
|
|
INTERFACE_IInArchive(;)
|
|
STDMETHOD(GetStream)(UInt32 index, ISequentialInStream **stream);
|
|
};
|
|
|
|
// that limit can be increased, if there are such dmg files
|
|
static const size_t kXmlSizeMax = 0xFFFF0000; // 4 GB - 64 KB;
|
|
|
|
struct CMethods
|
|
{
|
|
CRecordVector<UInt32> Types;
|
|
CRecordVector<UInt32> ChecksumTypes;
|
|
|
|
void Update(const CFile &file);
|
|
void GetString(AString &s) const;
|
|
};
|
|
|
|
void CMethods::Update(const CFile &file)
|
|
{
|
|
ChecksumTypes.AddToUniqueSorted(file.Checksum.Type);
|
|
FOR_VECTOR (i, file.Blocks)
|
|
Types.AddToUniqueSorted(file.Blocks[i].Type);
|
|
}
|
|
|
|
void CMethods::GetString(AString &res) const
|
|
{
|
|
res.Empty();
|
|
|
|
unsigned i;
|
|
|
|
for (i = 0; i < Types.Size(); i++)
|
|
{
|
|
UInt32 type = Types[i];
|
|
if (type == METHOD_COMMENT || type == METHOD_END)
|
|
continue;
|
|
char buf[16];
|
|
const char *s;
|
|
switch (type)
|
|
{
|
|
case METHOD_ZERO_0: s = "Zero0"; break;
|
|
case METHOD_ZERO_2: s = "Zero2"; break;
|
|
case METHOD_COPY: s = "Copy"; break;
|
|
case METHOD_ADC: s = "ADC"; break;
|
|
case METHOD_ZLIB: s = "ZLIB"; break;
|
|
case METHOD_BZIP2: s = "BZip2"; break;
|
|
default: ConvertUInt32ToString(type, buf); s = buf;
|
|
}
|
|
res.Add_Space_if_NotEmpty();
|
|
res += s;
|
|
}
|
|
|
|
for (i = 0; i < ChecksumTypes.Size(); i++)
|
|
{
|
|
UInt32 type = ChecksumTypes[i];
|
|
char buf[32];
|
|
const char *s;
|
|
switch (type)
|
|
{
|
|
case kCheckSumType_CRC: s = "CRC"; break;
|
|
default:
|
|
ConvertUInt32ToString(type, MyStpCpy(buf, "Check"));
|
|
s = buf;
|
|
}
|
|
res.Add_Space_if_NotEmpty();
|
|
res += s;
|
|
}
|
|
}
|
|
|
|
struct CAppleName
|
|
{
|
|
bool IsFs;
|
|
const char *Ext;
|
|
const char *AppleName;
|
|
};
|
|
|
|
static const CAppleName k_Names[] =
|
|
{
|
|
{ true, "hfs", "Apple_HFS" },
|
|
{ true, "hfsx", "Apple_HFSX" },
|
|
{ true, "ufs", "Apple_UFS" },
|
|
{ false, "free", "Apple_Free" },
|
|
{ false, "ddm", "DDM" },
|
|
{ false, NULL, "Apple_partition_map" },
|
|
{ false, NULL, " GPT " },
|
|
{ false, NULL, "MBR" },
|
|
{ false, NULL, "Driver" },
|
|
{ false, NULL, "Patches" }
|
|
};
|
|
|
|
static const unsigned kNumAppleNames = ARRAY_SIZE(k_Names);
|
|
|
|
static const Byte kProps[] =
|
|
{
|
|
kpidPath,
|
|
kpidSize,
|
|
kpidPackSize,
|
|
kpidCRC,
|
|
kpidComment,
|
|
kpidMethod
|
|
};
|
|
|
|
IMP_IInArchive_Props
|
|
|
|
static const Byte kArcProps[] =
|
|
{
|
|
kpidMethod,
|
|
kpidNumBlocks
|
|
};
|
|
|
|
STDMETHODIMP CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value)
|
|
{
|
|
COM_TRY_BEGIN
|
|
NWindows::NCOM::CPropVariant prop;
|
|
switch (propID)
|
|
{
|
|
case kpidMethod:
|
|
{
|
|
CMethods m;
|
|
FOR_VECTOR (i, _files)
|
|
m.Update(_files[i]);
|
|
AString s;
|
|
m.GetString(s);
|
|
if (!s.IsEmpty())
|
|
prop = s;
|
|
break;
|
|
}
|
|
case kpidNumBlocks:
|
|
{
|
|
UInt64 numBlocks = 0;
|
|
FOR_VECTOR (i, _files)
|
|
numBlocks += _files[i].Blocks.Size();
|
|
prop = numBlocks;
|
|
break;
|
|
}
|
|
case kpidMainSubfile:
|
|
{
|
|
int mainIndex = -1;
|
|
unsigned numFS = 0;
|
|
unsigned numUnknown = 0;
|
|
FOR_VECTOR (i, _files)
|
|
{
|
|
const AString &name = _files[i].Name;
|
|
unsigned n;
|
|
for (n = 0; n < kNumAppleNames; n++)
|
|
{
|
|
const CAppleName &appleName = k_Names[n];
|
|
// if (name.Find(appleName.AppleName) >= 0)
|
|
if (strstr(name, appleName.AppleName))
|
|
{
|
|
if (appleName.IsFs)
|
|
{
|
|
numFS++;
|
|
mainIndex = i;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
if (n == kNumAppleNames)
|
|
{
|
|
mainIndex = i;
|
|
numUnknown++;
|
|
}
|
|
}
|
|
if (numFS + numUnknown == 1)
|
|
prop = (UInt32)mainIndex;
|
|
break;
|
|
}
|
|
case kpidWarning:
|
|
if (_masterCrcError)
|
|
prop = "Master CRC error";
|
|
break;
|
|
case kpidOffset: prop = _startPos; break;
|
|
case kpidPhySize: prop = _phySize; break;
|
|
}
|
|
prop.Detach(value);
|
|
return S_OK;
|
|
COM_TRY_END
|
|
}
|
|
|
|
IMP_IInArchive_ArcProps
|
|
|
|
HRESULT CFile::Parse(const Byte *p, UInt32 size)
|
|
{
|
|
const UInt32 kHeadSize = 0xCC;
|
|
if (size < kHeadSize)
|
|
return S_FALSE;
|
|
if (Get32(p) != 0x6D697368) // "mish" signature
|
|
return S_FALSE;
|
|
if (Get32(p + 4) != 1) // version
|
|
return S_FALSE;
|
|
// UInt64 firstSectorNumber = Get64(p + 8);
|
|
UInt64 numSectors = Get64(p + 0x10);
|
|
|
|
StartPos = Get64(p + 0x18);
|
|
|
|
// UInt32 decompressedBufRequested = Get32(p + 0x20); // ???
|
|
// UInt32 blocksDescriptor = Get32(p + 0x24); // number starting from -1?
|
|
// char Reserved1[24];
|
|
|
|
Checksum.Parse(p + 0x40);
|
|
PRF(printf("\n\nChecksum Type = %2d", Checksum.Type));
|
|
|
|
UInt32 numBlocks = Get32(p + 0xC8);
|
|
if (numBlocks > ((UInt32)1 << 28))
|
|
return S_FALSE;
|
|
|
|
const UInt32 kRecordSize = 40;
|
|
if (numBlocks * kRecordSize + kHeadSize != size)
|
|
return S_FALSE;
|
|
|
|
PackSize = 0;
|
|
Size = 0;
|
|
Blocks.ClearAndReserve(numBlocks);
|
|
FullFileChecksum = true;
|
|
|
|
p += kHeadSize;
|
|
UInt32 i;
|
|
|
|
for (i = 0; i < numBlocks; i++, p += kRecordSize)
|
|
{
|
|
CBlock b;
|
|
b.Type = Get32(p);
|
|
b.UnpPos = Get64(p + 0x08) << 9;
|
|
b.UnpSize = Get64(p + 0x10) << 9;
|
|
b.PackPos = Get64(p + 0x18);
|
|
b.PackSize = Get64(p + 0x20);
|
|
|
|
// b.PackPos can be 0 for some types. So we don't check it
|
|
if (!Blocks.IsEmpty())
|
|
if (b.UnpPos != Blocks.Back().GetNextUnpPos())
|
|
return S_FALSE;
|
|
|
|
PRF(printf("\nType=%8x m[1]=%8x uPos=%8x uSize=%7x pPos=%8x pSize=%7x",
|
|
b.Type, Get32(p + 4), (UInt32)b.UnpPos, (UInt32)b.UnpSize, (UInt32)b.PackPos, (UInt32)b.PackSize));
|
|
|
|
if (b.Type == METHOD_COMMENT)
|
|
continue;
|
|
if (b.Type == METHOD_END)
|
|
break;
|
|
PackSize += b.PackSize;
|
|
|
|
if (b.UnpSize != 0)
|
|
{
|
|
if (b.Type == METHOD_ZERO_2)
|
|
FullFileChecksum = false;
|
|
Blocks.AddInReserved(b);
|
|
}
|
|
}
|
|
|
|
if (i != numBlocks - 1)
|
|
return S_FALSE;
|
|
if (!Blocks.IsEmpty())
|
|
Size = Blocks.Back().GetNextUnpPos();
|
|
if (Size != (numSectors << 9))
|
|
return S_FALSE;
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
static int FindKeyPair(const CXmlItem &item, const AString &key, const AString &nextTag)
|
|
{
|
|
for (unsigned i = 0; i + 1 < item.SubItems.Size(); i++)
|
|
{
|
|
const CXmlItem &si = item.SubItems[i];
|
|
if (si.IsTagged("key") && si.GetSubString() == key && item.SubItems[i + 1].IsTagged(nextTag))
|
|
return i + 1;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
static const AString *GetStringFromKeyPair(const CXmlItem &item, const AString &key, const AString &nextTag)
|
|
{
|
|
int index = FindKeyPair(item, key, nextTag);
|
|
if (index >= 0)
|
|
return item.SubItems[index].GetSubStringPtr();
|
|
return NULL;
|
|
}
|
|
|
|
static const unsigned HEADER_SIZE = 0x200;
|
|
|
|
static const Byte k_Signature[] = { 'k','o','l','y', 0, 0, 0, 4, 0, 0, 2, 0 };
|
|
|
|
static inline bool IsKoly(const Byte *p)
|
|
{
|
|
return memcmp(p, k_Signature, ARRAY_SIZE(k_Signature)) == 0;
|
|
/*
|
|
if (Get32(p) != 0x6B6F6C79) // "koly" signature
|
|
return false;
|
|
if (Get32(p + 4) != 4) // version
|
|
return false;
|
|
if (Get32(p + 8) != HEADER_SIZE)
|
|
return false;
|
|
return true;
|
|
*/
|
|
}
|
|
|
|
HRESULT CHandler::Open2(IInStream *stream)
|
|
{
|
|
RINOK(stream->Seek(0, STREAM_SEEK_CUR, &_startPos));
|
|
|
|
Byte buf[HEADER_SIZE];
|
|
RINOK(ReadStream_FALSE(stream, buf, HEADER_SIZE));
|
|
|
|
UInt64 headerPos;
|
|
if (IsKoly(buf))
|
|
headerPos = _startPos;
|
|
else
|
|
{
|
|
RINOK(stream->Seek(0, STREAM_SEEK_END, &headerPos));
|
|
if (headerPos < HEADER_SIZE)
|
|
return S_FALSE;
|
|
headerPos -= HEADER_SIZE;
|
|
RINOK(stream->Seek(headerPos, STREAM_SEEK_SET, NULL));
|
|
RINOK(ReadStream_FALSE(stream, buf, HEADER_SIZE));
|
|
if (!IsKoly(buf))
|
|
return S_FALSE;
|
|
}
|
|
|
|
// UInt32 flags = Get32(buf + 12);
|
|
// UInt64 runningDataForkOffset = Get64(buf + 0x10);
|
|
UInt64 dataForkOffset = Get64(buf + 0x18);
|
|
UInt64 dataForkLen = Get64(buf + 0x20);
|
|
UInt64 rsrcOffset = Get64(buf + 0x28);
|
|
UInt64 rsrcLen = Get64(buf + 0x30);
|
|
// UInt32 segmentNumber = Get32(buf + 0x38);
|
|
// UInt32 segmentCount = Get32(buf + 0x3C);
|
|
// Byte segmentGUID[16];
|
|
// CChecksum dataForkChecksum;
|
|
// dataForkChecksum.Parse(buf + 0x50);
|
|
UInt64 xmlOffset = Get64(buf + 0xD8);
|
|
UInt64 xmlLen = Get64(buf + 0xE0);
|
|
|
|
if ( headerPos < dataForkOffset
|
|
|| headerPos - dataForkOffset < dataForkLen
|
|
|| headerPos < rsrcOffset
|
|
|| headerPos - rsrcOffset < rsrcLen
|
|
|| headerPos < xmlOffset
|
|
|| headerPos - xmlOffset < xmlLen)
|
|
return S_FALSE;
|
|
|
|
UInt64 totalLen = dataForkLen + rsrcLen + xmlLen;
|
|
if (totalLen > headerPos)
|
|
return S_FALSE;
|
|
_startPos = headerPos - totalLen;
|
|
_phySize = totalLen + HEADER_SIZE;
|
|
headerPos = totalLen;
|
|
|
|
// Byte reserved[0x78]
|
|
|
|
CChecksum masterChecksum;
|
|
masterChecksum.Parse(buf + 0x160);
|
|
|
|
// UInt32 imageVariant = Get32(buf + 0x1E8);
|
|
// UInt64 numSectors = Get64(buf + 0x1EC);
|
|
// Byte reserved[0x12]
|
|
|
|
const UInt32 RSRC_HEAD_SIZE = 0x100;
|
|
|
|
// We don't know the size of the field "offset" in rsrc.
|
|
// We suppose that it uses 24 bits. So we use Rsrc, only if the rsrcLen < (1 << 24).
|
|
bool useRsrc = (rsrcLen > RSRC_HEAD_SIZE && rsrcLen < ((UInt32)1 << 24));
|
|
// useRsrc = false;
|
|
|
|
if (useRsrc)
|
|
{
|
|
#ifdef DMG_SHOW_RAW
|
|
CExtraFile &extra = _extras.AddNew();
|
|
extra.Name = "rsrc.bin";
|
|
CByteBuffer &rsrcBuf = extra.Data;
|
|
#else
|
|
CByteBuffer rsrcBuf;
|
|
#endif
|
|
|
|
size_t rsrcLenT = (size_t)rsrcLen;
|
|
rsrcBuf.Alloc(rsrcLenT);
|
|
RINOK(stream->Seek(_startPos + rsrcOffset, STREAM_SEEK_SET, NULL));
|
|
RINOK(ReadStream_FALSE(stream, rsrcBuf, rsrcLenT));
|
|
|
|
const Byte *p = rsrcBuf;
|
|
UInt32 headSize = Get32(p + 0);
|
|
UInt32 footerOffset = Get32(p + 4);
|
|
UInt32 mainDataSize = Get32(p + 8);
|
|
UInt32 footerSize = Get32(p + 12);
|
|
if (headSize != RSRC_HEAD_SIZE ||
|
|
footerOffset >= rsrcLenT ||
|
|
mainDataSize >= rsrcLenT ||
|
|
footerOffset + footerSize != rsrcLenT ||
|
|
footerOffset != headSize + mainDataSize)
|
|
return S_FALSE;
|
|
if (footerSize < 16)
|
|
return S_FALSE;
|
|
if (memcmp(p, p + footerOffset, 16) != 0)
|
|
return S_FALSE;
|
|
|
|
p += footerOffset;
|
|
|
|
if ((UInt32)Get16(p + 0x18) != 0x1C)
|
|
return S_FALSE;
|
|
UInt32 namesOffset = Get16(p + 0x1A);
|
|
if (namesOffset > footerSize)
|
|
return S_FALSE;
|
|
|
|
UInt32 numItems = (UInt32)Get16(p + 0x1C) + 1;
|
|
if (numItems * 8 + 0x1E > namesOffset)
|
|
return S_FALSE;
|
|
|
|
for (UInt32 i = 0; i < numItems; i++)
|
|
{
|
|
const Byte *p2 = p + 0x1E + i * 8;
|
|
|
|
UInt32 typeId = Get32(p2);
|
|
if (typeId != 0x626C6B78) // blkx
|
|
continue;
|
|
|
|
UInt32 numFiles = (UInt32)Get16(p2 + 4) + 1;
|
|
UInt32 offs = Get16(p2 + 6);
|
|
if (0x1C + offs + 12 * numFiles > namesOffset)
|
|
return S_FALSE;
|
|
|
|
for (UInt32 k = 0; k < numFiles; k++)
|
|
{
|
|
const Byte *p3 = p + 0x1C + offs + k * 12;
|
|
// UInt32 id = Get16(p3);
|
|
UInt32 namePos = Get16(p3 + 2);
|
|
// Byte attributes = p3[4]; // = 0x50 for blkx
|
|
// we don't know how many bits we can use. So we use 24 bits only
|
|
UInt32 blockOffset = Get32(p3 + 4);
|
|
blockOffset &= (((UInt32)1 << 24) - 1);
|
|
// UInt32 unknown2 = Get32(p3 + 8); // ???
|
|
if (blockOffset + 4 >= mainDataSize)
|
|
return S_FALSE;
|
|
const Byte *pBlock = rsrcBuf + headSize + blockOffset;
|
|
UInt32 blockSize = Get32(pBlock);
|
|
|
|
#ifdef DMG_SHOW_RAW
|
|
{
|
|
CExtraFile &extra = _extras.AddNew();
|
|
{
|
|
char extraName[16];
|
|
ConvertUInt32ToString(_files.Size(), extraName);
|
|
extra.Name = extraName;
|
|
}
|
|
extra.Data.CopyFrom(pBlock + 4, blockSize);
|
|
}
|
|
#endif
|
|
|
|
CFile &file = _files.AddNew();
|
|
if (namePos != 0xFFFF)
|
|
{
|
|
UInt32 namesBlockSize = footerSize - namesOffset;
|
|
if (namePos >= namesBlockSize)
|
|
return S_FALSE;
|
|
const Byte *namePtr = p + namesOffset + namePos;
|
|
UInt32 nameLen = *namePtr;
|
|
if (namesBlockSize - namePos <= nameLen)
|
|
return S_FALSE;
|
|
for (UInt32 r = 1; r <= nameLen; r++)
|
|
{
|
|
Byte c = namePtr[r];
|
|
if (c < 0x20 || c >= 0x80)
|
|
break;
|
|
file.Name += (char)c;
|
|
}
|
|
}
|
|
RINOK(file.Parse(pBlock + 4, blockSize));
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (xmlLen >= kXmlSizeMax || xmlLen == 0)
|
|
return S_FALSE;
|
|
size_t size = (size_t)xmlLen;
|
|
if (size != xmlLen)
|
|
return S_FALSE;
|
|
|
|
RINOK(stream->Seek(_startPos + dataForkLen, STREAM_SEEK_SET, NULL));
|
|
|
|
CXml xml;
|
|
{
|
|
CObjArray<char> xmlStr(size + 1);
|
|
RINOK(ReadStream_FALSE(stream, xmlStr, size));
|
|
xmlStr[size] = 0;
|
|
// if (strlen(xmlStr) != size) return S_FALSE;
|
|
if (!xml.Parse(xmlStr))
|
|
return S_FALSE;
|
|
|
|
#ifdef DMG_SHOW_RAW
|
|
CExtraFile &extra = _extras.AddNew();
|
|
extra.Name = "a.xml";
|
|
extra.Data.CopyFrom((const Byte *)(const char *)xmlStr, size);
|
|
#endif
|
|
}
|
|
|
|
if (xml.Root.Name != "plist")
|
|
return S_FALSE;
|
|
|
|
int dictIndex = xml.Root.FindSubTag("dict");
|
|
if (dictIndex < 0)
|
|
return S_FALSE;
|
|
|
|
const CXmlItem &dictItem = xml.Root.SubItems[dictIndex];
|
|
int rfDictIndex = FindKeyPair(dictItem, "resource-fork", "dict");
|
|
if (rfDictIndex < 0)
|
|
return S_FALSE;
|
|
|
|
const CXmlItem &rfDictItem = dictItem.SubItems[rfDictIndex];
|
|
int arrIndex = FindKeyPair(rfDictItem, "blkx", "array");
|
|
if (arrIndex < 0)
|
|
return S_FALSE;
|
|
|
|
const CXmlItem &arrItem = rfDictItem.SubItems[arrIndex];
|
|
|
|
FOR_VECTOR (i, arrItem.SubItems)
|
|
{
|
|
const CXmlItem &item = arrItem.SubItems[i];
|
|
if (!item.IsTagged("dict"))
|
|
continue;
|
|
|
|
CByteBuffer rawBuf;
|
|
unsigned destLen = 0;
|
|
{
|
|
const AString *dataString = GetStringFromKeyPair(item, "Data", "data");
|
|
if (!dataString)
|
|
return S_FALSE;
|
|
destLen = dataString->Len() / 4 * 3 + 4;
|
|
rawBuf.Alloc(destLen);
|
|
{
|
|
const Byte *endPtr = Base64ToBin(rawBuf, *dataString);
|
|
if (!endPtr)
|
|
return S_FALSE;
|
|
destLen = (unsigned)(endPtr - rawBuf);
|
|
}
|
|
|
|
#ifdef DMG_SHOW_RAW
|
|
CExtraFile &extra = _extras.AddNew();
|
|
{
|
|
char extraName[16];
|
|
ConvertUInt32ToString(_files.Size(), extraName);
|
|
extra.Name = extraName;
|
|
}
|
|
extra.Data.CopyFrom(rawBuf, destLen);
|
|
#endif
|
|
}
|
|
CFile &file = _files.AddNew();
|
|
{
|
|
const AString *name = GetStringFromKeyPair(item, "Name", "string");
|
|
if (!name || name->IsEmpty())
|
|
name = GetStringFromKeyPair(item, "CFName", "string");
|
|
if (name)
|
|
file.Name = *name;
|
|
}
|
|
RINOK(file.Parse(rawBuf, destLen));
|
|
}
|
|
}
|
|
|
|
if (masterChecksum.IsCrc32())
|
|
{
|
|
UInt32 crc = CRC_INIT_VAL;
|
|
unsigned i;
|
|
for (i = 0; i < _files.Size(); i++)
|
|
{
|
|
const CChecksum &cs = _files[i].Checksum;
|
|
if ((cs.NumBits & 0x7) != 0)
|
|
break;
|
|
UInt32 len = cs.NumBits >> 3;
|
|
if (len > kChecksumSize_Max)
|
|
break;
|
|
crc = CrcUpdate(crc, cs.Data, (size_t)len);
|
|
}
|
|
if (i == _files.Size())
|
|
_masterCrcError = (CRC_GET_DIGEST(crc) != masterChecksum.GetCrc32());
|
|
}
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
STDMETHODIMP CHandler::Open(IInStream *stream,
|
|
const UInt64 * /* maxCheckStartPosition */,
|
|
IArchiveOpenCallback * /* openArchiveCallback */)
|
|
{
|
|
COM_TRY_BEGIN
|
|
{
|
|
Close();
|
|
if (Open2(stream) != S_OK)
|
|
return S_FALSE;
|
|
_inStream = stream;
|
|
}
|
|
return S_OK;
|
|
COM_TRY_END
|
|
}
|
|
|
|
STDMETHODIMP CHandler::Close()
|
|
{
|
|
_phySize = 0;
|
|
_inStream.Release();
|
|
_files.Clear();
|
|
_masterCrcError = false;
|
|
#ifdef DMG_SHOW_RAW
|
|
_extras.Clear();
|
|
#endif
|
|
return S_OK;
|
|
}
|
|
|
|
STDMETHODIMP CHandler::GetNumberOfItems(UInt32 *numItems)
|
|
{
|
|
*numItems = _files.Size()
|
|
#ifdef DMG_SHOW_RAW
|
|
+ _extras.Size()
|
|
#endif
|
|
;
|
|
return S_OK;
|
|
}
|
|
|
|
#define RAW_PREFIX "raw" STRING_PATH_SEPARATOR
|
|
|
|
STDMETHODIMP CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *value)
|
|
{
|
|
COM_TRY_BEGIN
|
|
NWindows::NCOM::CPropVariant prop;
|
|
|
|
#ifdef DMG_SHOW_RAW
|
|
if (index >= _files.Size())
|
|
{
|
|
const CExtraFile &extra = _extras[index - _files.Size()];
|
|
switch (propID)
|
|
{
|
|
case kpidPath:
|
|
prop = (AString)RAW_PREFIX + extra.Name;
|
|
break;
|
|
case kpidSize:
|
|
case kpidPackSize:
|
|
prop = (UInt64)extra.Data.Size();
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
#endif
|
|
{
|
|
const CFile &item = _files[index];
|
|
switch (propID)
|
|
{
|
|
case kpidSize: prop = item.Size; break;
|
|
case kpidPackSize: prop = item.PackSize; break;
|
|
case kpidCRC:
|
|
{
|
|
if (item.Checksum.IsCrc32() && item.FullFileChecksum)
|
|
prop = item.Checksum.GetCrc32();
|
|
break;
|
|
}
|
|
|
|
case kpidMethod:
|
|
{
|
|
CMethods m;
|
|
m.Update(item);
|
|
AString s;
|
|
m.GetString(s);
|
|
if (!s.IsEmpty())
|
|
prop = s;
|
|
break;
|
|
}
|
|
|
|
case kpidPath:
|
|
{
|
|
UString name;
|
|
wchar_t s[16];
|
|
ConvertUInt32ToString(index, s);
|
|
name = s;
|
|
unsigned num = 10;
|
|
unsigned numDigits;
|
|
for (numDigits = 1; num < _files.Size(); numDigits++)
|
|
num *= 10;
|
|
while (name.Len() < numDigits)
|
|
name.InsertAtFront(L'0');
|
|
|
|
AString subName;
|
|
int pos1 = item.Name.Find('(');
|
|
if (pos1 >= 0)
|
|
{
|
|
pos1++;
|
|
int pos2 = item.Name.Find(')', pos1);
|
|
if (pos2 >= 0)
|
|
{
|
|
subName.SetFrom(item.Name.Ptr(pos1), pos2 - pos1);
|
|
pos1 = subName.Find(':');
|
|
if (pos1 >= 0)
|
|
subName.DeleteFrom(pos1);
|
|
}
|
|
}
|
|
subName.Trim();
|
|
if (!subName.IsEmpty())
|
|
{
|
|
for (unsigned n = 0; n < kNumAppleNames; n++)
|
|
{
|
|
const CAppleName &appleName = k_Names[n];
|
|
if (appleName.Ext)
|
|
{
|
|
if (subName == appleName.AppleName)
|
|
{
|
|
subName = appleName.Ext;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
UString name2;
|
|
ConvertUTF8ToUnicode(subName, name2);
|
|
name += L'.';
|
|
name += name2;
|
|
}
|
|
else
|
|
{
|
|
UString name2;
|
|
ConvertUTF8ToUnicode(item.Name, name2);
|
|
if (!name2.IsEmpty())
|
|
name.AddAscii(" - ");
|
|
name += name2;
|
|
}
|
|
prop = name;
|
|
break;
|
|
}
|
|
|
|
case kpidComment:
|
|
{
|
|
UString name;
|
|
ConvertUTF8ToUnicode(item.Name, name);
|
|
prop = name;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
prop.Detach(value);
|
|
return S_OK;
|
|
COM_TRY_END
|
|
}
|
|
|
|
class CAdcDecoder:
|
|
public ICompressCoder,
|
|
public CMyUnknownImp
|
|
{
|
|
CLzOutWindow m_OutWindowStream;
|
|
CInBuffer m_InStream;
|
|
|
|
/*
|
|
void ReleaseStreams()
|
|
{
|
|
m_OutWindowStream.ReleaseStream();
|
|
m_InStream.ReleaseStream();
|
|
}
|
|
*/
|
|
|
|
class CCoderReleaser
|
|
{
|
|
CAdcDecoder *m_Coder;
|
|
public:
|
|
bool NeedFlush;
|
|
CCoderReleaser(CAdcDecoder *coder): m_Coder(coder), NeedFlush(true) {}
|
|
~CCoderReleaser()
|
|
{
|
|
if (NeedFlush)
|
|
m_Coder->m_OutWindowStream.Flush();
|
|
// m_Coder->ReleaseStreams();
|
|
}
|
|
};
|
|
friend class CCoderReleaser;
|
|
|
|
public:
|
|
MY_UNKNOWN_IMP
|
|
|
|
STDMETHOD(CodeReal)(ISequentialInStream *inStream,
|
|
ISequentialOutStream *outStream, const UInt64 *inSize, const UInt64 *outSize,
|
|
ICompressProgressInfo *progress);
|
|
|
|
STDMETHOD(Code)(ISequentialInStream *inStream,
|
|
ISequentialOutStream *outStream, const UInt64 *inSize, const UInt64 *outSize,
|
|
ICompressProgressInfo *progress);
|
|
};
|
|
|
|
STDMETHODIMP CAdcDecoder::CodeReal(ISequentialInStream *inStream,
|
|
ISequentialOutStream *outStream, const UInt64 *inSize, const UInt64 *outSize,
|
|
ICompressProgressInfo *progress)
|
|
{
|
|
if (!m_OutWindowStream.Create(1 << 18))
|
|
return E_OUTOFMEMORY;
|
|
if (!m_InStream.Create(1 << 18))
|
|
return E_OUTOFMEMORY;
|
|
|
|
m_OutWindowStream.SetStream(outStream);
|
|
m_OutWindowStream.Init(false);
|
|
m_InStream.SetStream(inStream);
|
|
m_InStream.Init();
|
|
|
|
CCoderReleaser coderReleaser(this);
|
|
|
|
const UInt32 kStep = (1 << 20);
|
|
UInt64 nextLimit = kStep;
|
|
|
|
UInt64 pos = 0;
|
|
while (pos < *outSize)
|
|
{
|
|
if (pos > nextLimit && progress)
|
|
{
|
|
UInt64 packSize = m_InStream.GetProcessedSize();
|
|
RINOK(progress->SetRatioInfo(&packSize, &pos));
|
|
nextLimit += kStep;
|
|
}
|
|
Byte b;
|
|
if (!m_InStream.ReadByte(b))
|
|
return S_FALSE;
|
|
UInt64 rem = *outSize - pos;
|
|
if (b & 0x80)
|
|
{
|
|
unsigned num = (b & 0x7F) + 1;
|
|
if (num > rem)
|
|
return S_FALSE;
|
|
for (unsigned i = 0; i < num; i++)
|
|
{
|
|
if (!m_InStream.ReadByte(b))
|
|
return S_FALSE;
|
|
m_OutWindowStream.PutByte(b);
|
|
}
|
|
pos += num;
|
|
continue;
|
|
}
|
|
Byte b1;
|
|
if (!m_InStream.ReadByte(b1))
|
|
return S_FALSE;
|
|
|
|
UInt32 len, distance;
|
|
|
|
if (b & 0x40)
|
|
{
|
|
len = ((UInt32)b & 0x3F) + 4;
|
|
Byte b2;
|
|
if (!m_InStream.ReadByte(b2))
|
|
return S_FALSE;
|
|
distance = ((UInt32)b1 << 8) + b2;
|
|
}
|
|
else
|
|
{
|
|
b &= 0x3F;
|
|
len = ((UInt32)b >> 2) + 3;
|
|
distance = (((UInt32)b & 3) << 8) + b1;
|
|
}
|
|
|
|
if (distance >= pos || len > rem)
|
|
return S_FALSE;
|
|
m_OutWindowStream.CopyBlock(distance, len);
|
|
pos += len;
|
|
}
|
|
if (*inSize != m_InStream.GetProcessedSize())
|
|
return S_FALSE;
|
|
coderReleaser.NeedFlush = false;
|
|
return m_OutWindowStream.Flush();
|
|
}
|
|
|
|
STDMETHODIMP CAdcDecoder::Code(ISequentialInStream *inStream,
|
|
ISequentialOutStream *outStream, const UInt64 *inSize, const UInt64 *outSize,
|
|
ICompressProgressInfo *progress)
|
|
{
|
|
try { return CodeReal(inStream, outStream, inSize, outSize, progress);}
|
|
catch(const CInBufferException &e) { return e.ErrorCode; }
|
|
catch(const CLzOutWindowException &e) { return e.ErrorCode; }
|
|
catch(...) { return S_FALSE; }
|
|
}
|
|
|
|
|
|
STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems,
|
|
Int32 testMode, IArchiveExtractCallback *extractCallback)
|
|
{
|
|
COM_TRY_BEGIN
|
|
bool allFilesMode = (numItems == (UInt32)(Int32)-1);
|
|
if (allFilesMode)
|
|
numItems = _files.Size();
|
|
if (numItems == 0)
|
|
return S_OK;
|
|
UInt64 totalSize = 0;
|
|
UInt32 i;
|
|
|
|
for (i = 0; i < numItems; i++)
|
|
{
|
|
UInt32 index = (allFilesMode ? i : indices[i]);
|
|
#ifdef DMG_SHOW_RAW
|
|
if (index >= _files.Size())
|
|
totalSize += _extras[index - _files.Size()].Data.Size();
|
|
else
|
|
#endif
|
|
totalSize += _files[index].Size;
|
|
}
|
|
extractCallback->SetTotal(totalSize);
|
|
|
|
UInt64 currentPackTotal = 0;
|
|
UInt64 currentUnpTotal = 0;
|
|
UInt64 currentPackSize = 0;
|
|
UInt64 currentUnpSize = 0;
|
|
|
|
const UInt32 kZeroBufSize = (1 << 14);
|
|
CByteBuffer zeroBuf(kZeroBufSize);
|
|
memset(zeroBuf, 0, kZeroBufSize);
|
|
|
|
NCompress::CCopyCoder *copyCoderSpec = new NCompress::CCopyCoder();
|
|
CMyComPtr<ICompressCoder> copyCoder = copyCoderSpec;
|
|
|
|
NCompress::NBZip2::CDecoder *bzip2CoderSpec = new NCompress::NBZip2::CDecoder();
|
|
CMyComPtr<ICompressCoder> bzip2Coder = bzip2CoderSpec;
|
|
|
|
NCompress::NZlib::CDecoder *zlibCoderSpec = new NCompress::NZlib::CDecoder();
|
|
CMyComPtr<ICompressCoder> zlibCoder = zlibCoderSpec;
|
|
|
|
CAdcDecoder *adcCoderSpec = new CAdcDecoder();
|
|
CMyComPtr<ICompressCoder> adcCoder = adcCoderSpec;
|
|
|
|
CLocalProgress *lps = new CLocalProgress;
|
|
CMyComPtr<ICompressProgressInfo> progress = lps;
|
|
lps->Init(extractCallback, false);
|
|
|
|
CLimitedSequentialInStream *streamSpec = new CLimitedSequentialInStream;
|
|
CMyComPtr<ISequentialInStream> inStream(streamSpec);
|
|
streamSpec->SetStream(_inStream);
|
|
|
|
for (i = 0; i < numItems; i++, currentPackTotal += currentPackSize, currentUnpTotal += currentUnpSize)
|
|
{
|
|
lps->InSize = currentPackTotal;
|
|
lps->OutSize = currentUnpTotal;
|
|
currentPackSize = 0;
|
|
currentUnpSize = 0;
|
|
RINOK(lps->SetCur());
|
|
CMyComPtr<ISequentialOutStream> realOutStream;
|
|
Int32 askMode = testMode ?
|
|
NExtract::NAskMode::kTest :
|
|
NExtract::NAskMode::kExtract;
|
|
UInt32 index = allFilesMode ? i : indices[i];
|
|
RINOK(extractCallback->GetStream(index, &realOutStream, askMode));
|
|
|
|
if (!testMode && !realOutStream)
|
|
continue;
|
|
RINOK(extractCallback->PrepareOperation(askMode));
|
|
|
|
|
|
COutStreamWithCRC *outCrcStreamSpec = new COutStreamWithCRC;
|
|
CMyComPtr<ISequentialOutStream> outCrcStream = outCrcStreamSpec;
|
|
outCrcStreamSpec->SetStream(realOutStream);
|
|
bool needCrc = false;
|
|
outCrcStreamSpec->Init(needCrc);
|
|
|
|
CLimitedSequentialOutStream *outStreamSpec = new CLimitedSequentialOutStream;
|
|
CMyComPtr<ISequentialOutStream> outStream(outStreamSpec);
|
|
outStreamSpec->SetStream(outCrcStream);
|
|
|
|
realOutStream.Release();
|
|
|
|
Int32 opRes = NExtract::NOperationResult::kOK;
|
|
#ifdef DMG_SHOW_RAW
|
|
if (index >= _files.Size())
|
|
{
|
|
const CByteBuffer &buf = _extras[index - _files.Size()].Data;
|
|
outStreamSpec->Init(buf.Size());
|
|
RINOK(WriteStream(outStream, buf, buf.Size()));
|
|
currentPackSize = currentUnpSize = buf.Size();
|
|
}
|
|
else
|
|
#endif
|
|
{
|
|
const CFile &item = _files[index];
|
|
currentPackSize = item.PackSize;
|
|
currentUnpSize = item.Size;
|
|
|
|
needCrc = item.Checksum.IsCrc32();
|
|
|
|
UInt64 unpPos = 0;
|
|
UInt64 packPos = 0;
|
|
{
|
|
FOR_VECTOR (j, item.Blocks)
|
|
{
|
|
lps->InSize = currentPackTotal + packPos;
|
|
lps->OutSize = currentUnpTotal + unpPos;
|
|
RINOK(lps->SetCur());
|
|
|
|
const CBlock &block = item.Blocks[j];
|
|
if (!block.ThereAreDataInBlock())
|
|
continue;
|
|
|
|
packPos += block.PackSize;
|
|
if (block.UnpPos != unpPos)
|
|
{
|
|
opRes = NExtract::NOperationResult::kDataError;
|
|
break;
|
|
}
|
|
|
|
RINOK(_inStream->Seek(_startPos + item.StartPos + block.PackPos, STREAM_SEEK_SET, NULL));
|
|
streamSpec->Init(block.PackSize);
|
|
bool realMethod = true;
|
|
outStreamSpec->Init(block.UnpSize);
|
|
HRESULT res = S_OK;
|
|
|
|
outCrcStreamSpec->EnableCalc(needCrc);
|
|
|
|
switch (block.Type)
|
|
{
|
|
case METHOD_ZERO_0:
|
|
case METHOD_ZERO_2:
|
|
realMethod = false;
|
|
if (block.PackSize != 0)
|
|
opRes = NExtract::NOperationResult::kUnsupportedMethod;
|
|
outCrcStreamSpec->EnableCalc(block.Type == METHOD_ZERO_0);
|
|
break;
|
|
|
|
case METHOD_COPY:
|
|
if (block.UnpSize != block.PackSize)
|
|
{
|
|
opRes = NExtract::NOperationResult::kUnsupportedMethod;
|
|
break;
|
|
}
|
|
res = copyCoder->Code(inStream, outStream, NULL, NULL, progress);
|
|
break;
|
|
|
|
case METHOD_ADC:
|
|
{
|
|
res = adcCoder->Code(inStream, outStream, &block.PackSize, &block.UnpSize, progress);
|
|
break;
|
|
}
|
|
|
|
case METHOD_ZLIB:
|
|
{
|
|
res = zlibCoder->Code(inStream, outStream, NULL, NULL, progress);
|
|
if (res == S_OK)
|
|
if (zlibCoderSpec->GetInputProcessedSize() != block.PackSize)
|
|
opRes = NExtract::NOperationResult::kDataError;
|
|
break;
|
|
}
|
|
|
|
case METHOD_BZIP2:
|
|
{
|
|
res = bzip2Coder->Code(inStream, outStream, NULL, NULL, progress);
|
|
if (res == S_OK)
|
|
if (bzip2CoderSpec->GetInputProcessedSize() != block.PackSize)
|
|
opRes = NExtract::NOperationResult::kDataError;
|
|
break;
|
|
}
|
|
|
|
default:
|
|
opRes = NExtract::NOperationResult::kUnsupportedMethod;
|
|
break;
|
|
}
|
|
|
|
if (res != S_OK)
|
|
{
|
|
if (res != S_FALSE)
|
|
return res;
|
|
if (opRes == NExtract::NOperationResult::kOK)
|
|
opRes = NExtract::NOperationResult::kDataError;
|
|
}
|
|
|
|
unpPos += block.UnpSize;
|
|
|
|
if (!outStreamSpec->IsFinishedOK())
|
|
{
|
|
if (realMethod && opRes == NExtract::NOperationResult::kOK)
|
|
opRes = NExtract::NOperationResult::kDataError;
|
|
|
|
while (outStreamSpec->GetRem() != 0)
|
|
{
|
|
UInt64 rem = outStreamSpec->GetRem();
|
|
UInt32 size = (UInt32)MyMin(rem, (UInt64)kZeroBufSize);
|
|
RINOK(WriteStream(outStream, zeroBuf, size));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (needCrc && opRes == NExtract::NOperationResult::kOK)
|
|
{
|
|
if (outCrcStreamSpec->GetCRC() != item.Checksum.GetCrc32())
|
|
opRes = NExtract::NOperationResult::kCRCError;
|
|
}
|
|
}
|
|
outStream.Release();
|
|
RINOK(extractCallback->SetOperationResult(opRes));
|
|
}
|
|
|
|
return S_OK;
|
|
COM_TRY_END
|
|
}
|
|
|
|
struct CChunk
|
|
{
|
|
int BlockIndex;
|
|
UInt64 AccessMark;
|
|
CByteBuffer Buf;
|
|
};
|
|
|
|
class CInStream:
|
|
public IInStream,
|
|
public CMyUnknownImp
|
|
{
|
|
UInt64 _virtPos;
|
|
int _latestChunk;
|
|
int _latestBlock;
|
|
UInt64 _accessMark;
|
|
CObjectVector<CChunk> _chunks;
|
|
|
|
NCompress::NBZip2::CDecoder *bzip2CoderSpec;
|
|
CMyComPtr<ICompressCoder> bzip2Coder;
|
|
|
|
NCompress::NZlib::CDecoder *zlibCoderSpec;
|
|
CMyComPtr<ICompressCoder> zlibCoder;
|
|
|
|
CAdcDecoder *adcCoderSpec;
|
|
CMyComPtr<ICompressCoder> adcCoder;
|
|
|
|
CBufPtrSeqOutStream *outStreamSpec;
|
|
CMyComPtr<ISequentialOutStream> outStream;
|
|
|
|
CLimitedSequentialInStream *limitedStreamSpec;
|
|
CMyComPtr<ISequentialInStream> inStream;
|
|
|
|
public:
|
|
CMyComPtr<IInStream> Stream;
|
|
UInt64 Size;
|
|
const CFile *File;
|
|
UInt64 _startPos;
|
|
|
|
HRESULT InitAndSeek(UInt64 startPos)
|
|
{
|
|
_startPos = startPos;
|
|
_virtPos = 0;
|
|
_latestChunk = -1;
|
|
_latestBlock = -1;
|
|
_accessMark = 0;
|
|
|
|
limitedStreamSpec = new CLimitedSequentialInStream;
|
|
inStream = limitedStreamSpec;
|
|
limitedStreamSpec->SetStream(Stream);
|
|
|
|
outStreamSpec = new CBufPtrSeqOutStream;
|
|
outStream = outStreamSpec;
|
|
return S_OK;
|
|
}
|
|
|
|
MY_UNKNOWN_IMP1(IInStream)
|
|
|
|
STDMETHOD(Read)(void *data, UInt32 size, UInt32 *processedSize);
|
|
STDMETHOD(Seek)(Int64 offset, UInt32 seekOrigin, UInt64 *newPosition);
|
|
};
|
|
|
|
|
|
unsigned FindBlock(const CRecordVector<CBlock> &blocks, UInt64 pos)
|
|
{
|
|
unsigned left = 0, right = blocks.Size();
|
|
for (;;)
|
|
{
|
|
unsigned mid = (left + right) / 2;
|
|
if (mid == left)
|
|
return left;
|
|
if (pos < blocks[mid].UnpPos)
|
|
right = mid;
|
|
else
|
|
left = mid;
|
|
}
|
|
}
|
|
|
|
STDMETHODIMP CInStream::Read(void *data, UInt32 size, UInt32 *processedSize)
|
|
{
|
|
COM_TRY_BEGIN
|
|
|
|
if (processedSize)
|
|
*processedSize = 0;
|
|
if (size == 0)
|
|
return S_OK;
|
|
if (_virtPos >= Size)
|
|
return S_OK; // (Size == _virtPos) ? S_OK: E_FAIL;
|
|
{
|
|
UInt64 rem = Size - _virtPos;
|
|
if (size > rem)
|
|
size = (UInt32)rem;
|
|
}
|
|
|
|
if (_latestBlock >= 0)
|
|
{
|
|
const CBlock &block = File->Blocks[_latestBlock];
|
|
if (_virtPos < block.UnpPos || (_virtPos - block.UnpPos) >= block.UnpSize)
|
|
_latestBlock = -1;
|
|
}
|
|
|
|
if (_latestBlock < 0)
|
|
{
|
|
_latestChunk = -1;
|
|
unsigned blockIndex = FindBlock(File->Blocks, _virtPos);
|
|
const CBlock &block = File->Blocks[blockIndex];
|
|
|
|
if (!block.IsZeroMethod() && block.Type != METHOD_COPY)
|
|
{
|
|
unsigned i;
|
|
for (i = 0; i < _chunks.Size(); i++)
|
|
if (_chunks[i].BlockIndex == (int)blockIndex)
|
|
break;
|
|
|
|
if (i != _chunks.Size())
|
|
_latestChunk = i;
|
|
else
|
|
{
|
|
const unsigned kNumChunksMax = 128;
|
|
unsigned chunkIndex;
|
|
|
|
if (_chunks.Size() != kNumChunksMax)
|
|
chunkIndex = _chunks.Add(CChunk());
|
|
else
|
|
{
|
|
chunkIndex = 0;
|
|
for (i = 0; i < _chunks.Size(); i++)
|
|
if (_chunks[i].AccessMark < _chunks[chunkIndex].AccessMark)
|
|
chunkIndex = i;
|
|
}
|
|
|
|
CChunk &chunk = _chunks[chunkIndex];
|
|
chunk.BlockIndex = -1;
|
|
chunk.AccessMark = 0;
|
|
|
|
if (chunk.Buf.Size() < block.UnpSize)
|
|
{
|
|
chunk.Buf.Free();
|
|
if (block.UnpSize > ((UInt32)1 << 31))
|
|
return E_FAIL;
|
|
chunk.Buf.Alloc((size_t)block.UnpSize);
|
|
}
|
|
|
|
outStreamSpec->Init(chunk.Buf, (size_t)block.UnpSize);
|
|
|
|
RINOK(Stream->Seek(_startPos + File->StartPos + block.PackPos, STREAM_SEEK_SET, NULL));
|
|
|
|
limitedStreamSpec->Init(block.PackSize);
|
|
HRESULT res = S_OK;
|
|
|
|
switch (block.Type)
|
|
{
|
|
case METHOD_COPY:
|
|
if (block.PackSize != block.UnpSize)
|
|
return E_FAIL;
|
|
res = ReadStream_FAIL(inStream, chunk.Buf, (size_t)block.UnpSize);
|
|
break;
|
|
|
|
case METHOD_ADC:
|
|
if (!adcCoder)
|
|
{
|
|
adcCoderSpec = new CAdcDecoder();
|
|
adcCoder = adcCoderSpec;
|
|
}
|
|
res = adcCoder->Code(inStream, outStream, &block.PackSize, &block.UnpSize, NULL);
|
|
break;
|
|
|
|
case METHOD_ZLIB:
|
|
if (!zlibCoder)
|
|
{
|
|
zlibCoderSpec = new NCompress::NZlib::CDecoder();
|
|
zlibCoder = zlibCoderSpec;
|
|
}
|
|
res = zlibCoder->Code(inStream, outStream, NULL, NULL, NULL);
|
|
if (res == S_OK && zlibCoderSpec->GetInputProcessedSize() != block.PackSize)
|
|
res = S_FALSE;
|
|
break;
|
|
|
|
case METHOD_BZIP2:
|
|
if (!bzip2Coder)
|
|
{
|
|
bzip2CoderSpec = new NCompress::NBZip2::CDecoder();
|
|
bzip2Coder = bzip2CoderSpec;
|
|
}
|
|
res = bzip2Coder->Code(inStream, outStream, NULL, NULL, NULL);
|
|
if (res == S_OK && bzip2CoderSpec->GetInputProcessedSize() != block.PackSize)
|
|
res = S_FALSE;
|
|
break;
|
|
|
|
default:
|
|
return E_FAIL;
|
|
}
|
|
|
|
if (res != S_OK)
|
|
return res;
|
|
if (block.Type != METHOD_COPY && outStreamSpec->GetPos() != block.UnpSize)
|
|
return E_FAIL;
|
|
chunk.BlockIndex = blockIndex;
|
|
_latestChunk = chunkIndex;
|
|
}
|
|
|
|
_chunks[_latestChunk].AccessMark = _accessMark++;
|
|
}
|
|
|
|
_latestBlock = blockIndex;
|
|
}
|
|
|
|
const CBlock &block = File->Blocks[_latestBlock];
|
|
UInt64 offset = _virtPos - block.UnpPos;
|
|
UInt64 rem = block.UnpSize - offset;
|
|
if (size > rem)
|
|
size = (UInt32)rem;
|
|
|
|
HRESULT res = S_OK;
|
|
|
|
if (block.Type == METHOD_COPY)
|
|
{
|
|
RINOK(Stream->Seek(_startPos + File->StartPos + block.PackPos + offset, STREAM_SEEK_SET, NULL));
|
|
res = Stream->Read(data, size, &size);
|
|
}
|
|
else if (block.IsZeroMethod())
|
|
memset(data, 0, size);
|
|
else if (size != 0)
|
|
memcpy(data, _chunks[_latestChunk].Buf + offset, size);
|
|
|
|
_virtPos += size;
|
|
if (processedSize)
|
|
*processedSize = size;
|
|
|
|
return res;
|
|
COM_TRY_END
|
|
}
|
|
|
|
STDMETHODIMP CInStream::Seek(Int64 offset, UInt32 seekOrigin, UInt64 *newPosition)
|
|
{
|
|
switch (seekOrigin)
|
|
{
|
|
case STREAM_SEEK_SET: break;
|
|
case STREAM_SEEK_CUR: offset += _virtPos; break;
|
|
case STREAM_SEEK_END: offset += Size; break;
|
|
default: return STG_E_INVALIDFUNCTION;
|
|
}
|
|
if (offset < 0)
|
|
return HRESULT_WIN32_ERROR_NEGATIVE_SEEK;
|
|
_virtPos = offset;
|
|
if (newPosition)
|
|
*newPosition = offset;
|
|
return S_OK;
|
|
}
|
|
|
|
STDMETHODIMP CHandler::GetStream(UInt32 index, ISequentialInStream **stream)
|
|
{
|
|
COM_TRY_BEGIN
|
|
|
|
#ifdef DMG_SHOW_RAW
|
|
if (index >= (UInt32)_files.Size())
|
|
return S_FALSE;
|
|
#endif
|
|
|
|
CInStream *spec = new CInStream;
|
|
CMyComPtr<ISequentialInStream> specStream = spec;
|
|
spec->File = &_files[index];
|
|
const CFile &file = *spec->File;
|
|
|
|
FOR_VECTOR (i, file.Blocks)
|
|
{
|
|
const CBlock &block = file.Blocks[i];
|
|
switch (block.Type)
|
|
{
|
|
case METHOD_ZERO_0:
|
|
case METHOD_ZERO_2:
|
|
case METHOD_COPY:
|
|
case METHOD_ADC:
|
|
case METHOD_ZLIB:
|
|
case METHOD_BZIP2:
|
|
case METHOD_END:
|
|
break;
|
|
default:
|
|
return S_FALSE;
|
|
}
|
|
}
|
|
|
|
spec->Stream = _inStream;
|
|
spec->Size = spec->File->Size;
|
|
RINOK(spec->InitAndSeek(_startPos));
|
|
*stream = specStream.Detach();
|
|
return S_OK;
|
|
|
|
COM_TRY_END
|
|
}
|
|
|
|
REGISTER_ARC_I(
|
|
"Dmg", "dmg", 0, 0xE4,
|
|
k_Signature,
|
|
0,
|
|
NArcInfoFlags::kBackwardOpen |
|
|
NArcInfoFlags::kUseGlobalOffset,
|
|
NULL)
|
|
|
|
}}
|