// MbrHandler.cpp #include "StdAfx.h" // #define SHOW_DEBUG_INFO #ifdef SHOW_DEBUG_INFO #include #endif #include "../../../C/CpuArch.h" #include "Common/Buffer.h" #include "Common/ComTry.h" #include "Common/IntToString.h" #include "Common/MyString.h" #include "Windows/PropVariant.h" #include "../Common/LimitedStreams.h" #include "../Common/ProgressUtils.h" #include "../Common/RegisterArc.h" #include "../Common/StreamUtils.h" #include "../Compress/CopyCoder.h" #ifdef SHOW_DEBUG_INFO #define PRF(x) x #else #define PRF(x) #endif using namespace NWindows; namespace NArchive { namespace NMbr { struct CChs { Byte Head; Byte SectCyl; Byte Cyl8; UInt32 GetSector() const { return SectCyl & 0x3F; } UInt32 GetCyl() const { return ((UInt32)SectCyl >> 6 << 8) | Cyl8; } void ToString(NCOM::CPropVariant &prop) const; void Parse(const Byte *p) { Head = p[0]; SectCyl = p[1]; Cyl8 = p[2]; } bool Check() const { return GetSector() > 0; } }; #define RINOZ(x) { int __tt = (x); if (__tt != 0) return __tt; } static int CompareChs(const CChs &c1, const CChs &c2) { RINOZ(MyCompare(c1.GetCyl(), c2.GetCyl())); RINOZ(MyCompare(c1.Head, c2.Head)); return MyCompare(c1.GetSector(), c2.GetSector()); } static void AddUIntToString(UInt32 val, AString &res) { char s[16]; ConvertUInt32ToString(val, s); res += s; } void CChs::ToString(NCOM::CPropVariant &prop) const { AString s; AddUIntToString(GetCyl(), s); s += '-'; AddUIntToString(Head, s); s += '-'; AddUIntToString(GetSector(), s); prop = s; } struct CPartition { Byte Status; CChs BeginChs; Byte Type; CChs EndChs; UInt32 Lba; UInt32 NumBlocks; CPartition() { memset (this, 0, sizeof(*this)); } bool IsEmpty() const { return Type == 0; } bool IsExtended() const { return Type == 5 || Type == 0xF; } UInt32 GetLimit() const { return Lba + NumBlocks; } // bool IsActive() const { return Status == 0x80; } UInt64 GetPos() const { return (UInt64)Lba * 512; } UInt64 GetSize() const { return (UInt64)NumBlocks * 512; } bool CheckLbaLimits() const { return (UInt32)0xFFFFFFFF - Lba >= NumBlocks; } bool Parse(const Byte *p) { Status = p[0]; BeginChs.Parse(p + 1); Type = p[4]; EndChs.Parse(p + 5); Lba = GetUi32(p + 8); NumBlocks = GetUi32(p + 12); if (Type == 0) return true; if (Status != 0 && Status != 0x80) return false; return BeginChs.Check() && EndChs.Check() && CompareChs(BeginChs, EndChs) <= 0 && NumBlocks > 0 && CheckLbaLimits(); } #ifdef SHOW_DEBUG_INFO void Print() const { NCOM::CPropVariant prop, prop2; BeginChs.ToString(prop); EndChs.ToString(prop2); printf(" %2x %2x %8X %8X %12S %12S", (int)Status, (int)Type, Lba, NumBlocks, prop.bstrVal, prop2.bstrVal); } #endif }; struct CPartType { UInt32 Id; const char *Ext; const char *Name; }; static const char *kFat = "fat"; static const CPartType kPartTypes[] = { { 0x01, kFat, "FAT12" }, { 0x04, kFat, "FAT16 DOS 3.0+" }, { 0x05, 0, "Extended" }, { 0x06, kFat, "FAT16 DOS 3.31+" }, { 0x07, "ntfs", "NTFS" }, { 0x0B, kFat, "FAT32" }, { 0x0C, kFat, "FAT32-LBA" }, { 0x0E, kFat, "FAT16-LBA" }, { 0x0F, 0, "Extended-LBA" }, { 0x11, kFat, "FAT12-Hidden" }, { 0x14, kFat, "FAT16-Hidden < 32 MB" }, { 0x16, kFat, "FAT16-Hidden >= 32 MB" }, { 0x1B, kFat, "FAT32-Hidden" }, { 0x1C, kFat, "FAT32-LBA-Hidden" }, { 0x1E, kFat, "FAT16-LBA-WIN95-Hidden" }, { 0x82, 0, "Solaris x86 / Linux swap" }, { 0x83, 0, "Linux" }, { 0xBE, 0, "Solaris 8 boot" }, { 0xBF, 0, "New Solaris x86" }, { 0xC2, 0, "Linux-Hidden" }, { 0xC3, 0, "Linux swap-Hidden" }, { 0xEE, 0, "EFI-MBR" }, { 0xEE, 0, "EFI" } }; static int FindPartType(UInt32 type) { for (int i = 0; i < sizeof(kPartTypes) / sizeof(kPartTypes[0]); i++) if (kPartTypes[i].Id == type) return i; return -1; } struct CItem { bool IsReal; bool IsPrim; UInt64 Size; CPartition Part; }; class CHandler: public IInArchive, public IInArchiveGetStream, public CMyUnknownImp { CMyComPtr _stream; CObjectVector _items; UInt64 _totalSize; CByteBuffer _buffer; HRESULT ReadTables(IInStream *stream, UInt32 baseLba, UInt32 lba, int level); public: MY_UNKNOWN_IMP2(IInArchive, IInArchiveGetStream) INTERFACE_IInArchive(;) STDMETHOD(GetStream)(UInt32 index, ISequentialInStream **stream); }; HRESULT CHandler::ReadTables(IInStream *stream, UInt32 baseLba, UInt32 lba, int level) { if (level >= 128 || _items.Size() >= 128) return S_FALSE; const int kNumHeaderParts = 4; CPartition parts[kNumHeaderParts]; { const UInt32 kSectorSize = 512; _buffer.SetCapacity(kSectorSize); Byte *buf = _buffer; UInt64 newPos = (UInt64)lba << 9; if (newPos + 512 > _totalSize) return S_FALSE; RINOK(stream->Seek(newPos, STREAM_SEEK_SET, NULL)); RINOK(ReadStream_FALSE(stream, buf, kSectorSize)); if (buf[0x1FE] != 0x55 || buf[0x1FF] != 0xAA) return S_FALSE; for (int i = 0; i < kNumHeaderParts; i++) if (!parts[i].Parse(buf + 0x1BE + 16 * i)) return S_FALSE; } PRF(printf("\n# %8X", lba)); UInt32 limLba = lba + 1; if (limLba == 0) return S_FALSE; for (int i = 0; i < kNumHeaderParts; i++) { CPartition &part = parts[i]; if (part.IsEmpty()) continue; PRF(printf("\n %2d ", (int)level)); #ifdef SHOW_DEBUG_INFO part.Print(); #endif int numItems = _items.Size(); UInt32 newLba = lba + part.Lba; if (part.IsExtended()) { // if (part.Type == 5) // Check it! newLba = baseLba + part.Lba; if (newLba < limLba) return S_FALSE; HRESULT res = ReadTables(stream, level < 1 ? newLba : baseLba, newLba, level + 1); if (res != S_FALSE && res != S_OK) return res; } if (newLba < limLba) return S_FALSE; part.Lba = newLba; if (!part.CheckLbaLimits()) return S_FALSE; CItem n; n.Part = part; bool addItem = false; if (numItems == _items.Size()) { n.IsPrim = (level == 0); n.IsReal = true; addItem = true; } else { const CItem &back = _items.Back(); UInt32 backLimit = back.Part.GetLimit(); UInt32 partLimit = part.GetLimit(); if (backLimit < partLimit) { n.IsReal = false; n.Part.Lba = backLimit; n.Part.NumBlocks = partLimit - backLimit; addItem = true; } } if (addItem) { if (n.Part.GetLimit() < limLba) return S_FALSE; limLba = n.Part.GetLimit(); n.Size = n.Part.GetSize(); _items.Add(n); } } return S_OK; } STDMETHODIMP CHandler::Open(IInStream *stream, const UInt64 * /* maxCheckStartPosition */, IArchiveOpenCallback * /* openArchiveCallback */) { COM_TRY_BEGIN Close(); RINOK(stream->Seek(0, STREAM_SEEK_END, &_totalSize)); RINOK(ReadTables(stream, 0, 0, 0)); if (_items.IsEmpty()) return S_FALSE; UInt32 lbaLimit = _items.Back().Part.GetLimit(); UInt64 lim = (UInt64)lbaLimit << 9; if (lim < _totalSize) { CItem n; n.Part.Lba = lbaLimit; n.Size = _totalSize - lim; n.IsReal = false; _items.Add(n); } _stream = stream; return S_OK; COM_TRY_END } STDMETHODIMP CHandler::Close() { _items.Clear(); _stream.Release(); return S_OK; } enum { kpidPrimary = kpidUserDefined, kpidBegChs, kpidEndChs }; STATPROPSTG kProps[] = { { NULL, kpidPath, VT_BSTR}, { NULL, kpidSize, VT_UI8}, { NULL, kpidFileSystem, VT_BSTR}, { NULL, kpidOffset, VT_UI8}, { L"Primary", kpidPrimary, VT_BOOL}, { L"Begin CHS", kpidBegChs, VT_BSTR}, { L"End CHS", kpidEndChs, VT_BSTR} }; IMP_IInArchive_Props_WITH_NAME IMP_IInArchive_ArcProps_NO_Table STDMETHODIMP CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value) { NCOM::CPropVariant prop; switch(propID) { case kpidMainSubfile: { int mainIndex = -1; for (int i = 0; i < _items.Size(); i++) if (_items[i].IsReal) { if (mainIndex >= 0) { mainIndex = -1; break; } mainIndex = i; } if (mainIndex >= 0) prop = (UInt32)mainIndex; break; } } prop.Detach(value); return S_OK; } STDMETHODIMP CHandler::GetNumberOfItems(UInt32 *numItems) { *numItems = _items.Size(); return S_OK; } STDMETHODIMP CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *value) { COM_TRY_BEGIN NCOM::CPropVariant prop; const CItem &item = _items[index]; const CPartition &part = item.Part; switch(propID) { case kpidPath: { AString s; AddUIntToString(index, s); if (item.IsReal) { int typeIndex = FindPartType(part.Type); s += '.'; const char *ext = "img"; if (typeIndex >= 0 && kPartTypes[typeIndex].Ext != 0) ext = kPartTypes[typeIndex].Ext; s += ext; } prop = s; break; } case kpidFileSystem: if (item.IsReal) { char s[32]; ConvertUInt32ToString(part.Type, s); const char *res = s; int typeIndex = FindPartType(part.Type); if (typeIndex >= 0 && kPartTypes[typeIndex].Name) res = kPartTypes[typeIndex].Name; prop = res; } break; case kpidSize: prop = item.Size; break;; case kpidPackSize: prop = item.Size; break; case kpidOffset: prop = part.GetPos(); break; case kpidPrimary: if (item.IsReal) prop = item.IsPrim; break; case kpidBegChs: if (item.IsReal) part.BeginChs.ToString(prop); break; case kpidEndChs: if (item.IsReal) part.EndChs.ToString(prop); break; } prop.Detach(value); return S_OK; COM_TRY_END } STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, Int32 testMode, IArchiveExtractCallback *extractCallback) { COM_TRY_BEGIN bool allFilesMode = (numItems == (UInt32)-1); if (allFilesMode) numItems = _items.Size(); if (numItems == 0) return S_OK; UInt64 totalSize = 0; UInt32 i; for (i = 0; i < numItems; i++) totalSize += _items[allFilesMode ? i : indices[i]].Size; extractCallback->SetTotal(totalSize); totalSize = 0; NCompress::CCopyCoder *copyCoderSpec = new NCompress::CCopyCoder(); CMyComPtr copyCoder = copyCoderSpec; CLocalProgress *lps = new CLocalProgress; CMyComPtr progress = lps; lps->Init(extractCallback, false); CLimitedSequentialInStream *streamSpec = new CLimitedSequentialInStream; CMyComPtr inStream(streamSpec); streamSpec->SetStream(_stream); for (i = 0; i < numItems; i++) { lps->InSize = totalSize; lps->OutSize = totalSize; RINOK(lps->SetCur()); CMyComPtr outStream; Int32 askMode = testMode ? NExtract::NAskMode::kTest : NExtract::NAskMode::kExtract; Int32 index = allFilesMode ? i : indices[i]; const CItem &item = _items[index]; const CPartition &part = item.Part; RINOK(extractCallback->GetStream(index, &outStream, askMode)); totalSize += item.Size; if (!testMode && !outStream) continue; RINOK(extractCallback->PrepareOperation(askMode)); RINOK(_stream->Seek(part.GetPos(), STREAM_SEEK_SET, NULL)); streamSpec->Init(item.Size); RINOK(copyCoder->Code(inStream, outStream, NULL, NULL, progress)); outStream.Release(); RINOK(extractCallback->SetOperationResult(copyCoderSpec->TotalSize == item.Size ? NExtract::NOperationResult::kOK: NExtract::NOperationResult::kDataError)); } return S_OK; COM_TRY_END } STDMETHODIMP CHandler::GetStream(UInt32 index, ISequentialInStream **stream) { COM_TRY_BEGIN const CItem &item = _items[index]; return CreateLimitedInStream(_stream, item.Part.GetPos(), item.Size, stream); COM_TRY_END } static IInArchive *CreateArc() { return new CHandler; } static CArcInfo g_ArcInfo = { L"MBR", L"mbr", 0, 0xDB, { 1, 1, 0 }, 3, false, CreateArc, 0 }; REGISTER_ARC(Mbr) }}