/// <summary> /// Initialises a new GPT and attempts to read its information from the specified disk. /// </summary> /// <param name="disk">The disk to read the GPT from.</param> public GPT(Hardware.Devices.DiskDevice disk) { #if GPT_TRACE BasicConsole.WriteLine("Checking for GPT..."); BasicConsole.DelayOutput(1); #endif //Assumed block size of 512. uint blockSize = 512; //Note: The GPT format specifies a protective MBR entry (1 partition // covering the entire disk) immediately followed (byte-wise) // by the GPT. Thus the GPT must come at 512th byte. // However, some disks can have 4096 bytes per sector (/block) // so the code below might break as reading LBA 1 (2nd LBA) would // return the wrong data. We probably ought to find some way to // check the block size and just load the required amount of data. //Check for single MBR partition with 0xEE system ID byte[] blockData = new byte[blockSize]; //Read the first sector of data. disk.ReadBlock(0UL, 1U, blockData); //Attempt to read the MBR MBR TheMBR = new MBR(blockData); //If the MBR isn't valid, the protective MBR partition specified as part of GPT // isn't present / valid so this isn't a valid GPT. if (!TheMBR.IsValid) { #if GPT_TRACE BasicConsole.WriteLine("MBR invalid."); BasicConsole.DelayOutput(1); #endif return; } //Or, if there is not one and only one partition in the MBR then the // protective MBR isn't valid so this isn't a valid GPT else if (TheMBR.NumPartitions != 1) { #if GPT_TRACE BasicConsole.WriteLine("No partitions in MBR."); BasicConsole.DelayOutput(1); #endif return; } //Or, the first (/only) partition entry has the wrong ID. 0xEE is the partition // ID for a GOT formatted MBR partition. else if (TheMBR.Partitions[0].SystemID != 0xEE) { #if GPT_TRACE BasicConsole.WriteLine(((FOS_System.String)"MBR partition 0 system ID not GPT. ") + TheMBR.Partitions[0].SystemID); BasicConsole.DelayOutput(1); #endif return; } #if GPT_TRACE BasicConsole.WriteLine("GPT MBR partition detected."); BasicConsole.DelayOutput(1); #endif //Now we know this is very-likely to be GPT formatted. // But we must check the GPT header for signature etc. //Read the GPT block disk.ReadBlock(1UL, 1U, blockData); //Check for GPT signature: 0x45 0x46 0x49 0x20 0x50 0x41 0x52 0x54 bool OK = blockData[0] == 0x45; OK = OK && blockData[1] == 0x46; OK = OK && blockData[2] == 0x49; OK = OK && blockData[3] == 0x20; OK = OK && blockData[4] == 0x50; OK = OK && blockData[5] == 0x41; OK = OK && blockData[6] == 0x52; OK = OK && blockData[7] == 0x54; //If any part of the ID was wrong, this is not a valid GPT. if (!OK) { #if GPT_TRACE BasicConsole.WriteLine("GPT signature invalid."); BasicConsole.DelayOutput(1); #endif return; } //Now we know, this is a valid GPT. Whether or not the actual entries are valid // is yet to be determined. There is of course the small chance that some other // data has conflicted with GPT data and that this isn't a GPT, it just looks // like it. If that is the case, what idiot formatted the disk we are reading // because a conflict like that is impossible to detect without user input! IsValid = true; #if GPT_TRACE BasicConsole.WriteLine("Valid GPT detected."); BasicConsole.DelayOutput(5); #endif //Load-in GPT global data Revision = ByteConverter.ToUInt32(blockData, 8); HeaderSize = ByteConverter.ToUInt32(blockData, 12); HeaderCRC32 = ByteConverter.ToUInt32(blockData, 16); HeaderLBA = ByteConverter.ToUInt64(blockData, 24); HeaderBackupLBA = ByteConverter.ToUInt64(blockData, 32); FirstUsableLBAForPartitions = ByteConverter.ToUInt64(blockData, 40); LastUsableLBAForPartitions = ByteConverter.ToUInt64(blockData, 48); #if GPT_TRACE BasicConsole.WriteLine(((FOS_System.String)"Revision : ") + Revision); BasicConsole.WriteLine(((FOS_System.String)"Header size : ") + HeaderSize); BasicConsole.WriteLine(((FOS_System.String)"Header CRC32 : ") + HeaderCRC32); BasicConsole.WriteLine(((FOS_System.String)"Header LBA : ") + HeaderLBA); BasicConsole.WriteLine(((FOS_System.String)"Header Backup LBA : ") + HeaderBackupLBA); BasicConsole.WriteLine(((FOS_System.String)"First usable LBA for partitions : ") + FirstUsableLBAForPartitions); BasicConsole.WriteLine(((FOS_System.String)"Last usable LBA for partitions : ") + LastUsableLBAForPartitions); BasicConsole.DelayOutput(5); #endif //Load the disk ID DiskGUID = new byte[16]; DiskGUID[0] = blockData[56]; DiskGUID[1] = blockData[57]; DiskGUID[2] = blockData[58]; DiskGUID[3] = blockData[59]; DiskGUID[4] = blockData[60]; DiskGUID[5] = blockData[61]; DiskGUID[6] = blockData[62]; DiskGUID[7] = blockData[63]; DiskGUID[8] = blockData[64]; DiskGUID[9] = blockData[65]; DiskGUID[10] = blockData[66]; DiskGUID[11] = blockData[67]; DiskGUID[12] = blockData[68]; DiskGUID[13] = blockData[69]; DiskGUID[14] = blockData[70]; DiskGUID[15] = blockData[71]; //Load more global GPT data StartingLBAOfPartitionArray = ByteConverter.ToUInt64(blockData, 72); NumPartitionEntries = ByteConverter.ToUInt32(blockData, 80); SizeOfPartitionEntry = ByteConverter.ToUInt32(blockData, 84); PartitionArrayCRC32 = ByteConverter.ToUInt32(blockData, 88); #if GPT_TRACE BasicConsole.WriteLine(((FOS_System.String)"Start LBA of part arrray : ") + StartingLBAOfPartitionArray); BasicConsole.WriteLine(((FOS_System.String)"Num part entries : ") + NumPartitionEntries); BasicConsole.WriteLine(((FOS_System.String)"Size of part entry : ") + SizeOfPartitionEntry); BasicConsole.WriteLine(((FOS_System.String)"Part array CRC32 : ") + PartitionArrayCRC32); BasicConsole.DelayOutput(5); #endif ulong blockNum = StartingLBAOfPartitionArray; uint entriesPerBlock = blockSize / SizeOfPartitionEntry; #if GPT_TRACE BasicConsole.WriteLine("Reading partition entries..."); BasicConsole.WriteLine(((FOS_System.String)"blockNum=") + blockNum); BasicConsole.WriteLine(((FOS_System.String)"entriesPerBlock=") + entriesPerBlock); BasicConsole.DelayOutput(1); #endif //TODO: Check the CRC32 values of the header and partition table // are correct. //Note: By not checking the CRCs, we have the option to manually edit // the GPT without rejecting it if CRCs end up incorrect. //TODO: Add an override option to ignore the CRCs //TODO: Add a method to update / correct the CRCs //Read partition infos for(uint i = 0; i < NumPartitionEntries; i++) { //If we're on a block boundary, we need to load the next block // of data to parse. if (i % entriesPerBlock == 0) { #if GPT_TRACE BasicConsole.WriteLine("Reading block data..."); BasicConsole.WriteLine(((FOS_System.String)"blockNum=") + blockNum); BasicConsole.DelayOutput(1); #endif //Load the next block of data disk.ReadBlock(blockNum++, 1u, blockData); } //Calculate the offset into the current data block uint offset = (i % entriesPerBlock) * SizeOfPartitionEntry; #if GPT_TRACE BasicConsole.WriteLine("Reading entry..."); BasicConsole.WriteLine(((FOS_System.String)"offset=") + offset); #endif //Attempt to load the partition info PartitionInfo inf = new PartitionInfo(blockData, offset, SizeOfPartitionEntry); //Partitions are marked as empty by an all-zero type ID. If the partition is empty, // there is no point adding it. if (!inf.Empty) { #if GPT_TRACE BasicConsole.WriteLine("Entry not empty."); #endif //Add the non-empty partition Partitions.Add(inf); } #if GPT_TRACE else { BasicConsole.WriteLine("Entry empty."); } #endif } }
/// <summary> /// Attempts to initialise a disk treating it as MBR formatted. /// </summary> /// <param name="aDiskDevice">The disk to initialise.</param> /// <returns>True if a valid MBR was detected and the disk was successfully initialised. Otherwise, false.</returns> private static bool InitAsMBR(DiskDevice aDiskDevice) { #if FSM_TRACE BasicConsole.WriteLine("Attempting to read MBR..."); #endif byte[] MBRData = new byte[512]; aDiskDevice.ReadBlock(0UL, 1U, MBRData); #if FSM_TRACE BasicConsole.WriteLine("Read potential MBR data. Attempting to init MBR..."); #endif MBR TheMBR = new MBR(MBRData); if (!TheMBR.IsValid) { return false; } else { #if FSM_TRACE BasicConsole.WriteLine("Valid MBR found."); #endif ProcessMBR(TheMBR, aDiskDevice); return true; } }
/// <summary> /// Processes a valid master boot record to initialize its partitions. /// </summary> /// <param name="anMBR">The MBR to process.</param> /// <param name="aDiskDevice">The disk device from which the MBR was read.</param> private static void ProcessMBR(MBR anMBR, DiskDevice aDiskDevice) { for (int i = 0; i < anMBR.NumPartitions; i++) { MBR.PartitionInfo aPartInfo = anMBR.Partitions[i]; if (aPartInfo.EBRLocation != 0) { byte[] EBRData = new byte[512]; aDiskDevice.ReadBlock(aPartInfo.EBRLocation, 1U, EBRData); EBR newEBR = new EBR(EBRData); ProcessMBR(newEBR, aDiskDevice); } else { Partitions.Add(new Partition(aDiskDevice, aPartInfo.StartSector, aPartInfo.SectorCount)); } } }
/// <summary> /// Initialises a new GPT and attempts to read its information from the specified disk. /// </summary> /// <param name="disk">The disk to read the GPT from.</param> public GPT(Hardware.Devices.DiskDevice disk) { #if GPT_TRACE BasicConsole.WriteLine("Checking for GPT..."); BasicConsole.DelayOutput(1); #endif //Assumed block size of 512. uint blockSize = 512; //Note: The GPT format specifies a protective MBR entry (1 partition // covering the entire disk) immediately followed (byte-wise) // by the GPT. Thus the GPT must come at 512th byte. // However, some disks can have 4096 bytes per sector (/block) // so the code below might break as reading LBA 1 (2nd LBA) would // return the wrong data. We probably ought to find some way to // check the block size and just load the required amount of data. //Check for single MBR partition with 0xEE system ID byte[] blockData = new byte[blockSize]; //Read the first sector of data. disk.ReadBlock(0UL, 1U, blockData); //Attempt to read the MBR MBR TheMBR = new MBR(blockData); //If the MBR isn't valid, the protective MBR partition specified as part of GPT // isn't present / valid so this isn't a valid GPT. if (!TheMBR.IsValid) { #if GPT_TRACE BasicConsole.WriteLine("MBR invalid."); BasicConsole.DelayOutput(1); #endif return; } //Or, if there is not one and only one partition in the MBR then the // protective MBR isn't valid so this isn't a valid GPT else if (TheMBR.NumPartitions != 1) { #if GPT_TRACE BasicConsole.WriteLine("No partitions in MBR."); BasicConsole.DelayOutput(1); #endif return; } //Or, the first (/only) partition entry has the wrong ID. 0xEE is the partition // ID for a GOT formatted MBR partition. else if (TheMBR.Partitions[0].SystemID != 0xEE) { #if GPT_TRACE BasicConsole.WriteLine(((FOS_System.String) "MBR partition 0 system ID not GPT. ") + TheMBR.Partitions[0].SystemID); BasicConsole.DelayOutput(1); #endif return; } #if GPT_TRACE BasicConsole.WriteLine("GPT MBR partition detected."); BasicConsole.DelayOutput(1); #endif //Now we know this is very-likely to be GPT formatted. // But we must check the GPT header for signature etc. //Read the GPT block disk.ReadBlock(1UL, 1U, blockData); //Check for GPT signature: 0x45 0x46 0x49 0x20 0x50 0x41 0x52 0x54 bool OK = blockData[0] == 0x45; OK = OK && blockData[1] == 0x46; OK = OK && blockData[2] == 0x49; OK = OK && blockData[3] == 0x20; OK = OK && blockData[4] == 0x50; OK = OK && blockData[5] == 0x41; OK = OK && blockData[6] == 0x52; OK = OK && blockData[7] == 0x54; //If any part of the ID was wrong, this is not a valid GPT. if (!OK) { #if GPT_TRACE BasicConsole.WriteLine("GPT signature invalid."); BasicConsole.DelayOutput(1); #endif return; } //Now we know, this is a valid GPT. Whether or not the actual entries are valid // is yet to be determined. There is of course the small chance that some other // data has conflicted with GPT data and that this isn't a GPT, it just looks // like it. If that is the case, what idiot formatted the disk we are reading // because a conflict like that is impossible to detect without user input! IsValid = true; #if GPT_TRACE BasicConsole.WriteLine("Valid GPT detected."); BasicConsole.DelayOutput(5); #endif //Load-in GPT global data Revision = ByteConverter.ToUInt32(blockData, 8); HeaderSize = ByteConverter.ToUInt32(blockData, 12); HeaderCRC32 = ByteConverter.ToUInt32(blockData, 16); HeaderLBA = ByteConverter.ToUInt64(blockData, 24); HeaderBackupLBA = ByteConverter.ToUInt64(blockData, 32); FirstUsableLBAForPartitions = ByteConverter.ToUInt64(blockData, 40); LastUsableLBAForPartitions = ByteConverter.ToUInt64(blockData, 48); #if GPT_TRACE BasicConsole.WriteLine(((FOS_System.String) "Revision : ") + Revision); BasicConsole.WriteLine(((FOS_System.String) "Header size : ") + HeaderSize); BasicConsole.WriteLine(((FOS_System.String) "Header CRC32 : ") + HeaderCRC32); BasicConsole.WriteLine(((FOS_System.String) "Header LBA : ") + HeaderLBA); BasicConsole.WriteLine(((FOS_System.String) "Header Backup LBA : ") + HeaderBackupLBA); BasicConsole.WriteLine(((FOS_System.String) "First usable LBA for partitions : ") + FirstUsableLBAForPartitions); BasicConsole.WriteLine(((FOS_System.String) "Last usable LBA for partitions : ") + LastUsableLBAForPartitions); BasicConsole.DelayOutput(5); #endif //Load the disk ID DiskGUID = new byte[16]; DiskGUID[0] = blockData[56]; DiskGUID[1] = blockData[57]; DiskGUID[2] = blockData[58]; DiskGUID[3] = blockData[59]; DiskGUID[4] = blockData[60]; DiskGUID[5] = blockData[61]; DiskGUID[6] = blockData[62]; DiskGUID[7] = blockData[63]; DiskGUID[8] = blockData[64]; DiskGUID[9] = blockData[65]; DiskGUID[10] = blockData[66]; DiskGUID[11] = blockData[67]; DiskGUID[12] = blockData[68]; DiskGUID[13] = blockData[69]; DiskGUID[14] = blockData[70]; DiskGUID[15] = blockData[71]; //Load more global GPT data StartingLBAOfPartitionArray = ByteConverter.ToUInt64(blockData, 72); NumPartitionEntries = ByteConverter.ToUInt32(blockData, 80); SizeOfPartitionEntry = ByteConverter.ToUInt32(blockData, 84); PartitionArrayCRC32 = ByteConverter.ToUInt32(blockData, 88); #if GPT_TRACE BasicConsole.WriteLine(((FOS_System.String) "Start LBA of part arrray : ") + StartingLBAOfPartitionArray); BasicConsole.WriteLine(((FOS_System.String) "Num part entries : ") + NumPartitionEntries); BasicConsole.WriteLine(((FOS_System.String) "Size of part entry : ") + SizeOfPartitionEntry); BasicConsole.WriteLine(((FOS_System.String) "Part array CRC32 : ") + PartitionArrayCRC32); BasicConsole.DelayOutput(5); #endif ulong blockNum = StartingLBAOfPartitionArray; uint entriesPerBlock = blockSize / SizeOfPartitionEntry; #if GPT_TRACE BasicConsole.WriteLine("Reading partition entries..."); BasicConsole.WriteLine(((FOS_System.String) "blockNum=") + blockNum); BasicConsole.WriteLine(((FOS_System.String) "entriesPerBlock=") + entriesPerBlock); BasicConsole.DelayOutput(1); #endif //TODO: Check the CRC32 values of the header and partition table // are correct. //Note: By not checking the CRCs, we have the option to manually edit // the GPT without rejecting it if CRCs end up incorrect. //TODO: Add an override option to ignore the CRCs //TODO: Add a method to update / correct the CRCs //Read partition infos for (uint i = 0; i < NumPartitionEntries; i++) { //If we're on a block boundary, we need to load the next block // of data to parse. if (i % entriesPerBlock == 0) { #if GPT_TRACE BasicConsole.WriteLine("Reading block data..."); BasicConsole.WriteLine(((FOS_System.String) "blockNum=") + blockNum); BasicConsole.DelayOutput(1); #endif //Load the next block of data disk.ReadBlock(blockNum++, 1u, blockData); } //Calculate the offset into the current data block uint offset = (i % entriesPerBlock) * SizeOfPartitionEntry; #if GPT_TRACE BasicConsole.WriteLine("Reading entry..."); BasicConsole.WriteLine(((FOS_System.String) "offset=") + offset); #endif //Attempt to load the partition info PartitionInfo inf = new PartitionInfo(blockData, offset, SizeOfPartitionEntry); //Partitions are marked as empty by an all-zero type ID. If the partition is empty, // there is no point adding it. if (!inf.Empty) { #if GPT_TRACE BasicConsole.WriteLine("Entry not empty."); #endif //Add the non-empty partition Partitions.Add(inf); } #if GPT_TRACE else { BasicConsole.WriteLine("Entry empty."); } #endif } }