// Archive/IsoIn.cpp

#include "StdAfx.h"

#include "IsoIn.h"

#include "../../Common/StreamUtils.h"

namespace NArchive {
namespace NIso {
 
Byte CInArchive::ReadByte()
{
  if (m_BufferPos >= BlockSize)
    m_BufferPos = 0;
  if (m_BufferPos == 0)
  {
    size_t processedSize = BlockSize;
    if (ReadStream(_stream, m_Buffer, &processedSize) != S_OK)
      throw 1;
    if (processedSize != BlockSize)
      throw 1;
  }
  Byte b = m_Buffer[m_BufferPos++];
  _position++;
  return b;
}

void CInArchive::ReadBytes(Byte *data, UInt32 size)
{
  for (UInt32 i = 0; i < size; i++)
    data[i] = ReadByte();
}

void CInArchive::Skip(size_t size)
{
  while (size-- != 0)
    ReadByte();
}

void CInArchive::SkipZeros(size_t size)
{
  while (size-- != 0)
  {
    Byte b = ReadByte();
    if (b != 0)
      throw 1;
  }
}

UInt16 CInArchive::ReadUInt16Spec()
{
  UInt16 value = 0;
  for (int i = 0; i < 2; i++)
    value |= ((UInt16)(ReadByte()) << (8 * i));
  return value;
}


UInt16 CInArchive::ReadUInt16()
{
  Byte b[4];
  ReadBytes(b, 4);
  UInt32 value = 0;
  for (int i = 0; i < 2; i++)
  {
    if (b[i] != b[3 - i])
      IncorrectBigEndian = true;
    value |= ((UInt16)(b[i]) << (8 * i));
  }
  return (UInt16)value;
}

UInt32 CInArchive::ReadUInt32Le()
{
  UInt32 value = 0;
  for (int i = 0; i < 4; i++)
    value |= ((UInt32)(ReadByte()) << (8 * i));
  return value;
}

UInt32 CInArchive::ReadUInt32Be()
{
  UInt32 value = 0;
  for (int i = 0; i < 4; i++)
  {
    value <<= 8;
    value |= ReadByte();
  }
  return value;
}

UInt32 CInArchive::ReadUInt32()
{
  Byte b[8];
  ReadBytes(b, 8);
  UInt32 value = 0;
  for (int i = 0; i < 4; i++)
  {
    if (b[i] != b[7 - i])
      throw 1;
    value |= ((UInt32)(b[i]) << (8 * i));
  }
  return value;
}

UInt32 CInArchive::ReadDigits(int numDigits)
{
  UInt32 res = 0;
  for (int i = 0; i < numDigits; i++)
  {
    Byte b = ReadByte();
    if (b < '0' || b > '9')
    {
      if (b == 0) // it's bug in some CD's
        b = '0';
      else
        throw 1;
    }
    UInt32 d = (UInt32)(b - '0');
    res *= 10;
    res += d;
  }
  return res;
}

void CInArchive::ReadDateTime(CDateTime &d)
{
  d.Year = (UInt16)ReadDigits(4);
  d.Month = (Byte)ReadDigits(2);
  d.Day = (Byte)ReadDigits(2);
  d.Hour = (Byte)ReadDigits(2);
  d.Minute = (Byte)ReadDigits(2);
  d.Second = (Byte)ReadDigits(2);
  d.Hundredths = (Byte)ReadDigits(2);
  d.GmtOffset = (signed char)ReadByte();
}

void CInArchive::ReadBootRecordDescriptor(CBootRecordDescriptor &d)
{
  ReadBytes(d.BootSystemId, sizeof(d.BootSystemId));
  ReadBytes(d.BootId, sizeof(d.BootId));
  ReadBytes(d.BootSystemUse, sizeof(d.BootSystemUse));
}

void CInArchive::ReadRecordingDateTime(CRecordingDateTime &t)
{
  t.Year = ReadByte();
  t.Month = ReadByte();
  t.Day = ReadByte();
  t.Hour = ReadByte();
  t.Minute = ReadByte();
  t.Second = ReadByte();
  t.GmtOffset = (signed char)ReadByte();
}

void CInArchive::ReadDirRecord2(CDirRecord &r, Byte len)
{
  r.ExtendedAttributeRecordLen = ReadByte();
  if (r.ExtendedAttributeRecordLen != 0)
    throw 1;
  r.ExtentLocation = ReadUInt32();
  r.DataLength = ReadUInt32();
  ReadRecordingDateTime(r.DateTime);
  r.FileFlags = ReadByte();
  r.FileUnitSize = ReadByte();
  r.InterleaveGapSize = ReadByte();
  r.VolSequenceNumber = ReadUInt16();
  Byte idLen = ReadByte();
  r.FileId.SetCapacity(idLen);
  ReadBytes((Byte *)r.FileId, idLen);
  int padSize = 1 - (idLen & 1);
  
  // SkipZeros(1 - (idLen & 1));
  Skip(1 - (idLen & 1)); // it's bug in some cd's. Must be zeros

  int curPos = 33 + idLen + padSize;
  if (curPos > len)
    throw 1;
  int rem = len - curPos;
  r.SystemUse.SetCapacity(rem);
  ReadBytes((Byte *)r.SystemUse, rem);
}

void CInArchive::ReadDirRecord(CDirRecord &r)
{
  Byte len = ReadByte();
  // Some CDs can have incorrect value len = 48 ('0') in VolumeDescriptor.
  // But maybe we must use real "len" for other records.
  len = 34;
  ReadDirRecord2(r, len);
}

void CInArchive::ReadVolumeDescriptor(CVolumeDescriptor &d)
{
  d.VolFlags = ReadByte();
  ReadBytes(d.SystemId, sizeof(d.SystemId));
  ReadBytes(d.VolumeId, sizeof(d.VolumeId));
  SkipZeros(8);
  d.VolumeSpaceSize = ReadUInt32();
  ReadBytes(d.EscapeSequence, sizeof(d.EscapeSequence));
  d.VolumeSetSize = ReadUInt16();
  d.VolumeSequenceNumber = ReadUInt16();
  d.LogicalBlockSize = ReadUInt16();
  d.PathTableSize = ReadUInt32();
  d.LPathTableLocation = ReadUInt32Le();
  d.LOptionalPathTableLocation = ReadUInt32Le();
  d.MPathTableLocation = ReadUInt32Be();
  d.MOptionalPathTableLocation = ReadUInt32Be();
  ReadDirRecord(d.RootDirRecord);
  ReadBytes(d.VolumeSetId, sizeof(d.VolumeSetId));
  ReadBytes(d.PublisherId, sizeof(d.PublisherId));
  ReadBytes(d.DataPreparerId, sizeof(d.DataPreparerId));
  ReadBytes(d.ApplicationId, sizeof(d.ApplicationId));
  ReadBytes(d.CopyrightFileId, sizeof(d.CopyrightFileId));
  ReadBytes(d.AbstractFileId, sizeof(d.AbstractFileId));
  ReadBytes(d.BibFileId, sizeof(d.BibFileId));
  ReadDateTime(d.CTime);
  ReadDateTime(d.MTime);
  ReadDateTime(d.ExpirationTime);
  ReadDateTime(d.EffectiveTime);
  d.FileStructureVersion = ReadByte(); // = 1
  SkipZeros(1);
  ReadBytes(d.ApplicationUse, sizeof(d.ApplicationUse));
  SkipZeros(653);
}

static const Byte kSig_CD001[5] = { 'C', 'D', '0', '0', '1' };

static const Byte kSig_NSR02[5] = { 'N', 'S', 'R', '0', '2' };
static const Byte kSig_NSR03[5] = { 'N', 'S', 'R', '0', '3' };
static const Byte kSig_BEA01[5] = { 'B', 'E', 'A', '0', '1' };
static const Byte kSig_TEA01[5] = { 'T', 'E', 'A', '0', '1' };

static inline bool CheckSignature(const Byte *sig, const Byte *data)
{
  for (int i = 0; i < 5; i++)
    if (sig[i] != data[i])
      return false;
  return true;
}

void CInArchive::SeekToBlock(UInt32 blockIndex)
{
  if (_stream->Seek((UInt64)blockIndex * VolDescs[MainVolDescIndex].LogicalBlockSize, STREAM_SEEK_SET, &_position) != S_OK)
    throw 1;
  m_BufferPos = 0;
}

void CInArchive::ReadDir(CDir &d, int level)
{
  if (!d.IsDir())
    return;
  SeekToBlock(d.ExtentLocation);
  UInt64 startPos = _position;

  bool firstItem = true;
  for (;;)
  {
    UInt64 offset = _position - startPos;
    if (offset >= d.DataLength)
      break;
    Byte len = ReadByte();
    if (len == 0)
      continue;
    CDir subItem;
    ReadDirRecord2(subItem, len);
    if (firstItem && level == 0)
      IsSusp = subItem.CheckSusp(SuspSkipSize);
      
    if (!subItem.IsSystemItem())
      d._subItems.Add(subItem);

    firstItem = false;
  }
  for (int i = 0; i < d._subItems.Size(); i++)
    ReadDir(d._subItems[i], level + 1);
}

void CInArchive::CreateRefs(CDir &d)
{
  if (!d.IsDir())
    return;
  for (int i = 0; i < d._subItems.Size(); i++)
  {
    CRef ref;
    CDir &subItem = d._subItems[i];
    subItem.Parent = &d;
    ref.Dir = &d;
    ref.Index = i;
    Refs.Add(ref);
    CreateRefs(subItem);
  }
}

void CInArchive::ReadBootInfo()
{
  if (!_bootIsDefined)
    return;
  if (memcmp(_bootDesc.BootSystemId, kElToritoSpec, sizeof(_bootDesc.BootSystemId)) != 0)
    return;
  
  const Byte *p = (const Byte *)_bootDesc.BootSystemUse;
  UInt32 blockIndex = p[0] | ((UInt32)p[1] << 8) | ((UInt32)p[2] << 16) | ((UInt32)p[3] << 24);
  SeekToBlock(blockIndex);
  Byte b = ReadByte();
  if (b != NBootEntryId::kValidationEntry)
    return;
  {
    CBootValidationEntry e;
    e.PlatformId = ReadByte();
    if (ReadUInt16Spec() != 0)
      throw 1;
    ReadBytes(e.Id, sizeof(e.Id));
    /* UInt16 checkSum = */ ReadUInt16Spec();
    if (ReadByte() != 0x55)
      throw 1;
    if (ReadByte() != 0xAA)
      throw 1;
  }
  b = ReadByte();
  if (b == NBootEntryId::kInitialEntryBootable || b == NBootEntryId::kInitialEntryNotBootable)
  {
    CBootInitialEntry e;
    e.Bootable = (b == NBootEntryId::kInitialEntryBootable);
    e.BootMediaType = ReadByte();
    e.LoadSegment = ReadUInt16Spec();
    e.SystemType = ReadByte();
    if (ReadByte() != 0)
      throw 1;
    e.SectorCount = ReadUInt16Spec();
    e.LoadRBA = ReadUInt32Le();
    if (ReadByte() != 0)
      throw 1;
    BootEntries.Add(e);
  }
  else
    return;
}

HRESULT CInArchive::Open2()
{
  Clear();
  RINOK(_stream->Seek(kStartPos, STREAM_SEEK_CUR, &_position));

  m_BufferPos = 0;
  BlockSize = kBlockSize;
  for (;;)
  {
    Byte sig[7];
    ReadBytes(sig, 7);
    Byte ver = sig[6];
    if (!CheckSignature(kSig_CD001, sig + 1))
    {
      return S_FALSE;
      /*
      if (sig[0] != 0 || ver != 1)
        break;
      if (CheckSignature(kSig_BEA01, sig + 1))
      {
      }
      else if (CheckSignature(kSig_TEA01, sig + 1))
      {
        break;
      }
      else if (CheckSignature(kSig_NSR02, sig + 1))
      {
      }
      else
        break;
      SkipZeros(0x800 - 7);
      continue;
      */
    }
    // version = 2 for ISO 9660:1999?
    if (ver > 2)
      throw S_FALSE;

    if (sig[0] == NVolDescType::kTerminator)
    {
      break;
      // Skip(0x800 - 7);
      // continue;
    }
    switch(sig[0])
    {
      case NVolDescType::kBootRecord:
      {
        _bootIsDefined = true;
        ReadBootRecordDescriptor(_bootDesc);
        break;
      }
      case NVolDescType::kPrimaryVol:
      case NVolDescType::kSupplementaryVol:
      {
        // some ISOs have two PrimaryVols.
        CVolumeDescriptor vd;
        ReadVolumeDescriptor(vd);
        if (sig[0] == NVolDescType::kPrimaryVol)
        {
          // some burners write "Joliet" Escape Sequence to primary volume
          memset(vd.EscapeSequence, 0, sizeof(vd.EscapeSequence));
        }
        VolDescs.Add(vd);
        break;
      }
      default:
        break;
    }
  }
  if (VolDescs.IsEmpty())
    return S_FALSE;
  for (MainVolDescIndex = VolDescs.Size() - 1; MainVolDescIndex > 0; MainVolDescIndex--)
    if (VolDescs[MainVolDescIndex].IsJoliet())
      break;
  // MainVolDescIndex = 0; // to read primary volume
  const CVolumeDescriptor &vd = VolDescs[MainVolDescIndex];
  if (vd.LogicalBlockSize != kBlockSize)
    return S_FALSE;
  (CDirRecord &)_rootDir = vd.RootDirRecord;
  ReadDir(_rootDir, 0);
  CreateRefs(_rootDir);
  ReadBootInfo();
  return S_OK;
}

HRESULT CInArchive::Open(IInStream *inStream)
{
  _stream = inStream;
  UInt64 pos;
  RINOK(_stream->Seek(0, STREAM_SEEK_CUR, &pos));
  RINOK(_stream->Seek(0, STREAM_SEEK_END, &_archiveSize));
  RINOK(_stream->Seek(pos, STREAM_SEEK_SET, &_position));
  HRESULT res = S_FALSE;
  try { res = Open2(); }
  catch(...) { Clear(); res = S_FALSE; }
  _stream.Release();
  return res;
}

void CInArchive::Clear()
{
  IncorrectBigEndian = false;
  Refs.Clear();
  _rootDir.Clear();
  VolDescs.Clear();
  _bootIsDefined = false;
  BootEntries.Clear();
  SuspSkipSize = 0;
  IsSusp = false;
}

}}
