// /***************************************************************************
// The Disc Image Chef
// ----------------------------------------------------------------------------
//
// Filename       : MBR.cs
// Author(s)      : Natalia Portillo <claunia@claunia.com>
//
// Component      : Partitioning scheme plugins.
//
// --[ Description ] ----------------------------------------------------------
//
//     Manages Intel/Microsoft MBR and UNIX slicing inside it.
//
// --[ License ] --------------------------------------------------------------
//
//     This library is free software; you can redistribute it and/or modify
//     it under the terms of the GNU Lesser General Public License as
//     published by the Free Software Foundation; either version 2.1 of the
//     License, or (at your option) any later version.
//
//     This library is distributed in the hope that it will be useful, but
//     WITHOUT ANY WARRANTY; without even the implied warranty of
//     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
//     Lesser General Public License for more details.
//
//     You should have received a copy of the GNU Lesser General Public
//     License along with this library; if not, see <http://www.gnu.org/licenses/>.
//
// ----------------------------------------------------------------------------
// Copyright © 2011-2017 Natalia Portillo
// ****************************************************************************/

using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using DiscImageChef.CommonTypes;
using DiscImageChef.Console;
using DiscImageChef.ImagePlugins;

namespace DiscImageChef.PartPlugins
{
    // TODO: Support AAP extensions
    public class MBR : PartPlugin
    {
        const ulong GptMagic = 0x5452415020494645;

        public MBR()
        {
            Name = "Master Boot Record";
            PluginUUID = new Guid("5E8A34E8-4F1A-59E6-4BF7-7EA647063A76");
        }

        public override bool GetInformation(ImagePlugins.ImagePlugin imagePlugin, out List<CommonTypes.Partition> partitions, ulong sectorOffset)
        {
            ulong counter = 0;

            partitions = new List<CommonTypes.Partition>();

            if(imagePlugin.GetSectorSize() < 512)
                return false;

            uint sectorSize = imagePlugin.GetSectorSize();
            // Divider of sector size in MBR between real sector size
            ulong divider = 1;

            if(imagePlugin.ImageInfo.xmlMediaType == ImagePlugins.XmlMediaType.OpticalDisc)
            {
                sectorSize = 512;
                divider = 4;
            }

            byte[] sector = imagePlugin.ReadSector(sectorOffset);

            GCHandle handle = GCHandle.Alloc(sector, GCHandleType.Pinned);
            MasterBootRecord mbr = (MasterBootRecord)Marshal.PtrToStructure(handle.AddrOfPinnedObject(), typeof(MasterBootRecord));
            TimedMasterBootRecord mbr_time = (TimedMasterBootRecord)Marshal.PtrToStructure(handle.AddrOfPinnedObject(), typeof(TimedMasterBootRecord));
            SerializedMasterBootRecord mbr_serial = (SerializedMasterBootRecord)Marshal.PtrToStructure(handle.AddrOfPinnedObject(), typeof(SerializedMasterBootRecord));
            ModernMasterBootRecord mbr_modern = (ModernMasterBootRecord)Marshal.PtrToStructure(handle.AddrOfPinnedObject(), typeof(ModernMasterBootRecord));
            NecMasterBootRecord mbr_nec = (NecMasterBootRecord)Marshal.PtrToStructure(handle.AddrOfPinnedObject(), typeof(NecMasterBootRecord));
            DiskManagerMasterBootRecord mbr_ontrack = (DiskManagerMasterBootRecord)Marshal.PtrToStructure(handle.AddrOfPinnedObject(), typeof(DiskManagerMasterBootRecord));
            handle.Free();

            DicConsole.DebugWriteLine("MBR plugin", "xmlmedia = {0}", imagePlugin.ImageInfo.xmlMediaType);
            DicConsole.DebugWriteLine("MBR plugin", "mbr.magic = {0:X4}", mbr.magic);

            if(mbr.magic != MBR_Magic)
                return false; // Not MBR

            byte[] hdrBytes = imagePlugin.ReadSector(1 + sectorOffset);

            ulong signature = BitConverter.ToUInt64(hdrBytes, 0);

            DicConsole.DebugWriteLine("MBR Plugin", "gpt.signature = 0x{0:X16}", signature);

            if(signature == GptMagic)
                return false;

            if(signature != GptMagic && imagePlugin.ImageInfo.xmlMediaType == XmlMediaType.OpticalDisc)
            {
                hdrBytes = imagePlugin.ReadSector(sectorOffset);
                signature = BitConverter.ToUInt64(hdrBytes, 512);
                DicConsole.DebugWriteLine("MBR Plugin", "gpt.signature @ 0x200 = 0x{0:X16}", signature);
                if(signature == GptMagic)
                    return false;
            }

            MBRPartitionEntry[] entries;

            if(mbr_ontrack.dm_magic == DM_Magic)
                entries = mbr_ontrack.entries;
            else if(mbr_nec.nec_magic == NEC_Magic)
                entries = mbr_nec.entries;
            else
                entries = mbr.entries;

            foreach(MBRPartitionEntry entry in entries)
            {
                byte start_sector = (byte)(entry.start_sector & 0x3F);
                ushort start_cylinder = (ushort)(((entry.start_sector & 0xC0) << 2) | entry.start_cylinder);
                byte end_sector = (byte)(entry.end_sector & 0x3F);
                ushort end_cylinder = (ushort)(((entry.end_sector & 0xC0) << 2) | entry.end_cylinder);
                ulong lba_start = entry.lba_start;
                ulong lba_sectors = entry.lba_sectors;

                // Let's start the fun...

                bool valid = true;
                bool extended = false;
                bool minix = false;

                if(entry.status != 0x00 && entry.status != 0x80)
                    return false; // Maybe a FAT filesystem
                valid &= entry.type != 0x00;
                if(entry.type == 0x05 || entry.type == 0x0F || entry.type == 0x15 || entry.type == 0x1F || entry.type == 0x85 ||
                  entry.type == 0x91 || entry.type == 0x9B || entry.type == 0xC5 || entry.type == 0xCF || entry.type == 0xD5)
                {
                    valid = false;
                    extended = true; // Extended partition
                }
                minix |= entry.type == 0x81 || entry.type == 0x80; // MINIX partition

                valid &= entry.lba_start != 0 || entry.lba_sectors != 0 || entry.start_cylinder != 0 || entry.start_head != 0 || entry.start_sector != 0 || entry.end_cylinder != 0 || entry.end_head != 0 || entry.end_sector != 0;
                if(entry.lba_start == 0 && entry.lba_sectors == 0 && valid)
                {
                    lba_start = Helpers.CHS.ToLBA(start_cylinder, entry.start_head, start_sector, imagePlugin.ImageInfo.heads, imagePlugin.ImageInfo.sectorsPerTrack);
                    lba_sectors = Helpers.CHS.ToLBA(end_cylinder, entry.end_head, entry.end_sector, imagePlugin.ImageInfo.heads, imagePlugin.ImageInfo.sectorsPerTrack) - lba_start;
                }

                // For optical media
                lba_start /= divider;
                lba_sectors /= divider;

                if(minix && lba_start == sectorOffset)
                    minix = false;

                if(lba_start > imagePlugin.GetSectors())
                {
                    valid = false;
                    extended = false;
                }

                // Some buggy implementations do some rounding errors getting a few sectors beyond device size
                if(lba_start + lba_sectors > imagePlugin.GetSectors())
                    lba_sectors = imagePlugin.GetSectors() - lba_start;

                DicConsole.DebugWriteLine("MBR plugin", "entry.status {0}", entry.status);
                DicConsole.DebugWriteLine("MBR plugin", "entry.type {0}", entry.type);
                DicConsole.DebugWriteLine("MBR plugin", "entry.lba_start {0}", entry.lba_start);
                DicConsole.DebugWriteLine("MBR plugin", "entry.lba_sectors {0}", entry.lba_sectors);
                DicConsole.DebugWriteLine("MBR plugin", "entry.start_cylinder {0}", start_cylinder);
                DicConsole.DebugWriteLine("MBR plugin", "entry.start_head {0}", entry.start_head);
                DicConsole.DebugWriteLine("MBR plugin", "entry.start_sector {0}", start_sector);
                DicConsole.DebugWriteLine("MBR plugin", "entry.end_cylinder {0}", end_cylinder);
                DicConsole.DebugWriteLine("MBR plugin", "entry.end_head {0}", entry.end_head);
                DicConsole.DebugWriteLine("MBR plugin", "entry.end_sector {0}", end_sector);

                DicConsole.DebugWriteLine("MBR plugin", "entry.minix = {0}", minix);

                DicConsole.DebugWriteLine("MBR plugin", "lba_start {0}", lba_start);
                DicConsole.DebugWriteLine("MBR plugin", "lba_sectors {0}", lba_sectors);

                if(valid && minix) // Let's mix the fun
                {
                    if(GetMinix(imagePlugin, lba_start, divider, sectorOffset, sectorSize, out List<Partition> mnx_parts))
                        partitions.AddRange(mnx_parts);
                    else
                        minix = false;
                }

                if(valid && !minix)
                {
                    CommonTypes.Partition part = new CommonTypes.Partition();
                    if((lba_start > 0 || imagePlugin.ImageInfo.xmlMediaType == XmlMediaType.OpticalDisc) && lba_sectors > 0)
                    {
                        part.Start = lba_start + sectorOffset;
                        part.Length = lba_sectors;
                        part.Offset = part.Start * sectorSize;
                        part.Size = part.Length * sectorSize;
                    }
                    else
                        valid = false;

                    if(valid)
                    {
                        part.Type = string.Format("0x{0:X2}", entry.type);
                        part.Name = DecodeMBRType(entry.type);
                        part.Sequence = counter;
                        part.Description = entry.status == 0x80 ? "Partition is bootable." : "";
                        part.Scheme = Name;

                        counter++;

                        partitions.Add(part);
                    }
                }

                DicConsole.DebugWriteLine("MBR plugin", "entry.extended = {0}", extended);

                if(extended) // Let's extend the fun
                {
                    bool processing_extended = true;
                    ulong chain_start = lba_start;

                    while(processing_extended)
                    {
                        sector = imagePlugin.ReadSector(lba_start);

                        handle = GCHandle.Alloc(sector, GCHandleType.Pinned);
                        ExtendedBootRecord ebr = (ExtendedBootRecord)Marshal.PtrToStructure(handle.AddrOfPinnedObject(), typeof(ExtendedBootRecord));
                        handle.Free();

                        DicConsole.DebugWriteLine("MBR plugin", "ebr.magic == MBR_Magic = {0}", ebr.magic == MBR_Magic);

                        if(ebr.magic != MBR_Magic)
                            break;

                        ulong next_start = 0;

                        foreach(MBRPartitionEntry ebr_entry in ebr.entries)
                        {
                            bool ext_valid = true;
                            start_sector = (byte)(ebr_entry.start_sector & 0x3F);
                            start_cylinder = (ushort)(((ebr_entry.start_sector & 0xC0) << 2) | ebr_entry.start_cylinder);
                            end_sector = (byte)(ebr_entry.end_sector & 0x3F);
                            end_cylinder = (ushort)(((ebr_entry.end_sector & 0xC0) << 2) | ebr_entry.end_cylinder);
                            ulong ext_start = ebr_entry.lba_start;
                            ulong ext_sectors = ebr_entry.lba_sectors;
                            bool ext_minix = false;

                            DicConsole.DebugWriteLine("MBR plugin", "ebr_entry.status {0}", ebr_entry.status);
                            DicConsole.DebugWriteLine("MBR plugin", "ebr_entry.type {0}", ebr_entry.type);
                            DicConsole.DebugWriteLine("MBR plugin", "ebr_entry.lba_start {0}", ebr_entry.lba_start);
                            DicConsole.DebugWriteLine("MBR plugin", "ebr_entry.lba_sectors {0}", ebr_entry.lba_sectors);
                            DicConsole.DebugWriteLine("MBR plugin", "ebr_entry.start_cylinder {0}", start_cylinder);
                            DicConsole.DebugWriteLine("MBR plugin", "ebr_entry.start_head {0}", ebr_entry.start_head);
                            DicConsole.DebugWriteLine("MBR plugin", "ebr_entry.start_sector {0}", start_sector);
                            DicConsole.DebugWriteLine("MBR plugin", "ebr_entry.end_cylinder {0}", end_cylinder);
                            DicConsole.DebugWriteLine("MBR plugin", "ebr_entry.end_head {0}", ebr_entry.end_head);
                            DicConsole.DebugWriteLine("MBR plugin", "ebr_entry.end_sector {0}", end_sector);

                            // Let's start the fun...
                            ext_valid &= ebr_entry.status == 0x00 || ebr_entry.status == 0x80;
                            ext_valid &= ebr_entry.type != 0x00;
                            ext_valid &= ebr_entry.lba_start != 0 || ebr_entry.lba_sectors != 0 || ebr_entry.start_cylinder != 0 || ebr_entry.start_head != 0 ||
                                            ebr_entry.start_sector != 0 || ebr_entry.end_cylinder != 0 || ebr_entry.end_head != 0 || ebr_entry.end_sector != 0;
                            if(ebr_entry.lba_start == 0 && ebr_entry.lba_sectors == 0 && ext_valid)
                            {
                                ext_start = Helpers.CHS.ToLBA(start_cylinder, ebr_entry.start_head, start_sector, imagePlugin.ImageInfo.heads, imagePlugin.ImageInfo.sectorsPerTrack);
                                ext_sectors = Helpers.CHS.ToLBA(end_cylinder, ebr_entry.end_head, ebr_entry.end_sector, imagePlugin.ImageInfo.heads, imagePlugin.ImageInfo.sectorsPerTrack) - ext_start;
                            }
                            ext_minix |= (ebr_entry.type == 0x81 || ebr_entry.type == 0x80);

                            // For optical media
                            ext_start /= divider;
                            ext_sectors /= divider;

                            DicConsole.DebugWriteLine("MBR plugin", "ext_start {0}", ext_start);
                            DicConsole.DebugWriteLine("MBR plugin", "ext_sectors {0}", ext_sectors);

                            if(ebr_entry.type == 0x05 || ebr_entry.type == 0x0F || ebr_entry.type == 0x15 || ebr_entry.type == 0x1F || ebr_entry.type == 0x85 ||
                                ebr_entry.type == 0x91 || ebr_entry.type == 0x9B || ebr_entry.type == 0xC5 || ebr_entry.type == 0xCF || ebr_entry.type == 0xD5)
                            {
                                ext_valid = false;
                                next_start = chain_start + ext_start;
                            }

                            ext_start += lba_start;
                            ext_valid &= ext_start <= imagePlugin.GetSectors();

                            // Some buggy implementations do some rounding errors getting a few sectors beyond device size
                            if(ext_start + ext_sectors > imagePlugin.GetSectors())
                                ext_sectors = imagePlugin.GetSectors() - ext_start;

                            if(ext_valid && ext_minix) // Let's mix the fun
                            {
                                if(GetMinix(imagePlugin, lba_start, divider, sectorOffset, sectorSize, out List<Partition> mnx_parts))
                                    partitions.AddRange(mnx_parts);
                                else
                                    ext_minix = false;
                            }

                            if(ext_valid && !ext_minix)
                            {
                                Partition part = new Partition();
                                if(ext_start > 0 && ext_sectors > 0)
                                {
                                    part.Start = ext_start + sectorOffset;
                                    part.Length = ext_sectors;
                                    part.Offset = part.Start * sectorSize;
                                    part.Size = part.Length * sectorSize;
                                }
                                else
                                    ext_valid = false;

                                if(ext_valid)
                                {
                                    part.Type = string.Format("0x{0:X2}", ebr_entry.type);
                                    part.Name = DecodeMBRType(ebr_entry.type);
                                    part.Sequence = counter;
                                    part.Description = ebr_entry.status == 0x80 ? "Partition is bootable." : "";
                                    part.Scheme = Name;
                                    counter++;

                                    partitions.Add(part);
                                }
                            }
                        }

                        DicConsole.DebugWriteLine("MBR plugin", "next_start {0}", next_start);
                        processing_extended &= next_start != 0;
                        processing_extended &= (next_start <= imagePlugin.GetSectors());
                        lba_start = next_start;
                    }
                }
            }

            // An empty MBR may exist, NeXT creates one and then hardcodes its disklabel
            return partitions.Count != 0;
        }

        static bool GetMinix(ImagePlugin imagePlugin, ulong start, ulong divider, ulong sectorOffset, uint sectorSize, out List<Partition> partitions)
        {
            partitions = new List<Partition>();

            byte[] sector = imagePlugin.ReadSector(start);

            GCHandle handle = GCHandle.Alloc(sector, GCHandleType.Pinned);
            ExtendedBootRecord mnx = (ExtendedBootRecord)Marshal.PtrToStructure(handle.AddrOfPinnedObject(), typeof(ExtendedBootRecord));
            handle.Free();

            DicConsole.DebugWriteLine("MBR plugin", "mnx.magic == MBR_Magic = {0}", mnx.magic == MBR_Magic);

            if(mnx.magic != MBR_Magic)
                return false;

            bool any_mnx = false;

            foreach(MBRPartitionEntry mnx_entry in mnx.entries)
            {
                bool mnx_valid = true;
                byte start_sector = (byte)(mnx_entry.start_sector & 0x3F);
                ushort start_cylinder = (ushort)(((mnx_entry.start_sector & 0xC0) << 2) | mnx_entry.start_cylinder);
                byte end_sector = (byte)(mnx_entry.end_sector & 0x3F);
                ushort end_cylinder = (ushort)(((mnx_entry.end_sector & 0xC0) << 2) | mnx_entry.end_cylinder);
                ulong mnx_start = mnx_entry.lba_start;
                ulong mnx_sectors = mnx_entry.lba_sectors;

                DicConsole.DebugWriteLine("MBR plugin", "mnx_entry.status {0}", mnx_entry.status);
                DicConsole.DebugWriteLine("MBR plugin", "mnx_entry.type {0}", mnx_entry.type);
                DicConsole.DebugWriteLine("MBR plugin", "mnx_entry.lba_start {0}", mnx_entry.lba_start);
                DicConsole.DebugWriteLine("MBR plugin", "mnx_entry.lba_sectors {0}", mnx_entry.lba_sectors);
                DicConsole.DebugWriteLine("MBR plugin", "mnx_entry.start_cylinder {0}", start_cylinder);
                DicConsole.DebugWriteLine("MBR plugin", "mnx_entry.start_head {0}", mnx_entry.start_head);
                DicConsole.DebugWriteLine("MBR plugin", "mnx_entry.start_sector {0}", start_sector);
                DicConsole.DebugWriteLine("MBR plugin", "mnx_entry.end_cylinder {0}", end_cylinder);
                DicConsole.DebugWriteLine("MBR plugin", "mnx_entry.end_head {0}", mnx_entry.end_head);
                DicConsole.DebugWriteLine("MBR plugin", "mnx_entry.end_sector {0}", end_sector);

                mnx_valid &= mnx_entry.status == 0x00 || mnx_entry.status == 0x80;
                mnx_valid &= mnx_entry.type == 0x81 || mnx_entry.type == 0x80;
                mnx_valid &= mnx_entry.lba_start != 0 || mnx_entry.lba_sectors != 0 || mnx_entry.start_cylinder != 0 || mnx_entry.start_head != 0 ||
                                mnx_entry.start_sector != 0 || mnx_entry.end_cylinder != 0 || mnx_entry.end_head != 0 || mnx_entry.end_sector != 0;
                if(mnx_entry.lba_start == 0 && mnx_entry.lba_sectors == 0 && mnx_valid)
                {
                    mnx_start = Helpers.CHS.ToLBA(start_cylinder, mnx_entry.start_head, start_sector, imagePlugin.ImageInfo.heads, imagePlugin.ImageInfo.sectorsPerTrack);
                    mnx_sectors = Helpers.CHS.ToLBA(end_cylinder, mnx_entry.end_head, mnx_entry.end_sector, imagePlugin.ImageInfo.heads, imagePlugin.ImageInfo.sectorsPerTrack) - mnx_start;
                }

                // For optical media
                mnx_start /= divider;
                mnx_sectors /= divider;

                DicConsole.DebugWriteLine("MBR plugin", "mnx_start {0}", mnx_start);
                DicConsole.DebugWriteLine("MBR plugin", "mnx_sectors {0}", mnx_sectors);

                if(mnx_valid)
                {
                    CommonTypes.Partition part = new CommonTypes.Partition();
                    if(mnx_start > 0 && mnx_sectors > 0)
                    {
                        part.Start = mnx_start + sectorOffset;
                        part.Length = mnx_sectors;
                        part.Offset = part.Start * sectorSize;
                        part.Size = part.Length * sectorSize;
                    }
                    else
                        mnx_valid = false;

                    if(mnx_valid)
                    {
                        any_mnx = true;
                        part.Type = "MINIX";
                        part.Name = "MINIX";
                        part.Description = mnx_entry.status == 0x80 ? "Partition is bootable." : "";
                        part.Scheme = "MINIX";

                        partitions.Add(part);
                    }
                }
            }

            return any_mnx;
        }

        static readonly string[] MBRTypes = {
            // 0x00
            "Empty", "FAT12", "XENIX root", "XENIX /usr",
            // 0x04
            "FAT16 < 32 MiB", "Extended", "FAT16", "IFS (HPFS/NTFS)",
            // 0x08
            "AIX boot, OS/2, Commodore DOS", "AIX data, Coherent, QNX", "Coherent swap, OPUS, OS/2 Boot Manager", "FAT32",
            // 0x0C
            "FAT32 (LBA)", "Unknown", "FAT16 (LBA)", "Extended (LBA)",
            // 0x10
            "OPUS", "Hidden FAT12", "Compaq diagnostics, recovery partition", "Unknown",
            // 0x14
            "Hidden FAT16 < 32 MiB, AST-DOS", "Unknown", "Hidden FAT16", "Hidden IFS (HPFS/NTFS)",
            // 0x18
            "AST-Windows swap", "Willowtech Photon coS", "Unknown", "Hidden FAT32",
            // 0x1C
            "Hidden FAT32 (LBA)", "Unknown", "Hidden FAT16 (LBA)", "Unknown",
            // 0x20
            "Willowsoft Overture File System", "Oxygen FSo2", "Oxygen Extended ", "SpeedStor reserved",
            // 0x24
            "NEC-DOS", "Unknown", "SpeedStor reserved", "Hidden NTFS",
            // 0x28
            "Unknown", "Unknown", "Unknown", "Unknown",
            // 0x2C
            "Unknown", "Unknown", "Unknown", "Unknown",
            // 0x30
            "Unknown", "SpeedStor reserved", "Unknown", "SpeedStor reserved",
            // 0x34
            "SpeedStor reserved", "Unknown", "SpeedStor reserved", "Unknown",
            // 0x38
            "Theos", "Plan 9", "Unknown", "Unknown",
            // 0x3C
            "Partition Magic", "Hidden NetWare", "Unknown", "Unknown",
            // 0x40
            "VENIX 80286", "PReP Boot", "Secure File System", "PTS-DOS",
            // 0x44
            "Unknown", "Priam, EUMEL/Elan", "EUMEL/Elan", "EUMEL/Elan",
            // 0x48
            "EUMEL/Elan", "Unknown", "ALFS/THIN lightweight filesystem for DOS", "Unknown",
            // 0x4C
            "Unknown", "QNX 4", "QNX 4", "QNX 4, Oberon",
            // 0x50
            "Ontrack DM, R/O, FAT", "Ontrack DM, R/W, FAT", "CP/M, Microport UNIX", "Ontrack DM 6",
            // 0x54
            "Ontrack DM 6", "EZ-Drive", "Golden Bow VFeature", "Unknown",
            // 0x58
            "Unknown", "Unknown", "Unknown", "Unknown",
            // 0x5C
            "Priam EDISK", "Unknown", "Unknown", "Unknown",
            // 0x60
            "Unknown", "SpeedStor", "Unknown", "GNU Hurd, System V, 386/ix",
            // 0x64
            "NetWare 286", "NetWare", "NetWare 386", "NetWare",
            // 0x68
            "NetWare", "NetWare NSS", "Unknown", "Unknown",
            // 0x6C
            "Unknown", "Unknown", "Unknown", "Unknown",
            // 0x70
            "DiskSecure Multi-Boot", "Unknown", "UNIX 7th Edition", "Unknown",
            // 0x74
            "Unknown", "IBM PC/IX", "Unknown", "Unknown",
            // 0x78
            "Unknown", "Unknown", "Unknown", "Unknown",
            // 0x7C
            "Unknown", "Unknown", "Unknown", "Unknown",
            // 0x80
            "Old MINIX", "MINIX, Old Linux", "Linux swap, Solaris", "Linux",
            // 0x84
            "Hidden by OS/2, APM hibernation", "Linux extended", "NT Stripe Set", "NT Stripe Set",
            // 0x88
            "Linux Plaintext", "Unknown", "Unknown", "Unknown",
            // 0x8C
            "Unknown", "Unknown", "Linux LVM", "Unknown",
            // 0x90
            "Unknown", "Unknown", "Unknown", "Amoeba, Hidden Linux",
            // 0x94
            "Amoeba bad blocks", "Unknown", "Unknown", "Unknown",
            // 0x98
            "Unknown", "Mylex EISA SCSI", "Unknown", "Unknown",
            // 0x9C
            "Unknown", "Unknown", "Unknown", "BSD/OS",
            // 0xA0
            "Hibernation", "HP Volume Expansion", "Unknown", "HP Volume Expansion",
            // 0xA4
            "HP Volume Expansion", "FreeBSD", "OpenBSD", "NeXTStep",
            // 0xA8
            "Apple UFS", "NetBSD", "Olivetti DOS FAT12", "Apple Boot",
            // 0xAC
            "Unknown", "Unknown", "Unknown", "Apple HFS",
            // 0xB0
            "BootStar", "HP Volume Expansion", "Unknown", "HP Volume Expansion",
            // 0xB4
            "HP Volume Expansion", "Unknown", "HP Volume Expansion", "BSDi",
            // 0xB8
            "BSDi swap", "Unknown", "Unknown", "PTS BootWizard",
            // 0xBC
            "Unknown", "Unknown", "Solaris boot", "Solaris",
            // 0xC0
            "Novell DOS, DR-DOS secured", "DR-DOS secured FAT12", "DR-DOS reserved", "DR-DOS reserved",
            // 0xC4
            "DR-DOS secured FAT16 < 32 MiB", "Unknown", "DR-DOS secured FAT16", "Syrinx",
            // 0xC8
            "DR-DOS reserved", "DR-DOS reserved", "DR-DOS reserved", "DR-DOS secured FAT32",
            // 0xCC
            "DR-DOS secured FAT32 (LBA)", "DR-DOS reserved", "DR-DOS secured FAT16 (LBA)", "DR-DOS secured extended (LBA)",
            // 0xD0
            "Multiuser DOS secured FAT12", "Multiuser DOS secured FAT12", "Unknown", "Unknown",
            // 0xD4
            "Multiuser DOS secured FAT16 < 32 MiB", "Multiuser DOS secured extended", "Multiuser DOS secured FAT16", "Unknown",
            // 0xD8
            "CP/M", "Unknown", "Filesystem-less data", "CP/M, CCP/M, CTOS",
            // 0xDC
            "Unknown", "Unknown", "Dell partition", "BootIt EMBRM",
            // 0xE0
            "Unknown", "SpeedStor", "DOS read/only", "SpeedStor",
            // 0xE4
            "SpeedStor", "Tandy DOS", "SpeedStor", "Unknown",
            // 0xE8
            "Unknown", "Unknown", "Unknown", "BeOS",
            // 0xEC
            "Unknown", "Spryt*x", "Guid Partition Table", "EFI system partition",
            // 0xF0
            "Linux boot", "SpeedStor", "DOS 3.3 secondary, Unisys DOS", "SpeedStor",
            // 0xF4
            "SpeedStor", "Prologue", "SpeedStor", "Unknown",
            // 0xF8
            "Unknown", "Unknown", "Unknown", "VMWare VMFS",
            // 0xFC
            "VMWare VMKCORE", "Linux RAID, FreeDOS", "SpeedStor, LANStep, PS/2 IML", "Xenix bad block"
        };

        static string DecodeMBRType(byte type)
        {
            return MBRTypes[type];
        }

        public const ushort MBR_Magic = 0xAA55;
        public const ushort NEC_Magic = 0xA55A;
        public const ushort DM_Magic = 0x55AA;

        [StructLayout(LayoutKind.Sequential, Pack = 1)]
        public struct MasterBootRecord
        {
            /// <summary>Boot code</summary>
            [MarshalAs(UnmanagedType.ByValArray, SizeConst = 446)]
            public byte[] boot_code;
            /// <summary>Partitions</summary>
            [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
            public MBRPartitionEntry[] entries;
            /// <summary><see cref="MBR_Magic"/></summary>
            public ushort magic;
        }

        // TODO: IBM Boot Manager
        [StructLayout(LayoutKind.Sequential, Pack = 1)]
        public struct ExtendedBootRecord
        {
            /// <summary>Boot code, almost always unused</summary>
            [MarshalAs(UnmanagedType.ByValArray, SizeConst = 446)]
            public byte[] boot_code;
            /// <summary>Partitions or pointers</summary>
            [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
            public MBRPartitionEntry[] entries;
            /// <summary><see cref="MBR_Magic"/></summary>
            public ushort magic;
        }

        [StructLayout(LayoutKind.Sequential, Pack = 1)]
        public struct TimedMasterBootRecord
        {
            /// <summary>Boot code</summary>
            [MarshalAs(UnmanagedType.ByValArray, SizeConst = 218)]
            public byte[] boot_code;
            /// <summary>Set to 0</summary>
            public ushort zero;
            /// <summary>Original physical drive</summary>
            public byte drive;
            /// <summary>Disk timestamp, seconds</summary>
            public byte seconds;
            /// <summary>Disk timestamp, minutes</summary>
            public byte minutes;
            /// <summary>Disk timestamp, hours</summary>
            public byte hours;
            /// <summary>Boot code, continuation</summary>
            [MarshalAs(UnmanagedType.ByValArray, SizeConst = 222)]
            public byte[] boot_code2;
            /// <summary>Partitions</summary>
            [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
            public MBRPartitionEntry[] entries;
            /// <summary><see cref="MBR_Magic"/></summary>
            public ushort magic;
        }

        [StructLayout(LayoutKind.Sequential, Pack = 1)]
        public struct SerializedMasterBootRecord
        {
            /// <summary>Boot code</summary>
            [MarshalAs(UnmanagedType.ByValArray, SizeConst = 440)]
            public byte[] boot_code;
            /// <summary>Disk serial number</summary>
            public uint serial;
            /// <summary>Set to 0</summary>
            public ushort zero;
            /// <summary>Partitions</summary>
            [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
            public MBRPartitionEntry[] entries;
            /// <summary><see cref="MBR_Magic"/></summary>
            public ushort magic;
        }

        [StructLayout(LayoutKind.Sequential, Pack = 1)]
        public struct ModernMasterBootRecord
        {
            /// <summary>Boot code</summary>
            [MarshalAs(UnmanagedType.ByValArray, SizeConst = 218)]
            public byte[] boot_code;
            /// <summary>Set to 0</summary>
            public ushort zero;
            /// <summary>Original physical drive</summary>
            public byte drive;
            /// <summary>Disk timestamp, seconds</summary>
            public byte seconds;
            /// <summary>Disk timestamp, minutes</summary>
            public byte minutes;
            /// <summary>Disk timestamp, hours</summary>
            public byte hours;
            /// <summary>Boot code, continuation</summary>
            [MarshalAs(UnmanagedType.ByValArray, SizeConst = 216)]
            public byte[] boot_code2;
            /// <summary>Disk serial number</summary>
            public uint serial;
            /// <summary>Set to 0</summary>
            public ushort zero2;
            /// <summary>Partitions</summary>
            [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
            public MBRPartitionEntry[] entries;
            /// <summary><see cref="MBR_Magic"/></summary>
            public ushort magic;
        }

        [StructLayout(LayoutKind.Sequential, Pack = 1)]
        public struct NecMasterBootRecord
        {
            /// <summary>Boot code</summary>
            [MarshalAs(UnmanagedType.ByValArray, SizeConst = 380)]
            public byte[] boot_code;
            /// <summary><see cref="NEC_Magic"/></summary>
            public ushort nec_magic;
            /// <summary>Partitions</summary>
            [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)]
            public MBRPartitionEntry[] entries;
            /// <summary><see cref="MBR_Magic"/></summary>
            public ushort magic;
        }

        [StructLayout(LayoutKind.Sequential, Pack = 1)]
        public struct DiskManagerMasterBootRecord
        {
            /// <summary>Boot code</summary>
            [MarshalAs(UnmanagedType.ByValArray, SizeConst = 252)]
            public byte[] boot_code;
            /// <summary><see cref="DM_Magic"/></summary>
            public ushort dm_magic;
            /// <summary>Partitions</summary>
            [MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)]
            public MBRPartitionEntry[] entries;
            /// <summary><see cref="MBR_Magic"/></summary>
            public ushort magic;
        }

        [StructLayout(LayoutKind.Sequential, Pack = 1)]
        public struct MBRPartitionEntry
        {
            /// <summary>Partition status, 0x80 or 0x00, else invalid</summary>
            public byte status;
            /// <summary>Starting head [0,254]</summary>
            public byte start_head;
            /// <summary>Starting sector [1,63]</summary>
            public byte start_sector;
            /// <summary>Starting cylinder [0,1023]</summary>
            public byte start_cylinder;
            /// <summary>Partition type</summary>
            public byte type;
            /// <summary>Ending head [0,254]</summary>
            public byte end_head;
            /// <summary>Ending sector [1,63]</summary>
            public byte end_sector;
            /// <summary>Ending cylinder [0,1023]</summary>
            public byte end_cylinder;
            /// <summary>Starting absolute sector</summary>
            public uint lba_start;
            /// <summary>Total sectors</summary>
            public uint lba_sectors;
        }
    }
}