diff --git a/proprietary/DiskImage/Filesystem/Fat32BootSector.cs b/proprietary/DiskImage/Filesystem/Fat32BootSector.cs
new file mode 100644
index 000000000..5829aac9e
--- /dev/null
+++ b/proprietary/DiskImage/Filesystem/Fat32BootSector.cs
@@ -0,0 +1,230 @@
+// Copyright (c) 2026 Duplicati Inc. All rights reserved.
+
+using System;
+using System.Buffers.Binary;
+using System.Text;
+
+namespace Duplicati.Proprietary.DiskImage.Filesystem;
+
+///
+/// Represents a parsed FAT32 Boot Sector (BIOS Parameter Block).
+/// This is a read-only struct that extracts geometry information from the boot sector.
+///
+public readonly record struct Fat32BootSector
+{
+ ///
+ /// The boot sector signature at offset 510 (0x55AA).
+ ///
+ private const ushort BootSectorSignature = 0x55AA;
+
+ ///
+ /// Offset to the boot sector signature.
+ ///
+ private const int SignatureOffset = 510;
+
+ ///
+ /// Offset to the filesystem type string (should contain "FAT32").
+ ///
+ private const int FilesystemTypeOffset = 0x52;
+
+ ///
+ /// Length of the filesystem type string (8 bytes).
+ ///
+ private const int FilesystemTypeLength = 8;
+
+ ///
+ /// Offset to BytesPerSector field.
+ ///
+ private const int BytesPerSectorOffset = 0x0B;
+
+ ///
+ /// Offset to SectorsPerCluster field.
+ ///
+ private const int SectorsPerClusterOffset = 0x0D;
+
+ ///
+ /// Offset to ReservedSectorCount field.
+ ///
+ private const int ReservedSectorCountOffset = 0x0E;
+
+ ///
+ /// Offset to NumberOfFats field.
+ ///
+ private const int NumberOfFatsOffset = 0x10;
+
+ ///
+ /// Offset to TotalSectors32 field.
+ ///
+ private const int TotalSectors32Offset = 0x20;
+
+ ///
+ /// Offset to FatSize32 field (sectors per FAT).
+ ///
+ private const int FatSize32Offset = 0x24;
+
+ ///
+ /// Offset to RootCluster field.
+ ///
+ private const int RootClusterOffset = 0x2C;
+
+ ///
+ /// Offset to FsInfoSector field.
+ ///
+ private const int FsInfoSectorOffset = 0x30;
+
+ ///
+ /// Minimum valid bytes per sector (512).
+ ///
+ private const ushort MinBytesPerSector = 512;
+
+ ///
+ /// Maximum valid bytes per sector (4096).
+ ///
+ private const ushort MaxBytesPerSector = 4096;
+
+ ///
+ /// Minimum valid sectors per cluster (1).
+ ///
+ private const byte MinSectorsPerCluster = 1;
+
+ ///
+ /// Maximum valid sectors per cluster (128).
+ ///
+ private const byte MaxSectorsPerCluster = 128;
+
+ ///
+ /// Minimum required buffer size (512 bytes for boot sector).
+ ///
+ private const int MinimumBufferSize = 512;
+
+ ///
+ /// Gets the number of bytes per sector (typically 512, 1024, 2048, or 4096).
+ ///
+ public ushort BytesPerSector { get; }
+
+ ///
+ /// Gets the number of sectors per cluster (power of 2: 1, 2, 4, 8, 16, 32, 64, 128).
+ ///
+ public byte SectorsPerCluster { get; }
+
+ ///
+ /// Gets the number of reserved sectors (includes boot sector and FSInfo).
+ ///
+ public ushort ReservedSectorCount { get; }
+
+ ///
+ /// Gets the number of FAT copies (typically 2).
+ ///
+ public byte NumberOfFats { get; }
+
+ ///
+ /// Gets the total number of sectors in the volume (32-bit value).
+ ///
+ public uint TotalSectors32 { get; }
+
+ ///
+ /// Gets the number of sectors per FAT.
+ ///
+ public uint FatSize32 { get; }
+
+ ///
+ /// Gets the root directory cluster number (typically 2).
+ ///
+ public uint RootCluster { get; }
+
+ ///
+ /// Gets the FSInfo sector number.
+ ///
+ public ushort FsInfoSector { get; }
+
+ ///
+ /// Gets the size of a cluster in bytes.
+ ///
+ public int ClusterSize => BytesPerSector * SectorsPerCluster;
+
+ ///
+ /// Gets the byte offset from the partition start to the first FAT.
+ ///
+ public long FatStartOffset => (long)ReservedSectorCount * BytesPerSector;
+
+ ///
+ /// Gets the byte offset from the partition start to the data region.
+ ///
+ public long DataStartOffset => (long)(ReservedSectorCount + NumberOfFats * FatSize32) * BytesPerSector;
+
+ ///
+ /// Gets the total number of data clusters in the volume.
+ ///
+ public uint TotalDataClusters => (uint)((TotalSectors32 - (ReservedSectorCount + NumberOfFats * FatSize32)) / SectorsPerCluster);
+
+ ///
+ /// Initializes a new instance of the struct by parsing the provided boot sector data.
+ ///
+ /// The boot sector data (must be at least 512 bytes).
+ /// Thrown when the buffer is too small or validation fails.
+ public Fat32BootSector(ReadOnlySpan bootSectorData)
+ {
+ if (bootSectorData.Length < MinimumBufferSize)
+ throw new ArgumentException($"Boot sector data must be at least {MinimumBufferSize} bytes.", nameof(bootSectorData));
+
+ // Validate boot sector signature (0x55AA at offset 510)
+ var signature = BinaryPrimitives.ReadUInt16LittleEndian(bootSectorData.Slice(SignatureOffset, 2));
+ if (signature != BootSectorSignature)
+ throw new ArgumentException($"Invalid boot sector signature: expected 0x{BootSectorSignature:X4}, got 0x{signature:X4}.", nameof(bootSectorData));
+
+ // Parse fields from the boot sector
+ BytesPerSector = BinaryPrimitives.ReadUInt16LittleEndian(bootSectorData.Slice(BytesPerSectorOffset, 2));
+ SectorsPerCluster = bootSectorData[SectorsPerClusterOffset];
+ ReservedSectorCount = BinaryPrimitives.ReadUInt16LittleEndian(bootSectorData.Slice(ReservedSectorCountOffset, 2));
+ NumberOfFats = bootSectorData[NumberOfFatsOffset];
+ TotalSectors32 = BinaryPrimitives.ReadUInt32LittleEndian(bootSectorData.Slice(TotalSectors32Offset, 4));
+ FatSize32 = BinaryPrimitives.ReadUInt32LittleEndian(bootSectorData.Slice(FatSize32Offset, 4));
+ RootCluster = BinaryPrimitives.ReadUInt32LittleEndian(bootSectorData.Slice(RootClusterOffset, 4));
+ FsInfoSector = BinaryPrimitives.ReadUInt16LittleEndian(bootSectorData.Slice(FsInfoSectorOffset, 2));
+
+ // Validate BytesPerSector is a power of 2 (512, 1024, 2048, or 4096)
+ if (!IsPowerOfTwo(BytesPerSector) || BytesPerSector < MinBytesPerSector || BytesPerSector > MaxBytesPerSector)
+ throw new ArgumentException($"Invalid BytesPerSector: {BytesPerSector}. Must be a power of 2 between {MinBytesPerSector} and {MaxBytesPerSector}.", nameof(bootSectorData));
+
+ // Validate SectorsPerCluster is a power of 2 (1, 2, 4, 8, 16, 32, 64, or 128)
+ if (!IsPowerOfTwo(SectorsPerCluster) || SectorsPerCluster < MinSectorsPerCluster || SectorsPerCluster > MaxSectorsPerCluster)
+ throw new ArgumentException($"Invalid SectorsPerCluster: {SectorsPerCluster}. Must be a power of 2 between {MinSectorsPerCluster} and {MaxSectorsPerCluster}.", nameof(bootSectorData));
+
+ // Validate NumberOfFats >= 1
+ if (NumberOfFats < 1)
+ throw new ArgumentException($"Invalid NumberOfFats: {NumberOfFats}. Must be at least 1.", nameof(bootSectorData));
+
+ // Validate the filesystem type string at offset 0x52 contains "FAT32"
+ var fsType = Encoding.ASCII.GetString(bootSectorData.Slice(FilesystemTypeOffset, FilesystemTypeLength));
+ if (!fsType.Contains("FAT32"))
+ throw new ArgumentException($"Invalid filesystem type: expected 'FAT32' in filesystem type string, got '{fsType}'.", nameof(bootSectorData));
+ }
+
+ ///
+ /// Converts a cluster number to its byte offset in the data region.
+ ///
+ /// The cluster number (clusters are 2-indexed).
+ /// The byte offset from the partition start.
+ /// Thrown when cluster number is less than 2.
+ public long ClusterToByteOffset(uint clusterNumber)
+ {
+ if (clusterNumber < 2)
+ throw new ArgumentException("Cluster numbers start at 2.", nameof(clusterNumber));
+
+ return DataStartOffset + (clusterNumber - 2) * ClusterSize;
+ }
+
+ ///
+ /// Checks if a value is a power of 2.
+ ///
+ /// The value to check.
+ /// True if the value is a power of 2; otherwise, false.
+ private static bool IsPowerOfTwo(ushort value) => value != 0 && (value & (value - 1)) == 0;
+
+ ///
+ /// Checks if a value is a power of 2.
+ ///
+ /// The value to check.
+ /// True if the value is a power of 2; otherwise, false.
+ private static bool IsPowerOfTwo(byte value) => value != 0 && (value & (value - 1)) == 0;
+}