using System.Buffers.Binary; using System.Text; using XFESystem.Core; namespace XFESystem.Storage; public interface IPhysicalDisk { string Name { get; } ulong SectorCount { get; } int LogicalSectorBytes => 512; bool CanWrite { get; } void Read(ulong lba, Span bytes); void Write(ulong lba, ReadOnlySpan bytes); void Flush(); void AuthorizePartition(ulong start, ulong count); } public readonly record struct DiskPartition(int Number, ulong Start, ulong Count, bool DataPartition, string Name); public static class DiskPartitions { static uint U32(byte[] b,int at)=>BinaryPrimitives.ReadUInt32LittleEndian(b.AsSpan(at)); static ulong U64(byte[] b,int at)=>BinaryPrimitives.ReadUInt64LittleEndian(b.AsSpan(at)); static ulong Sectors(IPhysicalDisk disk)=>disk.SectorCount/(ulong)(disk.LogicalSectorBytes/512); static DiskPartition[] Normalize(IEnumerable partitions,IPhysicalDisk disk) {ulong unit=(ulong)(disk.LogicalSectorBytes/512);return partitions.Select(p=>p with{Start=checked(p.Start*unit),Count=checked(p.Count*unit)}).ToArray();} static byte[] Read(IPhysicalDisk disk,ulong lba,int count=1) { if(lba>=Sectors(disk)||(ulong)count>Sectors(disk)-lba)throw new IOException("Partition metadata leaves its disk."); byte[] bytes=new byte[checked(count*disk.LogicalSectorBytes)];disk.Read(lba*(ulong)(disk.LogicalSectorBytes/512),bytes);return bytes; } public static DiskPartition[] Read(IPhysicalDisk disk) { if(disk.LogicalSectorBytes is not(512 or 4096)||disk.SectorCount%(ulong)(disk.LogicalSectorBytes/512)!=0)throw new IOException("Unsupported disk sector geometry."); byte[] mbr=Read(disk,0);if(mbr[510]!=85||mbr[511]!=170)return []; var result=new List(); if(Enumerable.Range(0,4).Any(i=>mbr[446+i*16+4]==0xee))return Normalize(Gpt(disk),disk); var containers=new List<(ulong Start,ulong Count)>(); for(int i=0;i<4;i++) { int at=446+i*16;byte type=mbr[at+4];ulong start=U32(mbr,at+8),count=U32(mbr,at+12); if(type==0||count==0)continue;Bounds(disk,start,count); if(type is 5 or 15 or 133){containers.Add((start,count));continue;} result.Add(new(i+1,start,count,type is 7 or 6 or 11 or 12 or 14,"")); } Validate(result.Concat(containers.Select(c=>new DiskPartition(0,c.Start,c.Count,false,""))).ToList()); int number=5;var ebrSectors=new List(); foreach(var container in containers) { ulong current=container.Start;var seen=new HashSet(); for(int limit=0;limit<128;limit++) { if(!seen.Add(current)||current=container.Count)throw new IOException("Cyclic or invalid extended partition chain."); ebrSectors.Add(current); byte[] ebr=Read(disk,current);if(ebr[510]!=85||ebr[511]!=170)throw new IOException("Invalid extended partition signature."); byte type=ebr[450];ulong relative=U32(ebr,454),count=U32(ebr,458); if(type!=0&&count!=0) { if(relative==0||relative>ulong.MaxValue-current)throw new IOException("Invalid logical partition offset."); ulong start=current+relative;Bounds(disk,start,count); if(start=container.Count||count>container.Count-(start-container.Start))throw new IOException("Logical partition leaves its container."); result.Add(new(number++,start,count,type is 7 or 6 or 11 or 12 or 14,"")); } byte linkType=ebr[466];ulong link=U32(ebr,470); if(linkType==0&&link==0)break; if(linkType is not(5 or 15 or 133)||link==0||link>=container.Count||limit==127)throw new IOException("Invalid extended partition link."); current=container.Start+link; } } Validate(result); if(ebrSectors.Any(lba=>result.Any(p=>lba>=p.Start&&lba-p.Start128||size<128||size>1024||(size&7)!=0)throw new IOException("Unsupported GPT entry geometry."); int bytes=checked((int)(count*size)),tableSectors=(bytes+disk.LogicalSectorBytes-1)/disk.LogicalSectorBytes; byte[] table=Read(disk,U64(header,72),tableSectors); if(UsbWorkspaceFormat.Crc(table.AsSpan(0,bytes))!=U32(header,88))throw new IOException("GPT partition CRC mismatch."); byte[] backup=Read(disk,sectors-1);Header(backup,sectors-1,1,sectors); if(!header.AsSpan(56,16).SequenceEqual(backup.AsSpan(56,16))||U32(backup,80)!=count||U32(backup,84)!=size||U32(backup,88)!=U32(header,88)|| !Read(disk,U64(backup,72),tableSectors).AsSpan(0,bytes).SequenceEqual(table.AsSpan(0,bytes)))throw new IOException("GPT primary and backup disagree."); ulong first=U64(header,40),last=U64(header,48); ulong primaryTable=U64(header,72),backupTable=U64(backup,72); if(first<2+(ulong)tableSectors||last=sectors-1||first!=U64(backup,40)||last!=U64(backup,48)|| primaryTable<2||primaryTable>=first||(ulong)tableSectors>first-primaryTable|| backupTable<=last||backupTable>=sectors-1||(ulong)tableSectors>sectors-1-backupTable)throw new IOException("Invalid GPT usable range or table placement."); var result=new List();var ids=new HashSet(); for(int i=0;ilast)throw new IOException("Invalid GPT partition identity or range."); bool data=type==new Guid("ebd0a0a2-b9e5-4433-87c0-68b6b72699c7")&&(U64(table,at+48)&(1UL<<60))==0; result.Add(new(i+1,start,end-start+1,data,Encoding.Unicode.GetString(table,at+56,72).TrimEnd('\0'))); } Validate(result);return result.ToArray(); } static void Header(byte[] header,ulong current,ulong other,ulong sectors) { uint size=U32(header,12);if(!header.AsSpan(0,8).SequenceEqual("EFI PART"u8)||U32(header,8)!=65536||size<92||size>header.Length||U32(header,20)!=0||U64(header,24)!=current||U64(header,32)!=other||other>=sectors)throw new IOException("Invalid GPT header."); uint crc=U32(header,16);byte[] copy=header.AsSpan(0,(int)size).ToArray();copy.AsSpan(16,4).Clear(); if(UsbWorkspaceFormat.Crc(copy)!=crc)throw new IOException("GPT header CRC mismatch."); } static void Bounds(IPhysicalDisk disk,ulong start,ulong count) {if(start==0||count==0||start>=Sectors(disk)||count>Sectors(disk)-start)throw new IOException("Partition leaves its disk.");} static void Validate(List parts) { var sorted=parts.OrderBy(p=>p.Start).ToArray(); for(int i=1;i