| // HfsIn.cpp |
| |
| #include "StdAfx.h" |
| |
| #include "../../Common/StreamUtils.h" |
| #include "Common/IntToString.h" |
| |
| #include "HfsIn.h" |
| |
| #include "../../../../C/CpuArch.h" |
| |
| #define Get16(p) GetBe16(p) |
| #define Get32(p) GetBe32(p) |
| #define Get64(p) GetBe64(p) |
| |
| namespace NArchive { |
| namespace NHfs { |
| |
| #define RINOZ(x) { int __tt = (x); if (__tt != 0) return __tt; } |
| |
| static int CompareIdToIndex(const CIdIndexPair *p1, const CIdIndexPair *p2, void * /* param */) |
| { |
| RINOZ(MyCompare(p1->ID, p2->ID)); |
| return MyCompare(p1->Index, p2->Index); |
| } |
| |
| bool operator< (const CIdIndexPair &a1, const CIdIndexPair &a2) { return (a1.ID < a2.ID); } |
| bool operator> (const CIdIndexPair &a1, const CIdIndexPair &a2) { return (a1.ID > a2.ID); } |
| bool operator==(const CIdIndexPair &a1, const CIdIndexPair &a2) { return (a1.ID == a2.ID); } |
| bool operator!=(const CIdIndexPair &a1, const CIdIndexPair &a2) { return (a1.ID != a2.ID); } |
| |
| static UString GetSpecName(const UString &name, UInt32 /* id */) |
| { |
| UString name2 = name; |
| name2.Trim(); |
| if (name2.IsEmpty()) |
| { |
| /* |
| wchar_t s[32]; |
| ConvertUInt64ToString(id, s); |
| return L"[" + (UString)s + L"]"; |
| */ |
| return L"[]"; |
| } |
| return name; |
| } |
| |
| UString CDatabase::GetItemPath(int index) const |
| { |
| const CItem *item = &Items[index]; |
| UString name = GetSpecName(item->Name, item->ID); |
| |
| for (int i = 0; i < 1000; i++) |
| { |
| if (item->ParentID < 16 && item->ParentID != 2) |
| { |
| if (item->ParentID != 1) |
| break; |
| return name; |
| } |
| CIdIndexPair pair; |
| pair.ID = item->ParentID; |
| pair.Index = 0; |
| int indexInMap = IdToIndexMap.FindInSorted(pair); |
| if (indexInMap < 0) |
| break; |
| item = &Items[IdToIndexMap[indexInMap].Index]; |
| name = GetSpecName(item->Name, item->ID) + WCHAR_PATH_SEPARATOR + name; |
| } |
| return (UString)L"Unknown" + WCHAR_PATH_SEPARATOR + name; |
| } |
| |
| void CFork::Parse(const Byte *p) |
| { |
| Size = Get64(p); |
| // ClumpSize = Get32(p + 8); |
| NumBlocks = Get32(p + 0xC); |
| for (int i = 0; i < 8; i++) |
| { |
| CExtent &e = Extents[i]; |
| e.Pos = Get32(p + 0x10 + i * 8); |
| e.NumBlocks = Get32(p + 0x10 + i * 8 + 4); |
| } |
| } |
| |
| static HRESULT ReadExtent(int blockSizeLog, IInStream *inStream, Byte *buf, const CExtent &e) |
| { |
| RINOK(inStream->Seek((UInt64)e.Pos << blockSizeLog, STREAM_SEEK_SET, NULL)); |
| return ReadStream_FALSE(inStream, buf, (size_t)e.NumBlocks << blockSizeLog); |
| } |
| |
| HRESULT CDatabase::ReadFile(const CFork &fork, CByteBuffer &buf, IInStream *inStream) |
| { |
| if (fork.NumBlocks >= Header.NumBlocks) |
| return S_FALSE; |
| size_t totalSize = (size_t)fork.NumBlocks << Header.BlockSizeLog; |
| if ((totalSize >> Header.BlockSizeLog) != fork.NumBlocks) |
| return S_FALSE; |
| buf.SetCapacity(totalSize); |
| UInt32 curBlock = 0; |
| for (int i = 0; i < 8; i++) |
| { |
| if (curBlock >= fork.NumBlocks) |
| break; |
| const CExtent &e = fork.Extents[i]; |
| if (fork.NumBlocks - curBlock < e.NumBlocks || e.Pos >= Header.NumBlocks) |
| return S_FALSE; |
| RINOK(ReadExtent(Header.BlockSizeLog, inStream, |
| (Byte *)buf + ((size_t)curBlock << Header.BlockSizeLog), e)); |
| curBlock += e.NumBlocks; |
| } |
| return S_OK; |
| } |
| |
| struct CNodeDescriptor |
| { |
| UInt32 fLink; |
| UInt32 bLink; |
| Byte Kind; |
| Byte Height; |
| UInt16 NumRecords; |
| // UInt16 Reserved; |
| void Parse(const Byte *p); |
| }; |
| |
| void CNodeDescriptor::Parse(const Byte *p) |
| { |
| fLink = Get32(p); |
| bLink = Get32(p + 4); |
| Kind = p[8]; |
| Height = p[9]; |
| NumRecords = Get16(p + 10); |
| } |
| |
| struct CHeaderRec |
| { |
| // UInt16 TreeDepth; |
| // UInt32 RootNode; |
| // UInt32 LeafRecords; |
| UInt32 FirstLeafNode; |
| // UInt32 LastLeafNode; |
| int NodeSizeLog; |
| // UInt16 MaxKeyLength; |
| UInt32 TotalNodes; |
| // UInt32 FreeNodes; |
| // UInt16 Reserved1; |
| // UInt32 ClumpSize; |
| // Byte BtreeType; |
| // Byte KeyCompareType; |
| // UInt32 Attributes; |
| // UInt32 Reserved3[16]; |
| |
| HRESULT Parse(const Byte *p); |
| }; |
| |
| HRESULT CHeaderRec::Parse(const Byte *p) |
| { |
| // TreeDepth = Get16(p); |
| // RootNode = Get32(p + 2); |
| // LeafRecords = Get32(p + 6); |
| FirstLeafNode = Get32(p + 0xA); |
| // LastLeafNode = Get32(p + 0xE); |
| UInt32 nodeSize = Get16(p + 0x12); |
| |
| int i; |
| for (i = 9; ((UInt32)1 << i) != nodeSize; i++) |
| if (i == 16) |
| return S_FALSE; |
| NodeSizeLog = i; |
| |
| // MaxKeyLength = Get16(p + 0x14); |
| TotalNodes = Get32(p + 0x16); |
| // FreeNodes = Get32(p + 0x1A); |
| // Reserved1 = Get16(p + 0x1E); |
| // ClumpSize = Get32(p + 0x20); |
| // BtreeType = p[0x24]; |
| // KeyCompareType = p[0x25]; |
| // Attributes = Get32(p + 0x26); |
| /* |
| for (int i = 0; i < 16; i++) |
| Reserved3[i] = Get32(p + 0x2A + i * 4); |
| */ |
| return S_OK; |
| } |
| |
| |
| enum ENodeType |
| { |
| NODE_TYPE_LEAF = 0xFF, |
| NODE_TYPE_INDEX = 0, |
| NODE_TYPE_HEADER = 1, |
| NODE_TYPE_MODE = 2 |
| }; |
| |
| HRESULT CDatabase::LoadExtentFile(IInStream *inStream) |
| { |
| // FileExtents.Clear(); |
| // ResExtents.Clear(); |
| |
| CByteBuffer extents; |
| RINOK(ReadFile(Header.ExtentsFile, extents, inStream)); |
| |
| const Byte *p = (const Byte *)extents; |
| |
| // CNodeDescriptor nodeDesc; |
| // nodeDesc.Parse(p); |
| CHeaderRec hr; |
| RINOK(hr.Parse(p + 14)); |
| |
| UInt32 node = hr.FirstLeafNode; |
| if (node != 0) |
| return S_FALSE; |
| /* |
| while (node != 0) |
| { |
| size_t nodeOffset = node * hr.NodeSize; |
| if ((node + 1)* hr.NodeSize > CatalogBuf.GetCapacity()) |
| return S_FALSE; |
| CNodeDescriptor desc; |
| desc.Parse(p + nodeOffset); |
| if (desc.Kind != NODE_TYPE_LEAF) |
| return S_FALSE; |
| UInt32 ptr = hr.NodeSize; |
| for (int i = 0; i < desc.NumRecords; i++) |
| { |
| UInt32 offs = Get16(p + nodeOffset + hr.NodeSize - (i + 1) * 2); |
| UInt32 offsNext = Get16(p + nodeOffset + hr.NodeSize - (i + 2) * 2); |
| |
| const Byte *r = p + nodeOffset + offs; |
| int keyLength = Get16(r); |
| Byte forkType = r[2]; |
| UInt32 id = Get16(r + 4); |
| UInt32 startBlock = Get16(r + 4); |
| CObjectVector<CIdExtents> *extents = (forkType == 0) ? &FileExtents : &ResExtents; |
| if (extents->Size() == 0) |
| extents->Add(CIdExtents()); |
| else |
| { |
| CIdExtents &e = extents->Back(); |
| if (e.ID != id) |
| { |
| if (e.ID > id) |
| return S_FALSE; |
| extents->Add(CIdExtents()); |
| } |
| } |
| CIdExtents &e = extents->Back(); |
| for (UInt32 k = offs + 10 + 2; k + 8 <= offsNext; k += 8) |
| { |
| CExtent ee; |
| ee.Pos = Get32(p + nodeOffset + k); |
| ee.NumBlocks = Get32(p + nodeOffset + k * 4); |
| e.Extents.Add(ee); |
| } |
| } |
| node = desc.fLink; |
| } |
| */ |
| return S_OK; |
| } |
| |
| |
| HRESULT CDatabase::LoadCatalog(IInStream *inStream, CProgressVirt *progress) |
| { |
| Items.Clear(); |
| IdToIndexMap.ClearAndFree(); |
| |
| CByteBuffer catalogBuf; |
| RINOK(ReadFile(Header.CatalogFile, catalogBuf, inStream)); |
| const Byte *p = (const Byte *)catalogBuf; |
| |
| // CNodeDescriptor nodeDesc; |
| // nodeDesc.Parse(p); |
| CHeaderRec hr; |
| hr.Parse(p + 14); |
| |
| // CaseSensetive = (Header.IsHfsX() && hr.KeyCompareType == 0xBC); |
| |
| if ((catalogBuf.GetCapacity() >> hr.NodeSizeLog) < hr.TotalNodes) |
| return S_FALSE; |
| |
| CByteBuffer usedBuf; |
| usedBuf.SetCapacity(hr.TotalNodes); |
| for (UInt32 i = 0; i < hr.TotalNodes; i++) |
| usedBuf[i] = 0; |
| |
| UInt32 node = hr.FirstLeafNode; |
| while (node != 0) |
| { |
| if (node >= hr.TotalNodes) |
| return S_FALSE; |
| if (usedBuf[node]) |
| return S_FALSE; |
| usedBuf[node] = 1; |
| size_t nodeOffset = (size_t)node << hr.NodeSizeLog; |
| CNodeDescriptor desc; |
| desc.Parse(p + nodeOffset); |
| if (desc.Kind != NODE_TYPE_LEAF) |
| return S_FALSE; |
| for (int i = 0; i < desc.NumRecords; i++) |
| { |
| UInt32 nodeSize = (1 << hr.NodeSizeLog); |
| UInt32 offs = Get16(p + nodeOffset + nodeSize - (i + 1) * 2); |
| UInt32 offsNext = Get16(p + nodeOffset + nodeSize - (i + 2) * 2); |
| UInt32 recSize = offsNext - offs; |
| if (offsNext >= nodeSize || offsNext < offs || recSize < 6) |
| return S_FALSE; |
| |
| CItem item; |
| |
| const Byte *r = p + nodeOffset + offs; |
| UInt32 keyLength = Get16(r); |
| item.ParentID = Get32(r + 2); |
| UString name; |
| if (keyLength < 6 || (keyLength & 1) != 0 || keyLength + 2 > recSize) |
| return S_FALSE; |
| r += 6; |
| recSize -= 6; |
| keyLength -= 6; |
| |
| int nameLength = Get16(r); |
| if (nameLength * 2 != (int)keyLength) |
| return S_FALSE; |
| r += 2; |
| recSize -= 2; |
| |
| wchar_t *pp = name.GetBuffer(nameLength + 1); |
| |
| int j; |
| for (j = 0; j < nameLength; j++) |
| pp[j] = ((wchar_t)r[j * 2] << 8) | r[j * 2 + 1]; |
| pp[j] = 0; |
| name.ReleaseBuffer(); |
| r += j * 2; |
| recSize -= j * 2; |
| |
| if (recSize < 2) |
| return S_FALSE; |
| item.Type = Get16(r); |
| |
| if (item.Type != RECORD_TYPE_FOLDER && item.Type != RECORD_TYPE_FILE) |
| continue; |
| if (recSize < 0x58) |
| return S_FALSE; |
| |
| // item.Flags = Get16(r + 2); |
| // item.Valence = Get32(r + 4); |
| item.ID = Get32(r + 8); |
| item.CTime = Get32(r + 0xC); |
| item.MTime = Get32(r + 0x10); |
| // item.AttrMTime = Get32(r + 0x14); |
| item.ATime = Get32(r + 0x18); |
| // item.BackupDate = Get32(r + 0x1C); |
| |
| /* |
| item.OwnerID = Get32(r + 0x20); |
| item.GroupID = Get32(r + 0x24); |
| item.AdminFlags = r[0x28]; |
| item.OwnerFlags = r[0x29]; |
| item.FileMode = Get16(r + 0x2A); |
| item.special.iNodeNum = Get16(r + 0x2C); |
| */ |
| |
| item.Name = name; |
| |
| if (item.IsDir()) |
| { |
| CIdIndexPair pair; |
| pair.ID = item.ID; |
| pair.Index = Items.Size(); |
| IdToIndexMap.Add(pair); |
| } |
| else |
| { |
| CFork fd; |
| recSize -= 0x58; |
| r += 0x58; |
| if (recSize < 0x50 * 2) |
| return S_FALSE; |
| fd.Parse(r); |
| item.Size = fd.Size; |
| item.NumBlocks = fd.NumBlocks; |
| UInt32 curBlock = 0; |
| for (int j = 0; j < 8; j++) |
| { |
| if (curBlock >= fd.NumBlocks) |
| break; |
| const CExtent &e = fd.Extents[j]; |
| item.Extents.Add(e); |
| curBlock += e.NumBlocks; |
| } |
| } |
| Items.Add(item); |
| if (progress && Items.Size() % 100 == 0) |
| { |
| RINOK(progress->SetCompleted(Items.Size())); |
| } |
| } |
| node = desc.fLink; |
| } |
| IdToIndexMap.Sort(CompareIdToIndex, NULL); |
| return S_OK; |
| } |
| |
| HRESULT CDatabase::Open(IInStream *inStream, CProgressVirt *progress) |
| { |
| static const UInt32 kHeaderSize = 1024 + 512; |
| Byte buf[kHeaderSize]; |
| RINOK(ReadStream_FALSE(inStream, buf, kHeaderSize)); |
| int i; |
| for (i = 0; i < 1024; i++) |
| if (buf[i] != 0) |
| return S_FALSE; |
| const Byte *p = buf + 1024; |
| CVolHeader &h = Header; |
| |
| h.Header[0] = p[0]; |
| h.Header[1] = p[1]; |
| if (p[0] != 'H' || (p[1] != '+' && p[1] != 'X')) |
| return S_FALSE; |
| h.Version = Get16(p + 2); |
| if (h.Version < 4 || h.Version > 5) |
| return S_FALSE; |
| |
| // h.Attr = Get32(p + 4); |
| // h.LastMountedVersion = Get32(p + 8); |
| // h.JournalInfoBlock = Get32(p + 0xC); |
| |
| h.CTime = Get32(p + 0x10); |
| h.MTime = Get32(p + 0x14); |
| // h.BackupTime = Get32(p + 0x18); |
| // h.CheckedTime = Get32(p + 0x1C); |
| |
| // h.NumFiles = Get32(p + 0x20); |
| // h.NumFolders = Get32(p + 0x24); |
| |
| UInt32 numFiles = Get32(p + 0x20); |
| UInt32 numFolders = Get32(p + 0x24);; |
| if (progress) |
| { |
| RINOK(progress->SetTotal(numFolders + numFiles)); |
| } |
| |
| UInt32 blockSize = Get32(p + 0x28); |
| |
| for (i = 9; ((UInt32)1 << i) != blockSize; i++) |
| if (i == 31) |
| return S_FALSE; |
| h.BlockSizeLog = i; |
| |
| h.NumBlocks = Get32(p + 0x2C); |
| h.NumFreeBlocks = Get32(p + 0x30); |
| |
| /* |
| h.WriteCount = Get32(p + 0x44); |
| for (i = 0; i < 6; i++) |
| h.FinderInfo[i] = Get32(p + 0x50 + i * 4); |
| h.VolID = Get64(p + 0x68); |
| */ |
| |
| UInt64 endPos; |
| RINOK(inStream->Seek(0, STREAM_SEEK_END, &endPos)); |
| if ((endPos >> h.BlockSizeLog) < h.NumBlocks) |
| return S_FALSE; |
| |
| // h.AllocationFile.Parse(p + 0x70 + 0x50 * 0); |
| h.ExtentsFile.Parse( p + 0x70 + 0x50 * 1); |
| h.CatalogFile.Parse( p + 0x70 + 0x50 * 2); |
| // h.AttributesFile.Parse(p + 0x70 + 0x50 * 3); |
| // h.StartupFile.Parse( p + 0x70 + 0x50 * 4); |
| |
| RINOK(LoadExtentFile(inStream)); |
| RINOK(LoadCatalog(inStream, progress)); |
| |
| // if (Header.NumFiles + Header.NumFolders != (UInt32)Items.Size()) return S_OK; |
| |
| return S_OK; |
| } |
| |
| }} |