674 lines
15 KiB
C++
674 lines
15 KiB
C++
// Archive/IsoIn.cpp
|
|
|
|
#include "StdAfx.h"
|
|
|
|
#include "../../../../C/CpuArch.h"
|
|
|
|
#include "../../../Common/MyException.h"
|
|
|
|
#include "../../Common/StreamUtils.h"
|
|
|
|
#include "../HandlerCont.h"
|
|
|
|
#include "IsoIn.h"
|
|
|
|
namespace NArchive {
|
|
namespace NIso {
|
|
|
|
struct CUnexpectedEndException {};
|
|
struct CHeaderErrorException {};
|
|
struct CEndianErrorException {};
|
|
|
|
static const char * const kMediaTypes[] =
|
|
{
|
|
"NoEmul"
|
|
, "1.2M"
|
|
, "1.44M"
|
|
, "2.88M"
|
|
, "HardDisk"
|
|
};
|
|
|
|
bool CBootInitialEntry::Parse(const Byte *p)
|
|
{
|
|
Bootable = (p[0] == NBootEntryId::kInitialEntryBootable);
|
|
BootMediaType = p[1];
|
|
LoadSegment = GetUi16(p + 2);
|
|
SystemType = p[4];
|
|
SectorCount = GetUi16(p + 6);
|
|
LoadRBA = GetUi32(p + 8);
|
|
memcpy(VendorSpec, p + 12, 20);
|
|
if (p[5] != 0)
|
|
return false;
|
|
if (p[0] != NBootEntryId::kInitialEntryBootable
|
|
&& p[0] != NBootEntryId::kInitialEntryNotBootable)
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
AString CBootInitialEntry::GetName() const
|
|
{
|
|
AString s = (Bootable ? "Boot" : "NotBoot");
|
|
s += '-';
|
|
|
|
if (BootMediaType < ARRAY_SIZE(kMediaTypes))
|
|
s += kMediaTypes[BootMediaType];
|
|
else
|
|
{
|
|
char name[16];
|
|
ConvertUInt32ToString(BootMediaType, name);
|
|
s += name;
|
|
}
|
|
|
|
if (VendorSpec[0] == 1)
|
|
{
|
|
// "Language and Version Information (IBM)"
|
|
|
|
unsigned i;
|
|
for (i = 1; i < sizeof(VendorSpec); i++)
|
|
if (VendorSpec[i] > 0x7F)
|
|
break;
|
|
if (i == sizeof(VendorSpec))
|
|
{
|
|
s += '-';
|
|
for (i = 1; i < sizeof(VendorSpec); i++)
|
|
{
|
|
char c = VendorSpec[i];
|
|
if (c == 0)
|
|
break;
|
|
if (c == '\\' || c == '/')
|
|
c = '_';
|
|
s += c;
|
|
}
|
|
}
|
|
}
|
|
|
|
s += ".img";
|
|
return s;
|
|
}
|
|
|
|
Byte CInArchive::ReadByte()
|
|
{
|
|
if (m_BufferPos >= kBlockSize)
|
|
m_BufferPos = 0;
|
|
if (m_BufferPos == 0)
|
|
{
|
|
size_t processed = kBlockSize;
|
|
HRESULT res = ReadStream(_stream, m_Buffer, &processed);
|
|
if (res != S_OK)
|
|
throw CSystemException(res);
|
|
if (processed != kBlockSize)
|
|
throw CUnexpectedEndException();
|
|
UInt64 end = _position + processed;
|
|
if (PhySize < end)
|
|
PhySize = end;
|
|
}
|
|
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 CHeaderErrorException();
|
|
}
|
|
}
|
|
|
|
UInt16 CInArchive::ReadUInt16()
|
|
{
|
|
Byte b[4];
|
|
ReadBytes(b, 4);
|
|
UInt32 val = 0;
|
|
for (int i = 0; i < 2; i++)
|
|
{
|
|
if (b[i] != b[3 - i])
|
|
IncorrectBigEndian = true;
|
|
val |= ((UInt16)(b[i]) << (8 * i));
|
|
}
|
|
return (UInt16)val;
|
|
}
|
|
|
|
UInt32 CInArchive::ReadUInt32Le()
|
|
{
|
|
UInt32 val = 0;
|
|
for (int i = 0; i < 4; i++)
|
|
val |= ((UInt32)(ReadByte()) << (8 * i));
|
|
return val;
|
|
}
|
|
|
|
UInt32 CInArchive::ReadUInt32Be()
|
|
{
|
|
UInt32 val = 0;
|
|
for (int i = 0; i < 4; i++)
|
|
{
|
|
val <<= 8;
|
|
val |= ReadByte();
|
|
}
|
|
return val;
|
|
}
|
|
|
|
UInt32 CInArchive::ReadUInt32()
|
|
{
|
|
Byte b[8];
|
|
ReadBytes(b, 8);
|
|
UInt32 val = 0;
|
|
for (int i = 0; i < 4; i++)
|
|
{
|
|
if (b[i] != b[7 - i])
|
|
throw CEndianErrorException();
|
|
val |= ((UInt32)(b[i]) << (8 * i));
|
|
}
|
|
return val;
|
|
}
|
|
|
|
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 CHeaderErrorException();
|
|
}
|
|
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 CHeaderErrorException();
|
|
r.ExtentLocation = ReadUInt32();
|
|
r.Size = ReadUInt32();
|
|
ReadRecordingDateTime(r.DateTime);
|
|
r.FileFlags = ReadByte();
|
|
r.FileUnitSize = ReadByte();
|
|
r.InterleaveGapSize = ReadByte();
|
|
r.VolSequenceNumber = ReadUInt16();
|
|
Byte idLen = ReadByte();
|
|
r.FileId.Alloc(idLen);
|
|
ReadBytes((Byte *)r.FileId, idLen);
|
|
unsigned padSize = 1 - (idLen & 1);
|
|
|
|
// SkipZeros(padSize);
|
|
Skip(padSize); // it's bug in some cd's. Must be zeros
|
|
|
|
unsigned curPos = 33 + idLen + padSize;
|
|
if (curPos > len)
|
|
throw CHeaderErrorException();
|
|
unsigned rem = len - curPos;
|
|
r.SystemUse.Alloc(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));
|
|
|
|
// Most ISO contains zeros in the following field (reserved for future standardization).
|
|
// But some ISO programs write some data to that area.
|
|
// So we disable check for zeros.
|
|
Skip(653); // 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)
|
|
{
|
|
HRESULT res = _stream->Seek((UInt64)blockIndex * VolDescs[MainVolDescIndex].LogicalBlockSize, STREAM_SEEK_SET, &_position);
|
|
if (res != S_OK)
|
|
throw CSystemException(res);
|
|
m_BufferPos = 0;
|
|
}
|
|
|
|
static const int kNumLevelsMax = 256;
|
|
|
|
void CInArchive::ReadDir(CDir &d, int level)
|
|
{
|
|
if (!d.IsDir())
|
|
return;
|
|
if (level > kNumLevelsMax)
|
|
{
|
|
TooDeepDirs = true;
|
|
return;
|
|
}
|
|
|
|
{
|
|
FOR_VECTOR (i, UniqStartLocations)
|
|
if (UniqStartLocations[i] == d.ExtentLocation)
|
|
{
|
|
SelfLinkedDirs = true;
|
|
return;
|
|
}
|
|
UniqStartLocations.Add(d.ExtentLocation);
|
|
}
|
|
|
|
SeekToBlock(d.ExtentLocation);
|
|
UInt64 startPos = _position;
|
|
|
|
bool firstItem = true;
|
|
for (;;)
|
|
{
|
|
UInt64 offset = _position - startPos;
|
|
if (offset >= d.Size)
|
|
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_VECTOR (i, d._subItems)
|
|
ReadDir(d._subItems[i], level + 1);
|
|
|
|
UniqStartLocations.DeleteBack();
|
|
}
|
|
|
|
void CInArchive::CreateRefs(CDir &d)
|
|
{
|
|
if (!d.IsDir())
|
|
return;
|
|
for (unsigned i = 0; i < d._subItems.Size();)
|
|
{
|
|
CRef ref;
|
|
CDir &subItem = d._subItems[i];
|
|
subItem.Parent = &d;
|
|
ref.Dir = &d;
|
|
ref.Index = i++;
|
|
ref.NumExtents = 1;
|
|
ref.TotalSize = subItem.Size;
|
|
if (subItem.IsNonFinalExtent())
|
|
{
|
|
for (;;)
|
|
{
|
|
if (i == d._subItems.Size())
|
|
{
|
|
HeadersError = true;
|
|
break;
|
|
}
|
|
const CDir &next = d._subItems[i];
|
|
if (!subItem.AreMultiPartEqualWith(next))
|
|
break;
|
|
i++;
|
|
ref.NumExtents++;
|
|
ref.TotalSize += next.Size;
|
|
if (!next.IsNonFinalExtent())
|
|
break;
|
|
}
|
|
}
|
|
Refs.Add(ref);
|
|
CreateRefs(subItem);
|
|
}
|
|
}
|
|
|
|
void CInArchive::ReadBootInfo()
|
|
{
|
|
if (!_bootIsDefined)
|
|
return;
|
|
HeadersError = true;
|
|
|
|
if (memcmp(_bootDesc.BootSystemId, kElToritoSpec, sizeof(_bootDesc.BootSystemId)) != 0)
|
|
return;
|
|
|
|
UInt32 blockIndex = GetUi32(_bootDesc.BootSystemUse);
|
|
SeekToBlock(blockIndex);
|
|
|
|
Byte buf[32];
|
|
ReadBytes(buf, 32);
|
|
|
|
if (buf[0] != NBootEntryId::kValidationEntry
|
|
|| buf[2] != 0
|
|
|| buf[3] != 0
|
|
|| buf[30] != 0x55
|
|
|| buf[31] != 0xAA)
|
|
return;
|
|
|
|
{
|
|
UInt32 sum = 0;
|
|
for (unsigned i = 0; i < 32; i += 2)
|
|
sum += GetUi16(buf + i);
|
|
if ((sum & 0xFFFF) != 0)
|
|
return;
|
|
/*
|
|
CBootValidationEntry e;
|
|
e.PlatformId = buf[1];
|
|
memcpy(e.Id, buf + 4, sizeof(e.Id));
|
|
// UInt16 checkSum = GetUi16(p + 28);
|
|
*/
|
|
}
|
|
|
|
ReadBytes(buf, 32);
|
|
{
|
|
CBootInitialEntry e;
|
|
if (!e.Parse(buf))
|
|
return;
|
|
BootEntries.Add(e);
|
|
}
|
|
|
|
bool error = false;
|
|
|
|
for (;;)
|
|
{
|
|
ReadBytes(buf, 32);
|
|
Byte headerIndicator = buf[0];
|
|
if (headerIndicator != NBootEntryId::kMoreHeaders
|
|
&& headerIndicator != NBootEntryId::kFinalHeader)
|
|
break;
|
|
|
|
// Section Header
|
|
// Byte platform = p[1];
|
|
unsigned numEntries = GetUi16(buf + 2);
|
|
// id[28]
|
|
|
|
for (unsigned i = 0; i < numEntries; i++)
|
|
{
|
|
ReadBytes(buf, 32);
|
|
CBootInitialEntry e;
|
|
if (!e.Parse(buf))
|
|
{
|
|
error = true;
|
|
break;
|
|
}
|
|
if (e.BootMediaType & (1 << 5))
|
|
{
|
|
// Section entry extension
|
|
for (unsigned j = 0;; j++)
|
|
{
|
|
ReadBytes(buf, 32);
|
|
if (j > 32 || buf[0] != NBootEntryId::kExtensionIndicator)
|
|
{
|
|
error = true;
|
|
break;
|
|
}
|
|
if ((buf[1] & (1 << 5)) == 0)
|
|
break;
|
|
// info += (buf + 2, 30)
|
|
}
|
|
}
|
|
BootEntries.Add(e);
|
|
}
|
|
|
|
if (headerIndicator != NBootEntryId::kMoreHeaders)
|
|
break;
|
|
}
|
|
|
|
HeadersError = error;
|
|
}
|
|
|
|
HRESULT CInArchive::Open2()
|
|
{
|
|
_position = 0;
|
|
RINOK(_stream->Seek(0, STREAM_SEEK_END, &_fileSize));
|
|
if (_fileSize < kStartPos)
|
|
return S_FALSE;
|
|
RINOK(_stream->Seek(kStartPos, STREAM_SEEK_SET, &_position));
|
|
|
|
PhySize = _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)
|
|
return 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;
|
|
|
|
IsArc = true;
|
|
|
|
(CDirRecord &)_rootDir = vd.RootDirRecord;
|
|
ReadDir(_rootDir, 0);
|
|
CreateRefs(_rootDir);
|
|
ReadBootInfo();
|
|
|
|
{
|
|
FOR_VECTOR (i, Refs)
|
|
{
|
|
const CRef &ref = Refs[i];
|
|
for (UInt32 j = 0; j < ref.NumExtents; j++)
|
|
{
|
|
const CDir &item = ref.Dir->_subItems[ref.Index + j];
|
|
if (!item.IsDir())
|
|
UpdatePhySize(item.ExtentLocation, item.Size);
|
|
}
|
|
}
|
|
}
|
|
{
|
|
FOR_VECTOR (i, BootEntries)
|
|
{
|
|
const CBootInitialEntry &be = BootEntries[i];
|
|
UpdatePhySize(be.LoadRBA, GetBootItemSize(i));
|
|
}
|
|
}
|
|
|
|
if (PhySize < _fileSize)
|
|
{
|
|
UInt64 rem = _fileSize - PhySize;
|
|
const UInt64 kRemMax = 1 << 21;
|
|
if (rem <= kRemMax)
|
|
{
|
|
RINOK(_stream->Seek(PhySize, STREAM_SEEK_SET, NULL));
|
|
bool areThereNonZeros = false;
|
|
UInt64 numZeros = 0;
|
|
RINOK(ReadZeroTail(_stream, areThereNonZeros, numZeros, kRemMax));
|
|
if (!areThereNonZeros)
|
|
PhySize += numZeros;
|
|
}
|
|
}
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
HRESULT CInArchive::Open(IInStream *inStream)
|
|
{
|
|
Clear();
|
|
_stream = inStream;
|
|
try { return Open2(); }
|
|
catch(const CSystemException &e) { return e.ErrorCode; }
|
|
catch(CUnexpectedEndException &) { UnexpectedEnd = true; return S_FALSE; }
|
|
catch(CHeaderErrorException &) { HeadersError = true; return S_FALSE; }
|
|
catch(CEndianErrorException &) { IncorrectBigEndian = true; return S_FALSE; }
|
|
}
|
|
|
|
void CInArchive::Clear()
|
|
{
|
|
IsArc = false;
|
|
UnexpectedEnd = false;
|
|
HeadersError = false;
|
|
IncorrectBigEndian = false;
|
|
TooDeepDirs = false;
|
|
SelfLinkedDirs = false;
|
|
|
|
UniqStartLocations.Clear();
|
|
|
|
Refs.Clear();
|
|
_rootDir.Clear();
|
|
VolDescs.Clear();
|
|
_bootIsDefined = false;
|
|
BootEntries.Clear();
|
|
SuspSkipSize = 0;
|
|
IsSusp = false;
|
|
}
|
|
|
|
}}
|