d074cc7c07
Plugin: ns7zip v2.0.0 Architectures: x86-ansi, x86-unicode, amd64-unicode License: LGPL-2.1-or-later
2337 lines
59 KiB
C++
2337 lines
59 KiB
C++
// DmgHandler.cpp
|
|
|
|
#include "StdAfx.h"
|
|
|
|
#include "../../../C/Alloc.h"
|
|
#include "../../../C/CpuArch.h"
|
|
|
|
#include "../../Common/AutoPtr.h"
|
|
#include "../../Common/ComTry.h"
|
|
#include "../../Common/IntToString.h"
|
|
#include "../../Common/MyBuffer2.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/LzfseDecoder.h"
|
|
#include "../Compress/XzDecoder.h"
|
|
#include "../Compress/ZlibDecoder.h"
|
|
|
|
#include "Common/OutStreamWithCRC.h"
|
|
|
|
// #define DMG_SHOW_RAW
|
|
|
|
// #define SHOW_DEBUG_INFO
|
|
|
|
/* for debug only: we can use block cache also for METHOD_COPY blocks.
|
|
It can reduce the number of Stream->Read() requests.
|
|
But it can increase memory usage.
|
|
Note: METHOD_COPY blocks are not fused usually.
|
|
But if METHOD_COPY blocks is fused in some dmg example,
|
|
block size can exceed k_Chunk_Size_MAX.
|
|
So we don't use cache for METHOD_COPY block, if (block_size > k_Chunk_Size_MAX)
|
|
*/
|
|
// for debug only:
|
|
// #define Z7_DMG_USE_CACHE_FOR_COPY_BLOCKS
|
|
|
|
#ifdef SHOW_DEBUG_INFO
|
|
#include <stdio.h>
|
|
#define PRF(x) x
|
|
#else
|
|
#define PRF(x)
|
|
#endif
|
|
|
|
|
|
#define Get16(p) GetBe16(p)
|
|
#define Get32(p) GetBe32(p)
|
|
#define Get64(p) GetBe64(p)
|
|
|
|
#define Get32a(p) GetBe32a(p)
|
|
#define Get64a(p) GetBe64a(p)
|
|
|
|
Byte *Base64ToBin(Byte *dest, const char *src);
|
|
|
|
namespace NArchive {
|
|
namespace NDmg {
|
|
|
|
// allocation limits for compressed blocks for GetStream() interface:
|
|
static const unsigned k_NumChunks_MAX = 128;
|
|
static const size_t k_Chunk_Size_MAX = (size_t)1 << 28;
|
|
// 256 MB cache for 32-bit:
|
|
// 4 GB cache for 64-bit:
|
|
static const size_t k_Chunks_TotalSize_MAX = (size_t)1 << (sizeof(size_t) + 24);
|
|
|
|
// 2 GB limit for 32-bit:
|
|
// 4 GB limit for 64-bit:
|
|
// that limit can be increased for 64-bit mode, if there are such dmg files
|
|
static const size_t k_XmlSize_MAX =
|
|
((size_t)1 << (sizeof(size_t) / 4 + 30)) - 256;
|
|
|
|
static const UInt32 METHOD_ZERO_0 = 0; // sparse
|
|
static const UInt32 METHOD_COPY = 1;
|
|
static const UInt32 METHOD_ZERO_2 = 2; // sparse : without file CRC calculation
|
|
static const UInt32 METHOD_ADC = 0x80000004;
|
|
static const UInt32 METHOD_ZLIB = 0x80000005;
|
|
static const UInt32 METHOD_BZIP2 = 0x80000006;
|
|
static const UInt32 METHOD_LZFSE = 0x80000007;
|
|
static const UInt32 METHOD_XZ = 0x80000008;
|
|
static const UInt32 METHOD_COMMENT = 0x7FFFFFFE; // is used to comment "+beg" and "+end" in extra field.
|
|
static const UInt32 METHOD_END = 0xFFFFFFFF;
|
|
|
|
|
|
struct CBlock
|
|
{
|
|
UInt32 Type;
|
|
UInt64 UnpPos;
|
|
// UInt64 UnpSize;
|
|
UInt64 PackPos;
|
|
UInt64 PackSize;
|
|
|
|
bool NeedCrc() const { return Type != METHOD_ZERO_2; }
|
|
bool IsZeroMethod() const
|
|
{
|
|
return (Type & ~(UInt32)METHOD_ZERO_2) == 0;
|
|
// return Type == METHOD_ZERO_0 || Type == METHOD_ZERO_2;
|
|
}
|
|
|
|
bool IsClusteredMethod() const
|
|
{
|
|
// most of dmg files have non-fused COPY_METHOD blocks.
|
|
// so we don't exclude COPY_METHOD blocks when we try to detect size of cluster.
|
|
return !IsZeroMethod(); // include COPY_METHOD blocks.
|
|
// Type > METHOD_ZERO_2; // for debug: exclude COPY_METHOD blocks.
|
|
}
|
|
|
|
bool NeedAllocateBuffer() const
|
|
{
|
|
return
|
|
#ifdef Z7_DMG_USE_CACHE_FOR_COPY_BLOCKS
|
|
!IsZeroMethod();
|
|
#else
|
|
Type > METHOD_ZERO_2;
|
|
// !IsZeroMethod() && Type != METHOD_COPY;
|
|
#endif
|
|
}
|
|
// we don't store non-data blocks now
|
|
// bool ThereAreDataInBlock() const { return Type != METHOD_COMMENT && Type != METHOD_END; }
|
|
};
|
|
|
|
static const UInt32 kCheckSumType_CRC = 2;
|
|
static const unsigned 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 PrintType(AString &s) const;
|
|
void Print(AString &s) const;
|
|
void Print_with_Name(AString &s) const;
|
|
void AddToComment(AString &s, const char *name) const;
|
|
};
|
|
|
|
void CChecksum::Parse(const Byte *p)
|
|
{
|
|
Type = Get32(p);
|
|
NumBits = Get32(p + 4);
|
|
memcpy(Data, p + 8, kChecksumSize_Max);
|
|
}
|
|
|
|
|
|
void CChecksum::PrintType(AString &s) const
|
|
{
|
|
if (NumBits == 0)
|
|
return;
|
|
if (IsCrc32())
|
|
s += "CRC";
|
|
else
|
|
{
|
|
s += "Checksum";
|
|
s.Add_UInt32(Type);
|
|
s.Add_Minus();
|
|
s.Add_UInt32(NumBits);
|
|
}
|
|
}
|
|
|
|
void CChecksum::Print(AString &s) const
|
|
{
|
|
if (NumBits == 0)
|
|
return;
|
|
char temp[kChecksumSize_Max * 2 + 2];
|
|
/*
|
|
if (IsCrc32())
|
|
ConvertUInt32ToHex8Digits(GetCrc32(), temp);
|
|
else
|
|
*/
|
|
{
|
|
UInt32 numBits = kChecksumSize_Max * 8;
|
|
if (numBits > NumBits)
|
|
numBits = NumBits;
|
|
const unsigned numBytes = (numBits + 7) >> 3;
|
|
if (numBytes <= 8)
|
|
ConvertDataToHex_Upper(temp, Data, numBytes);
|
|
else
|
|
ConvertDataToHex_Lower(temp, Data, numBytes);
|
|
}
|
|
s += temp;
|
|
}
|
|
|
|
void CChecksum::Print_with_Name(AString &s) const
|
|
{
|
|
if (NumBits == 0)
|
|
return;
|
|
PrintType(s);
|
|
s += ": ";
|
|
Print(s);
|
|
}
|
|
|
|
static void AddToComment_Prop(AString &s, const char *name, const char *val)
|
|
{
|
|
s += name;
|
|
s += ": ";
|
|
s += val;
|
|
s.Add_LF();
|
|
}
|
|
|
|
void CChecksum::AddToComment(AString &s, const char *name) const
|
|
{
|
|
AString s2;
|
|
Print_with_Name(s2);
|
|
if (!s2.IsEmpty())
|
|
AddToComment_Prop(s, name, s2);
|
|
}
|
|
|
|
|
|
struct CFile
|
|
{
|
|
UInt64 Size;
|
|
CRecordVector<CBlock> Blocks;
|
|
UInt64 PackSize;
|
|
UInt64 StartPackPos;
|
|
UInt64 BlockSize_MAX;
|
|
UInt64 StartUnpackSector; // unpack sector position of this file from all files
|
|
UInt64 NumUnpackSectors;
|
|
Int32 Descriptor;
|
|
bool IsCorrect;
|
|
bool FullFileChecksum;
|
|
AString Name;
|
|
// AString Id;
|
|
CChecksum Checksum;
|
|
|
|
UInt64 GetUnpackSize_of_Block(unsigned blockIndex) const
|
|
{
|
|
return (blockIndex == Blocks.Size() - 1 ?
|
|
Size : Blocks[blockIndex + 1].UnpPos) - Blocks[blockIndex].UnpPos;
|
|
}
|
|
|
|
CFile():
|
|
Size(0),
|
|
PackSize(0),
|
|
StartPackPos(0),
|
|
BlockSize_MAX(0),
|
|
StartUnpackSector(0),
|
|
NumUnpackSectors(0),
|
|
Descriptor(0),
|
|
IsCorrect(false),
|
|
FullFileChecksum(false)
|
|
{
|
|
Checksum.Type = 0;
|
|
Checksum.NumBits = 0;
|
|
}
|
|
HRESULT Parse(const Byte *p, UInt32 size);
|
|
};
|
|
|
|
#ifdef DMG_SHOW_RAW
|
|
struct CExtraFile
|
|
{
|
|
CByteBuffer Data;
|
|
AString Name;
|
|
};
|
|
#endif
|
|
|
|
|
|
static void AddToComment_UInt64(AString &s, UInt64 v, const char *name)
|
|
{
|
|
s += name;
|
|
s += ": ";
|
|
s.Add_UInt64(v);
|
|
s.Add_LF();
|
|
}
|
|
|
|
struct CForkPair
|
|
{
|
|
UInt64 Offset;
|
|
UInt64 Len;
|
|
|
|
// (p) is aligned for 8-bytes
|
|
void Parse(const Byte *p)
|
|
{
|
|
Offset = Get64a(p);
|
|
Len = Get64a(p + 8);
|
|
}
|
|
|
|
bool GetEndPos(UInt64 &endPos)
|
|
{
|
|
endPos = Offset + Len;
|
|
return endPos >= Offset;
|
|
}
|
|
|
|
bool UpdateTop(UInt64 limit, UInt64 &top)
|
|
{
|
|
if (Offset > limit || Len > limit - Offset)
|
|
return false;
|
|
const UInt64 top2 = Offset + Len;
|
|
if (top <= top2)
|
|
top = top2;
|
|
return true;
|
|
}
|
|
|
|
void Print(AString &s, const char *name) const;
|
|
};
|
|
|
|
void CForkPair::Print(AString &s, const char *name) const
|
|
{
|
|
if (Offset != 0 || Len != 0)
|
|
{
|
|
s += name; s.Add_Minus(); AddToComment_UInt64(s, Offset, "offset");
|
|
s += name; s.Add_Minus(); AddToComment_UInt64(s, Len, "length");
|
|
}
|
|
}
|
|
|
|
|
|
Z7_CLASS_IMP_CHandler_IInArchive_1(
|
|
IInArchiveGetStream
|
|
)
|
|
bool _masterCrcError;
|
|
bool _headersError;
|
|
bool _dataForkError;
|
|
bool _rsrcMode_wasUsed;
|
|
|
|
CMyComPtr<IInStream> _inStream;
|
|
CObjectVector<CFile> _files;
|
|
|
|
// UInt32 _dataStartOffset;
|
|
UInt64 _startPos;
|
|
UInt64 _phySize;
|
|
|
|
AString _name;
|
|
|
|
CForkPair _dataForkPair;
|
|
CForkPair rsrcPair, xmlPair, blobPair;
|
|
|
|
// UInt64 _runningDataForkOffset;
|
|
// UInt32 _segmentNumber;
|
|
// UInt32 _segmentCount;
|
|
UInt64 _numSectors; // total unpacked number of sectors
|
|
Byte _segmentGUID[16];
|
|
|
|
CChecksum _dataForkChecksum;
|
|
CChecksum _masterChecksum;
|
|
|
|
#ifdef DMG_SHOW_RAW
|
|
CObjectVector<CExtraFile> _extras;
|
|
#endif
|
|
|
|
HRESULT ReadData(IInStream *stream, const CForkPair &pair, CByteBuffer &buf);
|
|
bool ParseBlob(const CByteBuffer &data);
|
|
HRESULT Open2(IInStream *stream, IArchiveOpenCallback *openArchiveCallback);
|
|
HRESULT Extract(IInStream *stream);
|
|
};
|
|
|
|
|
|
struct CMethods
|
|
{
|
|
CRecordVector<UInt32> Types;
|
|
|
|
void Update(const CFile &file);
|
|
void AddToString(AString &s) const;
|
|
};
|
|
|
|
void CMethods::Update(const CFile &file)
|
|
{
|
|
FOR_VECTOR (i, file.Blocks)
|
|
{
|
|
if (Types.Size() >= (1 << 8))
|
|
break;
|
|
Types.AddToUniqueSorted(file.Blocks[i].Type);
|
|
}
|
|
}
|
|
|
|
void CMethods::AddToString(AString &res) const
|
|
{
|
|
FOR_VECTOR (i, Types)
|
|
{
|
|
const UInt32 type = Types[i];
|
|
/*
|
|
if (type == METHOD_COMMENT || type == METHOD_END)
|
|
continue;
|
|
*/
|
|
char buf[16];
|
|
const char *s;
|
|
switch (type)
|
|
{
|
|
// case METHOD_COMMENT: s = "Comment"; break;
|
|
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;
|
|
case METHOD_LZFSE: s = "LZFSE"; break;
|
|
case METHOD_XZ: s = "XZ"; break;
|
|
default: ConvertUInt32ToHex(type, buf); s = buf;
|
|
}
|
|
res.Add_OptSpaced(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" },
|
|
{ true, "apfs", "Apple_APFS" },
|
|
{ true, "iso", "Apple_ISO" },
|
|
|
|
// efi_sys partition is FAT32, but it's not main file. So we use (IsFs = false)
|
|
{ false, "efi_sys", "C12A7328-F81F-11D2-BA4B-00A0C93EC93B" },
|
|
|
|
{ false, "free", "Apple_Free" },
|
|
{ false, "ddm", "DDM" },
|
|
{ false, NULL, "Apple_partition_map" },
|
|
{ false, NULL, " GPT " },
|
|
/* " GPT " is substring of full name entry in dmg that contains
|
|
some small metadata GPT entry. It's not real FS entry:
|
|
"Primary GPT Header",
|
|
"Primary GPT Table"
|
|
"Backup GPT Header",
|
|
"Backup GPT Table",
|
|
*/
|
|
{ false, NULL, "MBR" },
|
|
{ false, NULL, "Driver" },
|
|
{ false, NULL, "Patches" }
|
|
};
|
|
|
|
static const unsigned kNumAppleNames = Z7_ARRAY_SIZE(k_Names);
|
|
|
|
const char *Find_Apple_FS_Ext(const AString &name);
|
|
const char *Find_Apple_FS_Ext(const AString &name)
|
|
{
|
|
for (unsigned i = 0; i < kNumAppleNames; i++)
|
|
{
|
|
const CAppleName &a = k_Names[i];
|
|
if (a.Ext)
|
|
if (name.IsEqualTo(a.AppleName))
|
|
return a.Ext;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
|
|
bool Is_Apple_FS_Or_Unknown(const AString &name);
|
|
bool Is_Apple_FS_Or_Unknown(const AString &name)
|
|
{
|
|
for (unsigned i = 0; i < kNumAppleNames; i++)
|
|
{
|
|
const CAppleName &a = k_Names[i];
|
|
// if (name.Find(a.AppleName) >= 0)
|
|
if (strstr(name, a.AppleName))
|
|
return a.IsFs;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
|
|
|
|
// define it for debug only:
|
|
// #define Z7_DMG_SINGLE_FILE_MODE
|
|
|
|
static const Byte kProps[] =
|
|
{
|
|
kpidPath,
|
|
kpidSize,
|
|
kpidPackSize,
|
|
kpidComment,
|
|
kpidMethod,
|
|
kpidNumBlocks,
|
|
kpidClusterSize,
|
|
kpidChecksum,
|
|
kpidCRC,
|
|
kpidId
|
|
// kpidOffset
|
|
#ifdef Z7_DMG_SINGLE_FILE_MODE
|
|
, kpidPosition
|
|
#endif
|
|
};
|
|
|
|
IMP_IInArchive_Props
|
|
|
|
static const Byte kArcProps[] =
|
|
{
|
|
kpidMethod,
|
|
kpidNumBlocks,
|
|
kpidClusterSize,
|
|
kpidComment
|
|
};
|
|
|
|
|
|
Z7_COM7F_IMF(CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value))
|
|
{
|
|
COM_TRY_BEGIN
|
|
NWindows::NCOM::CPropVariant prop;
|
|
switch (propID)
|
|
{
|
|
case kpidMethod:
|
|
{
|
|
CMethods m;
|
|
CRecordVector<UInt32> ChecksumTypes;
|
|
{
|
|
FOR_VECTOR (i, _files)
|
|
{
|
|
const CFile &file = _files[i];
|
|
m.Update(file);
|
|
if (ChecksumTypes.Size() < (1 << 8))
|
|
ChecksumTypes.AddToUniqueSorted(file.Checksum.Type);
|
|
}
|
|
}
|
|
AString s;
|
|
m.AddToString(s);
|
|
|
|
FOR_VECTOR (i, ChecksumTypes)
|
|
{
|
|
const UInt32 type = ChecksumTypes[i];
|
|
switch (type)
|
|
{
|
|
case kCheckSumType_CRC:
|
|
s.Add_OptSpaced("CRC");
|
|
break;
|
|
default:
|
|
s.Add_OptSpaced("Checksum");
|
|
s.Add_UInt32(type);
|
|
}
|
|
}
|
|
|
|
if (!s.IsEmpty())
|
|
prop = s;
|
|
break;
|
|
}
|
|
case kpidNumBlocks:
|
|
{
|
|
UInt64 numBlocks = 0;
|
|
FOR_VECTOR (i, _files)
|
|
numBlocks += _files[i].Blocks.Size();
|
|
prop = numBlocks;
|
|
break;
|
|
}
|
|
case kpidClusterSize:
|
|
{
|
|
UInt64 blockSize_MAX = 0;
|
|
FOR_VECTOR (i, _files)
|
|
{
|
|
const UInt64 a = _files[i].BlockSize_MAX;
|
|
if (blockSize_MAX < a)
|
|
blockSize_MAX = a;
|
|
}
|
|
prop = blockSize_MAX;
|
|
break;
|
|
}
|
|
case kpidMainSubfile:
|
|
{
|
|
int mainIndex = -1;
|
|
FOR_VECTOR (i, _files)
|
|
{
|
|
if (Is_Apple_FS_Or_Unknown(_files[i].Name))
|
|
{
|
|
if (mainIndex != -1)
|
|
{
|
|
mainIndex = -1;
|
|
break;
|
|
}
|
|
mainIndex = (int)i;
|
|
}
|
|
}
|
|
if (mainIndex != -1)
|
|
prop = (UInt32)(Int32)mainIndex;
|
|
break;
|
|
}
|
|
case kpidWarning:
|
|
if (_masterCrcError)
|
|
prop = "Master CRC error";
|
|
break;
|
|
|
|
case kpidWarningFlags:
|
|
{
|
|
UInt32 v = 0;
|
|
if (_headersError) v |= kpv_ErrorFlags_HeadersError;
|
|
if (_dataForkError) v |= kpv_ErrorFlags_CrcError;
|
|
if (v != 0)
|
|
prop = v;
|
|
break;
|
|
}
|
|
|
|
case kpidOffset: prop = _startPos; break;
|
|
case kpidPhySize: prop = _phySize; break;
|
|
|
|
case kpidComment:
|
|
{
|
|
AString s;
|
|
if (!_name.IsEmpty())
|
|
AddToComment_Prop(s, "Name", _name);
|
|
AddToComment_UInt64(s, _numSectors << 9, "unpack-size");
|
|
{
|
|
char temp[sizeof(_segmentGUID) * 2 + 2];
|
|
ConvertDataToHex_Lower(temp, _segmentGUID, sizeof(_segmentGUID));
|
|
AddToComment_Prop(s, "ID", temp);
|
|
}
|
|
_masterChecksum.AddToComment(s, "master-checksum");
|
|
_dataForkChecksum.AddToComment(s, "pack-checksum");
|
|
{
|
|
/*
|
|
if (_dataStartOffset != 0)
|
|
AddToComment_UInt64(s, _dataStartOffset, "payload-start-offset");
|
|
*/
|
|
// if (_dataForkPair.Offset != 0)
|
|
_dataForkPair.Print(s, "pack");
|
|
rsrcPair.Print(s, "rsrc");
|
|
xmlPair.Print(s, "xml");
|
|
blobPair.Print(s, "blob");
|
|
}
|
|
if (_rsrcMode_wasUsed)
|
|
s += "RSRC_MODE\n";
|
|
if (!s.IsEmpty())
|
|
prop = s;
|
|
}
|
|
break;
|
|
|
|
case kpidName:
|
|
if (!_name.IsEmpty())
|
|
prop = _name + ".dmg";
|
|
break;
|
|
}
|
|
prop.Detach(value);
|
|
return S_OK;
|
|
COM_TRY_END
|
|
}
|
|
|
|
IMP_IInArchive_ArcProps
|
|
|
|
|
|
|
|
static const UInt64 kSectorNumber_LIMIT = (UInt64)1 << (63 - 9);
|
|
|
|
HRESULT CFile::Parse(const Byte *p, UInt32 size)
|
|
{
|
|
// CFile was initialized to default values: 0 in size variables and (IsCorrect == false)
|
|
const unsigned 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;
|
|
|
|
StartUnpackSector = Get64(p + 8);
|
|
NumUnpackSectors = Get64(p + 0x10);
|
|
StartPackPos = Get64(p + 0x18);
|
|
|
|
#ifdef SHOW_DEBUG_INFO
|
|
/* the number of sectors that must be allocated.
|
|
== 0x208 for 256KB clusters
|
|
== 0x808 for 1MB clusters
|
|
== 0x1001 for 1MB clusters in some example
|
|
*/
|
|
const UInt32 decompressedBufRequested = Get32(p + 0x20);
|
|
#endif
|
|
|
|
// Descriptor is file index. usually started from -1
|
|
// in one dmg it was started from 0
|
|
Descriptor = (Int32)Get32(p + 0x24);
|
|
// char Reserved1[24];
|
|
|
|
Checksum.Parse(p + 0x40);
|
|
PRF(printf("\n" " Checksum Type = %2u"
|
|
"\n StartUnpackSector = %8x"
|
|
"\n NumUnpackSectors = %8x"
|
|
"\n StartPos = %8x"
|
|
"\n decompressedBufRequested=%8x"
|
|
"\n blocksDescriptor=%8x"
|
|
, (unsigned)Checksum.Type
|
|
, (unsigned)StartUnpackSector
|
|
, (unsigned)NumUnpackSectors
|
|
, (unsigned)StartPackPos
|
|
, (unsigned)decompressedBufRequested
|
|
, (unsigned)Descriptor
|
|
);)
|
|
|
|
const UInt32 numBlocks = Get32(p + 0xC8);
|
|
const unsigned kRecordSize = 40;
|
|
if ((UInt64)numBlocks * kRecordSize + kHeadSize != size)
|
|
return S_FALSE;
|
|
|
|
Blocks.ClearAndReserve(numBlocks);
|
|
FullFileChecksum = true;
|
|
/* IsCorrect = false; by default
|
|
So we return S_OK, if we can ignore some error in headers.
|
|
*/
|
|
|
|
p += kHeadSize;
|
|
UInt32 i;
|
|
|
|
for (i = 0; i < numBlocks; i++, p += kRecordSize)
|
|
{
|
|
CBlock b;
|
|
b.Type = Get32(p);
|
|
{
|
|
const UInt64 a = Get64(p + 0x08);
|
|
if (a >= kSectorNumber_LIMIT)
|
|
return S_OK;
|
|
b.UnpPos = a << 9;
|
|
}
|
|
UInt64 unpSize;
|
|
{
|
|
const UInt64 a = Get64(p + 0x10);
|
|
if (a >= kSectorNumber_LIMIT)
|
|
return S_OK;
|
|
unpSize = a << 9;
|
|
}
|
|
const UInt64 newSize = b.UnpPos + unpSize;
|
|
if (newSize >= ((UInt64)1 << 63))
|
|
return S_OK;
|
|
|
|
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 (b.UnpPos != Size)
|
|
return S_OK;
|
|
|
|
PRF(printf("\nType=%8x comment=%8x uPos=%8x uSize=%7x pPos=%8x pSize=%7x",
|
|
(unsigned)b.Type, Get32(p + 4), (unsigned)b.UnpPos, (unsigned)unpSize, (unsigned)b.PackPos, (unsigned)b.PackSize));
|
|
|
|
if (b.Type == METHOD_COMMENT)
|
|
{
|
|
// some files contain 2 comment block records:
|
|
// record[0] : Type=METHOD_COMMENT, comment_field = "+beg"
|
|
// record[num-2] : Type=METHOD_COMMENT, comment_field = "+end"
|
|
// we skip these useless records.
|
|
continue;
|
|
}
|
|
if (b.Type == METHOD_END)
|
|
break;
|
|
|
|
// we add only blocks that have non empty unpacked data:
|
|
if (unpSize != 0)
|
|
{
|
|
const UInt64 k_max_pos = (UInt64)1 << 63;
|
|
if (b.PackPos >= k_max_pos ||
|
|
b.PackSize >= k_max_pos - b.PackPos)
|
|
return S_OK;
|
|
|
|
/* we don't count non-ZERO blocks here, because
|
|
ZERO blocks in dmg files are not limited by some cluster size.
|
|
note: COPY blocks also sometimes are fused to larger blocks.
|
|
*/
|
|
if (b.IsClusteredMethod())
|
|
if (BlockSize_MAX < unpSize)
|
|
BlockSize_MAX = unpSize;
|
|
|
|
PackSize += b.PackSize;
|
|
if (!b.NeedCrc())
|
|
FullFileChecksum = false;
|
|
Blocks.AddInReserved(b);
|
|
Size = newSize;
|
|
}
|
|
}
|
|
|
|
PRF(printf("\n");)
|
|
|
|
if (i != numBlocks - 1)
|
|
{
|
|
// return S_FALSE;
|
|
return S_OK;
|
|
}
|
|
|
|
if ((Size >> 9) == NumUnpackSectors)
|
|
IsCorrect = true;
|
|
return S_OK;
|
|
}
|
|
|
|
|
|
static const CXmlItem *FindKeyPair(const CXmlItem &item, const char *key, const char *nextTag)
|
|
{
|
|
for (unsigned i = 0; i + 1 < item.SubItems.Size(); i++)
|
|
{
|
|
const CXmlItem &si = item.SubItems[i];
|
|
if (si.IsTagged("key") && si.GetSubString().IsEqualTo(key))
|
|
{
|
|
const CXmlItem *si_1 = &item.SubItems[i + 1];
|
|
if (si_1->IsTagged(nextTag))
|
|
return si_1;
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
static const AString *GetStringFromKeyPair(const CXmlItem &item, const char *key, const char *nextTag)
|
|
{
|
|
const CXmlItem *si_1 = FindKeyPair(item, key, nextTag);
|
|
if (si_1)
|
|
return si_1->GetSubStringPtr();
|
|
return NULL;
|
|
}
|
|
|
|
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, Z7_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::ReadData(IInStream *stream, const CForkPair &pair, CByteBuffer &buf)
|
|
{
|
|
size_t size = (size_t)pair.Len;
|
|
if (size != pair.Len)
|
|
return E_OUTOFMEMORY;
|
|
buf.Alloc(size);
|
|
RINOK(InStream_SeekSet(stream, _startPos + pair.Offset))
|
|
return ReadStream_FALSE(stream, buf, size);
|
|
}
|
|
|
|
|
|
|
|
bool CHandler::ParseBlob(const CByteBuffer &data)
|
|
{
|
|
const unsigned kHeaderSize = 3 * 4;
|
|
if (data.Size() < kHeaderSize)
|
|
return false;
|
|
const Byte * const p = (const Byte *)data;
|
|
if (Get32a(p) != 0xfade0cc0) // CSMAGIC_EMBEDDED_SIGNATURE
|
|
return true;
|
|
const UInt32 size = Get32a(p + 4);
|
|
if (size != data.Size())
|
|
return false;
|
|
const UInt32 num = Get32a(p + 8);
|
|
if (num > (size - kHeaderSize) / 8)
|
|
return false;
|
|
|
|
const UInt32 limit = num * 8 + kHeaderSize;
|
|
for (size_t i = kHeaderSize; i < limit; i += 8)
|
|
{
|
|
// type == 0 == CSSLOT_CODEDIRECTORY for CSMAGIC_CODEDIRECTORY item
|
|
// UInt32 type = Get32(p + i);
|
|
const UInt32 offset = Get32a(p + i + 4);
|
|
if (offset < limit || offset > size - 8)
|
|
return false;
|
|
// offset is not aligned for 4 here !!!
|
|
const Byte * const p2 = p + offset;
|
|
const UInt32 magic = Get32(p2);
|
|
const UInt32 len = Get32(p2 + 4);
|
|
if (size - offset < len || len < 8)
|
|
return false;
|
|
|
|
#ifdef DMG_SHOW_RAW
|
|
CExtraFile &extra = _extras.AddNew();
|
|
extra.Name = "_blob_";
|
|
extra.Data.CopyFrom(p2, len);
|
|
#endif
|
|
|
|
if (magic == 0xfade0c02) // CSMAGIC_CODEDIRECTORY
|
|
{
|
|
#ifdef DMG_SHOW_RAW
|
|
extra.Name += "codedir";
|
|
#endif
|
|
|
|
if (len < 11 * 4)
|
|
return false;
|
|
const UInt32 idOffset = Get32(p2 + 5 * 4);
|
|
if (idOffset >= len)
|
|
return false;
|
|
UInt32 len2 = len - idOffset;
|
|
const UInt32 kNameLenMax = 1 << 8;
|
|
if (len2 > kNameLenMax)
|
|
len2 = kNameLenMax;
|
|
_name.SetFrom_CalcLen((const char *)(p2 + idOffset), len2);
|
|
/*
|
|
// #define kSecCodeSignatureHashSHA1 1
|
|
// #define kSecCodeSignatureHashSHA256 2
|
|
const UInt32 hashOffset = Get32(p2 + 4 * 4);
|
|
const UInt32 nSpecialSlots = Get32(p2 + 6 * 4);
|
|
const UInt32 nCodeSlots = Get32(p2 + 7 * 4);
|
|
const unsigned hashSize = p2[36];
|
|
const unsigned hashType = p2[37];
|
|
// const unsigned unused = p2[38];
|
|
const unsigned pageSize = p2[39];
|
|
*/
|
|
}
|
|
#ifdef DMG_SHOW_RAW
|
|
else if (magic == 0xfade0c01) extra.Name += "requirements";
|
|
else if (magic == 0xfade0b01) extra.Name += "signed";
|
|
else
|
|
{
|
|
char temp[16];
|
|
ConvertUInt32ToHex8Digits(magic, temp);
|
|
extra.Name += temp;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
|
|
|
|
HRESULT CHandler::Open2(IInStream *stream, IArchiveOpenCallback *openArchiveCallback)
|
|
{
|
|
/*
|
|
- usual dmg contains Koly Header at the end:
|
|
- rare case old dmg contains Koly Header at the begin.
|
|
*/
|
|
|
|
// _dataStartOffset = 0;
|
|
UInt64 fileSize;
|
|
RINOK(InStream_GetPos_GetSize(stream, _startPos, fileSize))
|
|
|
|
const unsigned HEADER_SIZE = 0x200;
|
|
UInt64 buf[HEADER_SIZE / sizeof(UInt64)];
|
|
RINOK(ReadStream_FALSE(stream, buf, HEADER_SIZE))
|
|
|
|
UInt64 headerPos;
|
|
bool front_Koly_Mode = false;
|
|
|
|
/*
|
|
_dataForkChecksum.Offset == 0 for koly-at-the-end
|
|
_dataForkChecksum.Offset == 512 for koly-at-the-start
|
|
so we can use (_dataForkChecksum.Offset) to detect "koly-at-the-start" mode
|
|
*/
|
|
|
|
if (IsKoly((const Byte *)(const void *)buf))
|
|
{
|
|
// it can be normal koly-at-the-end or koly-at-the-start
|
|
headerPos = _startPos;
|
|
/*
|
|
if (_startPos <= (1 << 8))
|
|
{
|
|
// we want to support front_Koly_Mode, even if there is
|
|
// some data before dmg file, like 128 bytes MacBin header
|
|
_dataStartOffset = HEADER_SIZE;
|
|
front_Koly_Mode = true;
|
|
}
|
|
*/
|
|
}
|
|
else
|
|
{
|
|
/* we try to open in backward mode only for first attempt
|
|
when (_startPos == 0) */
|
|
if (_startPos != 0)
|
|
return S_FALSE;
|
|
headerPos = fileSize;
|
|
if (headerPos < HEADER_SIZE)
|
|
return S_FALSE;
|
|
headerPos -= HEADER_SIZE;
|
|
RINOK(InStream_SeekSet(stream, headerPos))
|
|
RINOK(ReadStream_FALSE(stream, buf, HEADER_SIZE))
|
|
if (!IsKoly((const Byte *)(const void *)buf))
|
|
return S_FALSE;
|
|
}
|
|
|
|
// UInt32 flags = Get32a((const Byte *)(const void *)buf + 12);
|
|
// _runningDataForkOffset = Get64a((const Byte *)(const void *)buf + 0x10);
|
|
_dataForkPair.Parse((const Byte *)(const void *)buf + 0x18);
|
|
rsrcPair.Parse((const Byte *)(const void *)buf + 0x28);
|
|
// _segmentNumber = Get32a(buf + 0x38); // 0 or 1
|
|
// _segmentCount = Get32a(buf + 0x3C); // 0 (if not set) or 1
|
|
memcpy(_segmentGUID, (const Byte *)(const void *)buf + 0x40, 16);
|
|
_dataForkChecksum.Parse((const Byte *)(const void *)buf + 0x50);
|
|
xmlPair.Parse((const Byte *)(const void *)buf + 0xD8);
|
|
// Byte resereved[]
|
|
blobPair.Parse((const Byte *)(const void *)buf + 0x128);
|
|
_masterChecksum.Parse((const Byte *)(const void *)buf + 0x160);
|
|
// UInt32 imageVariant = Get32a((const Byte *)(const void *)buf + 0x1E8); imageVariant = imageVariant;
|
|
_numSectors = Get64((const Byte *)(const void *)buf + 0x1EC); // it's not aligned for 8-bytes
|
|
// Byte resereved[12];
|
|
|
|
if (_dataForkPair.Offset == HEADER_SIZE
|
|
&& headerPos + HEADER_SIZE < fileSize)
|
|
front_Koly_Mode = true;
|
|
|
|
const UInt64 limit = front_Koly_Mode ? fileSize : headerPos;
|
|
UInt64 top = 0;
|
|
if (!_dataForkPair.UpdateTop(limit, top)) return S_FALSE;
|
|
if (!xmlPair.UpdateTop(limit, top)) return S_FALSE;
|
|
if (!rsrcPair.UpdateTop(limit, top)) return S_FALSE;
|
|
|
|
/* Some old dmg files contain garbage data in blobPair field.
|
|
So we need to ignore such garbage case;
|
|
And we still need to detect offset of start of archive for "parser" mode. */
|
|
const bool useBlob = blobPair.UpdateTop(limit, top);
|
|
|
|
if (front_Koly_Mode)
|
|
_phySize = top;
|
|
else
|
|
{
|
|
_phySize = headerPos + HEADER_SIZE;
|
|
_startPos = 0;
|
|
if (top != headerPos)
|
|
{
|
|
/*
|
|
if expected absolute offset is not equal to real header offset,
|
|
2 cases are possible:
|
|
- additional (unknown) headers
|
|
- archive with offset.
|
|
So we try to read XML with absolute offset to select from these two ways.
|
|
*/
|
|
CForkPair xmlPair2 = xmlPair;
|
|
const char *sz = "<?xml version";
|
|
const unsigned len = (unsigned)strlen(sz);
|
|
if (xmlPair2.Len > len)
|
|
xmlPair2.Len = len;
|
|
CByteBuffer buf2;
|
|
if (xmlPair2.Len < len
|
|
|| ReadData(stream, xmlPair2, buf2) != S_OK
|
|
|| memcmp(buf2, sz, len) != 0)
|
|
{
|
|
// if absolute offset is not OK, probably it's archive with offset
|
|
_startPos = headerPos - top;
|
|
_phySize = top + HEADER_SIZE;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (useBlob
|
|
&& blobPair.Len != 0
|
|
&& blobPair.Len <= (1u << 24)) // we don't want parsing of big blobs
|
|
{
|
|
#ifdef DMG_SHOW_RAW
|
|
CExtraFile &extra = _extras.AddNew();
|
|
extra.Name = "_blob.bin";
|
|
CByteBuffer &blobBuf = extra.Data;
|
|
#else
|
|
CByteBuffer blobBuf;
|
|
#endif
|
|
RINOK(ReadData(stream, blobPair, blobBuf))
|
|
if (!ParseBlob(blobBuf))
|
|
_headersError = true;
|
|
}
|
|
|
|
|
|
UInt64 openTotal = 0;
|
|
UInt64 openCur = 0;
|
|
if (_dataForkChecksum.IsCrc32())
|
|
openTotal = _dataForkPair.Len;
|
|
|
|
const UInt32 RSRC_HEAD_SIZE = 0x100;
|
|
|
|
/* we have 2 ways to read files and blocks metadata:
|
|
via Xml or via Rsrc.
|
|
But some images have no Rsrc fork.
|
|
Is it possible that there is no Xml?
|
|
Rsrc method will not work for big files. */
|
|
// v23.02: we use xml mode by default
|
|
// if (rsrcPair.Len >= RSRC_HEAD_SIZE && rsrcPair.Len <= ((UInt32)1 << 24)) // for debug
|
|
if (xmlPair.Len == 0)
|
|
{
|
|
// 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 rsrcPair.Len <= (1 << 24).
|
|
const bool canUseRsrc = (rsrcPair.Len >= RSRC_HEAD_SIZE && rsrcPair.Len <= ((UInt32)1 << 24));
|
|
if (!canUseRsrc)
|
|
return S_FALSE;
|
|
|
|
_rsrcMode_wasUsed = true;
|
|
#ifdef DMG_SHOW_RAW
|
|
CExtraFile &extra = _extras.AddNew();
|
|
extra.Name = "rsrc.bin";
|
|
CByteBuffer &rsrcBuf = extra.Data;
|
|
#else
|
|
CByteBuffer rsrcBuf;
|
|
#endif
|
|
|
|
RINOK(ReadData(stream, rsrcPair, rsrcBuf))
|
|
|
|
const Byte *p = rsrcBuf;
|
|
const UInt32 headSize = Get32a(p + 0);
|
|
const UInt32 footerOffset = Get32a(p + 4);
|
|
const UInt32 mainDataSize = Get32a(p + 8);
|
|
const UInt32 footerSize = Get32a(p + 12);
|
|
if (headSize != RSRC_HEAD_SIZE
|
|
|| footerOffset >= rsrcPair.Len
|
|
|| mainDataSize >= rsrcPair.Len
|
|
|| footerOffset < mainDataSize
|
|
|| footerOffset != headSize + mainDataSize)
|
|
return S_FALSE;
|
|
|
|
{
|
|
const UInt32 footerEnd = footerOffset + footerSize;
|
|
if (footerEnd != rsrcPair.Len)
|
|
{
|
|
// there is rare case dmg example, where there are 4 additional bytes
|
|
const UInt64 rem = rsrcPair.Len - footerOffset;
|
|
if (rem < footerSize
|
|
|| rem - footerSize != 4
|
|
|| Get32(p + footerEnd) != 0)
|
|
return S_FALSE;
|
|
}
|
|
}
|
|
|
|
if (footerSize < 0x1e)
|
|
return S_FALSE;
|
|
if (memcmp(p, p + footerOffset, 16) != 0)
|
|
return S_FALSE;
|
|
|
|
p += footerOffset;
|
|
|
|
if ((UInt32)Get16(p + 0x18) != 0x1c)
|
|
return S_FALSE;
|
|
const UInt32 namesOffset = Get16(p + 0x1a);
|
|
if (namesOffset > footerSize)
|
|
return S_FALSE;
|
|
|
|
const 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 + (size_t)i * 8;
|
|
const UInt32 typeId = Get32(p2);
|
|
const UInt32 k_typeId_blkx = 0x626c6b78; // blkx
|
|
#ifndef DMG_SHOW_RAW
|
|
if (typeId != k_typeId_blkx)
|
|
continue;
|
|
#endif
|
|
const UInt32 numFiles = (UInt32)Get16(p2 + 4) + 1;
|
|
const 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);
|
|
const UInt32 namePos = Get16(p3 + 2);
|
|
// Byte attributes = p3[4]; // = 0x50 for blkx #define (ATTRIBUTE_HDIUTIL)
|
|
// 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;
|
|
const UInt32 blockSize = Get32(pBlock);
|
|
if (mainDataSize - (blockOffset + 4) < blockSize)
|
|
return S_FALSE;
|
|
|
|
AString name;
|
|
|
|
if (namePos != 0xffff)
|
|
{
|
|
const UInt32 namesBlockSize = footerSize - namesOffset;
|
|
if (namePos >= namesBlockSize)
|
|
return S_FALSE;
|
|
const Byte *namePtr = p + namesOffset + namePos;
|
|
const UInt32 nameLen = *namePtr;
|
|
if (namesBlockSize - namePos <= nameLen)
|
|
return S_FALSE;
|
|
for (UInt32 r = 1; r <= nameLen; r++)
|
|
{
|
|
const Byte c = namePtr[r];
|
|
if (c < 0x20 || c >= 0x80)
|
|
break;
|
|
name += (char)c;
|
|
}
|
|
}
|
|
|
|
if (typeId == k_typeId_blkx)
|
|
{
|
|
CFile &file = _files.AddNew();
|
|
file.Name = name;
|
|
RINOK(file.Parse(pBlock + 4, blockSize))
|
|
if (!file.IsCorrect)
|
|
_headersError = true;
|
|
}
|
|
|
|
#ifdef DMG_SHOW_RAW
|
|
{
|
|
AString name2;
|
|
name2.Add_UInt32(i);
|
|
name2 += '_';
|
|
{
|
|
char temp[4 + 1] = { 0 };
|
|
memcpy(temp, p2, 4);
|
|
name2 += temp;
|
|
}
|
|
name2.Trim();
|
|
name2 += '_';
|
|
name2.Add_UInt32(k);
|
|
if (!name.IsEmpty())
|
|
{
|
|
name2 += '_';
|
|
name2 += name;
|
|
}
|
|
CExtraFile &extra2 = _extras.AddNew();
|
|
extra2.Name = name2;
|
|
extra2.Data.CopyFrom(pBlock + 4, blockSize);
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (xmlPair.Len > k_XmlSize_MAX)
|
|
return S_FALSE;
|
|
// if (!canUseXml) return S_FALSE;
|
|
const size_t size = (size_t)xmlPair.Len;
|
|
// if (size + 1 <= xmlPair.Len) return S_FALSE; // optional check
|
|
RINOK(InStream_SeekSet(stream, _startPos + xmlPair.Offset))
|
|
CXml xml;
|
|
{
|
|
openTotal += size;
|
|
if (openArchiveCallback)
|
|
{
|
|
RINOK(openArchiveCallback->SetTotal(NULL, &openTotal))
|
|
}
|
|
size_t pos = 0;
|
|
CAlignedBuffer1 xmlStr(size + 1);
|
|
for (;;)
|
|
{
|
|
const size_t k_OpenStep = 1 << 24;
|
|
const size_t cur = MyMin(k_OpenStep, size - pos);
|
|
RINOK(ReadStream_FALSE(stream, xmlStr + pos, cur))
|
|
pos += cur;
|
|
openCur += cur;
|
|
if (pos == size)
|
|
break;
|
|
if (openArchiveCallback)
|
|
{
|
|
RINOK(openArchiveCallback->SetCompleted(NULL, &openCur))
|
|
}
|
|
}
|
|
xmlStr[size] = 0;
|
|
// if (strlen((const char *)(const void *)(const Byte *)xmlStr) != size) return S_FALSE;
|
|
if (!xml.Parse((char *)(void *)(Byte *)xmlStr))
|
|
return S_FALSE;
|
|
|
|
#ifdef DMG_SHOW_RAW
|
|
CExtraFile &extra = _extras.AddNew();
|
|
extra.Name = "a.xml";
|
|
extra.Data.CopyFrom(xmlStr, size);
|
|
#endif
|
|
}
|
|
|
|
if (!xml.Root.Name.IsEqualTo("plist"))
|
|
return S_FALSE;
|
|
|
|
const CXmlItem *dictItem = xml.Root.FindSubTag_GetPtr("dict");
|
|
if (!dictItem)
|
|
return S_FALSE;
|
|
|
|
const CXmlItem *rfDictItem = FindKeyPair(*dictItem, "resource-fork", "dict");
|
|
if (!rfDictItem)
|
|
return S_FALSE;
|
|
|
|
const CXmlItem *arrItem = FindKeyPair(*rfDictItem, "blkx", "array");
|
|
if (!arrItem)
|
|
return S_FALSE;
|
|
|
|
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 - (const Byte *)rawBuf);
|
|
}
|
|
|
|
#ifdef DMG_SHOW_RAW
|
|
CExtraFile &extra = _extras.AddNew();
|
|
extra.Name.Add_UInt32(_files.Size());
|
|
extra.Data.CopyFrom(rawBuf, destLen);
|
|
#endif
|
|
}
|
|
CFile &file = _files.AddNew();
|
|
{
|
|
/* xml code removes front space for such string:
|
|
<string> (Apple_Free : 3)</string>
|
|
maybe we shoud fix xml code and return full string with space.
|
|
*/
|
|
const AString *name = GetStringFromKeyPair(item, "Name", "string");
|
|
if (!name || name->IsEmpty())
|
|
name = GetStringFromKeyPair(item, "CFName", "string");
|
|
if (name)
|
|
file.Name = *name;
|
|
}
|
|
/*
|
|
{
|
|
const AString *s = GetStringFromKeyPair(item, "ID", "string");
|
|
if (s)
|
|
file.Id = *s;
|
|
}
|
|
*/
|
|
RINOK(file.Parse(rawBuf, destLen))
|
|
if (!file.IsCorrect)
|
|
_headersError = true;
|
|
}
|
|
}
|
|
|
|
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;
|
|
const 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());
|
|
}
|
|
|
|
{
|
|
UInt64 sec = 0;
|
|
FOR_VECTOR (i, _files)
|
|
{
|
|
const CFile &file = _files[i];
|
|
/*
|
|
if (file.Descriptor != (Int32)i - 1)
|
|
_headersError = true;
|
|
*/
|
|
if (file.StartUnpackSector != sec)
|
|
_headersError = true;
|
|
if (file.NumUnpackSectors >= kSectorNumber_LIMIT)
|
|
_headersError = true;
|
|
sec += file.NumUnpackSectors;
|
|
if (sec >= kSectorNumber_LIMIT)
|
|
_headersError = true;
|
|
}
|
|
if (sec != _numSectors)
|
|
_headersError = true;
|
|
}
|
|
|
|
// data checksum calculation can be slow for big dmg file
|
|
if (_dataForkChecksum.IsCrc32())
|
|
{
|
|
UInt64 endPos;
|
|
if (!_dataForkPair.GetEndPos(endPos)
|
|
|| _dataForkPair.Offset >= ((UInt64)1 << 63))
|
|
_headersError = true;
|
|
else
|
|
{
|
|
const UInt64 seekPos = _startPos + _dataForkPair.Offset;
|
|
if (seekPos > fileSize
|
|
|| endPos > fileSize - _startPos)
|
|
{
|
|
_headersError = true;
|
|
// kpv_ErrorFlags_UnexpectedEnd
|
|
}
|
|
else
|
|
{
|
|
const size_t kBufSize = 1 << 15;
|
|
CAlignedBuffer1 buf2(kBufSize);
|
|
RINOK(InStream_SeekSet(stream, seekPos))
|
|
if (openArchiveCallback)
|
|
{
|
|
RINOK(openArchiveCallback->SetTotal(NULL, &openTotal))
|
|
}
|
|
UInt32 crc = CRC_INIT_VAL;
|
|
UInt64 pos = 0;
|
|
for (;;)
|
|
{
|
|
const UInt64 rem = _dataForkPair.Len - pos;
|
|
size_t cur = kBufSize;
|
|
if (cur > rem)
|
|
cur = (UInt32)rem;
|
|
if (cur == 0)
|
|
break;
|
|
RINOK(ReadStream_FALSE(stream, buf2, cur))
|
|
crc = CrcUpdate(crc, buf2, cur);
|
|
pos += cur;
|
|
openCur += cur;
|
|
if ((pos & ((1 << 24) - 1)) == 0 && openArchiveCallback)
|
|
{
|
|
RINOK(openArchiveCallback->SetCompleted(NULL, &openCur))
|
|
}
|
|
}
|
|
if (_dataForkChecksum.GetCrc32() != CRC_GET_DIGEST(crc))
|
|
_dataForkError = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
|
|
|
|
Z7_COM7F_IMF(CHandler::Open(IInStream *stream,
|
|
const UInt64 * /* maxCheckStartPosition */,
|
|
IArchiveOpenCallback *openArchiveCallback))
|
|
{
|
|
COM_TRY_BEGIN
|
|
Close();
|
|
RINOK(Open2(stream, openArchiveCallback))
|
|
_inStream = stream;
|
|
return S_OK;
|
|
COM_TRY_END
|
|
}
|
|
|
|
Z7_COM7F_IMF(CHandler::Close())
|
|
{
|
|
_masterCrcError = false;
|
|
_headersError = false;
|
|
_dataForkError = false;
|
|
_rsrcMode_wasUsed = false;
|
|
_phySize = 0;
|
|
_startPos = 0;
|
|
_name.Empty();
|
|
_inStream.Release();
|
|
_files.Clear();
|
|
#ifdef DMG_SHOW_RAW
|
|
_extras.Clear();
|
|
#endif
|
|
return S_OK;
|
|
}
|
|
|
|
Z7_COM7F_IMF(CHandler::GetNumberOfItems(UInt32 *numItems))
|
|
{
|
|
*numItems = _files.Size()
|
|
#ifdef DMG_SHOW_RAW
|
|
+ _extras.Size()
|
|
#endif
|
|
;
|
|
return S_OK;
|
|
}
|
|
|
|
#ifdef DMG_SHOW_RAW
|
|
#define RAW_PREFIX "raw" STRING_PATH_SEPARATOR
|
|
#endif
|
|
|
|
Z7_COM7F_IMF(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 kpidChecksum:
|
|
{
|
|
AString s;
|
|
item.Checksum.Print(s);
|
|
if (!s.IsEmpty())
|
|
prop = s;
|
|
break;
|
|
}
|
|
|
|
/*
|
|
case kpidOffset:
|
|
{
|
|
prop = item.StartPackPos;
|
|
break;
|
|
}
|
|
*/
|
|
|
|
case kpidNumBlocks:
|
|
prop = (UInt32)item.Blocks.Size();
|
|
break;
|
|
case kpidClusterSize:
|
|
prop = item.BlockSize_MAX;
|
|
break;
|
|
|
|
case kpidMethod:
|
|
{
|
|
AString s;
|
|
if (!item.IsCorrect)
|
|
s.Add_OptSpaced("CORRUPTED");
|
|
CMethods m;
|
|
m.Update(item);
|
|
m.AddToString(s);
|
|
{
|
|
AString s2;
|
|
item.Checksum.PrintType(s2);
|
|
if (!s2.IsEmpty())
|
|
s.Add_OptSpaced(s2);
|
|
}
|
|
if (!s.IsEmpty())
|
|
prop = s;
|
|
break;
|
|
}
|
|
|
|
case kpidPath:
|
|
{
|
|
#ifdef Z7_DMG_SINGLE_FILE_MODE
|
|
prop = "a.img";
|
|
#else
|
|
UString name;
|
|
name.Add_UInt32(index);
|
|
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++;
|
|
const 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);
|
|
}
|
|
}
|
|
else
|
|
subName = item.Name; // new apfs dmg can be without braces
|
|
subName.Trim();
|
|
if (!subName.IsEmpty())
|
|
{
|
|
const char *ext = Find_Apple_FS_Ext(subName);
|
|
if (ext)
|
|
subName = ext;
|
|
UString name2;
|
|
ConvertUTF8ToUnicode(subName, name2);
|
|
name.Add_Dot();
|
|
name += name2;
|
|
}
|
|
else
|
|
{
|
|
UString name2;
|
|
ConvertUTF8ToUnicode(item.Name, name2);
|
|
if (!name2.IsEmpty())
|
|
name += "_";
|
|
name += name2;
|
|
}
|
|
prop = name;
|
|
#endif
|
|
|
|
break;
|
|
}
|
|
|
|
case kpidComment:
|
|
{
|
|
UString name;
|
|
ConvertUTF8ToUnicode(item.Name, name);
|
|
prop = name;
|
|
break;
|
|
}
|
|
case kpidId:
|
|
{
|
|
prop.Set_Int32((Int32)item.Descriptor);
|
|
/*
|
|
if (!item.Id.IsEmpty())
|
|
{
|
|
UString s;
|
|
ConvertUTF8ToUnicode(item.Id, s);
|
|
prop = s;
|
|
}
|
|
*/
|
|
break;
|
|
}
|
|
#ifdef Z7_DMG_SINGLE_FILE_MODE
|
|
case kpidPosition:
|
|
prop = item.StartUnpackSector << 9;
|
|
break;
|
|
#endif
|
|
}
|
|
}
|
|
prop.Detach(value);
|
|
return S_OK;
|
|
COM_TRY_END
|
|
}
|
|
|
|
|
|
class CAdcDecoder
|
|
{
|
|
CLzOutWindow m_OutWindowStream;
|
|
CInBuffer m_InStream;
|
|
|
|
class CCoderReleaser Z7_final
|
|
{
|
|
CAdcDecoder *m_Coder;
|
|
public:
|
|
bool NeedFlush;
|
|
CCoderReleaser(CAdcDecoder *coder): m_Coder(coder), NeedFlush(true) {}
|
|
~CCoderReleaser()
|
|
{
|
|
if (NeedFlush)
|
|
m_Coder->m_OutWindowStream.Flush();
|
|
}
|
|
};
|
|
friend class CCoderReleaser;
|
|
|
|
public:
|
|
HRESULT Code(ISequentialInStream * const inStream,
|
|
ISequentialOutStream *outStream,
|
|
const UInt64 * const inSize,
|
|
const UInt64 * const outSize,
|
|
ICompressProgressInfo * const progress);
|
|
};
|
|
|
|
|
|
HRESULT CAdcDecoder::Code(ISequentialInStream * const inStream,
|
|
ISequentialOutStream *outStream,
|
|
const UInt64 * const inSize,
|
|
const UInt64 * const outSizePtr,
|
|
ICompressProgressInfo * const progress)
|
|
{
|
|
try {
|
|
|
|
if (!m_OutWindowStream.Create(1 << 18)) // at least (1 << 16) is required here
|
|
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 << 22;
|
|
UInt64 nextLimit = kStep;
|
|
const UInt64 outSize = *outSizePtr;
|
|
UInt64 pos = 0;
|
|
/* match sequences and literal sequences do not cross 64KB range
|
|
in some dmg archive examples. But is it so for any Adc stream? */
|
|
|
|
while (pos < outSize)
|
|
{
|
|
if (pos >= nextLimit && progress)
|
|
{
|
|
nextLimit += kStep;
|
|
const UInt64 packSize = m_InStream.GetProcessedSize();
|
|
RINOK(progress->SetRatioInfo(&packSize, &pos))
|
|
}
|
|
Byte b;
|
|
if (!m_InStream.ReadByte(b))
|
|
return S_FALSE;
|
|
const UInt64 rem = outSize - pos;
|
|
if (b & 0x80)
|
|
{
|
|
unsigned num = (unsigned)b - 0x80 + 1;
|
|
if (num > rem)
|
|
return S_FALSE;
|
|
pos += num;
|
|
do
|
|
{
|
|
if (!m_InStream.ReadByte(b))
|
|
return S_FALSE;
|
|
m_OutWindowStream.PutByte(b);
|
|
}
|
|
while (--num);
|
|
continue;
|
|
}
|
|
Byte b1;
|
|
if (!m_InStream.ReadByte(b1))
|
|
return S_FALSE;
|
|
|
|
UInt32 len, dist;
|
|
|
|
if (b & 0x40)
|
|
{
|
|
len = (UInt32)b - 0x40 + 4;
|
|
Byte b2;
|
|
if (!m_InStream.ReadByte(b2))
|
|
return S_FALSE;
|
|
dist = ((UInt32)b1 << 8) + b2;
|
|
}
|
|
else
|
|
{
|
|
len = ((UInt32)b >> 2) + 3;
|
|
dist = (((UInt32)b & 3) << 8) + b1;
|
|
}
|
|
|
|
if (/* dist >= pos || */ len > rem)
|
|
return S_FALSE;
|
|
if (!m_OutWindowStream.CopyBlock(dist, len))
|
|
return S_FALSE;
|
|
pos += len;
|
|
}
|
|
if (*inSize != m_InStream.GetProcessedSize())
|
|
return S_FALSE;
|
|
coderReleaser.NeedFlush = false;
|
|
return m_OutWindowStream.Flush();
|
|
|
|
}
|
|
catch(const CInBufferException &e) { return e.ErrorCode; }
|
|
catch(const CLzOutWindowException &e) { return e.ErrorCode; }
|
|
catch(...) { return S_FALSE; }
|
|
}
|
|
|
|
|
|
|
|
struct CDecoders
|
|
{
|
|
CMyComPtr2<ICompressCoder, NCompress::NZlib::CDecoder> zlib;
|
|
CMyComPtr2<ICompressCoder, NCompress::NBZip2::CDecoder> bzip2;
|
|
CMyComPtr2<ICompressCoder, NCompress::NLzfse::CDecoder> lzfse;
|
|
CMyUniquePtr<NCompress::NXz::CDecoder> xz;
|
|
CMyUniquePtr<CAdcDecoder> adc;
|
|
|
|
HRESULT Code(ISequentialInStream *inStream, ISequentialOutStream *outStream,
|
|
const CBlock &block, const UInt64 *unpSize, ICompressProgressInfo *progress);
|
|
};
|
|
|
|
HRESULT CDecoders::Code(ISequentialInStream *inStream, ISequentialOutStream *outStream,
|
|
const CBlock &block, const UInt64 *unpSize, ICompressProgressInfo *progress)
|
|
{
|
|
HRESULT hres;
|
|
UInt64 processed;
|
|
switch (block.Type)
|
|
{
|
|
case METHOD_ADC:
|
|
adc.Create_if_Empty();
|
|
return adc->Code(inStream, outStream, &block.PackSize, unpSize, progress);
|
|
case METHOD_LZFSE:
|
|
lzfse.Create_if_Empty();
|
|
return lzfse.Interface()->Code(inStream, outStream, &block.PackSize, unpSize, progress);
|
|
case METHOD_ZLIB:
|
|
zlib.Create_if_Empty();
|
|
hres = zlib.Interface()->Code(inStream, outStream, NULL, unpSize, progress);
|
|
processed = zlib->GetInputProcessedSize();
|
|
break;
|
|
case METHOD_BZIP2:
|
|
bzip2.Create_if_Empty();
|
|
hres = bzip2.Interface()->Code(inStream, outStream, NULL, unpSize, progress);
|
|
processed = bzip2->GetInputProcessedSize();
|
|
break;
|
|
case METHOD_XZ:
|
|
xz.Create_if_Empty();
|
|
hres = xz->Decode(inStream, outStream, unpSize, true, progress);
|
|
processed = xz->Stat.InSize;
|
|
break;
|
|
default:
|
|
return E_NOTIMPL;
|
|
}
|
|
if (hres == S_OK && processed != block.PackSize)
|
|
hres = S_FALSE;
|
|
return hres;
|
|
}
|
|
|
|
|
|
Z7_COM7F_IMF(CHandler::Extract(const UInt32 *indices, UInt32 numItems,
|
|
Int32 testMode, IArchiveExtractCallback *extractCallback))
|
|
{
|
|
COM_TRY_BEGIN
|
|
const bool allFilesMode = (numItems == (UInt32)(Int32)-1);
|
|
if (allFilesMode)
|
|
numItems = _files.Size()
|
|
#ifdef DMG_SHOW_RAW
|
|
+ _extras.Size()
|
|
#endif
|
|
;
|
|
if (numItems == 0)
|
|
return S_OK;
|
|
UInt64 totalSize = 0;
|
|
UInt32 i;
|
|
|
|
for (i = 0; i < numItems; i++)
|
|
{
|
|
const 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;
|
|
}
|
|
RINOK(extractCallback->SetTotal(totalSize))
|
|
|
|
const size_t kZeroBufSize = 1 << 14;
|
|
CAlignedBuffer1 zeroBuf(kZeroBufSize);
|
|
memset(zeroBuf, 0, kZeroBufSize);
|
|
|
|
CDecoders decoders;
|
|
CMyComPtr2_Create<ICompressCoder, NCompress::CCopyCoder> copyCoder;
|
|
CMyComPtr2_Create<ICompressProgressInfo, CLocalProgress> lps;
|
|
lps->Init(extractCallback, false);
|
|
CMyComPtr2_Create<ISequentialInStream, CLimitedSequentialInStream> inStream;
|
|
inStream->SetStream(_inStream);
|
|
|
|
UInt64 total_PackSize = 0;
|
|
UInt64 total_UnpackSize = 0;
|
|
UInt64 cur_PackSize = 0;
|
|
UInt64 cur_UnpackSize = 0;
|
|
|
|
for (i = 0;; i++,
|
|
total_PackSize += cur_PackSize,
|
|
total_UnpackSize += cur_UnpackSize)
|
|
{
|
|
lps->InSize = total_PackSize;
|
|
lps->OutSize = total_UnpackSize;
|
|
cur_PackSize = 0;
|
|
cur_UnpackSize = 0;
|
|
RINOK(lps->SetCur())
|
|
if (i >= numItems)
|
|
return S_OK;
|
|
|
|
Int32 opRes = NExtract::NOperationResult::kOK;
|
|
{
|
|
CMyComPtr<ISequentialOutStream> realOutStream;
|
|
const Int32 askMode = testMode ?
|
|
NExtract::NAskMode::kTest :
|
|
NExtract::NAskMode::kExtract;
|
|
const UInt32 index = allFilesMode ? i : indices[i];
|
|
RINOK(extractCallback->GetStream(index, &realOutStream, askMode))
|
|
|
|
if (!testMode && !realOutStream)
|
|
continue;
|
|
RINOK(extractCallback->PrepareOperation(askMode))
|
|
|
|
#ifdef DMG_SHOW_RAW
|
|
if (index >= _files.Size())
|
|
{
|
|
const CByteBuffer &buf = _extras[index - _files.Size()].Data;
|
|
if (realOutStream)
|
|
RINOK(WriteStream(realOutStream, buf, buf.Size()))
|
|
cur_PackSize = cur_UnpackSize = buf.Size();
|
|
}
|
|
else
|
|
#endif
|
|
{
|
|
const CFile &item = _files[index];
|
|
cur_PackSize = item.PackSize;
|
|
cur_UnpackSize = item.Size;
|
|
|
|
if (!item.IsCorrect)
|
|
opRes = NExtract::NOperationResult::kHeadersError;
|
|
else
|
|
{
|
|
CMyComPtr2_Create<ISequentialOutStream, COutStreamWithCRC> outCrcStream;
|
|
outCrcStream->SetStream(realOutStream);
|
|
// realOutStream.Release();
|
|
const bool needCrc = item.Checksum.IsCrc32();
|
|
outCrcStream->Init(needCrc);
|
|
|
|
CMyComPtr2_Create<ISequentialOutStream, CLimitedSequentialOutStream> outStream;
|
|
outStream->SetStream(outCrcStream);
|
|
|
|
UInt64 unpPos = 0;
|
|
UInt64 packPos = 0;
|
|
|
|
FOR_VECTOR (blockIndex, item.Blocks)
|
|
{
|
|
lps->InSize = total_PackSize + packPos;
|
|
lps->OutSize = total_UnpackSize + unpPos;
|
|
RINOK(lps->SetCur())
|
|
|
|
const CBlock &block = item.Blocks[blockIndex];
|
|
// if (!block.ThereAreDataInBlock()) continue;
|
|
|
|
packPos += block.PackSize;
|
|
if (block.UnpPos != unpPos)
|
|
{
|
|
opRes = NExtract::NOperationResult::kHeadersError;
|
|
break;
|
|
}
|
|
|
|
RINOK(InStream_SeekSet(_inStream, _startPos + _dataForkPair.Offset + item.StartPackPos + block.PackPos))
|
|
inStream->Init(block.PackSize);
|
|
|
|
const UInt64 unpSize = item.GetUnpackSize_of_Block(blockIndex);
|
|
|
|
outStream->Init(unpSize);
|
|
HRESULT res = S_OK;
|
|
|
|
outCrcStream->EnableCalc(needCrc && block.NeedCrc());
|
|
|
|
if (block.IsZeroMethod())
|
|
{
|
|
if (block.PackSize != 0)
|
|
opRes = NExtract::NOperationResult::kUnsupportedMethod;
|
|
}
|
|
else if (block.Type == METHOD_COPY)
|
|
{
|
|
if (unpSize != block.PackSize)
|
|
opRes = NExtract::NOperationResult::kUnsupportedMethod;
|
|
else
|
|
res = copyCoder.Interface()->Code(inStream, outStream, NULL, NULL, lps);
|
|
}
|
|
else
|
|
res = decoders.Code(inStream, outStream, block, &unpSize, lps);
|
|
|
|
if (res != S_OK)
|
|
{
|
|
if (res != S_FALSE)
|
|
{
|
|
if (res != E_NOTIMPL)
|
|
return res;
|
|
opRes = NExtract::NOperationResult::kUnsupportedMethod;
|
|
}
|
|
if (opRes == NExtract::NOperationResult::kOK)
|
|
opRes = NExtract::NOperationResult::kDataError;
|
|
}
|
|
|
|
unpPos += unpSize;
|
|
|
|
if (!outStream->IsFinishedOK())
|
|
{
|
|
if (!block.IsZeroMethod() && opRes == NExtract::NOperationResult::kOK)
|
|
opRes = NExtract::NOperationResult::kDataError;
|
|
|
|
for (unsigned k = 0;;)
|
|
{
|
|
const UInt64 rem = outStream->GetRem();
|
|
if (rem == 0)
|
|
break;
|
|
size_t size = kZeroBufSize;
|
|
if (size > rem)
|
|
size = (size_t)rem;
|
|
RINOK(WriteStream(outStream, zeroBuf, size))
|
|
k++;
|
|
if ((k & 0xfff) == 0)
|
|
{
|
|
lps->OutSize = total_UnpackSize + unpPos - outStream->GetRem();
|
|
RINOK(lps->SetCur())
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (needCrc && opRes == NExtract::NOperationResult::kOK)
|
|
{
|
|
if (outCrcStream->GetCRC() != item.Checksum.GetCrc32())
|
|
opRes = NExtract::NOperationResult::kCRCError;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
RINOK(extractCallback->SetOperationResult(opRes))
|
|
}
|
|
|
|
COM_TRY_END
|
|
}
|
|
|
|
|
|
|
|
|
|
struct CChunk
|
|
{
|
|
int BlockIndex;
|
|
UInt64 AccessMark;
|
|
Byte *Buf;
|
|
size_t BufSize;
|
|
|
|
void Free()
|
|
{
|
|
z7_AlignedFree(Buf);
|
|
Buf = NULL;
|
|
BufSize = 0;
|
|
}
|
|
void Alloc(size_t size)
|
|
{
|
|
Buf = (Byte *)z7_AlignedAlloc(size);
|
|
}
|
|
};
|
|
|
|
|
|
Z7_CLASS_IMP_IInStream(
|
|
CInStream
|
|
)
|
|
bool _errorMode;
|
|
UInt64 _virtPos;
|
|
int _latestChunk;
|
|
int _latestBlock;
|
|
UInt64 _accessMark;
|
|
UInt64 _chunks_TotalSize;
|
|
CRecordVector<CChunk> _chunks;
|
|
|
|
public:
|
|
CMyComPtr<IInStream> Stream;
|
|
const CFile *File;
|
|
UInt64 Size;
|
|
private:
|
|
UInt64 _startPos;
|
|
|
|
~CInStream();
|
|
CMyComPtr2<ISequentialOutStream, CBufPtrSeqOutStream> outStream;
|
|
CMyComPtr2<ISequentialInStream, CLimitedSequentialInStream> inStream;
|
|
CDecoders decoders;
|
|
public:
|
|
|
|
// HRESULT
|
|
void Init(UInt64 startPos)
|
|
{
|
|
_errorMode = false;
|
|
_startPos = startPos;
|
|
_virtPos = 0;
|
|
_latestChunk = -1;
|
|
_latestBlock = -1;
|
|
_accessMark = 0;
|
|
_chunks_TotalSize = 0;
|
|
|
|
inStream.Create_if_Empty();
|
|
inStream->SetStream(Stream);
|
|
|
|
outStream.Create_if_Empty();
|
|
// return S_OK;
|
|
}
|
|
};
|
|
|
|
|
|
CInStream::~CInStream()
|
|
{
|
|
unsigned i = _chunks.Size();
|
|
while (i)
|
|
_chunks[--i].Free();
|
|
}
|
|
|
|
static unsigned FindBlock(const CRecordVector<CBlock> &blocks, UInt64 pos)
|
|
{
|
|
unsigned left = 0, right = blocks.Size();
|
|
for (;;)
|
|
{
|
|
const unsigned mid = (left + right) / 2;
|
|
if (mid == left)
|
|
return left;
|
|
if (pos < blocks[mid].UnpPos)
|
|
right = mid;
|
|
else
|
|
left = mid;
|
|
}
|
|
}
|
|
|
|
Z7_COM7F_IMF(CInStream::Read(void *data, UInt32 size, UInt32 *processedSize))
|
|
{
|
|
// COM_TRY_BEGIN
|
|
try {
|
|
|
|
if (_errorMode)
|
|
return E_OUTOFMEMORY;
|
|
|
|
if (processedSize)
|
|
*processedSize = 0;
|
|
if (size == 0)
|
|
return S_OK;
|
|
if (_virtPos >= Size)
|
|
return S_OK; // (Size == _virtPos) ? S_OK: E_FAIL;
|
|
{
|
|
const UInt64 rem = Size - _virtPos;
|
|
if (size > rem)
|
|
size = (UInt32)rem;
|
|
}
|
|
|
|
if (_latestBlock >= 0)
|
|
{
|
|
const CBlock &block = File->Blocks[(unsigned)_latestBlock];
|
|
if (_virtPos < block.UnpPos ||
|
|
_virtPos - block.UnpPos >= File->GetUnpackSize_of_Block((unsigned)_latestBlock))
|
|
_latestBlock = -1;
|
|
}
|
|
|
|
if (_latestBlock < 0)
|
|
{
|
|
_latestChunk = -1;
|
|
const unsigned blockIndex = FindBlock(File->Blocks, _virtPos);
|
|
const CBlock &block = File->Blocks[blockIndex];
|
|
const UInt64 unpSize = File->GetUnpackSize_of_Block(blockIndex);
|
|
|
|
if (block.NeedAllocateBuffer()
|
|
&& unpSize <= k_Chunk_Size_MAX)
|
|
{
|
|
unsigned i = 0;
|
|
{
|
|
unsigned numChunks = _chunks.Size();
|
|
if (numChunks)
|
|
{
|
|
const CChunk *chunk = _chunks.ConstData();
|
|
do
|
|
{
|
|
if (chunk->BlockIndex == (int)blockIndex)
|
|
break;
|
|
chunk++;
|
|
}
|
|
while (--numChunks);
|
|
i = _chunks.Size() - numChunks;
|
|
}
|
|
}
|
|
if (i != _chunks.Size())
|
|
_latestChunk = (int)i;
|
|
else
|
|
{
|
|
unsigned chunkIndex;
|
|
for (;;)
|
|
{
|
|
if (_chunks.IsEmpty() ||
|
|
(_chunks.Size() < k_NumChunks_MAX
|
|
&& _chunks_TotalSize + unpSize <= k_Chunks_TotalSize_MAX))
|
|
{
|
|
CChunk chunk;
|
|
chunk.Buf = NULL;
|
|
chunk.BufSize = 0;
|
|
chunk.BlockIndex = -1;
|
|
chunk.AccessMark = 0;
|
|
chunkIndex = _chunks.Add(chunk);
|
|
break;
|
|
}
|
|
chunkIndex = 0;
|
|
if (_chunks.Size() == 1)
|
|
break;
|
|
{
|
|
const CChunk *chunks = _chunks.ConstData();
|
|
UInt64 accessMark_min = chunks[chunkIndex].AccessMark;
|
|
const unsigned numChunks = _chunks.Size();
|
|
for (i = 1; i < numChunks; i++)
|
|
{
|
|
if (chunks[i].AccessMark < accessMark_min)
|
|
{
|
|
chunkIndex = i;
|
|
accessMark_min = chunks[i].AccessMark;
|
|
}
|
|
}
|
|
}
|
|
{
|
|
CChunk &chunk = _chunks[chunkIndex];
|
|
const UInt64 newTotalSize = _chunks_TotalSize - chunk.BufSize;
|
|
if (newTotalSize + unpSize <= k_Chunks_TotalSize_MAX)
|
|
break;
|
|
_chunks_TotalSize = newTotalSize;
|
|
chunk.Free();
|
|
}
|
|
// we have called chunk.Free() before, because
|
|
// _chunks.Delete() doesn't call chunk.Free().
|
|
_chunks.Delete(chunkIndex);
|
|
PRF(printf("\n++num_chunks=%u, _chunks_TotalSize = %u\n", (unsigned)_chunks.Size(), (unsigned)_chunks_TotalSize);)
|
|
}
|
|
|
|
CChunk &chunk = _chunks[chunkIndex];
|
|
chunk.BlockIndex = -1;
|
|
chunk.AccessMark = 0;
|
|
|
|
if (chunk.BufSize < unpSize)
|
|
{
|
|
_chunks_TotalSize -= chunk.BufSize;
|
|
chunk.Free();
|
|
// if (unpSize > k_Chunk_Size_MAX) return E_FAIL;
|
|
chunk.Alloc((size_t)unpSize);
|
|
if (!chunk.Buf)
|
|
return E_OUTOFMEMORY;
|
|
chunk.BufSize = (size_t)unpSize;
|
|
_chunks_TotalSize += chunk.BufSize;
|
|
}
|
|
|
|
RINOK(InStream_SeekSet(Stream, _startPos + File->StartPackPos + block.PackPos))
|
|
|
|
inStream->Init(block.PackSize);
|
|
#ifdef Z7_DMG_USE_CACHE_FOR_COPY_BLOCKS
|
|
if (block.Type == METHOD_COPY)
|
|
{
|
|
if (block.PackSize != unpSize)
|
|
return E_FAIL;
|
|
RINOK(ReadStream_FAIL(inStream, chunk.Buf, (size_t)unpSize))
|
|
}
|
|
else
|
|
#endif
|
|
{
|
|
outStream->Init(chunk.Buf, (size_t)unpSize);
|
|
RINOK(decoders.Code(inStream, outStream, block, &unpSize, NULL))
|
|
if (outStream->GetPos() != unpSize)
|
|
return E_FAIL;
|
|
}
|
|
chunk.BlockIndex = (int)blockIndex;
|
|
_latestChunk = (int)chunkIndex;
|
|
}
|
|
|
|
_chunks[_latestChunk].AccessMark = _accessMark++;
|
|
}
|
|
|
|
_latestBlock = (int)blockIndex;
|
|
}
|
|
|
|
const CBlock &block = File->Blocks[(unsigned)_latestBlock];
|
|
const UInt64 offset = _virtPos - block.UnpPos;
|
|
{
|
|
const UInt64 rem = File->GetUnpackSize_of_Block((unsigned)_latestBlock) - offset;
|
|
if (size > rem)
|
|
size = (UInt32)rem;
|
|
if (size == 0) // it's unexpected case. but we check it.
|
|
return S_OK;
|
|
}
|
|
HRESULT res = S_OK;
|
|
|
|
if (block.IsZeroMethod())
|
|
memset(data, 0, size);
|
|
else if (_latestChunk >= 0)
|
|
memcpy(data, _chunks[_latestChunk].Buf + (size_t)offset, size);
|
|
else
|
|
{
|
|
if (block.Type != METHOD_COPY)
|
|
return E_FAIL;
|
|
RINOK(InStream_SeekSet(Stream, _startPos + File->StartPackPos + block.PackPos + offset))
|
|
res = Stream->Read(data, size, &size);
|
|
}
|
|
|
|
_virtPos += size;
|
|
if (processedSize)
|
|
*processedSize = size;
|
|
return res;
|
|
// COM_TRY_END
|
|
}
|
|
catch(...)
|
|
{
|
|
_errorMode = true;
|
|
return E_OUTOFMEMORY;
|
|
}
|
|
}
|
|
|
|
|
|
Z7_COM7F_IMF(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 = (UInt64)offset;
|
|
if (newPosition)
|
|
*newPosition = (UInt64)offset;
|
|
return S_OK;
|
|
}
|
|
|
|
Z7_COM7F_IMF(CHandler::GetStream(UInt32 index, ISequentialInStream **stream))
|
|
{
|
|
COM_TRY_BEGIN
|
|
|
|
#ifdef DMG_SHOW_RAW
|
|
if (index >= _files.Size())
|
|
return S_FALSE;
|
|
#endif
|
|
|
|
CMyComPtr2<ISequentialInStream, CInStream> spec;
|
|
spec.Create_if_Empty();
|
|
spec->File = &_files[index];
|
|
const CFile &file = *spec->File;
|
|
|
|
if (!file.IsCorrect)
|
|
return S_FALSE;
|
|
|
|
FOR_VECTOR (i, file.Blocks)
|
|
{
|
|
const CBlock &block = file.Blocks[i];
|
|
if (!block.NeedAllocateBuffer())
|
|
continue;
|
|
|
|
switch (block.Type)
|
|
{
|
|
#ifdef Z7_DMG_USE_CACHE_FOR_COPY_BLOCKS
|
|
case METHOD_COPY:
|
|
break;
|
|
#endif
|
|
case METHOD_ADC:
|
|
case METHOD_ZLIB:
|
|
case METHOD_BZIP2:
|
|
case METHOD_LZFSE:
|
|
case METHOD_XZ:
|
|
// case METHOD_END:
|
|
if (file.GetUnpackSize_of_Block(i) > k_Chunk_Size_MAX)
|
|
return S_FALSE;
|
|
break;
|
|
default:
|
|
return S_FALSE;
|
|
}
|
|
}
|
|
|
|
spec->Stream = _inStream;
|
|
spec->Size = spec->File->Size;
|
|
// RINOK(
|
|
spec->Init(_startPos + _dataForkPair.Offset);
|
|
*stream = spec.Detach();
|
|
return S_OK;
|
|
|
|
COM_TRY_END
|
|
}
|
|
|
|
REGISTER_ARC_I(
|
|
"Dmg", "dmg", NULL, 0xE4,
|
|
k_Signature,
|
|
0,
|
|
NArcInfoFlags::kBackwardOpen |
|
|
NArcInfoFlags::kUseGlobalOffset,
|
|
NULL)
|
|
|
|
}}
|