From 5e20714d050392d15e7cb21d71e87eab1465eb9b Mon Sep 17 00:00:00 2001 From: Pascal Craponne Date: Fri, 1 Sep 2017 18:52:43 +0200 Subject: [PATCH] Directory reader, a start --- .gitattributes | 2 + ExFat.Core/Buffer/BufferData.cs | 52 ----- ExFat.Core/Buffer/BufferInt64.cs | 19 -- ExFat.Core/Buffer/BufferUInt32.cs | 25 --- ExFat.Core/Buffer/BufferUInt64.cs | 25 --- ExFat.Core/Buffers/Buffer.cs | 108 +++++++++++ .../{Buffer => Buffers}/BufferByteString.cs | 29 ++- ExFat.Core/{Buffer => Buffers}/BufferBytes.cs | 19 +- ExFat.Core/Buffers/BufferUInt16.cs | 28 +++ ExFat.Core/Buffers/BufferUInt32.cs | 28 +++ ExFat.Core/Buffers/BufferUInt64.cs | 28 +++ ExFat.Core/Buffers/BufferUInt8.cs | 28 +++ ExFat.Core/Buffers/BufferWideString.cs | 78 ++++++++ .../{Buffer => Buffers}/BytesExtensions.cs | 2 +- ExFat.Core/Buffers/EnumValueProvider.cs | 32 ++++ ExFat.Core/Buffers/IValueProvider.cs | 13 ++ .../AllocationBitmapExFatDirectoryEntry.cs | 20 ++ ExFat.Core/Entries/ExFatDirectoryEntry.cs | 47 +++++ ExFat.Core/Entries/ExFatDirectoryEntryType.cs | 18 ++ ExFat.Core/Entries/ExFatFileAttributes.cs | 14 ++ .../Entries/ExFatGeneralSecondaryFlags.cs | 11 ++ ExFat.Core/Entries/FatDirectoryEntry.cs | 41 ++++ ExFat.Core/Entries/FileExFatDirectoryEntry.cs | 32 ++++ .../FileNameExtensionExFatDirectoryEntry.cs | 18 ++ .../StreamExtensionExFatDirectoryEntry.cs | 26 +++ .../Entries/UpCaseTableExFatDirectoryEntry.cs | 20 ++ .../Entries/VolumeLabelExFatDirectoryEntry.cs | 28 +++ ExFat.Core/ExFat.Core.csproj | 36 +++- ExFat.Core/ExFatBootSector.cs | 103 +++++++++- ExFat.Core/ExFatDirectory.cs | 22 +++ ExFat.Core/ExFatFS.cs | 79 +++++++- ExFat.Core/IClusterInformationReader.cs | 7 + ExFat.Core/IO/ClusterStream.cs | 177 ++++++++++++++++++ ExFat.Core/IPartitionReader.cs | 11 ++ .../ExFat.DiscUtils.Tests.csproj | 5 + ExFat.DiscUtils.Tests/StructureTests.cs | 21 +++ ExFat.DiscUtils/ExFatFileSystem.cs | 8 +- 37 files changed, 1093 insertions(+), 167 deletions(-) delete mode 100644 ExFat.Core/Buffer/BufferData.cs delete mode 100644 ExFat.Core/Buffer/BufferInt64.cs delete mode 100644 ExFat.Core/Buffer/BufferUInt32.cs delete mode 100644 ExFat.Core/Buffer/BufferUInt64.cs create mode 100644 ExFat.Core/Buffers/Buffer.cs rename ExFat.Core/{Buffer => Buffers}/BufferByteString.cs (53%) rename ExFat.Core/{Buffer => Buffers}/BufferBytes.cs (75%) create mode 100644 ExFat.Core/Buffers/BufferUInt16.cs create mode 100644 ExFat.Core/Buffers/BufferUInt32.cs create mode 100644 ExFat.Core/Buffers/BufferUInt64.cs create mode 100644 ExFat.Core/Buffers/BufferUInt8.cs create mode 100644 ExFat.Core/Buffers/BufferWideString.cs rename ExFat.Core/{Buffer => Buffers}/BytesExtensions.cs (96%) create mode 100644 ExFat.Core/Buffers/EnumValueProvider.cs create mode 100644 ExFat.Core/Buffers/IValueProvider.cs create mode 100644 ExFat.Core/Entries/AllocationBitmapExFatDirectoryEntry.cs create mode 100644 ExFat.Core/Entries/ExFatDirectoryEntry.cs create mode 100644 ExFat.Core/Entries/ExFatDirectoryEntryType.cs create mode 100644 ExFat.Core/Entries/ExFatFileAttributes.cs create mode 100644 ExFat.Core/Entries/ExFatGeneralSecondaryFlags.cs create mode 100644 ExFat.Core/Entries/FatDirectoryEntry.cs create mode 100644 ExFat.Core/Entries/FileExFatDirectoryEntry.cs create mode 100644 ExFat.Core/Entries/FileNameExtensionExFatDirectoryEntry.cs create mode 100644 ExFat.Core/Entries/StreamExtensionExFatDirectoryEntry.cs create mode 100644 ExFat.Core/Entries/UpCaseTableExFatDirectoryEntry.cs create mode 100644 ExFat.Core/Entries/VolumeLabelExFatDirectoryEntry.cs create mode 100644 ExFat.Core/ExFatDirectory.cs create mode 100644 ExFat.Core/IClusterInformationReader.cs create mode 100644 ExFat.Core/IO/ClusterStream.cs create mode 100644 ExFat.Core/IPartitionReader.cs create mode 100644 ExFat.DiscUtils.Tests/StructureTests.cs diff --git a/.gitattributes b/.gitattributes index 08464ec..a6dd704 100644 --- a/.gitattributes +++ b/.gitattributes @@ -2,3 +2,5 @@ *.cs text eol=crlf diff=csharp *.csproj text eol=crlf diff=csharp + +*.gz binary diff --git a/ExFat.Core/Buffer/BufferData.cs b/ExFat.Core/Buffer/BufferData.cs deleted file mode 100644 index 846e06b..0000000 --- a/ExFat.Core/Buffer/BufferData.cs +++ /dev/null @@ -1,52 +0,0 @@ -namespace ExFat.Core.Buffer -{ - using System; - using System.Collections.Generic; - - public abstract class BufferData - { - private readonly byte[] _buffer; - private readonly int _offset; - private readonly int _length; - - protected void SetAt(int index, byte value) - { - if (index < 0 || index >= _length) - throw new ArgumentOutOfRangeException(); - _buffer[_offset + index] = value; - } - - protected byte GetAt(int index) - { - if (index < 0 || index >= _length) - throw new ArgumentOutOfRangeException(); - return _buffer[_offset + index]; - } - - /// - /// Gets the bytes. - /// - /// - protected byte[] GetAll() - { - var bytes = new byte[_length]; - Array.Copy(_buffer, _offset, bytes, 0, _length); - return bytes; - } - - protected void Set(IList bytes) - { - if (bytes.Count > _length) - throw new ArgumentException(nameof(bytes)); - for (int index = 0; index < bytes.Count; index++) - _buffer[_offset + index] = bytes[index]; - } - - protected BufferData(byte[] buffer, int offset, int length) - { - _buffer = buffer; - _offset = offset; - _length = length; - } - } -} \ No newline at end of file diff --git a/ExFat.Core/Buffer/BufferInt64.cs b/ExFat.Core/Buffer/BufferInt64.cs deleted file mode 100644 index 8e0a0e6..0000000 --- a/ExFat.Core/Buffer/BufferInt64.cs +++ /dev/null @@ -1,19 +0,0 @@ -namespace ExFat.Core.Buffer -{ - using System; - using System.Diagnostics; - - [DebuggerDisplay("{" + nameof(Value) + "}")] - public class BufferInt64 : BufferData - { - public Int64 Value - { - get { return BitConverter.ToInt64(GetAll().FromLittleEndian(), 0); } - set { Set(BitConverter.GetBytes(value).ToLittleEndian()); } - } - - public BufferInt64(byte[] buffer, int offset) : base(buffer, offset, sizeof(Int64)) - { - } - } -} diff --git a/ExFat.Core/Buffer/BufferUInt32.cs b/ExFat.Core/Buffer/BufferUInt32.cs deleted file mode 100644 index 2e8e787..0000000 --- a/ExFat.Core/Buffer/BufferUInt32.cs +++ /dev/null @@ -1,25 +0,0 @@ -namespace ExFat.Core.Buffer -{ - using System; - using System.Diagnostics; - - [DebuggerDisplay("{" + nameof(Value) + "}")] - public class BufferUInt32 : BufferData - { - /// - /// Gets or sets the value. - /// - /// - /// The value. - /// - public UInt64 Value - { - get { return BitConverter.ToUInt32(GetAll().FromLittleEndian(), 0); } - set { Set(BitConverter.GetBytes(value).ToLittleEndian()); } - } - - public BufferUInt32(byte[] buffer, int offset) : base(buffer, offset, sizeof(UInt32)) - { - } - } -} diff --git a/ExFat.Core/Buffer/BufferUInt64.cs b/ExFat.Core/Buffer/BufferUInt64.cs deleted file mode 100644 index c6188d0..0000000 --- a/ExFat.Core/Buffer/BufferUInt64.cs +++ /dev/null @@ -1,25 +0,0 @@ -namespace ExFat.Core.Buffer -{ - using System; - using System.Diagnostics; - - [DebuggerDisplay("{" + nameof(Value) + "}")] - public class BufferUInt64 : BufferData - { - /// - /// Gets or sets the value. - /// - /// - /// The value. - /// - public UInt64 Value - { - get { return BitConverter.ToUInt64(GetAll().FromLittleEndian(), 0); } - set { Set(BitConverter.GetBytes(value).ToLittleEndian()); } - } - - public BufferUInt64(byte[] buffer, int offset) : base(buffer, offset, sizeof(UInt64)) - { - } - } -} diff --git a/ExFat.Core/Buffers/Buffer.cs b/ExFat.Core/Buffers/Buffer.cs new file mode 100644 index 0000000..ffc62ef --- /dev/null +++ b/ExFat.Core/Buffers/Buffer.cs @@ -0,0 +1,108 @@ +namespace ExFat.Core.Buffers +{ + using System; + using System.Collections.Generic; + + public class Buffer + { + private readonly int _offset; + private readonly byte[] _bytes; + + /// + /// Gets the length. + /// + /// + /// The length. + /// + public int Length { get; } + + /// + /// Gets or sets the at the specified index. + /// + /// + /// The . + /// + /// The index. + /// + /// + /// index + /// or + /// index + /// + public byte this[int index] + { + get + { + if (index < 0 || index >= Length) + throw new ArgumentOutOfRangeException(nameof(index)); + return _bytes[_offset + index]; + } + set + { + if (index < 0 || index >= Length) + throw new ArgumentOutOfRangeException(nameof(index)); + _bytes[_offset + index] = value; + } + } + + /// + /// Gets the bytes. + /// + /// + public byte[] GetBytes() + { + var bytes = new byte[Length]; + Array.Copy(_bytes, _offset, bytes, 0, Length); + return bytes; + } + + public void Set(IList bytes) + { + if (bytes.Count > Length) + throw new ArgumentException(nameof(bytes)); + for (int index = 0; index < bytes.Count; index++) + _bytes[_offset + index] = bytes[index]; + } + + /// + /// Initializes a new instance of the class given a raw array of bytes. + /// + /// The bytes. + public Buffer(byte[] bytes) + { + _bytes = bytes; + Length = _bytes.Length; + } + + /// + /// Initializes a new instance of the class given another . + /// + /// The buffer. + /// The offset. + /// The length. + /// + /// offset + /// or + /// length + /// + public Buffer(Buffer buffer, int offset, int length) + { + if (offset < 0 || offset >= buffer.Length) + throw new ArgumentOutOfRangeException(nameof(offset)); + if (offset + length > buffer.Length) + throw new ArgumentOutOfRangeException(nameof(length)); + _bytes = buffer._bytes; + _offset = buffer._offset + offset; + Length = length; + } + + /// + /// Initializes a new instance of the class given another . + /// + /// The buffer. + public Buffer(Buffer buffer) + : this(buffer, 0, buffer.Length) + { + } + } +} diff --git a/ExFat.Core/Buffer/BufferByteString.cs b/ExFat.Core/Buffers/BufferByteString.cs similarity index 53% rename from ExFat.Core/Buffer/BufferByteString.cs rename to ExFat.Core/Buffers/BufferByteString.cs index dc867b8..ac02a39 100644 --- a/ExFat.Core/Buffer/BufferByteString.cs +++ b/ExFat.Core/Buffers/BufferByteString.cs @@ -1,18 +1,19 @@ -namespace ExFat.Core.Buffer +namespace ExFat.Core.Buffers { using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Text; - [DebuggerDisplay("{Value}")] - public class BufferByteString : BufferData + [DebuggerDisplay("{" + nameof(Value) + "}")] + public class BufferByteString : IValueProvider { private readonly Encoding _encoding; + private readonly Buffer _buffer; - private IEnumerable GetZero() + private IEnumerable GetZeroBytes() { - foreach (var b in GetAll()) + foreach (var b in _buffer.GetBytes()) { if (b == 0) break; @@ -28,20 +29,28 @@ private IEnumerable GetZero() /// public string Value { - get { return _encoding.GetString(GetZero().ToArray()); } - set { Set(_encoding.GetBytes(value)); } + get { return _encoding.GetString(GetZeroBytes().ToArray()); } + set + { + var stringBytes = _encoding.GetBytes(value); + // first of all, inject bytes + _buffer.Set(stringBytes); + // then pad + for (int index = stringBytes.Length; index < _buffer.Length; index++) + _buffer[index] = 0; + } } /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The buffer. /// The offset. /// The length. /// The encoding (defaults to ASCII). - public BufferByteString(byte[] buffer, int offset, int length, Encoding encoding = null) - : base(buffer, offset, length) + public BufferByteString(Buffer buffer, int offset, int length, Encoding encoding = null) { + _buffer = new Buffer(buffer, offset, length); _encoding = encoding ?? Encoding.ASCII; } } diff --git a/ExFat.Core/Buffer/BufferBytes.cs b/ExFat.Core/Buffers/BufferBytes.cs similarity index 75% rename from ExFat.Core/Buffer/BufferBytes.cs rename to ExFat.Core/Buffers/BufferBytes.cs index 5b6b218..ff50f31 100644 --- a/ExFat.Core/Buffer/BufferBytes.cs +++ b/ExFat.Core/Buffers/BufferBytes.cs @@ -1,4 +1,4 @@ -namespace ExFat.Core.Buffer +namespace ExFat.Core.Buffers { using System; using System.Collections; @@ -9,10 +9,11 @@ /// /// Represents bytes in the buffer /// - /// [DebuggerDisplay("{" + nameof(DebugLiteral) + "}")] - public class BufferBytes : BufferData, IEnumerable + public class BufferBytes : IEnumerable { + private readonly Buffers.Buffer _buffer; + /// /// Gets or sets the at the specified index. /// @@ -25,15 +26,15 @@ public class BufferBytes : BufferData, IEnumerable /// public byte this[int index] { - get { return GetAt(index); } - set { SetAt(index, value); } + get { return _buffer[index]; } + set { _buffer[index] = value; } } private string DebugLiteral { get { - var bytes = GetAll(); + var bytes = _buffer.GetBytes(); var s = string.Join(", ", bytes.Take(10).Select(b => $"0x{b:X2}")); if (bytes.Length > 10) s += " ..."; @@ -47,14 +48,14 @@ private string DebugLiteral /// The buffer. /// The offset. /// The length. - public BufferBytes(byte[] buffer, int offset, int length) - : base(buffer, offset, length) + public BufferBytes(Buffers.Buffer buffer, int offset, int length) { + _buffer = new Buffers.Buffer(buffer, offset, length); } public IEnumerator GetEnumerator() { - return ((IEnumerable)GetAll()).GetEnumerator(); + return ((IEnumerable)_buffer.GetBytes()).GetEnumerator(); } IEnumerator IEnumerable.GetEnumerator() diff --git a/ExFat.Core/Buffers/BufferUInt16.cs b/ExFat.Core/Buffers/BufferUInt16.cs new file mode 100644 index 0000000..dee4534 --- /dev/null +++ b/ExFat.Core/Buffers/BufferUInt16.cs @@ -0,0 +1,28 @@ +namespace ExFat.Core.Buffers +{ + using System; + using System.Diagnostics; + + [DebuggerDisplay("{" + nameof(Value) + "}")] + public class BufferUInt16 : IValueProvider + { + private readonly Buffer _buffer; + + /// + /// Gets or sets the value. + /// + /// + /// The value. + /// + public UInt16 Value + { + get { return BitConverter.ToUInt16(_buffer.GetBytes().FromLittleEndian(), 0); } + set { _buffer.Set(BitConverter.GetBytes(value).ToLittleEndian()); } + } + + public BufferUInt16(Buffers.Buffer buffer, int offset) + { + _buffer = new Buffers.Buffer(buffer, offset, sizeof(UInt16)); + } + } +} diff --git a/ExFat.Core/Buffers/BufferUInt32.cs b/ExFat.Core/Buffers/BufferUInt32.cs new file mode 100644 index 0000000..846d43e --- /dev/null +++ b/ExFat.Core/Buffers/BufferUInt32.cs @@ -0,0 +1,28 @@ +namespace ExFat.Core.Buffers +{ + using System; + using System.Diagnostics; + + [DebuggerDisplay("{" + nameof(Value) + "}")] + public class BufferUInt32 : IValueProvider + { + private readonly Buffer _buffer; + + /// + /// Gets or sets the value. + /// + /// + /// The value. + /// + public UInt32 Value + { + get { return BitConverter.ToUInt32(_buffer.GetBytes().FromLittleEndian(), 0); } + set { _buffer.Set(BitConverter.GetBytes(value).ToLittleEndian()); } + } + + public BufferUInt32(Buffers.Buffer buffer, int offset) + { + _buffer = new Buffers.Buffer(buffer, offset, sizeof(UInt32)); + } + } +} diff --git a/ExFat.Core/Buffers/BufferUInt64.cs b/ExFat.Core/Buffers/BufferUInt64.cs new file mode 100644 index 0000000..453a12d --- /dev/null +++ b/ExFat.Core/Buffers/BufferUInt64.cs @@ -0,0 +1,28 @@ +namespace ExFat.Core.Buffers +{ + using System; + using System.Diagnostics; + + [DebuggerDisplay("{" + nameof(Value) + "}")] + public class BufferUInt64 : IValueProvider + { + private readonly Buffers.Buffer _buffer; + + /// + /// Gets or sets the value. + /// + /// + /// The value. + /// + public UInt64 Value + { + get { return BitConverter.ToUInt64(_buffer.GetBytes().FromLittleEndian(), 0); } + set { _buffer.Set(BitConverter.GetBytes(value).ToLittleEndian()); } + } + + public BufferUInt64(Buffers.Buffer buffer, int offset) + { + _buffer = new Buffers.Buffer(buffer, offset, sizeof(UInt64)); + } + } +} diff --git a/ExFat.Core/Buffers/BufferUInt8.cs b/ExFat.Core/Buffers/BufferUInt8.cs new file mode 100644 index 0000000..c3c2975 --- /dev/null +++ b/ExFat.Core/Buffers/BufferUInt8.cs @@ -0,0 +1,28 @@ +namespace ExFat.Core.Buffers +{ + using System; + using System.Diagnostics; + + [DebuggerDisplay("{" + nameof(Value) + "}")] + public class BufferUInt8 : IValueProvider + { + private readonly Buffer _buffer; + + /// + /// Gets or sets the value. + /// + /// + /// The value. + /// + public Byte Value + { + get { return _buffer[0]; } + set { _buffer[0] = value; } + } + + public BufferUInt8(Buffers.Buffer buffer, int offset) + { + _buffer = new Buffers.Buffer(buffer, offset, 1); + } + } +} diff --git a/ExFat.Core/Buffers/BufferWideString.cs b/ExFat.Core/Buffers/BufferWideString.cs new file mode 100644 index 0000000..67abddd --- /dev/null +++ b/ExFat.Core/Buffers/BufferWideString.cs @@ -0,0 +1,78 @@ +namespace ExFat.Core.Buffers +{ + using System.Collections.Generic; + using System.Diagnostics; + using System.Linq; + + [DebuggerDisplay("{" + nameof(Value) + "}")] + public class BufferWideString : IValueProvider + { + private readonly Buffer _buffer; + + private IEnumerable GetChars() + { + var all = _buffer.GetBytes(); + for (int index = 0; index < all.Length; index += 2) + yield return ToChar(all[index], all[index + 1]); + } + + private IEnumerable GetZeroChars() + { + foreach (var b in GetChars()) + { + if (b == 0) + break; + yield return b; + } + } + + private static char ToChar(byte first, byte second) + { + return (char)(first | second << 8); + } + + private static byte[] ToBytes(char c) + { + return new[] { (byte)(c & 0xFF), (byte)(c >> 8) }; + } + + /// + /// Gets or sets the value. + /// + /// + /// The value. + /// + public string Value + { + get { return new string(GetZeroChars().ToArray()); } + set + { + for (int index = 0; index < _buffer.Length; index++) + { + var t = ToBytes(value[index]); + if (index < value.Length) + { + _buffer[index] = t[0]; + _buffer[index + 1] = t[1]; + } + else + { + _buffer[index] = 0; + _buffer[index + 1] = 0; + } + } + } + } + + /// + /// Initializes a new instance of the class. + /// + /// The buffer. + /// The offset. + /// The length. + public BufferWideString(Buffer buffer, int offset, int charsLength) + { + _buffer = new Buffer(buffer, offset, charsLength * 2); + } + } +} \ No newline at end of file diff --git a/ExFat.Core/Buffer/BytesExtensions.cs b/ExFat.Core/Buffers/BytesExtensions.cs similarity index 96% rename from ExFat.Core/Buffer/BytesExtensions.cs rename to ExFat.Core/Buffers/BytesExtensions.cs index 0581356..8353826 100644 --- a/ExFat.Core/Buffer/BytesExtensions.cs +++ b/ExFat.Core/Buffers/BytesExtensions.cs @@ -1,4 +1,4 @@ -namespace ExFat.Core.Buffer +namespace ExFat.Core.Buffers { using System; using System.Linq; diff --git a/ExFat.Core/Buffers/EnumValueProvider.cs b/ExFat.Core/Buffers/EnumValueProvider.cs new file mode 100644 index 0000000..e8fa8f7 --- /dev/null +++ b/ExFat.Core/Buffers/EnumValueProvider.cs @@ -0,0 +1,32 @@ +namespace ExFat.Core.Buffers +{ + using System.Diagnostics; + + [DebuggerDisplay("{" + nameof(Value) + "}")] + public class EnumValueProvider : IValueProvider + { + private readonly IValueProvider _backingValueProvider; + + /// + /// Gets or sets the value. + /// + /// + /// The value. + /// + public TEnum Value + { + // the casts are a bit dirty here, however they do the job + get { return (TEnum)(object)_backingValueProvider.Value; } + set { _backingValueProvider.Value = (TBacking)(object)value; } + } + + /// + /// Initializes a new instance of the class. + /// + /// The backing value provider. + public EnumValueProvider(IValueProvider backingValueProvider) + { + _backingValueProvider = backingValueProvider; + } + } +} \ No newline at end of file diff --git a/ExFat.Core/Buffers/IValueProvider.cs b/ExFat.Core/Buffers/IValueProvider.cs new file mode 100644 index 0000000..f073186 --- /dev/null +++ b/ExFat.Core/Buffers/IValueProvider.cs @@ -0,0 +1,13 @@ +namespace ExFat.Core.Buffers +{ + public interface IValueProvider + { + /// + /// Gets or sets the value. + /// + /// + /// The value. + /// + TValue Value { get; set; } + } +} \ No newline at end of file diff --git a/ExFat.Core/Entries/AllocationBitmapExFatDirectoryEntry.cs b/ExFat.Core/Entries/AllocationBitmapExFatDirectoryEntry.cs new file mode 100644 index 0000000..227ab6f --- /dev/null +++ b/ExFat.Core/Entries/AllocationBitmapExFatDirectoryEntry.cs @@ -0,0 +1,20 @@ +namespace ExFat.Core.Entries +{ + using System; + using Buffers; + using Buffer = Buffers.Buffer; + + public class AllocationBitmapExFatDirectoryEntry : ExFatDirectoryEntry + { + public IValueProvider BitmapFlags { get; } + public IValueProvider FirstCluster { get; } + public IValueProvider DataLength { get; } + + public AllocationBitmapExFatDirectoryEntry(Buffer buffer) : base(buffer) + { + BitmapFlags = new BufferUInt8(buffer, 1); + FirstCluster = new BufferUInt32(buffer, 20); + DataLength = new BufferUInt64(buffer, 24); + } + } +} \ No newline at end of file diff --git a/ExFat.Core/Entries/ExFatDirectoryEntry.cs b/ExFat.Core/Entries/ExFatDirectoryEntry.cs new file mode 100644 index 0000000..81a325c --- /dev/null +++ b/ExFat.Core/Entries/ExFatDirectoryEntry.cs @@ -0,0 +1,47 @@ +namespace ExFat.Core.Entries +{ + using System; + using Buffers; + using Buffer = Buffers.Buffer; + + public abstract class ExFatDirectoryEntry + { + public IValueProvider EntryType { get; } + + public bool IsDeleted => ((int)EntryType.Value & 0x80) == 0; + + protected ExFatDirectoryEntry(Buffer buffer) + { + EntryType = new EnumValueProvider(new BufferUInt8(buffer, 0)); + } + + /// + /// Creates a given a buffer. + /// + /// The buffer. + /// + /// + public static ExFatDirectoryEntry Create(Buffer buffer) + { + switch ((ExFatDirectoryEntryType)(buffer[0] & 0x7F)) + { + case 0: + return null; + case ExFatDirectoryEntryType.AllocationBitmap: + return new AllocationBitmapExFatDirectoryEntry(buffer); + case ExFatDirectoryEntryType.UpCaseTable: + return new UpCaseTableExFatDirectoryEntry(buffer); + case ExFatDirectoryEntryType.VolumeLabel: + return new VolumeLabelExFatDirectoryEntry(buffer); + case ExFatDirectoryEntryType.File: + return new FileExFatDirectoryEntry(buffer); + case ExFatDirectoryEntryType.Stream: + return new StreamExtensionExFatDirectoryEntry(buffer); + case ExFatDirectoryEntryType.FileName: + return new FileNameExtensionExFatDirectoryEntry(buffer); + default: + throw new ArgumentOutOfRangeException(); + } + } + } +} diff --git a/ExFat.Core/Entries/ExFatDirectoryEntryType.cs b/ExFat.Core/Entries/ExFatDirectoryEntryType.cs new file mode 100644 index 0000000..c8e9d27 --- /dev/null +++ b/ExFat.Core/Entries/ExFatDirectoryEntryType.cs @@ -0,0 +1,18 @@ +namespace ExFat.Core.Entries +{ + using System; + + [Flags] + public enum ExFatDirectoryEntryType : byte + { + Allocated = 0x80, + + AllocationBitmap = 0x01, + UpCaseTable = 0x02, + VolumeLabel = 0x03, + File = 0x05, + + Stream = 0x40, + FileName = 0x41, + } +} \ No newline at end of file diff --git a/ExFat.Core/Entries/ExFatFileAttributes.cs b/ExFat.Core/Entries/ExFatFileAttributes.cs new file mode 100644 index 0000000..e170545 --- /dev/null +++ b/ExFat.Core/Entries/ExFatFileAttributes.cs @@ -0,0 +1,14 @@ +namespace ExFat.Core.Entries +{ + using System; + + [Flags] + public enum ExFatFileAttributes: UInt16 + { + ReadOnly = 0x01, + Hidden = 0x02, + System = 0x04, + Directory = 0x10, + Archive = 0x20, + } +} diff --git a/ExFat.Core/Entries/ExFatGeneralSecondaryFlags.cs b/ExFat.Core/Entries/ExFatGeneralSecondaryFlags.cs new file mode 100644 index 0000000..c7359fc --- /dev/null +++ b/ExFat.Core/Entries/ExFatGeneralSecondaryFlags.cs @@ -0,0 +1,11 @@ +namespace ExFat.Core.Entries +{ + using System; + + [Flags] + public enum ExFatGeneralSecondaryFlags : Byte + { + ClusterAllocationPossible = 0x01, + NoFatChain = 0x02, + } +} \ No newline at end of file diff --git a/ExFat.Core/Entries/FatDirectoryEntry.cs b/ExFat.Core/Entries/FatDirectoryEntry.cs new file mode 100644 index 0000000..78e20cb --- /dev/null +++ b/ExFat.Core/Entries/FatDirectoryEntry.cs @@ -0,0 +1,41 @@ +namespace ExFat.Core.Entries +{ + using System.Text; + using Buffers; + + /// + /// Directory entry in exFAT + /// Thanks to https://events.linuxfoundation.org/images/stories/pdf/lceu11_munegowda_s.pdf + /// + public class FatDirectoryEntry + { + public BufferByteString DirName { get; } + public BufferUInt8 DirAttr { get; } + public BufferUInt8 DirNtRes { get; } + public BufferUInt8 DirCrtTimeTenth { get; } + public BufferUInt16 DirCrtTime { get; } + public BufferUInt16 DirCrtDate { get; } + public BufferUInt16 DirLastAccDate { get; } + public BufferUInt16 DirFstClusHI { get; } + public BufferUInt16 DirWrtTime { get; } + public BufferUInt16 DirWrtDate { get; } + public BufferUInt16 DirFstClusLO { get; } + public BufferUInt32 DirFileSize { get; } + + public FatDirectoryEntry(Buffer buffer) + { + DirName = new BufferByteString(buffer, 0, 11, Encoding.Default); + DirAttr = new BufferUInt8(buffer, 11); + DirNtRes = new BufferUInt8(buffer, 12); + DirCrtTimeTenth = new BufferUInt8(buffer, 13); + DirCrtTime = new BufferUInt16(buffer, 14); + DirCrtDate = new BufferUInt16(buffer, 16); + DirLastAccDate = new BufferUInt16(buffer, 18); + DirFstClusHI = new BufferUInt16(buffer, 20); + DirWrtTime = new BufferUInt16(buffer, 22); + DirWrtDate = new BufferUInt16(buffer, 24); + DirFstClusLO = new BufferUInt16(buffer, 26); + DirFileSize = new BufferUInt32(buffer, 28); + } + } +} diff --git a/ExFat.Core/Entries/FileExFatDirectoryEntry.cs b/ExFat.Core/Entries/FileExFatDirectoryEntry.cs new file mode 100644 index 0000000..0821400 --- /dev/null +++ b/ExFat.Core/Entries/FileExFatDirectoryEntry.cs @@ -0,0 +1,32 @@ +namespace ExFat.Core.Entries +{ + using System; + using Buffers; + using Buffer = Buffers.Buffer; + + public class FileExFatDirectoryEntry : ExFatDirectoryEntry + { + public IValueProvider SecondaryCount { get; } + public IValueProvider SetChecksum { get; } + public IValueProvider FileAttributes { get; } + public IValueProvider CreationTime { get; } + public IValueProvider LastModified { get; } + public IValueProvider LastAccessed { get; } + public IValueProvider Create10msIncrement { get; } + public IValueProvider LastModified10msIncrement { get; } + public IValueProvider LastAccessed10msIncrement { get; } + + public FileExFatDirectoryEntry(Buffer buffer) : base(buffer) + { + SecondaryCount = new BufferUInt8(buffer, 1); + SetChecksum = new BufferUInt16(buffer, 2); + FileAttributes = new EnumValueProvider(new BufferUInt16(buffer, 4)); + CreationTime = new BufferUInt16(buffer, 8); + LastModified = new BufferUInt16(buffer, 12); + LastAccessed = new BufferUInt16(buffer, 16); + Create10msIncrement = new BufferUInt8(buffer, 20); + LastModified10msIncrement = new BufferUInt8(buffer, 21); + LastAccessed10msIncrement = new BufferUInt8(buffer, 22); + } + } +} \ No newline at end of file diff --git a/ExFat.Core/Entries/FileNameExtensionExFatDirectoryEntry.cs b/ExFat.Core/Entries/FileNameExtensionExFatDirectoryEntry.cs new file mode 100644 index 0000000..5fe3e2d --- /dev/null +++ b/ExFat.Core/Entries/FileNameExtensionExFatDirectoryEntry.cs @@ -0,0 +1,18 @@ +namespace ExFat.Core.Entries +{ + using System; + using Buffers; + using Buffer = Buffers.Buffer; + + public class FileNameExtensionExFatDirectoryEntry : ExFatDirectoryEntry + { + public IValueProvider GeneralSecondaryFlags { get; } + public IValueProvider FileName { get; } + + public FileNameExtensionExFatDirectoryEntry(Buffer buffer) : base(buffer) + { + GeneralSecondaryFlags = new EnumValueProvider(new BufferUInt8(buffer, 1)); + FileName = new BufferWideString(buffer, 2, 15); + } + } +} \ No newline at end of file diff --git a/ExFat.Core/Entries/StreamExtensionExFatDirectoryEntry.cs b/ExFat.Core/Entries/StreamExtensionExFatDirectoryEntry.cs new file mode 100644 index 0000000..0b83335 --- /dev/null +++ b/ExFat.Core/Entries/StreamExtensionExFatDirectoryEntry.cs @@ -0,0 +1,26 @@ +namespace ExFat.Core.Entries +{ + using System; + using Buffers; + using Buffer = Buffers.Buffer; + + public class StreamExtensionExFatDirectoryEntry : ExFatDirectoryEntry + { + public IValueProvider GeneralSecondaryFlags { get; } + public IValueProvider NameLength { get; } + public IValueProvider NameHash { get; } + public IValueProvider ValidDataLength { get; } + public IValueProvider FirstCluster { get; } + public IValueProvider DataLength { get; } + + public StreamExtensionExFatDirectoryEntry(Buffer buffer) : base(buffer) + { + GeneralSecondaryFlags = new EnumValueProvider(new BufferUInt8(buffer, 1)); + NameLength = new BufferUInt8(buffer, 3); + NameHash = new BufferUInt16(buffer, 4); + ValidDataLength = new BufferUInt64(buffer, 8); + FirstCluster = new BufferUInt32(buffer, 20); + DataLength = new BufferUInt64(buffer, 24); + } + } +} \ No newline at end of file diff --git a/ExFat.Core/Entries/UpCaseTableExFatDirectoryEntry.cs b/ExFat.Core/Entries/UpCaseTableExFatDirectoryEntry.cs new file mode 100644 index 0000000..b3af0e2 --- /dev/null +++ b/ExFat.Core/Entries/UpCaseTableExFatDirectoryEntry.cs @@ -0,0 +1,20 @@ +namespace ExFat.Core.Entries +{ + using System; + using Buffers; + using Buffer = Buffers.Buffer; + + public class UpCaseTableExFatDirectoryEntry : ExFatDirectoryEntry + { + public IValueProvider TableChecksum { get; } + public IValueProvider FirstCluster { get; } + public IValueProvider DataLength { get; } + + public UpCaseTableExFatDirectoryEntry(Buffer buffer) : base(buffer) + { + TableChecksum = new BufferUInt32(buffer, 4); + FirstCluster = new BufferUInt32(buffer, 20); + DataLength = new BufferUInt64(buffer, 24); + } + } +} diff --git a/ExFat.Core/Entries/VolumeLabelExFatDirectoryEntry.cs b/ExFat.Core/Entries/VolumeLabelExFatDirectoryEntry.cs new file mode 100644 index 0000000..a2fcd94 --- /dev/null +++ b/ExFat.Core/Entries/VolumeLabelExFatDirectoryEntry.cs @@ -0,0 +1,28 @@ +namespace ExFat.Core.Entries +{ + using System; + using Buffers; + using Buffer = Buffers.Buffer; + + public class VolumeLabelExFatDirectoryEntry : ExFatDirectoryEntry + { + public IValueProvider CharacterCount { get; } + public IValueProvider AllVolumeLabel { get; } + + public string VolumeLabel + { + get { return AllVolumeLabel.Value.Substring(0, CharacterCount.Value); } + set + { + CharacterCount.Value = (byte)value.Length; + AllVolumeLabel.Value = value; + } + } + + public VolumeLabelExFatDirectoryEntry(Buffer buffer) : base(buffer) + { + CharacterCount = new BufferUInt8(buffer, 1); + AllVolumeLabel = new BufferWideString(buffer, 2, 11); + } + } +} diff --git a/ExFat.Core/ExFat.Core.csproj b/ExFat.Core/ExFat.Core.csproj index 59a3d35..1af924b 100644 --- a/ExFat.Core/ExFat.Core.csproj +++ b/ExFat.Core/ExFat.Core.csproj @@ -33,23 +33,39 @@ - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/ExFat.Core/ExFatBootSector.cs b/ExFat.Core/ExFatBootSector.cs index a9f4048..8e00c74 100644 --- a/ExFat.Core/ExFatBootSector.cs +++ b/ExFat.Core/ExFatBootSector.cs @@ -2,21 +2,97 @@ { using System.IO; using System.Linq; - using System.Runtime.InteropServices; - using Buffer; + using Buffers; public class ExFatBootSector { public static readonly byte[] DefaultMarker = new byte[] { 0xEB, 0x76, 0x90 }; public const string ExFatFileSystemName = "EXFAT "; - private byte[] _bytes; + private readonly byte[] _bytes; public BufferBytes Marker { get; } public BufferByteString FileSystemName { get; } + + /// + /// Total number of Sectors. + /// + /// + /// The length of the volume. + /// public BufferUInt64 VolumeLength { get; } + /// + /// Sector address of 1st FAT. + /// + /// + /// The fat offset. + /// public BufferUInt32 FatOffset { get; } + /// + /// Size of FAT in sectors. + /// + /// + /// The length of the fat. + /// public BufferUInt32 FatLength { get; } + /// + /// Starting sector of cluster heap + /// + /// + /// The cluster offset. + /// + public BufferUInt32 ClusterOffset { get; } + /// + /// Number of clusters. + /// + /// + /// The cluster count. + /// + public BufferUInt32 ClusterCount { get; } + /// + /// First cluster of root directory. + /// + /// + /// The root directory. + /// + public BufferUInt32 RootDirectory { get; } + /// + /// Gets the volume flags. + /// Bit 0 – Active FAT + /// 0 – 1st , 1 – 2nd + /// Bit 1 – Volume Dirty + /// 0 – Clean, 1- dirty + /// Bits 2 & 3 – Media failure + /// 0 – No failures, 1 – failures reported + /// + /// + /// The volume flags. + /// + public BufferUInt16 VolumeFlags { get; } + /// + /// This is power of 2; Minimal value is 9; 2^9=512 bytes + /// Bytes and maximum 2^12=4096 Bytes + /// + /// + /// The bytes per sector. + /// + public BufferUInt8 BytesPerSector { get; } + /// + /// This is power of 2; Minimal value is 1; 2^0=1 + /// sector (512 Bytes) and maximum 32 MB cluster + /// size in bytes + /// + /// + /// The sectors per cluster. + /// + public BufferUInt8 SectorsPerCluster { get; } + /// + /// Either 1 or 2; if TexFAT is supported then it will be 2 + /// + /// + /// The number of fats. + /// + public BufferUInt8 NumberOfFats { get; } /// /// Returns true if this boot sector is valid (better check this after reading it). @@ -26,14 +102,25 @@ public class ExFatBootSector /// public bool IsValid => Marker.SequenceEqual(DefaultMarker) && FileSystemName.Value == ExFatFileSystemName; + /// + /// Initializes a new instance of the class. + /// public ExFatBootSector() { _bytes = new byte[512]; - Marker = new BufferBytes(_bytes, 0, 3); - FileSystemName = new BufferByteString(_bytes, 3, 8); - VolumeLength = new BufferUInt64(_bytes, 72); - FatOffset = new BufferUInt32(_bytes, 80); - FatLength = new BufferUInt32(_bytes, 84); + var buffer = new Buffers.Buffer(_bytes); + Marker = new BufferBytes(buffer, 0, 3); + FileSystemName = new BufferByteString(buffer, 3, 8); + VolumeLength = new BufferUInt64(buffer, 72); + FatOffset = new BufferUInt32(buffer, 80); + FatLength = new BufferUInt32(buffer, 84); + ClusterOffset = new BufferUInt32(buffer, 88); + ClusterCount = new BufferUInt32(buffer, 92); + RootDirectory = new BufferUInt32(buffer, 96); + VolumeFlags = new BufferUInt16(buffer, 106); + BytesPerSector = new BufferUInt8(buffer, 108); + SectorsPerCluster = new BufferUInt8(buffer, 109); + NumberOfFats = new BufferUInt8(buffer, 110); } public void Read(Stream stream) diff --git a/ExFat.Core/ExFatDirectory.cs b/ExFat.Core/ExFatDirectory.cs new file mode 100644 index 0000000..82fe4bc --- /dev/null +++ b/ExFat.Core/ExFatDirectory.cs @@ -0,0 +1,22 @@ +namespace ExFat.Core +{ + using System.IO; + using Buffers; + using Entries; + + public class ExFatDirectory + { + public ExFatDirectory(Stream directoryStream) + { + for (; ; ) + { + var entryBytes = new byte[32]; + if (directoryStream.Read(entryBytes, 0, entryBytes.Length) != 32) + break; + var directoryEntry = ExFatDirectoryEntry.Create(new Buffer(entryBytes)); + if (directoryEntry == null) + break; + } + } + } +} diff --git a/ExFat.Core/ExFatFS.cs b/ExFat.Core/ExFatFS.cs index ca60a16..4199a97 100644 --- a/ExFat.Core/ExFatFS.cs +++ b/ExFat.Core/ExFatFS.cs @@ -1,28 +1,95 @@ namespace ExFat.Core { + using System; using System.IO; - using System.Runtime.InteropServices; /// /// The ExFAT filesystem. /// The class is a quite low-level manipulator /// TODO: come up with a better name :) /// - public class ExFatFS + public class ExFatFS : IClusterInformationReader, IPartitionReader { - private readonly Stream _partitionStream; + public Stream PartitionStream { get; } + public ExFatBootSector BootSector { get; } + + public long SectorsPerCluster => 1 << BootSector.SectorsPerCluster.Value; + public int BytesPerSector => 1 << BootSector.BytesPerSector.Value; + + public int BytesPerCluster => 1 << (BootSector.BytesPerSector.Value + BootSector.BytesPerSector.Value); public ExFatFS(Stream partitionStream) { - _partitionStream = partitionStream; + PartitionStream = partitionStream; + BootSector = ReadBootSector(PartitionStream); } - public ExFatBootSector ReadBootSector(Stream partitionStream) + public static ExFatBootSector ReadBootSector(Stream partitionStream) { partitionStream.Seek(0, SeekOrigin.Begin); var bootSector = new ExFatBootSector(); bootSector.Read(partitionStream); return bootSector; } + + public long GetClusterOffset(long clusterIndex) + { + return (BootSector.ClusterOffset.Value + (clusterIndex - 2) * SectorsPerCluster) * BytesPerSector; + } + + public void SeekCluster(long clusterIndex) + { + PartitionStream.Seek(GetClusterOffset(clusterIndex), SeekOrigin.Begin); + } + + public long GetSectorOffset(long sectorIndex) + { + return sectorIndex * BytesPerSector; + } + + public void SeekSector(long sectorIndex) + { + PartitionStream.Seek(GetSectorOffset(sectorIndex), SeekOrigin.Begin); + } + + private long _fatPageIndex = -1; + private byte[] _fatPage; + private const int SectorsPerFatPage = 1; + private int FatPageSize => BytesPerSector * SectorsPerFatPage; + private int ClustersPerFatPage => FatPageSize / sizeof(Int32); + + private byte[] GetFatPage(long cluster) + { + if (_fatPage == null) + _fatPage = new byte[FatPageSize]; + + var fatPageIndex = cluster / ClustersPerFatPage; + if (fatPageIndex != _fatPageIndex) + { + ReadSectors(BootSector.FatOffset.Value + fatPageIndex * SectorsPerFatPage, _fatPage, SectorsPerFatPage); + _fatPageIndex = fatPageIndex; + } + return _fatPage; + } + + public long GetNext(long cluster) + { + // TODO: optimize... A lot! + var fatPage = GetFatPage(cluster); + var clusterIndex = cluster % ClustersPerFatPage; + return BitConverter.ToInt32(fatPage, (int)clusterIndex * sizeof(Int32)); + } + + public void ReadCluster(long cluster, byte[] clusterBuffer) + { + SeekCluster(cluster); + PartitionStream.Read(clusterBuffer, 0, BytesPerCluster); + } + + public void ReadSectors(long sector, byte[] sectorBuffer, int sectorCount) + { + SeekSector(sector); + PartitionStream.Read(sectorBuffer, 0, BytesPerSector * sectorCount); + } } -} \ No newline at end of file +} diff --git a/ExFat.Core/IClusterInformationReader.cs b/ExFat.Core/IClusterInformationReader.cs new file mode 100644 index 0000000..dcf4403 --- /dev/null +++ b/ExFat.Core/IClusterInformationReader.cs @@ -0,0 +1,7 @@ +namespace ExFat.Core +{ +public interface IClusterInformationReader +{ + long GetNext(long cluster); +} +} \ No newline at end of file diff --git a/ExFat.Core/IO/ClusterStream.cs b/ExFat.Core/IO/ClusterStream.cs new file mode 100644 index 0000000..feb9683 --- /dev/null +++ b/ExFat.Core/IO/ClusterStream.cs @@ -0,0 +1,177 @@ +namespace ExFat.Core.IO +{ + using System; + using System.IO; + public class ClusterStream : Stream + { + private readonly IClusterInformationReader _clusterInformationReader; + private readonly IPartitionReader _partitionReader; + private readonly long _startCluster; + private readonly long? _length; + private long _position; + private byte[] _currentClusterData; + private long _currentClusterDataIndex = -1; + private long CurrentClusterIndex => _position / _partitionReader.BytesPerCluster; + private long _currentCluster; + + private int CurrentClusterOffset => (int)_position % _partitionReader.BytesPerCluster; + + public override bool CanRead => true; + public override bool CanSeek => _length.HasValue; + public override bool CanWrite => false; + + /// + /// Gets the length in bytes of the stream. + /// + /// + public override long Length + { + get + { + if (!_length.HasValue) + throw new NotSupportedException(); + return _length.Value; + } + } + + /// + /// Gets or sets the position within the current stream. + /// + public override long Position + { + get { return Seek(0, SeekOrigin.Current); } + set { Seek(value, SeekOrigin.Begin); } + } + + /// + /// Initializes a new instance of the class. + /// + /// The cluster information reader. + /// The partition reader. + /// The start cluster. + /// The length. + public ClusterStream(IClusterInformationReader clusterInformationReader, IPartitionReader partitionReader, long startCluster, long? length) + { + _clusterInformationReader = clusterInformationReader; + _partitionReader = partitionReader; + _startCluster = startCluster; + _length = length; + + _position = 0; + _currentCluster = _startCluster; + } + + public override void Flush() + { + } + + /// Sets the position within the current stream. + /// A byte offset relative to the parameter. + /// A value of type indicating the reference point used to obtain the new position. + /// The new position within the current stream. + /// An I/O error occurs. + /// The stream does not support seeking, such as if the stream is constructed from a pipe or console output. + /// Methods were called after the stream was closed. + /// + public override long Seek(long offset, SeekOrigin origin) + { + if (!_length.HasValue) + throw new InvalidOperationException(); + + // simplify complex seeks + if (origin == SeekOrigin.End) + return Seek(_length.Value + offset, SeekOrigin.Begin); + if (origin == SeekOrigin.Current) + return Seek(_position + offset, SeekOrigin.Begin); + + if (offset < 0) + offset = 0; + if (offset > _length) + offset = _length.Value; + + // if the seek is in the same cluster, keep here + var clusterIndex = offset / _partitionReader.BytesPerCluster; + if (clusterIndex == CurrentClusterIndex) + { + _position = offset; + return _position; + } + + // seek to begin is also special, we clear it all + if (offset == 0) + { + _position = 0; + _currentCluster = _startCluster; + return 0; + } + + // if we need to rewind, get forward from scratch + if (offset < _position) + { + Seek(0, SeekOrigin.Begin); + return Seek(offset, SeekOrigin.Begin); + } + + // now, it's only a forward seek + for (var index = CurrentClusterIndex; index < clusterIndex; index++) + _currentCluster = _clusterInformationReader.GetNext(_currentCluster); + _position = offset; + return offset; + } + + public override void SetLength(long value) + { + throw new System.NotImplementedException(); + } + + private byte[] GetCurrentCluster() + { + // lazy initialization of cluster index + if (_currentClusterData == null) + _currentClusterData = new byte[_partitionReader.BytesPerCluster]; + + // if the current requested cluster is not the one we have, read it + if (CurrentClusterIndex != _currentClusterDataIndex) + { + _partitionReader.ReadCluster(_currentCluster, _currentClusterData); + _currentClusterDataIndex = CurrentClusterIndex; + } + + return _currentClusterData; + } + + public override int Read(byte[] buffer, int offset, int count) + { + var totalRead = 0; + while (count > 0) + { + if (_currentCluster < 0) + break; + // what remaings in current cluster + var remainingInCluster = _partitionReader.BytesPerCluster - CurrentClusterOffset; + var toRead = Math.Min(remainingInCluster, count); + if (_length.HasValue) + { + var leftInFile = _length.Value - _position; + if (leftInFile == 0) + break; + if (toRead > leftInFile) + toRead = (int)leftInFile; + } + Buffer.BlockCopy(GetCurrentCluster(), CurrentClusterOffset, buffer, offset, toRead); + _position += toRead; + if (_position >= (CurrentClusterIndex + 1) * _partitionReader.BytesPerCluster) + _currentCluster = _clusterInformationReader.GetNext(_currentCluster); + offset += toRead; + count -= toRead; + totalRead += toRead; + } + return totalRead; + } + + public override void Write(byte[] buffer, int offset, int count) + { + throw new System.NotImplementedException(); + } + } +} \ No newline at end of file diff --git a/ExFat.Core/IPartitionReader.cs b/ExFat.Core/IPartitionReader.cs new file mode 100644 index 0000000..54314a4 --- /dev/null +++ b/ExFat.Core/IPartitionReader.cs @@ -0,0 +1,11 @@ +namespace ExFat.Core +{ + public interface IPartitionReader + { + int BytesPerCluster { get; } + int BytesPerSector { get; } + + void ReadCluster(long cluster, byte[] clusterBuffer); + void ReadSectors(long sector, byte[] sectorBuffer, int sectorCount); + } +} diff --git a/ExFat.DiscUtils.Tests/ExFat.DiscUtils.Tests.csproj b/ExFat.DiscUtils.Tests/ExFat.DiscUtils.Tests.csproj index c1ee059..4c5a255 100644 --- a/ExFat.DiscUtils.Tests/ExFat.DiscUtils.Tests.csproj +++ b/ExFat.DiscUtils.Tests/ExFat.DiscUtils.Tests.csproj @@ -41,6 +41,7 @@ + @@ -52,6 +53,10 @@ + + {694BBB9A-204F-41A3-927E-2BCB8AA9DADE} + ExFat.Core + {D3FE0435-345E-4BDE-A22E-AD923638275C} ExFat.DiscUtils diff --git a/ExFat.DiscUtils.Tests/StructureTests.cs b/ExFat.DiscUtils.Tests/StructureTests.cs new file mode 100644 index 0000000..926e30f --- /dev/null +++ b/ExFat.DiscUtils.Tests/StructureTests.cs @@ -0,0 +1,21 @@ +namespace ExFat.DiscUtils.Tests +{ + using Core; + using Core.IO; + using Microsoft.VisualStudio.TestTools.UnitTesting; + + [TestClass] + public class StructureTests + { + [TestMethod] + public void ValidVolume() + { + using (var testEnvironment = new TestEnvironment()) + { + var fs = new ExFatFS(testEnvironment.PartitionStream); + var rootDirectoryStream = new ClusterStream(fs, fs, fs.BootSector.RootDirectory.Value, null); + var d = new ExFatDirectory(rootDirectoryStream); + } + } + } +} diff --git a/ExFat.DiscUtils/ExFatFileSystem.cs b/ExFat.DiscUtils/ExFatFileSystem.cs index d43a96c..66c0b08 100644 --- a/ExFat.DiscUtils/ExFatFileSystem.cs +++ b/ExFat.DiscUtils/ExFatFileSystem.cs @@ -10,8 +10,9 @@ public class ExFatFileSystem : DiscFileSystem { private Stream _partitionStream; private ExFatFS _fs; + private ExFatBootSector _bootSector; - public override string FriendlyName { get; } + public override string FriendlyName => "Microsoft exFAT"; /// /// Gets a value indicating whether the file system is read-only or read-write. @@ -24,13 +25,16 @@ public class ExFatFileSystem : DiscFileSystem public ExFatFileSystem(Stream partitionStream) { _fs = new ExFatFS(partitionStream); + _bootSector = ExFatFS.ReadBootSector(partitionStream); + if (!_bootSector.IsValid) + throw new InvalidOperationException("Given stream is not exFAT volume"); _partitionStream = partitionStream; } public static bool Detect(Stream partitionStream) { var fs = new ExFatFS(partitionStream); - var bootSector = fs.ReadBootSector(partitionStream); + var bootSector = ExFatFS.ReadBootSector(partitionStream); return bootSector.IsValid; }