From 1e7659f87c43603d1d45a8b7e1f592bbaeec1175 Mon Sep 17 00:00:00 2001 From: Natalia Portillo Date: Mon, 2 Feb 2026 15:45:01 +0000 Subject: [PATCH] [HFS+] Fix handling of wrapped volumes. --- Aaru.Filesystems/AppleHFSPlus/AppleHFSPlus.cs | 3 + Aaru.Filesystems/AppleHFSPlus/Catalog.cs | 9 +- Aaru.Filesystems/AppleHFSPlus/Extents.cs | 9 +- Aaru.Filesystems/AppleHFSPlus/File.cs | 9 +- Aaru.Filesystems/AppleHFSPlus/Mount.cs | 114 ++++++++++-------- Aaru.Filesystems/AppleHFSPlus/Xattr.cs | 9 +- 6 files changed, 96 insertions(+), 57 deletions(-) diff --git a/Aaru.Filesystems/AppleHFSPlus/AppleHFSPlus.cs b/Aaru.Filesystems/AppleHFSPlus/AppleHFSPlus.cs index 11d5925d5..f76aa6365 100644 --- a/Aaru.Filesystems/AppleHFSPlus/AppleHFSPlus.cs +++ b/Aaru.Filesystems/AppleHFSPlus/AppleHFSPlus.cs @@ -53,6 +53,9 @@ public sealed partial class AppleHFSPlus : IReadOnlyFilesystem /// Filesystem information FileSystemInfo _fileSystemInfo; + /// Offset in sectors to the start of HFS+ volume (for wrapped volumes, 0 for pure HFS+) + ulong _hfsPlusVolumeOffset; + /// Media image plugin reference IMediaImage _imagePlugin; diff --git a/Aaru.Filesystems/AppleHFSPlus/Catalog.cs b/Aaru.Filesystems/AppleHFSPlus/Catalog.cs index 18c0e9456..fe83a18f3 100644 --- a/Aaru.Filesystems/AppleHFSPlus/Catalog.cs +++ b/Aaru.Filesystems/AppleHFSPlus/Catalog.cs @@ -69,8 +69,13 @@ public sealed partial class AppleHFSPlus ulong blockOffset = (ulong)extent.startBlock * _volumeHeader.blockSize + offsetInExtent; // Convert to device sector address - ulong deviceSector = (_partitionStart * _sectorSize + blockOffset) / _sectorSize; - var byteOffset = (uint)((_partitionStart * _sectorSize + blockOffset) % _sectorSize); + // For wrapped volumes, blocks start after the HFS+ volume offset + // For pure HFS+, _hfsPlusVolumeOffset is 0 + ulong deviceSector = ((_partitionStart + _hfsPlusVolumeOffset) * _sectorSize + blockOffset) / + _sectorSize; + + var byteOffset = (uint)(((_partitionStart + _hfsPlusVolumeOffset) * _sectorSize + blockOffset) % + _sectorSize); uint sectorsToRead = (_catalogBTreeHeader.nodeSize + byteOffset + _sectorSize - 1) / _sectorSize; diff --git a/Aaru.Filesystems/AppleHFSPlus/Extents.cs b/Aaru.Filesystems/AppleHFSPlus/Extents.cs index d11794f79..1c89b97ac 100644 --- a/Aaru.Filesystems/AppleHFSPlus/Extents.cs +++ b/Aaru.Filesystems/AppleHFSPlus/Extents.cs @@ -271,8 +271,13 @@ public sealed partial class AppleHFSPlus ulong blockOffset = (ulong)extent.startBlock * _volumeHeader.blockSize + offsetInExtent; // Convert to device sector address - ulong deviceSector = (_partitionStart * _sectorSize + blockOffset) / _sectorSize; - var byteOffset = (uint)((_partitionStart * _sectorSize + blockOffset) % _sectorSize); + // For wrapped volumes, blocks start after the HFS+ volume offset + // For pure HFS+, _hfsPlusVolumeOffset is 0 + ulong deviceSector = ((_partitionStart + _hfsPlusVolumeOffset) * _sectorSize + blockOffset) / + _sectorSize; + + var byteOffset = (uint)(((_partitionStart + _hfsPlusVolumeOffset) * _sectorSize + blockOffset) % + _sectorSize); uint sectorsToRead = (_extentsFileHeader.nodeSize + byteOffset + _sectorSize - 1) / _sectorSize; diff --git a/Aaru.Filesystems/AppleHFSPlus/File.cs b/Aaru.Filesystems/AppleHFSPlus/File.cs index 1c346a545..f96d73438 100644 --- a/Aaru.Filesystems/AppleHFSPlus/File.cs +++ b/Aaru.Filesystems/AppleHFSPlus/File.cs @@ -226,8 +226,13 @@ public sealed partial class AppleHFSPlus ulong blockOffsetBytes = (ulong)extent.startBlock * _volumeHeader.blockSize + offsetInExtent; // Convert to device sector address - ulong deviceSector = (_partitionStart * _sectorSize + blockOffsetBytes) / _sectorSize; - var byteOffset = (uint)((_partitionStart * _sectorSize + blockOffsetBytes) % _sectorSize); + // For wrapped volumes, blocks start after the HFS+ volume offset + // For pure HFS+, _hfsPlusVolumeOffset is 0 + ulong deviceSector = ((_partitionStart + _hfsPlusVolumeOffset) * _sectorSize + blockOffsetBytes) / + _sectorSize; + + var byteOffset = (uint)(((_partitionStart + _hfsPlusVolumeOffset) * _sectorSize + blockOffsetBytes) % + _sectorSize); var sectorCount = (uint)((toReadFromExtent + byteOffset + _sectorSize - 1) / _sectorSize); diff --git a/Aaru.Filesystems/AppleHFSPlus/Mount.cs b/Aaru.Filesystems/AppleHFSPlus/Mount.cs index acf971c30..ac9c951d7 100644 --- a/Aaru.Filesystems/AppleHFSPlus/Mount.cs +++ b/Aaru.Filesystems/AppleHFSPlus/Mount.cs @@ -175,11 +175,10 @@ public sealed partial class AppleHFSPlus // 1. Offset 0x0400 (1024 bytes) for pure HFS+ volumes // 2. At an offset calculated from the HFS MDB for HFS+ wrapped in HFS - // First, try to read the HFS MDB to check if this is a wrapped HFS+ volume - ulong hfspOffset = 0; // Offset in 512-byte sectors + // Read 0x800 (2048) bytes to cover both HFS MDB and HFS+ VH locations + uint sectorsToRead = 0x800 / _sectorSize; - // Read sector 2 (512-byte sectors) which contains the HFS MDB or HFS+ VH - uint sectorsToRead = (0x400 + 512 + _sectorSize - 1) / _sectorSize; + if(0x800 % _sectorSize > 0) sectorsToRead++; ErrorNumber errno = _imagePlugin.ReadSectors(_partitionStart, false, @@ -189,72 +188,86 @@ public sealed partial class AppleHFSPlus if(errno != ErrorNumber.NoError) return errno; - if(vhSectors.Length < 0x400 + 512) return ErrorNumber.InvalidArgument; + if(vhSectors.Length < 0x800) return ErrorNumber.InvalidArgument; // Check if there's an HFS MDB at offset 0x0400 - var hfsMdbSig = BigEndianBitConverter.ToUInt16(vhSectors, 0x400); + var drSigWord = BigEndianBitConverter.ToUInt16(vhSectors, 0x400); - if(hfsMdbSig == AppleCommon.HFS_MAGIC) + if(drSigWord == AppleCommon.HFS_MAGIC) { // This is an HFS wrapper around HFS+ - // Read the embedded HFS+ location from the HFS MDB - // MDB offsets: - // 0x414: drAlBlkSiz (allocation block size) - // 0x41C: drAlBlSt (first allocation block start, in 512-byte sectors) - // 0x47E: xdrStABN (start allocation block number of embedded HFS+) + // Check for embedded HFS+ signature at 0x47C + drSigWord = BigEndianBitConverter.ToUInt16(vhSectors, 0x47C); - var drAlBlkSiz = BigEndianBitConverter.ToUInt32(vhSectors, 0x400 + 0x14); - var drAlBlSt = BigEndianBitConverter.ToUInt16(vhSectors, 0x400 + 0x1C); - var xdrStABN = BigEndianBitConverter.ToUInt16(vhSectors, 0x400 + 0x7E); + if(drSigWord == AppleCommon.HFSP_MAGIC) // "H+" + { + // Read the embedded HFS+ location from the HFS MDB + // MDB offsets (absolute from partition start): + // 0x414: drAlBlkSiz (allocation block size) + // 0x41C: drAlBlSt (first allocation block start, in 512-byte sectors) + // 0x47E: xdrStABN (start allocation block number of embedded HFS+) - // Calculate the offset to the embedded HFS+ volume header - // offset = drAlBlSt + (xdrStABN * drAlBlkSiz) - // This gives us the byte offset, which we need to convert to sector offset - ulong byteOffset = (ulong)drAlBlSt * 512 + (ulong)xdrStABN * drAlBlkSiz; - hfspOffset = byteOffset / _sectorSize; + var drAlBlkSiz = BigEndianBitConverter.ToUInt32(vhSectors, 0x414); + var drAlBlSt = BigEndianBitConverter.ToUInt16(vhSectors, 0x41C); + var xdrStABN = BigEndianBitConverter.ToUInt16(vhSectors, 0x47E); - AaruLogging.Debug(MODULE_NAME, - $"Found HFS wrapper: drAlBlSt={drAlBlSt}, xdrStABN={xdrStABN}, drAlBlkSiz={drAlBlkSiz}, hfspOffset={hfspOffset} sectors"); + // Calculate the offset to the embedded HFS+ volume header + // offset (in bytes) = (drAlBlSt * 512) + (xdrStABN * drAlBlkSiz) + // Then convert to sector offset (matching GetInformation logic) + ulong byteOffset = (ulong)drAlBlSt * 512 + (ulong)xdrStABN * drAlBlkSiz; + ulong hfspOffset = byteOffset / _sectorSize; - // Now read the HFS+ VH from the calculated offset - sectorsToRead = (uint)((byteOffset % _sectorSize + 512 + _sectorSize - 1) / _sectorSize); + _hfsPlusVolumeOffset = hfspOffset; - errno = _imagePlugin.ReadSectors(_partitionStart + hfspOffset, false, sectorsToRead, out vhSectors, out _); + AaruLogging.Debug(MODULE_NAME, + $"Found HFS wrapper: drAlBlSt={drAlBlSt}, xdrStABN={xdrStABN}, drAlBlkSiz={drAlBlkSiz}, VH at sector offset {hfspOffset}"); - if(errno != ErrorNumber.NoError) return errno; + // Now read the HFS+ VH from the calculated sector offset + // Read the same amount of data (0x800) from the new offset + sectorsToRead = 0x800 / _sectorSize; - var vhByteOffset = (uint)(byteOffset % _sectorSize); + if(0x800 % _sectorSize > 0) sectorsToRead++; - if(vhSectors.Length < vhByteOffset + 512) return ErrorNumber.InvalidArgument; + errno = _imagePlugin.ReadSectors(_partitionStart + hfspOffset, + false, + sectorsToRead, + out vhSectors, + out _); - // Parse the Volume Header structure - _volumeHeader = Marshal.ByteArrayToStructureBigEndian(vhSectors, - (int)vhByteOffset, - System.Runtime.InteropServices - .Marshal - .SizeOf(typeof(VolumeHeader))); + if(errno != ErrorNumber.NoError) return errno; + + if(vhSectors.Length < 0x400 + 512) return ErrorNumber.InvalidArgument; + + // Parse the Volume Header structure from offset 0x400 (as in GetInformation) + _volumeHeader = Marshal.ByteArrayToStructureBigEndian(vhSectors, + 0x400, + System.Runtime.InteropServices.Marshal.SizeOf(typeof(VolumeHeader))); + } + else + { + // HFS wrapper but no embedded HFS+ found + return ErrorNumber.InvalidArgument; + } } else { // Pure HFS+ volume, VH is at offset 0x0400 from partition start - uint vhByteOffset = 0x400 % _sectorSize; - uint vhSectorOffset = 0x400 / _sectorSize; + // Read 0x800 bytes from partition start, then parse from offset 0x400 + _hfsPlusVolumeOffset = 0; // No wrapper offset for pure HFS+ - sectorsToRead = (vhByteOffset + 512 + _sectorSize - 1) / _sectorSize; + sectorsToRead = 0x800 / _sectorSize; - errno = _imagePlugin.ReadSectors(_partitionStart + vhSectorOffset, - false, - sectorsToRead, - out vhSectors, - out _); + if(0x800 % _sectorSize > 0) sectorsToRead++; + + errno = _imagePlugin.ReadSectors(_partitionStart, false, sectorsToRead, out vhSectors, out _); if(errno != ErrorNumber.NoError) return errno; - if(vhSectors.Length < vhByteOffset + 512) return ErrorNumber.InvalidArgument; + if(vhSectors.Length < 0x400 + 512) return ErrorNumber.InvalidArgument; - // Parse the Volume Header structure + // Parse the Volume Header structure from offset 0x400 _volumeHeader = Marshal.ByteArrayToStructureBigEndian(vhSectors, - (int)vhByteOffset, + 0x400, System.Runtime.InteropServices .Marshal .SizeOf(typeof(VolumeHeader))); @@ -303,8 +316,13 @@ public sealed partial class AppleHFSPlus ulong catalogFileOffset = firstExtent.startBlock * _volumeHeader.blockSize; // Convert to device sector address - ulong deviceSector = (_partitionStart * _sectorSize + catalogFileOffset) / _sectorSize; - var byteOffset = (uint)((_partitionStart * _sectorSize + catalogFileOffset) % _sectorSize); + // For wrapped volumes, blocks start after the HFS+ volume header offset + // For pure HFS+, _hfsPlusVolumeOffset is 0 + ulong deviceSector = (_partitionStart + _hfsPlusVolumeOffset) * _sectorSize + catalogFileOffset; + deviceSector /= _sectorSize; + + var byteOffset = + (uint)(((_partitionStart + _hfsPlusVolumeOffset) * _sectorSize + catalogFileOffset) % _sectorSize); // Read the header node (first node in the catalog B-Tree) // For now, we don't know the node size yet, so read a reasonable amount @@ -357,9 +375,7 @@ public sealed partial class AppleHFSPlus _isCaseSensitive = _catalogBTreeHeader.keyCompareType == kHFSBinaryCompare; if(_volumeHeader.signature == AppleCommon.HFSX_MAGIC) - { AaruLogging.Debug(MODULE_NAME, $"HFSX volume: case-sensitive={_isCaseSensitive}"); - } return ErrorNumber.NoError; } diff --git a/Aaru.Filesystems/AppleHFSPlus/Xattr.cs b/Aaru.Filesystems/AppleHFSPlus/Xattr.cs index 29fe45506..9b321bcc3 100644 --- a/Aaru.Filesystems/AppleHFSPlus/Xattr.cs +++ b/Aaru.Filesystems/AppleHFSPlus/Xattr.cs @@ -291,8 +291,13 @@ public sealed partial class AppleHFSPlus ulong blockOffsetBytes = (ulong)extent.startBlock * _volumeHeader.blockSize; // Convert to device sector address - ulong deviceSector = (_partitionStart * _sectorSize + blockOffsetBytes) / _sectorSize; - var byteOffset = (uint)((_partitionStart * _sectorSize + blockOffsetBytes) % _sectorSize); + // For wrapped volumes, blocks start after the HFS+ volume offset + // For pure HFS+, _hfsPlusVolumeOffset is 0 + ulong deviceSector = ((_partitionStart + _hfsPlusVolumeOffset) * _sectorSize + blockOffsetBytes) / + _sectorSize; + + var byteOffset = (uint)(((_partitionStart + _hfsPlusVolumeOffset) * _sectorSize + blockOffsetBytes) % + _sectorSize); var sectorsToRead = (uint)((toRead + byteOffset + _sectorSize - 1) / _sectorSize);