mirror of
https://github.com/aaru-dps/Aaru.git
synced 2026-02-04 08:54:40 +00:00
[exFAT] Implement mount.
This commit is contained in:
98
Aaru.Filesystems/exFAT/Clusters.cs
Normal file
98
Aaru.Filesystems/exFAT/Clusters.cs
Normal file
@@ -0,0 +1,98 @@
|
||||
// /***************************************************************************
|
||||
// Aaru Data Preservation Suite
|
||||
// ----------------------------------------------------------------------------
|
||||
//
|
||||
// Filename : Clusters.cs
|
||||
// Author(s) : Natalia Portillo <claunia@claunia.com>
|
||||
//
|
||||
// Component : Microsoft exFAT filesystem plugin.
|
||||
//
|
||||
// --[ 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-2026 Natalia Portillo
|
||||
// ****************************************************************************/
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using Aaru.CommonTypes.Enums;
|
||||
|
||||
namespace Aaru.Filesystems;
|
||||
|
||||
// Information from https://github.com/MicrosoftDocs/win32/blob/docs/desktop-src/FileIO/exfat-specification.md
|
||||
/// <inheritdoc />
|
||||
public sealed partial class exFAT
|
||||
{
|
||||
/// <summary>Reads a cluster chain from the image.</summary>
|
||||
/// <param name="firstCluster">First cluster of the chain.</param>
|
||||
/// <param name="isContiguous">If true, the chain is contiguous and FAT is not used.</param>
|
||||
/// <param name="expectedLength">Expected length in bytes (used for contiguous allocations).</param>
|
||||
/// <returns>Byte array containing the cluster chain data, or null on error.</returns>
|
||||
byte[] ReadClusterChain(uint firstCluster, bool isContiguous = false, ulong expectedLength = 0)
|
||||
{
|
||||
if(firstCluster < 2 || firstCluster > _clusterCount + 1) return null;
|
||||
|
||||
var clusters = new List<uint>();
|
||||
|
||||
if(isContiguous && expectedLength > 0)
|
||||
{
|
||||
// For contiguous allocations, calculate the number of clusters needed
|
||||
var clusterCountNeeded = (uint)((expectedLength + _bytesPerCluster - 1) / _bytesPerCluster);
|
||||
|
||||
for(uint i = 0; i < clusterCountNeeded; i++) clusters.Add(firstCluster + i);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Follow the FAT chain
|
||||
uint currentCluster = firstCluster;
|
||||
|
||||
while(currentCluster >= 2 && currentCluster <= _clusterCount + 1)
|
||||
{
|
||||
clusters.Add(currentCluster);
|
||||
|
||||
// Prevent infinite loops
|
||||
if(clusters.Count > _clusterCount) break;
|
||||
|
||||
uint nextCluster = _fatEntries[currentCluster];
|
||||
|
||||
// End of chain markers
|
||||
if(nextCluster >= 0xFFFFFFF8) break;
|
||||
|
||||
// Bad cluster marker
|
||||
if(nextCluster == 0xFFFFFFF7) break;
|
||||
|
||||
currentCluster = nextCluster;
|
||||
}
|
||||
}
|
||||
|
||||
if(clusters.Count == 0) return null;
|
||||
|
||||
var ms = new MemoryStream();
|
||||
|
||||
foreach(uint cluster in clusters)
|
||||
{
|
||||
ulong sector = _clusterHeapOffset + (ulong)(cluster - 2) * _sectorsPerCluster;
|
||||
|
||||
ErrorNumber errno = _image.ReadSectors(sector, false, _sectorsPerCluster, out byte[] buffer, out _);
|
||||
|
||||
if(errno != ErrorNumber.NoError) return null;
|
||||
|
||||
ms.Write(buffer, 0, buffer.Length);
|
||||
}
|
||||
|
||||
return ms.ToArray();
|
||||
}
|
||||
}
|
||||
161
Aaru.Filesystems/exFAT/Dir.cs
Normal file
161
Aaru.Filesystems/exFAT/Dir.cs
Normal file
@@ -0,0 +1,161 @@
|
||||
// /***************************************************************************
|
||||
// Aaru Data Preservation Suite
|
||||
// ----------------------------------------------------------------------------
|
||||
//
|
||||
// Filename : Dir.cs
|
||||
// Author(s) : Natalia Portillo <claunia@claunia.com>
|
||||
//
|
||||
// Component : Microsoft exFAT filesystem plugin.
|
||||
//
|
||||
// --[ 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-2026 Natalia Portillo
|
||||
// ****************************************************************************/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using Marshal = Aaru.Helpers.Marshal;
|
||||
|
||||
namespace Aaru.Filesystems;
|
||||
|
||||
// Information from https://github.com/MicrosoftDocs/win32/blob/docs/desktop-src/FileIO/exfat-specification.md
|
||||
/// <inheritdoc />
|
||||
public sealed partial class exFAT
|
||||
{
|
||||
/// <summary>Parses a directory and populates the cache.</summary>
|
||||
/// <param name="directoryData">Raw directory data.</param>
|
||||
/// <param name="cache">Cache to populate with parsed entries.</param>
|
||||
void ParseDirectory(byte[] directoryData, Dictionary<string, CompleteDirectoryEntry> cache)
|
||||
{
|
||||
const int ENTRY_SIZE = 32;
|
||||
|
||||
for(var i = 0; i < directoryData.Length; i += ENTRY_SIZE)
|
||||
{
|
||||
byte entryType = directoryData[i];
|
||||
|
||||
// End of directory
|
||||
if(entryType == 0x00) break;
|
||||
|
||||
// Unused entry (deleted or available)
|
||||
if(entryType < 0x80) continue;
|
||||
|
||||
// Volume Label (0x83)
|
||||
if(entryType == (byte)EntryType.VolumeLabel)
|
||||
{
|
||||
VolumeLabelDirectoryEntry volumeLabelEntry =
|
||||
Marshal.ByteArrayToStructureLittleEndian<VolumeLabelDirectoryEntry>(directoryData, i, ENTRY_SIZE);
|
||||
|
||||
if(volumeLabelEntry.CharacterCount > 0 && volumeLabelEntry.CharacterCount <= 11)
|
||||
{
|
||||
Metadata.VolumeName =
|
||||
Encoding.Unicode.GetString(volumeLabelEntry.VolumeLabel,
|
||||
0,
|
||||
volumeLabelEntry.CharacterCount * 2);
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// Allocation Bitmap (0x81) - skip
|
||||
if(entryType == (byte)EntryType.AllocationBitmap) continue;
|
||||
|
||||
// Up-case Table (0x82) - skip
|
||||
if(entryType == (byte)EntryType.UpcaseTable) continue;
|
||||
|
||||
// Volume GUID (0xA0) - skip
|
||||
if(entryType == (byte)EntryType.VolumeGuid) continue;
|
||||
|
||||
// TexFAT Padding (0xA1) - skip
|
||||
if(entryType == (byte)EntryType.TexFatPadding) continue;
|
||||
|
||||
// File entry (0x85)
|
||||
if(entryType == (byte)EntryType.File)
|
||||
{
|
||||
FileDirectoryEntry fileEntry =
|
||||
Marshal.ByteArrayToStructureLittleEndian<FileDirectoryEntry>(directoryData, i, ENTRY_SIZE);
|
||||
|
||||
if(fileEntry.SecondaryCount < 2) continue; // Need at least Stream Extension and one File Name entry
|
||||
|
||||
// Check we have enough data for all secondary entries
|
||||
if(i + (fileEntry.SecondaryCount + 1) * ENTRY_SIZE > directoryData.Length) continue;
|
||||
|
||||
// Read Stream Extension entry (must be immediately after File entry)
|
||||
int streamOffset = i + ENTRY_SIZE;
|
||||
byte streamEntryType = directoryData[streamOffset];
|
||||
|
||||
if(streamEntryType != (byte)EntryType.StreamExtension) continue;
|
||||
|
||||
StreamExtensionDirectoryEntry streamEntry =
|
||||
Marshal.ByteArrayToStructureLittleEndian<StreamExtensionDirectoryEntry>(directoryData,
|
||||
streamOffset,
|
||||
ENTRY_SIZE);
|
||||
|
||||
// Read File Name entries
|
||||
var fileNameBuilder = new StringBuilder();
|
||||
int fileNameEntriesNeeded = (streamEntry.NameLength + 14) / 15; // 15 chars per entry
|
||||
var fileNameEntriesRead = 0;
|
||||
|
||||
for(var j = 2; j <= fileEntry.SecondaryCount && fileNameEntriesRead < fileNameEntriesNeeded; j++)
|
||||
{
|
||||
int nameOffset = i + j * ENTRY_SIZE;
|
||||
byte nameEntryType = directoryData[nameOffset];
|
||||
|
||||
if(nameEntryType != (byte)EntryType.FileName) continue;
|
||||
|
||||
FileNameDirectoryEntry nameEntry =
|
||||
Marshal.ByteArrayToStructureLittleEndian<FileNameDirectoryEntry>(directoryData,
|
||||
nameOffset,
|
||||
ENTRY_SIZE);
|
||||
|
||||
// Each File Name entry contains up to 15 Unicode characters (30 bytes)
|
||||
int charsToRead = Math.Min(15, streamEntry.NameLength - fileNameEntriesRead * 15);
|
||||
|
||||
if(charsToRead > 0)
|
||||
fileNameBuilder.Append(Encoding.Unicode.GetString(nameEntry.FileName, 0, charsToRead * 2));
|
||||
|
||||
fileNameEntriesRead++;
|
||||
}
|
||||
|
||||
var fileName = fileNameBuilder.ToString();
|
||||
|
||||
if(string.IsNullOrEmpty(fileName)) continue;
|
||||
|
||||
// Skip . and .. entries
|
||||
if(fileName == "." || fileName == "..") continue;
|
||||
|
||||
// Create complete entry
|
||||
var completeEntry = new CompleteDirectoryEntry
|
||||
{
|
||||
FileEntry = fileEntry,
|
||||
StreamEntry = streamEntry,
|
||||
FileName = fileName,
|
||||
FirstCluster = streamEntry.FirstCluster,
|
||||
DataLength = streamEntry.DataLength,
|
||||
ValidDataLength = streamEntry.ValidDataLength,
|
||||
IsContiguous = (streamEntry.GeneralSecondaryFlags & (byte)GeneralSecondaryFlags.NoFatChain) != 0,
|
||||
IsDirectory = fileEntry.FileAttributes.HasFlag(FileAttributes.Directory)
|
||||
};
|
||||
|
||||
cache[fileName] = completeEntry;
|
||||
|
||||
// Skip the secondary entries we've processed
|
||||
i += fileEntry.SecondaryCount * ENTRY_SIZE;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
161
Aaru.Filesystems/exFAT/Mount.cs
Normal file
161
Aaru.Filesystems/exFAT/Mount.cs
Normal file
@@ -0,0 +1,161 @@
|
||||
// /***************************************************************************
|
||||
// Aaru Data Preservation Suite
|
||||
// ----------------------------------------------------------------------------
|
||||
//
|
||||
// Filename : Mount.cs
|
||||
// Author(s) : Natalia Portillo <claunia@claunia.com>
|
||||
//
|
||||
// Component : Microsoft exFAT filesystem plugin.
|
||||
//
|
||||
// --[ 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-2026 Natalia Portillo
|
||||
// ****************************************************************************/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using Aaru.CommonTypes.AaruMetadata;
|
||||
using Aaru.CommonTypes.Enums;
|
||||
using Aaru.CommonTypes.Interfaces;
|
||||
using Aaru.Logging;
|
||||
using FileSystemInfo = Aaru.CommonTypes.Structs.FileSystemInfo;
|
||||
using Marshal = Aaru.Helpers.Marshal;
|
||||
using Partition = Aaru.CommonTypes.Partition;
|
||||
|
||||
namespace Aaru.Filesystems;
|
||||
|
||||
// Information from https://github.com/MicrosoftDocs/win32/blob/docs/desktop-src/FileIO/exfat-specification.md
|
||||
/// <inheritdoc />
|
||||
public sealed partial class exFAT
|
||||
{
|
||||
#region IReadOnlyFilesystem Members
|
||||
|
||||
/// <inheritdoc />
|
||||
public ErrorNumber Mount(IMediaImage imagePlugin, Partition partition, Encoding encoding,
|
||||
Dictionary<string, string> options, string @namespace)
|
||||
{
|
||||
Metadata = new FileSystem();
|
||||
|
||||
options ??= GetDefaultOptions();
|
||||
|
||||
if(options.TryGetValue("debug", out string debugString)) bool.TryParse(debugString, out _debug);
|
||||
|
||||
AaruLogging.Debug(MODULE_NAME, Localization.Reading_BPB);
|
||||
|
||||
ErrorNumber errno = imagePlugin.ReadSector(0 + partition.Start, false, out byte[] vbrSector, out _);
|
||||
|
||||
if(errno != ErrorNumber.NoError) return errno;
|
||||
|
||||
if(vbrSector.Length < 512) return ErrorNumber.InvalidArgument;
|
||||
|
||||
VolumeBootRecord vbr = Marshal.ByteArrayToStructureLittleEndian<VolumeBootRecord>(vbrSector);
|
||||
|
||||
// Validate signature
|
||||
if(!_signature.SequenceEqual(vbr.FileSystemName)) return ErrorNumber.InvalidArgument;
|
||||
|
||||
// Validate boot signature
|
||||
if(vbr.BootSignature != BOOT_SIGNATURE) return ErrorNumber.InvalidArgument;
|
||||
|
||||
// Get filesystem parameters
|
||||
_bytesPerSector = (uint)(1 << vbr.BytesPerSectorShift);
|
||||
_sectorsPerCluster = (uint)(1 << vbr.SectorsPerClusterShift);
|
||||
_bytesPerCluster = _bytesPerSector * _sectorsPerCluster;
|
||||
_fatFirstSector = partition.Start + vbr.FatOffset;
|
||||
_clusterHeapOffset = partition.Start + vbr.ClusterHeapOffset;
|
||||
_clusterCount = vbr.ClusterCount;
|
||||
_firstClusterOfRootDirectory = vbr.FirstClusterOfRootDirectory;
|
||||
|
||||
// Determine which FAT to use
|
||||
_useFirstFat = !vbr.VolumeFlags.HasFlag(VolumeFlags.ActiveFat);
|
||||
|
||||
AaruLogging.Debug(MODULE_NAME, "Bytes per sector: {0}", _bytesPerSector);
|
||||
AaruLogging.Debug(MODULE_NAME, "Sectors per cluster: {0}", _sectorsPerCluster);
|
||||
AaruLogging.Debug(MODULE_NAME, "Bytes per cluster: {0}", _bytesPerCluster);
|
||||
AaruLogging.Debug(MODULE_NAME, "FAT first sector: {0}", _fatFirstSector);
|
||||
AaruLogging.Debug(MODULE_NAME, "Cluster heap offset: {0}", _clusterHeapOffset);
|
||||
AaruLogging.Debug(MODULE_NAME, "Cluster count: {0}", _clusterCount);
|
||||
AaruLogging.Debug(MODULE_NAME, "Root directory first cluster: {0}", _firstClusterOfRootDirectory);
|
||||
AaruLogging.Debug(MODULE_NAME, "Using first FAT: {0}", _useFirstFat);
|
||||
|
||||
// Validate root directory cluster
|
||||
if(_firstClusterOfRootDirectory < 2 || _firstClusterOfRootDirectory > _clusterCount + 1)
|
||||
return ErrorNumber.InvalidArgument;
|
||||
|
||||
// Read FAT
|
||||
AaruLogging.Debug(MODULE_NAME, "Reading FAT");
|
||||
|
||||
ulong fatSector = _useFirstFat ? _fatFirstSector : _fatFirstSector + vbr.FatLength;
|
||||
|
||||
errno = imagePlugin.ReadSectors(fatSector, false, vbr.FatLength, out byte[] fatBytes, out _);
|
||||
|
||||
if(errno != ErrorNumber.NoError) return errno;
|
||||
|
||||
// Cast FAT bytes to uint array (exFAT uses 32-bit FAT entries)
|
||||
_fatEntries = new uint[_clusterCount + 2];
|
||||
Buffer.BlockCopy(fatBytes, 0, _fatEntries, 0, Math.Min(fatBytes.Length, (int)((_clusterCount + 2) * 4)));
|
||||
|
||||
// Validate FAT entries 0 and 1
|
||||
if((_fatEntries[0] & 0xFFFFFFF0) != 0xFFFFFFF0)
|
||||
AaruLogging.Debug(MODULE_NAME, "FAT entry 0 is not valid: {0:X8}", _fatEntries[0]);
|
||||
|
||||
if(_fatEntries[1] != 0xFFFFFFFF)
|
||||
AaruLogging.Debug(MODULE_NAME, "FAT entry 1 is not valid: {0:X8}", _fatEntries[1]);
|
||||
|
||||
// Store image reference (needed by ReadClusterChain)
|
||||
_image = imagePlugin;
|
||||
|
||||
// Read root directory
|
||||
AaruLogging.Debug(MODULE_NAME, "Reading root directory");
|
||||
|
||||
byte[] rootDirectory = ReadClusterChain(_firstClusterOfRootDirectory);
|
||||
|
||||
if(rootDirectory is null) return ErrorNumber.InvalidArgument;
|
||||
|
||||
// Initialize caches
|
||||
_rootDirectoryCache = new Dictionary<string, CompleteDirectoryEntry>();
|
||||
_directoryCache = new Dictionary<string, Dictionary<string, CompleteDirectoryEntry>>();
|
||||
|
||||
// Parse root directory
|
||||
ParseDirectory(rootDirectory, _rootDirectoryCache);
|
||||
|
||||
// Setup filesystem information
|
||||
_statfs = new FileSystemInfo
|
||||
{
|
||||
FilenameLength = 255,
|
||||
Files = (ulong)_rootDirectoryCache.Count,
|
||||
FreeFiles = 0,
|
||||
PluginId = Id,
|
||||
FreeBlocks = 0, // Would require traversing the FAT
|
||||
Blocks = _clusterCount,
|
||||
Type = FS_TYPE
|
||||
};
|
||||
|
||||
// Setup metadata
|
||||
Metadata.Type = FS_TYPE;
|
||||
Metadata.ClusterSize = _bytesPerCluster;
|
||||
Metadata.Clusters = _clusterCount;
|
||||
Metadata.VolumeSerial = $"{vbr.VolumeSerialNumber:X8}";
|
||||
Metadata.Dirty = vbr.VolumeFlags.HasFlag(VolumeFlags.VolumeDirty);
|
||||
|
||||
_mounted = true;
|
||||
|
||||
return ErrorNumber.NoError;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -28,13 +28,9 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Text;
|
||||
using Aaru.CommonTypes.AaruMetadata;
|
||||
using Aaru.CommonTypes.Enums;
|
||||
using Aaru.CommonTypes.Interfaces;
|
||||
using Aaru.CommonTypes.Structs;
|
||||
using Partition = Aaru.CommonTypes.Partition;
|
||||
|
||||
namespace Aaru.Filesystems;
|
||||
|
||||
@@ -42,22 +38,12 @@ namespace Aaru.Filesystems;
|
||||
/// <inheritdoc />
|
||||
public sealed partial class exFAT
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public FileSystem Metadata { get; }
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<(string name, Type type, string description)> SupportedOptions { get; }
|
||||
/// <inheritdoc />
|
||||
public Dictionary<string, string> Namespaces { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public ErrorNumber Mount(IMediaImage imagePlugin, Partition partition, Encoding encoding, Dictionary<string, string> options,
|
||||
string @namespace) => throw new NotImplementedException();
|
||||
|
||||
/// <inheritdoc />
|
||||
public ErrorNumber Unmount() => throw new NotImplementedException();
|
||||
|
||||
/// <inheritdoc />
|
||||
public ErrorNumber GetAttributes(string path, out CommonTypes.Structs.FileAttributes attributes) => throw new NotImplementedException();
|
||||
public ErrorNumber GetAttributes(string path, out CommonTypes.Structs.FileAttributes attributes) =>
|
||||
throw new NotImplementedException();
|
||||
|
||||
/// <inheritdoc />
|
||||
public ErrorNumber ListXAttr(string path, out List<string> xattrs) => throw new NotImplementedException();
|
||||
@@ -81,7 +67,8 @@ public sealed partial class exFAT
|
||||
public ErrorNumber CloseFile(IFileNode node) => throw new NotImplementedException();
|
||||
|
||||
/// <inheritdoc />
|
||||
public ErrorNumber ReadFile(IFileNode node, long length, byte[] buffer, out long read) => throw new NotImplementedException();
|
||||
public ErrorNumber ReadFile(IFileNode node, long length, byte[] buffer, out long read) =>
|
||||
throw new NotImplementedException();
|
||||
|
||||
/// <inheritdoc />
|
||||
public ErrorNumber OpenDir(string path, out IDirNode node) => throw new NotImplementedException();
|
||||
|
||||
@@ -27,8 +27,11 @@
|
||||
// ****************************************************************************/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Aaru.CommonTypes.AaruMetadata;
|
||||
using Aaru.CommonTypes.Interfaces;
|
||||
using FileSystemInfo = Aaru.CommonTypes.Structs.FileSystemInfo;
|
||||
|
||||
namespace Aaru.Filesystems;
|
||||
|
||||
@@ -40,7 +43,38 @@ namespace Aaru.Filesystems;
|
||||
// ReSharper disable once InconsistentNaming
|
||||
public sealed partial class exFAT : IReadOnlyFilesystem
|
||||
{
|
||||
#region IFilesystem Members
|
||||
const string MODULE_NAME = "exFAT plugin";
|
||||
|
||||
// Cached data
|
||||
uint[] _fatEntries;
|
||||
Dictionary<string, CompleteDirectoryEntry> _rootDirectoryCache;
|
||||
Dictionary<string, Dictionary<string, CompleteDirectoryEntry>> _directoryCache;
|
||||
|
||||
// File system parameters
|
||||
IMediaImage _image;
|
||||
bool _mounted;
|
||||
uint _bytesPerSector;
|
||||
uint _sectorsPerCluster;
|
||||
uint _bytesPerCluster;
|
||||
ulong _fatFirstSector;
|
||||
ulong _clusterHeapOffset;
|
||||
uint _clusterCount;
|
||||
uint _firstClusterOfRootDirectory;
|
||||
FileSystemInfo _statfs;
|
||||
bool _useFirstFat;
|
||||
bool _debug;
|
||||
|
||||
static Dictionary<string, string> GetDefaultOptions() => new()
|
||||
{
|
||||
{
|
||||
"debug", false.ToString()
|
||||
}
|
||||
};
|
||||
|
||||
#region IReadOnlyFilesystem Members
|
||||
|
||||
/// <inheritdoc />
|
||||
public FileSystem Metadata { get; private set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public string Name => Localization.exFAT_Name;
|
||||
@@ -51,5 +85,50 @@ public sealed partial class exFAT : IReadOnlyFilesystem
|
||||
/// <inheritdoc />
|
||||
public string Author => Authors.NataliaPortillo;
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<(string name, Type type, string description)> SupportedOptions => [];
|
||||
|
||||
/// <inheritdoc />
|
||||
public Dictionary<string, string> Namespaces => new()
|
||||
{
|
||||
{
|
||||
"exfat", "exFAT default namespace"
|
||||
}
|
||||
};
|
||||
|
||||
#endregion
|
||||
|
||||
#region Nested type: CompleteDirectoryEntry
|
||||
|
||||
/// <summary>Represents a complete directory entry set with file entry, stream extension, and file name.</summary>
|
||||
sealed class CompleteDirectoryEntry
|
||||
{
|
||||
/// <summary>Data length in bytes.</summary>
|
||||
public ulong DataLength;
|
||||
/// <summary>The File directory entry.</summary>
|
||||
public FileDirectoryEntry FileEntry;
|
||||
|
||||
/// <summary>The complete file name assembled from File Name directory entries.</summary>
|
||||
public string FileName;
|
||||
|
||||
/// <summary>First cluster of the file data.</summary>
|
||||
public uint FirstCluster;
|
||||
|
||||
/// <summary>Whether the allocation is contiguous (NoFatChain).</summary>
|
||||
public bool IsContiguous;
|
||||
|
||||
/// <summary>Whether this entry is a directory.</summary>
|
||||
public bool IsDirectory;
|
||||
|
||||
/// <summary>The Stream Extension directory entry.</summary>
|
||||
public StreamExtensionDirectoryEntry StreamEntry;
|
||||
|
||||
/// <summary>Valid data length in bytes.</summary>
|
||||
public ulong ValidDataLength;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string ToString() => FileName ?? string.Empty;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
Reference in New Issue
Block a user