The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
// 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;
}

}}